Compare commits

..
Author SHA1 Message Date
Plastered_Crab 35b2b33eb4 mega keystone? more like iron helmet 2026-09-14 23:03:03 -07:00
Plastered_Crab 9900fbf3d6 Get back to parity with main 2026-09-14 15:38:52 -07:00
Plastered_Crab 1e8d012371 Add in all non ZA and MD mega stones 2026-09-14 14:20:49 -07:00
Plastered_Crab 82ef84d82b Merge branch 'main' into mega-space-program
# Conflicts:
#	common/src/main/java/com/cobblemon/mod/common/mixin/ClientPacketListenerMixin.java
#	common/src/main/kotlin/com/cobblemon/mod/common/api/molang/function/EvolutionMoLangFunctions.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/molang/function/PokemonMoLangFunctions.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/tms/TechnicalMachines.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/block/entity/PokeSnackBlockEntity.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/block/tmmachine/TMMachineMenu.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/client/gui/summary/Summary.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/client/render/models/blockbench/pokemon/gen1/RattataAlolanModel.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/client/render/models/blockbench/pokemon/gen1/RattataModel.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/net/messages/client/pokemon/update/evolution/ClientboundSeatAssignmentPacket.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/pokemon/requirements/StatCompareRequirement.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/pokemon/requirements/StatEqualRequirement.kt
#	common/src/main/resources/data/cobblemon/species/generation5/panpour.json
#	common/src/main/resources/data/cobblemon/species/generation8/duraludon.json
2026-09-12 16:15:46 -07:00
Plastered_Crab c0154cd82d Add Mega Shards and Mega Stones as well as simple "mega bond" increasing system 2026-09-12 16:02:03 -07:00
Plastered_Crab 1c9c833a49 Add Mega Shards and Mega Stones as well as simple "mega bond" increasing system 2026-09-12 15:52:50 -07:00
Plastered_Crab 901e3e09e1 Add Mega Shards and Mega Stones as well as simple "mega bond" increasing system 2026-09-12 14:44:44 -07:00
Plastered_Crab 94c159b42b Meteors land where they should and test spawning minior / dropping items 2026-08-03 20:17:00 -07:00
Hiroku 3c80a61011 Started on implementing falling stars, they hit the ground weirdly 2026-07-24 14:25:13 +10:00
Paul e29087c805 Added molang TransformationRequirement 2026-06-12 15:18:51 +10:00
Paul e850253d88 Implemented action-effect driven temporary form reversion and custom Schedulables in action effects 2026-06-12 14:42:14 +10:00
Paul bbb692f521 Moved transformation progress to a common location and reduced the Transformation tree complexity. Progress works for forms 2026-06-12 12:22:15 +10:00
Paul da1d67bbe6 Clean up parentForm mechanism in FormData 2026-06-08 09:01:24 +10:00
Paul 792834f219 Oh yeah compilation should succeed 2026-06-04 18:27:37 +10:00
Paul 94b31cd658 Remove comment 2026-06-04 18:08:52 +10:00
Paul 19364f39c6 Renaming and restructuring of the actual starting of transformations, added FormEvents 2026-06-04 14:45:13 +10:00
Paul 2fc8f6b9df Oi you got a loicense for that television bruv 2026-05-31 22:15:06 +10:00
Paul 9089ff3bbb Why is the species schema JSON malformed 2026-05-31 21:47:02 +10:00
Paul 90f581051c Simplifications and straightening of transformation controller class tree and codecs 2026-05-31 21:43:06 +10:00
Paul 061bdcc478 Action effect improvements for form transition effects, some cleanup of TemporaryForm reverting 2026-05-31 14:52:12 +10:00
Paul 6811394f71 Compilation fixes and merge conflict resolutions 2026-05-30 13:08:20 +10:00
Paul 1235a70323 Conflict resolve maybe 2026-05-26 21:15:00 +10:00
Segfault Guy d94952347f Portrait updates and form effects. 2025-04-30 00:30:06 -04:00
Segfault Guy 7fe6141640 form20 blaze it 2025-04-20 23:41:12 -04:00
Segfault Guy 4b1653ca6c Merge remote-tracking branch 'origin/main' into form_changes 2025-04-20 20:53:54 -04:00
Segfault Guy 7265ab57b9 Apply species schema changes to evolutions. 2025-04-11 01:47:41 -04:00
Segfault Guy 27e3a95b06 cleanup test changes 2025-04-11 01:17:29 -04:00
Segfault Guy 38de3c6267 In-battle form changes and PokemonProperties form completion. 2025-04-09 00:18:10 -04:00
Segfault Guy 376e5ac906 Merge branch 'main' into form_changes 2025-03-18 22:26:00 -04:00
Segfault Guy b225283dcc Toggleable, deferrable, and wild forms. 2025-03-12 23:00:29 -04:00
Segfault Guy b90f852a71 Clear invalid forms on species change. 2025-02-24 20:12:23 -05:00
Segfault Guy fdf7a9703a coalesce temp and perm forms under same property and instead deserialize by custom adapter instead of having unique properties. 2025-02-23 23:21:43 -05:00
Segfault Guy bb41f28342 Merge remote-tracking branch 'origin/main' into form_changes 2025-02-18 22:33:23 -05:00
Segfault Guy c5ee7ebe7a Fix ability registration for forms. 2025-02-11 21:40:35 -05:00
Segfault Guy f6ae10e1d9 It's about f(orm)cking time. 2025-02-03 23:38:53 -05:00
Segfault GuyandSegfault Guy b2070b53fb Changing form... changes form? Wow! 2024-12-03 23:41:17 -05:00
Segfault GuyandSegfault Guy 7fb873dc2c Showdown Form and Species registration rework. 2024-12-03 23:10:25 -05:00
Segfault GuyandSegfault Guy 641f529de3 Better Form and Evolution triggering. 2024-12-03 19:12:41 -05:00
Segfault GuyandSegfault Guy db9fb4fefa Refactor Evolution and add Transformation to expand application of Evolution events and requirements. 2024-12-02 23:13:57 -05:00
6021 changed files with 18375 additions and 125907 deletions
-3
View File
@@ -157,9 +157,6 @@ build:
paths:
- neoforge/build/libs
- fabric/build/libs
exclude:
- neoforge/build/libs/*-dev-shadow.jar
- fabric/build/libs/*-dev-shadow.jar
publish:
stage: deploy
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

-11
View File
@@ -1,11 +0,0 @@
### 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
-8
View File
@@ -1,8 +0,0 @@
### Additions
- Added Pokémon breeding for Pokémon left in pasture blocks.
- Added nest blocks that Pokémon can lay eggs in.
### Developer
- Spawning Influences now have the context of what the other buckets are when adjusting bucket weights. This will break existing influences that do bucket weight adjustment.
- Renamed heaps of things in the spawning system to make more sense.
+34 -94
View File
@@ -1,101 +1,41 @@
# Changelog
## [1.8.2 (MONTH xth, 2026)](#1-8-2)
### Additions
- The following items can now be placed on the ground as blocks:
- King's Rock, Galarica Cuff, Unremarkable Teacup, Masterpiece Teacup, Chipped Pot, Cracked Pot, Sachet, Metal Powder, Quick Powder, Bright Powder, Icy Rock, Heat Rock, Smooth Rock, Damp Rock, Destiny Knot, Luminous Moss, Never-Melt Ice, Wide Lens, Scope Lens, and Zoom Lens.
- Iron Ball, Metal Coat, Throat Spray, HP Up, Protein, Iron, Carbos, Calcium, Zinc, PP Up, PP Max, Upgrade, Dubious Disc, and Loaded Dice.
- The following items can now be placed on various surfaces:
- Galarica Wreath, Magnet, Flame Orb, Life Orb, Toxic Orb, Light Ball, and Smoke Ball.
- The following items can now be placed as blocks and have additional functionality:
- Ring Target, Metronome, and Eject Button.
- The following items can now be worn by Players and Pokémon:
- Galarica Wreath, Cleanse Tag, Spell Tag, Sharp Beak, Covert Cloak, Shell Helmet, and Never-Melt Ice.
- Wearable items can now be equipped by a player by holding it and right-clicking (or rebound use/interact)
- Wearable items that can be placed down are exempt from this behaviour.
### Pokémon Added
#### Generation 4
- Snover
- Abomasnow
- Burmy
- Wormadam
- Mothim
#### Generation 5
- Tympole
- Palpitoad
- Seismitoad
#### Generation 6
- Helioptile
- Heliolisk
#### Generation 7
- Grubbin
- Charjabug
- Vikavolt
#### Generation 9
- Greavard
- Houndstone
### Model updates for the following Pokémon
- Cacnea
- Cacturne
- Binacle
- Barbaracle
- Glimmet
- Glimmora
- Deerling
- Sawsbuck (All forms)
- Absol
## [1.8.1 (September 13th, 2026)](#1-8-1)
### Animation updates for the following Pokémon
- Cacnea
- Cacturne
### Changes
- Updated various recipes to display in Recipe Groups within the Recipe Book
- Updated most recipes to display more logically/consistently within the Recipe Book's categories
- Make ride rotations less jittery in multiplayer.
- Changes tm shelf discs to use textures instead of tinting and overlays to line up with item display
- Blank TM can now be inserted into the Disc Shelf
- Flapple, Appletun, Dipplin, Hydrapple (placeholder animations only).
### Fixes
- Correctly grants "They're Minerals!" advancement when planting a type gem
- Type Gem Blocks now have advancements to give you the recipe in the recipe book (the data for the advancements were inside the recipes somehow??)
- Some other recipes without advancements have also been corrected
- Corrected the TM Recipes for Charge Beam and Payday
- Type Gem blocks now properly generate clusters off of the block.
- Starter Screen Categories will no longer stop rendering if they are bigger than the viewport, and you are not viewing top or bottom edges
- Random Starter option now properly handled in height calculation, so now it won't run off into other categories or be un-accessible due to scroll limits if it causes a new row
- Mochi and Feathers no longer drop glass bottles on use
- Fixed a rare set of concurrency bugs related to reloading packs on NeoForge; fixes a ModernFix issue
- Removed unnecessary conditions for Mimikyu's standing pose. Hopefully fixes the rare instance of it using its shoulder animations during battle in multiplayer scenarios.
- Fixed Rockruff line not blinking
- Fixed Flygon sometimes using its running animation instead of walking
- Fixed Bramblin always sleeping outside of battle
- Fixed Dragonair using its battle idle as a normal idle
- Fixed Lycanroc's head movement
- Added head movement to Archaludon
- Fixed Duraludon line not having any sleep animations
- Fixed Diglett line not having their faint animations
- Fixed Kingambit moving its legs while walking
- Various Structure fixes
- Fixed shiny pokemon under certain circumstances forgetting taught TMs
### Developer
- Fixed wrong order of operations in the code flow prior to the `FOSSIL_REVIVED` event causing pokemon to appear without entity link
- Fixed TestPartySlot command not correctly comparing the provided spec with the target Pokémon
### Molang & Datapacks
- A new `negate` variant for evolution requirements has been added, which will negate (invert) the provided inner condition.
- Added Molang functions for individual Pokédex ids, wherein a subdex is passed in as a string to `dex_caught_count` etc.
### Particles
#### New Move Animations
- Fixed a potential world generation stall caused by Type Gem clusters.
- Fixed Pokémon on shoulders not animating if they are visibly holding a held item.
- Fixed NPCs getting their own TM progress file.
- Fixed odd NeoForge crash due to a dependency being outdated. It will now correctly flag the dependency for being outdated.
- Fixed the TM Machine refusing move selections that the `unlockAllMoveDexMovesByDefault` config had made visible, leaving the move slot blank and the craft button unresponsive.
- Fixed the Beast Ball not applying its 0.1× penalty to Pokémon that are not Ultra Beasts.
- Fixed the Moon Ball reading the world's total elapsed time instead of the time of day, so its bonus applied during the day instead.
- Fixed the Love Ball never awarding its 2.5× opposite gender bonus, and matching species by display name instead of identifier.
- Fixed the Timer Ball being weaker than a regular Poké Ball on early turns and only reaching 4× on turn 14 instead of turn 10.
- Fixed the Luxury Ball only doubling Friendship gains of 1, leaving level up and berry gains unaffected.
- Fixed Rufflet, Braviary, and Hisuian Braviary flooding logs with errors about missing sound events.
- Fixed Pokémon property typos in Pangoro and Dedenne herd spawn files.
- Fixed unintentional watermelon cosmetic for Hisuian Electrode.
- Fixed Blastoise having an unintentional passenger seat.
- Fixed form typo in Rockruff-Dusk spawn, preventing it from being able to spawn.
- Fixed the `stat_compare` and `stat_equal` evolution requirements crashing the game when given `special_attack` or `special_defence`, and made them treat any non-permanent stat as a failed requirement instead of crashing.
- Fixed Rowap and Jacoba berries not correctly altering Pokémon sizes when used as bait.
- Fixed Ampharos' eyes not emitting particles.
- Fixed Alolan Raticate's cry not playing.
- Fixed several incorrect light-based spawning conditions for ghosts and bugs.
- Removed unused, placeholder habitat pools.
- Fixed Mantyke, Mantine, and Quaxly getting stuck in a recoil 'expression' during battles due to having an expression assigned as a recoil animation.
- Fixed Lucario's aura particles not emitting.
- Fixed Lycanroc's head movement.
- Added head movement to Archaludon.
- Fixed Duraludon line not having their sleep animations.
- Fixed Diglett line not having their faint animations.
- Fixed Kingambit moving its legs while walking.
### Localization
- Updated translations for:
- Updated translations for:
- Brazilian Portuguese
- Simplified Chinese
- Japanese
-223
View File
@@ -1,223 +0,0 @@
# Habitat Block GUI Editor - Developer Instructions
These instructions are brought to you by AI slop with Hiroku reviewing it afterwards. It does seem fairly accurate
though.
## Overview
The Habitat Block is a spawning tool that allows creative mode players to configure custom Pokémon spawning behavior. We need a GUI editor so read below to understand what it needs to do.
Phases might need a bit of explanation. The idea is that each habitat block will have a current "phase", which changes every day. The number of different phases is something that can be configured per block, as well as what order the phase will move through when it changes.
In the case of 3 phases, the different phase orders do the following:
SIMPLE: 1-2-3-1-2-3-1-2-3
FIXED_RANDOM: 1-3-2-1-3-2-1-3-2 (or some other repeating pattern, based on the position of the habitat block)
FULL_RANDOM: 1-3-1-2-3-1-3-1-2 (completely random value each day)
The idea is like, if you had set 3 phases for a habitat block, you could make Rattata spawn in phase 1, Patrat in phase 2, and Zigzagoon in phase 3.
## Entry Point
- GUI opens when a creative mode player right-clicks a Habitat Block
- Packet: `OpenHabitatBlockEditorPacket` sends current settings to client
- When you're done you send a `SaveHabitatBlockSettingsPacket` back to the server with the same settings DTO instance (but modified, one would assume)
- Settings are applied via `HabitatBlockEntity.applySettings(HabitatSettingsDTO)`
## Data Structure
### Root: `HabitatSettingsDTO`
All settings are contained in this DTO which is sent between client and server.
#### Common Settings (always present)
```kotlin
numberOfPhases: Int = 1 // Number of spawn phases (TODO: not yet implemented)
phaseOrder: HabitatPhaseOrder // How the passage of phases will work with each day
levelRange: IntRange // Min..Max level for spawned Pokémon
modifiers: String // PokemonProperties string (e.g., "shiny gender=male")
mimicId: ResourceLocation // Block appearance (e.g., "minecraft:stone")
```
#### Spawning Mode Toggle
```kotlin
isActivatedSpawning: Boolean // true = Activated mode, false = Natural mode
```
Based on this boolean, EITHER `activatedSettings` OR `naturalSettings` will be populated (never both).
---
## Spawning Modes
### Mode 1: Activated Spawning (`ActivatedHabitatSettingsDTO`)
Triggered spawning system - spawns occur based on specific triggers.
```kotlin
trigger: Enum // REDSTONE, TICK, or RANDOM_TICK
chance: Float = 1.0 // Probability (0.0-1.0) spawn attempt occurs on trigger
cancelledNaturalSpawningRange: Int // Radius (blocks) where natural spawning is disabled
spawnRange: Int // Radius (blocks) where spawning can occur
maxSpawns: Int = -1 // Max concurrent spawns from this block (-1 = infinite)
maxSpawnsPerActivation: Int = 1 // Max spawns per single trigger event
habitatPool: HabitatPoolSettingsDTO // What Pokémon can spawn (see below)
```
**Trigger Types:**
- `REDSTONE`: Spawns on redstone pulse (rising edge)
- `TICK`: Attempts spawn every game tick (very frequent)
- `RANDOM_TICK`: Attempts spawn on random tick events
### Mode 2: Natural Spawning (`NaturalHabitatSettingsDTO`)
Modifies the natural spawning system in the area.
```kotlin
replaceSpawns: Boolean // true = only this pool spawns, false = adds to natural spawns
rangeOfInfluence: Int // Radius (blocks) where this influences spawning
habitatPool: HabitatPoolSettingsDTO // What Pokémon can spawn (see below)
```
---
## Habitat Pool Settings (`HabitatPoolSettingsDTO`)
Defines what Pokémon can spawn. Can reference existing pools or define custom ones.
```kotlin
id: ResourceLocation // Pool identifier
isReference: Boolean // true = use existing pool, false = custom pool
isActivated: Boolean // Must match parent spawning mode
// Only present when isReference = false:
name: String? // Display name for custom pool
spawns: List<HabitatSpawn>? // List of spawn entries
```
**Pool Types:**
- **Reference Pool**: `isReference = true` - Uses a pre-defined pool by ID (e.g., `cobblemon:forest_day`)
- **Custom Pool**: `isReference = false` - Defines spawns inline with `name` and `spawns` list
**Custom Pool IDs**: Must start with `custom__` prefix (e.g., `cobblemon:custom__my_pool`)
---
## GUI Requirements
### Layout Sections
#### 1. Common Settings Panel
- **Number of Phases**: Integer input greater than zero (default: 1)
- **Phase Order**: Habitat Phase Order dropdown (default: SIMPLE)
- **Level Range**: Two integer inputs (Min/Max) - Default: 1 to config max Pokémon level
- **Modifiers**: Text input for PokemonProperties string
- Examples: `shiny`, `gender=male`, `shiny gender=female level=50`
- **Mimic Block**: Block selector/dropdown
- Default: `minecraft:stone`
- See `HabitatBlockEntity.mimicCycle` for suggested blocks
#### 2. Spawning Mode Selector
Radio buttons or toggle:
- **Activated Spawning** (trigger-based)
- **Natural Spawning** (world spawning influenced)
#### 3. Mode-Specific Settings
**If Activated Spawning:**
- **Trigger**: Dropdown (REDSTONE / TICK / RANDOM_TICK)
- **Chance**: Float slider (0.0 - 1.0, default: 1.0)
- **Cancel Natural Spawning Range**: Integer input (blocks)
- **Spawn Range**: Integer input (blocks)
- **Max Spawns**: Integer input (-1 for infinite, default: -1)
- **Max Spawns Per Activation**: Integer input (default: 1)
- **Habitat Pool**: Pool selector (see below)
**If Natural Spawning:**
- **Replace Spawns**: Checkbox (true = exclusive, false = additive)
- **Range of Influence**: Integer input (blocks)
- **Habitat Pool**: Pool selector (see below)
#### 4. Habitat Pool Selector
Two options:
- **Use Existing Pool**: Dropdown of registered pools
- Set `isReference = true`
- Set `id` to selected pool
- Ensure `isActivated` matches spawning mode
- **Create Custom Pool**: Opens pool editor
- Set `isReference = false`
- Generate ID with `custom__` prefix
- Set `name` and `spawns` list
- User defines spawn entries (implementation depends on `HabitatSpawn` structure)
---
## Validation Rules
1. **Level Range**: Min must be ≥ 1, Max must be ≤ config max, Min ≤ Max
2. **Number of Phases**: Must be greater than zero
3. **Chance**: Must be between 0.0 and 1.0
4. **Ranges**: All range values must be ≥ 0
5. **Pool Validation**:
- If `isActivated = true`, pool must be `ActivatedHabitatPool`
- If `isActivated = false`, pool must be `NaturalHabitatPool`
- If reference pool not found, server logs error and rejects settings
6. **Custom Pool IDs**: Must start with `custom__`
---
## Network Flow
1. **Opening GUI**: Server sends `OpenHabitatBlockEditorPacket` with current `HabitatSettingsDTO`
2. **Saving Changes**: Client sends updated `HabitatSettingsDTO` back to server
3. **Server Processing**:
- `HabitatBlockEntity.applySettings()` validates and applies changes
- Calls `refreshFromSettings()` to regenerate spawn details
- Marks block entity as changed and syncs to clients
---
## Implementation Notes
- **Creative Mode Only**: GUI should only be accessible to creative mode players
---
## Example Configurations
### Example 1: Redstone-Triggered Shiny Spawner
```kotlin
isActivatedSpawning = true
phaseOrder = FIXED_RANDOM
numberOfPhases = 4
levelRange = 50..60
modifiers = "shiny"
mimicId = "minecraft:diamond_block"
activatedSettings:
trigger = REDSTONE
chance = 1.0
spawnRange = 10
maxSpawns = 1
maxSpawnsPerActivation = 1
habitatPool = (reference to existing pool)
```
### Example 2: Natural Spawn Replacement Zone
```kotlin
isActivatedSpawning = false
levelRange = 1..100
phaseOrder = SIMPLE
numberOfPhases = 1
modifiers = ""
mimicId = "minecraft:moss_block"
naturalSettings:
replaceSpawns = true
rangeOfInfluence = 32
habitatPool = (custom pool with specific Pokémon)
```
---
## Related Files
- `HabitatBlockEntity.kt` - Main block entity logic
- `HabitatSettingsDTO.kt` - Root settings container
- `ActivatedHabitatSettingsDTO.kt` - Activated mode settings
- `NaturalHabitatSettingsDTO.kt` - Natural mode settings
- `HabitatPoolSettingsDTO.kt` - Pool configuration
- `ActivatedHabitatSpawning.kt` - Activated spawning implementation
- `NaturalHabitatSpawning.kt` - Natural spawning implementation
+9
View File
@@ -0,0 +1,9 @@
- 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.
-9
View File
@@ -1,9 +0,0 @@
IVs layer
Move layer
Remove nature layer in favour of modification layer
Reconsider form layer. Its timing seems more important than anything else
Remove cosmetic layer
Add shiny layer
Fix the way the client is the one deciding when hatching will happen from that TriggerEggHatchHandler
Clean up duplication in hatchPokemon logic for nest and incubator, should be a common function in EggComponent probably for hatching into a player's party
+2 -5
View File
@@ -1,6 +1,3 @@
import utilities.gitBranch
import utilities.gitShortCommit
plugins {
id("cobblemon.root-conventions")
}
@@ -9,6 +6,6 @@ version = "${project.property("mod_version")}+${project.property("mc_version")}"
val isSnapshot = project.property("snapshot")?.equals("true") == true
if (isSnapshot) {
val fixedBranchName = gitBranch().substringAfter("/")
version = "$version-${fixedBranchName}-${gitShortCommit()}"
val fixedBranchName = versioning.info.branch.substringAfter("/")
version = "$version-${fixedBranchName}-${versioning.info.build}"
}
-41
View File
@@ -1,41 +0,0 @@
# Changelog
## [1.8.1 (September 13th, 2026)](#1-8-1)
### Animation updates for the following Pokémon
- Flapple, Appletun, Dipplin, Hydrapple (placeholder animations only).
### Fixes
- Fixed a potential world generation stall caused by Type Gem clusters.
- Fixed Pokémon on shoulders not animating if they are visibly holding a held item.
- Fixed NPCs getting their own TM progress file.
- Fixed odd NeoForge crash due to a dependency being outdated. It will now correctly flag the dependency for being outdated.
- Fixed the TM Machine refusing move selections that the `unlockAllMoveDexMovesByDefault` config had made visible, leaving the move slot blank and the craft button unresponsive.
- Fixed the Beast Ball not applying its 0.1× penalty to Pokémon that are not Ultra Beasts.
- Fixed the Moon Ball reading the world's total elapsed time instead of the time of day, so its bonus applied during the day instead.
- Fixed the Love Ball never awarding its 2.5× opposite gender bonus, and matching species by display name instead of identifier.
- Fixed the Timer Ball being weaker than a regular Poké Ball on early turns and only reaching 4× on turn 14 instead of turn 10.
- Fixed the Luxury Ball only doubling Friendship gains of 1, leaving level up and berry gains unaffected.
- Fixed Rufflet, Braviary, and Hisuian Braviary flooding logs with errors about missing sound events.
- Fixed Pokémon property typos in Pangoro and Dedenne herd spawn files.
- Fixed unintentional watermelon cosmetic for Hisuian Electrode.
- Fixed Blastoise having an unintentional passenger seat.
- Fixed form typo in Rockruff-Dusk spawn, preventing it from being able to spawn.
- Fixed the `stat_compare` and `stat_equal` evolution requirements crashing the game when given `special_attack` or `special_defence`, and made them treat any non-permanent stat as a failed requirement instead of crashing.
- Fixed Rowap and Jacoba berries not correctly altering Pokémon sizes when used as bait.
- Fixed Ampharos' eyes not emitting particles.
- Fixed Alolan Raticate's cry not playing.
- Fixed several incorrect light-based spawning conditions for ghosts and bugs.
- Removed unused, placeholder habitat pools.
- Fixed Mantyke, Mantine, and Quaxly getting stuck in a recoil 'expression' during battles due to having an expression assigned as a recoil animation.
- Fixed Lucario's aura particles not emitting.
- Fixed Lycanroc's head movement.
- Added head movement to Archaludon.
- Fixed Duraludon line not having their sleep animations.
- Fixed Diglett line not having their faint animations.
- Fixed Kingambit moving its legs while walking.
### Localization
- Updated translations for:
- Brazilian Portuguese
- Simplified Chinese
- Japanese
+6 -15
View File
@@ -1,8 +1,9 @@
import java.time.OffsetDateTime
import java.time.ZoneOffset
import java.time.format.DateTimeFormatter
import utilities.isSnapshot
import utilities.version
import utilities.gitBranch
import utilities.gitCommit
/*
*
@@ -71,14 +72,6 @@ tasks.withType<Test> {
}
}
// Git info lives in its own output dir (not processResources) so a new commit only rewrites this one file.
val writeGitInfo by tasks.registering(WriteProperties::class) {
destinationFile = layout.buildDirectory.file("generated/gitInfo/cobblemon-git.properties")
property("commit", gitCommit())
property("branch", gitBranch())
}
sourceSets.main { output.dir(mapOf("builtBy" to writeGitInfo), layout.buildDirectory.dir("generated/gitInfo")) }
tasks.processResources {
exclude("data/**/README.md")
}
@@ -105,13 +98,11 @@ sourceSets {
property("modid", "cobblemon")
property("version", project.version())
property("isSnapshot", if(rootProject.isSnapshot()) "true" else "false")
property("gitCommit", versioning.info.commit)
property("branch", versioning.info.branch)
System.getProperty("buildNumber")?.let { property("buildNumber", it) }
property("timestamp", OffsetDateTime.now(ZoneOffset.UTC).format(DateTimeFormatter.ofPattern("MM/dd/yyyy hh:mm:ss")) + " UTC")
}
}
}
}
// The platform jars are built from :common:jar, so the remapped common jars are only needed by `publish`
tasks.assemble {
setDependsOn(dependsOn.filterNot { (it as? TaskProvider<*>)?.name in setOf("remapJar", "remapSourcesJar") })
}
@@ -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).getBottomCenter();
newPos = pos.relative(facing).getCenter();
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.getBottomCenter();
var center = pos.getCenter();
newPos = new Vec3(
center.x + level.random.nextFloat() * 0.6 - 0.3,
center.y + level.random.nextFloat() * 0.6 - 0.3,
@@ -1,36 +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.CobblemonItemComponents;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.ItemModelShaper;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.ModelResourceLocation;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(ItemModelShaper.class)
public class ItemModelShaperMixin {
@Inject(method = "getItemModel(Lnet/minecraft/world/item/ItemStack;)Lnet/minecraft/client/resources/model/BakedModel;", at = @At("HEAD"), cancellable = true)
public void getItemModel(ItemStack stack, CallbackInfoReturnable<BakedModel> cir) {
String eggModel = stack.get(CobblemonItemComponents.EGG_MODEL);
if (eggModel == null) return;
ModelResourceLocation modelResourceLocation = ModelResourceLocation.inventory(ResourceLocation.parse("cobblemon:egg/" + eggModel));
BakedModel bakedModel = Minecraft.getInstance().getModelManager().getModel(modelResourceLocation);
cir.setReturnValue(bakedModel);
}
}
@@ -8,7 +8,6 @@
package com.cobblemon.mod.common.mixin;
import com.cobblemon.mod.common.CobblemonNetwork;
import com.cobblemon.mod.common.OrientationControllable;
import com.cobblemon.mod.common.api.orientation.OrientationController;
import com.cobblemon.mod.common.api.pokemon.effect.ShoulderEffectRegistry;
@@ -18,7 +17,6 @@ import com.cobblemon.mod.common.api.riding.behaviour.types.liquid.SubmarineBehav
import com.cobblemon.mod.common.client.entity.PokemonClientDelegate;
import com.cobblemon.mod.common.client.render.MatrixWrapper;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import com.cobblemon.mod.common.net.messages.server.orientation.ServerboundUpdateOrientationPacket;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import net.minecraft.server.level.ServerPlayer;
@@ -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.mixin;
import net.minecraft.client.color.block.BlockColors;
import net.minecraft.client.renderer.block.model.BlockModel;
import net.minecraft.client.resources.model.BlockStateModelLoader;
import net.minecraft.client.resources.model.ModelBakery;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.profiling.ProfilerFiller;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.List;
import java.util.Map;
@Mixin(ModelBakery.class)
public abstract class ModelBakeryMixin {
@Final
@Shadow
private Map<ResourceLocation, BlockModel> modelResources;
@Shadow
protected abstract void loadItemModelAndDependencies(ResourceLocation modelLocation);
@Inject(method = "<init>", at = @At("TAIL"))
private void cobblemon$loadEggModels(
BlockColors blockColors,
ProfilerFiller profilerFiller,
Map<ResourceLocation, BlockModel> modelResources,
Map<ResourceLocation, List<BlockStateModelLoader.LoadedJson>> blockStateResources,
CallbackInfo ci
) {
for (ResourceLocation fileLocation : this.modelResources.keySet()) {
if (!fileLocation.getNamespace().equals("cobblemon")) continue;
String path = fileLocation.getPath();
if (!path.startsWith("models/item/egg/")) continue;
String modelPath = path.replace("models/item/", "").replace(".json", "");
ResourceLocation modelId = new ResourceLocation("cobblemon", modelPath);
this.loadItemModelAndDependencies(modelId);
}
}
}
@@ -10,9 +10,7 @@ package com.cobblemon.mod.common.mixin.accessor;
import net.minecraft.client.gui.components.StateSwitchingButton;
import net.minecraft.client.gui.screens.recipebook.RecipeBookComponent;
import net.minecraft.network.chat.Component;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Mutable;
import org.spongepowered.asm.mixin.gen.Accessor;
@Mixin(RecipeBookComponent.class)
@@ -15,11 +15,9 @@ import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import com.llamalad7.mixinextras.sugar.Local;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.NonNullList;
import net.minecraft.world.Containers;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.alchemy.PotionBrewing;
import net.minecraft.world.item.crafting.RecipeHolder;
import net.minecraft.world.item.crafting.RecipeManager;
@@ -34,6 +32,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.List;
import java.util.Optional;
@Mixin(BrewingStandBlockEntity.class)
public class BrewingStandBlockEntityMixin {
@@ -9,17 +9,11 @@
package com.cobblemon.mod.common.mixin.brewing;
import com.cobblemon.mod.common.duck.RecipeAwareSlot;
import com.cobblemon.mod.common.item.crafting.brewingstand.BrewingStandRecipe;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import com.llamalad7.mixinextras.sugar.Local;
import net.minecraft.world.Container;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.BrewingStandMenu;
import net.minecraft.world.inventory.ContainerData;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
@@ -13,7 +13,6 @@ import com.cobblemon.mod.common.item.crafting.brewingstand.BrewingStandRecipe;
import net.minecraft.world.inventory.BrewingStandMenu;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.RecipeManager;
import net.minecraft.world.level.Level;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
@@ -1,29 +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.client.CobblemonClient;
import net.minecraft.client.Minecraft;
import net.minecraft.world.entity.Entity;
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(Minecraft.class)
public class AIDebugGlowMixin {
@Inject(method = "shouldEntityAppearGlowing", at = @At("HEAD"), cancellable = true)
public void cobblemon$debugEntityGlow(Entity entity, CallbackInfoReturnable<Boolean> cir) {
if (entity.getId() == CobblemonClient.INSTANCE.getDebuggedEntityId()) {
cir.setReturnValue(true);
}
}
}
@@ -19,7 +19,6 @@ import net.minecraft.client.Camera;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.phys.Vec3;
import org.joml.Quaternionf;
import org.joml.Vector3f;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
@@ -10,9 +10,7 @@ package com.cobblemon.mod.common.mixin.client;
import com.cobblemon.mod.common.CobblemonRecipeCategories;
import com.cobblemon.mod.common.item.crafting.CookingPotBookCategory;
import com.cobblemon.mod.common.item.crafting.CookingPotRecipe;
import com.cobblemon.mod.common.item.crafting.CookingPotRecipeBase;
import com.cobblemon.mod.common.item.crafting.CookingPotShapelessRecipe;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
@@ -12,11 +12,9 @@ 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.api.breeding.EggTextures;
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.EggComponent;
import com.cobblemon.mod.common.item.components.TMMoveComponent;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.multiplayer.ClientLevel;
@@ -58,29 +56,20 @@ public abstract class ItemRendererMixin {
at = @At("HEAD"),
cancellable = true
)
private void cobblemon$overrideItemModel(ItemStack stack, ItemDisplayContext renderMode, boolean leftHanded, PoseStack matrices, MultiBufferSource multiBufferSource, int light, int overlay, BakedModel model, CallbackInfo ci) throws Exception {
boolean shouldBe2d = renderMode == ItemDisplayContext.GUI || renderMode == ItemDisplayContext.FIXED || stack.getItem() == CobblemonItems.POKEMON_EGG || stack.getItem() == CobblemonItems.TECHNICAL_MACHINE;
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;
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.POKEMON_EGG) {
EggComponent component = stack.get(CobblemonItemComponents.EGG_DATA);
if (component != null) {
resourceLocation = EggTextures.getEggTexture(component.getIdentifier(), component.getVariant());
}
} else if (stack.getItem() == CobblemonItems.TECHNICAL_MACHINE) {
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();
}
if (renderMode != ItemDisplayContext.HEAD && stack.getItem() instanceof WearableItem wearableItem) resourceLocation = wearableItem.getModel2d();
if (resourceLocation != null) {
BakedModel replacementModel = this.itemModelShaper.getModelManager().getModel(new ModelResourceLocation(resourceLocation, "inventory"));
@@ -8,12 +8,8 @@ object CobblemonBuildDetails {
const val VERSION = "{{ version }}"
const val SNAPSHOT = {{ isSnapshot }}
// Read at runtime rather than templated in, so a new commit doesn't invalidate compiled code.
private val git = java.util.Properties().apply {
CobblemonBuildDetails::class.java.getResourceAsStream("/cobblemon-git.properties")?.use(::load)
}
@JvmField val GIT_COMMIT: String = git.getProperty("commit", "Not Specified")
@JvmField val BRANCH: String = git.getProperty("branch", "Not Specified")
const val GIT_COMMIT = "{{ gitCommit | default("Not Specified") }}"
const val BRANCH = "{{ branch | default("Not Specified") }}"
fun smallCommitHash(): String {
return this.GIT_COMMIT.substring(0, 7)
@@ -54,10 +54,6 @@ import com.cobblemon.mod.common.api.storage.adapter.database.MongoDBStoreAdapter
import com.cobblemon.mod.common.api.storage.adapter.flatfile.FileStoreAdapter
import com.cobblemon.mod.common.api.storage.adapter.flatfile.JSONStoreAdapter
import com.cobblemon.mod.common.api.storage.adapter.flatfile.NBTStoreAdapter
import com.cobblemon.mod.common.api.storage.egg.EggStoreManager
import com.cobblemon.mod.common.api.storage.egg.JsonEggStoreBackend
import com.cobblemon.mod.common.api.storage.egg.MongoEggStoreBackend
import com.cobblemon.mod.common.api.storage.egg.NbtEggStoreBackend
import com.cobblemon.mod.common.api.storage.factory.FileBackedPokemonStoreFactory
import com.cobblemon.mod.common.api.storage.molang.NbtMoLangDataStoreFactory
import com.cobblemon.mod.common.api.storage.pc.PCStore
@@ -101,10 +97,10 @@ 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.FORM_ASPECTS
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
import com.cobblemon.mod.common.pokemon.feature.SlowpokeTailRegrowthSpeciesFeatureProvider
import com.cobblemon.mod.common.pokemon.feature.TagSeasonResolver
@@ -119,6 +115,7 @@ import com.cobblemon.mod.common.pokemon.properties.NoAIProperty
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.pokemon.transformation.triggers.BlockClickTrigger
import com.cobblemon.mod.common.starter.CobblemonStarterHandler
import com.cobblemon.mod.common.util.*
import com.cobblemon.mod.common.world.feature.CobblemonPlacedFeatures
@@ -179,7 +176,6 @@ object Cobblemon {
var storage = PokemonStoreManager()
var molangData = NbtMoLangDataStoreFactory
lateinit var playerDataManager: PlayerInstancedDataStoreManager
lateinit var eggStoreManager: EggStoreManager
lateinit var starterConfig: StarterConfig
val dataProvider: DataProvider = CobblemonDataProvider
var permissionValidator: PermissionValidator by Delegates.observable(LaxPermissionValidator().also { it.initialize() }) { _, _, newValue -> newValue.initialize() }
@@ -188,8 +184,6 @@ 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
@@ -212,8 +206,6 @@ object Cobblemon {
this.LOGGER.info(" - Branch: ${CobblemonBuildDetails.BRANCH}")
}
this.loadConfig()
implementation.registerRecipeSerializers()
implementation.registerRecipeTypes()
implementation.registerPermissionValidator()
@@ -242,6 +234,7 @@ object Cobblemon {
ExperienceGroups.registerDefaults()
CaptureCalculators.registerDefaults()
this.loadConfig()
// CobblemonBlockPredicates.touch()
CobblemonOrePlacedFeatures.register()
CobblemonPlacedFeatures.register()
@@ -267,7 +260,6 @@ 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)
@@ -282,13 +274,8 @@ object Cobblemon {
PlatformEvents.RIGHT_CLICK_BLOCK.subscribe { event ->
val player = event.player
val block = player.level().getBlockState(event.pos).block
player.party().forEach { pokemon ->
pokemon.lockedEvolutions
.filterIsInstance<BlockClickEvolution>()
.forEach { evolution ->
evolution.attemptEvolution(pokemon, BlockClickEvolution.BlockInteractionContext(block, player.level()))
}
}
val context = BlockClickTrigger.Context(block, player.level())
player.party().forEach { pokemon -> pokemon.triggerTransformations<BlockClickTrigger>(context) }
}
PlatformEvents.CHANGE_DIMENSION.subscribe {
@@ -332,6 +319,7 @@ object Cobblemon {
SHINY_ASPECT.register()
GENDER_ASPECT.register()
FORM_ASPECTS.register()
COSMETIC_SLOT_ASPECT.register()
CHARACTERISTIC_RAINBOW_ASPECT.register()
ALPHA_ASPECT.register()
@@ -384,12 +372,6 @@ object Cobblemon {
playerDataManager = PlayerInstancedDataStoreManager().also { it.setup(server) }
HabitatPools.onServerLoading(server)
val eggBackend = when (config.storageFormat) {
"nbt" -> NbtEggStoreBackend()
"json" -> JsonEggStoreBackend()
else -> null // MongoDB handled below
}
val mongoClient: MongoClient?
val pokemonStoreRoot = server.getWorldPath(LevelResource.ROOT).resolve("pokemon").toFile()
@@ -441,10 +423,6 @@ object Cobblemon {
playerDataManager.setFactory(generalMongoFactory, PlayerInstancedDataStoreTypes.GENERAL)
playerDataManager.setFactory(pokedexMongoFactory, PlayerInstancedDataStoreTypes.POKEDEX)
playerDataManager.setFactory(tmMovesMongoFactory, PlayerInstancedDataStoreTypes.TM_MOVES)
eggStoreManager = EggStoreManager(MongoEggStoreBackend(mongoClient, config.mongoDBDatabaseName))
eggStoreManager.setup(server)
MongoDBStoreAdapter(mongoClient, config.mongoDBDatabaseName)
} catch (e: ClassNotFoundException) {
LOGGER.error("MongoDB driver not found.")
@@ -458,11 +436,6 @@ object Cobblemon {
}
.with(ReforgedConversion(server.getWorldPath(LevelResource.ROOT))) as FileStoreAdapter<*>
if (eggBackend != null) {
eggStoreManager = EggStoreManager(eggBackend)
eggStoreManager.setup(server)
}
storage.registerFactory(
priority = Priority.LOWEST,
factory = FileBackedPokemonStoreFactory(
@@ -475,7 +448,6 @@ object Cobblemon {
PlatformEvents.SERVER_STOPPED.subscribe {
storage.unregisterAll(it.server.registryAccess())
eggStoreManager.shutdown()
playerDataManager.saveAllStores()
playerDataManager.saveExecutor.shutdown()
playerDataManager.saveExecutor.awaitTermination(30L, TimeUnit.SECONDS)
@@ -9,11 +9,10 @@
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
@@ -21,18 +20,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.ObjectiveConfigAdapter
import com.cobblemon.mod.common.util.adapters.SenseConfigAdapter
import com.cobblemon.mod.common.util.adapters.SensorTypeAdapter
import com.cobblemon.mod.common.util.adapters.TaskConfigAdapter
import com.cobblemon.mod.common.util.adapters.TranslatedTextAdapter
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
@@ -59,8 +47,6 @@ 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
@@ -9,7 +9,6 @@
package com.cobblemon.mod.common
import com.cobblemon.mod.common.block.entity.*
import com.cobblemon.mod.common.block.entity.DisplayCaseBlockEntity
import com.cobblemon.mod.common.block.habitat.HabitatBlockEntity
import com.cobblemon.mod.common.block.multiblock.FossilMultiblockBuilder
import com.cobblemon.mod.common.platform.PlatformRegistry
@@ -47,10 +46,12 @@ object CobblemonBlockEntities : PlatformRegistry<Registry<BlockEntityType<*>>, R
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,
CobblemonBlocks.GILDED_CHEST,
@@ -84,38 +85,6 @@ object CobblemonBlockEntities : PlatformRegistry<Registry<BlockEntityType<*>>, R
).build(null)
)
val NEST: BlockEntityType<NestBlockEntity> = this.create("nest",
BlockEntityType.Builder.of(
::NestBlockEntity,
CobblemonBlocks.NEST_FIERY,
CobblemonBlocks.NEST_GRASSY,
CobblemonBlocks.NEST_LEAFY,
CobblemonBlocks.NEST_ICY,
CobblemonBlocks.NEST_ROCKY,
CobblemonBlocks.NEST_SANDY,
CobblemonBlocks.NEST_SHROOMY,
CobblemonBlocks.NEST_TWIGGY,
CobblemonBlocks.NEST_HAY,
CobblemonBlocks.NEST_KELP,
CobblemonBlocks.NEST_WATERY
).build(null))
val EGG: BlockEntityType<EggBlockEntity> = this.create("egg",
BlockEntityType.Builder.of(
::EggBlockEntity,
CobblemonBlocks.EGG
).build(null)
)
@JvmField
val INCUBATOR: BlockEntityType<IncubatorBlockEntity> = this.create("incubator",
BlockEntityType.Builder.of(
::IncubatorBlockEntity,
CobblemonBlocks.INCUBATOR
).build(null)
)
@JvmField
val DISPLAY_CASE: BlockEntityType<DisplayCaseBlockEntity> = this.create("display_case",
BlockEntityType.Builder.of(::DisplayCaseBlockEntity, CobblemonBlocks.DISPLAY_CASE).build(null)
@@ -9,57 +9,13 @@
package com.cobblemon.mod.common
import com.cobblemon.mod.common.api.apricorn.Apricorn
import com.cobblemon.mod.common.api.nest.WildNestType
import com.cobblemon.mod.common.api.nest.WildNestVariant
import com.cobblemon.mod.common.api.pokemon.stats.Stats
import com.cobblemon.mod.common.block.ApricornBlock
import com.cobblemon.mod.common.block.ApricornSaplingBlock
import com.cobblemon.mod.common.block.BerryBlock
import com.cobblemon.mod.common.block.BigRootBlock
import com.cobblemon.mod.common.block.CoinPouchBlock
import com.cobblemon.mod.common.block.DamagedMonitorBlock
import com.cobblemon.mod.common.block.DecorativeItemBlock
import com.cobblemon.mod.common.block.DiscShelfBlock
import com.cobblemon.mod.common.block.DisplayCaseBlock
import com.cobblemon.mod.common.block.EggBlock
import com.cobblemon.mod.common.block.EjectButtonBlock
import com.cobblemon.mod.common.block.EnergyRootBlock
import com.cobblemon.mod.common.block.FossilAnalyzerBlock
import com.cobblemon.mod.common.block.GalaricaWreathBlock
import com.cobblemon.mod.common.block.HealingMachineBlock
import com.cobblemon.mod.common.block.HeartyGrainsBlock
import com.cobblemon.mod.common.block.IncubatorBlock
import com.cobblemon.mod.common.block.*
import com.cobblemon.mod.common.block.LecternBlock
import com.cobblemon.mod.common.block.MagnetBlock
import com.cobblemon.mod.common.block.MedicinalLeekBlock
import com.cobblemon.mod.common.block.MintBlock
import com.cobblemon.mod.common.block.MintBlock.MintType
import com.cobblemon.mod.common.block.MonitorBlock
import com.cobblemon.mod.common.block.NestBlock
import com.cobblemon.mod.common.block.NutBushBlock
import com.cobblemon.mod.common.block.OrbBlock
import com.cobblemon.mod.common.block.PCBlock
import com.cobblemon.mod.common.block.PastureBlock
import com.cobblemon.mod.common.block.PlaqueBlock
import com.cobblemon.mod.common.block.PokeSnackBlock
import com.cobblemon.mod.common.block.RestorationTankBlock
import com.cobblemon.mod.common.block.RevivalHerbBlock
import com.cobblemon.mod.common.block.SaccharineLeafBlock
import com.cobblemon.mod.common.block.SaccharineLogBlock
import com.cobblemon.mod.common.block.SaccharineLogSlatheredBlock
import com.cobblemon.mod.common.block.StackableItemBlock
import com.cobblemon.mod.common.block.TumblestoneBlock
import com.cobblemon.mod.common.block.TypeGemClusterBlock
import com.cobblemon.mod.common.block.TypeGemCoreBlock
import com.cobblemon.mod.common.block.VivichokeBlock
import com.cobblemon.mod.common.block.WallAttachedDirectionalShapeBlock
import com.cobblemon.mod.common.block.WallAttachedStackableItemBlock
import com.cobblemon.mod.common.block.WildNestBlock
import com.cobblemon.mod.common.block.campfirepot.CampfireBlock
import com.cobblemon.mod.common.block.campfirepot.CampfirePotBlock
import com.cobblemon.mod.common.block.chest.GildedChestBlock
import com.cobblemon.mod.common.block.decor.ActivatableDecorationBlock
import com.cobblemon.mod.common.block.decor.RingTargetBlock
import com.cobblemon.mod.common.block.general.BaleBlock
import com.cobblemon.mod.common.block.general.HorizontalRotationCarpetBlock
import com.cobblemon.mod.common.block.general.HorizontalRotationalBlock
@@ -664,88 +620,6 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
@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))
@JvmField
val RING_TARGET = create("ring_target", RingTargetBlock(BlockBehaviour.Properties.of().sound(SoundType.WOOD).mapColor(MapColor.NONE).noOcclusion().strength(0.125f)))
@JvmField
val MAGNET = create("magnet", MagnetBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.NONE).noOcclusion().strength(0.075f)))
@JvmField
val GALARICA_WREATH = create("galarica_wreath", GalaricaWreathBlock(BlockBehaviour.Properties.of().sound(SoundType.AZALEA_LEAVES).mapColor(MapColor.NONE).noOcclusion().noCollission()))
@JvmField
val FLAME_ORB = create("flame_orb",OrbBlock(BlockBehaviour.Properties.of().sound(SoundType.GLASS).mapColor(MapColor.NONE).noOcclusion().lightLevel { 15 }))
@JvmField
val TOXIC_ORB = create("toxic_orb", OrbBlock(BlockBehaviour.Properties.of().sound(SoundType.GLASS).mapColor(MapColor.NONE).noOcclusion()))
@JvmField
val LIFE_ORB = create("life_orb", OrbBlock(BlockBehaviour.Properties.of().sound(SoundType.GLASS).mapColor(MapColor.NONE).noOcclusion().lightLevel{7}))
@JvmField
val LIGHT_BALL = create("light_ball", OrbBlock(BlockBehaviour.Properties.of().sound(SoundType.GLASS).mapColor(MapColor.NONE).noOcclusion().lightLevel{13}))
@JvmField
val SMOKE_BALL = create("smoke_ball", OrbBlock(BlockBehaviour.Properties.of().sound(SoundType.GLASS).mapColor(MapColor.NONE).noOcclusion()))
@JvmField
val KINGS_ROCK = create("kings_rock", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.TUMBLESTONE_BLOCK_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.CENTERED_6x))
@JvmField
val GALARICA_CUFF = create("galarica_cuff", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.AZALEA_LEAVES).mapColor(MapColor.NONE).noCollission().noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.FLATTENED_10x4))
@JvmField
val UNREMARKABLE_TEACUP = create("unremarkable_teacup", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.DECORATED_POT).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.TEACUP))
@JvmField
val MASTERPIECE_TEACUP = create("masterpiece_teacup", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.DECORATED_POT).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.TEACUP))
@JvmField
val CHIPPED_POT = create("chipped_pot", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.DECORATED_POT).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.TEAPOT))
@JvmField
val CRACKED_POT = create("cracked_pot", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.DECORATED_POT).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.TEAPOT))
@JvmField
val SACHET = create("sachet", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.WOOL).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.POUCH))
@JvmField
val METAL_POWDER = create("metal_powder", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.WOOL).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.POUCH))
@JvmField
val QUICK_POWDER = create("quick_powder", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.WOOL).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.POUCH))
@JvmField
val BRIGHT_POWDER = create("bright_powder", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.WOOL).mapColor(MapColor.NONE).noOcclusion().lightLevel{3}, DecorativeItemBlock.DecorativeItemBlockType.POUCH))
@JvmField
val NEVER_MELT_ICE = create("never_melt_ice", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.GLASS).mapColor(MapColor.NONE).noOcclusion().friction(0.989F), DecorativeItemBlock.DecorativeItemBlockType.NEVERMELT))
@JvmField
val SCOPE_LENS = create("scope_lens", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.COPPER).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.SCOPE))
@JvmField
val WIDE_LENS = create("wide_lens", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.COPPER).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.WIDE))
@JvmField
val ZOOM_LENS = create("zoom_lens", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.COPPER).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.ZOOM))
@JvmField
val ICY_ROCK = create("icy_rock", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.DEEPSLATE).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.FLATTENED_8x4))
@JvmField
val HEAT_ROCK = create("heat_rock", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.DEEPSLATE).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.FLATTENED_8x4))
@JvmField
val SMOOTH_ROCK = create("smooth_rock", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.DEEPSLATE).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.FLATTENED_8x4))
@JvmField
val DAMP_ROCK = create("damp_rock", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.DEEPSLATE).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.FLATTENED_8x6))
@JvmField
val DESTINY_KNOT = create("destiny_knot", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.WOOL).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.KNOT))
@JvmField
val LUMINOUS_MOSS = create("luminous_moss", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.MOSS).mapColor(MapColor.NONE).noOcclusion().lightLevel{5}, DecorativeItemBlock.DecorativeItemBlockType.CENTERED_8x))
@JvmField
val METRONOME = create("metronome", ActivatableDecorationBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.NONE).pushReaction(PushReaction.DESTROY).noOcclusion(), ActivatableDecorationBlock.ActivatableDecorationBlockType.METRONOME))
@JvmField
val EJECT_BUTTON = create("eject_button", EjectButtonBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.NONE).pushReaction(PushReaction.DESTROY).noOcclusion().strength(0.125f), 40))
@JvmField
val LOADED_DICE = create("loaded_dice", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.DICE))
@JvmField
val IRON_BALL = create("iron_ball", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.IRON_BALL))
@JvmField
val METAL_COAT = create("metal_coat", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.METAL_COAT))
@JvmField
val UPGRADE = create("upgrade", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.UPGRADE))
@JvmField
val DUBIOUS_DISC = create("dubious_disc", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.UPGRADE))
@JvmField
val THROAT_SPRAY = create("throat_spray", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.THROAT_SPRAY))
@JvmField
val FULL_HEAL = create("full_heal", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
@JvmField
@@ -796,23 +670,6 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
@JvmField
val X_SPEED = create("x_${Stats.SPEED.identifier.path}", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.BATTLE_ITEM))
@JvmField
val HP_UP = create("hp_up", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
@JvmField
val PROTEIN = create("protein", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
@JvmField
val IRON = create("iron", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
@JvmField
val CARBOS = create("carbos", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
@JvmField
val CALCIUM = create("calcium", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
@JvmField
val ZINC = create("zinc", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
@JvmField
val PP_UP = create("pp_up", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
@JvmField
val PP_MAX = create("pp_max", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
@JvmField
val MONITOR = create("monitor", MonitorBlock(BlockBehaviour.Properties.of().mapColor(MapColor.METAL).sound(SoundType.METAL).pushReaction(PushReaction.BLOCK).requiresCorrectToolForDrops().strength(5.0F, 6.0F).lightLevel { if (it.getValue(MonitorBlock.SCREEN) != MonitorBlock.MonitorScreen.OFF) 13 else 0 }))
@JvmField
@@ -828,7 +685,6 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
@JvmField
val PC = create("pc", PCBlock(BlockBehaviour.Properties.of().mapColor(MapColor.METAL).sound(SoundType.METAL).pushReaction(PushReaction.BLOCK).strength(2F).noOcclusion().lightLevel { if ((it.getValue(PCBlock.ON) as Boolean) && (it.getValue(PCBlock.PART) == PCBlock.PCPart.TOP)) 13 else 0 }))
@JvmField
val TM_MACHINE = create(
"tm_machine",
TMMachineBlock(
@@ -843,16 +699,7 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
@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 }
))
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)))
@@ -866,7 +713,7 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
@JvmField
val PASTURE = create("pasture", PastureBlock(BlockBehaviour.Properties.of().mapColor(MapColor.COLOR_BROWN).sound(SoundType.WOOD).strength(2F).noOcclusion().pushReaction(PushReaction.BLOCK).lightLevel { if ((it.getValue(PastureBlock.ON) as Boolean) && (it.getValue(PastureBlock.PART) == PastureBlock.PasturePart.TOP)) 13 else 0 }))
@JvmField
val RED_MINT = create("red_mint", MintBlock(MintType.RED, BlockBehaviour.Properties.of().mapColor(MapColor.COLOR_RED).noCollission().randomTicks().instabreak().sound(CobblemonSounds.MINT_SOUNDS).pushReaction(PushReaction.DESTROY)))
@JvmField
val BLUE_MINT = create("blue_mint", MintBlock(MintType.BLUE, BlockBehaviour.Properties.of().mapColor(MapColor.COLOR_BLUE).noCollission().randomTicks().instabreak().sound(CobblemonSounds.MINT_SOUNDS).pushReaction(PushReaction.DESTROY)))
@@ -1084,82 +931,6 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
val YACHE_BERRY = this.berryBlock("yache")
//val BINDING_SOIL = this.create("binding_soil", BindingSoilBlock(BlockBehaviour.Properties.of()))
val NEST_FIERY = this.create(
"nest_fiery",
WildNestBlock.create(
WildNestType.FIERY,
BlockBehaviour.Properties.of()
.noOcclusion()
.pushReaction(PushReaction.DESTROY)
.lightLevel { state ->
val variant = (state.block as? WildNestBlock)?.variantProperty?.let { state.getValue(it) }
when (variant) {
WildNestVariant.FIERY_NETHERRACK -> 15
WildNestVariant.FIERY_SOUL_SAND -> 10
else -> 3
}
}
)
)
val NEST_GRASSY = this.create(
"nest_grassy",
WildNestBlock.create(WildNestType.GRASSY, BlockBehaviour.Properties.of().noOcclusion().pushReaction(PushReaction.DESTROY))
)
val NEST_LEAFY = this.create(
"nest_leafy",
WildNestBlock.create(WildNestType.LEAFY, BlockBehaviour.Properties.of().noOcclusion().pushReaction(PushReaction.DESTROY))
)
val NEST_ICY = this.create(
"nest_icy",
WildNestBlock.create(WildNestType.ICY, BlockBehaviour.Properties.of().noOcclusion().pushReaction(PushReaction.DESTROY))
)
val NEST_HAY = this.create(
"nest_hay",
WildNestBlock.create(WildNestType.PLAYER, BlockBehaviour.Properties.of().noOcclusion().pushReaction(PushReaction.DESTROY))
)
val NEST_KELP = this.create(
"nest_kelp",
WildNestBlock.create(WildNestType.KELP, BlockBehaviour.Properties.of().noOcclusion().pushReaction(PushReaction.DESTROY))
)
val NEST_ROCKY = this.create(
"nest_rocky",
WildNestBlock.create(WildNestType.ROCKY, BlockBehaviour.Properties.of().noOcclusion().pushReaction(PushReaction.DESTROY))
)
val NEST_SANDY = this.create(
"nest_sandy",
WildNestBlock.create(WildNestType.SANDY, BlockBehaviour.Properties.of().noOcclusion().pushReaction(PushReaction.DESTROY))
)
val NEST_SHROOMY = this.create(
"nest_shroomy",
WildNestBlock.create(WildNestType.SHROOMY, BlockBehaviour.Properties.of().noOcclusion().pushReaction(PushReaction.DESTROY))
)
val NEST_TWIGGY = this.create(
"nest_twiggy",
WildNestBlock.create(WildNestType.TWIGGY, BlockBehaviour.Properties.of().noOcclusion().pushReaction(PushReaction.DESTROY))
)
val NEST_WATERY = this.create(
"nest_watery",
WildNestBlock.create(WildNestType.WATERY, BlockBehaviour.Properties.of().noOcclusion().pushReaction(PushReaction.DESTROY))
)
val EGG = this.create("egg", EggBlock(BlockBehaviour.Properties
.ofFullCopy(Blocks.DRAGON_EGG)
.pushReaction(PushReaction.BLOCK)
.sound(SoundType.BONE_BLOCK)
))
@JvmField
val INCUBATOR = this.create("incubator", IncubatorBlock(BlockBehaviour.Properties.of()
.mapColor(MapColor.COLOR_LIGHT_GRAY)
.sound(SoundType.METAL)
.strength(Blocks.COAL_BLOCK.defaultDestroyTime(), Blocks.DIAMOND_ORE.explosionResistance)
.noOcclusion()
.pushReaction(PushReaction.BLOCK)
.lightLevel { state ->
if (state.getValue(IncubatorBlock.HAS_EGG) && !state.getValue(IncubatorBlock.READY)) 8 else 0 // emit light only when warming the egg
}
))
init {
/**
* Makes all blocks in array flammable by adding them to FireBlock's flammableRegistry.
@@ -1200,12 +971,6 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
return block
}
private fun nestBlock(variant: NestBlock.NestVariant): NestBlock {
val identifier = cobblemonResource(variant.serializedName)
val block = NestBlock(variant, BlockBehaviour.Properties.of().noOcclusion().pushReaction(PushReaction.DESTROY))
return this.create(identifier.path, block)
}
/**
* Calls helper method from Vanilla
*/
@@ -41,6 +41,8 @@ object CobblemonCommands {
HeldItemCommand.register(dispatcher, registry)
PcCommand.register(dispatcher)
SpawnPokemonFromPool.register(dispatcher)
SpawnMeteorCommand.register(dispatcher)
SetMegaBondCommand.register(dispatcher)
PokeboxCommand.register(dispatcher)
PokedexCommand.register(dispatcher)
TestStoreCommand.register(dispatcher)
@@ -61,8 +63,6 @@ object CobblemonCommands {
ChangeBoxWallpaperCommand.register(dispatcher)
RunMolangCommand.register(dispatcher)
RunMolangScriptCommand.register(dispatcher)
GivePokemonEggCommand.register(dispatcher)
GiveAllPokemonEggCommand.register(dispatcher)
SpectateBattleCommand.register(dispatcher)
CobblemonConfigCommand.register(dispatcher)
UnlockPCBoxWallpaperCommand.register(dispatcher)
@@ -75,11 +75,9 @@ object CobblemonCommands {
PcTakeCommand.register(dispatcher)
GiveTmCommand.register(dispatcher)
TmCommand.register(dispatcher)
BreedPokemonCommand.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)
DebugCobblemonAICommand.register(dispatcher)
}
}
@@ -16,6 +16,7 @@ import com.cobblemon.mod.common.entity.generic.GenericBedrockEntity
import com.cobblemon.mod.common.entity.npc.NPCEntity
import com.cobblemon.mod.common.entity.pokeball.EmptyPokeBallEntity
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.entity.projectile.FallingStarEntity
import com.cobblemon.mod.common.platform.PlatformRegistry
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.core.Registry
@@ -26,8 +27,6 @@ import net.minecraft.world.entity.LivingEntity
import net.minecraft.resources.ResourceKey
import net.minecraft.world.entity.MobCategory
import net.minecraft.world.entity.ai.attributes.AttributeSupplier
import com.cobblemon.mod.common.entity.RealtimeMoveProjectile
import com.example.mod.entity.RealtimeMoveAreaEffect
object CobblemonEntities : PlatformRegistry<Registry<EntityType<*>>, ResourceKey<Registry<EntityType<*>>>, EntityType<*>>() {
@@ -89,25 +88,6 @@ object CobblemonEntities : PlatformRegistry<Registry<EntityType<*>>, ResourceKey
.build(GENERIC_BEDROCK_ENTITY_KEY.toString())
)
@JvmField
val REALTIME_MOVE_PROJECTILE_ENTITY_KEY = cobblemonResource("realtime_move_projectile")
@JvmField
val REALTIME_MOVE_PROJECTILE_ENTITY: EntityType<RealtimeMoveProjectile> = this.create(
REALTIME_MOVE_PROJECTILE_ENTITY_KEY.path,
EntityType.Builder.of({ _, world -> RealtimeMoveProjectile(world) }, MobCategory.MISC)
.build(REALTIME_MOVE_PROJECTILE_ENTITY_KEY.toString())
)
@JvmField
val REALTIME_MOVE_PROJECTILE_AOE_KEY = cobblemonResource("realtime_move_aoe")
@JvmField
val REALTIME_MOVE_PROJECTILE_AOE: EntityType<RealtimeMoveAreaEffect> = this.create(
REALTIME_MOVE_PROJECTILE_AOE_KEY.path,
EntityType.Builder.of({ _, world -> RealtimeMoveAreaEffect(world) }, MobCategory.MISC)
.build(REALTIME_MOVE_PROJECTILE_AOE_KEY.toString())
)
@JvmField
val NPC_KEY = cobblemonResource("npc")
@JvmField
@@ -116,6 +96,15 @@ object CobblemonEntities : PlatformRegistry<Registry<EntityType<*>>, ResourceKey
EntityType.Builder.of({ _, world -> NPCEntity(world) }, MobCategory.CREATURE).build("$NPC_KEY")
)
@JvmField
val FALLING_STAR_KEY = cobblemonResource("falling_star")
@JvmField
val FALLING_STAR: EntityType<FallingStarEntity> = create(
FALLING_STAR_KEY.path,
EntityType.Builder.of(::FallingStarEntity, MobCategory.MISC).sized(0.5F, 0.5F).clientTrackingRange(8)
.build(FALLING_STAR_KEY.toString())
)
fun registerAttributes(consumer: (EntityType<out LivingEntity>, AttributeSupplier.Builder) -> Unit) {
consumer(POKEMON, PokemonEntity.createAttributes())
consumer(NPC, NPCEntity.createAttributes())
@@ -12,7 +12,6 @@ import com.cobblemon.mod.common.api.net.NetworkPacket
import com.mojang.brigadier.arguments.ArgumentType
import kotlin.reflect.KClass
import net.minecraft.commands.synchronization.ArgumentTypeInfo
import net.minecraft.core.HolderLookup
import net.minecraft.resources.ResourceKey
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.MinecraftServer
@@ -20,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
@@ -169,16 +167,12 @@ interface CobblemonImplementation {
* TODO
*
* @param identifier
* @param reloaderFactory
* @param reloader
* @param type
* @param dependencies
*/
fun registerResourceReloader(
identifier: ResourceLocation,
type: PackType,
dependencies: Collection<ResourceLocation>,
reloaderFactory: (HolderLookup.Provider) -> PreparableReloadListener
)
fun registerResourceReloader(identifier: ResourceLocation, reloader: PreparableReloadListener, type: PackType, dependencies: Collection<ResourceLocation>)
/**
* TODO
*
@@ -18,7 +18,6 @@ import net.minecraft.core.registries.Registries
import net.minecraft.network.codec.ByteBufCodecs
import net.minecraft.resources.ResourceKey
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.ExtraCodecs
object CobblemonItemComponents : PlatformRegistry<Registry<DataComponentType<*>>, ResourceKey<Registry<DataComponentType<*>>>, DataComponentType<*>>() {
@@ -34,6 +33,12 @@ object CobblemonItemComponents : PlatformRegistry<Registry<DataComponentType<*>>
.networkSynchronized(HeldItemEffectComponent.PACKET_CODEC)
.build())
@JvmField
val MEGA_BOND: DataComponentType<MegaBondComponent> = create("mega_bond", DataComponentType.builder<MegaBondComponent>()
.persistent(MegaBondComponent.CODEC)
.networkSynchronized(MegaBondComponent.PACKET_CODEC)
.build())
@JvmField
val BAIT: DataComponentType<RodBaitComponent> = create("bait", DataComponentType.builder<RodBaitComponent>()
.persistent(RodBaitComponent.CODEC)
@@ -69,16 +74,6 @@ object CobblemonItemComponents : PlatformRegistry<Registry<DataComponentType<*>>
.persistent(FoodColourComponent.CODEC)
.networkSynchronized(FoodColourComponent.PACKET_CODEC)
.build())
@JvmField
val EGG_DATA: DataComponentType<EggComponent> = create("egg", DataComponentType.builder<EggComponent>()
.persistent(EggComponent.CODEC) // For saving/loading
.networkSynchronized(EggComponent.PACKET_CODEC) // For network synchronization
.build())
@JvmField
val EGG_MODEL: DataComponentType<String> = create("egg_model", DataComponentType.builder<String>()
.persistent(ExtraCodecs.NON_EMPTY_STRING)
.networkSynchronized(ByteBufCodecs.STRING_UTF8)
.build())
@JvmField
val TM_MOVE: DataComponentType<TMMoveComponent> = create("tm_move", DataComponentType.builder<TMMoveComponent>()
@@ -122,8 +117,6 @@ object CobblemonItemComponents : PlatformRegistry<Registry<DataComponentType<*>>
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:ingredient"), INGREDIENT)
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:food"), FOOD)
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:mob_effects"), MOB_EFFECTS)
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:egg_data"), EGG_DATA)
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:egg_model"), EGG_MODEL)
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:held_item_effect"), HELD_ITEM_EFFECT)
}
@@ -472,9 +472,9 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
// Evolution items
@JvmField val LINK_CABLE = create("link_cable", LinkCableItem())
@JvmField val DRAGON_SCALE = noSettingsItem("dragon_scale")
@JvmField val METAL_COAT = itemNameBlockItem("metal_coat", CobblemonBlocks.METAL_COAT)
@JvmField val UPGRADE = itemNameBlockItem("upgrade", CobblemonBlocks.UPGRADE)
@JvmField val DUBIOUS_DISC = itemNameBlockItem("dubious_disc", CobblemonBlocks.DUBIOUS_DISC)
@JvmField val METAL_COAT = noSettingsItem("metal_coat")
@JvmField val UPGRADE = noSettingsItem("upgrade")
@JvmField val DUBIOUS_DISC = noSettingsItem("dubious_disc")
@JvmField val DEEP_SEA_SCALE = noSettingsItem("deep_sea_scale")
@JvmField val DEEP_SEA_TOOTH = noSettingsItem("deep_sea_tooth")
@JvmField val ELECTIRIZER = noSettingsItem("electirizer")
@@ -483,7 +483,7 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField val PROTECTOR = noSettingsItem("protector")
@JvmField val REAPER_CLOTH = noSettingsItem("reaper_cloth")
@JvmField val PRISM_SCALE = noSettingsItem("prism_scale")
@JvmField val SACHET = itemNameBlockItem("sachet", CobblemonBlocks.SACHET)
@JvmField val SACHET = noSettingsItem("sachet")
@JvmField val WHIPPED_DREAM = create("whipped_dream", foodItem(8, 0.3f))
@JvmField val STRAWBERRY_SWEET = create("strawberry_sweet", foodItem(6, 0.125f))
@JvmField val LOVE_SWEET = create("love_sweet", foodItem(6, 0.125f))
@@ -492,20 +492,22 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField val FLOWER_SWEET = create("flower_sweet", foodItem(6, 0.125f))
@JvmField val STAR_SWEET = create("star_sweet", foodItem(6, 0.125f))
@JvmField val RIBBON_SWEET = create("ribbon_sweet", foodItem(6, 0.125f))
@JvmField val CHIPPED_POT = itemNameBlockItem("chipped_pot", CobblemonBlocks.CHIPPED_POT)
@JvmField val CRACKED_POT = itemNameBlockItem("cracked_pot", CobblemonBlocks.CRACKED_POT)
@JvmField val MASTERPIECE_TEACUP = itemNameBlockItem("masterpiece_teacup", CobblemonBlocks.MASTERPIECE_TEACUP)
@JvmField val UNREMARKABLE_TEACUP = itemNameBlockItem("unremarkable_teacup", CobblemonBlocks.UNREMARKABLE_TEACUP)
@JvmField val CHIPPED_POT = noSettingsItem("chipped_pot")
@JvmField val CRACKED_POT = noSettingsItem("cracked_pot")
@JvmField val MASTERPIECE_TEACUP = noSettingsItem("masterpiece_teacup")
@JvmField val UNREMARKABLE_TEACUP = noSettingsItem("unremarkable_teacup")
@JvmField val SWEET_APPLE = compostableItem("sweet_apple", foodItem(4, 0.3f), 0.65f)
@JvmField val TART_APPLE = compostableItem("tart_apple", foodItem(4, 0.3f), 0.65f)
@JvmField val SYRUPY_APPLE = compostableItem("syrupy_apple", foodItem(4, 0.3f), 0.65f)
@JvmField val GALARICA_CUFF = itemNameBlockItem("galarica_cuff", CobblemonBlocks.GALARICA_CUFF)
@JvmField val GALARICA_CUFF = noSettingsItem("galarica_cuff")
@JvmField val GALARICA_WREATH = noSettingsItem("galarica_wreath")
@JvmField val BLACK_AUGURITE = noSettingsItem("black_augurite")
@JvmField val PEAT_BLOCK = noSettingsItem("peat_block")
@JvmField val RAZOR_CLAW = noSettingsItem("razor_claw")
@JvmField val RAZOR_FANG = noSettingsItem("razor_fang")
@JvmField val AUSPICIOUS_ARMOR = heldItem("auspicious_armor")
@JvmField val MALICIOUS_ARMOR = heldItem("malicious_armor")
@JvmField val SHELL_HELMET = heldItem("shell_helmet")
@JvmField val METAL_ALLOY = noSettingsItem("metal_alloy")
@JvmField val SCROLL_OF_DARKNESS = noSettingsItem("scroll_of_darkness")
@JvmField val SCROLL_OF_WATERS = noSettingsItem("scroll_of_waters")
@@ -602,22 +604,6 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField val BERRY_JUICE = this.create("berry_juice", BerryJuiceItem())
val NEST_GRASSY = blockItem("nest_grassy", CobblemonBlocks.NEST_GRASSY)
val NEST_LEAFY = blockItem("nest_leafy", CobblemonBlocks.NEST_LEAFY)
val NEST_FIERY = blockItem("nest_fiery", CobblemonBlocks.NEST_FIERY)
val NEST_ICY = blockItem("nest_icy", CobblemonBlocks.NEST_ICY)
val NEST_HAY = blockItem("nest_hay", CobblemonBlocks.NEST_HAY)
val NEST_KELP = blockItem("nest_kelp", CobblemonBlocks.NEST_KELP)
val NEST_ROCKY = blockItem("nest_rocky", CobblemonBlocks.NEST_ROCKY)
val NEST_SANDY = blockItem("nest_sandy", CobblemonBlocks.NEST_SANDY)
val NEST_SHROOMY = blockItem("nest_shroomy", CobblemonBlocks.NEST_SHROOMY)
val NEST_TWIGGY = blockItem("nest_twiggy", CobblemonBlocks.NEST_TWIGGY)
val NEST_WATERY = blockItem("nest_watery", CobblemonBlocks.NEST_WATERY)
@JvmField
val INCUBATOR = blockItem("incubator", CobblemonBlocks.INCUBATOR)
@JvmField
val HABITAT_BLOCK = blockItem("habitat_block", CobblemonBlocks.HABITAT_BLOCK)
@@ -665,17 +651,17 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val EXPERIENCE_CANDY_XL = candyItem("exp_candy_xl") { _, _ -> CandyItem.DEFAULT_XL_CANDY_YIELD }
@JvmField
val CALCIUM = create("calcium", VitaminItem(Stats.SPECIAL_ATTACK, CobblemonBlocks.CALCIUM))
val CALCIUM = create("calcium", VitaminItem(Stats.SPECIAL_ATTACK))
@JvmField
val CARBOS = create("carbos", VitaminItem(Stats.SPEED, CobblemonBlocks.CARBOS))
val CARBOS = create("carbos", VitaminItem(Stats.SPEED))
@JvmField
val HP_UP = create("hp_up", VitaminItem(Stats.HP, CobblemonBlocks.HP_UP))
val HP_UP = create("hp_up", VitaminItem(Stats.HP))
@JvmField
val IRON = create("iron", VitaminItem(Stats.DEFENCE, CobblemonBlocks.IRON))
val IRON = create("iron", VitaminItem(Stats.DEFENCE))
@JvmField
val PROTEIN = create("protein", VitaminItem(Stats.ATTACK, CobblemonBlocks.PROTEIN))
val PROTEIN = create("protein", VitaminItem(Stats.ATTACK))
@JvmField
val ZINC = create("zinc", VitaminItem(Stats.SPECIAL_DEFENCE, CobblemonBlocks.ZINC))
val ZINC = create("zinc", VitaminItem(Stats.SPECIAL_DEFENCE))
@JvmField
val HEALTH_MOCHI = create("health_mochi", MochiItem(Stats.HP))
@JvmField
@@ -757,9 +743,9 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val MAX_REVIVE = create("max_revive", ReviveItem(max = true))
@JvmField
val PP_UP = create("pp_up", PPUpItem(1, CobblemonBlocks.PP_UP))
val PP_UP = create("pp_up", PPUpItem(1))
@JvmField
val PP_MAX = create("pp_max", PPUpItem(3, CobblemonBlocks.PP_MAX))
val PP_MAX = create("pp_max", PPUpItem(3))
@JvmField
val RED_MINT_SEEDS = mintSeed("red", MintType.RED.getCropBlock())
@@ -928,6 +914,10 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
val THUNDER_STONE = noSettingsItem("thunder_stone")
@JvmField
val WATER_STONE = noSettingsItem("water_stone")
@JvmField
val MEGA_SHARD = noSettingsItem("mega_shard")
@JvmField
val DORMANT_MEGA_STONE = heldItem("dormant_mega_stone")
val wearables = mutableListOf<WearableItem>()
//Wearable items (these items should have a corresponding 3D model)
@@ -936,31 +926,44 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField val CHOICE_SPECS = wearableItem("choice_specs")
@JvmField val EXP_SHARE = wearableItem("exp_share")
@JvmField val FOCUS_BAND = wearableItem("focus_band")
@JvmField val KINGS_ROCK = wearableBlockItem("kings_rock", CobblemonBlocks.KINGS_ROCK)
@JvmField val NEVER_MELT_ICE = wearableBlockItem("never_melt_ice", CobblemonBlocks.NEVER_MELT_ICE)
@JvmField val KINGS_ROCK = wearableItem("kings_rock")
@JvmField val MUSCLE_BAND = wearableItem("muscle_band")
@JvmField val ROCKY_HELMET = wearableItem("rocky_helmet")
@JvmField val SAFETY_GOGGLES = wearableItem("safety_goggles")
@JvmField val WISE_GLASSES = wearableItem("wise_glasses")
@JvmField val SHARP_BEAK = wearableItem("sharp_beak")
@JvmField val COVERT_CLOAK = wearableItem("covert_cloak")
@JvmField val SHELL_HELMET = wearableItem("shell_helmet")
@JvmField val GALARICA_WREATH = wearableBlockItem("galarica_wreath", CobblemonBlocks.GALARICA_WREATH)
@JvmField val CLEANSE_TAG = wearableBlockItem("cleanse_tag", CobblemonBlocks.CLEANSE_TAG)
@JvmField val SPELL_TAG = wearableBlockItem("spell_tag", CobblemonBlocks.SPELL_TAG)
// Held Items
@JvmField
val ABILITY_SHIELD = heldItem("ability_shield")
@JvmField
val ABOMASITE = heldItem("abomasite")
@JvmField
val ABSOLITE = heldItem("absolite")
@JvmField
val ABSORB_BULB = heldItem("absorb_bulb")
@JvmField
val AERODACTYLITE = heldItem("aerodactylite")
@JvmField
val AGGRONITE = heldItem("aggronite")
@JvmField
val AIR_BALLOON = heldItem("air_balloon")
@JvmField
val ALAKAZITE = heldItem("alakazite")
@JvmField
val ALTARIANITE = heldItem("altarianite")
@JvmField
val AMPHAROSITE = heldItem("ampharosite")
@JvmField
val ASSAULT_VEST = heldItem("assault_vest")
@JvmField
val BIG_ROOT = compostableBlockItem("big_root", CobblemonBlocks.BIG_ROOT, 0.30f)
@JvmField
val AUDINITE = heldItem("audinite")
@JvmField
val BANETTITE = heldItem("banettite")
@JvmField
val BEEDRILLITE = heldItem("beedrillite")
@JvmField
val BINDING_BAND = heldItem("binding_band")
@JvmField
val BLACK_BELT = heldItem("black_belt")
@@ -969,19 +972,35 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val BLUNDER_POLICY = itemNameBlockItem("blunder_policy", CobblemonBlocks.BLUNDER_POLICY)
@JvmField
val BLASTOISINITE = heldItem("blastoisinite")
@JvmField
val BLAZIKENITE = heldItem("blazikenite")
@JvmField
val CAMERUPTITE = heldItem("cameruptite")
@JvmField
val CELL_BATTERY = heldItem("cell_battery")
@JvmField
val CHARCOAL = heldItem("charcoal_stick", remappedName = "charcoal")
@JvmField
val CHARIZARDITE_X = heldItem("charizardite_x")
@JvmField
val CHARIZARDITE_Y = heldItem("charizardite_y")
@JvmField
val CHOICE_SCARF = heldItem("choice_scarf")
@JvmField
val CLEANSE_TAG = itemNameBlockItem("cleanse_tag", CobblemonBlocks.CLEANSE_TAG)
@JvmField
val CLEAR_AMULET = heldItem("clear_amulet")
@JvmField
val DESTINY_KNOT = create("destiny_knot", ItemNameBlockItem(CobblemonBlocks.DESTINY_KNOT, Item.Properties().durability(256)))
val COVERT_CLOAK = heldItem("covert_cloak")
@JvmField
val DESTINY_KNOT = heldItem("destiny_knot")
@JvmField
val DIANCITE = heldItem("diancite")
@JvmField
val DRAGON_FANG = heldItem("dragon_fang")
@JvmField
val EJECT_BUTTON = itemNameBlockItem("eject_button", CobblemonBlocks.EJECT_BUTTON)
val EJECT_BUTTON = heldItem("eject_button")
@JvmField
val EJECT_PACK = heldItem("eject_pack")
@JvmField
@@ -995,46 +1014,90 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val FAIRY_FEATHER = heldItem("fairy_feather")
@JvmField
val FLAME_ORB = itemNameBlockItem("flame_orb", CobblemonBlocks.FLAME_ORB)
val FLAME_ORB = heldItem("flame_orb")
@JvmField
val FLOAT_STONE = heldItem("float_stone")
@JvmField
val FOCUS_SASH = heldItem("focus_sash")
@JvmField
val GALLADITE = heldItem("galladite")
@JvmField
val GARCHOMPITE = heldItem("garchompite")
@JvmField
val GARDEVOIRITE = heldItem("gardevoirite")
@JvmField
val GENGARITE = heldItem("gengarite")
@JvmField
val GLALITITE = heldItem("glalitite")
@JvmField
val GRASSY_SEED = heldItem("grassy_seed")
@JvmField
val GRIP_CLAW = heldItem("grip_claw")
@JvmField
val GYARADOSITE = heldItem("gyaradosite")
@JvmField
val HARD_STONE = heldItem("hard_stone")
@JvmField
val HEAVY_DUTY_BOOTS = heldItem("heavy_duty_boots")
@JvmField
val IRON_BALL = itemNameBlockItem("iron_ball", CobblemonBlocks.IRON_BALL)
val HERACRONITE = heldItem("heracronite")
@JvmField
val HOUNDOOMINITE = heldItem("houndoominite")
@JvmField
val IRON_BALL = heldItem("iron_ball")
@JvmField
val KANGASKHANITE = heldItem("kangaskhanite")
@JvmField
val LAGGING_TAIL = heldItem("lagging_tail")
@JvmField
val LEFTOVERS = compostableHeldItem("leftovers", null, 0.50f)
@JvmField
val LIFE_ORB = itemNameBlockItem("life_orb", CobblemonBlocks.LIFE_ORB)
val LATIASITE = heldItem("latiasite")
@JvmField
val LIGHT_BALL = itemNameBlockItem("light_ball", CobblemonBlocks.LIGHT_BALL)
val LATIOSITE = heldItem("latiosite")
@JvmField
val LIFE_ORB = heldItem("life_orb")
@JvmField
val LIGHT_BALL = heldItem("light_ball")
@JvmField
val LIGHT_CLAY = heldItem("light_clay")
@JvmField
val LOADED_DICE = itemNameBlockItem("loaded_dice", CobblemonBlocks.LOADED_DICE)
val LOADED_DICE = heldItem("loaded_dice")
@JvmField
val LOPUNNITE = heldItem("lopunnite")
@JvmField
val LUCARIONITE = heldItem("lucarionite")
@JvmField
val LUCKY_EGG = heldItem("lucky_egg")
@JvmField
val LUMINOUS_MOSS = itemNameBlockItem("luminous_moss", CobblemonBlocks.LUMINOUS_MOSS)
val LUMINOUS_MOSS = heldItem("luminous_moss")
@JvmField
val MAGNET = itemNameBlockItem("magnet", CobblemonBlocks.MAGNET)
val MAGNET = heldItem("magnet")
@JvmField
val METRONOME = itemNameBlockItem("metronome", CobblemonBlocks.METRONOME)
val MANECTITE = heldItem("manectite")
@JvmField
val MAWILITE = heldItem("mawilite")
@JvmField
val MEDICHAMITE = heldItem("medichamite")
@JvmField
val METAGROSSITE = heldItem("metagrossite")
@JvmField
val METRONOME = heldItem("metronome")
@JvmField
val MEWTWONITE_X = heldItem("mewtwonite_x")
@JvmField
val MEWTWONITE_Y = heldItem("mewtwonite_y")
@JvmField
val MIRACLE_SEED = heldItem("miracle_seed")
@JvmField
val MYSTIC_WATER = heldItem("mystic_water")
@JvmField
val NEVER_MELT_ICE = heldItem("never_melt_ice")
@JvmField
val PIDGEOTITE = heldItem("pidgeotite")
@JvmField
val PINSIRITE = heldItem("pinsirite")
@JvmField
val POISON_BARB = heldItem("poison_barb")
@JvmField
val POWER_ANKLET = heldItem("power_anklet")
@@ -1059,11 +1122,23 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val RED_CARD = heldItem("red_card")
@JvmField
val RING_TARGET = itemNameBlockItem("ring_target", CobblemonBlocks.RING_TARGET)
val RING_TARGET = heldItem("ring_target")
@JvmField
val ROOM_SERVICE = heldItem("room_service")
@JvmField
val SCOPE_LENS = itemNameBlockItem("scope_lens", CobblemonBlocks.SCOPE_LENS)
val SABLENITE = heldItem("sablenite")
@JvmField
val SALAMENCITE = heldItem("salamencite")
@JvmField
val SCEPTILITE = heldItem("sceptilite")
@JvmField
val SCIZORITE = heldItem("scizorite")
@JvmField
val SCOPE_LENS = heldItem("scope_lens")
@JvmField
val SHARP_BEAK = heldItem("sharp_beak")
@JvmField
val SHARPEDONITE = heldItem("sharpedonite")
@JvmField
val SHED_SHELL = heldItem("shed_shell")
@JvmField
@@ -1073,29 +1148,41 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val SILVER_POWDER = heldItem("silver_powder")
@JvmField
val SLOWBRONITE = heldItem("slowbronite")
@JvmField
val SOFT_SAND = heldItem("soft_sand")
@JvmField
val SMOKE_BALL = itemNameBlockItem("smoke_ball", CobblemonBlocks.SMOKE_BALL)
val SPELL_TAG = itemNameBlockItem("spell_tag", CobblemonBlocks.SPELL_TAG)
@JvmField
val SMOKE_BALL = heldItem("smoke_ball")
@JvmField
val SOOTHE_BELL = heldItem("soothe_bell")
@JvmField
val STEELIXITE = heldItem("steelixite")
@JvmField
val STICKY_BARB = heldItem("sticky_barb")
@JvmField
val SWAMPERTITE = heldItem("swampertite")
@JvmField
val TERRAIN_EXTENDER = heldItem("terrain_extender")
@JvmField
val THROAT_SPRAY = itemNameBlockItem("throat_spray", CobblemonBlocks.THROAT_SPRAY)
val THROAT_SPRAY = heldItem("throat_spray")
@JvmField
val TOXIC_ORB = itemNameBlockItem("toxic_orb", CobblemonBlocks.TOXIC_ORB)
val TOXIC_ORB = heldItem("toxic_orb")
@JvmField
val TWISTED_SPOON = heldItem("twisted_spoon")
@JvmField
val TYRANITARITE = heldItem("tyranitarite")
@JvmField
val UTILITY_UMBRELLA = heldItem("utility_umbrella")
@JvmField
val WEAKNESS_POLICY = itemNameBlockItem("weakness_policy", CobblemonBlocks.WEAKNESS_POLICY)
@JvmField
val WIDE_LENS = itemNameBlockItem("wide_lens", CobblemonBlocks.WIDE_LENS)
val VENUSAURITE = heldItem("venusaurite")
@JvmField
val ZOOM_LENS = itemNameBlockItem("zoom_lens", CobblemonBlocks.ZOOM_LENS)
val WIDE_LENS = heldItem("wide_lens")
@JvmField
val ZOOM_LENS = heldItem("zoom_lens")
@JvmField
val MENTAL_HERB = compostableHeldItem("mental_herb", null, 0.85F)
@JvmField
@@ -1107,19 +1194,19 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val WHITE_HERB = compostableHeldItem("white_herb", null, 0.85F)
@JvmField
val BRIGHT_POWDER = itemNameBlockItem("bright_powder", CobblemonBlocks.BRIGHT_POWDER)
val BRIGHT_POWDER = heldItem("bright_powder")
@JvmField
val METAL_POWDER = itemNameBlockItem("metal_powder", CobblemonBlocks.METAL_POWDER)
val METAL_POWDER = heldItem("metal_powder")
@JvmField
val QUICK_POWDER = itemNameBlockItem("quick_powder", CobblemonBlocks.QUICK_POWDER)
val QUICK_POWDER = heldItem("quick_powder")
@JvmField
val DAMP_ROCK = itemNameBlockItem("damp_rock", CobblemonBlocks.DAMP_ROCK)
val DAMP_ROCK = heldItem("damp_rock")
@JvmField
val HEAT_ROCK = itemNameBlockItem("heat_rock", CobblemonBlocks.HEAT_ROCK)
val HEAT_ROCK = heldItem("heat_rock")
@JvmField
val SMOOTH_ROCK = itemNameBlockItem("smooth_rock", CobblemonBlocks.SMOOTH_ROCK)
val SMOOTH_ROCK = heldItem("smooth_rock")
@JvmField
val ICY_ROCK = itemNameBlockItem("icy_rock", CobblemonBlocks.ICY_ROCK)
val ICY_ROCK = heldItem("icy_rock")
// Mulch
@JvmField
@@ -1573,10 +1660,6 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
//@JvmField
//val BINDING_SOIL = blockItem("binding_soil", CobblemonBlocks.BINDING_SOIL)
@JvmField
val POKEMON_EGG = eggItem("egg", CobblemonBlocks.EGG, Item.Properties().stacksTo(1))
private fun eggItem(name: String, block: Block, settings: Item.Properties = Item.Properties()): BlockItem = this.create(name, BlockItem(block, settings))
private fun blockItem(name: String, block: Block, rarity: Rarity = Rarity.COMMON): BlockItem = this.create(name, BlockItem(block, Item.Properties().rarity(rarity)))
@@ -1614,16 +1697,7 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
private fun wearableItem(name: String, heldItemRemappedName: String? = null): CobblemonItem = create(
name,
WearableHatItem(name).also {
wearables.add(it)
if (heldItemRemappedName != null) {
CobblemonHeldItemManager.registerRemap(it, heldItemRemappedName)
}
}
)
private fun wearableBlockItem(name: String, block: Block, heldItemRemappedName: String? = null): WearableBlockItem = create(
name,
WearableBlockItem(name, block).also {
WearableItem(name).also {
wearables.add(it)
if (heldItemRemappedName != null) {
CobblemonHeldItemManager.registerRemap(it, heldItemRemappedName)
@@ -1675,9 +1749,9 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
return create(name, RegionalFoodItem(properties))
}
private fun heldItem(name: String, remappedName: String? = null, settings:Item.Properties = Item.Properties()): CobblemonItem = create(
private fun heldItem(name: String, remappedName: String? = null): CobblemonItem = create(
name,
CobblemonItem(settings).also {
CobblemonItem(Item.Properties()).also {
if (remappedName != null) {
CobblemonHeldItemManager.registerRemap(it, remappedName)
}
@@ -1780,4 +1854,4 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
private fun foodItem(nutrition: Int, saturationModifier: Float): Item {
return CobblemonItem(Item.Properties().food(FoodProperties.Builder().nutrition(nutrition).saturationModifier(saturationModifier).build()))
}
}
}
@@ -12,7 +12,6 @@ import com.cobblemon.mod.common.api.ai.CobblemonAttackTargetData
import com.cobblemon.mod.common.api.ai.CobblemonWanderControl
import com.cobblemon.mod.common.api.dialogue.ActiveDialogue
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.mojang.serialization.Codec
import com.mojang.serialization.codecs.ListCodec
import com.mojang.serialization.codecs.PrimitiveCodec
@@ -22,7 +21,6 @@ import net.minecraft.core.BlockPos
import net.minecraft.core.UUIDUtil
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.entity.player.Player
object CobblemonMemories {
val memories = mutableMapOf<String, MemoryModuleType<*>>()
@@ -41,14 +39,6 @@ object CobblemonMemories {
@JvmField
val ACTIVE_ACTION_EFFECT = register<ActionEffectContext>("active_action_effect")
@JvmField
val POTENTIAL_MATE = register<PokemonEntity>("potential_mate")
@JvmField
val MATE_SCAN_COOLDOWN = register("mate_scan_cooldown", PrimitiveCodec.BOOL)
@JvmField
val NEST_LOCATION = register<BlockPos>("nest_location")
@JvmField
val PATERNAL_NEST_LOCATION = register<BlockPos>("paternal_nest", BlockPos.CODEC)
@JvmField
val POKEMON_FLYING = register("pokemon_flying", PrimitiveCodec.BOOL)
@JvmField
val POKEMON_DROWSY = register("pokemon_drowsy", PrimitiveCodec.BOOL)
@@ -65,28 +55,16 @@ object CobblemonMemories {
@JvmField
val NEARBY_GROWABLE_CROPS = register<BlockPos>("nearby_growable_crops")
@JvmField
val EGG_THIEF = register<Player>("egg_thief") // Stores the player stealing the egg
@JvmField
val EGG_THIEF_LAST_SEEN_TICK = register("egg_thief_last_seen_tick", PrimitiveCodec.LONG)
@JvmField
val EGG_LOCATION = register<BlockPos>("egg_location") // Stores the last known location of the egg
@JvmField
val EGG_IN_NEST = register("egg_in_nest", PrimitiveCodec.BOOL)
@JvmField
val HOLDING_EGG = register("holding_egg", PrimitiveCodec.BOOL)
@JvmField
val EGG_SEARCH_COUNTDOWN = register ("egg_search_countdown", PrimitiveCodec.BOOL)
@JvmField
val IDEAL_NEST_LOCATION = register<BlockPos>("ideal_nest_location")
@JvmField
val NEST_DESPAWN_IMMUNE_UNTIL_TICK = register("nest_despawn_immune_until_tick", PrimitiveCodec.LONG)
@JvmField
val NEARBY_SWEET_BERRY_BUSH = register<BlockPos>("nearest_sweet_berry_bush")
@JvmField
val DISABLE_WALK_TO_BERRY_BUSH = register<Boolean>("disable_walk_to_berry_bush")
@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")
@@ -128,4 +106,4 @@ object CobblemonMemories {
memories[id] = memoryModule
return memoryModule
}
}
}
@@ -22,7 +22,6 @@ 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
@@ -85,7 +84,6 @@ 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
@@ -109,10 +107,14 @@ import com.cobblemon.mod.common.net.messages.client.pokemon.update.evolution.Add
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.pokemon.update.form.AddFormPacket
import com.cobblemon.mod.common.net.messages.client.pokemon.update.form.ClearFormsPacket
import com.cobblemon.mod.common.net.messages.client.pokemon.update.form.RemoveFormPacket
import com.cobblemon.mod.common.net.messages.client.settings.OpenCobblemonConfigScreenPacket
import com.cobblemon.mod.common.net.messages.client.settings.ServerSettingsPacket
import com.cobblemon.mod.common.net.messages.client.sound.UnvalidatedPlaySoundS2CPacket
import com.cobblemon.mod.common.net.messages.client.spawn.SpawnGenericBedrockPacket
import com.cobblemon.mod.common.net.messages.client.spawn.SpawnFallingStarPacket
import com.cobblemon.mod.common.net.messages.client.spawn.SpawnNPCPacket
import com.cobblemon.mod.common.net.messages.client.spawn.SpawnPokeballPacket
import com.cobblemon.mod.common.net.messages.client.spawn.SpawnPokemonPacket
@@ -168,7 +170,6 @@ import com.cobblemon.mod.common.net.messages.server.behaviour.DamageOnCollisionP
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.breeding.TriggerEggHatchPacket
import com.cobblemon.mod.common.net.messages.server.callback.move.MoveSelectCancelledPacket
import com.cobblemon.mod.common.net.messages.server.callback.move.MoveSelectedPacket
import com.cobblemon.mod.common.net.messages.server.callback.party.PartyPokemonSelectedPacket
@@ -197,6 +198,8 @@ import com.cobblemon.mod.common.net.messages.server.pokemon.update.SetItemHidden
import com.cobblemon.mod.common.net.messages.server.pokemon.update.SetMarkingsPacket
import com.cobblemon.mod.common.net.messages.server.pokemon.update.SetNicknamePacket
import com.cobblemon.mod.common.net.messages.server.pokemon.update.evolution.AcceptEvolutionPacket
import com.cobblemon.mod.common.net.messages.server.pokemon.update.form.AcceptFormPacket
import com.cobblemon.mod.common.net.messages.server.pokemon.update.form.RevertFormPacket
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
@@ -214,9 +217,11 @@ import com.cobblemon.mod.common.net.messages.server.trade.ChangeTradeAcceptanceP
import com.cobblemon.mod.common.net.messages.server.trade.OfferTradePacket
import com.cobblemon.mod.common.net.messages.server.trade.PerformTradePacket
import com.cobblemon.mod.common.net.messages.server.trade.UpdateTradeOfferPacket
import com.cobblemon.mod.common.net.serverhandling.AcceptFormHandler
import com.cobblemon.mod.common.net.serverhandling.ChallengeHandler
import com.cobblemon.mod.common.net.serverhandling.ChallengeResponseHandler
import com.cobblemon.mod.common.net.serverhandling.RequestInteractionsHandler
import com.cobblemon.mod.common.net.serverhandling.RevertFormHandler
import com.cobblemon.mod.common.net.serverhandling.battle.BattleSelectActionsHandler
import com.cobblemon.mod.common.net.serverhandling.battle.RemoveSpectatorHandler
import com.cobblemon.mod.common.net.serverhandling.battle.SpectateBattleHandler
@@ -227,7 +232,6 @@ import com.cobblemon.mod.common.net.serverhandling.behaviour.DamageOnCollisionPa
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.breeding.TriggerEggHatchHandler
import com.cobblemon.mod.common.net.serverhandling.callback.move.MoveSelectCancelledHandler
import com.cobblemon.mod.common.net.serverhandling.callback.move.MoveSelectedHandler
import com.cobblemon.mod.common.net.serverhandling.callback.party.PartyPokemonSelectedHandler
@@ -461,6 +465,7 @@ object CobblemonNetwork {
list.add(PacketRegisterInfo(SpawnPokemonPacket.ID, SpawnPokemonPacket::decode, SpawnExtraDataEntityHandler()))
list.add(PacketRegisterInfo(SpawnPokeballPacket.ID, SpawnPokeballPacket::decode, SpawnExtraDataEntityHandler()))
list.add(PacketRegisterInfo(SpawnGenericBedrockPacket.ID, SpawnGenericBedrockPacket::decode, SpawnExtraDataEntityHandler()))
list.add(PacketRegisterInfo(SpawnFallingStarPacket.ID, SpawnFallingStarPacket::decode, SpawnExtraDataEntityHandler()))
list.add(PacketRegisterInfo(SpawnNPCPacket.ID, SpawnNPCPacket::decode, SpawnExtraDataEntityHandler()))
list.add(PacketRegisterInfo(ToastPacket.ID, ToastPacket::decode, ToastPacketHandler))
@@ -510,6 +515,11 @@ object CobblemonNetwork {
// Pokédex scanning
list.add(PacketRegisterInfo(ServerConfirmedRegisterPacket.ID, ServerConfirmedRegisterPacket::decode, ServerConfirmedRegisterHandler))
// Form packets
list.add(PacketRegisterInfo(AddFormPacket.ID, AddFormPacket::decode, PokemonUpdatePacketHandler()))
list.add(PacketRegisterInfo(ClearFormsPacket.ID, ClearFormsPacket::decode, PokemonUpdatePacketHandler()))
list.add(PacketRegisterInfo(RemoveFormPacket.ID, RemoveFormPacket::decode, PokemonUpdatePacketHandler()))
// Riding
list.add(PacketRegisterInfo(ClientboundUpdateRidingStatePacket.ID, ClientboundUpdateRidingStatePacket::decode, ClientboundUpdateRidingStateHandler))
list.add(PacketRegisterInfo(ClientboundUpdateDriverInputPacket.ID, ClientboundUpdateDriverInputPacket::decode, ClientboundUpdateDriverInputHandler))
@@ -520,8 +530,6 @@ object CobblemonNetwork {
// Debug
list.add(PacketRegisterInfo(OpenRidingStatsDebugGUIPacket.ID, OpenRidingStatsDebugGUIPacket::decode, OpenRidingStatsDebugGUIHandler))
list.add(PacketRegisterInfo(UpdateTrackedBrainDataPacket.ID, UpdateTrackedBrainDataPacket::decode, UpdateTrackedBrainDataHandler))
return list
@@ -627,6 +635,10 @@ object CobblemonNetwork {
// NPC packets
list.add(PacketRegisterInfo(SaveNPCPacket.ID, SaveNPCPacket::decode, SaveNPCHandler))
// Form Packets
list.add(PacketRegisterInfo(AcceptFormPacket.ID, AcceptFormPacket::decode, AcceptFormHandler))
list.add(PacketRegisterInfo(RevertFormPacket.ID, RevertFormPacket::decode, RevertFormHandler))
// Riding packet(s)
list.add(PacketRegisterInfo(ServerboundUpdateOrientationPacket.ID, ServerboundUpdateOrientationPacket::decode, ServerboundUpdateOrientationHandler))
list.add(PacketRegisterInfo(ServerboundUpdateRidingStatePacket.ID, ServerboundUpdateRidingStatePacket::decode, ServerboundUpdateRidingStateHandler))
@@ -645,9 +657,6 @@ object CobblemonNetwork {
list.add(PacketRegisterInfo(SetActiveTMPacket.ID, SetActiveTMPacket::decode, SetActiveTMPacketHandler))
list.add(PacketRegisterInfo(SetTMMachineContainerDataPacket.ID, SetTMMachineContainerDataPacket::decode, SetTMMachineContainerDataPacketHandler))
// Breeding Packets
list.add(PacketRegisterInfo(TriggerEggHatchPacket.ID, TriggerEggHatchPacket::decode, TriggerEggHatchHandler))
// Behaviour Packets
list.add(PacketRegisterInfo(SetEntityBehaviourPacket.ID, SetEntityBehaviourPacket::decode, SetEntityBehaviourHandler))
list.add(PacketRegisterInfo(DamageOnCollisionPacket.ID, DamageOnCollisionPacket::decode, DamageOnCollisionPacketHandler))
@@ -18,7 +18,6 @@ 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
@@ -35,8 +34,6 @@ object CobblemonPoiTypes: PlatformRegistry<Registry<PoiType>, ResourceKey<Regist
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))
@JvmField
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 ->
@@ -1,59 +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.bedrockk.molang.Expression
import com.cobblemon.mod.common.api.data.JsonDataRegistry
import com.cobblemon.mod.common.api.molang.ExpressionLike
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.api.types.ElementalType
import com.cobblemon.mod.common.api.types.adapters.ElementalTypeAdapter
import com.cobblemon.mod.common.pokemon.ai.moves.RealTimeMove
import com.cobblemon.mod.common.pokemon.ai.moves.events.MoveEventAction
import com.cobblemon.mod.common.util.adapters.ExpressionAdapter
import com.cobblemon.mod.common.util.adapters.ExpressionLikeAdapter
import com.cobblemon.mod.common.util.adapters.IdentifierAdapter
import com.cobblemon.mod.common.util.adapters.RealTimeMoveConfigAdapter
import com.cobblemon.mod.common.util.adapters.RealTimeMoveEventActionConfigAdapter
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 CobblemonRealTimeMoves : JsonDataRegistry<RealTimeMove> {
override val type = PackType.SERVER_DATA
override val observable = SimpleObservable<CobblemonRealTimeMoves>()
override val typeToken = TypeToken.get(RealTimeMove::class.java)
override val resourcePath: String = "real_time_moves"
override val id: ResourceLocation = cobblemonResource(resourcePath)
override val gson: Gson = GsonBuilder()
.setPrettyPrinting()
.registerTypeAdapter(ResourceLocation::class.java, IdentifierAdapter)
.registerTypeAdapter(ExpressionLike::class.java, ExpressionLikeAdapter)
.registerTypeAdapter(Expression::class.java, ExpressionAdapter)
.registerTypeAdapter(RealTimeMove::class.java, RealTimeMoveConfigAdapter)
.registerTypeAdapter(MoveEventAction::class.java, RealTimeMoveEventActionConfigAdapter)
.registerTypeAdapter(ElementalType::class.java, ElementalTypeAdapter)
.create()
val realTimeMoves = mutableMapOf<ResourceLocation, RealTimeMove>()
override fun reload(data: Map<ResourceLocation, RealTimeMove>) {
realTimeMoves.clear()
data.forEach { location, move -> move.id = location }
realTimeMoves.putAll(data)
}
override fun sync(player: ServerPlayer) {
// nah
}
}
@@ -12,10 +12,10 @@ import com.cobblemon.mod.common.entity.pokemon.ai.sensors.*
import com.cobblemon.mod.common.entity.sensor.BattlingPokemonSensor
import com.cobblemon.mod.common.entity.sensor.NPCBattlingSensor
import com.cobblemon.mod.common.pokemon.ai.PokemonDisturbancesSensor
import java.util.function.Supplier
import net.minecraft.world.entity.Entity
import net.minecraft.world.entity.ai.sensing.Sensor
import net.minecraft.world.entity.ai.sensing.SensorType
import java.util.function.Supplier
object CobblemonSensors {
val sensors = mutableMapOf<String, SensorType<*>>()
@@ -49,6 +49,9 @@ 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)
@@ -61,4 +64,4 @@ object CobblemonSensors {
sensors[id] = sensor
return sensor
}
}
}
@@ -94,9 +94,6 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, ResourceKey<Regi
@JvmField
val POKEDEX_SCAN_ZOOM_INCREMENT = this.create("item.pokedex.scan_zoom_increment")
@JvmField
val BREEDING_EGG_HATCH = this.create("breeding.egg_hatch")
@JvmField
val ITEM_USE = this.create("item.use")
@JvmField
@@ -8,6 +8,7 @@
package com.cobblemon.mod.common
import java.util.Optional
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.util.RandomSource
import net.minecraft.world.entity.Entity
@@ -18,7 +19,6 @@ import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Items
import net.minecraft.world.item.trading.ItemCost
import net.minecraft.world.item.trading.MerchantOffer
import java.util.Optional
/**
* A generator for various trade offers in the mod.
@@ -11,10 +11,10 @@ package com.cobblemon.mod.common.advancement.criterion
import com.cobblemon.mod.common.util.cobblemonResource
import com.mojang.serialization.Codec
import com.mojang.serialization.codecs.RecordCodecBuilder
import java.util.Optional
import net.minecraft.advancements.critereon.ContextAwarePredicate
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
import java.util.Optional
class AspectCriterion(
playerCtx: Optional<ContextAwarePredicate>,
@@ -14,8 +14,8 @@ import com.mojang.serialization.codecs.RecordCodecBuilder
import java.util.Optional
import net.minecraft.advancements.critereon.ContextAwarePredicate
import net.minecraft.advancements.critereon.EntityPredicate
import net.minecraft.server.level.ServerPlayer
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
class CastPokeRodContext(val baitId: ResourceLocation)
@@ -10,10 +10,10 @@ package com.cobblemon.mod.common.advancement.criterion
import com.mojang.serialization.Codec
import com.mojang.serialization.codecs.RecordCodecBuilder
import java.util.Optional
import net.minecraft.advancements.critereon.ContextAwarePredicate
import net.minecraft.advancements.critereon.EntityPredicate
import net.minecraft.server.level.ServerPlayer
import java.util.Optional
/**
* A context that is used when you require a [CountableContext] along with some type string.
@@ -14,8 +14,8 @@ import com.mojang.serialization.codecs.RecordCodecBuilder
import java.util.Optional
import net.minecraft.advancements.critereon.ContextAwarePredicate
import net.minecraft.advancements.critereon.EntityPredicate
import net.minecraft.server.level.ServerPlayer
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
class ReelInPokemonContext(val species : ResourceLocation, val baitId: ResourceLocation)
@@ -19,7 +19,6 @@ package com.cobblemon.mod.common.api
open class PrioritizedList<T> : Iterable<T> {
protected val priorityMap = mutableMapOf<Priority, MutableList<T>>()
// Please don't change arraylist, we need insertion order
// MutableList doesn't necessarily preserve insertion order apparently
protected val ordered = arrayListOf<T>()
val mapping: Map<Priority, List<T>>
@@ -27,8 +26,8 @@ open class PrioritizedList<T> : Iterable<T> {
private fun reorder() {
ordered.clear()
Priority.entries.forEach {
priorityMap[it]?.let(ordered::addAll)
Priority.values().forEach {
priorityMap[it]?.let { ordered.addAll(it) }
}
}
@@ -11,51 +11,28 @@ package com.cobblemon.mod.common.api.abilities
import com.cobblemon.mod.common.Cobblemon.LOGGER
import com.cobblemon.mod.common.api.PrioritizedList
import com.cobblemon.mod.common.api.Priority
import com.cobblemon.mod.common.api.molang.MoLangFunctions.addAbilityPoolFunctions
import com.cobblemon.mod.common.api.molang.ObjectValue
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.pokemon.Species
/**
* A pool of potential abilities, as a [PrioritizedList]. The added logic of this subclass
* is that it has selection logic. Given a species and a set of aspects, it will go through
* each priority group, and if it can then it will pick from that group and return it. It also
* returns the index of that ability within the type band it is.
* each priority group, and if it can then it will pick from that group and return it.
*
* @author Hiroku
* @since July 28th, 2022
*/
open class AbilityPool : PrioritizedList<PotentialAbility>() {
val struct = ObjectValue(this)
.also { it.addAbilityPoolFunctions(this) }
fun getInheritedAbility(parent: Pokemon): Ability? {
if (parent.ability.forced) {
return null
}
val parentAbilityType = parent.form.abilities.find { it.template == parent.ability.template }?.type ?: return null // Should be impossible
val matchingTypes = ordered.filter { it.type == parentAbilityType }
val desiredIndex = parent.ability.index
val matchingPotentialAbility = matchingTypes.getOrNull(desiredIndex) ?: return null
return matchingPotentialAbility.template.create().also { it.index = desiredIndex }
}
fun select(species: Species, aspects: Set<String>): Ability {
val typeMap = ordered.groupBy { it.type }
fun select(species: Species, aspects: Set<String>): Pair<Ability, Priority> {
for (priority in Priority.entries) {
val potentialAbilities = priorityMap[priority]?.filter { it.isSatisfiedBy(aspects) } ?: continue
if (potentialAbilities.isNotEmpty()) {
val selected = potentialAbilities.random()
val ability = selected.template.create(priority = selected.priority)
ability.index = typeMap[selected.type]!!.indexOf(selected)
return ability
return potentialAbilities.random().template.create() to priority
}
}
LOGGER.error("Unable to select an ability from the pool for $species and aspects: ${aspects.joinToString()}")
LOGGER.error("Please show this to the Cobblemon developers!")
Exception().printStackTrace()
return Abilities.first().create()
return Abilities.first().create() to Priority.LOWEST
}
}
@@ -9,6 +9,7 @@
package com.cobblemon.mod.common.api.abilities
import com.cobblemon.mod.common.api.Priority
import com.cobblemon.mod.common.api.data.ShowdownIdentifiable
import com.cobblemon.mod.common.util.codec.CodecUtils
import com.google.gson.JsonObject
import com.mojang.serialization.Codec
@@ -25,7 +26,7 @@ class AbilityTemplate(
var builder: (AbilityTemplate, forced: Boolean, priority: Priority) -> Ability = { template, forced, priority -> Ability(template, forced, priority) },
val displayName: String = "cobblemon.ability.$name",
val description: String = "cobblemon.ability.$name.desc"
) {
) : ShowdownIdentifiable {
/**
* Returns the Ability or if applicable the extension connected to this template
*/
@@ -45,6 +46,8 @@ class AbilityTemplate(
*/
fun create(json: JsonObject) = create().loadFromJSON(json)
override fun showdownId(): String = ShowdownIdentifiable.REGEX.replace(this.name.lowercase(), "")
companion object {
@JvmStatic
@@ -12,10 +12,6 @@ import com.bedrockk.molang.runtime.MoLangRuntime
import com.bedrockk.molang.runtime.struct.QueryStruct
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.Objective
import com.cobblemon.mod.common.api.ai.biggerbrain.sense.Sense
import com.cobblemon.mod.common.api.ai.config.BehaviourConfig
import com.cobblemon.mod.common.api.molang.ExpressionLike
import com.cobblemon.mod.common.api.molang.MoLangFunctions.addStandardFunctions
@@ -49,15 +45,8 @@ class BehaviourConfigurationContext {
val memories = mutableSetOf<MemoryModuleType<*>>()
val sensors = mutableSetOf<SensorType<*>>()
val objectives = mutableMapOf<Int, MutableList<Objective>>()
val senses = mutableSetOf<Sense>()
val onAdd = mutableListOf<ExpressionLike>()
init {
memories.add(CobblemonMemories.NEST_DESPAWN_IMMUNE_UNTIL_TICK)
}
fun getOrCreateActivity(activity: Activity): ActivityConfigurationContext {
return activities.firstOrNull { it.activity == activity } ?: ActivityConfigurationContext(activity).also(activities::add)
}
@@ -78,22 +67,6 @@ 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)
}
@@ -114,23 +87,8 @@ class BehaviourConfigurationContext {
var brain = entity.brain
// Apply the brain config
if (entity is MoLangScriptingEntity) {
val useBiggerBrain = objectives.isNotEmpty() || senses.isNotEmpty()
brain = entity.assignNewBrainWithMemoriesAndSensors(dynamic, memories, if (useBiggerBrain) emptySet() else sensors)
brain = entity.assignNewBrainWithMemoriesAndSensors(dynamic, memories, 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)
@@ -177,4 +135,4 @@ class BehaviourConfigurationContext {
}
return struct
}
}
}
@@ -42,8 +42,7 @@ class CobblemonAttackTargetData(
/** An expression that returns false when the target is no longer worth attacking. */
var shouldContinue: Expression = "true".asExpression(),
var hitsUntilDisengage: Int = -1, // -1 means no limit
var disengageAction: (entity: Entity) -> Unit = {},
var hitsLanded : Int = 0
var disengageAction: (entity: Entity) -> Unit = {}
) {
val struct = ObjectValue<CobblemonAttackTargetData>(this).also {
it.addFunction("hits_until_disengage") { DoubleValue(hitsUntilDisengage.toDouble()) }
@@ -13,7 +13,6 @@ 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.resolve
import com.cobblemon.mod.common.util.withQueryValue
@@ -9,40 +9,43 @@
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
/**
* 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.
* 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.
*
* @author Hiroku
* @since July 18, 2025
*/
class CobblemonWanderControl(
var center: WanderCenter? = null,
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
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,
) {
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") { 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("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("set_center") { params ->
if (params.params.size < 3) {
throw IllegalArgumentException("set_center requires at least 3 parameters: x, y, z")
@@ -64,6 +67,26 @@ 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,23 +9,9 @@
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
@@ -45,50 +31,6 @@ import net.minecraft.world.entity.LivingEntity
*/
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
@@ -1,194 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.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.PrioritizedList
import com.cobblemon.mod.common.api.Priority
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 net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.entity.ai.memory.MemoryStatus
import java.util.UUID
/**
* 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 subscriptions = mutableMapOf<MemoryModuleType<*>, PrioritizedList<MemorySubscription<*>>>()
class MemorySubscription<U>(
val subscription: (U?, labels: MutableSet<String>) -> Boolean
) {
fun handle(value: Any?, labels: MutableSet<String>): Boolean {
val castValue = value as? U
return subscription(castValue, labels)
}
}
fun <U, M : MemoryModuleType<U>> subscribe(priority: Priority = Priority.NORMAL, memory: M, handler: (U?, labels: MutableSet<String>) -> Boolean) {
subscriptions
.getOrPut(memory, ::PrioritizedList)
.add(priority, MemorySubscription(handler))
}
var fixation: EntityFixation? = null
var debuggingPlayers = mutableSetOf<UUID>()
fun registerObjective(objective: Objective) {
objectives.add(objective)
objective.onRegister(entity, this)
}
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)
}
}
val f = fixation
if (f?.tick(entity, this) == false && fixation === f) {
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)
}
val labels = mutableSetOf<String>()
subscriptions[memory]?.forEach { it.handle(value = value, labels = labels) }
}
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)
val labels = mutableSetOf<String>()
subscriptions[memory]?.forEach { it.handle(value = null, labels = labels) }
}
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)
}
}
@@ -1,74 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.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)
}
}
}
@@ -1,38 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.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())
}
}
@@ -1,27 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.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
}
@@ -1,30 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.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()
}
@@ -1,240 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.ai.biggerbrain
import com.cobblemon.mod.common.api.ai.movement.TraversalPolicy
import com.cobblemon.mod.common.entity.ai.CobblemonWalkTarget
import com.cobblemon.mod.common.entity.ai.OmniPathNavigation
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,
/**
* The traversal rules for this movement. They stay attached to the path, including when it's recomputed or
* re-planned because the target moved. Null uses the legacy rules. Only honoured by [OmniPathNavigation].
*/
policy: TraversalPolicy? = null,
/** An already planned path to follow instead of planning one here. */
initialPath: Path? = null
) : 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, policy: TraversalPolicy? = null): RegularMovementTracker? {
return RegularMovementTracker(entity, walkTarget, policy).takeIf { it.isValid }
}
/** Creates a tracker that follows a path which was already planned, under the policy it was planned with. */
fun following(entity: PathfinderMob, walkTarget: WalkTarget, path: Path, policy: TraversalPolicy?): RegularMovementTracker? {
return RegularMovementTracker(entity, walkTarget, policy, path).takeIf { it.isValid }
}
}
/** The policy given explicitly, otherwise whatever the walk target asks for. */
val policy: TraversalPolicy? = policy ?: (walkTarget as? CobblemonWalkTarget)?.policy
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 && initialPath != null) {
this.path = initialPath
this.lastKnownTargetPos = walkTarget.target.currentBlockPosition()
isValid = true
} else 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
val navigation = entity.navigation
if (policy != null && navigation is OmniPathNavigation) {
// The policy replaces node filtering; destination filtering still applies below.
var planned = navigation.planPath(target, 0, policy)
if (planned != null && walkTarget is CobblemonWalkTarget) {
while (planned.endNode?.type?.let { walkTarget.destinationNodeTypeFilter(it) } == false) {
planned.deleteNode(planned.nodeCount - 1)
}
if (planned.nodeCount == 0) {
planned = null
}
}
path = planned
} else if (walkTarget is CobblemonWalkTarget) {
withNodeFilter(
entity = entity,
nodeFilter = walkTarget.nodeTypeFilter,
) {
var resolvedPath = entity.navigation.createPath(target, 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
}
}
}
@@ -1,293 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.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.api.ai.movement.FluidKind
import com.cobblemon.mod.common.api.ai.movement.MovementCapabilities
import com.cobblemon.mod.common.api.ai.movement.MovementIntent
import com.cobblemon.mod.common.api.ai.movement.Occupancy
import com.cobblemon.mod.common.api.ai.movement.TraversalPolicy
import com.cobblemon.mod.common.entity.OmniPathingEntity
import com.cobblemon.mod.common.entity.ai.CobblemonRandomSurfacePos
import com.cobblemon.mod.common.entity.ai.CobblemonWalkTarget
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
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.entity.ai.util.RandomPos
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 = { entity, pos, _, speedMultiplier ->
CobblemonWalkTarget(
pos = pos,
nodeTypeFilter = { it == PathType.WATER || it == PathType.WATER_BORDER },
speedModifier = speedMultiplier,
completionRange = 0,
// Stays swimming in water, wherever in the water it's comfortable or not.
policy = TraversalPolicy.forIntent(entity, MovementIntent.IDLE) { occupancy, fluid ->
fluid == FluidKind.WATER && (occupancy == Occupancy.SWIM || occupancy == Occupancy.SWIM_SUBMERGED)
}
)
}
),
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
)
},
// Only goes through what it's comfortable with while idling, e.g. no swimming for non-breathers.
policy = TraversalPolicy.forIntent(entity, MovementIntent.IDLE)
)
}
),
/**
* Seafloor walkers in water: they can't swim, but can breathe, so they wander along the bottom (or wade, or step
* out onto land) instead. Neither [LAND] nor [WATER] applies to them while they're in water.
*/
SEABED(
isValid = { entity -> entity.isInWater && isSeafloorWalker(MovementCapabilities.of(entity)) },
choosePosition = { entity, _, _, horizontalRange, verticalRange ->
chooseBottomPosition(entity, horizontalRange, verticalRange)
},
createWalkTarget = { entity, pos, _, speedMultiplier ->
CobblemonWalkTarget(
pos = pos,
speedModifier = speedMultiplier,
completionRange = 0,
policy = TraversalPolicy.forIntent(entity, MovementIntent.IDLE)
)
}
)
;
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
}
}
}
/** Bodies that walk along the bottom of water rather than swimming in it, and can breathe down there. */
private fun isSeafloorWalker(caps: MovementCapabilities) = caps.canWalk && !caps.canSwimInWater && caps.canBreatheUnderwater
private const val BOTTOM_POSITION_SAMPLES = 10
private const val MAX_BOTTOM_DROP = 24
/**
* Picks a random position the entity could stand at, settling sampled points onto the bottom the way a non-swimmer
* would sink onto it. Only positions an idle policy allows are kept; reachability is the pathfinder's job.
*/
private fun chooseBottomPosition(entity: PathfinderMob, horizontalRange: Int, verticalRange: Int): BlockPos? {
val level = entity.level()
val policy = TraversalPolicy.forIntent(entity, MovementIntent.IDLE)
val origin = entity.blockPosition()
val tethering = (entity as? PokemonEntity)?.tethering
repeat(BOTTOM_POSITION_SAMPLES) {
var pos = origin.offset(RandomPos.generateRandomDirection(entity.random, horizontalRange, verticalRange))
var climbed = 0
while (level.getBlockState(pos).isSolid && climbed++ < verticalRange) {
pos = pos.above()
}
var occupancy = Occupancy.at(policy.capabilities, entity.eyeHeight, pos, level::getBlockState)
var dropped = 0
while (occupancy?.isStandable == false && dropped++ < MAX_BOTTOM_DROP && pos.y > level.minBuildHeight) {
pos = pos.below()
occupancy = Occupancy.at(policy.capabilities, entity.eyeHeight, pos, level::getBlockState)
}
if (occupancy == null || pos == origin) {
return@repeat
} else if (policy.cost(occupancy, FluidKind.of(level.getFluidState(pos))) == null) {
return@repeat
} else if (tethering?.canRoamTo(pos) == false) {
return@repeat
}
return pos
}
return null
}
@@ -1,42 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.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)
@@ -1,28 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.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,
}
@@ -1,178 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.ai.biggerbrain.fixation
import com.bedrockk.molang.Expression
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.DistractionType
import com.cobblemon.mod.common.api.ai.movement.MovementIntent
import com.cobblemon.mod.common.api.ai.movement.TraversalPolicy
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.pokemon.ai.moves.RealTimeMove
import com.cobblemon.mod.common.pokemon.ai.moves.RealTimeMoveInstance
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.withQueryValue
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.behavior.EntityTracker
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.entity.ai.memory.WalkTarget
import net.minecraft.world.entity.player.Player
import kotlin.collections.set
class AttackFixation(
val targetEntity: LivingEntity,
/** Distraction types that will be respected. */
private val speedMultiplier: Expression = "1.0".asExpression(),
private val availableMoves: List<RealTimeMove> = emptyList(),
val distractionTypes: Set<DistractionType>,
) : EntityFixation {
private val globalAttackCooldownTicks: Int = 20 // TODO: pull from pokemon stats
var movementTracker: MovementTracker? = null
var lookingTracker: LookingTracker? = null
var activeMoveInstance: RealTimeMoveInstance? = null
/** Ticks until it may plan again after a failed or impossible path, so it holds instead of re-pathing every tick. */
var remainingRepathTicks = 0
override fun getDescription(entity: LivingEntity): String {
return "Attacking Target"
}
override fun canDistract(distraction: Distraction, entity: LivingEntity): Boolean {
return distraction.types.any(distractionTypes::contains)
}
override fun tick(entity: LivingEntity, brain: BiggerBrain): Boolean {
if (entity !is PokemonEntity || !entity.isAlive) return false
entity as PokemonEntity // Intellij and my brain says this is redundant but I'm not getting smart casting later on sooo
val targetedEntity = brain.getMemory(MemoryModuleType.ATTACK_TARGET)
?: return false
if (targetedEntity.isRemoved || targetedEntity.isDeadOrDying || brain.getMemory(MemoryModuleType.AVOID_TARGET) != null) {
brain.eraseMemory(MemoryModuleType.ATTACK_TARGET)
brain.eraseMemory(MemoryModuleType.ANGRY_AT)
return false
}
if (targetedEntity is Player && (targetedEntity.isCreative || targetedEntity.isSpectator)) {
brain.eraseMemory(MemoryModuleType.ATTACK_TARGET)
brain.eraseMemory(MemoryModuleType.ANGRY_AT)
return false
}
// If we have a current target, check if it's still valid and within roam range (if tethered)
if (targetedEntity.isAlive) {
val tethering = (entity).tethering
if (tethering != null && !tethering.canRoamTo(targetedEntity.blockPosition())) {
// Target moved out of range let's kill the memory >:)
brain.distract(Distraction.FORCE)
entity.brain.eraseMemory(MemoryModuleType.ATTACK_TARGET)
return false
}
}
if (activeMoveInstance != null && activeMoveInstance!!.completed) {
activeMoveInstance = null
}
var runtime = mainThreadRuntime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
runtime = runtime.withQueryValue("target", targetedEntity.asMostSpecificMoLangValue())
val speed = runtime.resolveFloat(speedMultiplier)
var selectedMoveIndex = -1
var distanceToTarget = -1.0f
if (!(entity.biggerBrain.getMemory(MemoryModuleType.ATTACK_COOLING_DOWN) ?: false)) {
// Not blocked by global cooldown, check if any available moves can be performed
var highestPriority = Int.MAX_VALUE
distanceToTarget = entity.distanceTo(targetedEntity)
availableMoves.forEachIndexed { index, move ->
// check for cooldown ticks, priority, and range
// TODO: Account for hitbox sizes when doing range checks
if (move.id !in entity.pokemon.realTimeMoveCooldowns && move.priority < highestPriority
&& (distanceToTarget <= move.maxRange || entity.isWithinMeleeAttackRange(targetedEntity))
&& (distanceToTarget >= move.minRange)
) {
selectedMoveIndex = index
highestPriority = move.priority
}
}
if (selectedMoveIndex != -1) {
val selectedMove = availableMoves[selectedMoveIndex]
activeMoveInstance = selectedMove.createInstance(entity, targetedEntity)
activeMoveInstance?.start()
// Reset cooldowns
entity.pokemon.realTimeMoveCooldowns[selectedMove.id] = selectedMove.cooldownTicks
brain.setMemory(MemoryModuleType.ATTACK_COOLING_DOWN, true, globalAttackCooldownTicks.toLong())
}
}
if (lookingTracker != null) {
lookingTracker!!.tick()
}
if (movementTracker != null) {
val movementStatus = movementTracker!!.tick()
if (movementStatus != MovementStatus.PENDING) {
movementTracker!!.stop()
movementTracker = null
if (movementStatus == MovementStatus.FAILED) {
remainingRepathTicks = REPATH_COOLDOWN_TICKS
}
}
} else if (remainingRepathTicks > 0) {
remainingRepathTicks--
if (lookingTracker == null) {
lookingTracker = LookingTracker.forTargetEntity(entity, targetedEntity, trackEyeHeight = true)
}
} else {
// Not moving right now and we need to figure out to what distance to the enemy we want to move to
var idealDistanceToTarget = Float.MAX_VALUE
var highestPriority = Int.MAX_VALUE
availableMoves.forEachIndexed { index, move ->
// We want the highest priority move that's not on cooldown
if (move.id !in entity.pokemon.realTimeMoveCooldowns && move.priority < highestPriority) {
highestPriority = move.priority
idealDistanceToTarget = move.maxRange
}
}
// Get distance to target if we didn't initialize it in the earlier block
if (distanceToTarget < 0) {
distanceToTarget = entity.distanceTo(targetedEntity)
}
if (idealDistanceToTarget < distanceToTarget) {
val walkTarget = WalkTarget(EntityTracker(targetedEntity, true), speed, idealDistanceToTarget.toInt())
val policy = TraversalPolicy.forIntent(entity, MovementIntent.URGENT)
movementTracker = RegularMovementTracker.attempt(entity, walkTarget, policy)
if (movementTracker == null) {
remainingRepathTicks = REPATH_COOLDOWN_TICKS
}
lookingTracker = LookingTracker.forTargetEntity(entity, targetedEntity, trackEyeHeight = true)
} else if (lookingTracker == null) {
// Fixate at looking at the target so we don't wander off
lookingTracker = LookingTracker.forTargetEntity(entity, targetedEntity, trackEyeHeight = true)
}
}
return true
}
companion object {
const val REPATH_COOLDOWN_TICKS = 20
}
}
@@ -1,29 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.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()
}
@@ -1,31 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.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
}
}
@@ -1,39 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.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
}
@@ -1,18 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.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
@@ -1,43 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.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.distraction.Distraction
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.DistractionType
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.PathfinderMob
class LookAndLockFixation(
override val lookingTracker: LookingTracker?,
/** Distraction types that will be respected. */
val distractionTypes: Set<DistractionType>,
val condition: () -> Boolean
) : EntityFixation, LookingTrackerFixation {
override fun getDescription(entity: LivingEntity): String {
return "Looking at a nearby entity (Locked)"
}
override fun canDistract(distraction: Distraction, entity: LivingEntity): Boolean {
return distraction.types.any(distractionTypes::contains)
}
override fun tick(entity: LivingEntity, brain: BiggerBrain): Boolean {
val targetEntity = lookingTracker?.entity ?: return false
if ((targetEntity.isRemoved) || entity !is PathfinderMob) {
return false
}
val lookingTracker = lookingTracker
lookingTracker.tick()
return condition()
}
}
@@ -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.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
}
}
@@ -1,27 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.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?
}
@@ -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.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()
}
}
}
@@ -1,167 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.ai.biggerbrain.objective
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.Priority
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.distraction.Distraction
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.DistractionType
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.AttackFixation
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.molang.MoLangFunctions.asMostSpecificMoLangValue
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.mainThreadRuntime
import com.cobblemon.mod.common.util.resolveInt
import com.cobblemon.mod.common.util.resolveString
import com.cobblemon.mod.common.util.withQueryValue
import com.mojang.datafixers.util.Either
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.memory.MemoryModuleType
class AggressiveBehaviour(
speedMultiplier: Expression = "0.5".asExpression(), // How do we transfer speed to the attack behaviour? Do we want to?
engageRange: Expression = "15.0".asExpression(),
disengageRange: Expression = "20.0".asExpression(),
condition: Expression = "true".asExpression(), // Self/environment condition check
targetCondition: Expression = "true".asExpression(), // target condition (will be checked on entity interaction)
description: Expression = "'Aggressive Behaviour'".asExpression(),
val maxAttackDuration: Expression = "300".asExpression(),
) : EntityResponseObjective(
speedMultiplier = speedMultiplier, // How do we transfer speed to the attack behaviour? Do we want to?
engageRange = engageRange,
disengageRange = disengageRange,
condition = condition, // Self/environment condition check
targetCondition = targetCondition, // target condition (will be checked on entity interaction)
description = description,
) {
private var remainingCooldownTicks = 0
private var attackFixation : EntityFixation? = null
override fun perform(
entity: LivingEntity,
brain: BiggerBrain,
activeObjectiveTags: MutableSet<String>
): ObjectiveStatus {
if (entity !is PokemonEntity || !entity.isAlive) return ObjectiveStatus.INVALID
if (brain.getMemory(MemoryModuleType.AVOID_TARGET) != null) {
return ObjectiveStatus.BLOCKED
}
val attackTarget = brain.getMemory(MemoryModuleType.ATTACK_TARGET)
if (trackedEntity != null) {
// has active target
if (attackTarget != null && trackedEntity?.uuid == attackTarget.uuid) {
// Check if we continue
if (shouldContinue(entity, brain)) {
return ObjectiveStatus.ACTIVE
} else {
brain.distract(Distraction.FORCE)
attackFixation = null
brain.eraseMemory(MemoryModuleType.ATTACK_TARGET)
brain.eraseMemory(CobblemonMemories.ATTACK_TARGET_DATA)
return ObjectiveStatus.INACTIVE
}
} else {
// Something has overridden us and independently set the attack target
trackedEntity = null
attackFixation = null
return ObjectiveStatus.BLOCKED
}
} else {
// No active target
if (remainingCooldownTicks > 0) {
remainingCooldownTicks--
return ObjectiveStatus.INACTIVE
} else {
remainingCooldownTicks = 20
}
val targetEntity = scanForValidTargets(entity, brain)
if (targetEntity != null && brain.distract(Distraction.FORCE)) {
val runtime = mainThreadRuntime
val attackDuration = runtime.resolveInt(maxAttackDuration)
trackedEntity = targetEntity
brain.setMemory(MemoryModuleType.ATTACK_TARGET, targetEntity, attackDuration.toLong())
attackFixation = brain.fixate(
AttackFixation(
targetEntity = trackedEntity!!,
speedMultiplier = speedMultiplier,
distractionTypes = setOf(DistractionType.FORCE),
availableMoves = getRealTimeMoves(entity)
)
)
}
}
return if (trackedEntity != null) ObjectiveStatus.ACTIVE else ObjectiveStatus.INACTIVE
}
override fun onRegister(entity: LivingEntity, brain: BiggerBrain) {
brain.subscribe(
priority = Priority.LOW,
memory = CobblemonMemories.ATTACK_TARGET_DATA
) { attackData, labels ->
val targetEntity = brain.getMemory(MemoryModuleType.ATTACK_TARGET) ?: return@subscribe true
if (trackedEntity?.uuid == targetEntity.uuid) {
// We smacked them, refresh our attack timer
val runtime = mainThreadRuntime.withQueryValue("entity", (targetEntity).asMostSpecificMoLangValue())
val attackDuration = runtime.resolveInt(maxAttackDuration)
brain.setMemory(MemoryModuleType.ATTACK_TARGET, targetEntity, attackDuration.toLong())
}
return@subscribe true
}
}
class Config : ObjectiveConfig {
val speedMultiplier: ExpressionOrEntityVariable = Either.left("0.5".asExpression())
val engageRange: ExpressionOrEntityVariable = Either.left("15.0".asExpression())
val disengageRange: ExpressionOrEntityVariable = Either.left("20.0".asExpression())
val condition: ExpressionOrEntityVariable = Either.left("true".asExpression())
val targetCondition: ExpressionOrEntityVariable = Either.left("false".asExpression())
val maxAttackDuration: ExpressionOrEntityVariable = Either.left("300".asExpression())
val description: ExpressionOrEntityVariable = Either.left("'Aggressive Behaviour'".asExpression())
override fun getVariables(
entity: LivingEntity,
behaviourConfigurationContext: BehaviourConfigurationContext
): List<MoLangConfigVariable> {
return listOf(
condition,
targetCondition,
speedMultiplier,
engageRange,
disengageRange,
maxAttackDuration,
description
).asVariables()
}
override fun create(
entity: LivingEntity,
behaviourConfigurationContext: BehaviourConfigurationContext
): Objective {
return AggressiveBehaviour(
condition = condition.asSimplifiedExpression(entity),
targetCondition = targetCondition.asSimplifiedExpression(entity),
speedMultiplier = speedMultiplier.asSimplifiedExpression(entity),
engageRange = engageRange.asSimplifiedExpression(entity),
disengageRange = disengageRange.asSimplifiedExpression(entity),
maxAttackDuration = maxAttackDuration.asSimplifiedExpression(entity),
description = description.asSimplifiedExpression(entity)
)
}
}
}
@@ -1,196 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.ai.biggerbrain.objective
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.Priority
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.CobblemonAttackTargetData
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.fixation.EntityFixation
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.LookAndMoveFixation
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.movement.MovementIntent
import com.cobblemon.mod.common.api.ai.movement.TraversalPolicy
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.mainThreadRuntime
import com.cobblemon.mod.common.util.resolveFloat
import com.cobblemon.mod.common.util.resolveInt
import com.cobblemon.mod.common.util.withQueryValue
import com.mojang.datafixers.util.Either
import net.minecraft.world.InteractionHand
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.behavior.EntityTracker
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.entity.ai.memory.WalkTarget
import net.minecraft.world.entity.player.Player
class AttackTargetMelee(
private val speedMultiplier: Expression = "1.0".asExpression(),
private val attackRange: Expression = "2.0".asExpression(),
private val attackCooldownTicks: Int = 20
) : Objective {
private var movementTask: EntityFixation? = null
private var movementTracker: MovementTracker? = null
private var remainingCooldownTicks = 0
/** Ticks until the chase may plan again after failing, so an unreachable target doesn't get re-pathed every tick. */
private var remainingRepathTicks = 0
override fun getDescription(entity: LivingEntity) = "Attack target melee"
override fun perform(
entity: LivingEntity,
brain: BiggerBrain,
activeObjectiveTags: MutableSet<String>
): ObjectiveStatus {
if (entity !is PokemonEntity || !entity.isAlive) return ObjectiveStatus.INVALID
val target = brain.getMemory(MemoryModuleType.ATTACK_TARGET)
?: return ObjectiveStatus.INACTIVE
if (target.isRemoved || target.isDeadOrDying) {
brain.eraseMemory(MemoryModuleType.ATTACK_TARGET)
brain.eraseMemory(MemoryModuleType.ANGRY_AT)
return ObjectiveStatus.INACTIVE
}
if (target is Player && (target.isCreative || target.isSpectator)) {
brain.eraseMemory(MemoryModuleType.ATTACK_TARGET)
brain.eraseMemory(MemoryModuleType.ANGRY_AT)
return ObjectiveStatus.INACTIVE
}
if (remainingCooldownTicks > 0) {
remainingCooldownTicks--
}
val runtime = mainThreadRuntime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
val range = runtime.resolveFloat(attackRange)
val speed = runtime.resolveFloat(speedMultiplier)
// This is melee thing, but not everything will do melee in the future
if (entity.distanceToSqr(target) <= range * range && entity.hasLineOfSight(target)) {
if (remainingCooldownTicks <= 0) {
if (entity.doHurtTarget(target)) {
entity.swing(InteractionHand.MAIN_HAND)
var attackTargetData = brain.getMemory(CobblemonMemories.ATTACK_TARGET_DATA)
if (attackTargetData != null) {
attackTargetData.hitsLanded += 1
} else {
attackTargetData = CobblemonAttackTargetData()
attackTargetData.hitsLanded = 1
}
brain.setMemory(CobblemonMemories.ATTACK_TARGET_DATA, attackTargetData)
}
remainingCooldownTicks = attackCooldownTicks
}
return ObjectiveStatus.ACTIVE
}
// If we have a current target, check if it's still valid and within roam range (if tethered)
if (target.isAlive) {
val tethering = (entity).tethering
if (tethering != null && !tethering.canRoamTo(target.blockPosition())) {
// Target moved out of range let's kill the memory >:)
brain.distract(Distraction.FORCE)
entity.brain.eraseMemory(MemoryModuleType.ATTACK_TARGET)
}
}
if (brain.isFixation(movementTask)) {
return ObjectiveStatus.FIXATED
}
if (movementTracker?.lastStatus == MovementStatus.FAILED) {
// Got as close as it's willing to (e.g. the bank of a lake it won't enter). Hold there for a bit.
movementTracker = null
remainingRepathTicks = REPATH_COOLDOWN_TICKS
}
if (remainingRepathTicks > 0) {
remainingRepathTicks--
entity.lookControl.setLookAt(target)
return ObjectiveStatus.ACTIVE
}
if (!brain.distract(Distraction.FORCE)) return ObjectiveStatus.BLOCKED
val walkTarget = WalkTarget(EntityTracker(target, true), speed, 1)
val policy = TraversalPolicy.forIntent(entity, MovementIntent.URGENT)
val movementTracker = RegularMovementTracker.attempt(entity, walkTarget, policy)
this.movementTracker = movementTracker
if (movementTracker == null) {
remainingRepathTicks = REPATH_COOLDOWN_TICKS
return ObjectiveStatus.ACTIVE
}
movementTask = brain.fixate(LookAndMoveFixation(
distractionTypes = setOf(DistractionType.FORCE),
lookingTracker = LookingTracker.forTargetEntity(entity, target, trackEyeHeight = true),
movementTracker = movementTracker,
description = "Moving to attack target"))
return ObjectiveStatus.FIXATED
}
override fun onRegister(entity: LivingEntity, brain: BiggerBrain) {
brain.subscribe(
priority = Priority.LOW,
memory = MemoryModuleType.ATTACK_TARGET
) { value, labels ->
if (value == null) {
if (brain.isFixation(movementTask)) {
brain.distract(Distraction.FORCE)
movementTask = null
}
}
return@subscribe true
}
}
companion object {
const val REPATH_COOLDOWN_TICKS = 20
}
class Config : ObjectiveConfig {
val speedMultiplier: ExpressionOrEntityVariable = Either.left("1.0".asExpression())
val attackRange: ExpressionOrEntityVariable = Either.left("2.0".asExpression())
val attackCooldownTicks: ExpressionOrEntityVariable = Either.left("20".asExpression())
override fun getVariables(
entity: LivingEntity,
behaviourConfigurationContext: BehaviourConfigurationContext
): List<MoLangConfigVariable> {
return listOf(speedMultiplier, attackRange, attackCooldownTicks).asVariables()
}
override fun create(
entity: LivingEntity,
behaviourConfigurationContext: BehaviourConfigurationContext
): Objective {
return AttackTargetMelee(
speedMultiplier = speedMultiplier.asSimplifiedExpression(entity),
attackRange = attackRange.asSimplifiedExpression(entity),
attackCooldownTicks = behaviourConfigurationContext.runtime.resolveInt(
attackCooldownTicks.asSimplifiedExpression(entity)
)
)
}
}
}
@@ -1,173 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.ai.biggerbrain.objective
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.api.Priority
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.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.molang.MoLangFunctions.asMostSpecificMoLangValue
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.mainThreadRuntime
import com.cobblemon.mod.common.util.resolveFloat
import com.cobblemon.mod.common.util.withQueryValue
import com.mojang.datafixers.util.Either
import net.minecraft.core.BlockPos
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.entity.ai.memory.WalkTarget
import net.minecraft.world.entity.ai.util.LandRandomPos
import net.minecraft.world.entity.player.Player
class AvoidTarget(
private val speedMultiplier: Expression = "0.5".asExpression(),
) : Objective {
private var movementTask: EntityFixation? = null
private var remainingCooldownTicks = 0
override fun getDescription(entity: LivingEntity) = "Avoiding target"
override fun perform(
entity: LivingEntity,
brain: BiggerBrain,
activeObjectiveTags: MutableSet<String>
): ObjectiveStatus {
if (entity !is PokemonEntity || !entity.isAlive) return ObjectiveStatus.INVALID
val target = brain.getMemory(MemoryModuleType.AVOID_TARGET)
?: return ObjectiveStatus.INACTIVE
if (target.isRemoved || target.isDeadOrDying) {
brain.eraseMemory(MemoryModuleType.AVOID_TARGET)
return ObjectiveStatus.INACTIVE
}
if (target is Player && (target.isCreative || target.isSpectator)) {
brain.eraseMemory(MemoryModuleType.AVOID_TARGET)
return ObjectiveStatus.INACTIVE
}
if (brain.isFixation(movementTask)) {
return ObjectiveStatus.FIXATED
}
if (remainingCooldownTicks > 0) {
remainingCooldownTicks--
}
val runtime = mainThreadRuntime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
val speed = runtime.resolveFloat(speedMultiplier)
// If we have a current target, check if it's still valid and within roam range (if tethered)
if (target.isAlive) {
val tethering = (entity).tethering
if (tethering != null && !tethering.canRoamTo(target.blockPosition())) {
// Target moved out of range let's kill the memory >:)
brain.distract(Distraction.FORCE)
entity.brain.eraseMemory(MemoryModuleType.AVOID_TARGET)
}
}
if (brain.isFixation(movementTask)) {
return ObjectiveStatus.FIXATED
}
if (!brain.distract(Distraction.FORCE)) return ObjectiveStatus.BLOCKED
var targetPos : BlockPos? = null
// TODO: utilize wander target rules to find a valid target position
val direction = entity.position().subtract(target.position())
for (j in 0..9) {
val targetVec3 = LandRandomPos.getPosAway(entity, 16, 7, direction)
if (targetVec3 != null) {
targetPos = BlockPos.containing(targetVec3)
break
}
}
// val walkTarget = WanderAround.generateWalkTarget(entity)
if (targetPos != null) {
val movementTracker = RegularMovementTracker.attempt(
entity = entity,
walkTarget = WalkTarget(
targetPos,
speed,
0
)
)
if (movementTracker != null) {
movementTask = brain.fixate(
LookAndMoveFixation(
distractionTypes = setOf(
DistractionType.FORCE
),
lookingTracker = LookingTracker.fromNavigationPathOrWalkTarget(
entity,
movementTracker.walkTarget
),
movementTracker = movementTracker,
description = "Moving away from target entity",
)
)
return ObjectiveStatus.FIXATED
}
}
return ObjectiveStatus.ACTIVE
}
override fun onRegister(entity: LivingEntity, brain: BiggerBrain) {
brain.subscribe(
priority = Priority.LOW,
memory = MemoryModuleType.AVOID_TARGET
) { value, labels ->
if (value == null) {
if (brain.isFixation(movementTask)) {
brain.distract(Distraction.FORCE)
movementTask = null
}
}
return@subscribe true
}
}
class Config : ObjectiveConfig {
val speedMultiplier: ExpressionOrEntityVariable = Either.left("0.5".asExpression())
override fun getVariables(
entity: LivingEntity,
behaviourConfigurationContext: BehaviourConfigurationContext
): List<MoLangConfigVariable> {
return listOf(speedMultiplier).asVariables()
}
override fun create(
entity: LivingEntity,
behaviourConfigurationContext: BehaviourConfigurationContext
): Objective {
return AvoidTarget(
speedMultiplier = speedMultiplier.asSimplifiedExpression(entity),
)
}
}
}
@@ -1,306 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.ai.biggerbrain.objective
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.ai.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.fixation.DurationLockFixation
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 as resolveVariableInt
import com.cobblemon.mod.common.api.breeding.CobblemonBreeding
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
import com.cobblemon.mod.common.api.nest.WildNestType
import com.cobblemon.mod.common.api.nest.WildNestTypes
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
import com.cobblemon.mod.common.block.WildNestBlock
import com.cobblemon.mod.common.block.entity.NestBlockEntity
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.asExpression
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.withQueryValue
import com.mojang.datafixers.util.Either
import net.minecraft.core.BlockPos
import net.minecraft.core.particles.ParticleTypes
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.memory.WalkTarget
import net.minecraft.world.level.block.state.properties.BlockStateProperties
import net.minecraft.world.phys.Vec3
class Breed(
private val speedMultiplier: Expression = "0.4".asExpression(),
private val condition: Expression = "true".asExpression(),
private val layDurationTicks: Int = 40
) : Objective {
private var fixation: EntityFixation? = null
private var mateFixation: EntityFixation? = null
override fun getDescription(entity: LivingEntity) = "Breed"
override fun perform(entity: LivingEntity, brain: BiggerBrain, activeObjectiveTags: MutableSet<String>): ObjectiveStatus {
if (entity !is PokemonEntity || !entity.isAlive)
return ObjectiveStatus.INVALID
val world = entity.level() as? ServerLevel ?: return ObjectiveStatus.INVALID
val mate = brain.getMemory(CobblemonMemories.POTENTIAL_MATE) as? PokemonEntity ?: return ObjectiveStatus.INACTIVE
if (!mate.isAlive || mate.level() != world)
return ObjectiveStatus.INACTIVE
// ignore wild pokemon who are already parents of a nest
if (entity.pokemon.isWild() && brain.hasMemoryValue(CobblemonMemories.PATERNAL_NEST_LOCATION))
return ObjectiveStatus.INACTIVE
// make sure either parent doesn't have an active breeding cooldown (do we want to only do this for the females?)
if (entity.pokemon.breedingCooldown != 0 || mate.pokemon.breedingCooldown != 0)
return ObjectiveStatus.INACTIVE
// is breeding disabled from any preset conditions
if (!mainThreadRuntime.withQueryValue("entity", entity.asMostSpecificMoLangValue()).resolveBoolean(condition))
return ObjectiveStatus.INACTIVE
// try to get one parent to lead the other one and have the other one not do this stuff
if (entity.uuid > mate.uuid)
return ObjectiveStatus.INACTIVE
val nestPos = brain.getMemory(CobblemonMemories.NEST_LOCATION)
if (nestPos != null)
return breedAtNest(entity, brain, mate, world, nestPos)
val idealNestPos = brain.getMemory(CobblemonMemories.IDEAL_NEST_LOCATION) ?: return ObjectiveStatus.INACTIVE
return buildNest(entity, brain, mate, world, idealNestPos)
}
// get both parents to go to the nest and breed once within range
private fun breedAtNest(entity: PokemonEntity, brain: BiggerBrain, mate: PokemonEntity, world: ServerLevel, nestPos: BlockPos): ObjectiveStatus {
val nest = world.getBlockEntity(nestPos) as? NestBlockEntity ?: return ObjectiveStatus.INACTIVE
if (!canUseNest(entity, mate, world, nest))
return ObjectiveStatus.INACTIVE
if (!nest.getItem().isEmpty)
return ObjectiveStatus.INACTIVE
// make sure these lovers claim the nest because others may try to at the same time
nest.nestClaimedBy = Pair(entity.uuid, mate.uuid)
val nestCenter = Vec3.atCenterOf(nestPos)
val speed = mainThreadRuntime.withQueryValue("entity", entity.asMostSpecificMoLangValue()).resolveFloat(speedMultiplier)
// path to the nest site with mate until close enough (big bois had issues so we are also going to try to make it easier for them)
if (entity.position().distanceTo(nestCenter) > closeEnough(entity) || mate.position().distanceTo(nestCenter) > closeEnough(mate)) {
bringMate(mate, nestCenter, speed)
return walkToNest(entity, brain, nestCenter, speed, "in Breed fixation: going to nest")
}
return layEgg(entity, brain, mate, world, nest, nestPos)
}
// have both mates go to a ideal spot to make a nest
private fun buildNest(entity: PokemonEntity, brain: BiggerBrain, mate: PokemonEntity, world: ServerLevel, nestPos: BlockPos): ObjectiveStatus {
// probably should stop trying to build nest if there is an attack target
if (brain.hasMemoryValue(MemoryModuleType.ATTACK_TARGET))
return ObjectiveStatus.INACTIVE
// find out what kinds of nests this couple can make togethr
val types = WildNestTypes.combinedTypes(entity, mate)
if (types.isEmpty())
return ObjectiveStatus.INACTIVE
val nestCenter = Vec3.atCenterOf(nestPos)
val speed = mainThreadRuntime.withQueryValue("entity", entity.asMostSpecificMoLangValue()).resolveFloat(speedMultiplier)
// keep going until both mates are not too far from the nest
if (entity.position().distanceTo(nestCenter) > closeEnough(entity) || mate.position().distanceTo(nestCenter) > closeEnough(mate)) {
bringMate(mate, nestCenter, speed)
return walkToNest(entity, brain, nestCenter, speed, "in Breed fixation: going to nest")
}
// get our nest they are about to build and place it
val nestToBuild = WildNestTypes.resolvePlacement(types, world.getBlockState(nestPos.below()), world.getFluidState(nestPos)) ?: return ObjectiveStatus.INACTIVE
val nestBlock = WildNestTypes.blockFor(nestToBuild.type) as? WildNestBlock ?: return ObjectiveStatus.INACTIVE
var nestState = nestBlock.defaultBlockState().setValue(BlockStateProperties.WATERLOGGED, nestToBuild.waterlogged)
nestBlock.variantProperty?.let { nestState = nestState.setValue(it, nestToBuild.variant) }
world.setBlock(nestPos, nestState, 3)
// set the parents of the nest (hope this isn't too early to do this, might need to be after egg laying instead..)
(world.getBlockEntity(nestPos) as? NestBlockEntity)?.nestClaimedBy = Pair(entity.uuid, mate.uuid)
for (parentBrain in listOf(brain, mate.biggerBrain)) {
parentBrain.eraseMemory(CobblemonMemories.IDEAL_NEST_LOCATION)
// give our newly made nest to the parents now taht it exists
parentBrain.setMemory(CobblemonMemories.NEST_LOCATION, nestPos)
}
return ObjectiveStatus.ACTIVE
}
// make da baby and tell the parents about their new responsibility in life
private fun layEgg(entity: PokemonEntity, brain: BiggerBrain, mate: PokemonEntity, world: ServerLevel, nest: NestBlockEntity, nestPos: BlockPos): ObjectiveStatus {
if (!brain.distract(Distraction.FORCE))
return ObjectiveStatus.BLOCKED
val egg = CobblemonBreeding.createEgg(entity.pokemon, mate.pokemon) ?: return ObjectiveStatus.INACTIVE
nest.setItem(egg)
// wild nests are probably the only ones where we need to record the parents onto the nest
if (nest.ownerUUID == null) {
nest.parents = Pair(entity.uuid, mate.uuid)
nest.nestClaimedBy = Pair(entity.uuid, mate.uuid)
}
for (parent in listOf(entity, mate)) {
// might want to do the destiny knot damaging here for each parent that has one
if (Cobblemon.config.canDestinyKnotBreak)
if (parent.pokemon.heldItem.item == CobblemonItems.DESTINY_KNOT.asItem()) {
parent.pokemon.heldItemNoCopy().damageValue += 1
}
parent.pokemon.breedingCooldown = CobblemonBreeding.resetBreedingCooldown(parent.pokemon)
parent.biggerBrain.setMemory(CobblemonMemories.PATERNAL_NEST_LOCATION, nestPos)
parent.biggerBrain.setMemory(CobblemonMemories.EGG_IN_NEST, true)
parent.biggerBrain.eraseMemory(CobblemonMemories.IDEAL_NEST_LOCATION)
// todo do we want to use our own particles? look at this later
world.sendParticles(
ParticleTypes.HEART,
parent.getRandomX(1.0),
parent.getRandomY() + 0.5,
parent.getRandomZ(1.0),
4 + parent.random.nextInt(2),
0.2 * parent.random.nextGaussian(),
0.1 * parent.random.nextGaussian(),
0.2 * parent.random.nextGaussian(),
0.02
)
}
// try to keep them in one spot for a bit to give time for the magic to happen
fixation = brain.fixate(DurationLockFixation("Breed: laying egg", layDurationTicks))
if (mate.biggerBrain.distract(Distraction.FORCE)) {
mateFixation = mate.biggerBrain.fixate(DurationLockFixation("Breed: laying egg as the mate", layDurationTicks))
}
return ObjectiveStatus.FIXATED
}
// path to the nest location
private fun walkToNest(entity: PokemonEntity, brain: BiggerBrain, target: Vec3, speed: Float, description: String): ObjectiveStatus {
if (brain.isFixation(fixation))
return ObjectiveStatus.FIXATED
// wait for mate to get here
if (entity.position().distanceTo(target) <= closeEnough(entity))
return ObjectiveStatus.ACTIVE
if (!brain.distract(comfortDistraction))
return ObjectiveStatus.BLOCKED
val tracker = RegularMovementTracker.attempt(entity, WalkTarget(target, speed, closeEnough(entity).toInt())) ?: return ObjectiveStatus.ACTIVE
fixation = brain.fixate(
LookAndMoveFixation(
distractionTypes = setOf(DistractionType.DAMAGE, DistractionType.FORCE),
description = description,
lookingTracker = LookingTracker.fromNavigationPathOrWalkTarget(entity, tracker.walkTarget),
movementTracker = tracker
)
)
return ObjectiveStatus.FIXATED
}
// have the other mates be told where to go
private fun bringMate(mate: PokemonEntity, target: Vec3, speed: Float) {
val mateBrain = mate.biggerBrain
if (mate.position().distanceTo(target) <= closeEnough(mate) || mateBrain.isFixation(mateFixation))
return
if (!mateBrain.distract(comfortDistraction))
return
val tracker = RegularMovementTracker.attempt(mate, WalkTarget(target, speed, closeEnough(mate).toInt())) ?: return
mateFixation = mateBrain.fixate(
LookAndMoveFixation(
distractionTypes = setOf(DistractionType.DAMAGE, DistractionType.FORCE),
description = "Breed: folow mate",
lookingTracker = LookingTracker.fromNavigationPathOrWalkTarget(mate, tracker.walkTarget),
movementTracker = tracker
)
)
}
// checks for what nests are valid for the mates
private fun canUseNest(entity: PokemonEntity, mate: PokemonEntity, world: ServerLevel, nest: NestBlockEntity): Boolean {
val nestType = (nest.blockState.block as? WildNestBlock)?.type ?: return false
if (!isNestTypeAllowed(nestType, entity, mate))
return false
val waterlogged = nest.blockState.getOptionalValue(BlockStateProperties.WATERLOGGED).orElse(false)
if (!isWaterGroupCompatible(entity, mate, waterlogged))
return false
// check if the nest is claimed by other pokemon that exist and maybe on their way to it (CRAB NOTE: This helps prevent swarms of mons going to the same nest which looked weird)
val (existingParent1, existingParent2) = nest.nestClaimedBy
val claimedByMate1 = existingParent1 != null && existingParent1 != entity.uuid && world.getEntity(existingParent1) != null
val claimedByMate2 = existingParent2 != null && existingParent2 != entity.uuid && world.getEntity(existingParent2) != null
if (claimedByMate1 && claimedByMate2)
return false
return true
}
// make sure the nest type works for the mates and that wild use wild nests and owned pokemon use player placed nests
private fun isNestTypeAllowed(nestType: WildNestType, entity: PokemonEntity, mate: PokemonEntity): Boolean {
val allowed = WildNestTypes.combinedTypes(entity, mate).toMutableSet()
if (!entity.pokemon.isWild() && !mate.pokemon.isWild()) {
allowed.add(WildNestType.PLAYER)
allowed.add(WildNestType.KELP)
}
if (nestType == WildNestType.PLAYER || nestType == WildNestType.KELP)
return !entity.pokemon.isWild() && nestType in allowed
return nestType in allowed
}
// waterlogged nests only allow parents that both have a water egg group and land nests dont work for mates that only have water egg groups
private fun isWaterGroupCompatible(entity: PokemonEntity, mate: PokemonEntity, nestWaterlogged: Boolean): Boolean {
val mate1Groups = entity.pokemon.species.eggGroups.map { it.name.lowercase() }
val mate2Groups = mate.pokemon.species.eggGroups.map { it.name.lowercase() }
val mate1HasWaterGroup = mate1Groups.any { it in WATER_EGG_GROUPS || it == "ditto" }
val mate2HasWaterGroup = mate2Groups.any { it in WATER_EGG_GROUPS || it == "ditto" }
val mate1OnlyWaterGroup = mate1Groups.all { it in WATER_EGG_GROUPS }
val mate2OnlyWaterGroup = mate2Groups.all { it in WATER_EGG_GROUPS }
return if (nestWaterlogged) mate1HasWaterGroup && mate2HasWaterGroup else !(mate1OnlyWaterGroup || mate2OnlyWaterGroup)
}
class Config : ObjectiveConfig {
val speedMultiplier: ExpressionOrEntityVariable = Either.left("0.4".asExpression())
val condition: ExpressionOrEntityVariable = Either.left("true".asExpression())
val layDurationTicks: ExpressionOrEntityVariable = Either.left("40".asExpression())
override fun getVariables(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): List<MoLangConfigVariable> {
return listOf(speedMultiplier, condition, layDurationTicks).asVariables()
}
override fun create(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): Objective {
behaviourConfigurationContext.addMemories(
CobblemonMemories.POTENTIAL_MATE,
CobblemonMemories.NEST_LOCATION,
CobblemonMemories.PATERNAL_NEST_LOCATION,
CobblemonMemories.IDEAL_NEST_LOCATION,
CobblemonMemories.EGG_IN_NEST,
MemoryModuleType.ATTACK_TARGET
)
return Breed(
speedMultiplier = speedMultiplier.asSimplifiedExpression(entity),
condition = condition.asSimplifiedExpression(entity),
layDurationTicks = layDurationTicks.resolveVariableInt(behaviourConfigurationContext.runtime)
)
}
}
// lets help those with more cushion for the pushing reach those nests to breed
private fun closeEnough(entity: PokemonEntity) = CLOSE_ENOUGH_DISTANCE + entity.bbWidth / 2
companion object {
private const val CLOSE_ENOUGH_DISTANCE = 1.5 // we will have this be our baseline and then scale based on hitbox (like above) for the bigger mons
private val WATER_EGG_GROUPS = setOf("water_1", "water_2", "water_3")
}
}
@@ -1,167 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.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.resolveFloat
import com.cobblemon.mod.common.api.ai.resolveInt
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.entity.pokemon.ai.PokemonMoveControl
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
/**
* Swim or fly in a circle objective
*/
class CircleAround(
val poseTypes: Set<PoseType>,
val minTurnAngleDegrees: Float,
val maxTurnAngleDegrees: Float,
val verticalSpeed: Float,
val speed: Float,
val minDurationTicks: Int = 60,
val maxDurationTicks: Int = 180
) : Objective {
var chosenTurnAngle = 0F
var chosenDuration = 0
var fixation: EntityFixation? = null
var moveControl: PokemonMoveControl? = null
override fun getDescription(entity: LivingEntity) = "Circling"
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 (entity.getCurrentPoseType() !in poseTypes) {
return ObjectiveStatus.BLOCKED
} else if (brain.fixation != null) {
fixation = null
return ObjectiveStatus.BLOCKED // Since this doesn't distract anything then if the brain is fixated at all we are blocked
}
// TODO: should this not be able to distract anything? relates to the above fixation comment
// if (!brain.distract(idleBoredomDistraction)) {
// return ObjectiveStatus.BLOCKED
// }
entity.brain.eraseMemory(MemoryModuleType.WALK_TARGET)
entity.navigation.stop()
// Gather a random turn angle and random duration
this.chosenTurnAngle = entity.random.nextFloat() * (maxTurnAngleDegrees - minTurnAngleDegrees) + minTurnAngleDegrees
if (entity.random.nextBoolean()) {
chosenTurnAngle = -chosenTurnAngle
}
chosenDuration = entity.random.nextIntBetweenInclusive(minDurationTicks, maxDurationTicks)
// Grab moveControl off the entity to use during fixation
this.moveControl = entity.moveControl as PokemonMoveControl?
// Become fixated
fixation = brain.fixate(
CircleAroundFixation(
this,
null,
chosenDuration
)
)
return ObjectiveStatus.ACTIVE //TODO: should this have a cooldown? Currently just having it as the pure fallback and able to be distracted by anything. I guess fishes should always swim... probably not but I'll sort that later
}
class CircleAroundFixation(
val objective: CircleAround,
override val movementTracker: MovementTracker?,
var timer: Int
) : EntityFixation, MovementTrackerFixation {
override fun getDescription(entity: LivingEntity) = "Circling around at ${this.objective.chosenTurnAngle} degrees for ${this.timer} more ticks"
override fun onDistract(distraction: Distraction, entity: LivingEntity) {
super.onDistract(distraction, entity)
objective.moveControl?.stopBanking()
}
// can be distracted by anything
override fun canDistract(distraction: Distraction, entity: LivingEntity): Boolean {
return true
}
override fun tick(entity: LivingEntity, brain: BiggerBrain): Boolean {
objective.moveControl?.startBanking(
forwardBlocksPerTick = objective.speed,
upwardsBlocksPerTick = objective.verticalSpeed,
rightDegreesPerTick = objective.chosenTurnAngle,
durationTicks = 2 //TODO: is it odd we have the check in multiple spots?
)
this.timer--
if (this.timer < 0 || (entity as PokemonEntity).getCurrentPoseType() !in objective.poseTypes) {
objective.moveControl?.stopBanking()
return false
}
return true
}
}
class Config : ObjectiveConfig {
val poseTypes: Set<PoseType> = setOf(PoseType.FLY, PoseType.HOVER)
val minAngularVelocityDegrees: ExpressionOrEntityVariable = Either.left("0.0".asExpression())
val maxAngularVelocityDegrees: ExpressionOrEntityVariable = Either.left("1.0".asExpression())
val speed: ExpressionOrEntityVariable = Either.left("0.6".asExpression())
val verticalSpeed: ExpressionOrEntityVariable = Either.left("0.0".asExpression())
val minDurationTicks: ExpressionOrEntityVariable = Either.left("60".asExpression())
val maxDurationTicks: ExpressionOrEntityVariable = Either.left("180".asExpression())
override fun getVariables(
entity: LivingEntity,
behaviourConfigurationContext: BehaviourConfigurationContext
) = listOf(
minAngularVelocityDegrees,
maxAngularVelocityDegrees,
speed,
verticalSpeed,
minDurationTicks,
maxDurationTicks
).asVariables()
override fun create(
entity: LivingEntity,
behaviourConfigurationContext: BehaviourConfigurationContext
): Objective? {
if (entity !is PokemonEntity) return null
return CircleAround(
poseTypes = poseTypes,
minTurnAngleDegrees = minAngularVelocityDegrees.resolveFloat(behaviourConfigurationContext.runtime),
maxTurnAngleDegrees = maxAngularVelocityDegrees.resolveFloat(behaviourConfigurationContext.runtime),
speed = speed.resolveFloat(behaviourConfigurationContext.runtime),
verticalSpeed = verticalSpeed.resolveFloat(behaviourConfigurationContext.runtime),
minDurationTicks = minDurationTicks.resolveInt(behaviourConfigurationContext.runtime),
maxDurationTicks = maxDurationTicks.resolveInt(behaviourConfigurationContext.runtime)
)
}
}
}
@@ -1,158 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.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
}
}
@@ -1,384 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.ai.biggerbrain.objective
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.Priority
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.fixation.AttackFixation
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.EntityFixation
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.LookAndLockFixation
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 as resolveVariableInt
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.isInBattle
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.core.BlockPos
import net.minecraft.core.particles.ParticleTypes
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.memory.WalkTarget
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec3
class DefendNest(
speedMultiplier: Expression = "0.5".asExpression(),
warnRange: Expression = "15.0".asExpression(),
private val attackRange: Expression = "8.0".asExpression(),
disengageRange: Expression = "20.0".asExpression(),
condition: Expression = "true".asExpression(),
targetCondition: Expression = "true".asExpression(),
description: Expression = "'Defend Nest'".asExpression(),
private val maxAttackDuration: Expression = "300".asExpression(),
private val patrolMinRadius: Int = 4,
private val patrolMaxRadius: Int = 8
) : EntityResponseObjective(
speedMultiplier = speedMultiplier,
engageRange = warnRange,
disengageRange = disengageRange,
condition = condition,
targetCondition = targetCondition,
description = description
) {
private var fixation: EntityFixation? = null
private var scanCooldownTicks = 0
private var cryCooldownTicks = 0
private var patrolCooldownTicks = 0
override fun perform(entity: LivingEntity, brain: BiggerBrain, activeObjectiveTags: MutableSet<String>): ObjectiveStatus {
if (entity !is PokemonEntity || !entity.isAlive)
return ObjectiveStatus.INVALID
// so only wild mons will guard the nest (unless we want them to later on..)
if (!entity.pokemon.isWild())
return ObjectiveStatus.INACTIVE
// if the mon is battling then don't worry about defending (this was causing crying and particle spam lol)
if (entity.isBattling)
return ObjectiveStatus.INACTIVE
val nestPos = brain.getMemory(CobblemonMemories.PATERNAL_NEST_LOCATION)
if (nestPos == null) {
// mates used to give up attacking players if the egg and nest was gone but we should have them chase them to the ends of the earth for their crimes
val thief = brain.getMemory(CobblemonMemories.EGG_THIEF)
if (thief != null && thief.isAlive && entity.hasLineOfSight(thief))
return attack(entity, brain, thief)
return stopDefending(brain)
}
val eggInNest = brain.getMemory(CobblemonMemories.EGG_IN_NEST) == true
val thief = brain.getMemory(CobblemonMemories.EGG_THIEF)
// if we have no egg to watch over and no thief then we should not defend the nest anymore and need to switch to a different task
if (!eggInNest && thief == null)
return stopDefending(brain)
if (scanCooldownTicks > 0)
scanCooldownTicks--
if (cryCooldownTicks > 0)
cryCooldownTicks--
val nestCenter = Vec3.atCenterOf(nestPos)
val attackTarget = brain.getMemory(MemoryModuleType.ATTACK_TARGET)
if (attackTarget != null && attackTarget.uuid != trackedEntity?.uuid) {
trackedEntity = null
return ObjectiveStatus.BLOCKED
}
val tracked = trackedEntity
if (tracked != null) {
if (!keepTrackingPlayer(entity, brain, tracked, nestCenter)) {
brain.eraseMemory(MemoryModuleType.ATTACK_TARGET)
brain.eraseMemory(MemoryModuleType.ANGRY_AT)
trackedEntity = null
fixation = null
return ObjectiveStatus.ACTIVE
}
return respondToIntruder(entity, brain, tracked, nestCenter)
}
if (scanCooldownTicks <= 0) {
scanCooldownTicks = SCAN_INTERVAL_TICKS
trackedEntity = findIntruder(entity, brain, nestCenter)
if (trackedEntity != null) {
cryCooldownTicks = 0
return ObjectiveStatus.ACTIVE
}
}
return patrol(entity, brain, nestCenter, activeObjectiveTags)
}
override fun onRegister(entity: LivingEntity, brain: BiggerBrain) {
// Go after any known egg thieves
brain.subscribe(Priority.HIGH, CobblemonMemories.EGG_THIEF) { thief, _ ->
if (entity is PokemonEntity && thief != null) {
if (brain.fixation !is AttackFixation) {
trackedEntity = thief
attack(entity, brain, thief)
}
} else if (thief == null && fixation is AttackFixation && brain.isFixation(fixation)) {
brain.distract(Distraction.FORCE)
brain.eraseMemory(MemoryModuleType.ATTACK_TARGET)
brain.eraseMemory(MemoryModuleType.ANGRY_AT)
trackedEntity = null
fixation = null
}
true
}
}
// stop defending the nest and remove the fixation
private fun stopDefending(brain: BiggerBrain): ObjectiveStatus {
if (trackedEntity != null) {
brain.eraseMemory(MemoryModuleType.ATTACK_TARGET)
brain.eraseMemory(MemoryModuleType.ANGRY_AT)
trackedEntity = null
fixation = null
}
if (fixation is LookAndLockFixation && brain.isFixation(fixation))
brain.distract(Distraction.FORCE)
return ObjectiveStatus.INACTIVE
}
// various checks on if a player should be tracked still
private fun keepTrackingPlayer(entity: PokemonEntity, brain: BiggerBrain, tracked: LivingEntity, nestCenter: Vec3): Boolean {
if (tracked.isRemoved || !tracked.isAlive)
return false
if (tracked is Player && (tracked.isCreative || tracked.isSpectator))
return false
if (tracked is ServerPlayer && tracked.isInBattle())
return false
if (brain.getMemory(CobblemonMemories.EGG_THIEF)?.uuid == tracked.uuid)
return true
val runtime = mainThreadRuntime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
return tracked.position().distanceTo(nestCenter) <= runtime.resolveFloat(disengageRange)
}
// warn players that get too close or that steal an egg
private fun respondToIntruder(entity: PokemonEntity, brain: BiggerBrain, tracked: LivingEntity, nestCenter: Vec3): ObjectiveStatus {
val runtime = mainThreadRuntime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
val distanceToNest = tracked.position().distanceTo(nestCenter)
val isThief = brain.getMemory(CobblemonMemories.EGG_THIEF)?.uuid == tracked.uuid
return when {
isThief || distanceToNest <= runtime.resolveFloat(attackRange) -> attack(entity, brain, tracked) // stop you have violated the law!
distanceToNest <= runtime.resolveFloat(engageRange) -> warn(entity, brain, tracked, nestCenter) // almost too close!
else -> ObjectiveStatus.ACTIVE
}
}
// force an attack fixation
private fun attack(entity: PokemonEntity, brain: BiggerBrain, target: LivingEntity): ObjectiveStatus {
if (fixation is AttackFixation && brain.isFixation(fixation))
return ObjectiveStatus.FIXATED
if (!brain.distract(Distraction.FORCE))
return ObjectiveStatus.BLOCKED
val runtime = mainThreadRuntime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
entity.cry()
// todo we can use our own particles later
(entity.level() as? ServerLevel)?.sendParticles(
ParticleTypes.ANGRY_VILLAGER,
entity.getRandomX(1.0),
entity.getRandomY() + 0.5,
entity.getRandomZ(1.0),
4 + entity.random.nextInt(2),
0.2 * entity.random.nextGaussian(),
0.1 * entity.random.nextGaussian(),
0.2 * entity.random.nextGaussian(),
0.02
)
brain.setMemory(MemoryModuleType.ATTACK_TARGET, target, runtime.resolveInt(maxAttackDuration).toLong())
brain.setMemory(MemoryModuleType.ANGRY_AT, target.uuid)
brain.eraseMemory(CobblemonMemories.EGG_SEARCH_COUNTDOWN)
fixation = brain.fixate(
AttackFixation(
targetEntity = target,
speedMultiplier = speedMultiplier,
availableMoves = getRealTimeMoves(entity), // idk how this works but looks like Jazz lets us do this now
distractionTypes = setOf(DistractionType.FORCE)
)
)
return ObjectiveStatus.FIXATED
}
// cry at players that get too close to the nest
private fun warn(entity: PokemonEntity, brain: BiggerBrain, target: LivingEntity, nestCenter: Vec3): ObjectiveStatus {
if (cryCooldownTicks <= 0) {
entity.cry()
// todo replace these particles later here too
(entity.level() as? ServerLevel)?.sendParticles(
ParticleTypes.ANGRY_VILLAGER,
entity.getRandomX(1.0),
entity.getRandomY() + 0.5,
entity.getRandomZ(1.0),
4 + entity.random.nextInt(2),
0.2 * entity.random.nextGaussian(),
0.1 * entity.random.nextGaussian(),
0.2 * entity.random.nextGaussian(),
0.02
)
cryCooldownTicks = CRY_COOLDOWN_TICKS + entity.random.nextInt(CRY_COOLDOWN_RANDOM_TICKS)
}
if (fixation is LookAndLockFixation && brain.isFixation(fixation))
return ObjectiveStatus.FIXATED
if (!brain.distract(Distraction.FORCE))
return ObjectiveStatus.BLOCKED
val runtime = mainThreadRuntime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
val warnRange = runtime.resolveFloat(engageRange)
val attackRange = runtime.resolveFloat(attackRange)
fixation = brain.fixate(
LookAndLockFixation(
lookingTracker = LookingTracker.forTargetEntity(entity, target),
distractionTypes = setOf(DistractionType.DAMAGE, DistractionType.FORCE),
condition = {
val distance = target.position().distanceTo(nestCenter)
trackedEntity?.uuid == target.uuid && distance <= warnRange && distance > attackRange
}
)
)
return ObjectiveStatus.FIXATED
}
// looks for player within the warn range and too close to the nest
private fun findIntruder(entity: PokemonEntity, brain: BiggerBrain, nestCenter: Vec3): LivingEntity? {
val runtime = mainThreadRuntime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
if (!runtime.resolveBoolean(condition))
return null
val warnRange = runtime.resolveFloat(engageRange)
val visible = brain.getMemory(MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES) ?: return null
// todo how should we handle creative players?
return visible.findAll { candidate ->
candidate is Player &&
!(candidate is ServerPlayer && candidate.isInBattle()) &&
candidate.position().distanceTo(nestCenter) <= warnRange &&
runtime.withQueryValue("target", candidate.asMostSpecificMoLangValue()).resolveBoolean(targetCondition)
}.minByOrNull { it.position().distanceToSqr(nestCenter) }
}
// pathfind around various spots around the nest
private fun patrol(entity: PokemonEntity, brain: BiggerBrain, nestCenter: Vec3, activeObjectiveTags: MutableSet<String>): ObjectiveStatus {
activeObjectiveTags.add(Objective.OBJECTIVE_TAG_WANDERING)
if (fixation is LookAndMoveFixation && brain.isFixation(fixation))
return ObjectiveStatus.FIXATED
if (patrolCooldownTicks > 0) {
patrolCooldownTicks--
return ObjectiveStatus.ACTIVE
}
val world = entity.level() as? ServerLevel ?: return ObjectiveStatus.INVALID
val target = findPatrolPosition(world, nestCenter, entity) ?: return ObjectiveStatus.ACTIVE
if (!brain.distract(comfortDistraction))
return ObjectiveStatus.BLOCKED
val speed = mainThreadRuntime.withQueryValue("entity", entity.asMostSpecificMoLangValue()).resolveFloat(speedMultiplier)
val tracker = RegularMovementTracker.attempt(entity, WalkTarget(target, speed, 1)) ?: return ObjectiveStatus.ACTIVE
fixation = brain.fixate(
LookAndMoveFixation(
distractionTypes = setOf(DistractionType.DAMAGE, DistractionType.FORCE, DistractionType.COMFORT),
description = "patrol method in DefendNest objective",
lookingTracker = LookingTracker.fromNavigationPathOrWalkTarget(entity, tracker.walkTarget),
movementTracker = tracker,
onMovementComplete = { _, _ -> patrolCooldownTicks = PATROL_PAUSE_TICKS + entity.random.nextInt(PATROL_PAUSE_RANDOM_TICKS) }
)
)
return ObjectiveStatus.FIXATED
}
// find pathfindable spot to patrol to
private fun findPatrolPosition(world: ServerLevel, center: Vec3, entity: PokemonEntity): Vec3? {
repeat(PATROL_ATTEMPTS) {
val angle = world.random.nextDouble() * Math.PI * 2
val radius = patrolMinRadius + world.random.nextInt(patrolMaxRadius - patrolMinRadius + 1)
val candidate = center.add(Math.cos(angle) * radius, world.random.nextDouble() * 2 - 1, Math.sin(angle) * radius)
val blockPos = BlockPos.containing(candidate)
if (!world.getBlockState(blockPos).isSolidRender(world, blockPos) && entity.navigation.isStableDestination(blockPos)) {
return Vec3.atCenterOf(blockPos)
}
}
return null
}
class Config : ObjectiveConfig {
val speedMultiplier: ExpressionOrEntityVariable = Either.left("0.5".asExpression())
val warnRange: ExpressionOrEntityVariable = Either.left("15.0".asExpression())
val attackRange: ExpressionOrEntityVariable = Either.left("8.0".asExpression())
val disengageRange: ExpressionOrEntityVariable = Either.left("20.0".asExpression())
val condition: ExpressionOrEntityVariable = Either.left("true".asExpression())
val targetCondition: ExpressionOrEntityVariable = Either.left("true".asExpression())
val maxAttackDuration: ExpressionOrEntityVariable = Either.left("300".asExpression())
val description: ExpressionOrEntityVariable = Either.left("'Defend Nest'".asExpression())
val patrolMinRadius: ExpressionOrEntityVariable = Either.left("4".asExpression())
val patrolMaxRadius: ExpressionOrEntityVariable = Either.left("8".asExpression())
override fun getVariables(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): List<MoLangConfigVariable> {
return listOf(
speedMultiplier,
warnRange,
attackRange,
disengageRange,
condition,
targetCondition,
maxAttackDuration,
description,
patrolMinRadius,
patrolMaxRadius
).asVariables()
}
override fun create(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): Objective {
// I don't think we need any other memory than these
behaviourConfigurationContext.addMemories(
CobblemonMemories.PATERNAL_NEST_LOCATION,
CobblemonMemories.EGG_IN_NEST,
CobblemonMemories.EGG_THIEF,
CobblemonMemories.EGG_SEARCH_COUNTDOWN,
MemoryModuleType.ANGRY_AT,
MemoryModuleType.ATTACK_TARGET,
MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES
)
return DefendNest(
speedMultiplier = speedMultiplier.asSimplifiedExpression(entity),
warnRange = warnRange.asSimplifiedExpression(entity),
attackRange = attackRange.asSimplifiedExpression(entity),
disengageRange = disengageRange.asSimplifiedExpression(entity),
condition = condition.asSimplifiedExpression(entity),
targetCondition = targetCondition.asSimplifiedExpression(entity),
description = description.asSimplifiedExpression(entity),
maxAttackDuration = maxAttackDuration.asSimplifiedExpression(entity),
patrolMinRadius = patrolMinRadius.resolveVariableInt(behaviourConfigurationContext.runtime),
patrolMaxRadius = patrolMaxRadius.resolveVariableInt(behaviourConfigurationContext.runtime)
)
}
}
companion object {
private const val SCAN_INTERVAL_TICKS = 20
private const val CRY_COOLDOWN_TICKS = 75
private const val CRY_COOLDOWN_RANDOM_TICKS = 50
private const val PATROL_PAUSE_TICKS = 40
private const val PATROL_PAUSE_RANDOM_TICKS = 40
private const val PATROL_ATTEMPTS = 20
}
}
@@ -1,76 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.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.LookingTracker
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.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.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.npc.configuration.MoLangConfigVariable
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.entity.pokemon.ai.PokemonMoveControl
import com.cobblemon.mod.common.util.asExpression
import com.mojang.datafixers.util.Either
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.PathfinderMob
import net.minecraft.world.entity.ai.behavior.EntityTracker
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.entity.ai.memory.WalkTarget
/**
* Defend owner objective
* Handles transition of nearest visible attacker to a valid attack target when wanting to defend owner.
* Could possibly be more configurable in the future
*/
class DefendOwner : Objective {
var cooldownTicks = 0
override fun getDescription(entity: LivingEntity) = "Following parent species"
override fun perform(
entity: LivingEntity,
brain: BiggerBrain,
activeObjectiveTags: MutableSet<String>
): ObjectiveStatus {
if (entity !is PokemonEntity) return ObjectiveStatus.INVALID
if (brain.hasMemoryValue(MemoryModuleType.ATTACK_TARGET)) return ObjectiveStatus.INACTIVE
val attacker = brain.getMemory(CobblemonMemories.NEAREST_VISIBLE_ATTACKER)
?: return ObjectiveStatus.INACTIVE
brain.setMemory(MemoryModuleType.ATTACK_TARGET, attacker)
return ObjectiveStatus.ACTIVE
}
class Config : ObjectiveConfig {
override fun getVariables(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext) =
emptyList<MoLangConfigVariable>()
override fun create(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): Objective? {
if (entity !is PokemonEntity) return null
return DefendOwner()
}
}
}
@@ -1,170 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.ai.biggerbrain.objective
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.Priority
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.CobblemonAttackTargetData
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.objective.ObjectiveConfig
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.mainThreadRuntime
import com.cobblemon.mod.common.util.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.ai.memory.MemoryModuleType
import net.minecraft.world.entity.ai.memory.NearestVisibleLivingEntities
class Defensive(
private val speedMultiplier: Expression = "0.5".asExpression(), // How do we transfer speed to the attack behaviour? Do we want to?
private val engageRange: Expression = "5.0".asExpression(),
private val disengageRange: Expression = "20.0".asExpression(),
private val condition: Expression = "true".asExpression(),
private val maxAttackDuration: Expression = "300".asExpression(),
private val retaliationHitCount: Expression = "1".asExpression()
) : Objective {
private var remainingCooldownTicks = 0
private var trackedEntity: LivingEntity? = null
override fun getDescription(entity: LivingEntity) = "Defensive Behavior"
override fun perform(
entity: LivingEntity,
brain: BiggerBrain,
activeObjectiveTags: MutableSet<String>
): ObjectiveStatus {
if (entity !is PokemonEntity || !entity.isAlive) return ObjectiveStatus.INVALID
val attackTarget = brain.getMemory(MemoryModuleType.ATTACK_TARGET)
val avoidTarget = brain.getMemory(MemoryModuleType.AVOID_TARGET)
if (trackedEntity != null) {
if (attackTarget != null && trackedEntity?.uuid == attackTarget.uuid) {
// Check if we continue
return ObjectiveStatus.ACTIVE
} else if (avoidTarget != null && trackedEntity?.uuid === avoidTarget.uuid) {
// Check if we continue
val runtime = mainThreadRuntime.withQueryValue("entity", (trackedEntity!!).asMostSpecificMoLangValue())
val disengageDistance = runtime.resolveFloat(disengageRange)
if (disengageDistance < entity.distanceTo(trackedEntity!!)) {
brain.eraseMemory(MemoryModuleType.AVOID_TARGET)
trackedEntity = null
}
} else {
// Something has overrided us
trackedEntity = null
}
} else {
// Scan visible livingEntities
val entities: NearestVisibleLivingEntities =
brain.getMemory(MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES) ?: return ObjectiveStatus.INACTIVE
val entity = entities.findClosest { it ->
val runtime = mainThreadRuntime.withQueryValue("entity", (it).asMostSpecificMoLangValue())
runtime.resolveBoolean(condition) && runtime.resolveFloat(engageRange) > entity.distanceTo(it)
}.orElse(null)
if (entity != null) {
trackedEntity = entity
brain.setMemory(MemoryModuleType.AVOID_TARGET, entity)
}
}
if (remainingCooldownTicks > 0) {
remainingCooldownTicks--
}
return if (trackedEntity != null) ObjectiveStatus.ACTIVE else ObjectiveStatus.INACTIVE
}
override fun onRegister(entity: LivingEntity, brain: BiggerBrain) {
brain.subscribe(
priority = Priority.LOW,
memory = MemoryModuleType.HURT_BY_ENTITY
) { value, labels -> onHurtByEntityChanged(brain, value, labels) }
brain.subscribe(
priority = Priority.LOW,
memory = CobblemonMemories.ATTACK_TARGET_DATA
) { value, labels -> onAttackTargetDataChanged(brain, value, labels) }
}
fun onHurtByEntityChanged(brain: BiggerBrain, livingEntity: LivingEntity?, labels: MutableSet<String>): Boolean {
if (livingEntity != null) {
// Something just smacked us, see if we should get mad about it.
val livingEntity = livingEntity as LivingEntity
val runtime = mainThreadRuntime.withQueryValue("entity", (livingEntity).asMostSpecificMoLangValue())
val shouldTarget = runtime.resolveBoolean(condition)
if (shouldTarget) {
brain.setMemory(MemoryModuleType.ATTACK_TARGET, livingEntity,
runtime.resolveInt(maxAttackDuration).toLong()
)
brain.eraseMemory(MemoryModuleType.AVOID_TARGET)
trackedEntity = livingEntity
}
}
return true
}
fun onAttackTargetDataChanged(brain: BiggerBrain, attackData: CobblemonAttackTargetData?, labels: MutableSet<String>): Boolean {
if (attackData != null) {
// We smacked them, check if it's time for tactical retreat
val livingEntity = brain.getMemory(MemoryModuleType.ATTACK_TARGET) ?: return true
val runtime = mainThreadRuntime.withQueryValue("entity", (livingEntity).asMostSpecificMoLangValue())
if (attackData.hitsLanded >= runtime.resolveInt(retaliationHitCount)) {
// Justice served, carry on
brain.eraseMemory(MemoryModuleType.ATTACK_TARGET)
brain.eraseMemory(CobblemonMemories.ATTACK_TARGET_DATA)
brain.setMemory(MemoryModuleType.AVOID_TARGET, trackedEntity)
}
}
return true // Other listeners can handle it that's fine
}
class Config : ObjectiveConfig {
val speedMultiplier: ExpressionOrEntityVariable = Either.left("0.5".asExpression())
val engageRange: ExpressionOrEntityVariable = Either.left("10.0".asExpression())
val disengageRange: ExpressionOrEntityVariable = Either.left("10.0".asExpression())
val condition: ExpressionOrEntityVariable = Either.left("10.0".asExpression())
val maxAttackDuration: ExpressionOrEntityVariable = Either.left("300".asExpression())
val retaliationHitCount: ExpressionOrEntityVariable = Either.left("1".asExpression())
override fun getVariables(
entity: LivingEntity,
behaviourConfigurationContext: BehaviourConfigurationContext
): List<MoLangConfigVariable> {
return listOf(condition, speedMultiplier, engageRange, disengageRange, maxAttackDuration, retaliationHitCount).asVariables()
}
override fun create(
entity: LivingEntity,
behaviourConfigurationContext: BehaviourConfigurationContext
): Objective {
return Defensive(
condition = condition.asSimplifiedExpression(entity),
speedMultiplier = speedMultiplier.asSimplifiedExpression(entity),
engageRange = engageRange.asSimplifiedExpression(entity),
disengageRange = disengageRange.asSimplifiedExpression(entity),
maxAttackDuration = maxAttackDuration.asSimplifiedExpression(entity),
retaliationHitCount = retaliationHitCount.asSimplifiedExpression(entity),
)
}
}
}
@@ -1,147 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.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.distraction.comfortDistraction
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 (entity !is PokemonEntity) {
return ObjectiveStatus.INVALID
} else if (!brain.canDistract(comfortDistraction)) {
return ObjectiveStatus.BLOCKED
} 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
)
}
}
}
}
@@ -1,171 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.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()
// FORCE so it still takes over nearly anything, as before, but respects fixations that refuse everything.
if (canEat(itemStack, entity) && brain.distract(Distraction.FORCE)) {
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
)
}
}
}
@@ -1,349 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.ai.biggerbrain.objective
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.CobblemonItemComponents
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.Priority
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.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.LockFixation
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 as resolveVariableInt
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
import com.cobblemon.mod.common.block.entity.NestBlockEntity
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.mainThreadRuntime
import com.cobblemon.mod.common.util.resolveFloat
import com.cobblemon.mod.common.util.withQueryValue
import com.mojang.datafixers.util.Either
import net.minecraft.core.BlockPos
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.InteractionHand
import net.minecraft.world.entity.Entity
import net.minecraft.world.entity.LivingEntity
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.phys.Vec3
class EggRecovery(
private val speedMultiplier: Expression = "0.4".asExpression(),
private val searchRadius: Int = 20,
private val searchIntervalTicks: Int = 20
) : Objective {
private var fixation: EntityFixation? = null
private var searchCooldownTicks = 0
override fun getDescription(entity: LivingEntity) = "Egg Recovery"
override fun perform(entity: LivingEntity, brain: BiggerBrain, activeObjectiveTags: MutableSet<String>): ObjectiveStatus {
if (entity !is PokemonEntity || !entity.isAlive)
return ObjectiveStatus.INVALID
// so only wild mons recover their eggs
if (!entity.pokemon.isWild())
return ObjectiveStatus.INACTIVE
val world = entity.level() as? ServerLevel ?: return ObjectiveStatus.INVALID
// get the nest in memory if it exists and check to see if it has an egg
val nestPos = brain.getMemory(CobblemonMemories.PATERNAL_NEST_LOCATION) ?: return ObjectiveStatus.INACTIVE
if (brain.getMemory(CobblemonMemories.EGG_IN_NEST) != false)
return ObjectiveStatus.INACTIVE
// if there is a theif they are probably being chased so nothing to do about recovery yet
if (brain.hasMemoryValue(CobblemonMemories.EGG_THIEF))
return ObjectiveStatus.INACTIVE
if (brain.isFixation(fixation))
return ObjectiveStatus.FIXATED
// if egg is held it needs to go back to the nest
if (brain.getMemory(CobblemonMemories.HOLDING_EGG) == true)
return returnEgg(entity, brain, world, nestPos)
// if we have a known egg position then go get it
val eggPos = brain.getMemory(CobblemonMemories.EGG_LOCATION)
if (eggPos != null)
return fetchEgg(entity, brain, world, eggPos)
// if any of the above isnt needed then we probably have searching for the egg left
return searchForEgg(entity, brain, world, nestPos, activeObjectiveTags)
}
override fun onRegister(entity: LivingEntity, brain: BiggerBrain) {
// start with finding the egg if the location is known when we start.
// should we have an if statement above this to check if we should search for the egg first or go to the egg? idk
brain.subscribe(Priority.LOW, CobblemonMemories.EGG_LOCATION) { eggPos, _ ->
if (eggPos != null && fixation is LookAndMoveFixation && brain.isFixation(fixation)) {
brain.distract(Distraction.FORCE)
fixation = null
}
true
}
}
// look for the egg a while in an area until giving up
private fun searchForEgg(entity: PokemonEntity, brain: BiggerBrain, world: ServerLevel, nestPos: BlockPos, activeObjectiveTags: MutableSet<String>): ObjectiveStatus {
activeObjectiveTags.add(Objective.OBJECTIVE_TAG_WANDERING)
// if the countdown is up then clear objective
if (shouldGiveUpSearch(entity, brain, world, nestPos))
return ObjectiveStatus.INACTIVE
// if our cooldown is active then tick it down
if (searchCooldownTicks > 0) {
searchCooldownTicks--
return ObjectiveStatus.ACTIVE
}
// set the cooldown so it can begin
searchCooldownTicks = searchIntervalTicks
// choose a random spot around the nest to check for the egg todo should we have it also consider last seen egg position? or reincorporate player disturbances?
val searchPos = chooseRandomPos(entity, Vec3.atCenterOf(nestPos)) ?: return ObjectiveStatus.ACTIVE
if (!brain.distract(comfortDistraction))
return ObjectiveStatus.BLOCKED
val tracker = RegularMovementTracker.attempt(entity, WalkTarget(searchPos, mainThreadRuntime.withQueryValue("entity", entity.asMostSpecificMoLangValue()).resolveFloat(speedMultiplier), 1)) ?: return ObjectiveStatus.ACTIVE
fixation = brain.fixate(
LookAndMoveFixation(
distractionTypes = setOf(DistractionType.DAMAGE, DistractionType.FORCE, DistractionType.COMFORT),
description = "Egg Recovery: searching for the egg",
lookingTracker = LookingTracker.fromNavigationPathOrWalkTarget(entity, tracker.walkTarget),
movementTracker = tracker
)
)
return ObjectiveStatus.FIXATED
}
// has the search countdown ended and should the nest be abandoned?
private fun shouldGiveUpSearch(entity: PokemonEntity, brain: BiggerBrain, world: ServerLevel, nestPos: BlockPos): Boolean {
// if there is an active countdown and it isnt at the give up timeframe then don't wipe memories yet
if (brain.getMemory(CobblemonMemories.EGG_SEARCH_COUNTDOWN) != true)
return false
if (entity.brain.getTimeUntilExpiry(CobblemonMemories.EGG_SEARCH_COUNTDOWN) > GIVE_UP_SEARCH_TIME)
return false
brain.eraseMemory(CobblemonMemories.EGG_SEARCH_COUNTDOWN)
brain.eraseMemory(CobblemonMemories.EGG_LOCATION)
brain.eraseMemory(CobblemonMemories.PATERNAL_NEST_LOCATION)
brain.eraseMemory(CobblemonMemories.EGG_IN_NEST)
brain.eraseMemory(CobblemonMemories.POTENTIAL_MATE)
val nest = world.getBlockEntity(nestPos) as? NestBlockEntity
// clear the parent info on the nest itself if it is still around
if (nest != null) {
nest.parents = Pair(null, null)
nest.nestClaimedBy = Pair(null, null)
}
return true
}
// path to the egg and pick it up
private fun fetchEgg(entity: PokemonEntity, brain: BiggerBrain, world: ServerLevel, eggPos: BlockPos): ObjectiveStatus {
val eggCenter = Vec3.atCenterOf(eggPos)
if (entity.position().distanceTo(eggCenter) <= CLOSE_ENOUGH_DISTANCE) {
pickUpEgg(entity, brain, world)
return ObjectiveStatus.ACTIVE
}
if (!brain.distract(comfortDistraction))
return ObjectiveStatus.BLOCKED
val tracker = RegularMovementTracker.attempt(entity, WalkTarget(eggCenter, mainThreadRuntime.withQueryValue("entity", entity.asMostSpecificMoLangValue()).resolveFloat(speedMultiplier), 1)) ?: return ObjectiveStatus.ACTIVE
fixation = brain.fixate(FetchEggFixation(this, tracker, LookingTracker.fromNavigationPathOrWalkTarget(entity, tracker.walkTarget)))
return ObjectiveStatus.FIXATED
}
// pickup egg and put it in "hand" of the mon
private fun pickUpEgg(entity: PokemonEntity, brain: BiggerBrain, world: ServerLevel): Boolean {
// get any egg item entities that are from the parent and within their pickup radius
val eggItem = world.getEntitiesOfClass(ItemEntity::class.java, entity.boundingBox.inflate(PICKUP_REACH)) { item ->
item.item.item == CobblemonItems.POKEMON_EGG && item.item.get(CobblemonItemComponents.EGG_DATA)?.isParent(entity.pokemon.uuid) == true
}.minByOrNull { it.distanceToSqr(entity) }
if (eggItem == null) {
// the egg isn't where it was last seen so clear location memory
brain.eraseMemory(CobblemonMemories.EGG_LOCATION)
return false
}
entity.setItemInHand(InteractionHand.MAIN_HAND, eggItem.item.copy())
eggItem.remove(Entity.RemovalReason.DISCARDED)
brain.setMemory(CobblemonMemories.HOLDING_EGG, true)
brain.eraseMemory(CobblemonMemories.EGG_LOCATION)
brain.eraseMemory(CobblemonMemories.EGG_SEARCH_COUNTDOWN)
return true
}
// carry the egg back to the nest
private fun returnEgg(entity: PokemonEntity, brain: BiggerBrain, world: ServerLevel, nestPos: BlockPos): ObjectiveStatus {
// if the nest is gone when trying to bring it back we should maybe drop the egg and stop the objective.... todo or should they just hold the egg forever? build a new nest?
if (world.getBlockEntity(nestPos) !is NestBlockEntity) {
val heldEgg = entity.getItemInHand(InteractionHand.MAIN_HAND)
if (!heldEgg.isEmpty)
entity.spawnAtLocation(heldEgg.copy())
entity.setItemInHand(InteractionHand.MAIN_HAND, ItemStack.EMPTY)
brain.eraseMemory(CobblemonMemories.HOLDING_EGG)
return ObjectiveStatus.INACTIVE
}
val nestCenter = Vec3.atCenterOf(nestPos)
if (entity.position().distanceTo(nestCenter) <= CLOSE_ENOUGH_DISTANCE) {
placeEgg(entity, brain, world, nestPos)
return ObjectiveStatus.ACTIVE
}
// make sure only forced distactions stop the parent from bringing the egg back
if (!brain.distract(Distraction.FORCE))
return ObjectiveStatus.BLOCKED
val tracker = RegularMovementTracker.attempt(entity, WalkTarget(nestCenter, mainThreadRuntime.withQueryValue("entity", entity.asMostSpecificMoLangValue()).resolveFloat(speedMultiplier), 1)) ?: return ObjectiveStatus.ACTIVE
fixation = brain.fixate(ReturnEggFixation(this, nestPos, tracker, LookingTracker.fromNavigationPathOrWalkTarget(entity, tracker.walkTarget)))
return ObjectiveStatus.FIXATED
}
// place egg in the nest
private fun placeEgg(entity: PokemonEntity, brain: BiggerBrain, world: ServerLevel, nestPos: BlockPos): Boolean {
val nest = world.getBlockEntity(nestPos) as? NestBlockEntity ?: return false
val heldEgg = entity.getItemInHand(InteractionHand.MAIN_HAND)
// if for some reason the parent is not holding the egg anymore
if (heldEgg.isEmpty || heldEgg.item != CobblemonItems.POKEMON_EGG) {
brain.eraseMemory(CobblemonMemories.HOLDING_EGG)
return false
}
// don't place the egg if the nest is not empty
if (!nest.getItem().isEmpty)
return false
nest.setItem(heldEgg.copy())
entity.setItemInHand(InteractionHand.MAIN_HAND, ItemStack.EMPTY)
brain.eraseMemory(CobblemonMemories.HOLDING_EGG)
brain.setMemory(CobblemonMemories.EGG_IN_NEST, true)
return true
}
private fun chooseRandomPos(entity: PokemonEntity, center: Vec3): Vec3? {
// is this bad for performance? At low numbers I assume it won't be too bad but will need to be tested
repeat(SEARCH_ATTEMPTS) {
val offsetX = entity.random.nextDouble() * 2 * searchRadius - searchRadius
val offsetZ = entity.random.nextDouble() * 2 * searchRadius - searchRadius
val blockPos = BlockPos.containing(center.add(offsetX, 0.0, offsetZ))
if (entity.navigation.createPath(blockPos, 0) != null)
return Vec3.atCenterOf(blockPos)
}
return null
}
// fixation for walking to the egg and picking it up
class FetchEggFixation(
private val objective: EggRecovery,
override val movementTracker: MovementTracker,
override val lookingTracker: LookingTracker
) : EntityFixation, MovementTrackerFixation, LookingTrackerFixation {
override fun getDescription(entity: LivingEntity) = "Egg Recovery: going to get egg"
// if they are hurt or forced they should stop being fixated
override fun canDistract(distraction: Distraction, entity: LivingEntity) = distraction.isDamage || distraction.isForce
// we should stop the moving tracker when distracted (do we need to stop anything else?)
override fun onDistract(distraction: Distraction, entity: LivingEntity) {
movementTracker.stop()
}
override fun tick(entity: LivingEntity, brain: BiggerBrain): Boolean {
if (entity !is PokemonEntity)
return false
val world = entity.level() as? ServerLevel ?: return false
lookingTracker.tick()
when (movementTracker.tick()) {
MovementStatus.PENDING -> return true
// movement is done (hopefully at the right spot) so try to pickup the egg
MovementStatus.COMPLETED -> objective.pickUpEgg(entity, brain, world)
// if anything happens try to get a new egg location
MovementStatus.FAILED -> brain.eraseMemory(CobblemonMemories.EGG_LOCATION)
}
return false
}
}
// path back to nest and place the egg
class ReturnEggFixation(
private val objective: EggRecovery,
private val nestPos: BlockPos,
override val movementTracker: MovementTracker,
override val lookingTracker: LookingTracker
) : EntityFixation, MovementTrackerFixation, LookingTrackerFixation, LockFixation {
override fun getDescription(entity: LivingEntity) = "Egg Recovery: taking egg back to nest"
// only force for now but we might want to distract if damaged later
override fun canDistract(distraction: Distraction, entity: LivingEntity) = distraction.isForce
// same as above stop movement if distracted
override fun onDistract(distraction: Distraction, entity: LivingEntity) {
movementTracker.stop()
}
override fun tick(entity: LivingEntity, brain: BiggerBrain): Boolean {
if (entity !is PokemonEntity)
return false
val world = entity.level() as? ServerLevel ?: return false
lookingTracker.tick()
if (movementTracker.tick() == MovementStatus.PENDING)
return true
if (movementTracker.lastStatus == MovementStatus.COMPLETED)
objective.placeEgg(entity, brain, world, nestPos)
return false
}
}
class Config : ObjectiveConfig {
val speedMultiplier: ExpressionOrEntityVariable = Either.left("0.4".asExpression())
val searchRadius: ExpressionOrEntityVariable = Either.left("20".asExpression())
val searchIntervalTicks: ExpressionOrEntityVariable = Either.left("20".asExpression())
override fun getVariables(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): List<MoLangConfigVariable> {
return listOf(speedMultiplier, searchRadius, searchIntervalTicks).asVariables()
}
override fun create(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): Objective {
behaviourConfigurationContext.addMemories(
CobblemonMemories.PATERNAL_NEST_LOCATION,
CobblemonMemories.EGG_IN_NEST,
CobblemonMemories.EGG_LOCATION,
CobblemonMemories.EGG_THIEF,
CobblemonMemories.EGG_SEARCH_COUNTDOWN,
CobblemonMemories.HOLDING_EGG,
CobblemonMemories.POTENTIAL_MATE
)
return EggRecovery(
speedMultiplier = speedMultiplier.asSimplifiedExpression(entity),
searchRadius = searchRadius.resolveVariableInt(behaviourConfigurationContext.runtime),
searchIntervalTicks = searchIntervalTicks.resolveVariableInt(behaviourConfigurationContext.runtime)
)
}
}
companion object {
private const val CLOSE_ENOUGH_DISTANCE = 1.5 // should this be bigger for the bigger mons or be based off of hitbox?
private const val PICKUP_REACH = 1.5 // slight radius around the mon so they don't have to physically be on top of the egg
private const val SEARCH_ATTEMPTS = 10
private const val GIVE_UP_SEARCH_TIME = 20L // is this enough time for search? Needs playtesting
}
}
@@ -1,88 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.ai.biggerbrain.objective
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
import com.cobblemon.mod.common.api.pokemon.status.Statuses
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.pokemon.ai.moves.RealTimeMove
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.mainThreadRuntime
import com.cobblemon.mod.common.util.resolveBoolean
import com.cobblemon.mod.common.util.resolveFloat
import com.cobblemon.mod.common.util.resolveString
import com.cobblemon.mod.common.util.withQueryValue
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.entity.ai.memory.NearestVisibleLivingEntities
abstract class EntityResponseObjective(
val speedMultiplier: Expression = "0.5".asExpression(), // How do we transfer speed to the attack behaviour? Do we want to?
val engageRange: Expression = "15.0".asExpression(),
val disengageRange: Expression = "20.0".asExpression(),
val condition: Expression = "true".asExpression(), // Self/environment condition check
val targetCondition: Expression = "true".asExpression(), // target condition (will be checked on entity interaction)
description: Expression = "'EntityResponseObjective'".asExpression(),
) : Objective {
var trackedEntity: LivingEntity? = null
private val objectiveDescription: String = mainThreadRuntime.resolveString(description)
override fun getDescription(entity: LivingEntity) = objectiveDescription
open fun scanForValidTargets(entity: LivingEntity, brain: BiggerBrain): LivingEntity? {
//TODO: need to apply some sort of awareness modifier
// Sleep obviously should dramatically affect this but it's not hard to imagine other things that could do this
if (entity.isSleeping || (entity is PokemonEntity && entity.pokemon.status?.status == Statuses.SLEEP)) {
// Also why doesn't entity.isSleeping for PokemonEntities return true if the sleep status is applied?
return null
}
// Check if we're in the mood to fight
if (mainThreadRuntime.withQueryValue("entity", (entity).asMostSpecificMoLangValue())
.resolveBoolean(condition)
) {
// Scan visible livingEntities
val entities: NearestVisibleLivingEntities =
brain.getMemory(MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES) ?: return null
val targetEntity = entities.findClosest { it ->
val runtime = mainThreadRuntime.withQueryValue("target", (it).asMostSpecificMoLangValue())
val isValidTarget =
runtime.resolveBoolean(targetCondition) && runtime.resolveFloat(engageRange) > entity.distanceTo(it)
isValidTarget
}.orElse(null)
if (targetEntity != null) return targetEntity
}
return null
}
open fun shouldContinue(entity: LivingEntity, brain: BiggerBrain): Boolean {
trackedEntity ?: return false
if (trackedEntity!!.isDeadOrDying) return false
val runtime = mainThreadRuntime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
.withQueryValue("target", (trackedEntity!!).asMostSpecificMoLangValue())
return (runtime.resolveBoolean(condition) // self condition
&& runtime.resolveBoolean(targetCondition) // target condition
&& (runtime.resolveFloat(disengageRange) > entity.distanceTo(trackedEntity!!)))
}
open fun getRealTimeMoves(entity: LivingEntity) : List<RealTimeMove> {
var moves = if (entity is PokemonEntity) entity.pokemon.species.behaviour.combat.moves else emptyList()
if (moves.isEmpty() && entity is PokemonEntity) {
moves = listOf(
if (entity.pokemon.attack > entity.pokemon.specialAttack) RealTimeMove.defaultMelee else RealTimeMove.defaultRange
)
}
return moves
}
}
@@ -1,250 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.ai.biggerbrain.objective
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.Priority
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.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 onRegister(entity: LivingEntity, brain: BiggerBrain) {
brain.subscribe(
priority = Priority.LOW,
memory = MemoryModuleType.HURT_BY
) { value, labels ->
if (brain.isFixation(fixation) && value != null && stopBattleIfHurt) {
brain.distract(HurtDistraction(value))
}
return@subscribe true
}
}
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
)
}
}
}
@@ -1,257 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.ai.biggerbrain.objective
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.Priority
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.*
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.biggerbrain.fixation.LookingTrackerFixation
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.*
import com.mojang.datafixers.util.Either
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.PathfinderMob
import net.minecraft.world.entity.ai.behavior.EntityTracker
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.entity.ai.memory.NearestVisibleLivingEntities
import net.minecraft.world.entity.ai.memory.WalkTarget
class FriendlyBehaviour(
speedMultiplier: Expression = "0.5".asExpression(), // How do we transfer speed to the attack behaviour? Do we want to?
engageRange: Expression = "10.0".asExpression(),
disengageRange: Expression = "20.0".asExpression(),
condition: Expression = "true".asExpression(),
targetCondition: Expression = "false".asExpression(),
private val maxDuration: Expression = "300".asExpression(),
private val description: Expression = "Friendly Behaviour".asExpression(),
) : EntityResponseObjective(
speedMultiplier = speedMultiplier, // How do we transfer speed to the attack behaviour? Do we want to?
engageRange = engageRange,
disengageRange = disengageRange,
condition = condition, // Self/environment condition check
targetCondition = targetCondition, // target condition (will be checked on entity interaction)
description = description,
) {
private var remainingCooldownTicks = 0
private var fixation: EntityFixation? = null
private val closeEnoughDistance = 1.5
private var remainingTicks = 0
override fun perform(
entity: LivingEntity,
brain: BiggerBrain,
activeObjectiveTags: MutableSet<String>
): ObjectiveStatus {
if (entity !is PokemonEntity || !entity.isAlive) return ObjectiveStatus.INVALID
if (remainingCooldownTicks > 0) {
remainingCooldownTicks--
return if (brain.isFixation(fixation)) ObjectiveStatus.FIXATED else ObjectiveStatus.INACTIVE
} else {
remainingCooldownTicks = 5
}
if (brain.isFixation(fixation)) {
if (remainingTicks > 0) {
remainingTicks--
} else {
// Got bored, move on
brain.distract(Distraction.FORCE)
trackedEntity = null
fixation = null
}
if (trackedEntity != null) {
// Check if we continue
return attemptFixation(entity, brain)
} else {
trackedEntity = null
fixation = null
brain.distract(comfortDistraction)
}
return ObjectiveStatus.FIXATED
} else {
// Scan visible livingEntities
val targetEntity = scanForValidTargets(entity, brain)
if (targetEntity != null) {
trackedEntity = targetEntity
val status = attemptFixation(entity, brain)
if (status == ObjectiveStatus.FIXATED) {
remainingTicks = mainThreadRuntime.resolveInt(maxDuration)
}
return status
}
return if (trackedEntity != null) ObjectiveStatus.ACTIVE else ObjectiveStatus.INACTIVE
}
}
fun attemptFixation(entity: PathfinderMob, brain: BiggerBrain) : ObjectiveStatus {
if (trackedEntity != null) {
// Check if we continue
val runtime = mainThreadRuntime.withQueryValue("entity", (entity).asMostSpecificMoLangValue())
.withQueryValue("target", trackedEntity!!.asMostSpecificMoLangValue())
val distance = entity.distanceTo(trackedEntity!!)
if (runtime.resolveFloat(disengageRange) < distance || !shouldContinue(entity, brain)) {
brain.distract(comfortDistraction)
trackedEntity = null
fixation = null
return ObjectiveStatus.INACTIVE
} else if (distance <= closeEnoughDistance && !(brain.isFixation(fixation) && fixation is IdleLookFixation)) {
// transition to look
val distraction = if(brain.isFixation(fixation)) Distraction.FORCE else idleBoredomDistraction
if (brain.distract(distraction)) {
this.fixation = brain.fixate(
IdleLookFixation(
LookingTracker(
entity,
EntityTracker(trackedEntity!!, true)
),
remainingTicks
)
)
return ObjectiveStatus.FIXATED
} else {
// could not distract
return ObjectiveStatus.BLOCKED
}
} else if (distance > closeEnoughDistance && !(brain.isFixation(fixation) && fixation is LookAndMoveFixation)) {
// transition to walk
val distraction = if(brain.isFixation(fixation)) Distraction.FORCE else idleBoredomDistraction
if (brain.distract(distraction)) {
val walkTarget =
WalkTarget(EntityTracker(trackedEntity!!, true), runtime.resolveFloat(speedMultiplier), 0)
val movementTracker = RegularMovementTracker.attempt(entity, walkTarget)
?: return ObjectiveStatus.INACTIVE
this.fixation = brain.fixate(
LookAndMoveFixation(
description = "Following targeted entity",
distractionTypes = setOf(
DistractionType.COMFORT,
DistractionType.DAMAGE,
DistractionType.FORCE
),
lookingTracker = LookingTracker.forTargetEntity(entity, trackedEntity!!),
movementTracker = movementTracker,
onMovementComplete = { _, _ -> attemptFixation(entity, brain) } // maintains fixation between moving and looking
)
)
return ObjectiveStatus.FIXATED
} else {
return ObjectiveStatus.BLOCKED
}
} else {
return ObjectiveStatus.FIXATED
}
}
return ObjectiveStatus.INACTIVE
}
class IdleLookFixation(
override val lookingTracker: LookingTracker?,
var remainingTicks: Int,
) : EntityFixation, LookingTrackerFixation {
override fun getDescription(entity: LivingEntity): String {
return "Looking at a nearby entity ($remainingTicks ticks left)"
}
override fun canDistract(distraction: Distraction, entity: LivingEntity): Boolean {
return !distraction.isIdleBoredom
}
override fun tick(entity: LivingEntity, brain: BiggerBrain): Boolean {
val targetEntity = lookingTracker?.entity ?: return false
if ((targetEntity.isRemoved) || entity !is PathfinderMob) {
return false
}
val lookingTracker = lookingTracker
remainingTicks--
lookingTracker.tick()
return remainingTicks > 0
}
}
override fun onRegister(entity: LivingEntity, brain: BiggerBrain) {
brain.subscribe(
priority = Priority.LOW,
memory = CobblemonMemories.ATTACK_TARGET_DATA
) { value, labels ->
if (value != null) {
val livingEntity = brain.getMemory(MemoryModuleType.ATTACK_TARGET) ?: return@subscribe true
if (trackedEntity?.uuid == livingEntity.uuid) {
// We smacked them, refresh our attack timer
val runtime = mainThreadRuntime.withQueryValue("entity", (livingEntity).asMostSpecificMoLangValue())
val attackDuration = runtime.resolveInt(maxDuration)
brain.setMemory(MemoryModuleType.ATTACK_TARGET, livingEntity, attackDuration.toLong())
}
}
return@subscribe true
}
}
class Config : ObjectiveConfig {
val speedMultiplier: ExpressionOrEntityVariable = Either.left("0.5".asExpression())
val engageRange: ExpressionOrEntityVariable = Either.left("10.0".asExpression())
val disengageRange: ExpressionOrEntityVariable = Either.left("20.0".asExpression())
val condition: ExpressionOrEntityVariable = Either.left("true".asExpression())
val targetCondition: ExpressionOrEntityVariable = Either.left("true".asExpression())
val maxDuration: ExpressionOrEntityVariable = Either.left("300".asExpression())
val description: ExpressionOrEntityVariable = Either.left("Test".asExpression())
override fun getVariables(
entity: LivingEntity,
behaviourConfigurationContext: BehaviourConfigurationContext
): List<MoLangConfigVariable> {
return listOf(
condition,
targetCondition,
speedMultiplier,
engageRange,
disengageRange,
maxDuration,
description,
).asVariables()
}
override fun create(
entity: LivingEntity,
behaviourConfigurationContext: BehaviourConfigurationContext
): Objective {
return FriendlyBehaviour(
condition = condition.asSimplifiedExpression(entity),
targetCondition = targetCondition.asSimplifiedExpression(entity),
speedMultiplier = speedMultiplier.asSimplifiedExpression(entity),
engageRange = engageRange.asSimplifiedExpression(entity),
disengageRange = disengageRange.asSimplifiedExpression(entity),
maxDuration = maxDuration.asSimplifiedExpression(entity),
description = description.asSimplifiedExpression(entity)
)
}
}
}
@@ -1,508 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.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
}
}
@@ -1,155 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.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
/**
* Sleeps the entity.
*
* @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)
)
}
}
}
@@ -1,204 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.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)
)
}
}
}
@@ -1,409 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.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)
)
}
}
}
@@ -1,166 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.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)
}
}
@@ -1,136 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.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
)
}
}
}
@@ -1,65 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.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()
}
}
}
@@ -1,251 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.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.movement.can_walk && q.entity.movement.avoids_land) || q.entity.movement.can_fly".asExpression())
val allowAir = wanderControl?.allowAir
?: runtime.resolveBoolean("q.entity.movement.can_fly && !q.entity.movement.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)
)
}
}
}
@@ -1,258 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.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.movement.can_walk && !q.entity.movement.avoids_land) || q.entity.movement.can_fly".asExpression())
val allowAir = wanderControl?.allowAir
?: runtime.resolveBoolean("q.entity.movement.can_fly && !q.entity.movement.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)
)
}
}
}
@@ -1,166 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.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)
)
}
}
}
@@ -1,176 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.ai.biggerbrain.objective
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.Priority
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.distraction.DistractionType
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.AttackFixation
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.molang.MoLangFunctions.asMostSpecificMoLangValue
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.mainThreadRuntime
import com.cobblemon.mod.common.util.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.ai.memory.MemoryModuleType
class NeutralBehaviour(
speedMultiplier: Expression = "0.5".asExpression(), // How do we transfer speed to the attack behaviour? Do we want to?
engageRange: Expression = "5.0".asExpression(),
disengageRange: Expression = "20.0".asExpression(),
condition: Expression = "true".asExpression(),
targetCondition: Expression = "true".asExpression(), // target condition (will be checked on entity interaction)
description: Expression = "'Neutral Behaviour'".asExpression(),
private val maxAttackDuration: Expression = "300".asExpression(),
private val retaliationHitCount: Expression = "1".asExpression()
) : EntityResponseObjective(
speedMultiplier = speedMultiplier, // How do we transfer speed to the attack behaviour? Do we want to?
engageRange = engageRange,
disengageRange = disengageRange,
condition = condition, // Self/environment condition check
targetCondition = targetCondition, // target condition (will be checked on entity interaction)
description = description,
) {
private var fixation: EntityFixation? = null
override fun perform(
entity: LivingEntity,
brain: BiggerBrain,
activeObjectiveTags: MutableSet<String>
): ObjectiveStatus {
if (entity !is PokemonEntity || !entity.isAlive) return ObjectiveStatus.INVALID
if (trackedEntity != null) {
val attackTarget = brain.getMemory(MemoryModuleType.ATTACK_TARGET)
val avoidTarget = brain.getMemory(MemoryModuleType.AVOID_TARGET)
if (attackTarget != null && trackedEntity?.uuid == attackTarget.uuid) {
// Check if we continue
return ObjectiveStatus.ACTIVE
} else if (avoidTarget != null && trackedEntity?.uuid === avoidTarget.uuid) {
// Check if we continue
val runtime = mainThreadRuntime.withQueryValue("entity", (trackedEntity!!).asMostSpecificMoLangValue())
val disengageDistance = runtime.resolveFloat(disengageRange)
if (disengageDistance < entity.distanceTo(trackedEntity!!)) {
brain.eraseMemory(MemoryModuleType.AVOID_TARGET)
trackedEntity = null
}
} else {
// Something has overridden us
trackedEntity = null
}
}
return if (trackedEntity != null) ObjectiveStatus.ACTIVE else ObjectiveStatus.INACTIVE
}
override fun onRegister(entity: LivingEntity, brain: BiggerBrain) {
brain.subscribe(
priority = Priority.LOW,
memory = MemoryModuleType.HURT_BY_ENTITY
) { value, labels ->
if (value != null) {
// Something just smacked us, see if we should get mad about it.
val livingEntity = value
val runtime = mainThreadRuntime.withQueryValue("entity", (livingEntity).asMostSpecificMoLangValue())
val shouldTarget = runtime.resolveBoolean(condition)
if (shouldTarget && brain.fixation !is AttackFixation) {
brain.setMemory(
MemoryModuleType.ATTACK_TARGET, livingEntity,
runtime.resolveInt(maxAttackDuration).toLong()
)
brain.eraseMemory(MemoryModuleType.AVOID_TARGET)
trackedEntity = livingEntity
fixation = brain.fixate(
AttackFixation(
targetEntity = trackedEntity!!,
speedMultiplier = speedMultiplier,
distractionTypes = setOf(DistractionType.FORCE),
availableMoves = getRealTimeMoves(entity)
)
)
}
}
return@subscribe true
}
brain.subscribe(
priority = Priority.LOW,
memory = CobblemonMemories.ATTACK_TARGET_DATA
) { value, labels ->
if (value != null) {
// We smacked them, check if it's time for tactical retreat
val attackData = value
val livingEntity = brain.getMemory(MemoryModuleType.ATTACK_TARGET) ?: return@subscribe true
val runtime = mainThreadRuntime.withQueryValue("entity", (livingEntity).asMostSpecificMoLangValue())
if (attackData.hitsLanded >= runtime.resolveInt(retaliationHitCount)) {
// Justice served, carry on
brain.eraseMemory(MemoryModuleType.ATTACK_TARGET)
brain.eraseMemory(CobblemonMemories.ATTACK_TARGET_DATA)
brain.setMemory(MemoryModuleType.AVOID_TARGET, trackedEntity)
}
}
return@subscribe true
}
}
class Config : ObjectiveConfig {
val speedMultiplier: ExpressionOrEntityVariable = Either.left("0.5".asExpression())
val engageRange: ExpressionOrEntityVariable = Either.left("10.0".asExpression())
val disengageRange: ExpressionOrEntityVariable = Either.left("10.0".asExpression())
val condition: ExpressionOrEntityVariable = Either.left("true".asExpression())
val targetCondition: ExpressionOrEntityVariable = Either.left("false".asExpression())
val maxAttackDuration: ExpressionOrEntityVariable = Either.left("300".asExpression())
val retaliationHitCount: ExpressionOrEntityVariable = Either.left("1".asExpression())
val description: ExpressionOrEntityVariable = Either.left("'Neutral Behaviour'".asExpression())
override fun getVariables(
entity: LivingEntity,
behaviourConfigurationContext: BehaviourConfigurationContext
): List<MoLangConfigVariable> {
return listOf(
condition,
targetCondition,
engageRange,
disengageRange,
retaliationHitCount,
description
).asVariables()
}
override fun create(
entity: LivingEntity,
behaviourConfigurationContext: BehaviourConfigurationContext
): Objective {
return NeutralBehaviour(
condition = condition.asSimplifiedExpression(entity),
targetCondition = condition.asSimplifiedExpression(entity),
speedMultiplier = speedMultiplier.asSimplifiedExpression(entity),
engageRange = engageRange.asSimplifiedExpression(entity),
disengageRange = disengageRange.asSimplifiedExpression(entity),
maxAttackDuration = maxAttackDuration.asSimplifiedExpression(entity),
description = description.asSimplifiedExpression(entity),
retaliationHitCount = retaliationHitCount.asSimplifiedExpression(entity),
)
}
}
}
@@ -1,38 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.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"
}
/** 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
/** Runs when an objective is added to a [BiggerBrain]. Prime opportunity to register memory subscriptions. */
fun onRegister(entity: LivingEntity, brain: BiggerBrain) {}
}
@@ -1,28 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.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
}
@@ -1,234 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.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)
}
}
}

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