forked from mirrors/cobblemon
Compare commits
448
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d287ce3612 | ||
|
|
95b4dfcf43 | ||
|
|
03ca81abdf | ||
|
|
7f117fae07 | ||
|
|
0c90497abc | ||
|
|
449461e0b6 | ||
|
|
506214f858 | ||
|
|
7ed7d23ed0 | ||
|
|
0cb367e7cb | ||
|
|
05348c1f73 | ||
|
|
80716f72b5 | ||
|
|
6693e8c66c | ||
|
|
5873cb203f | ||
|
|
aa498450b5 | ||
|
|
e77124369e | ||
|
|
1aaf0d4be6 | ||
|
|
1d496bee5b | ||
|
|
244e1c0434 | ||
|
|
6d0e8dfbe5 | ||
|
|
a05380f95b | ||
|
|
283ff1ac8f | ||
|
|
bd1628a104 | ||
|
|
49ba657f1c | ||
|
|
e4ccfd4dba | ||
|
|
11452de369 | ||
|
|
fb4e5002a3 | ||
|
|
6dd7a7bc9a | ||
|
|
ba718561aa | ||
|
|
4ae1b84798 | ||
|
|
21411d26ac | ||
|
|
02b45e8b55 | ||
|
|
3f6c5ebc96 | ||
|
|
d217dfee54 | ||
|
|
9702cdebeb | ||
|
|
dc6e2fae2e | ||
|
|
2fec360805 | ||
|
|
5627233d3d | ||
|
|
766328874d | ||
|
|
953e7cb7ff | ||
|
|
01ce59c8c4 | ||
|
|
7468b180e2 | ||
|
|
cad0a9490d | ||
|
|
a6e046e405 | ||
|
|
fe3a893afc | ||
|
|
5c9530e10e | ||
|
|
5c45cf595c | ||
|
|
378f5434f7 | ||
|
|
64f32bf4b7 | ||
|
|
66ec46dcc6 | ||
|
|
1c3d027877 | ||
|
|
99cf6b8a65 | ||
|
|
1025a583d9 | ||
|
|
749303f640 | ||
|
|
fa80edf178 | ||
|
|
80f31a13d8 | ||
|
|
62ae8285e2 | ||
|
|
cba807be81 | ||
|
|
0bbdfd2c5e | ||
|
|
f43b7d3dd7 | ||
|
|
6291ac0e23 | ||
|
|
4f528c855b | ||
|
|
29c291fd7a | ||
|
|
493f71302e | ||
|
|
ab5a54f21e | ||
|
|
440e2855c0 | ||
|
|
8ebecc4928 | ||
|
|
4f0108c8a2 | ||
|
|
32cd79baec | ||
|
|
6df0173cbe | ||
|
|
a8ac858918 | ||
|
|
d46394cb1d | ||
|
|
d5cc2de7e5 | ||
|
|
a5088cf264 | ||
|
|
6c5fd6ea1c | ||
|
|
6b05ab19d4 | ||
|
|
ad995ad33f | ||
|
|
1a265fbe9a | ||
|
|
11b47e9c77 | ||
|
|
bab7a5e011 | ||
|
|
0fca9b17ed | ||
|
|
f648aa44a0 | ||
|
|
9b8663cb82 | ||
|
|
0ff5ef70c5 | ||
|
|
b978a4447b | ||
|
|
9bd52006c6 | ||
|
|
f66c782e22 | ||
|
|
3f75e5307f | ||
|
|
7c7488910d | ||
|
|
6ef1dc29ae | ||
|
|
e8376ed1c9 | ||
|
|
538d186488 | ||
|
|
dd70d159fb | ||
|
|
eece4256b8 | ||
|
|
b773110e9f | ||
|
|
1eceaa45b2 | ||
|
|
1ff2f16c61 | ||
|
|
a4a1c0a4d5 | ||
|
|
d80f0ed3c4 | ||
|
|
db4f6a04f3 | ||
|
|
19448a18e7 | ||
|
|
a0e125fedb | ||
|
|
b3b039677d | ||
|
|
621f539b98 | ||
|
|
79768a3407 | ||
|
|
117408ac20 | ||
|
|
2c0610757f | ||
|
|
f94e5eb2d3 | ||
|
|
075421856e | ||
|
|
97397aae7d | ||
|
|
0c9b3ede11 | ||
|
|
78e15b0d8e | ||
|
|
b31236dbfb | ||
|
|
da1e172164 | ||
|
|
ab0f2cbd79 | ||
|
|
62c7882469 | ||
|
|
b6c2c65a6c | ||
|
|
01206fb67a | ||
|
|
07af63db25 | ||
|
|
1047648f68 | ||
|
|
6523a610ff | ||
|
|
6d132e3f39 | ||
|
|
b311d092de | ||
|
|
6f134a50e0 | ||
|
|
a0531f2d2f | ||
|
|
80f5050ecd | ||
|
|
e32a2da8a4 | ||
|
|
e92609b6c7 | ||
|
|
abc0c72707 | ||
|
|
e55f46c990 | ||
|
|
6ee5714bf9 | ||
|
|
bb379764f7 | ||
|
|
3ec78d9d7e | ||
|
|
12973dd66a | ||
|
|
ed30e4cd6f | ||
|
|
32c260446e | ||
|
|
41f1eb699f | ||
|
|
e68a593e9e | ||
|
|
903ffe2143 | ||
|
|
1d6845d595 | ||
|
|
f8eb72e437 | ||
|
|
28a3b8edfe | ||
|
|
96ee23b7d1 | ||
|
|
d3ce829e5b | ||
|
|
4010f138ae | ||
|
|
5100d6c028 | ||
|
|
154ff75a9d | ||
|
|
678c98a5b6 | ||
|
|
d9143788bf | ||
|
|
7e1956f165 | ||
|
|
994a41af78 | ||
|
|
2d5f15a4db | ||
|
|
8442000708 | ||
|
|
695ac93f99 | ||
|
|
e5e5c29c12 | ||
|
|
893277fdec | ||
|
|
60dc881e3d | ||
|
|
f98acf0bc4 | ||
|
|
6e104fdb01 | ||
|
|
05c559e203 | ||
|
|
1323caeeec | ||
|
|
6dc5809622 | ||
|
|
d4416e50b2 | ||
|
|
520bcda956 | ||
|
|
d10c082e67 | ||
|
|
72d608b300 | ||
|
|
2b4dc467ff | ||
|
|
b9390838d0 | ||
|
|
e36bbd98f2 | ||
|
|
c4aa925f39 | ||
|
|
ad5e85ad97 | ||
|
|
5279603432 | ||
|
|
583fba4e77 | ||
|
|
9ee983b2a2 | ||
|
|
16ffbbbd9e | ||
|
|
f23495fe99 | ||
|
|
f61448c333 | ||
|
|
406770eaf1 | ||
|
|
2513fab317 | ||
|
|
3a79e49edb | ||
|
|
87dad0143b | ||
|
|
bdc91fea71 | ||
|
|
eb64666229 | ||
|
|
f7ed0c8799 | ||
|
|
9fde7adb7b | ||
|
|
8b8c07065b | ||
|
|
6d761fef4f | ||
|
|
737ce1314c | ||
|
|
7b89f82d45 | ||
|
|
2203d543a1 | ||
|
|
c129157687 | ||
|
|
276c49ac5c | ||
|
|
f517a63cc9 | ||
|
|
1e2f685ee5 | ||
|
|
5245614804 | ||
|
|
8b97c3cb85 | ||
|
|
d607463116 | ||
|
|
07cc92eb18 | ||
|
|
9665eba7b0 | ||
|
|
9f83c7c72c | ||
|
|
9bb74358f7 | ||
|
|
e2219730e6 | ||
|
|
4daaf50b1a | ||
|
|
b79792ebbf | ||
|
|
7a0b294d3d | ||
|
|
ad1312d077 | ||
|
|
d65d7b779f | ||
|
|
7ce25d8158 | ||
|
|
178d7293fb | ||
|
|
ba0bba2202 | ||
|
|
29d5ce5f0e | ||
|
|
b9ebfc17ed | ||
|
|
2e28792c92 | ||
|
|
648d15403f | ||
|
|
3fe3f521ee | ||
|
|
0ff638a0ca | ||
|
|
485b5f46f6 | ||
|
|
b7c2063510 | ||
|
|
94d7ae5290 | ||
|
|
d75d5da250 | ||
|
|
d3cd1be24e | ||
|
|
d1f11316ec | ||
|
|
1e50bd999b | ||
|
|
0257f6c6a0 | ||
|
|
a3bc1cf72b | ||
|
|
e236632c67 | ||
|
|
4730d97898 | ||
|
|
36994a134d | ||
|
|
97899b3fa9 | ||
|
|
713bd6d6ef | ||
|
|
48736fad1f | ||
|
|
5a47deb9a7 | ||
|
|
4b01facb4f | ||
|
|
158d4d6aed | ||
|
|
ab91776d97 | ||
|
|
286ffe7219 | ||
|
|
79da4cb8a8 | ||
|
|
32f3c75d45 | ||
|
|
acc81d60ad | ||
|
|
4b093a0072 | ||
|
|
e0c57007db | ||
|
|
66b3579a9b | ||
|
|
0981bb71ce | ||
|
|
6b762cd6d3 | ||
|
|
581c466c0e | ||
|
|
5622c65fca | ||
|
|
51c6ea576b | ||
|
|
b7b8fe1fb9 | ||
|
|
1634a20464 | ||
|
|
7190c33dab | ||
|
|
8ea20925d3 | ||
|
|
d4c7ce4eb9 | ||
|
|
625237457a | ||
|
|
0f21173f5f | ||
|
|
7fda4570c6 | ||
|
|
e0aae17f37 | ||
|
|
32b4459e19 | ||
|
|
9072a7ac3c | ||
|
|
0e39874f9a | ||
|
|
aa5d116910 | ||
|
|
1f49d0d21b | ||
|
|
05a612bf79 | ||
|
|
ab0d839006 | ||
|
|
56b061c93d | ||
|
|
ec97e16609 | ||
|
|
e4ce58414c | ||
|
|
b07856ed58 | ||
|
|
b7d2f38490 | ||
|
|
295185c1cf | ||
|
|
f0d8ee21d9 | ||
|
|
b203020b4d | ||
|
|
5962149da8 | ||
|
|
24101bed08 | ||
|
|
321736657e | ||
|
|
e50309871a | ||
|
|
36cd7e426a | ||
|
|
6660747b31 | ||
|
|
c021f28dc7 | ||
|
|
a7a2eceda6 | ||
|
|
a4cac592e6 | ||
|
|
b29c6e0d9c | ||
|
|
ba9ea5b793 | ||
|
|
47d5ac4108 | ||
|
|
c6df8bbb61 | ||
|
|
97055b7ede | ||
|
|
df64ac268e | ||
|
|
541a3e257b | ||
|
|
1a995dfca8 | ||
|
|
9aef6b02ad | ||
|
|
f914f20283 | ||
|
|
03e558efbb | ||
|
|
925a0551b2 | ||
|
|
1677f9fe89 | ||
|
|
1db4c90479 | ||
|
|
e3beeff5a8 | ||
|
|
52a6d84041 | ||
|
|
84f2183f36 | ||
|
|
492cd7bc9e | ||
|
|
c21a25610c | ||
|
|
4eed3c06a7 | ||
|
|
7f804445c4 | ||
|
|
89eee30488 | ||
|
|
b386a20351 | ||
|
|
ac06bc6103 | ||
|
|
cda8697dd9 | ||
|
|
f7f7400163 | ||
|
|
ea0c4731d8 | ||
|
|
204b923289 | ||
|
|
6f34b6347d | ||
|
|
a935edbb55 | ||
|
|
e58c3b9422 | ||
|
|
a5ddc556f3 | ||
|
|
a541cc3e55 | ||
|
|
e355bdd68a | ||
|
|
d687d91090 | ||
|
|
6b38147c23 | ||
|
|
0241dbcfa1 | ||
|
|
b2e641e269 | ||
|
|
63f32cb79c | ||
|
|
7b05868c5a | ||
|
|
c4fd0d6fd7 | ||
|
|
a4f0eb999c | ||
|
|
85b7231ff6 | ||
|
|
0f3d0008e9 | ||
|
|
74addf45c3 | ||
|
|
f97a8a8475 | ||
|
|
e8cc212968 | ||
|
|
7918924c1c | ||
|
|
28a41860b9 | ||
|
|
20ef700422 | ||
|
|
c017863ef8 | ||
|
|
d28ac2facf | ||
|
|
b391f13b11 | ||
|
|
c2f29a0dc3 | ||
|
|
0c4a148c52 | ||
|
|
5c4aa543fa | ||
|
|
d3a9b50c03 | ||
|
|
46962d95fc | ||
|
|
ea50837750 | ||
|
|
b03fe17e8c | ||
|
|
16cf5ecbb7 | ||
|
|
1b34278ec4 | ||
|
|
045d5a8a5e | ||
|
|
5729c9d5be | ||
|
|
a286584c32 | ||
|
|
a5895aaa7c | ||
|
|
0ab6fef566 | ||
|
|
a973d21bc0 | ||
|
|
e149d3f4c8 | ||
|
|
adfde3e236 | ||
|
|
18cdf705ed | ||
|
|
e5730a5119 | ||
|
|
b295cd7bef | ||
|
|
a168715b3c | ||
|
|
55f8212451 | ||
|
|
c5e7c79c2a | ||
|
|
d4a44b4977 | ||
|
|
f9f0668f6b | ||
|
|
d01beb3ba8 | ||
|
|
3cac052772 | ||
|
|
84bc8bc426 | ||
|
|
98a25908c2 | ||
|
|
4ed3d41eb1 | ||
|
|
672fd7365f | ||
|
|
39a16beb14 | ||
|
|
32dbd1f8c9 | ||
|
|
bcc23ec4fc | ||
|
|
143fc8256b | ||
|
|
70d753d1d3 | ||
|
|
00a67b22df | ||
|
|
12811fe504 | ||
|
|
40ffb99928 | ||
|
|
78fb8fb834 | ||
|
|
614663a49c | ||
|
|
ca9955f4cd | ||
|
|
2f40c5d71b | ||
|
|
1a84ca4ba0 | ||
|
|
5f29986d41 | ||
|
|
bfb63088c1 | ||
|
|
626bd7f4f4 | ||
|
|
bff941264c | ||
|
|
10b472e980 | ||
|
|
7888a8ac31 | ||
|
|
d79adfc0be | ||
|
|
e4baf5fe3e | ||
|
|
3013c30084 | ||
|
|
fc10b550cd | ||
|
|
514787a1c4 | ||
|
|
c32f3c5581 | ||
|
|
94b9b7ade3 | ||
|
|
ee33b4292c | ||
|
|
271e74d6f2 | ||
|
|
2ea41c3dae | ||
|
|
a6d93aba17 | ||
|
|
db1711b76f | ||
|
|
ab5b2e8fdf | ||
|
|
efa4028948 | ||
|
|
65529474cb | ||
|
|
7d756249ae | ||
|
|
3fcfd2ff6d | ||
|
|
98545541e0 | ||
|
|
56820ad626 | ||
|
|
64546b25f2 | ||
|
|
d5496fb465 | ||
|
|
bcd2474d5f | ||
|
|
e331b959c8 | ||
|
|
ce159ac742 | ||
|
|
3e4eaf0397 | ||
|
|
2a2308b4a4 | ||
|
|
47511bac89 | ||
|
|
c6a5e69f91 | ||
|
|
32a9185cc1 | ||
|
|
ec54aeb1fd | ||
|
|
9aab1113fd | ||
|
|
86578cd266 | ||
|
|
566d74a638 | ||
|
|
23988b55a1 | ||
|
|
d5418ed5c7 | ||
|
|
5318b1470d | ||
|
|
0ca91eb071 | ||
|
|
7d31719cd1 | ||
|
|
25ed3d28f4 | ||
|
|
5df64d038b | ||
|
|
ec6d838ea6 | ||
|
|
6bea8aafe6 | ||
|
|
5bbf5d2d85 | ||
|
|
6d09590374 | ||
|
|
0e0ebc0b48 | ||
|
|
0c4a4ddda1 | ||
|
|
1efeddc875 | ||
|
|
82c6e8f42d | ||
|
|
5ff522bb85 | ||
|
|
b2b73575d4 | ||
|
|
d7bbccab08 | ||
|
|
21b8fe8474 | ||
|
|
94c210efd4 | ||
|
|
d3100be95a | ||
|
|
3353c15af6 | ||
|
|
1cced11431 | ||
|
|
646ad5606a | ||
|
|
8214e848f9 | ||
|
|
ece9c63121 | ||
|
|
ce81f07aa2 | ||
|
|
9bcaa6dfad | ||
|
|
295efe8c11 | ||
|
|
5cff811aab | ||
|
|
00e721d714 | ||
|
|
0c602027cb | ||
|
|
7070369cea |
Generated
BIN
Binary file not shown.
|
Before Width: | Height: | Size: 2.8 KiB |
@@ -0,0 +1,11 @@
|
||||
### Additions
|
||||
|
||||
### Changes
|
||||
- Replaced the entity AI for Pokémon and NPCs with an entirely custom one. Yeah we changed the entity AI in 1.7 too, you're remembering right.
|
||||
- Improved performance of entity AI.
|
||||
|
||||
### Fixes
|
||||
|
||||
### Developer
|
||||
|
||||
### Molang & Datapacks
|
||||
@@ -0,0 +1,6 @@
|
||||
|
||||
|
||||
|
||||
### Developer
|
||||
- Spawning Influences now have the context of what the other buckets are when adjusting bucket weights. This will break existing influences that do bucket weight adjustment.
|
||||
- Renamed heaps of things in the spawning system to make more sense.
|
||||
@@ -0,0 +1,223 @@
|
||||
# Habitat Block GUI Editor - Developer Instructions
|
||||
These instructions are brought to you by AI slop with Hiroku reviewing it afterwards. It does seem fairly accurate
|
||||
though.
|
||||
|
||||
## Overview
|
||||
The Habitat Block is a spawning tool that allows creative mode players to configure custom Pokémon spawning behavior. We need a GUI editor so read below to understand what it needs to do.
|
||||
|
||||
Phases might need a bit of explanation. The idea is that each habitat block will have a current "phase", which changes every day. The number of different phases is something that can be configured per block, as well as what order the phase will move through when it changes.
|
||||
|
||||
In the case of 3 phases, the different phase orders do the following:
|
||||
SIMPLE: 1-2-3-1-2-3-1-2-3
|
||||
FIXED_RANDOM: 1-3-2-1-3-2-1-3-2 (or some other repeating pattern, based on the position of the habitat block)
|
||||
FULL_RANDOM: 1-3-1-2-3-1-3-1-2 (completely random value each day)
|
||||
|
||||
The idea is like, if you had set 3 phases for a habitat block, you could make Rattata spawn in phase 1, Patrat in phase 2, and Zigzagoon in phase 3.
|
||||
|
||||
## Entry Point
|
||||
- GUI opens when a creative mode player right-clicks a Habitat Block
|
||||
- Packet: `OpenHabitatBlockEditorPacket` sends current settings to client
|
||||
- When you're done you send a `SaveHabitatBlockSettingsPacket` back to the server with the same settings DTO instance (but modified, one would assume)
|
||||
- Settings are applied via `HabitatBlockEntity.applySettings(HabitatSettingsDTO)`
|
||||
|
||||
## Data Structure
|
||||
|
||||
### Root: `HabitatSettingsDTO`
|
||||
All settings are contained in this DTO which is sent between client and server.
|
||||
|
||||
#### Common Settings (always present)
|
||||
```kotlin
|
||||
numberOfPhases: Int = 1 // Number of spawn phases (TODO: not yet implemented)
|
||||
phaseOrder: HabitatPhaseOrder // How the passage of phases will work with each day
|
||||
levelRange: IntRange // Min..Max level for spawned Pokémon
|
||||
modifiers: String // PokemonProperties string (e.g., "shiny gender=male")
|
||||
mimicId: ResourceLocation // Block appearance (e.g., "minecraft:stone")
|
||||
```
|
||||
|
||||
#### Spawning Mode Toggle
|
||||
```kotlin
|
||||
isActivatedSpawning: Boolean // true = Activated mode, false = Natural mode
|
||||
```
|
||||
|
||||
Based on this boolean, EITHER `activatedSettings` OR `naturalSettings` will be populated (never both).
|
||||
|
||||
---
|
||||
|
||||
## Spawning Modes
|
||||
|
||||
### Mode 1: Activated Spawning (`ActivatedHabitatSettingsDTO`)
|
||||
Triggered spawning system - spawns occur based on specific triggers.
|
||||
|
||||
```kotlin
|
||||
trigger: Enum // REDSTONE, TICK, or RANDOM_TICK
|
||||
chance: Float = 1.0 // Probability (0.0-1.0) spawn attempt occurs on trigger
|
||||
cancelledNaturalSpawningRange: Int // Radius (blocks) where natural spawning is disabled
|
||||
spawnRange: Int // Radius (blocks) where spawning can occur
|
||||
maxSpawns: Int = -1 // Max concurrent spawns from this block (-1 = infinite)
|
||||
maxSpawnsPerActivation: Int = 1 // Max spawns per single trigger event
|
||||
habitatPool: HabitatPoolSettingsDTO // What Pokémon can spawn (see below)
|
||||
```
|
||||
|
||||
**Trigger Types:**
|
||||
- `REDSTONE`: Spawns on redstone pulse (rising edge)
|
||||
- `TICK`: Attempts spawn every game tick (very frequent)
|
||||
- `RANDOM_TICK`: Attempts spawn on random tick events
|
||||
|
||||
### Mode 2: Natural Spawning (`NaturalHabitatSettingsDTO`)
|
||||
Modifies the natural spawning system in the area.
|
||||
|
||||
```kotlin
|
||||
replaceSpawns: Boolean // true = only this pool spawns, false = adds to natural spawns
|
||||
rangeOfInfluence: Int // Radius (blocks) where this influences spawning
|
||||
habitatPool: HabitatPoolSettingsDTO // What Pokémon can spawn (see below)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Habitat Pool Settings (`HabitatPoolSettingsDTO`)
|
||||
|
||||
Defines what Pokémon can spawn. Can reference existing pools or define custom ones.
|
||||
|
||||
```kotlin
|
||||
id: ResourceLocation // Pool identifier
|
||||
isReference: Boolean // true = use existing pool, false = custom pool
|
||||
isActivated: Boolean // Must match parent spawning mode
|
||||
|
||||
// Only present when isReference = false:
|
||||
name: String? // Display name for custom pool
|
||||
spawns: List<HabitatSpawn>? // List of spawn entries
|
||||
```
|
||||
|
||||
**Pool Types:**
|
||||
- **Reference Pool**: `isReference = true` - Uses a pre-defined pool by ID (e.g., `cobblemon:forest_day`)
|
||||
- **Custom Pool**: `isReference = false` - Defines spawns inline with `name` and `spawns` list
|
||||
|
||||
**Custom Pool IDs**: Must start with `custom__` prefix (e.g., `cobblemon:custom__my_pool`)
|
||||
|
||||
---
|
||||
|
||||
## GUI Requirements
|
||||
|
||||
### Layout Sections
|
||||
|
||||
#### 1. Common Settings Panel
|
||||
- **Number of Phases**: Integer input greater than zero (default: 1)
|
||||
- **Phase Order**: Habitat Phase Order dropdown (default: SIMPLE)
|
||||
- **Level Range**: Two integer inputs (Min/Max) - Default: 1 to config max Pokémon level
|
||||
- **Modifiers**: Text input for PokemonProperties string
|
||||
- Examples: `shiny`, `gender=male`, `shiny gender=female level=50`
|
||||
- **Mimic Block**: Block selector/dropdown
|
||||
- Default: `minecraft:stone`
|
||||
- See `HabitatBlockEntity.mimicCycle` for suggested blocks
|
||||
|
||||
#### 2. Spawning Mode Selector
|
||||
Radio buttons or toggle:
|
||||
- **Activated Spawning** (trigger-based)
|
||||
- **Natural Spawning** (world spawning influenced)
|
||||
|
||||
#### 3. Mode-Specific Settings
|
||||
|
||||
**If Activated Spawning:**
|
||||
- **Trigger**: Dropdown (REDSTONE / TICK / RANDOM_TICK)
|
||||
- **Chance**: Float slider (0.0 - 1.0, default: 1.0)
|
||||
- **Cancel Natural Spawning Range**: Integer input (blocks)
|
||||
- **Spawn Range**: Integer input (blocks)
|
||||
- **Max Spawns**: Integer input (-1 for infinite, default: -1)
|
||||
- **Max Spawns Per Activation**: Integer input (default: 1)
|
||||
- **Habitat Pool**: Pool selector (see below)
|
||||
|
||||
**If Natural Spawning:**
|
||||
- **Replace Spawns**: Checkbox (true = exclusive, false = additive)
|
||||
- **Range of Influence**: Integer input (blocks)
|
||||
- **Habitat Pool**: Pool selector (see below)
|
||||
|
||||
#### 4. Habitat Pool Selector
|
||||
Two options:
|
||||
- **Use Existing Pool**: Dropdown of registered pools
|
||||
- Set `isReference = true`
|
||||
- Set `id` to selected pool
|
||||
- Ensure `isActivated` matches spawning mode
|
||||
- **Create Custom Pool**: Opens pool editor
|
||||
- Set `isReference = false`
|
||||
- Generate ID with `custom__` prefix
|
||||
- Set `name` and `spawns` list
|
||||
- User defines spawn entries (implementation depends on `HabitatSpawn` structure)
|
||||
|
||||
---
|
||||
|
||||
## Validation Rules
|
||||
|
||||
1. **Level Range**: Min must be ≥ 1, Max must be ≤ config max, Min ≤ Max
|
||||
2. **Number of Phases**: Must be greater than zero
|
||||
3. **Chance**: Must be between 0.0 and 1.0
|
||||
4. **Ranges**: All range values must be ≥ 0
|
||||
5. **Pool Validation**:
|
||||
- If `isActivated = true`, pool must be `ActivatedHabitatPool`
|
||||
- If `isActivated = false`, pool must be `NaturalHabitatPool`
|
||||
- If reference pool not found, server logs error and rejects settings
|
||||
6. **Custom Pool IDs**: Must start with `custom__`
|
||||
|
||||
---
|
||||
|
||||
## Network Flow
|
||||
|
||||
1. **Opening GUI**: Server sends `OpenHabitatBlockEditorPacket` with current `HabitatSettingsDTO`
|
||||
2. **Saving Changes**: Client sends updated `HabitatSettingsDTO` back to server
|
||||
3. **Server Processing**:
|
||||
- `HabitatBlockEntity.applySettings()` validates and applies changes
|
||||
- Calls `refreshFromSettings()` to regenerate spawn details
|
||||
- Marks block entity as changed and syncs to clients
|
||||
|
||||
---
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- **Creative Mode Only**: GUI should only be accessible to creative mode players
|
||||
|
||||
---
|
||||
|
||||
## Example Configurations
|
||||
|
||||
### Example 1: Redstone-Triggered Shiny Spawner
|
||||
```kotlin
|
||||
isActivatedSpawning = true
|
||||
phaseOrder = FIXED_RANDOM
|
||||
numberOfPhases = 4
|
||||
levelRange = 50..60
|
||||
modifiers = "shiny"
|
||||
mimicId = "minecraft:diamond_block"
|
||||
|
||||
activatedSettings:
|
||||
trigger = REDSTONE
|
||||
chance = 1.0
|
||||
spawnRange = 10
|
||||
maxSpawns = 1
|
||||
maxSpawnsPerActivation = 1
|
||||
habitatPool = (reference to existing pool)
|
||||
```
|
||||
|
||||
### Example 2: Natural Spawn Replacement Zone
|
||||
```kotlin
|
||||
isActivatedSpawning = false
|
||||
levelRange = 1..100
|
||||
phaseOrder = SIMPLE
|
||||
numberOfPhases = 1
|
||||
modifiers = ""
|
||||
mimicId = "minecraft:moss_block"
|
||||
|
||||
naturalSettings:
|
||||
replaceSpawns = true
|
||||
rangeOfInfluence = 32
|
||||
habitatPool = (custom pool with specific Pokémon)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Related Files
|
||||
- `HabitatBlockEntity.kt` - Main block entity logic
|
||||
- `HabitatSettingsDTO.kt` - Root settings container
|
||||
- `ActivatedHabitatSettingsDTO.kt` - Activated mode settings
|
||||
- `NaturalHabitatSettingsDTO.kt` - Natural mode settings
|
||||
- `HabitatPoolSettingsDTO.kt` - Pool configuration
|
||||
- `ActivatedHabitatSpawning.kt` - Activated spawning implementation
|
||||
- `NaturalHabitatSpawning.kt` - Natural spawning implementation
|
||||
@@ -18,6 +18,7 @@ import com.cobblemon.mod.common.api.drop.EvolutionItemDropEntry
|
||||
import com.cobblemon.mod.common.api.drop.ItemDropEntry
|
||||
import com.cobblemon.mod.common.api.events.CobblemonEvents
|
||||
import com.cobblemon.mod.common.api.events.CobblemonEvents.DATA_SYNCHRONIZED
|
||||
import com.cobblemon.mod.common.api.habitats.HabitatPools
|
||||
import com.cobblemon.mod.common.api.interaction.RequestManager
|
||||
import com.cobblemon.mod.common.api.molang.MoLangLoadedFilesCache
|
||||
import com.cobblemon.mod.common.api.molang.ObjectValue
|
||||
@@ -182,6 +183,8 @@ object Cobblemon {
|
||||
var wallpapers = mutableMapOf<UUID, Set<ResourceLocation>>()
|
||||
|
||||
val serverPlayerStructs = mutableMapOf<UUID, ObjectValue<Player>>()
|
||||
/** Player UUID to entity UUID that they're brain debuggin' */
|
||||
val serverPlayerDebuggingPokemonBrainEntities = mutableMapOf<UUID, UUID>()
|
||||
|
||||
val statistics: CobblemonStats = CobblemonStats
|
||||
|
||||
@@ -204,6 +207,8 @@ object Cobblemon {
|
||||
this.LOGGER.info(" - Branch: ${CobblemonBuildDetails.BRANCH}")
|
||||
}
|
||||
|
||||
this.loadConfig()
|
||||
|
||||
implementation.registerRecipeSerializers()
|
||||
implementation.registerRecipeTypes()
|
||||
implementation.registerPermissionValidator()
|
||||
@@ -232,7 +237,6 @@ object Cobblemon {
|
||||
ExperienceGroups.registerDefaults()
|
||||
CaptureCalculators.registerDefaults()
|
||||
|
||||
this.loadConfig()
|
||||
// CobblemonBlockPredicates.touch()
|
||||
CobblemonOrePlacedFeatures.register()
|
||||
CobblemonPlacedFeatures.register()
|
||||
@@ -256,6 +260,7 @@ object Cobblemon {
|
||||
playerDataManager.onPlayerDisconnect(it.player)
|
||||
RequestManager.onLogoff(it.player)
|
||||
serverPlayerStructs.remove(it.player.uuid)
|
||||
serverPlayerDebuggingPokemonBrainEntities.remove(it.player.uuid)
|
||||
}
|
||||
PlatformEvents.PLAYER_DEATH.subscribe {
|
||||
PCLinkManager.removeLink(it.player.uuid)
|
||||
@@ -363,6 +368,7 @@ object Cobblemon {
|
||||
val server = event.server
|
||||
MoLangLoadedFilesCache.initialize(server)
|
||||
playerDataManager = PlayerInstancedDataStoreManager().also { it.setup(server) }
|
||||
HabitatPools.onServerLoading(server)
|
||||
|
||||
val mongoClient: MongoClient?
|
||||
|
||||
|
||||
@@ -29,6 +29,8 @@ object CobblemonActivities {
|
||||
val POKEMON_HERD = register(Activity("pokemon_herd"))
|
||||
@JvmField
|
||||
val POKEMON_POLLINATION = register(Activity("pokemon_pollination"))
|
||||
@JvmField
|
||||
val GUARD_NEST = register(Activity("guard_nest"))
|
||||
|
||||
@JvmStatic
|
||||
fun register(activity: Activity): Activity {
|
||||
|
||||
@@ -12,6 +12,8 @@ import com.bedrockk.molang.Expression
|
||||
import com.cobblemon.mod.common.CobblemonNetwork.sendPacket
|
||||
import com.cobblemon.mod.common.api.ai.CobblemonBehaviour
|
||||
import com.cobblemon.mod.common.api.ai.config.BehaviourConfig
|
||||
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
|
||||
import com.cobblemon.mod.common.api.ai.config.sense.SenseConfig
|
||||
import com.cobblemon.mod.common.api.ai.config.task.TaskConfig
|
||||
import com.cobblemon.mod.common.api.data.JsonDataRegistry
|
||||
import com.cobblemon.mod.common.api.data.JsonDataRegistry.Companion.JSON_EXTENSION
|
||||
@@ -26,6 +28,8 @@ import com.cobblemon.mod.common.util.adapters.ExpressionLikeAdapter
|
||||
import com.cobblemon.mod.common.util.adapters.ExpressionOrEntityVariableAdapter
|
||||
import com.cobblemon.mod.common.util.adapters.IdentifierAdapter
|
||||
import com.cobblemon.mod.common.util.adapters.MemoryModuleTypeAdapter
|
||||
import com.cobblemon.mod.common.util.adapters.ObjectiveConfigAdapter
|
||||
import com.cobblemon.mod.common.util.adapters.SenseConfigAdapter
|
||||
import com.cobblemon.mod.common.util.adapters.SensorTypeAdapter
|
||||
import com.cobblemon.mod.common.util.adapters.TaskConfigAdapter
|
||||
import com.cobblemon.mod.common.util.adapters.TranslatedTextAdapter
|
||||
@@ -55,6 +59,8 @@ object CobblemonBehaviours : JsonDataRegistry<CobblemonBehaviour> {
|
||||
.registerTypeAdapter(SensorType::class.java, SensorTypeAdapter)
|
||||
.registerTypeAdapter(BehaviourConfig::class.java, BehaviourConfigAdapter)
|
||||
.registerTypeAdapter(TaskConfig::class.java, TaskConfigAdapter)
|
||||
.registerTypeAdapter(ObjectiveConfig::class.java, ObjectiveConfigAdapter)
|
||||
.registerTypeAdapter(SenseConfig::class.java, SenseConfigAdapter)
|
||||
.registerTypeAdapter(
|
||||
TypeToken.getParameterized(Either::class.java, Expression::class.java, MoLangConfigVariable::class.java).type,
|
||||
ExpressionOrEntityVariableAdapter
|
||||
|
||||
@@ -18,6 +18,7 @@ import com.cobblemon.mod.common.block.entity.RestorationTankBlockEntity
|
||||
import com.cobblemon.mod.common.block.multiblock.FossilMultiblockBuilder
|
||||
import com.cobblemon.mod.common.block.entity.*
|
||||
import com.cobblemon.mod.common.block.entity.DisplayCaseBlockEntity
|
||||
import com.cobblemon.mod.common.block.habitat.HabitatBlockEntity
|
||||
import com.cobblemon.mod.common.platform.PlatformRegistry
|
||||
import net.minecraft.core.Registry
|
||||
import net.minecraft.core.registries.BuiltInRegistries
|
||||
@@ -49,6 +50,11 @@ object CobblemonBlockEntities : PlatformRegistry<Registry<BlockEntityType<*>>, R
|
||||
@JvmField
|
||||
val HANGING_SIGN: BlockEntityType<CobblemonHangingSignBlockEntity> = this.create("hanging_sign", BlockEntityType.Builder.of(::CobblemonHangingSignBlockEntity, CobblemonBlocks.APRICORN_HANGING_SIGN, CobblemonBlocks.APRICORN_WALL_HANGING_SIGN, CobblemonBlocks.SACCHARINE_HANGING_SIGN, CobblemonBlocks.SACCHARINE_WALL_HANGING_SIGN).build(null))
|
||||
|
||||
@JvmField
|
||||
val HABITAT_BLOCK: BlockEntityType<HabitatBlockEntity> = this.create("habitat_block",
|
||||
BlockEntityType.Builder.of(::HabitatBlockEntity, CobblemonBlocks.HABITAT_BLOCK).build(null)
|
||||
)
|
||||
|
||||
@JvmField
|
||||
val GILDED_CHEST: BlockEntityType<GildedChestBlockEntity> = this.create("chest", BlockEntityType.Builder.of(::GildedChestBlockEntity,
|
||||
CobblemonBlocks.GILDED_CHEST,
|
||||
@@ -82,6 +88,35 @@ object CobblemonBlockEntities : PlatformRegistry<Registry<BlockEntityType<*>>, R
|
||||
).build(null)
|
||||
)
|
||||
|
||||
val NEST: BlockEntityType<NestBlockEntity> = this.create("nest",
|
||||
BlockEntityType.Builder.of(
|
||||
::NestBlockEntity,
|
||||
CobblemonBlocks.NEST_FIERY,
|
||||
CobblemonBlocks.NEST_GRASSY,
|
||||
CobblemonBlocks.NEST_ICY,
|
||||
CobblemonBlocks.NEST_ROCKY,
|
||||
CobblemonBlocks.NEST_SANDY,
|
||||
CobblemonBlocks.NEST_SHROOMY,
|
||||
CobblemonBlocks.NEST_TWIGGY,
|
||||
CobblemonBlocks.NEST_WATERY
|
||||
).build(null))
|
||||
|
||||
val EGG: BlockEntityType<EggBlockEntity> = this.create("egg",
|
||||
BlockEntityType.Builder.of(
|
||||
::EggBlockEntity,
|
||||
CobblemonBlocks.EGG
|
||||
).build(null)
|
||||
)
|
||||
|
||||
@JvmField
|
||||
val INCUBATOR: BlockEntityType<IncubatorBlockEntity> = this.create("incubator",
|
||||
BlockEntityType.Builder.of(
|
||||
::IncubatorBlockEntity,
|
||||
CobblemonBlocks.INCUBATOR
|
||||
).build(null)
|
||||
)
|
||||
|
||||
|
||||
@JvmField
|
||||
val DISPLAY_CASE: BlockEntityType<DisplayCaseBlockEntity> = this.create("display_case",
|
||||
BlockEntityType.Builder.of(::DisplayCaseBlockEntity, CobblemonBlocks.DISPLAY_CASE).build(null)
|
||||
|
||||
@@ -11,6 +11,7 @@ package com.cobblemon.mod.common
|
||||
import com.cobblemon.mod.common.api.apricorn.Apricorn
|
||||
import com.cobblemon.mod.common.api.pokemon.stats.Stats
|
||||
import com.cobblemon.mod.common.block.*
|
||||
import com.cobblemon.mod.common.api.nest.WildNestType
|
||||
import com.cobblemon.mod.common.block.campfirepot.CampfireBlock
|
||||
import com.cobblemon.mod.common.block.LecternBlock
|
||||
import com.cobblemon.mod.common.block.MintBlock.MintType
|
||||
@@ -24,6 +25,7 @@ import com.cobblemon.mod.common.block.sign.CobblemonWallHangingSignBlock
|
||||
import com.cobblemon.mod.common.block.sign.CobblemonWallSignBlock
|
||||
import com.cobblemon.mod.common.block.general.HorizontalRotationCarpetBlock
|
||||
import com.cobblemon.mod.common.block.general.HorizontalRotationalBlock
|
||||
import com.cobblemon.mod.common.block.habitat.HabitatBlock
|
||||
import com.cobblemon.mod.common.mixin.invoker.*
|
||||
import com.cobblemon.mod.common.platform.PlatformRegistry
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
@@ -46,7 +48,6 @@ import net.minecraft.world.level.block.state.properties.NoteBlockInstrument
|
||||
import net.minecraft.world.level.block.state.properties.WoodType
|
||||
import net.minecraft.world.level.material.MapColor
|
||||
import net.minecraft.world.level.material.PushReaction
|
||||
import net.minecraft.world.phys.shapes.Shapes
|
||||
|
||||
@Suppress("SameParameterValue", "HasPlatformType", "MemberVisibilityCanBePrivate", "unused")
|
||||
object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<Block>>, Block>() {
|
||||
@@ -813,6 +814,72 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
|
||||
val YACHE_BERRY = this.berryBlock("yache")
|
||||
//val BINDING_SOIL = this.create("binding_soil", BindingSoilBlock(BlockBehaviour.Properties.of()))
|
||||
|
||||
val NEST_FIERY = this.create(
|
||||
"nest_fiery",
|
||||
WildNestBlock(WildNestType.FIERY, BlockBehaviour.Properties.of().noOcclusion().pushReaction(PushReaction.DESTROY))
|
||||
)
|
||||
val NEST_GRASSY = this.create(
|
||||
"nest_grassy",
|
||||
WildNestBlock(WildNestType.GRASSY, BlockBehaviour.Properties.of().noOcclusion().pushReaction(PushReaction.DESTROY))
|
||||
)
|
||||
val NEST_ICY = this.create(
|
||||
"nest_icy",
|
||||
WildNestBlock(WildNestType.ICY, BlockBehaviour.Properties.of().noOcclusion().pushReaction(PushReaction.DESTROY))
|
||||
)
|
||||
val NEST_PLAYER = this.create(
|
||||
"nest_player",
|
||||
WildNestBlock(WildNestType.PLAYER, BlockBehaviour.Properties.of().noOcclusion().pushReaction(PushReaction.DESTROY))
|
||||
)
|
||||
val NEST_ROCKY = this.create(
|
||||
"nest_rocky",
|
||||
WildNestBlock(WildNestType.ROCKY, BlockBehaviour.Properties.of().noOcclusion().pushReaction(PushReaction.DESTROY))
|
||||
)
|
||||
val NEST_SANDY = this.create(
|
||||
"nest_sandy",
|
||||
WildNestBlock(WildNestType.SANDY, BlockBehaviour.Properties.of().noOcclusion().pushReaction(PushReaction.DESTROY))
|
||||
)
|
||||
val NEST_SHROOMY = this.create(
|
||||
"nest_shroomy",
|
||||
WildNestBlock(WildNestType.SHROOMY, BlockBehaviour.Properties.of().noOcclusion().pushReaction(PushReaction.DESTROY))
|
||||
)
|
||||
val NEST_TWIGGY = this.create(
|
||||
"nest_twiggy",
|
||||
WildNestBlock(WildNestType.TWIGGY, BlockBehaviour.Properties.of().noOcclusion().pushReaction(PushReaction.DESTROY))
|
||||
)
|
||||
val NEST_WATERY = this.create(
|
||||
"nest_watery",
|
||||
WildNestBlock(WildNestType.WATERY, BlockBehaviour.Properties.of().noOcclusion().pushReaction(PushReaction.DESTROY))
|
||||
)
|
||||
|
||||
val EGG = this.create("egg", EggBlock(BlockBehaviour.Properties
|
||||
.ofFullCopy(Blocks.DRAGON_EGG)
|
||||
.pushReaction(PushReaction.BLOCK)
|
||||
.sound(SoundType.BONE_BLOCK)
|
||||
))
|
||||
|
||||
@JvmField
|
||||
val HABITAT_BLOCK = this.create(
|
||||
name = "habitat_block",
|
||||
entry = HabitatBlock(
|
||||
properties = BlockBehaviour.Properties.of()
|
||||
.mapColor(MapColor.STONE)
|
||||
.strength(1.5F, Blocks.OBSIDIAN.explosionResistance)
|
||||
.pushReaction(PushReaction.BLOCK)
|
||||
)
|
||||
)
|
||||
|
||||
@JvmField
|
||||
val INCUBATOR = this.create("incubator", IncubatorBlock(BlockBehaviour.Properties.of()
|
||||
.mapColor(MapColor.COLOR_LIGHT_GRAY)
|
||||
.sound(SoundType.METAL)
|
||||
.strength(Blocks.COAL_BLOCK.defaultDestroyTime(), Blocks.DIAMOND_ORE.explosionResistance)
|
||||
.noOcclusion()
|
||||
.pushReaction(PushReaction.BLOCK)
|
||||
.lightLevel { state ->
|
||||
if (state.getValue(IncubatorBlock.HAS_EGG)) 5 else 0 // Emit 5 light when HAS_EGG is true
|
||||
}
|
||||
))
|
||||
|
||||
init {
|
||||
/**
|
||||
* Makes all blocks in array flammable by adding them to FireBlock's flammableRegistry.
|
||||
@@ -853,6 +920,12 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
|
||||
return block
|
||||
}
|
||||
|
||||
private fun nestBlock(variant: NestBlock.NestVariant): NestBlock {
|
||||
val identifier = cobblemonResource(variant.serializedName)
|
||||
val block = NestBlock(variant, BlockBehaviour.Properties.of().noOcclusion().pushReaction(PushReaction.DESTROY))
|
||||
return this.create(identifier.path, block)
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls helper method from Vanilla
|
||||
*/
|
||||
@@ -890,4 +963,4 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
|
||||
val block = BlocksInvoker.createLeavesBlock(SoundType.GRASS)
|
||||
return this.create(name, block)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,6 +61,8 @@ object CobblemonCommands {
|
||||
ChangeBoxWallpaperCommand.register(dispatcher)
|
||||
RunMolangCommand.register(dispatcher)
|
||||
RunMolangScriptCommand.register(dispatcher)
|
||||
GivePokemonEggCommand.register(dispatcher)
|
||||
GiveAllPokemonEggCommand.register(dispatcher)
|
||||
SpectateBattleCommand.register(dispatcher)
|
||||
CobblemonConfigCommand.register(dispatcher)
|
||||
UnlockPCBoxWallpaperCommand.register(dispatcher)
|
||||
@@ -76,5 +78,6 @@ object CobblemonCommands {
|
||||
// with a certain system environment variable set
|
||||
CobblemonInfoCommand.register(dispatcher)
|
||||
CalculateSeatPositionCommand.register(dispatcher)
|
||||
DebugCobblemonAICommand.register(dispatcher)
|
||||
}
|
||||
}
|
||||
@@ -68,6 +68,12 @@ object CobblemonItemComponents : PlatformRegistry<Registry<DataComponentType<*>>
|
||||
.persistent(FoodColourComponent.CODEC)
|
||||
.networkSynchronized(FoodColourComponent.PACKET_CODEC)
|
||||
.build())
|
||||
val EGG_DATA: DataComponentType<EggComponent> = create("egg", DataComponentType.builder<EggComponent>()
|
||||
.persistent(EggComponent.CODEC) // For saving/loading
|
||||
.networkSynchronized(EggComponent.PACKET_CODEC) // For network synchronization
|
||||
.build())
|
||||
|
||||
|
||||
|
||||
@JvmField
|
||||
val INGREDIENT: DataComponentType<IngredientComponent> = create("ingredient", DataComponentType.builder<IngredientComponent>()
|
||||
@@ -104,6 +110,7 @@ object CobblemonItemComponents : PlatformRegistry<Registry<DataComponentType<*>>
|
||||
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:ingredient"), INGREDIENT)
|
||||
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:food"), FOOD)
|
||||
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:mob_effects"), MOB_EFFECTS)
|
||||
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:egg_data"), EGG_DATA)
|
||||
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:held_item_effect"), HELD_ITEM_EFFECT)
|
||||
}
|
||||
|
||||
|
||||
@@ -51,13 +51,13 @@ import net.minecraft.network.chat.Component
|
||||
import net.minecraft.resources.ResourceKey
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.world.effect.MobEffectInstance
|
||||
import net.minecraft.world.item.Item.Properties
|
||||
import net.minecraft.world.effect.MobEffects
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.food.FoodProperties
|
||||
import net.minecraft.world.item.BlockItem
|
||||
import net.minecraft.world.item.HangingSignItem
|
||||
import net.minecraft.world.item.Item
|
||||
import net.minecraft.world.item.Item.Properties
|
||||
import net.minecraft.world.item.ItemNameBlockItem
|
||||
import net.minecraft.world.item.ItemStack
|
||||
import net.minecraft.world.item.Items
|
||||
@@ -599,6 +599,23 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
|
||||
@JvmField val BERRY_JUICE = this.create("berry_juice", BerryJuiceItem())
|
||||
|
||||
val NEST_GRASSY = blockItem("nest_grassy", CobblemonBlocks.NEST_GRASSY)
|
||||
val NEST_FIERY = blockItem("nest_fiery", CobblemonBlocks.NEST_FIERY)
|
||||
val NEST_ICY = blockItem("nest_icy", CobblemonBlocks.NEST_ICY)
|
||||
val NEST_PLAYER = blockItem("nest_player", CobblemonBlocks.NEST_PLAYER)
|
||||
val NEST_ROCKY = blockItem("nest_rocky", CobblemonBlocks.NEST_ROCKY)
|
||||
val NEST_SANDY = blockItem("nest_sandy", CobblemonBlocks.NEST_SANDY)
|
||||
val NEST_SHROOMY = blockItem("nest_shroomy", CobblemonBlocks.NEST_SHROOMY)
|
||||
val NEST_TWIGGY = blockItem("nest_twiggy", CobblemonBlocks.NEST_TWIGGY)
|
||||
val NEST_WATERY = blockItem("nest_watery", CobblemonBlocks.NEST_WATERY)
|
||||
|
||||
|
||||
@JvmField
|
||||
val INCUBATOR = blockItem("incubator", CobblemonBlocks.INCUBATOR)
|
||||
|
||||
@JvmField
|
||||
val HABITAT_BLOCK = blockItem("habitat_block", CobblemonBlocks.HABITAT_BLOCK)
|
||||
|
||||
@JvmField
|
||||
val GALARICA_NUTS = compostableItemNameBlockItem("galarica_nuts", CobblemonBlocks.GALARICA_NUT_BUSH)
|
||||
|
||||
@@ -952,7 +969,7 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
@JvmField
|
||||
val COVERT_CLOAK = heldItem("covert_cloak")
|
||||
@JvmField
|
||||
val DESTINY_KNOT = heldItem("destiny_knot")
|
||||
val DESTINY_KNOT = heldItem("destiny_knot", null, if (Cobblemon.config.canDestinyKnotBreak) Properties().durability(Cobblemon.config.destinyKnotDurability) else Properties())
|
||||
@JvmField
|
||||
val DRAGON_FANG = heldItem("dragon_fang")
|
||||
@JvmField
|
||||
@@ -1472,6 +1489,10 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
val FAIRY_GEM = noSettingsItem("fairy_gem")
|
||||
//@JvmField
|
||||
//val BINDING_SOIL = blockItem("binding_soil", CobblemonBlocks.BINDING_SOIL)
|
||||
@JvmField
|
||||
val POKEMON_EGG = eggItem("egg", CobblemonBlocks.EGG, Item.Properties().stacksTo(1))
|
||||
|
||||
private fun eggItem(name: String, block: Block, settings: Item.Properties = Item.Properties()): BlockItem = this.create(name, BlockItem(block, settings))
|
||||
|
||||
private fun blockItem(name: String, block: Block, rarity: Rarity = Rarity.COMMON): BlockItem = this.create(name, BlockItem(block, Item.Properties().rarity(rarity)))
|
||||
|
||||
@@ -1561,9 +1582,9 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
return create(name, RegionalFoodItem(properties))
|
||||
}
|
||||
|
||||
private fun heldItem(name: String, remappedName: String? = null): CobblemonItem = create(
|
||||
private fun heldItem(name: String, remappedName: String? = null, settings:Item.Properties = Item.Properties()): CobblemonItem = create(
|
||||
name,
|
||||
CobblemonItem(Item.Properties()).also {
|
||||
CobblemonItem(settings).also {
|
||||
if (remappedName != null) {
|
||||
CobblemonHeldItemManager.registerRemap(it, remappedName)
|
||||
}
|
||||
|
||||
@@ -12,15 +12,17 @@ import com.cobblemon.mod.common.api.ai.CobblemonAttackTargetData
|
||||
import com.cobblemon.mod.common.api.ai.CobblemonWanderControl
|
||||
import com.cobblemon.mod.common.api.dialogue.ActiveDialogue
|
||||
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.mojang.serialization.Codec
|
||||
import com.mojang.serialization.codecs.ListCodec
|
||||
import com.mojang.serialization.codecs.PrimitiveCodec
|
||||
import java.util.Optional
|
||||
import java.util.UUID
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.core.GlobalPos
|
||||
import net.minecraft.core.UUIDUtil
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.ai.memory.MemoryModuleType
|
||||
import java.util.*
|
||||
import net.minecraft.world.entity.player.Player
|
||||
|
||||
object CobblemonMemories {
|
||||
val memories = mutableMapOf<String, MemoryModuleType<*>>()
|
||||
@@ -39,6 +41,16 @@ object CobblemonMemories {
|
||||
@JvmField
|
||||
val ACTIVE_ACTION_EFFECT = register<ActionEffectContext>("active_action_effect")
|
||||
@JvmField
|
||||
val POTENTIAL_MATE = register<PokemonEntity>("potential_mate")
|
||||
@JvmField
|
||||
val MATE_SCAN_COOLDOWN = register("mate_scan_cooldown", PrimitiveCodec.BOOL)
|
||||
@JvmField
|
||||
val NEST_LOCATION = register<BlockPos>("nest_location")
|
||||
@JvmField
|
||||
val PATERNAL_NEST_LOCATION = register<BlockPos>("paternal_nest")
|
||||
@JvmField
|
||||
val IRRITATED_AT = register<UUID>("irritated_at")
|
||||
@JvmField
|
||||
val POKEMON_FLYING = register("pokemon_flying", PrimitiveCodec.BOOL)
|
||||
@JvmField
|
||||
val POKEMON_DROWSY = register("pokemon_drowsy", PrimitiveCodec.BOOL)
|
||||
@@ -55,6 +67,26 @@ object CobblemonMemories {
|
||||
@JvmField
|
||||
val NEARBY_GROWABLE_CROPS = register<BlockPos>("nearby_growable_crops")
|
||||
@JvmField
|
||||
val EGG_THIEF = register<Player>("egg_thief") // Stores the player stealing the egg
|
||||
@JvmField
|
||||
val EGG_THIEF_LAST_SEEN_TICK = register("egg_thief_last_seen_tick", PrimitiveCodec.LONG)
|
||||
@JvmField
|
||||
val EGG_LOCATION = register<BlockPos>("egg_location") // Stores the last known location of the egg
|
||||
@JvmField
|
||||
val EGG_IN_NEST = register("egg_in_nest", PrimitiveCodec.BOOL)
|
||||
@JvmField
|
||||
val READY_TO_GRAB_EGG = register("ready_to_grab_egg", PrimitiveCodec.BOOL)
|
||||
@JvmField
|
||||
val HOLDING_EGG = register("holding_egg", PrimitiveCodec.BOOL)
|
||||
@JvmField
|
||||
val EGG_SEARCH_COUNTDOWN = register ("egg_search_countdown", PrimitiveCodec.BOOL)
|
||||
@JvmField
|
||||
val IDEAL_NEST_LOCATION = register<BlockPos>("ideal_nest_location")
|
||||
@JvmField
|
||||
val READY_TO_BUILD_NEST = register("ready_to_build_nest", PrimitiveCodec.BOOL)
|
||||
@JvmField
|
||||
val NEST_DESPAWN_IMMUNE_UNTIL_TICK = register("nest_despawn_immune_until_tick", PrimitiveCodec.LONG)
|
||||
@JvmField
|
||||
val NEARBY_SWEET_BERRY_BUSH = register<BlockPos>("nearest_sweet_berry_bush")
|
||||
@JvmField
|
||||
val DISABLE_WALK_TO_BERRY_BUSH = register<Boolean>("disable_walk_to_berry_bush")
|
||||
@@ -63,8 +95,6 @@ object CobblemonMemories {
|
||||
@JvmField
|
||||
val IS_CONSUMING_ITEM = register<Boolean>("is_consuming_item")
|
||||
@JvmField
|
||||
val RECENTLY_ATE_GRASS = register<Boolean>("recently_ate_grass")
|
||||
@JvmField
|
||||
val HIVE_LOCATION = register<BlockPos>("hive_location", BlockPos.CODEC)
|
||||
@JvmField
|
||||
val HIVE_BLACKLIST = register<List<BlockPos>>("hive_blacklist")
|
||||
@@ -106,4 +136,4 @@ object CobblemonMemories {
|
||||
memories[id] = memoryModule
|
||||
return memoryModule
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import com.cobblemon.mod.common.client.net.data.CobblemonMechanicsSyncHandler
|
||||
import com.cobblemon.mod.common.client.net.data.DataRegistrySyncPacketHandler
|
||||
import com.cobblemon.mod.common.client.net.data.RideSettingsSyncHandler
|
||||
import com.cobblemon.mod.common.client.net.debug.OpenRidingStatsDebugGUIHandler
|
||||
import com.cobblemon.mod.common.client.net.debug.UpdateTrackedBrainDataHandler
|
||||
import com.cobblemon.mod.common.client.net.dialogue.DialogueClosedHandler
|
||||
import com.cobblemon.mod.common.client.net.dialogue.DialogueOpenedHandler
|
||||
import com.cobblemon.mod.common.client.net.effect.PokeSnackBlockParticlesHandler
|
||||
@@ -34,6 +35,7 @@ import com.cobblemon.mod.common.client.net.gui.ExpGainedDataPacketHandler
|
||||
import com.cobblemon.mod.common.client.net.gui.InteractPokemonUIPacketHandler
|
||||
import com.cobblemon.mod.common.client.net.gui.PokedexUIPacketHandler
|
||||
import com.cobblemon.mod.common.client.net.gui.SummaryUIPacketHandler
|
||||
import com.cobblemon.mod.common.client.net.habitat.OpenHabitatBlockEditorHandler
|
||||
import com.cobblemon.mod.common.client.net.npc.CloseNPCEditorHandler
|
||||
import com.cobblemon.mod.common.client.net.npc.OpenNPCEditorHandler
|
||||
import com.cobblemon.mod.common.client.net.orientation.ClientboundUpdateOrientationHandler
|
||||
@@ -74,6 +76,7 @@ import com.cobblemon.mod.common.net.messages.client.PlayerInteractOptionsPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.SetClientPlayerDataPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.animation.PlayPosableAnimationPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.*
|
||||
import com.cobblemon.mod.common.net.messages.client.breeding.BreedingRuleRegistrySyncPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.callback.OpenMoveCallbackPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.callback.OpenPartyCallbackPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.callback.OpenPartyMoveCallbackPacket
|
||||
@@ -81,6 +84,7 @@ import com.cobblemon.mod.common.net.messages.client.cooking.SeasoningRegistrySyn
|
||||
import com.cobblemon.mod.common.net.messages.client.cooking.ToggleCookingPotLidPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.data.*
|
||||
import com.cobblemon.mod.common.net.messages.client.debug.RequestOpenRidingStatsDebugGUIPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.debug.UpdateTrackedBrainDataPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.dialogue.DialogueClosedPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.dialogue.DialogueOpenedPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.effect.PokeSnackBlockParticlesPacket
|
||||
@@ -88,6 +92,7 @@ import com.cobblemon.mod.common.net.messages.client.effect.RunPosableMoLangPacke
|
||||
import com.cobblemon.mod.common.net.messages.client.effect.SaccharineLogBlockParticlesPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.effect.SpawnSnowstormEntityParticlePacket
|
||||
import com.cobblemon.mod.common.net.messages.client.effect.SpawnSnowstormParticlePacket
|
||||
import com.cobblemon.mod.common.net.messages.client.habitat.OpenHabitatBlockEditorPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.npc.CloseNPCEditorPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.npc.OpenNPCEditorPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.orientation.ClientboundUpdateDriverInputPacket
|
||||
@@ -140,7 +145,13 @@ import com.cobblemon.mod.common.net.messages.client.ui.ExpGainedDataPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.ui.InteractPokemonUIPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.ui.PokedexUIPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.ui.SummaryUIPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.*
|
||||
import com.cobblemon.mod.common.net.messages.server.BattleChallengePacket
|
||||
import com.cobblemon.mod.common.net.messages.server.BattleChallengeResponsePacket
|
||||
import com.cobblemon.mod.common.net.messages.server.BenchMovePacket
|
||||
import com.cobblemon.mod.common.net.messages.server.RequestMoveSwapPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.RequestPlayerInteractionsPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.SelectStarterPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.SendOutPokemonPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.battle.BattleSelectActionsPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.battle.BattleTeamLeavePacket
|
||||
import com.cobblemon.mod.common.net.messages.server.battle.BattleTeamRequestPacket
|
||||
@@ -150,6 +161,7 @@ import com.cobblemon.mod.common.net.messages.server.battle.SpectateBattlePacket
|
||||
import com.cobblemon.mod.common.net.messages.server.behaviour.DamageOnCollisionPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.behaviour.SetEntityBehaviourPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.block.AdjustBlockEntityViewerCountPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.breeding.TriggerEggHatchPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.callback.move.MoveSelectCancelledPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.callback.move.MoveSelectedPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.callback.party.PartyPokemonSelectedPacket
|
||||
@@ -162,6 +174,7 @@ import com.cobblemon.mod.common.net.messages.server.debug.ServerboundUpdateRidin
|
||||
import com.cobblemon.mod.common.net.messages.server.debug.ServerboundUpdateRidingStatsPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.dialogue.EscapeDialoguePacket
|
||||
import com.cobblemon.mod.common.net.messages.server.dialogue.InputToDialoguePacket
|
||||
import com.cobblemon.mod.common.net.messages.server.habitat.SaveHabitatBlockSettingsPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.npc.SaveNPCPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.orientation.ServerboundUpdateOrientationPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.pasture.PasturePokemonPacket
|
||||
@@ -205,6 +218,7 @@ import com.cobblemon.mod.common.net.serverhandling.battle.TeamRequestResponseHan
|
||||
import com.cobblemon.mod.common.net.serverhandling.behaviour.DamageOnCollisionPacketHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.behaviour.SetEntityBehaviourHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.block.AdjustBlockEntityViewerCountHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.breeding.TriggerEggHatchHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.callback.move.MoveSelectCancelledHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.callback.move.MoveSelectedHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.callback.party.PartyPokemonSelectedHandler
|
||||
@@ -218,9 +232,9 @@ import com.cobblemon.mod.common.net.serverhandling.debug.ServerboundUpdateRiding
|
||||
import com.cobblemon.mod.common.net.serverhandling.dialogue.EscapeDialogueHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.dialogue.InputToDialogueHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.evolution.AcceptEvolutionHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.habitat.SaveHabitatBlockSettingsHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.npc.SaveNPCHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.orientation.ServerboundUpdateOrientationHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.storage.party.PartyTutorialPacketHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.pasture.PasturePokemonHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.pasture.SetPastureConflictHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.pasture.UnpastureAllPokemonHandler
|
||||
@@ -243,6 +257,7 @@ import com.cobblemon.mod.common.net.serverhandling.storage.RequestMoveSwapHandle
|
||||
import com.cobblemon.mod.common.net.serverhandling.storage.SendOutPokemonHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.storage.SwapPCPartyPokemonHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.storage.party.MovePartyPokemonHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.storage.party.PartyTutorialPacketHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.storage.party.ReleasePCPokemonHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.storage.party.SwapPartyPokemonHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.storage.pc.*
|
||||
@@ -414,6 +429,7 @@ object CobblemonNetwork {
|
||||
list.add(PacketRegisterInfo(DexEntrySyncPacket.ID, DexEntrySyncPacket::decode, DataRegistrySyncPacketHandler()))
|
||||
list.add(PacketRegisterInfo(SpawnBaitRegistrySyncPacket.ID, SpawnBaitRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
|
||||
list.add(PacketRegisterInfo(SeasoningRegistrySyncPacket.ID, SeasoningRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
|
||||
list.add(PacketRegisterInfo(BreedingRuleRegistrySyncPacket.ID, BreedingRuleRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
|
||||
list.add(PacketRegisterInfo(CallbackRegistrySyncPacket.ID, CallbackRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
|
||||
list.add(PacketRegisterInfo(CosmeticItemAssignmentSyncPacket.ID, CosmeticItemAssignmentSyncPacket::decode, DataRegistrySyncPacketHandler()))
|
||||
list.add(PacketRegisterInfo(BehaviourSyncPacket.ID, BehaviourSyncPacket::decode, DataRegistrySyncPacketHandler()))
|
||||
@@ -488,8 +504,13 @@ object CobblemonNetwork {
|
||||
list.add(PacketRegisterInfo(ClientboundUpdateRidingStatePacket.ID, ClientboundUpdateRidingStatePacket::decode, ClientboundUpdateRidingStateHandler))
|
||||
list.add(PacketRegisterInfo(ClientboundUpdateDriverInputPacket.ID, ClientboundUpdateDriverInputPacket::decode, ClientboundUpdateDriverInputHandler))
|
||||
|
||||
// Habitat blocks
|
||||
list.add(PacketRegisterInfo(OpenHabitatBlockEditorPacket.ID, OpenHabitatBlockEditorPacket::decode, OpenHabitatBlockEditorHandler))
|
||||
|
||||
// Debug
|
||||
list.add(PacketRegisterInfo(OpenRidingStatsDebugGUIPacket.ID, OpenRidingStatsDebugGUIPacket::decode, OpenRidingStatsDebugGUIHandler))
|
||||
list.add(PacketRegisterInfo(UpdateTrackedBrainDataPacket.ID, UpdateTrackedBrainDataPacket::decode, UpdateTrackedBrainDataHandler))
|
||||
|
||||
|
||||
return list
|
||||
}
|
||||
@@ -606,13 +627,20 @@ object CobblemonNetwork {
|
||||
// Cooking
|
||||
list.add(PacketRegisterInfo(ToggleCookingPotLidPacket.ID, ToggleCookingPotLidPacket::decode, ToggleCookingPotLidHandler))
|
||||
|
||||
// Breeding Packets
|
||||
list.add(PacketRegisterInfo(TriggerEggHatchPacket.ID, TriggerEggHatchPacket::decode, TriggerEggHatchHandler))
|
||||
|
||||
// Behaviour Packets
|
||||
list.add(PacketRegisterInfo(SetEntityBehaviourPacket.ID, SetEntityBehaviourPacket::decode, SetEntityBehaviourHandler))
|
||||
list.add(PacketRegisterInfo(DamageOnCollisionPacket.ID, DamageOnCollisionPacket::decode, DamageOnCollisionPacketHandler))
|
||||
|
||||
// Debug
|
||||
// Habitat blocks
|
||||
list.add(PacketRegisterInfo(SaveHabitatBlockSettingsPacket.ID, SaveHabitatBlockSettingsPacket::decode, SaveHabitatBlockSettingsHandler))
|
||||
|
||||
// Debug / cheats
|
||||
list.add(PacketRegisterInfo(RequestOpenRidingStatsDebugGUIPacket.ID, RequestOpenRidingStatsDebugGUIPacket::decode, RequestOpenRidingStatsDebugGUIHandler))
|
||||
|
||||
|
||||
return list
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,10 @@ object CobblemonPoiTypes: PlatformRegistry<Registry<PoiType>, ResourceKey<Regist
|
||||
// TODO after 1.7
|
||||
// @JvmField
|
||||
// val INCENSE_SWEET_KEY = createKey("incense_sweet", CobblemonBlocks.INCENSE_SWEET, 0, 1)
|
||||
@JvmField
|
||||
val HABITAT_BLOCK_KEY: ResourceKey<PoiType> = createKey("habitat_block", CobblemonBlocks.HABITAT_BLOCK, 0, 1)
|
||||
@JvmField
|
||||
val HABITAT_BLOCK = create(HABITAT_BLOCK_KEY.location().path, PoiType(getBlockStates(CobblemonBlocks.HABITAT_BLOCK), 0, 1))
|
||||
|
||||
private fun createKey(string: String, block: Block, maxTickets: Int, validRange: Int ): ResourceKey<PoiType> =
|
||||
ResourceKey.create(Registries.POINT_OF_INTEREST_TYPE, cobblemonResource(string)).also { resourceKey ->
|
||||
|
||||
@@ -24,6 +24,10 @@ object CobblemonSensors {
|
||||
val NPC_BATTLING = register("npc_battling", ::NPCBattlingSensor)
|
||||
@JvmField
|
||||
val BATTLING_POKEMON = register("battling_pokemon", ::BattlingPokemonSensor)
|
||||
val MATE_SENSOR = register("mate_sensor", ::MateSensor)
|
||||
val NEST_SENSOR = register("nest_sensor", ::NestSensor)
|
||||
val EGG_SENSOR = register("egg_sensor", :: EggSensor)
|
||||
val IDEAL_NEST_LOCATION_SENSOR = register("ideal_nest_location_sensor", ::IdealNestPlacementSensor)
|
||||
// val NPC_BATTLING = register("npc_battling", ::NPCBattlingSensor)
|
||||
@JvmField
|
||||
val POKEMON_DROWSY = register("pokemon_drowsy", ::DrowsySensor)
|
||||
@@ -61,4 +65,4 @@ object CobblemonSensors {
|
||||
sensors[id] = sensor
|
||||
return sensor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,6 +94,9 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, ResourceKey<Regi
|
||||
@JvmField
|
||||
val POKEDEX_SCAN_ZOOM_INCREMENT = this.create("item.pokedex.scan_zoom_increment")
|
||||
|
||||
@JvmField
|
||||
val BREEDING_EGG_HATCH = this.create("breeding.egg_hatch")
|
||||
|
||||
@JvmField
|
||||
val ITEM_USE = this.create("item.use")
|
||||
@JvmField
|
||||
|
||||
+44
-3
@@ -12,13 +12,16 @@ import com.bedrockk.molang.runtime.MoLangRuntime
|
||||
import com.bedrockk.molang.runtime.struct.QueryStruct
|
||||
import com.bedrockk.molang.runtime.value.DoubleValue
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.Objective
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.sense.Sense
|
||||
import com.cobblemon.mod.common.api.ai.config.BehaviourConfig
|
||||
import com.cobblemon.mod.common.api.molang.ExpressionLike
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.addStandardFunctions
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.setup
|
||||
import com.cobblemon.mod.common.api.molang.ObjectValue
|
||||
import com.cobblemon.mod.common.api.scripting.CobblemonScripts
|
||||
import com.cobblemon.mod.common.entity.MoLangScriptingEntity
|
||||
import com.cobblemon.mod.common.util.activityRegistry
|
||||
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
|
||||
@@ -46,8 +49,15 @@ class BehaviourConfigurationContext {
|
||||
val memories = mutableSetOf<MemoryModuleType<*>>()
|
||||
val sensors = mutableSetOf<SensorType<*>>()
|
||||
|
||||
val objectives = mutableMapOf<Int, MutableList<Objective>>()
|
||||
val senses = mutableSetOf<Sense>()
|
||||
|
||||
val onAdd = mutableListOf<ExpressionLike>()
|
||||
|
||||
init {
|
||||
memories.add(CobblemonMemories.NEST_DESPAWN_IMMUNE_UNTIL_TICK)
|
||||
}
|
||||
|
||||
fun getOrCreateActivity(activity: Activity): ActivityConfigurationContext {
|
||||
return activities.firstOrNull { it.activity == activity } ?: ActivityConfigurationContext(activity).also(activities::add)
|
||||
}
|
||||
@@ -68,6 +78,22 @@ class BehaviourConfigurationContext {
|
||||
this.sensors.addAll(sensors)
|
||||
}
|
||||
|
||||
fun addSenses(vararg senses: Sense) {
|
||||
addSenses(senses.toList())
|
||||
}
|
||||
|
||||
fun addSenses(senses: Collection<Sense>) {
|
||||
outer@for (sense in senses) {
|
||||
for (existingSense in this.senses) {
|
||||
if (existingSense.merge(sense)) {
|
||||
continue@outer
|
||||
}
|
||||
}
|
||||
this.senses.add(sense)
|
||||
this.addMemories(sense.memories)
|
||||
}
|
||||
}
|
||||
|
||||
fun addOnAddScript(script: ExpressionLike) {
|
||||
onAdd.add(script)
|
||||
}
|
||||
@@ -88,8 +114,23 @@ class BehaviourConfigurationContext {
|
||||
var brain = entity.brain
|
||||
// Apply the brain config
|
||||
if (entity is MoLangScriptingEntity) {
|
||||
brain = entity.assignNewBrainWithMemoriesAndSensors(dynamic, memories, sensors)
|
||||
val useBiggerBrain = objectives.isNotEmpty() || senses.isNotEmpty()
|
||||
brain = entity.assignNewBrainWithMemoriesAndSensors(dynamic, memories, if (useBiggerBrain) emptySet() else sensors)
|
||||
onAdd.forEach { runtime.resolve(it) }
|
||||
if (useBiggerBrain) {
|
||||
val wereDebugging = entity.biggerBrain.debuggingPlayers ?: emptySet()
|
||||
val biggerBrain = BiggerBrain(entity)
|
||||
entity.biggerBrain = biggerBrain
|
||||
addSenses(biggerBrain.senses)
|
||||
val orderedObjectives = objectives.entries.sortedBy { it.key }.flatMap { it.value }
|
||||
orderedObjectives.forEach(biggerBrain::registerObjective)
|
||||
biggerBrain.senses.addAll(senses)
|
||||
biggerBrain.debuggingPlayers.addAll(wereDebugging)
|
||||
return
|
||||
} else {
|
||||
val biggerBrain = BiggerBrain(entity)
|
||||
entity.biggerBrain = biggerBrain
|
||||
}
|
||||
}
|
||||
activities.forEach { it.apply(entity) }
|
||||
brain.setCoreActivities(coreActivities)
|
||||
@@ -136,4 +177,4 @@ class BehaviourConfigurationContext {
|
||||
}
|
||||
return struct
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,44 +9,40 @@
|
||||
package com.cobblemon.mod.common.api.ai
|
||||
|
||||
import com.bedrockk.molang.runtime.struct.QueryStruct
|
||||
import com.bedrockk.molang.runtime.value.DoubleValue
|
||||
import com.cobblemon.mod.common.util.getBooleanOrNull
|
||||
import com.cobblemon.mod.common.util.getDoubleOrNull
|
||||
import kotlin.math.sqrt
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.world.entity.PathfinderMob
|
||||
|
||||
/**
|
||||
* Used to maintain some settings around how the entity should wander this tick. The idea is you
|
||||
* reset this with some script task at priority 0 then some number of steps before it gets to the wander
|
||||
* tasks you can have other conditional tasks jump in to configure parts of this.
|
||||
* Used to maintain some settings around how the entity should wander this tick. Anything left null
|
||||
* will fall back to defaults specified by the logic of the WanderAround objective.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since July 18, 2025
|
||||
*/
|
||||
class CobblemonWanderControl(
|
||||
var center: WanderCenter? = null,
|
||||
var maxAttempts: Int = 4, // How many times to try to find a suitable point before giving up on that tick.
|
||||
var allowLand: Boolean = true,
|
||||
var allowWater: Boolean = false,
|
||||
var allowAir: Boolean = false,
|
||||
var pathCooldownTicks: Int = 20 * 5, // Used to mitigate the cost of pathfinding.
|
||||
var wanderSpeed: Float = 0.35F,
|
||||
var maxAttempts: Int? = null, // How many times to try to find a suitable point before giving up on that tick.
|
||||
var allowLand: Boolean? = null,
|
||||
var allowWater: Boolean? = null,
|
||||
var allowAir: Boolean? = null,
|
||||
var wanderSpeed: Float? = null,
|
||||
var wanderChanceMultiplier: Float? = null
|
||||
) {
|
||||
class WanderCenter(val x: Number, val y: Number, val z: Number, val minRange: Float, val maxRange: Float)
|
||||
|
||||
@Transient
|
||||
val struct: QueryStruct = QueryStruct(hashMapOf())
|
||||
.addFunction("center_x") { center?.x ?: 0.0 }
|
||||
.addFunction("center_y") { center?.y ?: 0.0 }
|
||||
.addFunction("center_z") { center?.z ?: 0.0 }
|
||||
.addFunction("max_attempts") { maxAttempts.toDouble() }
|
||||
.addFunction("min_range") { center?.minRange ?: 0.0 }
|
||||
.addFunction("max_range") { center?.maxRange ?: 0.0 }
|
||||
.addFunction("allow_land") { if (allowLand) 1.0 else 0.0 }
|
||||
.addFunction("allow_water") { if (allowWater) 1.0 else 0.0 }
|
||||
.addFunction("allow_air") { if (allowAir) 1.0 else 0.0 }
|
||||
.addFunction("path_cooldown_ticks") { pathCooldownTicks.toDouble() }
|
||||
.addFunction("wander_speed") { wanderSpeed }
|
||||
.addFunction("center_x") { DoubleValue(center?.x ?: 0.0) }
|
||||
.addFunction("center_y") { DoubleValue(center?.y ?: 0.0) }
|
||||
.addFunction("center_z") { DoubleValue(center?.z ?: 0.0) }
|
||||
.addFunction("max_attempts") { DoubleValue(maxAttempts?.toDouble() ?: 0.0) }
|
||||
.addFunction("min_range") { DoubleValue(center?.minRange ?: 0.0) }
|
||||
.addFunction("max_range") { DoubleValue(center?.maxRange ?: 0.0) }
|
||||
.addFunction("allow_land") { DoubleValue(if (allowLand == true) 1.0 else 0.0) }
|
||||
.addFunction("allow_water") { DoubleValue(if (allowWater == true) 1.0 else 0.0) }
|
||||
.addFunction("allow_air") { DoubleValue(if (allowAir == true) 1.0 else 0.0) }
|
||||
.addFunction("wander_speed") { DoubleValue(wanderSpeed ?: 0.0) }
|
||||
.addFunction("set_center") { params ->
|
||||
if (params.params.size < 3) {
|
||||
throw IllegalArgumentException("set_center requires at least 3 parameters: x, y, z")
|
||||
@@ -68,26 +64,6 @@ class CobblemonWanderControl(
|
||||
.addFunction("set_allow_land") { params -> allowLand = params.getBooleanOrNull(0) ?: true }
|
||||
.addFunction("set_allow_water") { params -> allowWater = params.getBooleanOrNull(0) ?: false }
|
||||
.addFunction("set_allow_air") { params -> allowAir = params.getBooleanOrNull(0) ?: false }
|
||||
.addFunction("set_path_cooldown_ticks") { params -> pathCooldownTicks = params.getDoubleOrNull(0)?.toInt() ?: (20 * 5) }
|
||||
.addFunction("set_wander_speed") { params -> wanderSpeed = params.getDoubleOrNull(0)?.toFloat() ?: 0.35F }
|
||||
.addFunction("reset") { reset() }
|
||||
|
||||
fun isSuitable(position: BlockPos): Boolean {
|
||||
val center = center ?: return true
|
||||
val centerPos = BlockPos(center.x.toInt(), center.y.toInt(), center.z.toInt())
|
||||
val distance = sqrt(centerPos.distSqr(position))
|
||||
val suitable = distance >= center.minRange && distance <= center.maxRange
|
||||
return suitable
|
||||
}
|
||||
|
||||
fun reset() {
|
||||
center = null
|
||||
allowLand = false
|
||||
allowWater = false
|
||||
allowAir = false
|
||||
pathCooldownTicks = 20 * 5
|
||||
wanderSpeed = 0.35F
|
||||
maxAttempts = 4
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,9 +9,23 @@
|
||||
package com.cobblemon.mod.common.api.ai
|
||||
|
||||
import com.bedrockk.molang.Expression
|
||||
import com.bedrockk.molang.ast.NumberExpression
|
||||
import com.bedrockk.molang.ast.StringExpression
|
||||
import com.bedrockk.molang.runtime.MoLangRuntime
|
||||
import com.bedrockk.molang.runtime.value.DoubleValue
|
||||
import com.bedrockk.molang.runtime.value.StringValue
|
||||
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
|
||||
import com.cobblemon.mod.common.entity.MoLangScriptingEntity
|
||||
import com.cobblemon.mod.common.util.asExpression
|
||||
import com.cobblemon.mod.common.util.lang
|
||||
import com.cobblemon.mod.common.util.resolveBoolean
|
||||
import com.cobblemon.mod.common.util.resolveDouble
|
||||
import com.cobblemon.mod.common.util.resolveFloat
|
||||
import com.cobblemon.mod.common.util.resolveInt
|
||||
import com.cobblemon.mod.common.util.resolveString
|
||||
import com.mojang.datafixers.util.Either
|
||||
import kotlin.jvm.optionals.getOrNull
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
|
||||
/**
|
||||
* The issue in a lot of the behaviour datapacking is that there are times when a property should be exposed to
|
||||
@@ -31,6 +45,50 @@ import kotlin.jvm.optionals.getOrNull
|
||||
*/
|
||||
typealias ExpressionOrEntityVariable = Either<Expression, MoLangConfigVariable>
|
||||
|
||||
fun ExpressionOrEntityVariable.asExpression() = map({ it }, { "q.entity.config.${it.variableName}".asExpression() })
|
||||
fun ExpressionOrEntityVariable.resolveString(runtime: MoLangRuntime) = runtime.resolveString(asExpression())
|
||||
fun ExpressionOrEntityVariable.resolveBoolean(runtime: MoLangRuntime) = runtime.resolveBoolean(asExpression())
|
||||
fun ExpressionOrEntityVariable.resolveInt(runtime: MoLangRuntime) = runtime.resolveInt(asExpression())
|
||||
fun ExpressionOrEntityVariable.resolveDouble(runtime: MoLangRuntime) = runtime.resolveDouble(asExpression())
|
||||
fun ExpressionOrEntityVariable.resolveFloat(runtime: MoLangRuntime) = runtime.resolveFloat(asExpression())
|
||||
|
||||
private fun variable(category: String, name: String, type: MoLangConfigVariable.MoLangVariableType, default: String) = MoLangConfigVariable(
|
||||
variableName = name,
|
||||
category = lang("entity.variable.category.$category"),
|
||||
displayName = lang("entity.variable.$name.name"),
|
||||
description = lang("entity.variable.$name.desc"),
|
||||
type = type,
|
||||
defaultValue = default
|
||||
)
|
||||
|
||||
fun stringVariable(category: String, name: String, default: String) = variable(category = category, name = name, type = MoLangConfigVariable.MoLangVariableType.TEXT, default = default)
|
||||
fun numberVariable(category: String, name: String, default: Number) = variable(category = category, name = name, type = MoLangConfigVariable.MoLangVariableType.NUMBER, default = default.toString())
|
||||
fun booleanVariable(category: String, name: String, default: Boolean) = variable(category = category, name = name, type = MoLangConfigVariable.MoLangVariableType.BOOLEAN, default = default.toString())
|
||||
|
||||
fun getVariableExpression(name: String) = "q.entity.config.$name".asExpression()
|
||||
fun resolveBooleanVariable(name: String, runtime: MoLangRuntime) = runtime.resolveBoolean(getVariableExpression(name))
|
||||
|
||||
fun checkCondition(runtime: MoLangRuntime, expressionOrEntityVariable: ExpressionOrEntityVariable): Boolean {
|
||||
return expressionOrEntityVariable.resolveBoolean(runtime)
|
||||
}
|
||||
|
||||
fun ExpressionOrEntityVariable.asSimplifiedExpression(entity: LivingEntity): Expression {
|
||||
return map(
|
||||
{ it },
|
||||
{
|
||||
if (entity is MoLangScriptingEntity) {
|
||||
val variable = entity.config.map[it.variableName]
|
||||
if (variable is DoubleValue) {
|
||||
return@map NumberExpression(variable.value)
|
||||
} else if (variable is StringValue) {
|
||||
return@map StringExpression(variable)
|
||||
}
|
||||
}
|
||||
return@map "q.entity.config.${it.variableName}".asExpression()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* In the case of [ExpressionOrEntityVariable] the right side is the only one that requires visibility for a user,
|
||||
* this function is a shorthand for finding all of the values in the list that have something to show the user to
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain
|
||||
|
||||
import com.bedrockk.molang.runtime.MoLangRuntime
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.CobblemonNetwork.sendPacket
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.Distraction
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.EntityFixation
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.Objective
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.ObjectiveStatus
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.sense.Sense
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.setup
|
||||
import com.cobblemon.mod.common.api.text.text
|
||||
import com.cobblemon.mod.common.net.messages.client.debug.UpdateTrackedBrainDataPacket
|
||||
import com.cobblemon.mod.common.util.getMemorySafely
|
||||
import com.cobblemon.mod.common.util.getPlayer
|
||||
import com.cobblemon.mod.common.util.withQueryValue
|
||||
import java.util.UUID
|
||||
import kotlin.collections.mutableListOf
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.ai.memory.MemoryModuleType
|
||||
import net.minecraft.world.entity.ai.memory.MemoryStatus
|
||||
|
||||
/**
|
||||
* A better* version of Minecraft's [net.minecraft.world.entity.ai.Brain] system that makes development more
|
||||
* straightforward and allows easier debugging.
|
||||
*
|
||||
* We could name this thing better.
|
||||
*
|
||||
* There are three main principles inside this system:
|
||||
*
|
||||
* 1: Senses
|
||||
*
|
||||
* Senses are simple ticking objects that are instantiated per entity and are expected to just update memories based
|
||||
* on properties about the entity or its surroundings. This is an analogue of SensorType from Brains.
|
||||
*
|
||||
* 2: Objectives
|
||||
*
|
||||
* Objectives are also ticking objects that are instantiated per entity. They represent a goal for the entity and
|
||||
* run in a fixed order.
|
||||
*
|
||||
* 3: Fixations
|
||||
*
|
||||
* Fixations are a task that can be set on the brain that represents a more total commitment to a certain task. Only
|
||||
* a single fixation can be active at a time, and they can be 'distracted' by other objectives or potentially external
|
||||
* factors.
|
||||
*
|
||||
* These can also be debugged using the /debugcobblemonai command while looking at a Pokémon or NPC entity.
|
||||
*
|
||||
* *that remains to be seen
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since December 29th, 2025
|
||||
*/
|
||||
class BiggerBrain(val entity: LivingEntity) {
|
||||
val runtime = MoLangRuntime().setup().withQueryValue("entity", entity.asMostSpecificMoLangValue())
|
||||
|
||||
val senses = mutableListOf<Sense>()
|
||||
val objectives = mutableListOf<Objective>()
|
||||
val observedMemories = mutableMapOf<MemoryModuleType<*>, MutableList<Objective>>()
|
||||
|
||||
var fixation: EntityFixation? = null
|
||||
|
||||
var debuggingPlayers = mutableSetOf<UUID>()
|
||||
|
||||
fun registerObjective(objective: Objective) {
|
||||
objectives.add(objective)
|
||||
for (memory in objective.observedMemories) {
|
||||
val list = this.observedMemories.getOrPut(memory, ::ArrayList)
|
||||
list.add(objective)
|
||||
}
|
||||
}
|
||||
|
||||
fun isFixation(fixation: EntityFixation?): Boolean {
|
||||
return fixation != null && this.fixation == fixation
|
||||
}
|
||||
|
||||
fun sendDebugMessage(message: String) {
|
||||
debuggingPlayers.forEach {
|
||||
it.getPlayer()?.sendSystemMessage(message.text())
|
||||
}
|
||||
}
|
||||
|
||||
fun tick() {
|
||||
senses.forEach {
|
||||
it.ticksUntilNextExecution--
|
||||
if (it.ticksUntilNextExecution <= 0) {
|
||||
it.tick(entity, this)
|
||||
}
|
||||
}
|
||||
|
||||
if (fixation?.tick(entity, this) == false) {
|
||||
fixation = null
|
||||
}
|
||||
|
||||
val objectiveStatuses = mutableMapOf<Objective, ObjectiveStatus>()
|
||||
|
||||
val activeObjectiveTags = mutableSetOf<String>()
|
||||
for (objective in objectives) {
|
||||
val status = objective.perform(entity, this, activeObjectiveTags)
|
||||
objectiveStatuses[objective] = status
|
||||
}
|
||||
|
||||
if (debuggingPlayers.isNotEmpty()) {
|
||||
val state = BiggerBrainState(entity = entity, brain = this, objectiveStatuses = objectiveStatuses)
|
||||
val packet = UpdateTrackedBrainDataPacket(entity.id, state)
|
||||
debuggingPlayers.removeIf { uuid ->
|
||||
val player = uuid.getPlayer()
|
||||
if (player != null && Cobblemon.serverPlayerDebuggingPokemonBrainEntities[player.uuid] != entity.uuid) {
|
||||
return@removeIf true
|
||||
} else {
|
||||
player?.sendPacket(packet)
|
||||
}
|
||||
return@removeIf player == null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun fixate(fixation: EntityFixation): EntityFixation {
|
||||
this.fixation = fixation
|
||||
return fixation
|
||||
}
|
||||
|
||||
fun distract(distraction: Distraction): Boolean {
|
||||
if (fixation?.canDistract(distraction, entity) != false) {
|
||||
fixation?.onDistract(distraction, entity)
|
||||
this.fixation = null
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun canDistract(distraction: Distraction): Boolean {
|
||||
return fixation?.canDistract(distraction, entity) != false
|
||||
}
|
||||
|
||||
fun <T> setMemory(memory: MemoryModuleType<T>, value: T, expiry: Long? = null) {
|
||||
if (expiry != null) {
|
||||
entity.brain.setMemoryWithExpiry(memory, value, expiry)
|
||||
} else {
|
||||
entity.brain.setMemory(memory, value)
|
||||
}
|
||||
observedMemories[memory]?.forEach { objective ->
|
||||
objective.onMemoryChanged(entity, this, memory, value)
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> setMemoryIfChanged(memory: MemoryModuleType<T>, value: T, expiry: Long? = null) {
|
||||
if (entity.brain.getMemorySafely(memory).orElse(null) != value) {
|
||||
setMemory(memory, value, expiry)
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> eraseMemory(memory: MemoryModuleType<T>) {
|
||||
entity.brain.eraseMemory(memory)
|
||||
observedMemories[memory]?.forEach { objective ->
|
||||
objective.onMemoryChanged(entity, this, memory, null)
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> eraseMemoryIfPresent(memory: MemoryModuleType<T>) {
|
||||
if (entity.brain.hasMemoryValue(memory)) {
|
||||
eraseMemory(memory)
|
||||
}
|
||||
}
|
||||
|
||||
fun hasMemoryValue(memory: MemoryModuleType<*>): Boolean {
|
||||
return entity.brain.checkMemory(memory, MemoryStatus.VALUE_PRESENT)
|
||||
}
|
||||
|
||||
fun <T> getMemory(memory: MemoryModuleType<T>): T? {
|
||||
return entity.brain.getMemorySafely(memory).orElse(null)
|
||||
}
|
||||
}
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain
|
||||
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.Objective
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.ObjectiveStatus
|
||||
import kotlin.reflect.jvm.jvmName
|
||||
import net.minecraft.network.FriendlyByteBuf
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
|
||||
/**
|
||||
* A DTO of sorts that is sent to debugging players to keep them informed about the state of an entity's Cobblemon AI.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since December 29th, 2025
|
||||
*/
|
||||
class BiggerBrainState(
|
||||
val senses: List<SenseState>,
|
||||
val objectives: List<ObjectiveState>,
|
||||
val fixation: String? = null
|
||||
) {
|
||||
class SenseState(val className: String, val ticksUntilNextExecution: Int) {
|
||||
fun encode(buffer: FriendlyByteBuf) {
|
||||
buffer.writeUtf(className)
|
||||
buffer.writeInt(ticksUntilNextExecution)
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun decode(buffer: FriendlyByteBuf): SenseState {
|
||||
return SenseState(buffer.readUtf(), buffer.readInt())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ObjectiveState(val description: String, val status: ObjectiveStatus) {
|
||||
fun encode(buffer: FriendlyByteBuf) {
|
||||
buffer.writeUtf(description)
|
||||
buffer.writeEnum(status)
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun decode(buffer: FriendlyByteBuf): ObjectiveState {
|
||||
return ObjectiveState(buffer.readUtf(), buffer.readEnum(ObjectiveStatus::class.java))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
constructor(entity: LivingEntity, brain: BiggerBrain, objectiveStatuses: Map<Objective, ObjectiveStatus>): this(
|
||||
senses = brain.senses.map { SenseState(className = it::class.simpleName ?: it::class.jvmName, it.ticksUntilNextExecution) },
|
||||
objectives = objectiveStatuses.entries.map { ObjectiveState(it.key.getDescription(entity), it.value) },
|
||||
fixation = brain.fixation?.getDescription(entity)
|
||||
)
|
||||
|
||||
fun encode(buffer: FriendlyByteBuf) {
|
||||
buffer.writeCollection(senses) { _, sense -> sense.encode(buffer) }
|
||||
buffer.writeCollection(objectives) { _, objective -> objective.encode(buffer) }
|
||||
buffer.writeNullable(fixation) { _, fixation -> buffer.writeUtf(fixation) }
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun decode(buffer: FriendlyByteBuf): BiggerBrainState {
|
||||
val senses = buffer.readList { SenseState.decode(it) }
|
||||
val objectives = buffer.readList { ObjectiveState.decode(it) }
|
||||
val fixation = buffer.readNullable { buffer.readUtf() }
|
||||
return BiggerBrainState(senses, objectives, fixation)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain
|
||||
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.PathfinderMob
|
||||
import net.minecraft.world.entity.ai.behavior.BlockPosTracker
|
||||
import net.minecraft.world.entity.ai.behavior.EntityTracker
|
||||
import net.minecraft.world.entity.ai.behavior.PositionTracker
|
||||
import net.minecraft.world.entity.ai.memory.WalkTarget
|
||||
|
||||
/**
|
||||
* A trivial wrapper around an entity and a position tracker to make the entity look at the position each tick.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since December 29th, 2025
|
||||
*/
|
||||
class LookingTracker(val entity: PathfinderMob, val lookTarget: PositionTracker) {
|
||||
companion object {
|
||||
fun forTargetEntity(entity: PathfinderMob, target: LivingEntity, trackEyeHeight: Boolean = true): LookingTracker {
|
||||
return LookingTracker(entity, EntityTracker(target, trackEyeHeight))
|
||||
}
|
||||
|
||||
fun fromNavigationPathOrWalkTarget(entity: PathfinderMob, walkTarget: WalkTarget): LookingTracker {
|
||||
val destinationPoint = entity.navigation.path?.endNode?.asVec3() ?: walkTarget.target.currentPosition()
|
||||
return LookingTracker(entity, BlockPosTracker(destinationPoint.add(0.0, entity.eyeHeight.toDouble(), 0.0)))
|
||||
}
|
||||
}
|
||||
fun tick() {
|
||||
entity.lookControl.setLookAt(lookTarget.currentPosition())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain
|
||||
|
||||
/**
|
||||
* A simple enum representing the status of a movement action.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since December 29th, 2025
|
||||
*/
|
||||
enum class MovementStatus {
|
||||
/** Represents when the entity is still en route to the destination. */
|
||||
PENDING,
|
||||
/** Represents when the entity has successfully reached the destination. */
|
||||
COMPLETED,
|
||||
/**
|
||||
* Represents when the entity has failed to reach the destination. This can be due to
|
||||
* a change in surroundings or taking too long or all sorts of things.
|
||||
*/
|
||||
FAILED
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain
|
||||
|
||||
import net.minecraft.world.entity.ai.memory.WalkTarget
|
||||
|
||||
/**
|
||||
* A wrapping around logic that maintains an entity's journey towards a specific destination. Mainly used by fixations
|
||||
* to keep track of when pathing is done, and whether it failed or succeeded. Can also be told to stop to do any
|
||||
* necessary clean up. Implemented only by [RegularMovementTracker] which has me questioning if it should be an interface
|
||||
* or just a simple class like the [LookingTracker].
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since December 29th, 2025
|
||||
*/
|
||||
interface MovementTracker {
|
||||
val walkTarget: WalkTarget
|
||||
/** The last known status of this movement tracker. */
|
||||
var lastStatus: MovementStatus
|
||||
/** Performs a tick of movement tracking logic. Returns the current [MovementStatus]. */
|
||||
fun tick(): MovementStatus
|
||||
/** Stops the movement and does any necessary clean up. */
|
||||
fun stop()
|
||||
}
|
||||
+209
@@ -0,0 +1,209 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain
|
||||
|
||||
import com.cobblemon.mod.common.entity.ai.CobblemonWalkTarget
|
||||
import com.cobblemon.mod.common.pokemon.ai.OmniPathNodeMaker
|
||||
import com.cobblemon.mod.common.util.deleteNode
|
||||
import java.util.function.Predicate
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.world.entity.PathfinderMob
|
||||
import net.minecraft.world.entity.ai.behavior.EntityTracker
|
||||
import net.minecraft.world.entity.ai.memory.MemoryModuleType
|
||||
import net.minecraft.world.entity.ai.memory.WalkTarget
|
||||
import net.minecraft.world.entity.ai.util.DefaultRandomPos
|
||||
import net.minecraft.world.level.pathfinder.Path
|
||||
import net.minecraft.world.level.pathfinder.PathType
|
||||
import net.minecraft.world.phys.Vec3
|
||||
|
||||
/**
|
||||
* The default movement tracker used for tracking movement towards a [WalkTarget]. This is pretty much entirely
|
||||
* just the code for FollowWalkTargetTask from Minecraft brain stuff.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since December 29th, 2025
|
||||
*/
|
||||
class RegularMovementTracker(
|
||||
val entity: PathfinderMob,
|
||||
override val walkTarget: WalkTarget
|
||||
) : MovementTracker {
|
||||
companion object {
|
||||
/**
|
||||
* Attempts to create a tracker for the given entity and target. Returns null if the path
|
||||
* couldn't be created.
|
||||
*/
|
||||
fun attempt(entity: PathfinderMob, walkTarget: WalkTarget): RegularMovementTracker? {
|
||||
return RegularMovementTracker(entity, walkTarget).takeIf { it.isValid }
|
||||
}
|
||||
}
|
||||
|
||||
var isValid = false
|
||||
private set
|
||||
var started = false
|
||||
|
||||
override var lastStatus: MovementStatus = MovementStatus.PENDING
|
||||
private var path: Path? = null
|
||||
private var lastKnownTargetPos: BlockPos? = null
|
||||
|
||||
init {
|
||||
val hasReached = hasReached(entity, walkTarget)
|
||||
if (!hasReached && hasFinishedPath(entity, walkTarget)) {
|
||||
this.lastKnownTargetPos = walkTarget.target.currentBlockPosition()
|
||||
isValid = true
|
||||
} else {
|
||||
lastStatus = MovementStatus.FAILED
|
||||
}
|
||||
}
|
||||
|
||||
override fun tick(): MovementStatus {
|
||||
val path = path
|
||||
val status = if (path == null || isTargetSpectator(walkTarget)) {
|
||||
stop(entity)
|
||||
MovementStatus.FAILED
|
||||
} else if (hasReached(entity, walkTarget)) {
|
||||
stop(entity)
|
||||
MovementStatus.COMPLETED
|
||||
} else if (started && entity.navigation.isDone) {
|
||||
stop(entity)
|
||||
MovementStatus.FAILED
|
||||
} else {
|
||||
if (!started) {
|
||||
startOnPath(entity)
|
||||
started = true
|
||||
}
|
||||
|
||||
tick(entity)
|
||||
MovementStatus.PENDING
|
||||
}
|
||||
this.lastStatus = status
|
||||
return status
|
||||
}
|
||||
|
||||
fun startOnPath(entity: PathfinderMob) {
|
||||
entity.brain.setMemory(MemoryModuleType.PATH, this.path)
|
||||
entity.navigation.moveTo(this.path, walkTarget.speedModifier.toDouble())
|
||||
}
|
||||
|
||||
override fun stop() {
|
||||
stop(entity)
|
||||
}
|
||||
|
||||
fun stop(entity: PathfinderMob) {
|
||||
entity.navigation.stop()
|
||||
entity.brain.eraseMemory(MemoryModuleType.WALK_TARGET)
|
||||
entity.brain.eraseMemory(MemoryModuleType.PATH)
|
||||
}
|
||||
|
||||
private fun tick(entity: PathfinderMob) {
|
||||
val path = entity.navigation.path
|
||||
if (this.path !== path) {
|
||||
this.path = path
|
||||
this.startOnPath(entity)
|
||||
}
|
||||
|
||||
if (path != null && lastKnownTargetPos != null) {
|
||||
if (walkTarget.target.currentBlockPosition().distSqr(lastKnownTargetPos!!) > 4.0) {
|
||||
// The target has moved away from where we are pathing, try pathing to its new location
|
||||
if (hasFinishedPath(entity, walkTarget)) {
|
||||
lastKnownTargetPos = walkTarget.target.currentBlockPosition()
|
||||
startOnPath(entity)
|
||||
} else {
|
||||
// Uh oh, we can't get closer? Maybe next tick will be better?
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun withNodeFilter(
|
||||
entity: PathfinderMob,
|
||||
nodeFilter: Predicate<PathType>,
|
||||
block: () -> Unit) {
|
||||
val nodeEvaluator = entity.navigation.nodeEvaluator
|
||||
if (nodeEvaluator is OmniPathNodeMaker) {
|
||||
nodeEvaluator.nodeFilter = nodeFilter
|
||||
block()
|
||||
nodeEvaluator.nodeFilter = Predicate { true }
|
||||
} else {
|
||||
block()
|
||||
}
|
||||
}
|
||||
|
||||
private fun hasFinishedPath(entity: PathfinderMob, walkTarget: WalkTarget): Boolean {
|
||||
val blockPos = walkTarget.target.currentBlockPosition()
|
||||
this.path = generatePath(entity, walkTarget, blockPos)
|
||||
val hasReached = hasReached(entity, walkTarget)
|
||||
if (!hasReached && path == null) {
|
||||
// Desperate attempt to still path to the destination by going somewhere in the vicinity
|
||||
val vec3d = DefaultRandomPos.getPosTowards(
|
||||
entity,
|
||||
10,
|
||||
7,
|
||||
Vec3.atBottomCenterOf(blockPos),
|
||||
Math.PI / 2F
|
||||
)
|
||||
if (vec3d != null) {
|
||||
this.path = generatePath(entity, walkTarget, BlockPos.containing(vec3d))
|
||||
return this.path != null
|
||||
}
|
||||
} else if (!hasReached) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
private fun generatePath(
|
||||
entity: PathfinderMob,
|
||||
walkTarget: WalkTarget,
|
||||
target: BlockPos
|
||||
): Path? {
|
||||
var path: Path? = null
|
||||
if (walkTarget is CobblemonWalkTarget) {
|
||||
withNodeFilter(
|
||||
entity = entity,
|
||||
nodeFilter = walkTarget.nodeTypeFilter,
|
||||
) {
|
||||
var resolvedPath = entity.navigation.createPath(
|
||||
walkTarget.target.currentBlockPosition(),
|
||||
0
|
||||
)
|
||||
if (resolvedPath != null) {
|
||||
// A CobblemonWalkTarget may have a destinationNodeTypeFilter, which is used to filter the end node of the path.
|
||||
// Mainly useful for when the thing setting the target did it a bit crudely or didn't know how far the navigator
|
||||
// was going to get, and might land on something that is technically traversable by the entity but it doesn't
|
||||
// fit the intention of the task. Example, a little bird wandering might end up finishing on a flight node,
|
||||
// but it doesn't really want to fly in place so it'd prefer not ending there.
|
||||
while (resolvedPath.endNode?.type?.let { walkTarget.destinationNodeTypeFilter(it) } == false) {
|
||||
resolvedPath.deleteNode(resolvedPath.nodeCount - 1)
|
||||
}
|
||||
if (resolvedPath.nodeCount == 0) {
|
||||
resolvedPath = null
|
||||
}
|
||||
}
|
||||
path = resolvedPath
|
||||
}
|
||||
} else {
|
||||
path = entity.navigation.createPath(target, 0)
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
private fun hasReached(entity: PathfinderMob, walkTarget: WalkTarget): Boolean {
|
||||
return walkTarget.target.currentBlockPosition().distManhattan(entity.blockPosition()) <= walkTarget.closeEnoughDist
|
||||
}
|
||||
|
||||
fun isTargetSpectator(target: WalkTarget): Boolean {
|
||||
val lookTarget = target.target
|
||||
return if (lookTarget is EntityTracker) {
|
||||
lookTarget.entity.isSpectator
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain
|
||||
|
||||
import com.cobblemon.mod.common.api.ai.CobblemonWanderControl.WanderCenter
|
||||
import com.cobblemon.mod.common.api.ai.config.task.WanderTaskConfig.Companion.MAX_LOOK_DOWN_DISTANCE
|
||||
import com.cobblemon.mod.common.entity.OmniPathingEntity
|
||||
import com.cobblemon.mod.common.entity.ai.CobblemonRandomSurfacePos
|
||||
import com.cobblemon.mod.common.entity.ai.CobblemonWalkTarget
|
||||
import kotlin.math.ceil
|
||||
import kotlin.math.sqrt
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.server.level.ServerLevel
|
||||
import net.minecraft.world.entity.PathfinderMob
|
||||
import net.minecraft.world.entity.ai.behavior.BehaviorUtils
|
||||
import net.minecraft.world.entity.ai.memory.WalkTarget
|
||||
import net.minecraft.world.entity.ai.util.HoverRandomPos
|
||||
import net.minecraft.world.entity.ai.util.LandRandomPos
|
||||
import net.minecraft.world.level.material.Fluids
|
||||
import net.minecraft.world.level.pathfinder.PathType
|
||||
|
||||
/**
|
||||
* A type of wandering behaviour for an entity. I'm not sure if we keep this.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since December 29th, 2025
|
||||
*/
|
||||
enum class WanderType(
|
||||
val isValid: (PathfinderMob) -> Boolean,
|
||||
val choosePosition: (PathfinderMob, minimumHeight: Int, maximumHeight: Int, horizontalRange: Int, verticalRange: Int) -> BlockPos?,
|
||||
val createWalkTarget: (PathfinderMob, position: BlockPos, avoidsTargetingAir: Boolean, speedMultiplier: Float) -> WalkTarget?,
|
||||
) {
|
||||
|
||||
WATER(
|
||||
isValid = { it.isInWater },
|
||||
choosePosition = choosePosition@{ entity, _, _, horizontalRange, verticalRange ->
|
||||
var pos = BehaviorUtils.getRandomSwimmablePos(entity, horizontalRange, verticalRange)?.let(BlockPos::containing)
|
||||
?: return@choosePosition null
|
||||
|
||||
// Move target pos down so that we don't move out of water too much
|
||||
val requiredDepth = ceil(0.5 + entity.eyeHeight)
|
||||
// Get the current depth of the block
|
||||
var depth = 1
|
||||
var testPos = pos.above()
|
||||
var blockState = entity.level().getBlockState(testPos)
|
||||
while (blockState.fluidState.type == Fluids.WATER) {
|
||||
depth++
|
||||
testPos = testPos!!.above()
|
||||
blockState = entity.level().getBlockState(testPos)
|
||||
}
|
||||
|
||||
if (depth < requiredDepth) {
|
||||
// Iterate down until we reach the desired min depth or we reach a non water block
|
||||
pos = pos.below()
|
||||
blockState = entity.level().getBlockState(pos)
|
||||
while (blockState.fluidState.type == Fluids.WATER && depth < requiredDepth && pos.y > entity.level().minBuildHeight) {
|
||||
depth++
|
||||
pos = pos.below()
|
||||
blockState = entity.level().getBlockState(pos)
|
||||
}
|
||||
}
|
||||
|
||||
return@choosePosition if (depth < requiredDepth) null else pos
|
||||
},
|
||||
createWalkTarget = { _, pos, _, speedMultiplier ->
|
||||
CobblemonWalkTarget(
|
||||
pos = pos,
|
||||
nodeTypeFilter = { it == PathType.WATER || it == PathType.WATER_BORDER },
|
||||
speedModifier = speedMultiplier,
|
||||
completionRange = 0
|
||||
)
|
||||
}
|
||||
),
|
||||
LAND(
|
||||
isValid = { entity -> !entity.isInWater && !entity.isInLava },
|
||||
choosePosition = choosePosition@{ entity, minimumHeight, maximumHeight, horizontalRange, verticalRange ->
|
||||
val targetVec = when {
|
||||
maximumHeight != -1 -> {
|
||||
HoverRandomPos.getPos(
|
||||
entity,
|
||||
horizontalRange,
|
||||
verticalRange,
|
||||
entity.random.nextFloat() - 0.5,
|
||||
entity.random.nextFloat() - 0.5,
|
||||
Math.PI.toFloat(),
|
||||
maximumHeight,
|
||||
minimumHeight
|
||||
)
|
||||
}
|
||||
entity.isUnderWater && entity.canBreatheUnderwater() && !((entity as OmniPathingEntity).canSwimInWater()) -> {
|
||||
BehaviorUtils.getRandomSwimmablePos(
|
||||
entity,
|
||||
horizontalRange,
|
||||
verticalRange
|
||||
)
|
||||
}
|
||||
entity is OmniPathingEntity && (entity.canWalkOnWater() || entity.canWalkOnLava()) -> {
|
||||
CobblemonRandomSurfacePos.getPos(
|
||||
entity,
|
||||
horizontalRange,
|
||||
verticalRange
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
LandRandomPos.getPos(
|
||||
entity,
|
||||
horizontalRange,
|
||||
verticalRange
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val pos = targetVec?.let(BlockPos::containing) ?: return@choosePosition null
|
||||
LAND.applyHeightConstraints(pos, minimumHeight, maximumHeight, entity.level() as ServerLevel)
|
||||
},
|
||||
createWalkTarget = { entity, pos, avoidsTargetingAir, speedMultiplier ->
|
||||
CobblemonWalkTarget(
|
||||
pos = pos,
|
||||
speedModifier = speedMultiplier,
|
||||
completionRange = 0,
|
||||
nodeTypeFilter = { nodeType ->
|
||||
entity.canBreatheUnderwater() || nodeType !in listOf(
|
||||
PathType.WATER,
|
||||
PathType.WATER_BORDER
|
||||
)
|
||||
},
|
||||
destinationNodeTypeFilter = { nodeType ->
|
||||
!avoidsTargetingAir || nodeType !in listOf(
|
||||
PathType.OPEN
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
;
|
||||
|
||||
fun generateWalkTarget(
|
||||
entity: PathfinderMob,
|
||||
maxAttempts: Int,
|
||||
center: WanderCenter?,
|
||||
minimumHeight: Int,
|
||||
maximumHeight: Int,
|
||||
horizontalRange: Int,
|
||||
verticalRange: Int,
|
||||
avoidTargetingAir: Boolean,
|
||||
speedMultiplier: Float
|
||||
): WalkTarget? {
|
||||
var attempts = 0
|
||||
var pos: BlockPos? = null
|
||||
|
||||
while (attempts++ < maxAttempts && pos == null) {
|
||||
pos = choosePosition(entity, minimumHeight, maximumHeight, horizontalRange, verticalRange)
|
||||
?: return null
|
||||
if (!isSuitable(center, pos)) {
|
||||
pos = null
|
||||
}
|
||||
}
|
||||
|
||||
if (pos == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
return createWalkTarget(entity, pos, avoidTargetingAir, speedMultiplier)
|
||||
}
|
||||
|
||||
private fun isSuitable(center: WanderCenter?, position: BlockPos): Boolean {
|
||||
val center = center ?: return true
|
||||
val centerPos = BlockPos(center.x.toInt(), center.y.toInt(), center.z.toInt())
|
||||
val distance = sqrt(centerPos.distSqr(position))
|
||||
val suitable = distance >= center.minRange && distance <= center.maxRange
|
||||
return suitable
|
||||
}
|
||||
|
||||
private fun applyHeightConstraints(
|
||||
pos: BlockPos,
|
||||
minimumHeight: Int,
|
||||
maximumHeight: Int,
|
||||
world: ServerLevel
|
||||
): BlockPos {
|
||||
if (minimumHeight <= 0 && maximumHeight == -1) {
|
||||
return pos // It ain't a hoverer
|
||||
}
|
||||
|
||||
val block = world.getBlockState(pos)
|
||||
val altitude = if (!block.isAir) {
|
||||
0
|
||||
} else {
|
||||
(1..MAX_LOOK_DOWN_DISTANCE).firstOrNull {
|
||||
val newPos = pos.below(it)
|
||||
if (!world.getBlockState(newPos).isAir) {
|
||||
return@firstOrNull true
|
||||
} else if (newPos.y <= world.minBuildHeight) {
|
||||
// Don't adjust downward into the void (Mostly if we're in the end)
|
||||
// This doesn't stop fliers from wandering off end islands,
|
||||
// but it does stop them from diving directly into the void.
|
||||
return pos
|
||||
}
|
||||
return@firstOrNull false
|
||||
} ?: MAX_LOOK_DOWN_DISTANCE
|
||||
}
|
||||
|
||||
if (altitude > maximumHeight && maximumHeight >= 0) {
|
||||
val excess = altitude - maximumHeight
|
||||
val excessFromMinimum = altitude - minimumHeight
|
||||
val correction = world.random.nextIntBetweenInclusive(excess, excessFromMinimum)
|
||||
return pos.atY(pos.y - correction)
|
||||
} else if (altitude < minimumHeight && minimumHeight >= 0) {
|
||||
val deficit = minimumHeight - altitude
|
||||
val deficitFromMaximum = maximumHeight - altitude
|
||||
val correction = world.random.nextIntBetweenInclusive(deficit, deficitFromMaximum)
|
||||
return pos.atY(pos.y + correction)
|
||||
} else {
|
||||
return pos
|
||||
}
|
||||
}
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.distraction
|
||||
|
||||
import net.minecraft.world.damagesource.DamageSource
|
||||
|
||||
/**
|
||||
* Something that may distract an entity from its current fixation. The majority of fixations
|
||||
* will rely on the types of distraction to determine if they should be distracted.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since December 29th, 2025
|
||||
*/
|
||||
open class Distraction(val types: Set<DistractionType>) {
|
||||
constructor(vararg types: DistractionType) : this(types.toSet())
|
||||
|
||||
val isIdleBoredom = DistractionType.IDLE_BOREDOM in types
|
||||
val isForce = DistractionType.FORCE in types
|
||||
val isComfort = DistractionType.COMFORT in types
|
||||
val isDamage = DistractionType.DAMAGE in types
|
||||
|
||||
fun hasAnyOf(vararg distractionTypes: DistractionType): Boolean {
|
||||
return distractionTypes.any { it in types }
|
||||
}
|
||||
|
||||
companion object {
|
||||
val FORCE = Distraction(DistractionType.FORCE)
|
||||
}
|
||||
}
|
||||
|
||||
/** Reusable basic distraction for a COMFORT type of distraction. */
|
||||
val comfortDistraction = Distraction(DistractionType.COMFORT)
|
||||
/** Reusable basic distraction for an IDLE_BOREDOM type of distraction. */
|
||||
val idleBoredomDistraction = Distraction(DistractionType.IDLE_BOREDOM)
|
||||
|
||||
open class HurtDistraction(val damageSource: DamageSource) : Distraction(DistractionType.DAMAGE)
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.distraction
|
||||
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.EntityFixation
|
||||
|
||||
/**
|
||||
* A categorisation of distractions that can be attempted on an [EntityFixation].
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since December 29th, 2025
|
||||
*/
|
||||
enum class DistractionType {
|
||||
/** Represents a distraction that the entity is not particularly motivated by but would consider doing out of boredom. */
|
||||
IDLE_BOREDOM,
|
||||
/** Represents a distraction that is for the sake of the entity's comfort because it isn't comfortable where it is. */
|
||||
COMFORT,
|
||||
/** Represents a distraction motivated by being hurt. */
|
||||
DAMAGE,
|
||||
/** A forceful distraction. Just about everything should respect this but it's conceivable that something not. */
|
||||
FORCE,
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.fixation
|
||||
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.Distraction
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
|
||||
/**
|
||||
* A fixation that rejects all non-force distractions and does nothing, lasting until
|
||||
* the provided [condition] is no longer true.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since January 16th, 2026
|
||||
*/
|
||||
open class ConditionalLockFixation(
|
||||
val description: String,
|
||||
val condition: () -> Boolean
|
||||
): EntityFixation, LockFixation {
|
||||
override fun canDistract(distraction: Distraction, entity: LivingEntity) = distraction.isForce
|
||||
override fun getDescription(entity: LivingEntity) = description
|
||||
override fun tick(entity: LivingEntity, brain: BiggerBrain) = condition()
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.fixation
|
||||
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.Distraction
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
|
||||
/**
|
||||
* A fixation that rejects all non-force distractions and does nothing for some
|
||||
* number of ticks.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since January 16th, 2026
|
||||
*/
|
||||
open class DurationLockFixation(
|
||||
val description: String,
|
||||
var durationTicks: Int
|
||||
) : EntityFixation, LockFixation {
|
||||
override fun canDistract(distraction: Distraction, entity: LivingEntity) = distraction.isForce
|
||||
override fun getDescription(entity: LivingEntity) = description.replace("{ticks}", durationTicks.toString())
|
||||
override fun tick(entity: LivingEntity, brain: BiggerBrain): Boolean {
|
||||
return --durationTicks > 0
|
||||
}
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.fixation
|
||||
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.Distraction
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
|
||||
/**
|
||||
* An entity's fixation, representing a task that takes a central role in the entity's behaviour
|
||||
* and excludes all other possible fixations.
|
||||
*
|
||||
* The role of the fixation is to do an action which is the primary focus of the entity at that time,
|
||||
* and which may be distracted from under some circumstances. An entity may be fixated on moving from
|
||||
* A to B, and then be distracted upon being hurt. Or maybe it's an attack fixation, in which case
|
||||
* damage doesn't distract it at all.
|
||||
*
|
||||
* The difference between a fixation and an objective is that objectives are capable of running in parallel,
|
||||
* and a fixation may even be in the pursuit of an objective, but the fixation is central.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since December 28th, 2025
|
||||
*/
|
||||
interface EntityFixation {
|
||||
/** Returns true if the given distraction can stop this fixation. */
|
||||
fun canDistract(distraction: Distraction, entity: LivingEntity): Boolean
|
||||
/** Notifies the fixation that it has been distracted by the given distraction so it can do clean up. */
|
||||
fun onDistract(distraction: Distraction, entity: LivingEntity) {}
|
||||
/** Continues doing whatever the fixation does. Returns true if the fixation should continue. */
|
||||
fun tick(entity: LivingEntity, brain: BiggerBrain): Boolean
|
||||
/** Gets a current description of the fixation for the debug screen. */
|
||||
fun getDescription(entity: LivingEntity): String
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.fixation
|
||||
|
||||
/**
|
||||
* Represents a fixation that blocks battling, capture, etc. for Pokémon. This is
|
||||
* used to control the isBusy property on Pokémon.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since January 16th, 2026
|
||||
*/
|
||||
interface LockFixation
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.fixation
|
||||
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.LookingTracker
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.MovementStatus
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.MovementTracker
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.Distraction
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.DistractionType
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
|
||||
/**
|
||||
* A simple fixation that uses both a looking tracker and a movement tracker.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since December 29th, 2025
|
||||
*/
|
||||
open class LookAndMoveFixation(
|
||||
/** Distraction types that will be respected. */
|
||||
val distractionTypes: Set<DistractionType>,
|
||||
/** How to describe the fixation in the debug overlay. */
|
||||
val description: String,
|
||||
override val lookingTracker: LookingTracker,
|
||||
override val movementTracker: MovementTracker
|
||||
): EntityFixation, LookingTrackerFixation, MovementTrackerFixation {
|
||||
override fun getDescription(entity: LivingEntity) = description
|
||||
|
||||
override fun canDistract(distraction: Distraction, entity: LivingEntity): Boolean {
|
||||
return distraction.types.any(distractionTypes::contains)
|
||||
}
|
||||
|
||||
override fun onDistract(distraction: Distraction, entity: LivingEntity) {
|
||||
movementTracker.stop()
|
||||
}
|
||||
|
||||
override fun tick(entity: LivingEntity, brain: BiggerBrain): Boolean {
|
||||
lookingTracker.tick()
|
||||
return tickMovementFixation() == MovementStatus.PENDING
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.fixation
|
||||
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.LookingTracker
|
||||
|
||||
/**
|
||||
* Applied to fixations which may have looking associated with them. This is useful
|
||||
* in the case that an objective needs to know if the current fixation intends on (or is)
|
||||
* controlling their looking direction.
|
||||
*
|
||||
* Example: An objective that makes Pokémon bob their heads up and down while music is playing.
|
||||
* Said objective would not warrant a full fixation, but would still like to ensure that the
|
||||
* current fixation does not intend on using a looking tracker.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since December 29th, 2025
|
||||
*/
|
||||
interface LookingTrackerFixation {
|
||||
val lookingTracker: LookingTracker?
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.fixation
|
||||
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.MovementStatus
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.MovementTracker
|
||||
|
||||
/**
|
||||
* Applied to fixations which may have movement associated with them. This is useful
|
||||
* in the case that an objective needs to know if the current fixation intends on (or is)
|
||||
* controlling their movement.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since December 29th, 2025
|
||||
*/
|
||||
interface MovementTrackerFixation {
|
||||
/** The movement tracker at the moment. */
|
||||
val movementTracker: MovementTracker?
|
||||
|
||||
/** Ticks the movement tracker in a way that skips finalized movement trackers. */
|
||||
fun tickMovementFixation(): MovementStatus? {
|
||||
val movementTracker = this.movementTracker ?: return null
|
||||
return if (movementTracker.lastStatus != MovementStatus.PENDING) {
|
||||
movementTracker.lastStatus
|
||||
} else {
|
||||
movementTracker.tick()
|
||||
}
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.fixation
|
||||
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.MovementStatus
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.MovementTracker
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.Distraction
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
|
||||
class SimpleMoveFixation(
|
||||
override val movementTracker: MovementTracker,
|
||||
private val description: String
|
||||
) : EntityFixation, MovementTrackerFixation {
|
||||
override fun getDescription(entity: LivingEntity) = description
|
||||
|
||||
override fun canDistract(distraction: Distraction, entity: LivingEntity): Boolean {
|
||||
return distraction.isForce || distraction.isDamage || movementTracker.lastStatus == MovementStatus.COMPLETED
|
||||
}
|
||||
|
||||
override fun onDistract(distraction: Distraction, entity: LivingEntity) {
|
||||
movementTracker.stop()
|
||||
}
|
||||
|
||||
override fun tick(entity: LivingEntity, brain: BiggerBrain): Boolean {
|
||||
return tickMovementFixation() == MovementStatus.PENDING
|
||||
}
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.objective
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.Objective
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.ObjectiveStatus
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import net.minecraft.server.level.ServerLevel
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.ai.memory.MemoryModuleType
|
||||
import net.minecraft.world.entity.player.Player
|
||||
|
||||
class AttackEggThiefTask : Objective {
|
||||
private var remainingCooldownTicks = 0
|
||||
private var lastThiefId: java.util.UUID? = null
|
||||
|
||||
override fun getDescription(entity: LivingEntity) = "Attack egg thief"
|
||||
|
||||
override fun perform(
|
||||
entity: LivingEntity,
|
||||
brain: BiggerBrain,
|
||||
activeObjectiveTags: MutableSet<String>
|
||||
): ObjectiveStatus {
|
||||
if (entity !is PokemonEntity || !entity.isAlive) return ObjectiveStatus.INVALID
|
||||
|
||||
val eggThief = brain.getMemory(CobblemonMemories.EGG_THIEF) as? Player ?: run {
|
||||
lastThiefId = null
|
||||
return ObjectiveStatus.INACTIVE
|
||||
}
|
||||
|
||||
activeObjectiveTags.add(Objective.OBJECTIVE_TAG_WANDERING)
|
||||
|
||||
if (remainingCooldownTicks > 0 && lastThiefId == eggThief.uuid) {
|
||||
remainingCooldownTicks--
|
||||
return ObjectiveStatus.ACTIVE
|
||||
}
|
||||
|
||||
brain.sendDebugMessage("[AttackEggThief] Attacking egg thief ${eggThief.name.string}.")
|
||||
entity.cry()
|
||||
(entity.level() as? ServerLevel)?.sendParticles(
|
||||
net.minecraft.core.particles.ParticleTypes.ANGRY_VILLAGER,
|
||||
entity.getRandomX(1.0),
|
||||
entity.getRandomY() + 0.5,
|
||||
entity.getRandomZ(1.0),
|
||||
4 + kotlin.random.Random.nextInt(2),
|
||||
0.2 * entity.random.nextGaussian(),
|
||||
0.1 * entity.random.nextGaussian(),
|
||||
0.2 * entity.random.nextGaussian(),
|
||||
0.02
|
||||
)
|
||||
|
||||
brain.eraseMemory(CobblemonMemories.EGG_SEARCH_COUNTDOWN)
|
||||
brain.setMemory(MemoryModuleType.ANGRY_AT, eggThief.uuid)
|
||||
brain.setMemory(MemoryModuleType.ATTACK_TARGET, eggThief)
|
||||
|
||||
lastThiefId = eggThief.uuid
|
||||
remainingCooldownTicks = 40
|
||||
|
||||
return ObjectiveStatus.ACTIVE
|
||||
}
|
||||
}
|
||||
+142
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.objective
|
||||
|
||||
import com.bedrockk.molang.Expression
|
||||
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
|
||||
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
|
||||
import com.cobblemon.mod.common.api.ai.asSimplifiedExpression
|
||||
import com.cobblemon.mod.common.api.ai.asVariables
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.RegularMovementTracker
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.Distraction
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.EntityFixation
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.SimpleMoveFixation
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.Objective.Companion.OBJECTIVE_TAG_WANDERING
|
||||
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
|
||||
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.util.asExpression
|
||||
import com.cobblemon.mod.common.util.mainThreadRuntime
|
||||
import com.cobblemon.mod.common.util.resolveFloat
|
||||
import com.cobblemon.mod.common.util.resolveInt
|
||||
import com.cobblemon.mod.common.util.withQueryValue
|
||||
import com.mojang.datafixers.util.Either
|
||||
import net.minecraft.world.InteractionHand
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.PathfinderMob
|
||||
import net.minecraft.world.entity.ai.behavior.EntityTracker
|
||||
import net.minecraft.world.entity.ai.memory.MemoryModuleType
|
||||
import net.minecraft.world.entity.ai.memory.WalkTarget
|
||||
import net.minecraft.world.entity.player.Player
|
||||
|
||||
class AttackTarget(
|
||||
private val speedMultiplier: Expression = "1.0".asExpression(),
|
||||
private val attackRange: Expression = "2.0".asExpression(),
|
||||
private val attackCooldownTicks: Int = 20
|
||||
) : Objective {
|
||||
private var movementTask: EntityFixation? = null
|
||||
private var remainingCooldownTicks = 0
|
||||
|
||||
override val observedMemories = listOf(MemoryModuleType.ATTACK_TARGET)
|
||||
|
||||
override fun getDescription(entity: LivingEntity) = "Attack target"
|
||||
|
||||
override fun perform(
|
||||
entity: LivingEntity,
|
||||
brain: BiggerBrain,
|
||||
activeObjectiveTags: MutableSet<String>
|
||||
): ObjectiveStatus {
|
||||
if (entity !is PokemonEntity || !entity.isAlive) return ObjectiveStatus.INVALID
|
||||
if (entity !is PathfinderMob) return ObjectiveStatus.INVALID
|
||||
|
||||
val target = brain.getMemory(MemoryModuleType.ATTACK_TARGET) as? LivingEntity
|
||||
?: return ObjectiveStatus.INACTIVE
|
||||
|
||||
activeObjectiveTags.add(OBJECTIVE_TAG_WANDERING)
|
||||
|
||||
if (target.isRemoved || target.isDeadOrDying) {
|
||||
brain.eraseMemory(MemoryModuleType.ATTACK_TARGET)
|
||||
brain.eraseMemory(MemoryModuleType.ANGRY_AT)
|
||||
return ObjectiveStatus.INACTIVE
|
||||
}
|
||||
|
||||
if (target is Player && (target.isCreative || target.isSpectator)) {
|
||||
brain.eraseMemory(MemoryModuleType.ATTACK_TARGET)
|
||||
brain.eraseMemory(MemoryModuleType.ANGRY_AT)
|
||||
return ObjectiveStatus.INACTIVE
|
||||
}
|
||||
|
||||
if (remainingCooldownTicks > 0) {
|
||||
remainingCooldownTicks--
|
||||
}
|
||||
|
||||
val runtime = mainThreadRuntime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
|
||||
val range = runtime.resolveFloat(attackRange)
|
||||
val speed = runtime.resolveFloat(speedMultiplier)
|
||||
|
||||
if (entity.distanceToSqr(target) <= range * range && entity.hasLineOfSight(target)) {
|
||||
if (remainingCooldownTicks <= 0) {
|
||||
if (entity.doHurtTarget(target)) {
|
||||
entity.swing(InteractionHand.MAIN_HAND)
|
||||
}
|
||||
remainingCooldownTicks = attackCooldownTicks
|
||||
}
|
||||
return ObjectiveStatus.ACTIVE
|
||||
}
|
||||
|
||||
if (brain.isFixation(movementTask)) {
|
||||
return ObjectiveStatus.FIXATED
|
||||
}
|
||||
|
||||
if (!brain.distract(Distraction.FORCE)) return ObjectiveStatus.BLOCKED
|
||||
val walkTarget = WalkTarget(EntityTracker(target, true), speed, 1)
|
||||
val movementTracker = RegularMovementTracker.attempt(entity, walkTarget) ?: return ObjectiveStatus.ACTIVE
|
||||
movementTask = brain.fixate(SimpleMoveFixation(movementTracker, "Attack target"))
|
||||
return ObjectiveStatus.ACTIVE
|
||||
}
|
||||
|
||||
override fun <T> onMemoryChanged(
|
||||
entity: LivingEntity,
|
||||
brain: BiggerBrain,
|
||||
memory: MemoryModuleType<T>,
|
||||
value: T?
|
||||
) {
|
||||
if (memory == MemoryModuleType.ATTACK_TARGET && value == null) {
|
||||
movementTask = null
|
||||
}
|
||||
}
|
||||
|
||||
class Config : ObjectiveConfig {
|
||||
val speedMultiplier: ExpressionOrEntityVariable = Either.left("1.0".asExpression())
|
||||
val attackRange: ExpressionOrEntityVariable = Either.left("2.0".asExpression())
|
||||
val attackCooldownTicks: ExpressionOrEntityVariable = Either.left("20".asExpression())
|
||||
|
||||
override fun getVariables(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
): List<MoLangConfigVariable> {
|
||||
return listOf(speedMultiplier, attackRange, attackCooldownTicks).asVariables()
|
||||
}
|
||||
|
||||
override fun create(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
): Objective {
|
||||
return AttackTarget(
|
||||
speedMultiplier = speedMultiplier.asSimplifiedExpression(entity),
|
||||
attackRange = attackRange.asSimplifiedExpression(entity),
|
||||
attackCooldownTicks = behaviourConfigurationContext.runtime.resolveInt(
|
||||
attackCooldownTicks.asSimplifiedExpression(entity)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
+183
@@ -0,0 +1,183 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.objective
|
||||
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.CobblemonItems
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.Objective
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.ObjectiveStatus
|
||||
import com.cobblemon.mod.common.api.nest.WildNestType
|
||||
import com.cobblemon.mod.common.api.nest.WildNestTypes
|
||||
import com.cobblemon.mod.common.api.pokemon.breeding.Egg
|
||||
import com.cobblemon.mod.common.api.text.green
|
||||
import com.cobblemon.mod.common.block.WildNestBlock
|
||||
import com.cobblemon.mod.common.block.entity.NestBlockEntity
|
||||
import com.cobblemon.mod.common.breeding.BreedingLogicManager
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.server.level.ServerLevel
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.item.ItemStack
|
||||
import net.minecraft.world.phys.Vec3
|
||||
|
||||
class BreedAtNestTask : Objective {
|
||||
override fun getDescription(entity: LivingEntity) = "Breed at nest"
|
||||
|
||||
override fun perform(
|
||||
entity: LivingEntity,
|
||||
brain: BiggerBrain,
|
||||
activeObjectiveTags: MutableSet<String>
|
||||
): ObjectiveStatus {
|
||||
if (entity !is PokemonEntity || !entity.isAlive) return ObjectiveStatus.INVALID
|
||||
|
||||
val paternalNestLoc = brain.getMemory(CobblemonMemories.PATERNAL_NEST_LOCATION)
|
||||
if (entity.pokemon.isWild() && paternalNestLoc != null) {
|
||||
brain.sendDebugMessage("[BreedAtNest] Wild Pokémon already has paternal nest; skipping.")
|
||||
return ObjectiveStatus.INVALID
|
||||
}
|
||||
|
||||
val nestLocation = brain.getMemory(CobblemonMemories.NEST_LOCATION) ?: return ObjectiveStatus.INVALID
|
||||
val mate = brain.getMemory(CobblemonMemories.POTENTIAL_MATE) as? PokemonEntity ?: return ObjectiveStatus.INVALID
|
||||
|
||||
val world = entity.level() as? ServerLevel ?: return ObjectiveStatus.INVALID
|
||||
val nestEntity = world.getBlockEntity(nestLocation) as? NestBlockEntity ?: return ObjectiveStatus.INVALID
|
||||
|
||||
val allowedTypes = allowedNestTypes(entity, mate)
|
||||
val nestType = (nestEntity.blockState.block as? WildNestBlock)?.type ?: return ObjectiveStatus.INVALID
|
||||
if (!isNestTypeAllowed(nestType, entity, allowedTypes)) {
|
||||
brain.sendDebugMessage("[BreedAtNest] Nest type $nestType not allowed. Allowed=$allowedTypes.")
|
||||
return ObjectiveStatus.INVALID
|
||||
}
|
||||
val isNestWaterlogged = nestEntity.blockState.getOptionalValue(net.minecraft.world.level.block.state.properties.BlockStateProperties.WATERLOGGED).orElse(false)
|
||||
if (!isWaterGroupCompatible(entity, mate, isNestWaterlogged)) {
|
||||
brain.sendDebugMessage("[BreedAtNest] Water-group mismatch for nest at $nestLocation (waterlogged=$isNestWaterlogged).")
|
||||
return ObjectiveStatus.INVALID
|
||||
}
|
||||
|
||||
val targetVec = Vec3.atCenterOf(nestLocation)
|
||||
if (entity.distanceToSqr(targetVec) > 2.0 && mate.distanceToSqr(targetVec) > 2.0) {
|
||||
brain.sendDebugMessage("[BreedAtNest] Parents not in range of nest $nestLocation.")
|
||||
return ObjectiveStatus.INACTIVE
|
||||
}
|
||||
|
||||
if (nestEntity.getItem(0) != ItemStack.EMPTY) {
|
||||
brain.sendDebugMessage("[BreedAtNest] Nest at $nestLocation already has an egg.")
|
||||
return ObjectiveStatus.INVALID
|
||||
}
|
||||
|
||||
val parentOne = entity.pokemon
|
||||
val parentTwo = mate.pokemon
|
||||
val breedingResult = BreedingLogicManager.breed(parentOne, parentTwo)
|
||||
val form = BreedingLogicManager.calculateForm(parentOne, parentTwo)
|
||||
|
||||
val egg = breedingResult.pokemon?.let { result ->
|
||||
Egg(
|
||||
species = result.species.resourceIdentifier,
|
||||
parentOne = parentOne,
|
||||
parentTwo = parentTwo,
|
||||
form = form.name.lowercase(),
|
||||
timeToHatch = 6000F
|
||||
)
|
||||
} ?: return ObjectiveStatus.INVALID
|
||||
|
||||
nestEntity.setItem(egg.createEggItemStack())
|
||||
brain.sendDebugMessage("[BreedAtNest] Egg placed at $nestLocation.")
|
||||
|
||||
if (Cobblemon.config.canDestinyKnotBreak) {
|
||||
if (entity.pokemon.heldItem.item == CobblemonItems.DESTINY_KNOT.asItem())
|
||||
entity.pokemon.heldItemNoCopy().damageValue += 1
|
||||
if (mate.pokemon.heldItem.item == CobblemonItems.DESTINY_KNOT.asItem())
|
||||
mate.pokemon.heldItemNoCopy().damageValue += 1
|
||||
}
|
||||
|
||||
if (nestEntity.ownerUUID == null) {
|
||||
nestEntity.parents = Pair(entity.uuid, mate.uuid)
|
||||
nestEntity.nestClaimedBy = Pair(entity.uuid, mate.uuid)
|
||||
}
|
||||
|
||||
parentOne.breedingCooldown = BreedingLogicManager.resetBreedingCooldown(parentOne)
|
||||
parentTwo.breedingCooldown = BreedingLogicManager.resetBreedingCooldown(parentTwo)
|
||||
|
||||
world.sendParticles(
|
||||
net.minecraft.core.particles.ParticleTypes.HEART,
|
||||
entity.getRandomX(1.0),
|
||||
entity.getRandomY() + 0.5,
|
||||
entity.getRandomZ(1.0),
|
||||
4 + kotlin.random.Random.nextInt(2),
|
||||
0.2 * entity.random.nextGaussian(),
|
||||
0.1 * entity.random.nextGaussian(),
|
||||
0.2 * entity.random.nextGaussian(),
|
||||
0.02
|
||||
)
|
||||
world.sendParticles(
|
||||
net.minecraft.core.particles.ParticleTypes.HEART,
|
||||
mate.getRandomX(1.0),
|
||||
mate.getRandomY() + 0.5,
|
||||
mate.getRandomZ(1.0),
|
||||
4 + kotlin.random.Random.nextInt(2),
|
||||
0.2 * mate.random.nextGaussian(),
|
||||
0.1 * mate.random.nextGaussian(),
|
||||
0.2 * mate.random.nextGaussian(),
|
||||
0.02
|
||||
)
|
||||
|
||||
brain.setMemory(CobblemonMemories.PATERNAL_NEST_LOCATION, nestLocation)
|
||||
brain.setMemory(CobblemonMemories.EGG_IN_NEST, true)
|
||||
mate.biggerBrain.setMemory(CobblemonMemories.PATERNAL_NEST_LOCATION, nestLocation)
|
||||
mate.biggerBrain.setMemory(CobblemonMemories.EGG_IN_NEST, true)
|
||||
|
||||
world.players().forEach { player ->
|
||||
player.displayClientMessage("An egg has been laid in the nest!".green(), true)
|
||||
}
|
||||
|
||||
return ObjectiveStatus.ACTIVE
|
||||
}
|
||||
|
||||
private fun allowedNestTypes(entity: PokemonEntity, mate: PokemonEntity): Set<WildNestType> {
|
||||
val types = WildNestTypes.combinedTypes(entity, mate)
|
||||
val mutable = types.toMutableSet()
|
||||
if (!entity.pokemon.isWild() && !mate.pokemon.isWild()) {
|
||||
mutable.add(WildNestType.PLAYER)
|
||||
}
|
||||
return mutable
|
||||
}
|
||||
|
||||
private fun isNestTypeAllowed(
|
||||
nestType: WildNestType,
|
||||
entity: PokemonEntity,
|
||||
allowedTypes: Set<WildNestType>
|
||||
): Boolean {
|
||||
if (nestType == WildNestType.PLAYER) {
|
||||
return !entity.pokemon.isWild() && allowedTypes.contains(WildNestType.PLAYER)
|
||||
}
|
||||
return allowedTypes.contains(nestType)
|
||||
}
|
||||
|
||||
private fun isWaterGroupCompatible(
|
||||
entity: PokemonEntity,
|
||||
mate: PokemonEntity,
|
||||
isNestWaterlogged: Boolean
|
||||
): Boolean {
|
||||
val waterEggGroups = setOf("water_1", "water_2", "water_3")
|
||||
val entityEggGroups = entity.pokemon.species.eggGroups.map { it.name.lowercase() }
|
||||
val mateEggGroups = mate.pokemon.species.eggGroups.map { it.name.lowercase() }
|
||||
val entityHasWater = entityEggGroups.any { it in waterEggGroups }
|
||||
val mateHasWater = mateEggGroups.any { it in waterEggGroups }
|
||||
val entityOnlyWater = entityEggGroups.all { it in waterEggGroups }
|
||||
val mateOnlyWater = mateEggGroups.all { it in waterEggGroups }
|
||||
|
||||
return if (isNestWaterlogged) {
|
||||
entityHasWater && mateHasWater
|
||||
} else {
|
||||
!(entityOnlyWater || mateOnlyWater)
|
||||
}
|
||||
}
|
||||
}
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.objective
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.Objective
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.ObjectiveStatus
|
||||
import com.cobblemon.mod.common.api.nest.WildNestTypes
|
||||
import com.cobblemon.mod.common.api.text.green
|
||||
import com.cobblemon.mod.common.block.WildNestBlock
|
||||
import com.cobblemon.mod.common.block.entity.NestBlockEntity
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.server.level.ServerLevel
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.level.block.state.properties.BlockStateProperties
|
||||
import net.minecraft.world.phys.Vec3
|
||||
import kotlin.jvm.optionals.getOrNull
|
||||
|
||||
class BuildNestTask : Objective {
|
||||
override fun getDescription(entity: LivingEntity) = "Build nest"
|
||||
|
||||
override fun perform(
|
||||
entity: LivingEntity,
|
||||
brain: BiggerBrain,
|
||||
activeObjectiveTags: MutableSet<String>
|
||||
): ObjectiveStatus {
|
||||
if (entity !is PokemonEntity || !entity.isAlive) return ObjectiveStatus.INVALID
|
||||
|
||||
if (brain.getMemory(CobblemonMemories.NEST_LOCATION) != null) {
|
||||
brain.sendDebugMessage("[BuildNest] Nest already assigned; skipping.")
|
||||
return ObjectiveStatus.INVALID
|
||||
}
|
||||
if (brain.getMemory(CobblemonMemories.PATERNAL_NEST_LOCATION) != null) {
|
||||
brain.sendDebugMessage("[BuildNest] Paternal nest already set; skipping.")
|
||||
return ObjectiveStatus.INVALID
|
||||
}
|
||||
if (brain.hasMemoryValue(net.minecraft.world.entity.ai.memory.MemoryModuleType.ATTACK_TARGET)) {
|
||||
brain.sendDebugMessage("[BuildNest] In combat; skipping.")
|
||||
return ObjectiveStatus.INVALID
|
||||
}
|
||||
|
||||
val mate = brain.getMemory(CobblemonMemories.POTENTIAL_MATE) as? PokemonEntity ?: return ObjectiveStatus.INVALID
|
||||
val combinedTypes = WildNestTypes.combinedTypes(entity, mate)
|
||||
if (combinedTypes.isEmpty()) {
|
||||
brain.sendDebugMessage("[BuildNest] No combined nest types; skipping.")
|
||||
return ObjectiveStatus.INVALID
|
||||
}
|
||||
|
||||
val idealNestPos: BlockPos = brain.getMemory(CobblemonMemories.IDEAL_NEST_LOCATION)
|
||||
?: return ObjectiveStatus.INVALID
|
||||
val nestCenter = Vec3.atCenterOf(idealNestPos)
|
||||
|
||||
if (brain.getMemory(CobblemonMemories.READY_TO_BUILD_NEST) != true) {
|
||||
brain.sendDebugMessage("[BuildNest] Not ready to build; skipping.")
|
||||
return ObjectiveStatus.INVALID
|
||||
}
|
||||
|
||||
if (entity.distanceToSqr(nestCenter) > 2.0 || mate.distanceToSqr(nestCenter) > 2.0) {
|
||||
brain.sendDebugMessage("[BuildNest] Parents not in range of ideal nest $idealNestPos.")
|
||||
return ObjectiveStatus.INACTIVE
|
||||
}
|
||||
|
||||
val world = entity.level() as? ServerLevel ?: return ObjectiveStatus.INVALID
|
||||
val placement = WildNestTypes.resolvePlacement(
|
||||
combinedTypes,
|
||||
world.getBlockState(idealNestPos.below()),
|
||||
world.getFluidState(idealNestPos)
|
||||
) ?: return ObjectiveStatus.INVALID
|
||||
|
||||
val nestBlock = WildNestTypes.blockFor(placement.type)
|
||||
val nestState = nestBlock.defaultBlockState()
|
||||
.setValue(WildNestBlock.VARIANT, placement.variant)
|
||||
.setValue(BlockStateProperties.WATERLOGGED, placement.waterlogged)
|
||||
|
||||
world.setBlock(idealNestPos, nestState, 3)
|
||||
(world.getBlockEntity(idealNestPos) as NestBlockEntity).nestClaimedBy = Pair(entity.uuid, mate.uuid)
|
||||
brain.sendDebugMessage("[BuildNest] Built nest at $idealNestPos with ${placement.type}/${placement.variant}.")
|
||||
|
||||
brain.eraseMemory(CobblemonMemories.READY_TO_BUILD_NEST)
|
||||
brain.eraseMemory(CobblemonMemories.IDEAL_NEST_LOCATION)
|
||||
mate.biggerBrain.eraseMemory(CobblemonMemories.READY_TO_BUILD_NEST)
|
||||
mate.biggerBrain.eraseMemory(CobblemonMemories.IDEAL_NEST_LOCATION)
|
||||
|
||||
world.players().forEach { player ->
|
||||
player.displayClientMessage("A Pokémon has built a nest!".green(), true)
|
||||
}
|
||||
|
||||
return ObjectiveStatus.ACTIVE
|
||||
}
|
||||
}
|
||||
+146
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.objective
|
||||
|
||||
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
|
||||
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
|
||||
import com.cobblemon.mod.common.api.ai.asVariables
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.Distraction
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.EntityFixation
|
||||
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
|
||||
import com.cobblemon.mod.common.api.ai.resolveFloat
|
||||
import com.cobblemon.mod.common.api.ai.resolveInt
|
||||
import com.cobblemon.mod.common.api.pokemon.feature.FlagSpeciesFeature
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.util.DataKeys
|
||||
import com.cobblemon.mod.common.util.asExpression
|
||||
import com.mojang.datafixers.util.Either
|
||||
import net.minecraft.server.level.ServerLevel
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.level.GameRules
|
||||
import net.minecraft.world.level.block.Block
|
||||
import net.minecraft.world.level.block.Blocks
|
||||
import net.minecraft.world.level.block.state.predicate.BlockStatePredicate
|
||||
|
||||
/**
|
||||
* The baa's eat the graa's
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since January 9th, 2025
|
||||
*/
|
||||
class EatGrass(
|
||||
val cooldownTicks: Int,
|
||||
val eatGrassChance: Float
|
||||
) : Objective {
|
||||
var fixation: EntityFixation? = null
|
||||
val grassPredicate = BlockStatePredicate.forBlock(Blocks.GRASS_BLOCK)
|
||||
var remainingCooldownTicks = cooldownTicks
|
||||
|
||||
override fun getDescription(entity: LivingEntity) = "Eat grass"
|
||||
|
||||
override fun perform(
|
||||
entity: LivingEntity,
|
||||
brain: BiggerBrain,
|
||||
activeObjectiveTags: MutableSet<String>
|
||||
): ObjectiveStatus {
|
||||
if (brain.isFixation(fixation)) {
|
||||
return ObjectiveStatus.FIXATED
|
||||
} else if (brain.fixation != null) {
|
||||
return ObjectiveStatus.BLOCKED
|
||||
} else if (entity !is PokemonEntity) {
|
||||
return ObjectiveStatus.INVALID
|
||||
} else if (remainingCooldownTicks > 0) {
|
||||
remainingCooldownTicks--
|
||||
return ObjectiveStatus.ACTIVE
|
||||
}
|
||||
|
||||
val world = entity.level() as ServerLevel
|
||||
|
||||
entity.pokemon.getFeature<FlagSpeciesFeature>(DataKeys.HAS_BEEN_SHEARED)?.enabled?.takeIf { it } ?: run {
|
||||
return ObjectiveStatus.INACTIVE
|
||||
}
|
||||
|
||||
if (world.random.nextFloat() > eatGrassChance) {
|
||||
return ObjectiveStatus.ACTIVE
|
||||
} else {
|
||||
val blockPos = entity.blockPosition()
|
||||
|
||||
// Are we not even on a grass block? Alright don't bother
|
||||
if (!grassPredicate.test(world.getBlockState(blockPos)) && !world.getBlockState(blockPos.below()).`is`(Blocks.GRASS_BLOCK)) {
|
||||
return ObjectiveStatus.ACTIVE
|
||||
}
|
||||
|
||||
fixation = brain.fixate(EatGrassFixation(this))
|
||||
entity.playAnimation("eat")
|
||||
world.broadcastEntityEvent(entity, 10.toByte())
|
||||
return ObjectiveStatus.FIXATED
|
||||
}
|
||||
}
|
||||
|
||||
class EatGrassFixation(val objective: EatGrass) : EntityFixation {
|
||||
var timer = 40
|
||||
override fun getDescription(entity: LivingEntity) = "Eating grass"
|
||||
override fun canDistract(distraction: Distraction, entity: LivingEntity) = distraction.isComfort || distraction.isDamage || distraction.isForce
|
||||
override fun tick(entity: LivingEntity, brain: BiggerBrain): Boolean {
|
||||
timer--
|
||||
if (timer <= 0) {
|
||||
entity as PokemonEntity
|
||||
val world = entity.level() as ServerLevel
|
||||
val blockPos = entity.blockPosition()
|
||||
if (objective.grassPredicate.test(world.getBlockState(blockPos)) ) {
|
||||
if (world.gameRules.getBoolean(GameRules.RULE_MOBGRIEFING)) {
|
||||
world.destroyBlock(blockPos, false)
|
||||
}
|
||||
entity.ate()
|
||||
objective.remainingCooldownTicks = objective.cooldownTicks
|
||||
} else if (world.getBlockState(blockPos.below()).`is`(Blocks.GRASS_BLOCK)) {
|
||||
val below = blockPos.below()
|
||||
if (world.gameRules.getBoolean(GameRules.RULE_MOBGRIEFING)) {
|
||||
world.levelEvent(2001, below, Block.getId(Blocks.GRASS_BLOCK.defaultBlockState()))
|
||||
world.setBlock(below, Blocks.DIRT.defaultBlockState(), 2)
|
||||
}
|
||||
|
||||
entity.ate()
|
||||
objective.remainingCooldownTicks = objective.cooldownTicks
|
||||
}
|
||||
}
|
||||
return timer > 0
|
||||
}
|
||||
}
|
||||
|
||||
class Config : ObjectiveConfig {
|
||||
var cooldownTicks: ExpressionOrEntityVariable = Either.left("400".asExpression())
|
||||
var eatGrassChance: ExpressionOrEntityVariable = Either.left("1/400".asExpression())
|
||||
|
||||
override fun getVariables(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
) = listOf(
|
||||
cooldownTicks,
|
||||
eatGrassChance
|
||||
).asVariables()
|
||||
|
||||
override fun create(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
): Objective? {
|
||||
val cooldownTicks = cooldownTicks.resolveInt(behaviourConfigurationContext.runtime)
|
||||
val eatGrassChance = eatGrassChance.resolveFloat(behaviourConfigurationContext.runtime)
|
||||
return if (eatGrassChance == 0F) {
|
||||
null
|
||||
} else {
|
||||
EatGrass(
|
||||
cooldownTicks = cooldownTicks,
|
||||
eatGrassChance = eatGrassChance
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+232
@@ -0,0 +1,232 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.objective
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
|
||||
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
|
||||
import com.cobblemon.mod.common.api.ai.asVariables
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.EntityFixation
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.LookingTracker
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.Distraction
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.DistractionType
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.HurtDistraction
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.LockFixation
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.LookingTrackerFixation
|
||||
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
|
||||
import com.cobblemon.mod.common.api.ai.resolveBoolean
|
||||
import com.cobblemon.mod.common.api.ai.resolveInt
|
||||
import com.cobblemon.mod.common.battles.BattleRegistry
|
||||
import com.cobblemon.mod.common.entity.npc.NPCEntity
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.util.asExpression
|
||||
import com.mojang.datafixers.util.Either
|
||||
import net.minecraft.server.level.ServerLevel
|
||||
import net.minecraft.world.damagesource.DamageSource
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.ai.behavior.EntityTracker
|
||||
import net.minecraft.world.entity.ai.memory.MemoryModuleType
|
||||
|
||||
/**
|
||||
* An objective that ensures the entity, whether it's a PokemonEntity or an NPCEntity, will pay attention to its
|
||||
* opponents in battle. They will not move, and will look at its opponents. For Pokémon, they will focus on the
|
||||
* targeted opponent if one exists; otherwise, they will randomly look at one of their opponent Pokémon. For NPCs,
|
||||
* they will randomly look at one of their opponent Pokémon.
|
||||
*
|
||||
* If the entity gets hurt and [stopBattleIfHurt] is true, this objective stops the battle.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since December 29th, 2025
|
||||
*/
|
||||
class FocusDuringBattle(
|
||||
var stopBattleIfHurt: Boolean = true,
|
||||
var minDurationTicks: Int = 40,
|
||||
var maxDurationTicks: Int = 80
|
||||
) : Objective {
|
||||
var fixation: EntityFixation? = null
|
||||
|
||||
override fun getDescription(entity: LivingEntity) = "Look at battle opponents"
|
||||
override fun perform(entity: LivingEntity, brain: BiggerBrain, activeObjectiveTags: MutableSet<String>): ObjectiveStatus {
|
||||
if (entity !is NPCEntity && entity !is PokemonEntity) {
|
||||
return ObjectiveStatus.INVALID
|
||||
}
|
||||
|
||||
if (stopBattleIfHurt && brain.hasMemoryValue(MemoryModuleType.HURT_BY)) {
|
||||
if (brain.isFixation(fixation)) {
|
||||
if (entity is PokemonEntity) {
|
||||
entity.battle?.stop()
|
||||
} else if (entity is NPCEntity) {
|
||||
entity.battleIds.mapNotNull { BattleRegistry.getBattle(it) }.forEach { it.stop() }
|
||||
}
|
||||
brain.distract(Distraction.FORCE)
|
||||
}
|
||||
return ObjectiveStatus.INACTIVE
|
||||
}
|
||||
|
||||
val isInBattle = isInBattle(entity)
|
||||
if (fixation == brain.fixation) {
|
||||
if (isInBattle) {
|
||||
// Ok this is fine
|
||||
return ObjectiveStatus.FIXATED
|
||||
} else {
|
||||
// Alright my work here is done, going back to inactive.
|
||||
fixation = null
|
||||
brain.fixation = null
|
||||
return ObjectiveStatus.INACTIVE
|
||||
}
|
||||
} else if (!isInBattle) {
|
||||
// Ok we don't need to do anything
|
||||
fixation = null
|
||||
return ObjectiveStatus.INACTIVE
|
||||
} else {
|
||||
// We need to start focusing
|
||||
if (brain.distract(Distraction.FORCE)) {
|
||||
fixation = if (entity is NPCEntity) NPCFocusFixation(this) else PokemonFocusFixation(this)
|
||||
brain.fixation = fixation
|
||||
return ObjectiveStatus.FIXATED
|
||||
} else {
|
||||
return ObjectiveStatus.BLOCKED
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun <T> onMemoryChanged(entity: LivingEntity, brain: BiggerBrain, memory: MemoryModuleType<T>, value: T?) {
|
||||
if (brain.isFixation(fixation) && memory == MemoryModuleType.HURT_BY && value != null && stopBattleIfHurt) {
|
||||
brain.distract(HurtDistraction(value as DamageSource))
|
||||
}
|
||||
}
|
||||
|
||||
fun isInBattle(entity: LivingEntity): Boolean {
|
||||
return when (entity) {
|
||||
is PokemonEntity -> entity.isBattling
|
||||
is NPCEntity -> entity.isInBattle()
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
class NPCFocusFixation(val objective: FocusDuringBattle) : EntityFixation, LookingTrackerFixation {
|
||||
override var lookingTracker: LookingTracker? = null
|
||||
var ticksUntilReacquireTarget: Int = 0
|
||||
|
||||
override fun getDescription(entity: LivingEntity): String {
|
||||
return if (lookingTracker == null) {
|
||||
"Looking for an opponent Pokémon to look at"
|
||||
} else {
|
||||
"Looking at targeted or opponent Pokémon"
|
||||
}
|
||||
}
|
||||
|
||||
override fun canDistract(distraction: Distraction, entity: LivingEntity) = distraction.isForce
|
||||
|
||||
fun acquireLookTarget(entity: NPCEntity, brain: BiggerBrain) {
|
||||
val battlingPokemon = brain.getMemory(CobblemonMemories.BATTLING_POKEMON) ?: return
|
||||
val randomBattlingPokemon = battlingPokemon.randomOrNull()?.let { (entity.level() as ServerLevel).getEntity(it) }
|
||||
if (randomBattlingPokemon != null) {
|
||||
lookingTracker = LookingTracker(entity, EntityTracker(randomBattlingPokemon, true))
|
||||
}
|
||||
}
|
||||
|
||||
override fun tick(entity: LivingEntity, brain: BiggerBrain): Boolean {
|
||||
if (entity !is NPCEntity) {
|
||||
return false
|
||||
}
|
||||
|
||||
val battlingPokemon = brain.getMemory(CobblemonMemories.BATTLING_POKEMON) ?: return false
|
||||
|
||||
val lookingTracker = lookingTracker
|
||||
if (lookingTracker != null && lookingTracker.entity.uuid !in battlingPokemon) {
|
||||
this.lookingTracker = null
|
||||
ticksUntilReacquireTarget = 0
|
||||
}
|
||||
|
||||
if (ticksUntilReacquireTarget <= 0) {
|
||||
acquireLookTarget(entity, brain)
|
||||
ticksUntilReacquireTarget = (objective.minDurationTicks..objective.maxDurationTicks).random()
|
||||
} else {
|
||||
ticksUntilReacquireTarget--
|
||||
}
|
||||
|
||||
lookingTracker?.tick()
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
class PokemonFocusFixation(val objective: FocusDuringBattle) : EntityFixation, LookingTrackerFixation, LockFixation {
|
||||
var ticksUntilReacquireTarget: Int = 0
|
||||
override var lookingTracker: LookingTracker? = null
|
||||
|
||||
override fun getDescription(entity: LivingEntity): String {
|
||||
return if (lookingTracker == null) {
|
||||
"Looking for an opponent Pokémon to look at"
|
||||
} else {
|
||||
"Looking at targeted or opponent Pokémon"
|
||||
}
|
||||
}
|
||||
|
||||
override fun canDistract(distraction: Distraction, entity: LivingEntity) = distraction.isForce
|
||||
|
||||
override fun tick(entity: LivingEntity, brain: BiggerBrain): Boolean {
|
||||
if (entity !is PokemonEntity) {
|
||||
return false
|
||||
}
|
||||
|
||||
val battlingPokemon = brain.getMemory(CobblemonMemories.BATTLING_POKEMON) ?: return false
|
||||
val targetedBattlePokemon = brain.getMemory(CobblemonMemories.TARGETED_BATTLE_POKEMON)
|
||||
|
||||
val lookingTracker = lookingTracker
|
||||
// If there is a targetedBattlePokemon then that is the highest priority, and we should not look away
|
||||
// Otherwise, look at one of the entities for a while and occasionally reacquire the target.
|
||||
if (targetedBattlePokemon != null && (lookingTracker == null || lookingTracker.entity.uuid != targetedBattlePokemon)) {
|
||||
// We should be targeting the targetedBattlePokemon
|
||||
val targetedPokemon = (entity.level() as ServerLevel).getEntity(targetedBattlePokemon) ?: return false
|
||||
this.lookingTracker = LookingTracker(entity, EntityTracker(targetedPokemon, true))
|
||||
ticksUntilReacquireTarget = (objective.minDurationTicks..objective.maxDurationTicks).random()
|
||||
} else if (targetedBattlePokemon == null && ticksUntilReacquireTarget <= 0) {
|
||||
// Choose someone to look at
|
||||
val randomBattlingPokemon = battlingPokemon.randomOrNull()?.let { (entity.level() as ServerLevel).getEntity(it) }
|
||||
if (randomBattlingPokemon != null) {
|
||||
this.lookingTracker = LookingTracker(entity, EntityTracker(randomBattlingPokemon, true))
|
||||
ticksUntilReacquireTarget = (objective.minDurationTicks..objective.maxDurationTicks).random()
|
||||
}
|
||||
}
|
||||
|
||||
ticksUntilReacquireTarget--
|
||||
this.lookingTracker?.tick()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
class Config : ObjectiveConfig {
|
||||
var stopBattleIfHurt: ExpressionOrEntityVariable = Either.left("true".asExpression())
|
||||
var minDurationTicks: ExpressionOrEntityVariable = Either.left("40".asExpression())
|
||||
var maxDurationTicks: ExpressionOrEntityVariable = Either.left("80".asExpression())
|
||||
|
||||
override fun getVariables(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
) = listOf(
|
||||
stopBattleIfHurt,
|
||||
minDurationTicks,
|
||||
maxDurationTicks
|
||||
).asVariables()
|
||||
|
||||
override fun create(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): Objective {
|
||||
val stopBattleIfHurt = stopBattleIfHurt.resolveBoolean(behaviourConfigurationContext.runtime)
|
||||
val minDurationTicks = minDurationTicks.resolveInt(behaviourConfigurationContext.runtime)
|
||||
val maxDurationTicks = maxDurationTicks.resolveInt(behaviourConfigurationContext.runtime)
|
||||
return FocusDuringBattle(
|
||||
stopBattleIfHurt = stopBattleIfHurt,
|
||||
minDurationTicks = minDurationTicks,
|
||||
maxDurationTicks = maxDurationTicks
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
+155
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.objective
|
||||
|
||||
import com.bedrockk.molang.Expression
|
||||
import com.cobblemon.mod.common.CobblemonActivities
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
|
||||
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
|
||||
import com.cobblemon.mod.common.api.ai.asSimplifiedExpression
|
||||
import com.cobblemon.mod.common.api.ai.asVariables
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.MovementStatus
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.MovementTracker
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.EntityFixation
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.Distraction
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.MovementTrackerFixation
|
||||
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
|
||||
import com.cobblemon.mod.common.api.pokemon.status.Statuses
|
||||
import com.cobblemon.mod.common.api.storage.party.PartyStore
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.pokemon.status.PersistentStatusContainer
|
||||
import com.cobblemon.mod.common.util.asExpression
|
||||
import com.cobblemon.mod.common.util.getMemorySafely
|
||||
import com.cobblemon.mod.common.util.mainThreadRuntime
|
||||
import com.cobblemon.mod.common.util.resolveBoolean
|
||||
import com.mojang.datafixers.util.Either
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.ai.memory.MemoryModuleType
|
||||
|
||||
/**
|
||||
* Randomly looks at nearby entities out of curiosity. Very easily distracted once doing so.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since December 29th, 2025
|
||||
*/
|
||||
class GoToSleep(
|
||||
val onlyFromStatus: Expression = "false".asExpression(),
|
||||
) : Objective {
|
||||
var fixation: EntityFixation? = null
|
||||
|
||||
override fun getDescription(entity: LivingEntity) = "Sleeping"
|
||||
override fun perform(
|
||||
entity: LivingEntity,
|
||||
brain: BiggerBrain,
|
||||
activeObjectiveTags: MutableSet<String>
|
||||
): ObjectiveStatus {
|
||||
if (entity !is PokemonEntity) {
|
||||
return ObjectiveStatus.INVALID
|
||||
} else if (brain.isFixation(fixation)) {
|
||||
return ObjectiveStatus.FIXATED
|
||||
} else if (brain.fixation != null) {
|
||||
fixation = null
|
||||
return ObjectiveStatus.BLOCKED
|
||||
}
|
||||
|
||||
// Don't sleep if we're not tired
|
||||
brain.getMemory(CobblemonMemories.POKEMON_DROWSY)
|
||||
?: return ObjectiveStatus.INACTIVE
|
||||
|
||||
val hasSleepStatus = entity.pokemon.status?.status === Statuses.SLEEP
|
||||
if (entity.behaviour.resting.canSleep && entity.canSleepAt(entity.blockPosition().below()) || hasSleepStatus) {
|
||||
if (!hasSleepStatus) {
|
||||
if (mainThreadRuntime.resolveBoolean(onlyFromStatus)) {
|
||||
return ObjectiveStatus.BLOCKED
|
||||
}
|
||||
entity.pokemon.status = PersistentStatusContainer(Statuses.SLEEP)
|
||||
}
|
||||
entity.brain.eraseMemory(MemoryModuleType.WALK_TARGET)
|
||||
entity.navigation.stop()
|
||||
entity.brain.setActiveActivityToFirstValid(listOf(CobblemonActivities.POKEMON_SLEEPING_ACTIVITY))
|
||||
entity.brain.setMemory(CobblemonMemories.POKEMON_SLEEPING, true)
|
||||
|
||||
// Become fixated
|
||||
fixation = brain.fixate(
|
||||
SleepFixation(
|
||||
this,
|
||||
null
|
||||
)
|
||||
)
|
||||
return ObjectiveStatus.FIXATED
|
||||
}
|
||||
return ObjectiveStatus.BLOCKED
|
||||
}
|
||||
|
||||
|
||||
|
||||
class SleepFixation(
|
||||
val objective: GoToSleep,
|
||||
override val movementTracker: MovementTracker?,
|
||||
) : EntityFixation, MovementTrackerFixation {
|
||||
|
||||
override fun getDescription(entity: LivingEntity): String {
|
||||
return "Sleeping"
|
||||
}
|
||||
override fun tickMovementFixation(): MovementStatus? {
|
||||
return MovementStatus.PENDING
|
||||
}
|
||||
|
||||
override fun canDistract(distraction: Distraction, entity: LivingEntity): Boolean {
|
||||
if (distraction.isIdleBoredom) {
|
||||
return false
|
||||
}
|
||||
wakeUp(entity)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun tick(entity: LivingEntity, brain: BiggerBrain): Boolean {
|
||||
if (!entity.brain.getMemorySafely(CobblemonMemories.POKEMON_DROWSY).orElse(false)) {
|
||||
wakeUp(entity)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
fun wakeUp(entity: LivingEntity) {
|
||||
if (entity !is PokemonEntity) return
|
||||
val hasSleepStatus = entity.pokemon.status?.status == Statuses.SLEEP
|
||||
if (hasSleepStatus && entity.pokemon.storeCoordinates.get()?.store is PartyStore) {
|
||||
return // You can't wake up if you're in the party with the sleep status.
|
||||
}
|
||||
if (hasSleepStatus) {
|
||||
entity.pokemon.status = null
|
||||
}
|
||||
entity.brain.eraseMemory(CobblemonMemories.POKEMON_SLEEPING)
|
||||
entity.brain.useDefaultActivity()
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
class Config : ObjectiveConfig {
|
||||
|
||||
val onlyFromStatus: ExpressionOrEntityVariable = Either.left("false".asExpression())
|
||||
override fun getVariables(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
) = listOf(
|
||||
onlyFromStatus
|
||||
).asVariables()
|
||||
|
||||
override fun create(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
): Objective {
|
||||
return GoToSleep(
|
||||
onlyFromStatus = onlyFromStatus.asSimplifiedExpression(entity)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
+199
@@ -0,0 +1,199 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.objective
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.RegularMovementTracker
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.comfortDistraction
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.SimpleMoveFixation
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.Objective
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.ObjectiveStatus
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.server.level.ServerLevel
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.PathfinderMob
|
||||
import net.minecraft.world.entity.ai.memory.MemoryModuleType
|
||||
import net.minecraft.world.entity.ai.memory.NearestVisibleLivingEntities
|
||||
import net.minecraft.world.entity.ai.memory.WalkTarget
|
||||
import net.minecraft.world.entity.player.Player
|
||||
import net.minecraft.world.phys.AABB
|
||||
import net.minecraft.world.phys.Vec3
|
||||
|
||||
class GuardNestTask : Objective {
|
||||
private var movementTask: com.cobblemon.mod.common.api.ai.biggerbrain.fixation.EntityFixation? = null
|
||||
private var remainingCooldownTicks = 0
|
||||
private var patrolTarget: Vec3? = null
|
||||
private var warnCooldownTicks = 0
|
||||
private var attackCooldownTicks = 0
|
||||
private var lastWarnedPlayerId: java.util.UUID? = null
|
||||
private var lastAttackedPlayerId: java.util.UUID? = null
|
||||
|
||||
override fun getDescription(entity: LivingEntity) = "Guard nest"
|
||||
|
||||
override fun perform(
|
||||
entity: LivingEntity,
|
||||
brain: BiggerBrain,
|
||||
activeObjectiveTags: MutableSet<String>
|
||||
): ObjectiveStatus {
|
||||
if (entity !is PokemonEntity || !entity.isAlive) return ObjectiveStatus.INVALID
|
||||
if (entity !is PathfinderMob) return ObjectiveStatus.INVALID
|
||||
|
||||
if (warnCooldownTicks > 0) warnCooldownTicks--
|
||||
if (attackCooldownTicks > 0) attackCooldownTicks--
|
||||
|
||||
if (brain.isFixation(movementTask)) {
|
||||
brain.sendDebugMessage("[GuardNest] Fixation active.")
|
||||
return ObjectiveStatus.FIXATED
|
||||
}
|
||||
|
||||
val eggInNest = brain.getMemory(CobblemonMemories.EGG_IN_NEST)
|
||||
if (eggInNest != null && eggInNest == false) {
|
||||
return ObjectiveStatus.INVALID
|
||||
}
|
||||
|
||||
val nestLocation = brain.getMemory(CobblemonMemories.PATERNAL_NEST_LOCATION) ?: return ObjectiveStatus.INVALID
|
||||
val nestCenter = Vec3.atCenterOf(nestLocation)
|
||||
activeObjectiveTags.add(Objective.OBJECTIVE_TAG_WANDERING)
|
||||
|
||||
val visibleEntities = brain.getMemory(MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES) as? NearestVisibleLivingEntities
|
||||
val playerCandidates = if (visibleEntities != null) {
|
||||
visibleEntities
|
||||
.findAll { it is Player && !it.isSpectator && !(it as Player).isCreative }
|
||||
.filterIsInstance<Player>()
|
||||
} else {
|
||||
val searchBox = AABB(
|
||||
nestCenter.x,
|
||||
nestCenter.y,
|
||||
nestCenter.z,
|
||||
nestCenter.x,
|
||||
nestCenter.y,
|
||||
nestCenter.z
|
||||
).inflate(16.0)
|
||||
entity.level().getEntitiesOfClass(Player::class.java, searchBox) { candidate ->
|
||||
!candidate.isSpectator &&
|
||||
!candidate.isCreative &&
|
||||
!candidate.isInvisible &&
|
||||
entity.hasLineOfSight(candidate)
|
||||
}
|
||||
}
|
||||
val player = playerCandidates.firstOrNull()
|
||||
val irritatedAtTarget = brain.getMemory(CobblemonMemories.IRRITATED_AT)
|
||||
|
||||
if (player != null) {
|
||||
val playerDistanceSquared = player.position().distanceToSqr(nestCenter)
|
||||
|
||||
if (playerDistanceSquared <= ATTACK_RADIUS * ATTACK_RADIUS) {
|
||||
if (attackCooldownTicks == 0 || lastAttackedPlayerId != player.uuid) {
|
||||
brain.sendDebugMessage("[GuardNest] Escalation cry at player ${player.name.string}.")
|
||||
entity.cry()
|
||||
(entity.level() as? ServerLevel)?.sendParticles(
|
||||
net.minecraft.core.particles.ParticleTypes.ANGRY_VILLAGER,
|
||||
entity.getRandomX(1.0),
|
||||
entity.getRandomY() + 0.5,
|
||||
entity.getRandomZ(1.0),
|
||||
4 + kotlin.random.Random.nextInt(2),
|
||||
0.2 * entity.random.nextGaussian(),
|
||||
0.1 * entity.random.nextGaussian(),
|
||||
0.2 * entity.random.nextGaussian(),
|
||||
0.02
|
||||
)
|
||||
attackCooldownTicks = ATTACK_COOLDOWN_TICKS
|
||||
lastAttackedPlayerId = player.uuid
|
||||
}
|
||||
brain.sendDebugMessage("[GuardNest] Setting attack target to ${player.name.string}.")
|
||||
brain.setMemory(MemoryModuleType.ANGRY_AT, player.uuid)
|
||||
brain.setMemory(MemoryModuleType.ATTACK_TARGET, player)
|
||||
return ObjectiveStatus.ACTIVE
|
||||
}
|
||||
|
||||
if (playerDistanceSquared <= WARN_RADIUS * WARN_RADIUS) {
|
||||
if (irritatedAtTarget == null) {
|
||||
brain.setMemory(CobblemonMemories.IRRITATED_AT, player.uuid)
|
||||
}
|
||||
if (warnCooldownTicks == 0 || lastWarnedPlayerId != player.uuid) {
|
||||
brain.sendDebugMessage("[GuardNest] Warning cry at player ${player.name.string}.")
|
||||
entity.cry()
|
||||
(entity.level() as? ServerLevel)?.sendParticles(
|
||||
net.minecraft.core.particles.ParticleTypes.ANGRY_VILLAGER,
|
||||
entity.getRandomX(1.0),
|
||||
entity.getRandomY() + 0.5,
|
||||
entity.getRandomZ(1.0),
|
||||
4 + kotlin.random.Random.nextInt(2),
|
||||
0.2 * entity.random.nextGaussian(),
|
||||
0.1 * entity.random.nextGaussian(),
|
||||
0.2 * entity.random.nextGaussian(),
|
||||
0.02
|
||||
)
|
||||
warnCooldownTicks = WARN_COOLDOWN_TICKS
|
||||
lastWarnedPlayerId = player.uuid
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (patrolTarget != null && entity.distanceToSqr(patrolTarget) <= 2.0) {
|
||||
remainingCooldownTicks = 40 + entity.random.nextInt(40)
|
||||
patrolTarget = null
|
||||
return ObjectiveStatus.ACTIVE
|
||||
}
|
||||
|
||||
if (remainingCooldownTicks > 0) {
|
||||
remainingCooldownTicks--
|
||||
return ObjectiveStatus.ACTIVE
|
||||
}
|
||||
|
||||
val world = entity.level() as? ServerLevel ?: return ObjectiveStatus.INVALID
|
||||
val validPosition = findPatrolPosition(world, nestCenter, entity, PATROL_MIN_RADIUS, PATROL_MAX_RADIUS)
|
||||
?: return ObjectiveStatus.ACTIVE
|
||||
patrolTarget = validPosition
|
||||
|
||||
val walkTarget = WalkTarget(validPosition, 1.0F, 1)
|
||||
val movementTracker = RegularMovementTracker.attempt(entity, walkTarget) ?: return ObjectiveStatus.ACTIVE
|
||||
|
||||
if (!brain.distract(comfortDistraction)) return ObjectiveStatus.BLOCKED
|
||||
brain.sendDebugMessage("[GuardNest] Wandering near nest to $validPosition.")
|
||||
movementTask = brain.fixate(SimpleMoveFixation(movementTracker, "Guard nest"))
|
||||
return ObjectiveStatus.ACTIVE
|
||||
}
|
||||
|
||||
private fun findPatrolPosition(
|
||||
world: ServerLevel,
|
||||
center: Vec3,
|
||||
entity: PathfinderMob,
|
||||
minRadius: Int,
|
||||
maxRadius: Int
|
||||
): Vec3? {
|
||||
val random = world.random
|
||||
repeat(20) {
|
||||
val angle = random.nextDouble() * Math.PI * 2
|
||||
val radius = minRadius + random.nextInt(maxRadius - minRadius + 1)
|
||||
val offsetX = kotlin.math.cos(angle) * radius
|
||||
val offsetZ = kotlin.math.sin(angle) * radius
|
||||
val offsetY = random.nextDouble() * 2 - 1
|
||||
val candidate = center.add(offsetX, offsetY, offsetZ)
|
||||
val blockPos = BlockPos(candidate.x.toInt(), candidate.y.toInt(), candidate.z.toInt())
|
||||
if (!world.getBlockState(blockPos).isSolidRender(world, blockPos) &&
|
||||
entity.navigation.isStableDestination(blockPos)
|
||||
) {
|
||||
return Vec3.atCenterOf(blockPos)
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val WARN_RADIUS = 12.0
|
||||
private const val ATTACK_RADIUS = 8.0
|
||||
private const val WARN_COOLDOWN_TICKS = 80
|
||||
private const val ATTACK_COOLDOWN_TICKS = 40
|
||||
private const val PATROL_MIN_RADIUS = 4
|
||||
private const val PATROL_MAX_RADIUS = 8
|
||||
}
|
||||
}
|
||||
+204
@@ -0,0 +1,204 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.objective
|
||||
|
||||
import com.bedrockk.molang.Expression
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
|
||||
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
|
||||
import com.cobblemon.mod.common.api.ai.asSimplifiedExpression
|
||||
import com.cobblemon.mod.common.api.ai.asVariables
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.LookingTracker
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.MovementStatus
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.MovementTracker
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.RegularMovementTracker
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.Distraction
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.idleBoredomDistraction
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.EntityFixation
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.LookingTrackerFixation
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.MovementTrackerFixation
|
||||
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
|
||||
import com.cobblemon.mod.common.api.ai.resolveInt
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.util.asExpression
|
||||
import com.cobblemon.mod.common.util.distanceTo
|
||||
import com.cobblemon.mod.common.util.getMemorySafely
|
||||
import com.cobblemon.mod.common.util.mainThreadRuntime
|
||||
import com.cobblemon.mod.common.util.resolveFloat
|
||||
import com.cobblemon.mod.common.util.withQueryValue
|
||||
import com.mojang.datafixers.util.Either
|
||||
import net.minecraft.sounds.SoundEvents
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.PathfinderMob
|
||||
import net.minecraft.world.entity.ai.memory.WalkTarget
|
||||
import net.minecraft.world.item.ItemStack
|
||||
import net.minecraft.world.item.Items
|
||||
import net.minecraft.world.level.GameRules
|
||||
import net.minecraft.world.level.block.Block
|
||||
import net.minecraft.world.level.block.Blocks
|
||||
import net.minecraft.world.level.block.CaveVines
|
||||
import net.minecraft.world.level.block.SweetBerryBushBlock
|
||||
import net.minecraft.world.level.block.state.BlockState
|
||||
import net.minecraft.world.level.gameevent.GameEvent
|
||||
|
||||
/**
|
||||
* Paths to ripened sweet berry bushes AND glowberry vines with berries.
|
||||
* Upon reaching the target harvests the berries and stashes one in its
|
||||
* held item slot if possible.
|
||||
* Requires NEARBY_SWEET_BERRY_BUSH memory
|
||||
*
|
||||
* @author JazzMcNade
|
||||
* @since February 8th, 2026
|
||||
*/
|
||||
class HarvestSweetBerryBush(
|
||||
val ticksBetweenAttempts: Int = 60,
|
||||
val maxDistance: Expression = "14.0".asExpression(),
|
||||
val speedMultiplier: Expression = "0.4".asExpression()
|
||||
) : Objective {
|
||||
var harvestTask: EntityFixation? = null
|
||||
var remainingCooldownTicks = ticksBetweenAttempts
|
||||
|
||||
override fun getDescription(entity: LivingEntity) = "Harvesting sweet/glow berry"
|
||||
override fun perform(
|
||||
entity: LivingEntity,
|
||||
brain: BiggerBrain,
|
||||
activeObjectiveTags: MutableSet<String>
|
||||
): ObjectiveStatus {
|
||||
if (entity !is PokemonEntity) {
|
||||
return ObjectiveStatus.INVALID
|
||||
} else if (brain.isFixation(harvestTask)) {
|
||||
remainingCooldownTicks = ticksBetweenAttempts
|
||||
return ObjectiveStatus.FIXATED
|
||||
}
|
||||
|
||||
val berryPosition = entity.biggerBrain.getMemory(CobblemonMemories.NEARBY_SWEET_BERRY_BUSH) ?: return ObjectiveStatus.INACTIVE
|
||||
|
||||
if (remainingCooldownTicks > 0) {
|
||||
remainingCooldownTicks--
|
||||
return ObjectiveStatus.ACTIVE
|
||||
}
|
||||
|
||||
mainThreadRuntime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
|
||||
val maxDistance = mainThreadRuntime.resolveFloat(maxDistance)
|
||||
val speedMultiplier = mainThreadRuntime.resolveFloat(speedMultiplier)
|
||||
|
||||
val distance = entity.distanceTo(berryPosition)
|
||||
if (distance < maxDistance) {
|
||||
if (remainingCooldownTicks <= 0 && brain.distract(idleBoredomDistraction)) {
|
||||
remainingCooldownTicks = ticksBetweenAttempts
|
||||
val movementTracker = RegularMovementTracker.attempt(
|
||||
entity = entity,
|
||||
walkTarget = WalkTarget(
|
||||
berryPosition,
|
||||
speedMultiplier,
|
||||
1
|
||||
)
|
||||
)
|
||||
if (movementTracker != null) {
|
||||
harvestTask = brain.fixate(
|
||||
fixation = HarvestSweetBerryBushFixation(
|
||||
entity = entity,
|
||||
movementTracker = movementTracker
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
return ObjectiveStatus.ACTIVE
|
||||
}
|
||||
|
||||
|
||||
class HarvestSweetBerryBushFixation(
|
||||
entity: PathfinderMob,
|
||||
override val movementTracker: MovementTracker,
|
||||
): EntityFixation, LookingTrackerFixation, MovementTrackerFixation {
|
||||
|
||||
|
||||
override val lookingTracker: LookingTracker = LookingTracker(entity = entity, lookTarget = movementTracker.walkTarget.target)
|
||||
override fun getDescription(entity: LivingEntity) = "Harvesting sweet/glow berry"
|
||||
override fun canDistract(distraction: Distraction, entity: LivingEntity): Boolean {
|
||||
return distraction.isDamage || distraction.isForce || (movementTracker.lastStatus == MovementStatus.COMPLETED && entity.brain.getMemorySafely(
|
||||
CobblemonMemories.NEARBY_SWEET_BERRY_BUSH).orElse(null) != null)
|
||||
}
|
||||
|
||||
override fun onDistract(distraction: Distraction, entity: LivingEntity) {
|
||||
movementTracker.stop()
|
||||
}
|
||||
|
||||
override fun tick(entity: LivingEntity, brain: BiggerBrain): Boolean {
|
||||
lookingTracker.tick()
|
||||
movementTracker.tick()
|
||||
|
||||
if (movementTracker.lastStatus == MovementStatus.COMPLETED) {
|
||||
val world = entity.level()
|
||||
|
||||
if (entity !is PokemonEntity) return false
|
||||
|
||||
if (!world.gameRules.getRule(GameRules.RULE_MOBGRIEFING).get()) {
|
||||
// Not allowed to alter the world
|
||||
return false
|
||||
}
|
||||
val blockPos = entity.brain.getMemory(CobblemonMemories.NEARBY_SWEET_BERRY_BUSH).orElse(null) ?: return false
|
||||
val blockState = world.getBlockState(blockPos)
|
||||
if (CaveVines.hasGlowBerries(blockState)) { // Glow Berries Case
|
||||
CaveVines.use(entity, blockState, world, blockPos)
|
||||
} else if (blockState.`is`(Blocks.SWEET_BERRY_BUSH)) { // Sweet Berries Case
|
||||
blockState.setValue(SweetBerryBushBlock.AGE, 1)
|
||||
var j: Int = 1 + entity.level().random.nextInt(2) + if (blockState.getValue<Int>(SweetBerryBushBlock.AGE) == 3) 1 else 0
|
||||
val itemStack: ItemStack = entity.pokemon.heldItem
|
||||
if (itemStack.isEmpty) {
|
||||
entity.pokemon.swapHeldItem(ItemStack(Items.SWEET_BERRIES))
|
||||
--j
|
||||
}
|
||||
if (j > 0) {
|
||||
Block.popResource(entity.level(), blockPos, ItemStack(Items.SWEET_BERRIES, j))
|
||||
}
|
||||
entity.playSound(SoundEvents.SWEET_BERRY_BUSH_PICK_BERRIES, 1.0f, 1.0f)
|
||||
entity.level().setBlock(blockPos, blockState.setValue(SweetBerryBushBlock.AGE, 1) as BlockState, 2)
|
||||
entity.level().gameEvent(GameEvent.BLOCK_CHANGE, blockPos, GameEvent.Context.of(entity))
|
||||
}
|
||||
entity.brain.eraseMemory(CobblemonMemories.NEARBY_SWEET_BERRY_BUSH)
|
||||
return false
|
||||
} else if (movementTracker.lastStatus == MovementStatus.FAILED) {
|
||||
// Cannot reach, give up for a awhile
|
||||
entity.brain.setMemoryWithExpiry(CobblemonMemories.DISABLE_WALK_TO_BERRY_BUSH, true, 200)
|
||||
entity.brain.eraseMemory(CobblemonMemories.NEARBY_SWEET_BERRY_BUSH)
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
class Config : ObjectiveConfig {
|
||||
val ticksBetweenAttempts: ExpressionOrEntityVariable = Either.left("60".asExpression())
|
||||
val maxDistance: ExpressionOrEntityVariable = Either.left("10.0".asExpression())
|
||||
val speedMultiplier: ExpressionOrEntityVariable = Either.left("0.4".asExpression())
|
||||
|
||||
override fun getVariables(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
) = listOf(
|
||||
ticksBetweenAttempts,
|
||||
maxDistance,
|
||||
speedMultiplier
|
||||
).asVariables()
|
||||
|
||||
override fun create(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): Objective {
|
||||
val ticksBetweenAttempts = ticksBetweenAttempts.resolveInt(behaviourConfigurationContext.runtime)
|
||||
return HarvestSweetBerryBush(
|
||||
ticksBetweenAttempts = ticksBetweenAttempts,
|
||||
maxDistance = maxDistance.asSimplifiedExpression(entity),
|
||||
speedMultiplier = speedMultiplier.asSimplifiedExpression(entity)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,409 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.objective
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
|
||||
import com.cobblemon.mod.common.api.ai.CobblemonWanderControl
|
||||
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
|
||||
import com.cobblemon.mod.common.api.ai.asVariables
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.LookingTracker
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.MovementStatus
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.RegularMovementTracker
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.Distraction
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.DistractionType
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.comfortDistraction
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.EntityFixation
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.LookAndMoveFixation
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.MovementTrackerFixation
|
||||
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
|
||||
import com.cobblemon.mod.common.api.ai.resolveFloat
|
||||
import com.cobblemon.mod.common.api.ai.resolveInt
|
||||
import com.cobblemon.mod.common.api.pokemon.status.Statuses
|
||||
import com.cobblemon.mod.common.api.storage.party.PartyStore
|
||||
import com.cobblemon.mod.common.entity.PoseType
|
||||
import com.cobblemon.mod.common.entity.ai.CobblemonWalkTarget
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.entity.pokemon.ai.PokemonMoveControl
|
||||
import com.cobblemon.mod.common.pokemon.ai.ToleratedHerdLeader
|
||||
import com.cobblemon.mod.common.pokemon.status.PersistentStatusContainer
|
||||
import com.cobblemon.mod.common.util.asExpression
|
||||
import com.cobblemon.mod.common.util.toBlockPos
|
||||
import com.mojang.datafixers.util.Either
|
||||
import java.util.UUID
|
||||
import kotlin.math.cos
|
||||
import kotlin.math.sin
|
||||
import net.minecraft.server.level.ServerLevel
|
||||
import net.minecraft.util.Mth
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.ai.memory.MemoryModuleType
|
||||
import net.minecraft.world.entity.ai.memory.NearestVisibleLivingEntities
|
||||
import net.minecraft.world.entity.ai.memory.WalkTarget
|
||||
import net.minecraft.world.phys.Vec3
|
||||
|
||||
class Herd(
|
||||
var checkTicks: Int = 60,
|
||||
var tooFar: Float = 8F,
|
||||
var closeEnough: Float = 4F,
|
||||
/** Chance per tick of following the herd leader. */
|
||||
val chance: Float = 1 / 60F,
|
||||
val pathingCooldownTicks: Int = 80
|
||||
) : Objective {
|
||||
var wanderCenter: CobblemonWanderControl.WanderCenter? = null
|
||||
|
||||
var fixation: EntityFixation? = null
|
||||
var leader: PokemonEntity? = null
|
||||
var ticksUntilMaintainLeader: Int = checkTicks
|
||||
var remainingPathingCooldownTicks = 0
|
||||
|
||||
override fun getDescription(entity: LivingEntity) = "Herd"
|
||||
override fun perform(
|
||||
entity: LivingEntity,
|
||||
brain: BiggerBrain,
|
||||
activeObjectiveTags: MutableSet<String>
|
||||
): ObjectiveStatus {
|
||||
entity as PokemonEntity
|
||||
val moveControl = entity.moveControl as? PokemonMoveControl ?: return ObjectiveStatus.INVALID
|
||||
|
||||
if (remainingPathingCooldownTicks > 0) {
|
||||
remainingPathingCooldownTicks--
|
||||
}
|
||||
|
||||
val level = entity.level() as ServerLevel
|
||||
if (leader == null && brain.hasMemoryValue(CobblemonMemories.HERD_LEADER)) {
|
||||
leader = level.getEntity(brain.getMemory(CobblemonMemories.HERD_LEADER).let(UUID::fromString)) as? PokemonEntity
|
||||
if (leader == null) {
|
||||
brain.eraseMemory(CobblemonMemories.HERD_LEADER)
|
||||
}
|
||||
}
|
||||
|
||||
if (ticksUntilMaintainLeader > 0) {
|
||||
ticksUntilMaintainLeader--
|
||||
} else if (ticksUntilMaintainLeader == 0) {
|
||||
if (brain.fixation == null || brain.isFixation(fixation)) {
|
||||
// Don't maintain the herd leader if we're busy with something else
|
||||
maintainHerdLeader(entity, brain)
|
||||
}
|
||||
ticksUntilMaintainLeader = checkTicks
|
||||
}
|
||||
|
||||
if (leader?.level() != level) {
|
||||
this.leader = null
|
||||
}
|
||||
|
||||
val leader = leader
|
||||
if (leader == null && brain.isFixation(fixation)) {
|
||||
brain.distract(Distraction.FORCE)
|
||||
}
|
||||
|
||||
val wanderControl = brain.getMemory(CobblemonMemories.WANDER_CONTROL)
|
||||
?: CobblemonWanderControl()
|
||||
|
||||
if (this.wanderCenter != null && leader == null) {
|
||||
if (wanderControl.center == this.wanderCenter) {
|
||||
wanderControl.center = null
|
||||
wanderControl.wanderChanceMultiplier = null
|
||||
brain.setMemory(CobblemonMemories.WANDER_CONTROL, wanderControl)
|
||||
}
|
||||
this.wanderCenter = null
|
||||
} else if (leader != null) {
|
||||
wanderControl.center = CobblemonWanderControl.WanderCenter(
|
||||
x = leader.x,
|
||||
y = leader.y,
|
||||
z = leader.z,
|
||||
minRange = 1F,
|
||||
maxRange = tooFar
|
||||
)
|
||||
this.wanderCenter = wanderControl.center
|
||||
wanderControl.wanderChanceMultiplier = 0.25F
|
||||
brain.setMemory(CobblemonMemories.WANDER_CONTROL, wanderControl)
|
||||
}
|
||||
|
||||
leader ?: return ObjectiveStatus.INACTIVE
|
||||
|
||||
if (brain.isFixation(fixation) && fixation is MovementTrackerFixation) {
|
||||
return ObjectiveStatus.FIXATED
|
||||
}
|
||||
|
||||
val leaderFixation = leader.biggerBrain.fixation
|
||||
|
||||
val canMove = brain.canDistract(comfortDistraction) || brain.isFixation(fixation)
|
||||
val isMovingCloser = brain.isFixation(fixation) && fixation is LookAndMoveFixation
|
||||
|
||||
val leaderMoveControl = leader.moveControl as? PokemonMoveControl ?: return ObjectiveStatus.INVALID
|
||||
if (leader.distanceTo(entity) > tooFar) {
|
||||
return when {
|
||||
isMovingCloser -> ObjectiveStatus.FIXATED // Already have the fixation to move closer
|
||||
remainingPathingCooldownTicks > 0 -> manageFixation(brain) // Still cooling down from last pathing attempt
|
||||
!canMove -> ObjectiveStatus.BLOCKED // I'm not able to distract the brain and move closer
|
||||
else -> tryMovingCloser(entity, brain, level, leader) // let's give it a fair shake then
|
||||
}
|
||||
} else if (leader.isFlying() && !entity.isFlying() && !isMovingCloser) {
|
||||
if (!entity.canFly()) {
|
||||
entity.brain.eraseMemory(CobblemonMemories.HERD_LEADER) // We can't go where this guy's going
|
||||
this.leader = null
|
||||
return ObjectiveStatus.INACTIVE
|
||||
} else if (!canMove) {
|
||||
return ObjectiveStatus.BLOCKED
|
||||
}
|
||||
|
||||
return tryMovingCloser(entity, brain, level, leader)
|
||||
} else if (leaderMoveControl.banking && !isMovingCloser) {
|
||||
if (!canMove) {
|
||||
return ObjectiveStatus.BLOCKED
|
||||
}
|
||||
|
||||
if (leader.isInLiquid && !entity.isInLiquid) {
|
||||
if (remainingPathingCooldownTicks > 0) {
|
||||
return manageFixation(brain) // Still cooling down from last pathing attempt
|
||||
}
|
||||
|
||||
// We should get into water asap so we can mimic the banking
|
||||
return tryMovingCloser(entity, brain, level, leader, completionRange = 1)
|
||||
} else {
|
||||
removeSleep(entity, brain)
|
||||
entity.yRot = leader.yRot
|
||||
moveControl.startBanking(
|
||||
forwardBlocksPerTick = leaderMoveControl.bankForwardBlocksPerTick,
|
||||
upwardsBlocksPerTick = leaderMoveControl.bankUpwardsBlocksPerTick,
|
||||
durationTicks = 2,
|
||||
rightDegreesPerTick = 0F // Relying on the above yRot = leader.yRot because we dunno when we first started mimicking the banking
|
||||
)
|
||||
return ObjectiveStatus.FIXATED
|
||||
}
|
||||
} else if (leaderFixation is MovementTrackerFixation && leaderFixation.movementTracker?.lastStatus == MovementStatus.PENDING) {
|
||||
|
||||
if (entity.random.nextFloat() > chance) {
|
||||
return manageFixation(brain) // Not this tick
|
||||
} else if (!canMove) {
|
||||
return ObjectiveStatus.BLOCKED
|
||||
} else if (isMovingCloser) {
|
||||
return ObjectiveStatus.FIXATED // Already moving
|
||||
}
|
||||
|
||||
val leaderWalkTarget = leaderFixation.movementTracker?.walkTarget ?: return manageFixation(brain)
|
||||
|
||||
|
||||
// We're already closer to the walk target than the leader is, so no need to move. If anything we should get out of the way
|
||||
if (entity.distanceToSqr(leaderWalkTarget.target.currentPosition()) + closeEnough < leader.distanceToSqr(leaderWalkTarget.target.currentPosition())) {
|
||||
return manageFixation(brain)
|
||||
}
|
||||
|
||||
// Go to the leader's walk target, roughly.
|
||||
val walkTarget = CobblemonWalkTarget(
|
||||
pos = leaderWalkTarget.target.currentPosition().add(getRandomOffset(entity, level)).toBlockPos(),
|
||||
speedModifier = leaderWalkTarget.speedModifier,
|
||||
nodeTypeFilter = (leaderWalkTarget as? CobblemonWalkTarget)?.nodeTypeFilter ?: { true },
|
||||
completionRange = closeEnough.toInt()
|
||||
)
|
||||
|
||||
return tryMovingCloser(entity, brain, level, leader, walkTarget = walkTarget)
|
||||
} else if (leader.getCurrentPoseType() == PoseType.SLEEP) {
|
||||
// Leader is sleeping, if I'm drowsy and can sleep here then I will sleep too.
|
||||
if (entity.canSleepAt(entity.blockPosition().offset(0, -1, 0)) && brain.getMemory(CobblemonMemories.POKEMON_DROWSY) == true) {
|
||||
if (entity.random.nextFloat() < 1 / 40F) {
|
||||
applySleep(entity, brain)
|
||||
}
|
||||
}
|
||||
} else if (brain.getMemory(CobblemonMemories.POKEMON_SLEEPING) == true) {
|
||||
// Leader is awake, so we should be awake too. Maybe.
|
||||
if (entity.random.nextFloat() < 1 / 15F) {
|
||||
removeSleep(entity, brain)
|
||||
}
|
||||
}
|
||||
|
||||
return manageFixation(brain)
|
||||
}
|
||||
|
||||
fun manageFixation(brain: BiggerBrain): ObjectiveStatus {
|
||||
if (brain.isFixation(fixation)) {
|
||||
return ObjectiveStatus.FIXATED
|
||||
} else if (leader != null) {
|
||||
return ObjectiveStatus.ACTIVE
|
||||
} else {
|
||||
return ObjectiveStatus.INACTIVE
|
||||
}
|
||||
}
|
||||
|
||||
fun tryMovingCloser(
|
||||
entity: PokemonEntity,
|
||||
brain: BiggerBrain,
|
||||
level: ServerLevel,
|
||||
leader: PokemonEntity,
|
||||
walkTarget: WalkTarget? = null,
|
||||
completionRange: Int = closeEnough.toInt()
|
||||
): ObjectiveStatus {
|
||||
val walkTarget = walkTarget ?: CobblemonWalkTarget(
|
||||
pos = leader.position().add(getRandomOffset(entity, level)).toBlockPos(),
|
||||
speedModifier = 0.4F,
|
||||
completionRange = completionRange
|
||||
)
|
||||
|
||||
val movementTracker = RegularMovementTracker.attempt(entity, walkTarget)
|
||||
|
||||
remainingPathingCooldownTicks = pathingCooldownTicks
|
||||
if (movementTracker == null) {
|
||||
// Couldn't find a path, wait a bit before trying again
|
||||
return manageFixation(brain)
|
||||
} else if (!(brain.isFixation(fixation) && brain.distract(Distraction.FORCE)) && !brain.distract(comfortDistraction)) {
|
||||
return manageFixation(brain) // Not likely based on checks run on callers to this function.
|
||||
}
|
||||
|
||||
brain.sendDebugMessage("Moving closer, to ${movementTracker.walkTarget.target.currentBlockPosition()}")
|
||||
|
||||
removeSleep(entity, brain)
|
||||
fixation = brain.fixate(
|
||||
LookAndMoveFixation(
|
||||
distractionTypes = setOf(DistractionType.DAMAGE, DistractionType.COMFORT, DistractionType.FORCE),
|
||||
lookingTracker = LookingTracker.forTargetEntity(entity, leader, trackEyeHeight = true),
|
||||
movementTracker = movementTracker,
|
||||
description = "Moving towards herd leader"
|
||||
)
|
||||
)
|
||||
|
||||
return ObjectiveStatus.FIXATED
|
||||
}
|
||||
|
||||
fun maintainHerdLeader(entity: PokemonEntity, brain: BiggerBrain) {
|
||||
val world = entity.level() as ServerLevel
|
||||
val leader = brain.getMemory(CobblemonMemories.HERD_LEADER)?.let(UUID::fromString).let(world::getEntity) as? PokemonEntity
|
||||
val currentLeaderDefinition = leader?.let { leader ->
|
||||
entity.behaviour.herd.toleratedLeaders.find { it.pokemon.matches(leader) }
|
||||
}
|
||||
if (leader == null || currentLeaderDefinition == null) {
|
||||
entity.brain.eraseMemory(CobblemonMemories.HERD_LEADER)
|
||||
this.leader = null
|
||||
}
|
||||
|
||||
val bestLeader = getBestHerdLeader(
|
||||
currentLeader = if (leader == null || currentLeaderDefinition == null) null else (leader to currentLeaderDefinition),
|
||||
nearbyEntities = brain.getMemory(MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES) ?: return,
|
||||
entity = entity
|
||||
)
|
||||
|
||||
if (bestLeader != null && bestLeader != leader) {
|
||||
bestLeader.brain.eraseMemory(CobblemonMemories.HERD_LEADER)
|
||||
entity.brain.setMemory(CobblemonMemories.HERD_LEADER, bestLeader.uuid.toString())
|
||||
bestLeader.adjustHerdSize(1)
|
||||
leader?.adjustHerdSize(-1)
|
||||
this.leader = bestLeader
|
||||
}
|
||||
}
|
||||
|
||||
fun getRandomOffset(entity: PokemonEntity, level: ServerLevel): Vec3 {
|
||||
val leader = leader ?: return Vec3.ZERO
|
||||
|
||||
val minRange = closeEnough.toDouble()
|
||||
val maxRange = (tooFar - closeEnough / 2).toDouble()
|
||||
val randomDistance = Mth.nextDouble(level.random, minRange, maxRange)
|
||||
val randomAngle = Mth.nextDouble(level.random, 0.0, 2 * Math.PI)
|
||||
|
||||
var offset = Vec3(cos(randomAngle) * randomDistance, 0.0, sin(randomAngle) * randomDistance)
|
||||
|
||||
if ((entity.isInLiquid && leader.isInLiquid) || (entity.isFlying() && leader.isFlying())) {
|
||||
val verticalRange = randomDistance * 0.5
|
||||
val randomY = Mth.nextDouble(level.random, -verticalRange, verticalRange)
|
||||
offset = offset.add(0.0, randomY, 0.0)
|
||||
}
|
||||
|
||||
return offset
|
||||
}
|
||||
|
||||
|
||||
fun applySleep(entity: PokemonEntity, brain: BiggerBrain) {
|
||||
brain.setMemory(CobblemonMemories.POKEMON_SLEEPING, true)
|
||||
entity.pokemon.status = PersistentStatusContainer(Statuses.SLEEP)
|
||||
}
|
||||
|
||||
fun removeSleep(entity: PokemonEntity, brain: BiggerBrain) {
|
||||
if (brain.hasMemoryValue(CobblemonMemories.POKEMON_SLEEPING)) {
|
||||
brain.eraseMemory(CobblemonMemories.POKEMON_SLEEPING)
|
||||
val status = entity.pokemon.status
|
||||
if (status != null && status.status == Statuses.SLEEP) {
|
||||
entity.pokemon.status = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Logic used to choose the best herd leader from nearby visible entities.
|
||||
*
|
||||
* - Higher tier leaders are always preferred.
|
||||
* - Same-tier leaders that are closer are preferred except for when they're the same tier as the current leader.
|
||||
* - We don't want to exceed the target's herd size limit.
|
||||
* - We don't want to choose a leader that is themselves following a leader.
|
||||
* - Some leader definitions allow lower-level Pokémon to lead them.
|
||||
* - Leaders just have to not be in a party - pastured and wild Pokémon can mingle.
|
||||
*/
|
||||
fun getBestHerdLeader(
|
||||
currentLeader: Pair<PokemonEntity, ToleratedHerdLeader>?,
|
||||
nearbyEntities: NearestVisibleLivingEntities,
|
||||
entity: PokemonEntity,
|
||||
): PokemonEntity? {
|
||||
var bestLeader: PokemonEntity? = null
|
||||
val (currentLeaderEntity, currentLeaderDefinition) = currentLeader ?: Pair(null, null)
|
||||
var bestTier = currentLeaderDefinition?.tier?.let { it + 1 } ?: 0 // Only upgrade to a higher tier leader
|
||||
var bestDistance = Float.MAX_VALUE
|
||||
nearbyEntities.findAll {
|
||||
it is PokemonEntity &&
|
||||
it != currentLeader &&
|
||||
it.pokemon.storeCoordinates.get()?.store !is PartyStore &&
|
||||
it.getHerdSize() < it.behaviour.herd.maxSize &&
|
||||
!it.brain.hasMemoryValue(CobblemonMemories.POKEMON_DROWSY)
|
||||
}.forEach { possibleLeader ->
|
||||
possibleLeader as PokemonEntity
|
||||
val bestMatchingHerdLeader = entity.behaviour.herd.bestMatchLeader(entity, possibleLeader) ?: return@forEach
|
||||
val possibleLeaderIsAlreadyALeader = possibleLeader.brain.hasMemoryValue(CobblemonMemories.HERD_SIZE)
|
||||
val currentLeaderHerdTier = currentLeaderEntity?.getHerdTier() ?: entity.getHerdTier()
|
||||
if (currentLeaderHerdTier >= bestMatchingHerdLeader.tier) {
|
||||
return@forEach // This leader is following a leader of a higher tier, we can't replace them
|
||||
}
|
||||
val distance = entity.distanceTo(possibleLeader)
|
||||
if (bestMatchingHerdLeader.tier < bestTier) {
|
||||
return@forEach // No need to check further if this leader is worse than the best found so far
|
||||
} else if ((distance > bestDistance && !possibleLeaderIsAlreadyALeader) && bestMatchingHerdLeader.tier == bestTier) {
|
||||
return@forEach // No need to check further if this leader is farther than the best found so far, unless they are already a leader - I'd prefer to join a pack
|
||||
} else {
|
||||
bestLeader = possibleLeader
|
||||
bestDistance = distance
|
||||
bestTier = bestMatchingHerdLeader.tier
|
||||
}
|
||||
}
|
||||
return bestLeader ?: currentLeaderEntity
|
||||
}
|
||||
|
||||
class Config : ObjectiveConfig {
|
||||
// How frequently to check for whether it should herd. Probably isn't that expensive but might use this for chance.
|
||||
val checkTicks: ExpressionOrEntityVariable = Either.left("60".asExpression())
|
||||
val tooFar: ExpressionOrEntityVariable = Either.left("8".asExpression())
|
||||
val closeEnough: ExpressionOrEntityVariable = Either.left("4".asExpression())
|
||||
val chance: ExpressionOrEntityVariable = Either.left("1 / 60".asExpression())
|
||||
|
||||
override fun getVariables(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
) = listOf(checkTicks, tooFar, closeEnough, chance).asVariables()
|
||||
|
||||
override fun create(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
): Objective {
|
||||
behaviourConfigurationContext.addMemories(MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES)
|
||||
behaviourConfigurationContext.addMemories(CobblemonMemories.HERD_LEADER)
|
||||
behaviourConfigurationContext.addMemories(CobblemonMemories.HERD_SIZE)
|
||||
return Herd(
|
||||
checkTicks = checkTicks.resolveInt(behaviourConfigurationContext.runtime),
|
||||
tooFar = tooFar.resolveFloat(behaviourConfigurationContext.runtime),
|
||||
closeEnough = closeEnough.resolveFloat(behaviourConfigurationContext.runtime),
|
||||
chance = chance.resolveFloat(behaviourConfigurationContext.runtime)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
+136
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.objective
|
||||
|
||||
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
|
||||
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
|
||||
import com.cobblemon.mod.common.api.ai.asVariables
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.EntityFixation
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.LookingTracker
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.Distraction
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.LookingTrackerFixation
|
||||
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
|
||||
import com.cobblemon.mod.common.api.ai.resolveDouble
|
||||
import com.cobblemon.mod.common.api.ai.resolveInt
|
||||
import com.cobblemon.mod.common.util.asExpression
|
||||
import com.mojang.datafixers.util.Either
|
||||
import kotlin.math.pow
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.PathfinderMob
|
||||
import net.minecraft.world.entity.ai.behavior.EntityTracker
|
||||
import net.minecraft.world.entity.ai.memory.MemoryModuleType
|
||||
|
||||
/**
|
||||
* Randomly looks at nearby entities out of curiosity. Very easily distracted once doing so.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since December 29th, 2025
|
||||
*/
|
||||
class LookAtNearbyEntities(
|
||||
val maxDistance: Double = 10.0,
|
||||
val minDuration: Int = 20,
|
||||
val maxDuration: Int = 80
|
||||
) : Objective {
|
||||
var fixation: EntityFixation? = null
|
||||
|
||||
override fun getDescription(entity: LivingEntity) = "Look at nearby entities"
|
||||
override fun perform(
|
||||
entity: LivingEntity,
|
||||
brain: BiggerBrain,
|
||||
activeObjectiveTags: MutableSet<String>
|
||||
): ObjectiveStatus {
|
||||
if (brain.isFixation(fixation)) {
|
||||
return ObjectiveStatus.FIXATED
|
||||
} else if (brain.fixation != null) {
|
||||
fixation = null
|
||||
return ObjectiveStatus.BLOCKED
|
||||
}
|
||||
|
||||
val nearestVisibleLivingEntities = brain.getMemory(MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES)
|
||||
?: return ObjectiveStatus.INVALID
|
||||
|
||||
val closest = nearestVisibleLivingEntities.findClosest(
|
||||
{ otherEntity -> otherEntity.distanceToSqr(entity) <= maxDistance.pow(2) && !entity.hasPassenger(otherEntity) }
|
||||
).orElse(null)
|
||||
|
||||
if (closest == null) {
|
||||
return ObjectiveStatus.INACTIVE
|
||||
} else {
|
||||
// Become fixated
|
||||
fixation = brain.fixate(
|
||||
IdleLookFixation(
|
||||
this,
|
||||
closest,
|
||||
(minDuration..maxDuration).random()
|
||||
)
|
||||
)
|
||||
return ObjectiveStatus.FIXATED
|
||||
}
|
||||
}
|
||||
|
||||
class IdleLookFixation(
|
||||
val objective: LookAtNearbyEntities,
|
||||
val targetEntity: LivingEntity,
|
||||
var remainingTicks: Int
|
||||
) : EntityFixation, LookingTrackerFixation {
|
||||
override var lookingTracker: LookingTracker? = null
|
||||
|
||||
override fun getDescription(entity: LivingEntity): String {
|
||||
return "Looking at a nearby entity ($remainingTicks ticks left)"
|
||||
}
|
||||
|
||||
override fun canDistract(distraction: Distraction, entity: LivingEntity): Boolean {
|
||||
// Bro literally anything will distract me
|
||||
return true
|
||||
}
|
||||
|
||||
override fun tick(entity: LivingEntity, brain: BiggerBrain): Boolean {
|
||||
if (targetEntity.isRemoved || entity !is PathfinderMob) {
|
||||
return false
|
||||
}
|
||||
if (lookingTracker == null) {
|
||||
lookingTracker = LookingTracker(entity, EntityTracker(targetEntity, true))
|
||||
}
|
||||
val lookingTracker = lookingTracker!!
|
||||
remainingTicks--
|
||||
lookingTracker.tick()
|
||||
return remainingTicks > 0
|
||||
}
|
||||
}
|
||||
|
||||
class Config : ObjectiveConfig {
|
||||
val maxDistance: ExpressionOrEntityVariable = Either.left("10.0".asExpression())
|
||||
val minDuration: ExpressionOrEntityVariable = Either.left("20".asExpression())
|
||||
val maxDuration: ExpressionOrEntityVariable = Either.left("80".asExpression())
|
||||
|
||||
override fun getVariables(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
) = listOf(
|
||||
maxDistance,
|
||||
minDuration,
|
||||
maxDuration
|
||||
).asVariables()
|
||||
|
||||
override fun create(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
): Objective {
|
||||
val maxDistance = maxDistance.resolveDouble(behaviourConfigurationContext.runtime)
|
||||
val minDuration = minDuration.resolveInt(behaviourConfigurationContext.runtime)
|
||||
val maxDuration = maxDuration.resolveInt(behaviourConfigurationContext.runtime)
|
||||
return LookAtNearbyEntities(
|
||||
maxDistance = maxDistance,
|
||||
minDuration = minDuration,
|
||||
maxDuration = maxDuration
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.objective
|
||||
|
||||
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.MovementTrackerFixation
|
||||
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
|
||||
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
|
||||
import com.cobblemon.mod.common.entity.OmniPathingEntity
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
|
||||
class MaintainFluidY : Objective {
|
||||
override fun getDescription(entity: LivingEntity) = "Maintain Y in fluids"
|
||||
override fun perform(
|
||||
entity: LivingEntity,
|
||||
brain: BiggerBrain,
|
||||
activeObjectiveTags: MutableSet<String>
|
||||
): ObjectiveStatus {
|
||||
if (entity !is OmniPathingEntity) {
|
||||
return ObjectiveStatus.INVALID
|
||||
}
|
||||
|
||||
val fixation = brain.fixation
|
||||
if (fixation is MovementTrackerFixation) {
|
||||
return ObjectiveStatus.BLOCKED
|
||||
}
|
||||
|
||||
if (entity.isFlying()) {
|
||||
return ObjectiveStatus.INACTIVE
|
||||
}
|
||||
|
||||
if (entity.canSwimInWater() && entity.isUnderWater) {
|
||||
entity.isNoGravity = true
|
||||
} else if (entity.canSwimInLava() && entity.isInLava) {
|
||||
entity.isNoGravity = true
|
||||
} else {
|
||||
entity.isNoGravity = false
|
||||
}
|
||||
|
||||
return if (entity.isNoGravity) ObjectiveStatus.ACTIVE else ObjectiveStatus.INACTIVE
|
||||
}
|
||||
|
||||
class Config : ObjectiveConfig {
|
||||
override fun getVariables(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
): List<MoLangConfigVariable> {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
override fun create(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
): Objective {
|
||||
return MaintainFluidY()
|
||||
}
|
||||
}
|
||||
}
|
||||
+251
@@ -0,0 +1,251 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.objective
|
||||
|
||||
import com.bedrockk.molang.Expression
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.api.ai.*
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.*
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.Distraction
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.comfortDistraction
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.EntityFixation
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.MovementTrackerFixation
|
||||
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
|
||||
import com.cobblemon.mod.common.entity.ai.CobblemonWalkTarget
|
||||
import com.cobblemon.mod.common.util.*
|
||||
import com.mojang.datafixers.util.Either
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.PathfinderMob
|
||||
import net.minecraft.world.entity.TamableAnimal
|
||||
import net.minecraft.world.entity.ai.behavior.BehaviorUtils
|
||||
import net.minecraft.world.entity.ai.util.RandomPos
|
||||
import net.minecraft.world.level.pathfinder.PathType
|
||||
import net.minecraft.world.phys.Vec3
|
||||
|
||||
|
||||
/**
|
||||
* Tries to path into water if the entity usually avoids land.
|
||||
* Will pathfind through land even if the entity usually filters out land nodes.
|
||||
* If water cannot be found in the entity's wander range it'll wander randomly in a spiral-ish pattern
|
||||
*
|
||||
* @author JazzMcNade
|
||||
* @since January 25th, 2026
|
||||
*/
|
||||
class MoveIntoWater(
|
||||
val ticksBetweenAttempts: Int = 60,
|
||||
val maxDistance: Expression = "14.0".asExpression(),
|
||||
val speedMultiplier: Expression = "0.4".asExpression()
|
||||
) : Objective {
|
||||
var movementTask: EntityFixation? = null
|
||||
var remainingCooldownTicks = ticksBetweenAttempts
|
||||
var wanderDirection: Vec3? = null
|
||||
var isWanderTurnDirectionClockwise = true
|
||||
var wanderTurnChance = BASE_TURN_CHANCE
|
||||
var failCount = 0
|
||||
override fun getDescription(entity: LivingEntity) = "Move into water if currently out of water"
|
||||
override fun perform(
|
||||
entity: LivingEntity,
|
||||
brain: BiggerBrain,
|
||||
activeObjectiveTags: MutableSet<String>
|
||||
): ObjectiveStatus {
|
||||
if (entity !is TamableAnimal) {
|
||||
return ObjectiveStatus.INVALID
|
||||
}
|
||||
|
||||
if (entity.isInWater) {
|
||||
// All good, nothing to do here
|
||||
return ObjectiveStatus.INACTIVE
|
||||
}
|
||||
|
||||
if (brain.isFixation(movementTask)) {
|
||||
remainingCooldownTicks = ticksBetweenAttempts
|
||||
return ObjectiveStatus.FIXATED
|
||||
}
|
||||
|
||||
if (remainingCooldownTicks > 0) {
|
||||
remainingCooldownTicks--
|
||||
return ObjectiveStatus.INACTIVE
|
||||
} else {
|
||||
remainingCooldownTicks = ticksBetweenAttempts
|
||||
}
|
||||
|
||||
if (brain.distract(comfortDistraction)) {
|
||||
|
||||
val maxDistance = mainThreadRuntime.resolveInt(maxDistance)
|
||||
val speedMultiplier = mainThreadRuntime.resolveFloat(speedMultiplier)
|
||||
|
||||
val runtime = mainThreadRuntime
|
||||
val wanderControl = entity.brain.getMemorySafely(CobblemonMemories.WANDER_CONTROL).orElse(null)
|
||||
val allowLand = wanderControl?.allowLand
|
||||
?: runtime.resolveBoolean("(q.entity.behaviour.moving.walk.can_walk && q.entity.behaviour.moving.walk.avoids_land) || q.entity.behaviour.moving.fly.can_fly".asExpression())
|
||||
val allowAir = wanderControl?.allowAir
|
||||
?: runtime.resolveBoolean("q.entity.behaviour.moving.fly.can_fly && !q.entity.behaviour.moving.walk.can_walk".asExpression())
|
||||
|
||||
val types = mutableSetOf<WanderType>()
|
||||
if ((allowLand || allowAir)) {
|
||||
types.add(WanderType.WATER)
|
||||
}
|
||||
|
||||
if (types.isEmpty()) {
|
||||
return ObjectiveStatus.INACTIVE
|
||||
}
|
||||
var walkTarget: CobblemonWalkTarget? = null
|
||||
|
||||
val targetVec3 = BehaviorUtils.getRandomSwimmablePos(entity, 10, 7)
|
||||
|
||||
if (targetVec3 != null) {
|
||||
walkTarget = CobblemonWalkTarget(
|
||||
BlockPos.containing(targetVec3.x, targetVec3.y, targetVec3.z),
|
||||
speedMultiplier,
|
||||
0,
|
||||
nodeTypeFilter = { nodeType ->
|
||||
true
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if (walkTarget == null) {
|
||||
if (failCount >= 5) {
|
||||
// No out of water target could be found.
|
||||
// Need to choose a direction and move as far as possible, hoping we randomly find water
|
||||
// Totally possible that a shoreline is *just* outside the entity's wander range and
|
||||
// we choose a direction that's opposite of the shoreline
|
||||
// To counteract this, the pokemon moves in a (very roughly) spiral pattern outward from the origin
|
||||
var wanderPosition: BlockPos? = null
|
||||
if (wanderDirection == null) {
|
||||
val wanderOffset = RandomPos.generateRandomDirection(entity.level().random, maxDistance / 2, 0)
|
||||
wanderDirection = wanderOffset.toVec3d().normalize()
|
||||
isWanderTurnDirectionClockwise = entity.random.nextInt(2) > 0
|
||||
wanderTurnChance = BASE_TURN_CHANCE
|
||||
wanderPosition = entity.blockPosition().offset(wanderOffset.x, wanderOffset.y, wanderOffset.z)
|
||||
} else {
|
||||
|
||||
if (entity.level().random.nextFloat() < wanderTurnChance) {
|
||||
// Turn 30 degrees
|
||||
wanderDirection =
|
||||
wanderDirection!!.yRot(Math.PI.toFloat() / 3.0f * if (isWanderTurnDirectionClockwise) -1f else 1f)
|
||||
wanderTurnChance *= 0.7
|
||||
}
|
||||
val wanderVec = entity.position().add(wanderDirection!!.scale(maxDistance.toDouble()))
|
||||
wanderPosition = BlockPos.containing(wanderVec)
|
||||
|
||||
}
|
||||
val wanderTarget = generateWaterSafePosToward(entity, maxDistance, wanderPosition)
|
||||
|
||||
if (wanderTarget != null) {
|
||||
walkTarget = CobblemonWalkTarget(
|
||||
wanderPosition,
|
||||
speedMultiplier,
|
||||
0,
|
||||
nodeTypeFilter = { nodeType ->
|
||||
true
|
||||
}
|
||||
)
|
||||
} else {
|
||||
return ObjectiveStatus.ACTIVE
|
||||
}
|
||||
} else {
|
||||
failCount++
|
||||
}
|
||||
|
||||
}
|
||||
if (walkTarget != null) {
|
||||
failCount = 0
|
||||
val movementTracker = RegularMovementTracker.attempt(entity, walkTarget)
|
||||
?: return ObjectiveStatus.ACTIVE
|
||||
brain.distract(comfortDistraction)
|
||||
movementTask = brain.fixate(
|
||||
fixation = MoveIntoWaterFixation(
|
||||
movementTracker = movementTracker
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
return ObjectiveStatus.ACTIVE
|
||||
}
|
||||
|
||||
fun generateWaterSafePosToward(
|
||||
mob: PathfinderMob,
|
||||
range: Int,
|
||||
targetPos: BlockPos
|
||||
): BlockPos? {
|
||||
|
||||
for (i in 0..4) {
|
||||
val candidate: BlockPos? = RandomPos.generateRandomPosTowardDirection(
|
||||
mob,
|
||||
range,
|
||||
mob.level().random,
|
||||
targetPos
|
||||
)
|
||||
|
||||
if (candidate == null) continue
|
||||
|
||||
val pathType = mob.navigation.nodeEvaluator.getPathType(mob, candidate)
|
||||
// Don't actually care if it can be reached, only if it's not hazardous
|
||||
if (pathType == PathType.WALKABLE || mob.getPathfindingMalus(pathType) <= 0)
|
||||
return candidate
|
||||
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
companion object {
|
||||
val BASE_TURN_CHANCE = 0.8
|
||||
}
|
||||
|
||||
class MoveIntoWaterFixation(
|
||||
override val movementTracker: MovementTracker,
|
||||
) : EntityFixation, MovementTrackerFixation {
|
||||
override fun getDescription(entity: LivingEntity) = "Moving into water"
|
||||
override fun canDistract(distraction: Distraction, entity: LivingEntity): Boolean {
|
||||
return distraction.isDamage || distraction.isForce || movementTracker.lastStatus == MovementStatus.COMPLETED
|
||||
}
|
||||
|
||||
override fun onDistract(distraction: Distraction, entity: LivingEntity) {
|
||||
movementTracker.stop()
|
||||
}
|
||||
|
||||
override fun tick(entity: LivingEntity, brain: BiggerBrain): Boolean {
|
||||
val movementStatus = tickMovementFixation()
|
||||
if (movementStatus == MovementStatus.COMPLETED) return false
|
||||
if (!entity.isInWater) {
|
||||
return false
|
||||
}
|
||||
return movementStatus != MovementStatus.FAILED
|
||||
}
|
||||
}
|
||||
|
||||
class Config : ObjectiveConfig {
|
||||
val ticksBetweenAttempts: ExpressionOrEntityVariable = Either.left("60".asExpression())
|
||||
val maxDistance: ExpressionOrEntityVariable = Either.left("10.0".asExpression())
|
||||
val speedMultiplier: ExpressionOrEntityVariable = Either.left("0.4".asExpression())
|
||||
|
||||
override fun getVariables(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
) = listOf(
|
||||
ticksBetweenAttempts,
|
||||
maxDistance,
|
||||
speedMultiplier
|
||||
).asVariables()
|
||||
|
||||
override fun create(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
): Objective {
|
||||
val ticksBetweenAttempts = ticksBetweenAttempts.resolveInt(behaviourConfigurationContext.runtime)
|
||||
return MoveIntoWater(
|
||||
ticksBetweenAttempts = ticksBetweenAttempts,
|
||||
maxDistance = maxDistance.asSimplifiedExpression(entity),
|
||||
speedMultiplier = speedMultiplier.asSimplifiedExpression(entity)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
+258
@@ -0,0 +1,258 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.objective
|
||||
|
||||
import com.bedrockk.molang.Expression
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.api.ai.*
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.*
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.Distraction
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.comfortDistraction
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.EntityFixation
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.MovementTrackerFixation
|
||||
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
|
||||
import com.cobblemon.mod.common.entity.ai.CobblemonWalkTarget
|
||||
import com.cobblemon.mod.common.util.*
|
||||
import com.mojang.datafixers.util.Either
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.PathfinderMob
|
||||
import net.minecraft.world.entity.TamableAnimal
|
||||
import net.minecraft.world.entity.ai.util.LandRandomPos
|
||||
import net.minecraft.world.entity.ai.util.RandomPos
|
||||
import net.minecraft.world.level.pathfinder.PathType
|
||||
import net.minecraft.world.phys.Vec3
|
||||
|
||||
|
||||
/**
|
||||
* Tries to path out of water if the entity usually water to avoid water.
|
||||
* Will pathfind through water even if the entity usually filters out water path nodes.
|
||||
* If land cannot be found in the entity's wander range it'll wander randomly in a spiral-ish pattern
|
||||
*
|
||||
* @author JazzMcNade
|
||||
* @since January 17th, 2026
|
||||
*/
|
||||
class MoveOutOfWater(
|
||||
val ticksBetweenAttempts: Int = 60,
|
||||
val maxDistance: Expression = "14.0".asExpression(),
|
||||
val speedMultiplier: Expression = "0.4".asExpression()
|
||||
) : Objective {
|
||||
var movementTask: EntityFixation? = null
|
||||
var remainingCooldownTicks = ticksBetweenAttempts
|
||||
var wanderDirection: Vec3? = null
|
||||
var isWanderTurnDirectionClockwise = true
|
||||
var wanderTurnChance = BASE_TURN_CHANCE
|
||||
var failCount = 0
|
||||
override fun getDescription(entity: LivingEntity) = "Move out of water"
|
||||
override fun perform(
|
||||
entity: LivingEntity,
|
||||
brain: BiggerBrain,
|
||||
activeObjectiveTags: MutableSet<String>
|
||||
): ObjectiveStatus {
|
||||
if (entity !is TamableAnimal) {
|
||||
return ObjectiveStatus.INVALID
|
||||
}
|
||||
|
||||
if (brain.isFixation(movementTask)) {
|
||||
remainingCooldownTicks = ticksBetweenAttempts
|
||||
return ObjectiveStatus.FIXATED
|
||||
}
|
||||
if (!entity.isInWater || entity.isUnderWater) {
|
||||
// Focus on not drowning first
|
||||
// TODO: 100% boned if in an underwater cave
|
||||
return ObjectiveStatus.INACTIVE
|
||||
}
|
||||
|
||||
|
||||
if (remainingCooldownTicks > 0) {
|
||||
remainingCooldownTicks--
|
||||
return ObjectiveStatus.INACTIVE
|
||||
} else {
|
||||
remainingCooldownTicks = ticksBetweenAttempts
|
||||
}
|
||||
|
||||
val maxDistance = mainThreadRuntime.resolveInt(maxDistance)
|
||||
val speedMultiplier = mainThreadRuntime.resolveFloat(speedMultiplier)
|
||||
|
||||
val runtime = mainThreadRuntime
|
||||
val wanderControl = entity.brain.getMemorySafely(CobblemonMemories.WANDER_CONTROL).orElse(null)
|
||||
val allowLand = wanderControl?.allowLand
|
||||
?: runtime.resolveBoolean("(q.entity.behaviour.moving.walk.can_walk && !q.entity.behaviour.moving.walk.avoids_land) || q.entity.behaviour.moving.fly.can_fly".asExpression())
|
||||
val allowAir = wanderControl?.allowAir
|
||||
?: runtime.resolveBoolean("q.entity.behaviour.moving.fly.can_fly && !q.entity.behaviour.moving.walk.can_walk".asExpression())
|
||||
|
||||
val types = mutableSetOf<WanderType>()
|
||||
if ((allowLand || allowAir)) {
|
||||
types.add(WanderType.LAND)
|
||||
}
|
||||
|
||||
|
||||
if (types.isEmpty()) {
|
||||
return ObjectiveStatus.INACTIVE
|
||||
}
|
||||
|
||||
if (brain.distract(comfortDistraction)) {
|
||||
var walkTarget: CobblemonWalkTarget? = null
|
||||
val targetVec3 = LandRandomPos.getPos(entity, maxDistance, maxDistance)
|
||||
if (targetVec3 != null) {
|
||||
walkTarget = CobblemonWalkTarget(
|
||||
BlockPos.containing(targetVec3.x, targetVec3.y, targetVec3.z),
|
||||
speedMultiplier,
|
||||
0,
|
||||
nodeTypeFilter = { nodeType ->
|
||||
true
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if (walkTarget == null) {
|
||||
if (failCount >= 5) {
|
||||
// No out of water target black with a malus of 0 could be found.
|
||||
// Need to choose a direction and move as far as possible, hoping we randomly find land
|
||||
// Totally possible that a shoreline is *just* outside of the entity's wander range and
|
||||
// we choose a direction that's opposite of the shoreline
|
||||
// To counteract this, the pokemon moves in a (very roughly) spiral pattern outward from the origin
|
||||
var wanderPosition: BlockPos? = null
|
||||
if (wanderDirection == null) {
|
||||
val wanderOffset = RandomPos.generateRandomDirection(entity.level().random, maxDistance / 2, 0)
|
||||
wanderDirection = wanderOffset.toVec3d().normalize()
|
||||
isWanderTurnDirectionClockwise = entity.random.nextInt(2) > 0
|
||||
wanderTurnChance = BASE_TURN_CHANCE
|
||||
wanderPosition = entity.blockPosition().offset(wanderOffset.x, wanderOffset.y, wanderOffset.z)
|
||||
} else {
|
||||
|
||||
if (entity.level().random.nextFloat() < wanderTurnChance) {
|
||||
// Turn 30 degrees
|
||||
wanderDirection =
|
||||
wanderDirection!!.yRot(Math.PI.toFloat() / 3.0f * if (isWanderTurnDirectionClockwise) -1f else 1f)
|
||||
wanderTurnChance *= 0.7
|
||||
}
|
||||
val wanderVec = entity.position().add(wanderDirection!!.scale(maxDistance.toDouble()))
|
||||
wanderPosition = BlockPos.containing(wanderVec)
|
||||
|
||||
}
|
||||
val wanderTarget = generateWaterSafePosToward(entity, maxDistance, wanderPosition)
|
||||
|
||||
if (wanderTarget != null) {
|
||||
walkTarget = CobblemonWalkTarget(
|
||||
wanderPosition,
|
||||
speedMultiplier,
|
||||
0,
|
||||
nodeTypeFilter = { nodeType ->
|
||||
true
|
||||
}
|
||||
)
|
||||
} else {
|
||||
return ObjectiveStatus.ACTIVE
|
||||
}
|
||||
} else {
|
||||
failCount++
|
||||
}
|
||||
|
||||
}
|
||||
if (walkTarget != null) {
|
||||
failCount = 0
|
||||
val movementTracker = RegularMovementTracker.attempt(entity, walkTarget)
|
||||
?: return ObjectiveStatus.ACTIVE
|
||||
movementTask = brain.fixate(
|
||||
fixation = StayOutOfWaterFixation(
|
||||
movementTracker = movementTracker
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
return ObjectiveStatus.ACTIVE
|
||||
}
|
||||
|
||||
fun generateWaterSafePosToward(
|
||||
mob: PathfinderMob,
|
||||
range: Int,
|
||||
targetPos: BlockPos
|
||||
): BlockPos? {
|
||||
|
||||
for (i in 0..4) {
|
||||
val candidate: BlockPos? = RandomPos.generateRandomPosTowardDirection(
|
||||
mob,
|
||||
range,
|
||||
mob.level().random,
|
||||
targetPos
|
||||
)
|
||||
|
||||
if (candidate == null) continue
|
||||
|
||||
val pathType = mob.navigation.nodeEvaluator.getPathType(mob, candidate)
|
||||
// Don't actually care if it can be reached, only if it's not hazardous
|
||||
if (pathType == PathType.WATER || mob.getPathfindingMalus(pathType) <= 0)
|
||||
return candidate
|
||||
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val BASE_TURN_CHANCE = 0.8
|
||||
}
|
||||
|
||||
class StayOutOfWaterFixation(
|
||||
override val movementTracker: MovementTracker,
|
||||
) : EntityFixation, MovementTrackerFixation {
|
||||
var ticksOutOfWater = 0
|
||||
override fun getDescription(entity: LivingEntity) = "Moving out of water"
|
||||
override fun canDistract(distraction: Distraction, entity: LivingEntity): Boolean {
|
||||
// Easily distracted once we are no longer touching water.
|
||||
return distraction.isDamage || distraction.isForce || movementTracker.lastStatus == MovementStatus.COMPLETED
|
||||
}
|
||||
|
||||
override fun onDistract(distraction: Distraction, entity: LivingEntity) {
|
||||
movementTracker.stop()
|
||||
}
|
||||
|
||||
override fun tick(entity: LivingEntity, brain: BiggerBrain): Boolean {
|
||||
val movementStatus = tickMovementFixation()
|
||||
if (movementStatus == MovementStatus.COMPLETED) return false
|
||||
if (entity.isInWater) {
|
||||
ticksOutOfWater = 0
|
||||
} else {
|
||||
ticksOutOfWater++
|
||||
if (ticksOutOfWater > 20) {
|
||||
// Made it out of water, job done
|
||||
return false
|
||||
}
|
||||
}
|
||||
return movementStatus != MovementStatus.FAILED
|
||||
}
|
||||
}
|
||||
|
||||
class Config : ObjectiveConfig {
|
||||
val ticksBetweenAttempts: ExpressionOrEntityVariable = Either.left("60".asExpression())
|
||||
val maxDistance: ExpressionOrEntityVariable = Either.left("10.0".asExpression())
|
||||
val speedMultiplier: ExpressionOrEntityVariable = Either.left("0.4".asExpression())
|
||||
|
||||
override fun getVariables(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
) = listOf(
|
||||
ticksBetweenAttempts,
|
||||
maxDistance,
|
||||
speedMultiplier
|
||||
).asVariables()
|
||||
|
||||
override fun create(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
): Objective {
|
||||
val ticksBetweenAttempts = ticksBetweenAttempts.resolveInt(behaviourConfigurationContext.runtime)
|
||||
return MoveOutOfWater(
|
||||
ticksBetweenAttempts = ticksBetweenAttempts,
|
||||
maxDistance = maxDistance.asSimplifiedExpression(entity),
|
||||
speedMultiplier = speedMultiplier.asSimplifiedExpression(entity)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
+166
@@ -0,0 +1,166 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.objective
|
||||
|
||||
import com.bedrockk.molang.Expression
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.api.ai.*
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.*
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.Distraction
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.comfortDistraction
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.EntityFixation
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.MovementTrackerFixation
|
||||
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
|
||||
import com.cobblemon.mod.common.api.pokemon.status.Statuses
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.util.*
|
||||
import com.mojang.datafixers.util.Either
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.ai.memory.WalkTarget
|
||||
import net.minecraft.world.phys.AABB
|
||||
|
||||
|
||||
/**
|
||||
* Looks for a resting place if the Pokemon is drowsy, and if available paths to it.
|
||||
*
|
||||
* @author JazzMcNade
|
||||
* @since February 3rd, 2026
|
||||
*/
|
||||
class MoveToRestingPlace(
|
||||
val ticksBetweenAttempts: Int = 80,
|
||||
val horizontalSearchDistance: Expression = "14.0".asExpression(),
|
||||
val verticalSearchDistance: Expression = "6.0".asExpression(),
|
||||
val onlyFromStatus: Expression = "false".asExpression(),
|
||||
val speedMultiplier: Expression = "0.4".asExpression()
|
||||
) : Objective {
|
||||
var movementTask: EntityFixation? = null
|
||||
var remainingCooldownTicks = ticksBetweenAttempts
|
||||
override fun getDescription(entity: LivingEntity) = "Moving to a resting place"
|
||||
override fun perform(
|
||||
entity: LivingEntity,
|
||||
brain: BiggerBrain,
|
||||
activeObjectiveTags: MutableSet<String>
|
||||
): ObjectiveStatus {
|
||||
if (entity !is PokemonEntity) {
|
||||
return ObjectiveStatus.INVALID
|
||||
}
|
||||
brain.getMemory(CobblemonMemories.POKEMON_DROWSY)
|
||||
?: return ObjectiveStatus.INACTIVE
|
||||
|
||||
|
||||
if (brain.isFixation(movementTask)) {
|
||||
remainingCooldownTicks = ticksBetweenAttempts
|
||||
return ObjectiveStatus.FIXATED
|
||||
}
|
||||
|
||||
if (remainingCooldownTicks > 0) {
|
||||
remainingCooldownTicks--
|
||||
}
|
||||
|
||||
val hasSleepStatus = entity.pokemon.status?.status === Statuses.SLEEP
|
||||
if (hasSleepStatus) return ObjectiveStatus.INACTIVE // Already sleeping
|
||||
|
||||
if (entity.behaviour.resting.canSleep) {
|
||||
if (mainThreadRuntime.resolveBoolean(onlyFromStatus)) {
|
||||
return ObjectiveStatus.BLOCKED
|
||||
}
|
||||
|
||||
if (remainingCooldownTicks > 0 || entity.canSleepAt(entity.blockPosition().below())) {
|
||||
// Can sleep where we're at, no need to go anywhere
|
||||
return ObjectiveStatus.INACTIVE
|
||||
}
|
||||
|
||||
val hSearchDistance = mainThreadRuntime.resolveDouble(horizontalSearchDistance)
|
||||
val vSearchDistance = mainThreadRuntime.resolveDouble(verticalSearchDistance)
|
||||
|
||||
val positionList = entity.level()
|
||||
.getBlockPositions(AABB.ofSize(entity.position(), hSearchDistance, vSearchDistance, hSearchDistance))
|
||||
.filter(entity::canSleepAt)
|
||||
val position = if (positionList.isEmpty()) null else positionList.random(1).firstOrNull()
|
||||
|
||||
if (position != null && brain.distract(comfortDistraction)) {
|
||||
val speedMultiplier = mainThreadRuntime.resolveFloat(speedMultiplier)
|
||||
val movementTracker = RegularMovementTracker.attempt(
|
||||
entity = entity,
|
||||
walkTarget = WalkTarget(
|
||||
position,
|
||||
speedMultiplier,
|
||||
0
|
||||
)
|
||||
)
|
||||
if (movementTracker != null) {
|
||||
// Become fixated
|
||||
movementTask = brain.fixate(
|
||||
fixation = MoveToRestingPlaceFixation(
|
||||
movementTracker = movementTracker,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
} else {
|
||||
return ObjectiveStatus.INACTIVE
|
||||
}
|
||||
return ObjectiveStatus.ACTIVE
|
||||
}
|
||||
return ObjectiveStatus.INACTIVE
|
||||
}
|
||||
|
||||
|
||||
class MoveToRestingPlaceFixation(
|
||||
override val movementTracker: MovementTracker,
|
||||
) : EntityFixation, MovementTrackerFixation {
|
||||
override fun getDescription(entity: LivingEntity) = "Moving to a resting place"
|
||||
override fun canDistract(distraction: Distraction, entity: LivingEntity): Boolean {
|
||||
return distraction.isDamage || distraction.isForce || movementTracker.lastStatus == MovementStatus.COMPLETED
|
||||
}
|
||||
|
||||
override fun onDistract(distraction: Distraction, entity: LivingEntity) {
|
||||
movementTracker.stop()
|
||||
}
|
||||
|
||||
override fun tick(entity: LivingEntity, brain: BiggerBrain): Boolean {
|
||||
val movementStatus = tickMovementFixation()
|
||||
if (movementStatus == MovementStatus.COMPLETED) return false
|
||||
return movementStatus != MovementStatus.FAILED
|
||||
}
|
||||
}
|
||||
|
||||
class Config : ObjectiveConfig {
|
||||
val ticksBetweenAttempts: ExpressionOrEntityVariable = Either.left("60".asExpression())
|
||||
val horizontalSearchDistance: ExpressionOrEntityVariable = Either.left("10.0".asExpression())
|
||||
val verticalSearchDistance: ExpressionOrEntityVariable = Either.left("10.0".asExpression())
|
||||
val speedMultiplier: ExpressionOrEntityVariable = Either.left("0.4".asExpression())
|
||||
val onlyFromStatus: ExpressionOrEntityVariable = Either.left("false".asExpression())
|
||||
|
||||
override fun getVariables(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
) = listOf(
|
||||
ticksBetweenAttempts,
|
||||
horizontalSearchDistance,
|
||||
verticalSearchDistance,
|
||||
speedMultiplier,
|
||||
onlyFromStatus
|
||||
).asVariables()
|
||||
|
||||
override fun create(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
): Objective {
|
||||
val ticksBetweenAttempts = ticksBetweenAttempts.resolveInt(behaviourConfigurationContext.runtime)
|
||||
return MoveToRestingPlace(
|
||||
ticksBetweenAttempts = ticksBetweenAttempts,
|
||||
horizontalSearchDistance = horizontalSearchDistance.asSimplifiedExpression(entity),
|
||||
verticalSearchDistance = verticalSearchDistance.asSimplifiedExpression(entity),
|
||||
speedMultiplier = speedMultiplier.asSimplifiedExpression(entity),
|
||||
onlyFromStatus = onlyFromStatus.asSimplifiedExpression(entity)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.objective
|
||||
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.ai.memory.MemoryModuleType
|
||||
|
||||
/**
|
||||
* An ongoing priority for an entity. Objectives are managed by the [BiggerBrain] system. Objectives can act out
|
||||
* entirely within their own logic, or they can alter memories or create fixations for the entity to take up its
|
||||
* entire attention.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since December 29th, 2025
|
||||
*/
|
||||
interface Objective {
|
||||
companion object {
|
||||
/**
|
||||
* The idea behind this is that later objectives may like to know or respect that something impacting wandering
|
||||
* has taken place.
|
||||
*/
|
||||
val OBJECTIVE_TAG_WANDERING = "WANDERING"
|
||||
}
|
||||
|
||||
/** The memory types that, upon being changed, should trigger [onMemoryChanged]. */
|
||||
val observedMemories: List<MemoryModuleType<*>>
|
||||
get() = emptyList()
|
||||
|
||||
/** Gets a current description for this objective. This is purely used for the debug overlay. */
|
||||
fun getDescription(entity: LivingEntity): String
|
||||
/** Performs this objective's logic for a tick. */
|
||||
fun perform(entity: LivingEntity, brain: BiggerBrain, activeObjectiveTags: MutableSet<String>): ObjectiveStatus
|
||||
|
||||
/**
|
||||
* A subscription handler called for when a memory is set or removed (not expired). For a memory to trigger this
|
||||
* function, it must be in [observedMemories].
|
||||
*/
|
||||
fun <T> onMemoryChanged(entity: LivingEntity, brain: BiggerBrain, memory: MemoryModuleType<T>, value: T?) {
|
||||
// Default implementation does nothing
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.objective
|
||||
|
||||
/**
|
||||
* The current status of an [Objective]. Used for the debug overlay.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since December 29th, 2025
|
||||
*/
|
||||
enum class ObjectiveStatus {
|
||||
/** States that this objective owns the current fixation. */
|
||||
FIXATED,
|
||||
/** States that the objective's logic is ongoing and not impeded. */
|
||||
ACTIVE,
|
||||
/** States that the objective is not performing any meaningful logic right now but isn't actively blocked. */
|
||||
INACTIVE,
|
||||
/** States that there are other objectives, fixations, etc. in play which prevents the objective from being performed. */
|
||||
BLOCKED,
|
||||
/** States that the objective is not valid and shouldn't have been registered on the entity in the first place, such as when placed on the wrong entity type. */
|
||||
INVALID
|
||||
}
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.objective
|
||||
|
||||
import com.bedrockk.molang.Expression
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.RegularMovementTracker
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.comfortDistraction
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.SimpleMoveFixation
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.Objective
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.ObjectiveStatus
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.util.mainThreadRuntime
|
||||
import com.cobblemon.mod.common.util.resolveFloat
|
||||
import com.cobblemon.mod.common.util.withQueryValue
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.PathfinderMob
|
||||
import net.minecraft.world.entity.ai.memory.WalkTarget
|
||||
import net.minecraft.world.phys.Vec3
|
||||
|
||||
class PathToIdealNestLocationTask(
|
||||
private val speedMultiplier: Expression
|
||||
) : Objective {
|
||||
private var movementTask: com.cobblemon.mod.common.api.ai.biggerbrain.fixation.EntityFixation? = null
|
||||
|
||||
override fun getDescription(entity: LivingEntity) = "Path to ideal nest"
|
||||
|
||||
override fun perform(
|
||||
entity: LivingEntity,
|
||||
brain: BiggerBrain,
|
||||
activeObjectiveTags: MutableSet<String>
|
||||
): ObjectiveStatus {
|
||||
if (entity !is PokemonEntity || entity !is PathfinderMob || !entity.isAlive) return ObjectiveStatus.INVALID
|
||||
|
||||
if (brain.isFixation(movementTask)) {
|
||||
brain.sendDebugMessage("[PathToIdealNest] Fixation active.")
|
||||
return ObjectiveStatus.FIXATED
|
||||
}
|
||||
|
||||
if (brain.getMemory(CobblemonMemories.PATERNAL_NEST_LOCATION) != null) {
|
||||
brain.sendDebugMessage("[PathToIdealNest] Already a nest parent; skipping.")
|
||||
return ObjectiveStatus.INVALID
|
||||
}
|
||||
|
||||
if (entity.pokemon.breedingCooldown > 0) {
|
||||
brain.sendDebugMessage("[PathToIdealNest] Breeding cooldown active; skipping.")
|
||||
return ObjectiveStatus.INVALID
|
||||
}
|
||||
|
||||
if (brain.getMemory(CobblemonMemories.READY_TO_BUILD_NEST) != true) {
|
||||
brain.sendDebugMessage("[PathToIdealNest] Not ready to build.")
|
||||
return ObjectiveStatus.INVALID
|
||||
}
|
||||
|
||||
val mate = brain.getMemory(CobblemonMemories.POTENTIAL_MATE) as? PokemonEntity ?: return ObjectiveStatus.INVALID
|
||||
if (mate.pokemon.breedingCooldown > 0) {
|
||||
brain.sendDebugMessage("[PathToIdealNest] Mate breeding cooldown active; skipping.")
|
||||
return ObjectiveStatus.INVALID
|
||||
}
|
||||
val nestPos: BlockPos = brain.getMemory(CobblemonMemories.IDEAL_NEST_LOCATION) ?: return ObjectiveStatus.INVALID
|
||||
val nestCenter = Vec3.atCenterOf(nestPos)
|
||||
|
||||
if (entity.distanceToSqr(nestCenter) <= 2.0) {
|
||||
brain.sendDebugMessage("[PathToIdealNest] Already at ideal nest location $nestPos.")
|
||||
return ObjectiveStatus.INACTIVE
|
||||
}
|
||||
|
||||
mainThreadRuntime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
|
||||
val speed = mainThreadRuntime.resolveFloat(speedMultiplier)
|
||||
|
||||
if (!brain.distract(comfortDistraction)) return ObjectiveStatus.BLOCKED
|
||||
|
||||
val movementTracker = RegularMovementTracker.attempt(entity, WalkTarget(nestCenter, speed, 1))
|
||||
?: return ObjectiveStatus.ACTIVE
|
||||
brain.sendDebugMessage("[PathToIdealNest] Moving to ideal nest location $nestPos.")
|
||||
movementTask = brain.fixate(SimpleMoveFixation(movementTracker, "Move to ideal nest"))
|
||||
|
||||
moveMateTo(mate, nestCenter, speed)
|
||||
|
||||
return ObjectiveStatus.ACTIVE
|
||||
}
|
||||
|
||||
private fun moveMateTo(mate: PokemonEntity, target: Vec3, speed: Float) {
|
||||
val mateBrain = mate.biggerBrain
|
||||
if (!mateBrain.distract(comfortDistraction)) return
|
||||
val tracker = RegularMovementTracker.attempt(mate, WalkTarget(target, speed, 1)) ?: return
|
||||
mateBrain.fixate(SimpleMoveFixation(tracker, "Move to ideal nest (mate)"))
|
||||
}
|
||||
}
|
||||
+173
@@ -0,0 +1,173 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.objective
|
||||
|
||||
import com.bedrockk.molang.Expression
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.RegularMovementTracker
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.comfortDistraction
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.SimpleMoveFixation
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.Objective
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.ObjectiveStatus
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
|
||||
import com.cobblemon.mod.common.api.nest.WildNestType
|
||||
import com.cobblemon.mod.common.api.nest.WildNestTypes
|
||||
import com.cobblemon.mod.common.block.WildNestBlock
|
||||
import com.cobblemon.mod.common.block.entity.NestBlockEntity
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.util.mainThreadRuntime
|
||||
import com.cobblemon.mod.common.util.resolveFloat
|
||||
import com.cobblemon.mod.common.util.withQueryValue
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.server.level.ServerLevel
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.PathfinderMob
|
||||
import net.minecraft.world.entity.ai.memory.WalkTarget
|
||||
import net.minecraft.world.item.ItemStack
|
||||
import net.minecraft.world.phys.Vec3
|
||||
import kotlin.jvm.optionals.getOrNull
|
||||
|
||||
class PathToNestTask(
|
||||
private val speedMultiplier: Expression
|
||||
) : Objective {
|
||||
private var movementTask: com.cobblemon.mod.common.api.ai.biggerbrain.fixation.EntityFixation? = null
|
||||
|
||||
override fun getDescription(entity: LivingEntity) = "Path to nest"
|
||||
|
||||
override fun perform(
|
||||
entity: LivingEntity,
|
||||
brain: BiggerBrain,
|
||||
activeObjectiveTags: MutableSet<String>
|
||||
): ObjectiveStatus {
|
||||
if (entity !is PokemonEntity || entity !is PathfinderMob || !entity.isAlive) return ObjectiveStatus.INVALID
|
||||
|
||||
if (brain.isFixation(movementTask)) {
|
||||
brain.sendDebugMessage("[PathToNest] Fixation active.")
|
||||
return ObjectiveStatus.FIXATED
|
||||
}
|
||||
|
||||
val isHoldingEgg = brain.getMemory(CobblemonMemories.HOLDING_EGG) ?: false
|
||||
val eggInNest = brain.getMemory(CobblemonMemories.EGG_IN_NEST) ?: false
|
||||
val paternalNestLoc = brain.getMemory(CobblemonMemories.PATERNAL_NEST_LOCATION)
|
||||
|
||||
mainThreadRuntime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
|
||||
val speed = mainThreadRuntime.resolveFloat(speedMultiplier)
|
||||
|
||||
if (isHoldingEgg && !eggInNest && paternalNestLoc != null) {
|
||||
val paternalNestVec = Vec3.atCenterOf(paternalNestLoc)
|
||||
if (entity.distanceToSqr(paternalNestVec) <= 2.0) {
|
||||
brain.sendDebugMessage("[PathToNest] Already at paternal nest.")
|
||||
return ObjectiveStatus.INACTIVE
|
||||
}
|
||||
if (!brain.distract(comfortDistraction)) return ObjectiveStatus.BLOCKED
|
||||
val tracker = RegularMovementTracker.attempt(entity, WalkTarget(paternalNestVec, speed, 1)) ?: return ObjectiveStatus.ACTIVE
|
||||
brain.sendDebugMessage("[PathToNest] Moving to paternal nest at $paternalNestLoc.")
|
||||
movementTask = brain.fixate(SimpleMoveFixation(tracker, "Path to paternal nest"))
|
||||
return ObjectiveStatus.ACTIVE
|
||||
}
|
||||
|
||||
if (entity.pokemon.breedingCooldown != 0) return ObjectiveStatus.INVALID
|
||||
|
||||
val nestLocation = brain.getMemory(CobblemonMemories.NEST_LOCATION) ?: return ObjectiveStatus.INVALID
|
||||
val mate = brain.getMemory(CobblemonMemories.POTENTIAL_MATE) as? PokemonEntity ?: return ObjectiveStatus.INVALID
|
||||
|
||||
val world = entity.level() as? ServerLevel ?: return ObjectiveStatus.INVALID
|
||||
val nestEntity = world.getBlockEntity(nestLocation) as? NestBlockEntity ?: return ObjectiveStatus.INVALID
|
||||
|
||||
val allowedTypes = allowedNestTypes(entity, mate)
|
||||
val nestType = (nestEntity.blockState.block as? WildNestBlock)?.type ?: return ObjectiveStatus.INVALID
|
||||
if (!isNestTypeAllowed(nestType, entity, allowedTypes)) {
|
||||
brain.sendDebugMessage("[PathToNest] Nest type $nestType not allowed. Allowed=$allowedTypes.")
|
||||
return ObjectiveStatus.INVALID
|
||||
}
|
||||
val isNestWaterlogged = nestEntity.blockState.getOptionalValue(net.minecraft.world.level.block.state.properties.BlockStateProperties.WATERLOGGED).orElse(false)
|
||||
if (!isWaterGroupCompatible(entity, mate, isNestWaterlogged)) {
|
||||
brain.sendDebugMessage("[PathToNest] Water-group mismatch for nest at $nestLocation (waterlogged=$isNestWaterlogged).")
|
||||
return ObjectiveStatus.INVALID
|
||||
}
|
||||
|
||||
val claimerEntityOne = nestEntity.nestClaimedBy.first?.let { world.getEntity(it) }
|
||||
val claimerEntityTwo = nestEntity.nestClaimedBy.second?.let { world.getEntity(it) }
|
||||
if ((claimerEntityOne != null && nestEntity.nestClaimedBy.first != entity.uuid) &&
|
||||
(claimerEntityTwo != null && nestEntity.nestClaimedBy.second != entity.uuid)
|
||||
) {
|
||||
brain.sendDebugMessage("[PathToNest] Nest already claimed by others at $nestLocation.")
|
||||
return ObjectiveStatus.INVALID
|
||||
}
|
||||
|
||||
nestEntity.nestClaimedBy = Pair(entity.uuid, mate.uuid)
|
||||
|
||||
val targetVec = Vec3.atCenterOf(nestLocation)
|
||||
if (entity.distanceToSqr(targetVec) <= 2.0 && mate.distanceToSqr(targetVec) <= 2.0) {
|
||||
brain.sendDebugMessage("[PathToNest] Both parents at nest $nestLocation.")
|
||||
return ObjectiveStatus.INACTIVE
|
||||
}
|
||||
if (nestEntity.getItem(0) != ItemStack.EMPTY) {
|
||||
brain.sendDebugMessage("[PathToNest] Nest at $nestLocation already has an egg.")
|
||||
return ObjectiveStatus.INVALID
|
||||
}
|
||||
|
||||
if (!brain.distract(comfortDistraction)) return ObjectiveStatus.BLOCKED
|
||||
val movementTracker = RegularMovementTracker.attempt(entity, WalkTarget(targetVec, speed, 1)) ?: return ObjectiveStatus.ACTIVE
|
||||
brain.sendDebugMessage("[PathToNest] Moving to nest at $nestLocation.")
|
||||
movementTask = brain.fixate(SimpleMoveFixation(movementTracker, "Path to nest"))
|
||||
|
||||
moveMateTo(mate, targetVec, speed)
|
||||
|
||||
return ObjectiveStatus.ACTIVE
|
||||
}
|
||||
|
||||
private fun moveMateTo(mate: PokemonEntity, target: Vec3, speed: Float) {
|
||||
val mateBrain = mate.biggerBrain
|
||||
if (!mateBrain.distract(comfortDistraction)) return
|
||||
val tracker = RegularMovementTracker.attempt(mate, WalkTarget(target, speed, 1)) ?: return
|
||||
mateBrain.fixate(SimpleMoveFixation(tracker, "Path to nest (mate)"))
|
||||
}
|
||||
|
||||
private fun allowedNestTypes(entity: PokemonEntity, mate: PokemonEntity): Set<WildNestType> {
|
||||
val types = WildNestTypes.combinedTypes(entity, mate)
|
||||
val mutable = types.toMutableSet()
|
||||
if (!entity.pokemon.isWild() && !mate.pokemon.isWild()) {
|
||||
mutable.add(WildNestType.PLAYER)
|
||||
}
|
||||
return mutable
|
||||
}
|
||||
|
||||
private fun isNestTypeAllowed(
|
||||
nestType: WildNestType,
|
||||
entity: PokemonEntity,
|
||||
allowedTypes: Set<WildNestType>
|
||||
): Boolean {
|
||||
if (nestType == WildNestType.PLAYER) {
|
||||
return !entity.pokemon.isWild() && allowedTypes.contains(WildNestType.PLAYER)
|
||||
}
|
||||
return allowedTypes.contains(nestType)
|
||||
}
|
||||
|
||||
private fun isWaterGroupCompatible(
|
||||
entity: PokemonEntity,
|
||||
mate: PokemonEntity,
|
||||
isNestWaterlogged: Boolean
|
||||
): Boolean {
|
||||
val waterEggGroups = setOf("water_1", "water_2", "water_3")
|
||||
val entityEggGroups = entity.pokemon.species.eggGroups.map { it.name.lowercase() }
|
||||
val mateEggGroups = mate.pokemon.species.eggGroups.map { it.name.lowercase() }
|
||||
val entityHasWater = entityEggGroups.any { it in waterEggGroups }
|
||||
val mateHasWater = mateEggGroups.any { it in waterEggGroups }
|
||||
val entityOnlyWater = entityEggGroups.all { it in waterEggGroups }
|
||||
val mateOnlyWater = mateEggGroups.all { it in waterEggGroups }
|
||||
|
||||
return if (isNestWaterlogged) {
|
||||
entityHasWater && mateHasWater
|
||||
} else {
|
||||
!(entityOnlyWater || mateOnlyWater)
|
||||
}
|
||||
}
|
||||
}
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.objective
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonItemComponents
|
||||
import com.cobblemon.mod.common.CobblemonItems
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.Objective
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.ObjectiveStatus
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.world.InteractionHand
|
||||
import net.minecraft.world.entity.Entity
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.PathfinderMob
|
||||
import net.minecraft.world.entity.item.ItemEntity
|
||||
import net.minecraft.world.phys.Vec3
|
||||
|
||||
class PickUpEggTask : Objective {
|
||||
override fun getDescription(entity: LivingEntity) = "Pick up egg"
|
||||
|
||||
override fun perform(
|
||||
entity: LivingEntity,
|
||||
brain: BiggerBrain,
|
||||
activeObjectiveTags: MutableSet<String>
|
||||
): ObjectiveStatus {
|
||||
if (entity !is PokemonEntity || entity !is PathfinderMob || !entity.isAlive) return ObjectiveStatus.INVALID
|
||||
|
||||
if (brain.getMemory(CobblemonMemories.READY_TO_GRAB_EGG) != true) return ObjectiveStatus.INVALID
|
||||
val eggInNest = brain.getMemory(CobblemonMemories.EGG_IN_NEST)
|
||||
if (eggInNest != null && eggInNest == true) {
|
||||
brain.sendDebugMessage("[PickUpEgg] Egg already in nest.")
|
||||
return ObjectiveStatus.INVALID
|
||||
}
|
||||
|
||||
val eggPos: BlockPos = brain.getMemory(CobblemonMemories.EGG_LOCATION) ?: return ObjectiveStatus.INVALID
|
||||
val eggCenter = Vec3.atCenterOf(eggPos)
|
||||
|
||||
if (entity.distanceToSqr(eggCenter) > 2.0) {
|
||||
brain.sendDebugMessage("[PickUpEgg] Too far from egg at $eggPos; clearing ready flag.")
|
||||
brain.eraseMemory(CobblemonMemories.READY_TO_GRAB_EGG)
|
||||
return ObjectiveStatus.INACTIVE
|
||||
}
|
||||
|
||||
val nearbyEggs = entity.level().getEntitiesOfClass(ItemEntity::class.java, entity.boundingBox.inflate(1.5)) { itemEntity ->
|
||||
itemEntity.blockPosition() == eggPos && itemEntity.item.item == CobblemonItems.POKEMON_EGG
|
||||
}
|
||||
val eggItem = nearbyEggs.firstOrNull() ?: return ObjectiveStatus.INVALID
|
||||
|
||||
val eggComponent = eggItem.item.get(CobblemonItemComponents.EGG_DATA) ?: return ObjectiveStatus.INVALID
|
||||
val isParent = eggComponent.egg?.parentOne?.uuid == entity.pokemon.uuid ||
|
||||
eggComponent.egg?.parentTwo?.uuid == entity.pokemon.uuid
|
||||
if (!isParent) {
|
||||
brain.sendDebugMessage("[PickUpEgg] Egg at $eggPos does not belong to this Pokémon.")
|
||||
return ObjectiveStatus.INVALID
|
||||
}
|
||||
|
||||
entity.setItemInHand(InteractionHand.MAIN_HAND, eggItem.item.copy())
|
||||
eggItem.remove(Entity.RemovalReason.DISCARDED)
|
||||
|
||||
brain.eraseMemory(CobblemonMemories.EGG_LOCATION)
|
||||
brain.setMemory(CobblemonMemories.HOLDING_EGG, true)
|
||||
brain.eraseMemory(CobblemonMemories.EGG_SEARCH_COUNTDOWN)
|
||||
brain.eraseMemory(CobblemonMemories.READY_TO_GRAB_EGG)
|
||||
|
||||
brain.sendDebugMessage("[PickUpEgg] Picked up egg from $eggPos.")
|
||||
return ObjectiveStatus.ACTIVE
|
||||
}
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.objective
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonItems
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.Objective
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.ObjectiveStatus
|
||||
import com.cobblemon.mod.common.api.text.green
|
||||
import com.cobblemon.mod.common.block.entity.NestBlockEntity
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.server.level.ServerLevel
|
||||
import net.minecraft.world.InteractionHand
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.PathfinderMob
|
||||
import net.minecraft.world.item.ItemStack
|
||||
import net.minecraft.world.phys.Vec3
|
||||
|
||||
class PlaceEggInNestTask : Objective {
|
||||
override fun getDescription(entity: LivingEntity) = "Place egg in nest"
|
||||
|
||||
override fun perform(
|
||||
entity: LivingEntity,
|
||||
brain: BiggerBrain,
|
||||
activeObjectiveTags: MutableSet<String>
|
||||
): ObjectiveStatus {
|
||||
if (entity !is PokemonEntity || entity !is PathfinderMob || !entity.isAlive) return ObjectiveStatus.INVALID
|
||||
|
||||
if (brain.getMemory(CobblemonMemories.HOLDING_EGG) != true) return ObjectiveStatus.INVALID
|
||||
val eggInNest = brain.getMemory(CobblemonMemories.EGG_IN_NEST)
|
||||
if (eggInNest != null && eggInNest == true) {
|
||||
brain.sendDebugMessage("[PlaceEgg] Egg already in nest.")
|
||||
return ObjectiveStatus.INVALID
|
||||
}
|
||||
|
||||
val nestLocation: BlockPos = brain.getMemory(CobblemonMemories.PATERNAL_NEST_LOCATION) ?: return ObjectiveStatus.INVALID
|
||||
val world = entity.level() as? ServerLevel ?: return ObjectiveStatus.INVALID
|
||||
val nestEntity = world.getBlockEntity(nestLocation) as? NestBlockEntity ?: return ObjectiveStatus.INVALID
|
||||
|
||||
val targetVec = Vec3.atCenterOf(nestLocation)
|
||||
if (entity.distanceToSqr(targetVec) > 2.0) {
|
||||
brain.sendDebugMessage("[PlaceEgg] Too far from nest $nestLocation.")
|
||||
return ObjectiveStatus.INACTIVE
|
||||
}
|
||||
|
||||
val heldItem = entity.getItemInHand(InteractionHand.MAIN_HAND)
|
||||
if (heldItem.isEmpty || heldItem.item.asItem() != CobblemonItems.POKEMON_EGG.asItem()) {
|
||||
brain.sendDebugMessage("[PlaceEgg] No egg in hand.")
|
||||
return ObjectiveStatus.INVALID
|
||||
}
|
||||
|
||||
if (nestEntity.getItem(0) != ItemStack.EMPTY) {
|
||||
brain.sendDebugMessage("[PlaceEgg] Nest at $nestLocation already has an egg.")
|
||||
return ObjectiveStatus.INVALID
|
||||
}
|
||||
|
||||
nestEntity.setItem(heldItem.copy())
|
||||
entity.setItemInHand(InteractionHand.MAIN_HAND, ItemStack.EMPTY)
|
||||
|
||||
brain.eraseMemory(CobblemonMemories.HOLDING_EGG)
|
||||
brain.setMemory(CobblemonMemories.EGG_IN_NEST, true)
|
||||
brain.sendDebugMessage("[PlaceEgg] Egg placed in nest at $nestLocation.")
|
||||
|
||||
world.players().forEach { player ->
|
||||
player.displayClientMessage("The Pokémon placed an egg in its nest!".green(), true)
|
||||
}
|
||||
|
||||
return ObjectiveStatus.ACTIVE
|
||||
}
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.objective
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.RegularMovementTracker
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.comfortDistraction
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.SimpleMoveFixation
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.Objective
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.ObjectiveStatus
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.PathfinderMob
|
||||
import net.minecraft.world.entity.ai.memory.WalkTarget
|
||||
import net.minecraft.world.phys.Vec3
|
||||
|
||||
class RetrieveEggTask : Objective {
|
||||
private var movementTask: com.cobblemon.mod.common.api.ai.biggerbrain.fixation.EntityFixation? = null
|
||||
|
||||
override fun getDescription(entity: LivingEntity) = "Retrieve egg"
|
||||
|
||||
override fun perform(
|
||||
entity: LivingEntity,
|
||||
brain: BiggerBrain,
|
||||
activeObjectiveTags: MutableSet<String>
|
||||
): ObjectiveStatus {
|
||||
if (entity !is PokemonEntity || entity !is PathfinderMob || !entity.isAlive) return ObjectiveStatus.INVALID
|
||||
|
||||
if (brain.isFixation(movementTask)) {
|
||||
brain.sendDebugMessage("[RetrieveEgg] Fixation active.")
|
||||
return ObjectiveStatus.FIXATED
|
||||
}
|
||||
|
||||
val eggInNest = brain.getMemory(CobblemonMemories.EGG_IN_NEST)
|
||||
if (eggInNest != null && eggInNest == true) {
|
||||
brain.sendDebugMessage("[RetrieveEgg] Egg already in nest.")
|
||||
return ObjectiveStatus.INVALID
|
||||
}
|
||||
|
||||
val eggPos: BlockPos = brain.getMemory(CobblemonMemories.EGG_LOCATION) ?: return ObjectiveStatus.INVALID
|
||||
val eggCenter = Vec3.atCenterOf(eggPos)
|
||||
activeObjectiveTags.add(Objective.OBJECTIVE_TAG_WANDERING)
|
||||
|
||||
if (entity.distanceToSqr(eggCenter) <= 2.0) {
|
||||
brain.sendDebugMessage("[RetrieveEgg] At egg location $eggPos; ready to grab.")
|
||||
brain.setMemory(CobblemonMemories.READY_TO_GRAB_EGG, true)
|
||||
return ObjectiveStatus.INACTIVE
|
||||
}
|
||||
|
||||
brain.eraseMemory(CobblemonMemories.READY_TO_GRAB_EGG)
|
||||
|
||||
if (!brain.distract(comfortDistraction)) return ObjectiveStatus.BLOCKED
|
||||
val tracker = RegularMovementTracker.attempt(entity, WalkTarget(eggCenter, 1.0F, 1)) ?: return ObjectiveStatus.ACTIVE
|
||||
brain.sendDebugMessage("[RetrieveEgg] Moving to egg at $eggPos.")
|
||||
movementTask = brain.fixate(SimpleMoveFixation(tracker, "Retrieve egg"))
|
||||
|
||||
return ObjectiveStatus.ACTIVE
|
||||
}
|
||||
}
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.objective
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.RegularMovementTracker
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.comfortDistraction
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.SimpleMoveFixation
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.Objective
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.ObjectiveStatus
|
||||
import com.cobblemon.mod.common.block.entity.NestBlockEntity
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.server.level.ServerLevel
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.PathfinderMob
|
||||
import net.minecraft.world.entity.ai.memory.MemoryModuleType
|
||||
import net.minecraft.world.entity.ai.memory.WalkTarget
|
||||
import net.minecraft.world.phys.Vec3
|
||||
import kotlin.random.Random
|
||||
|
||||
class SearchForEggTask : Objective {
|
||||
private var movementTask: com.cobblemon.mod.common.api.ai.biggerbrain.fixation.EntityFixation? = null
|
||||
private var remainingCooldownTicks = 20
|
||||
|
||||
override fun getDescription(entity: LivingEntity) = "Search for egg"
|
||||
|
||||
override fun perform(
|
||||
entity: LivingEntity,
|
||||
brain: BiggerBrain,
|
||||
activeObjectiveTags: MutableSet<String>
|
||||
): ObjectiveStatus {
|
||||
if (entity !is PokemonEntity || entity !is PathfinderMob || !entity.isAlive) return ObjectiveStatus.INVALID
|
||||
|
||||
if (brain.isFixation(movementTask)) {
|
||||
brain.sendDebugMessage("[SearchForEgg] Fixation active.")
|
||||
return ObjectiveStatus.FIXATED
|
||||
}
|
||||
|
||||
val eggInNest = brain.getMemory(CobblemonMemories.EGG_IN_NEST)
|
||||
if (eggInNest != null && eggInNest == true) {
|
||||
brain.sendDebugMessage("[SearchForEgg] Egg already in nest.")
|
||||
return ObjectiveStatus.INVALID
|
||||
}
|
||||
|
||||
if (brain.getMemory(CobblemonMemories.HOLDING_EGG) == true) {
|
||||
brain.sendDebugMessage("[SearchForEgg] Already holding egg.")
|
||||
return ObjectiveStatus.INVALID
|
||||
}
|
||||
if (brain.getMemory(CobblemonMemories.EGG_LOCATION) != null) {
|
||||
brain.sendDebugMessage("[SearchForEgg] Egg location already known.")
|
||||
return ObjectiveStatus.INVALID
|
||||
}
|
||||
if (brain.getMemory(CobblemonMemories.EGG_THIEF) != null) {
|
||||
brain.sendDebugMessage("[SearchForEgg] Egg thief already detected.")
|
||||
return ObjectiveStatus.INVALID
|
||||
}
|
||||
|
||||
val nestPos = brain.getMemory(CobblemonMemories.PATERNAL_NEST_LOCATION) ?: return ObjectiveStatus.INVALID
|
||||
val world = entity.level() as? ServerLevel ?: return ObjectiveStatus.INVALID
|
||||
val nestBlockEntity = world.getBlockEntity(nestPos) as? NestBlockEntity
|
||||
activeObjectiveTags.add(Objective.OBJECTIVE_TAG_WANDERING)
|
||||
|
||||
val searchCountdownRemaining = entity.brain.getTimeUntilExpiry(CobblemonMemories.EGG_SEARCH_COUNTDOWN)
|
||||
val searchCountdownActive = brain.getMemory(CobblemonMemories.EGG_SEARCH_COUNTDOWN)
|
||||
|
||||
if (searchCountdownRemaining <= 20 && searchCountdownActive != false && searchCountdownActive != null) {
|
||||
brain.sendDebugMessage("[SearchForEgg] Search countdown expired; clearing nest memories.")
|
||||
(world.getBlockEntity(nestPos) as? NestBlockEntity)?.parents = Pair(null, null)
|
||||
|
||||
brain.eraseMemory(CobblemonMemories.EGG_SEARCH_COUNTDOWN)
|
||||
brain.eraseMemory(CobblemonMemories.EGG_LOCATION)
|
||||
brain.eraseMemory(CobblemonMemories.PATERNAL_NEST_LOCATION)
|
||||
brain.eraseMemory(CobblemonMemories.EGG_IN_NEST)
|
||||
brain.eraseMemory(CobblemonMemories.POTENTIAL_MATE)
|
||||
|
||||
if (nestBlockEntity != null) {
|
||||
nestBlockEntity.parents = Pair(null, null)
|
||||
nestBlockEntity.nestClaimedBy = Pair(null, null)
|
||||
}
|
||||
|
||||
return ObjectiveStatus.INACTIVE
|
||||
}
|
||||
|
||||
val nestCenter = Vec3.atCenterOf(nestPos)
|
||||
|
||||
if (remainingCooldownTicks > 0) {
|
||||
remainingCooldownTicks--
|
||||
return ObjectiveStatus.ACTIVE
|
||||
}
|
||||
remainingCooldownTicks = 20
|
||||
|
||||
val disturbancePos = brain.getMemory(MemoryModuleType.DISTURBANCE_LOCATION)
|
||||
val targetVec = if (disturbancePos != null) {
|
||||
val disturbanceVec = Vec3.atCenterOf(disturbancePos)
|
||||
if (entity.position().distanceTo(disturbanceVec) > DISTURBANCE_MIN_DISTANCE) {
|
||||
disturbanceVec
|
||||
} else {
|
||||
null
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
val finalTarget = targetVec ?: getRandomPositionAround(nestCenter, entity) ?: return ObjectiveStatus.ACTIVE
|
||||
|
||||
if (!brain.distract(comfortDistraction)) return ObjectiveStatus.BLOCKED
|
||||
val tracker = RegularMovementTracker.attempt(entity, WalkTarget(finalTarget, 1.0F, 1)) ?: return ObjectiveStatus.ACTIVE
|
||||
brain.sendDebugMessage("[SearchForEgg] Searching near ${finalTarget}.")
|
||||
movementTask = brain.fixate(SimpleMoveFixation(tracker, "Search for egg"))
|
||||
|
||||
return ObjectiveStatus.ACTIVE
|
||||
}
|
||||
|
||||
private fun getRandomPositionAround(center: Vec3, entity: PathfinderMob): Vec3? {
|
||||
repeat(10) {
|
||||
val offsetX = Random.nextDouble(-SEARCH_RADIUS, SEARCH_RADIUS)
|
||||
val offsetZ = Random.nextDouble(-SEARCH_RADIUS, SEARCH_RADIUS)
|
||||
val targetVec = center.add(offsetX, 0.0, offsetZ)
|
||||
val targetPos = BlockPos(targetVec.x.toInt(), targetVec.y.toInt(), targetVec.z.toInt())
|
||||
if (entity.navigation.createPath(targetPos, 0) != null) {
|
||||
return Vec3.atCenterOf(targetPos)
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val SEARCH_RADIUS = 32.0
|
||||
private const val DISTURBANCE_MIN_DISTANCE = 10.0
|
||||
}
|
||||
}
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.objective
|
||||
|
||||
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
|
||||
import com.cobblemon.mod.common.api.ai.asVariables
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.MovementTrackerFixation
|
||||
import com.cobblemon.mod.common.api.ai.booleanVariable
|
||||
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
|
||||
import com.cobblemon.mod.common.api.ai.config.task.SharedEntityVariables
|
||||
import com.cobblemon.mod.common.api.ai.numberVariable
|
||||
import com.cobblemon.mod.common.api.ai.resolveBoolean
|
||||
import com.cobblemon.mod.common.api.ai.resolveFloat
|
||||
import net.minecraft.tags.FluidTags
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.PathfinderMob
|
||||
|
||||
class StayAfloat(
|
||||
val chance: Float = 0.8F
|
||||
) : Objective {
|
||||
override fun getDescription(entity: LivingEntity) = "Stay Afloat"
|
||||
|
||||
override fun perform(
|
||||
entity: LivingEntity,
|
||||
brain: BiggerBrain,
|
||||
activeObjectiveTags: MutableSet<String>
|
||||
): ObjectiveStatus {
|
||||
if (entity !is PathfinderMob) {
|
||||
return ObjectiveStatus.INVALID
|
||||
}
|
||||
|
||||
val fixation = brain.fixation
|
||||
if (fixation is MovementTrackerFixation) {
|
||||
// Ok if movement is in progress then we don't try staying afloat
|
||||
return ObjectiveStatus.BLOCKED
|
||||
}
|
||||
|
||||
if (entity.isInWater && (entity.getFluidHeight(FluidTags.WATER) > entity.fluidJumpThreshold) ||
|
||||
(entity.isInLava && !entity.fireImmune())) {
|
||||
// Cool do something
|
||||
if (entity.random.nextFloat() < this.chance) {
|
||||
entity.jumpControl.jump()
|
||||
}
|
||||
return ObjectiveStatus.ACTIVE
|
||||
} else {
|
||||
return ObjectiveStatus.INACTIVE
|
||||
}
|
||||
}
|
||||
|
||||
class Config : ObjectiveConfig {
|
||||
val condition = booleanVariable(SharedEntityVariables.MOVEMENT_CATEGORY, "can_float", true).asExpressible()
|
||||
val chance = numberVariable(SharedEntityVariables.MOVEMENT_CATEGORY, "float_chance", 0.8F).asExpressible()
|
||||
|
||||
override fun getVariables(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
) = listOf(
|
||||
condition,
|
||||
chance
|
||||
).asVariables()
|
||||
|
||||
override fun create(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
): StayAfloat? {
|
||||
return when {
|
||||
condition.resolveBoolean(behaviourConfigurationContext.runtime) -> StayAfloat(chance.resolveFloat(behaviourConfigurationContext.runtime))
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+240
@@ -0,0 +1,240 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.objective
|
||||
|
||||
import com.bedrockk.molang.Expression
|
||||
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
|
||||
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
|
||||
import com.cobblemon.mod.common.api.ai.asSimplifiedExpression
|
||||
import com.cobblemon.mod.common.api.ai.asVariables
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.LookingTracker
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.MovementStatus
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.MovementTracker
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.RegularMovementTracker
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.Distraction
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.comfortDistraction
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.EntityFixation
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.LookingTrackerFixation
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.MovementTrackerFixation
|
||||
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
|
||||
import com.cobblemon.mod.common.api.ai.resolveInt
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
|
||||
import com.cobblemon.mod.common.util.asExpression
|
||||
import com.cobblemon.mod.common.util.mainThreadRuntime
|
||||
import com.cobblemon.mod.common.util.resolveFloat
|
||||
import com.cobblemon.mod.common.util.resolveInt
|
||||
import com.cobblemon.mod.common.util.withQueryValue
|
||||
import com.mojang.datafixers.util.Either
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.ceil
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.util.RandomSource
|
||||
import net.minecraft.world.entity.Entity
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.PathfinderMob
|
||||
import net.minecraft.world.entity.TamableAnimal
|
||||
import net.minecraft.world.entity.ai.behavior.EntityTracker
|
||||
import net.minecraft.world.entity.ai.memory.WalkTarget
|
||||
import net.minecraft.world.level.pathfinder.PathType
|
||||
import net.minecraft.world.level.pathfinder.WalkNodeEvaluator
|
||||
|
||||
/**
|
||||
* Keeps track of the owner entity, and if they are in the same world but are too far away, fixates on getting closer.
|
||||
* If they are extremely far away, will attempt to teleport to them. When walking to the owner, it pauses for a short
|
||||
* duration after reaching them to look at them. It's cute.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since December 29th, 2025
|
||||
*/
|
||||
class StayNearOwner(
|
||||
val ticksBetweenAttempts: Int = 60,
|
||||
val completionRange: Expression = "4.0".asExpression(),
|
||||
val maxDistance: Expression = "14.0".asExpression(),
|
||||
val teleportDistance: Expression = "24.0".asExpression(),
|
||||
val speedMultiplier: Expression = "0.4".asExpression()
|
||||
) : Objective {
|
||||
var followTask: EntityFixation? = null
|
||||
var remainingCooldownTicks = ticksBetweenAttempts
|
||||
|
||||
override fun getDescription(entity: LivingEntity) = "Stay near owner"
|
||||
override fun perform(
|
||||
entity: LivingEntity,
|
||||
brain: BiggerBrain,
|
||||
activeObjectiveTags: MutableSet<String>
|
||||
): ObjectiveStatus {
|
||||
if (entity !is TamableAnimal) {
|
||||
return ObjectiveStatus.INVALID
|
||||
} else if (brain.isFixation(followTask)) {
|
||||
remainingCooldownTicks = ticksBetweenAttempts
|
||||
return ObjectiveStatus.FIXATED
|
||||
}
|
||||
|
||||
val owner = entity.owner ?: return ObjectiveStatus.INVALID
|
||||
if (owner.level() != entity.level()) {
|
||||
return ObjectiveStatus.INVALID
|
||||
}
|
||||
|
||||
if (remainingCooldownTicks > 0) {
|
||||
remainingCooldownTicks--
|
||||
}
|
||||
|
||||
mainThreadRuntime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
|
||||
|
||||
val teleportDistance = mainThreadRuntime.resolveFloat(teleportDistance)
|
||||
val maxDistance = mainThreadRuntime.resolveFloat(maxDistance)
|
||||
val speedMultiplier = mainThreadRuntime.resolveFloat(speedMultiplier)
|
||||
val completionRange = mainThreadRuntime.resolveInt(completionRange)
|
||||
|
||||
val horizontalHitboxRadius = maxOf(entity.boundingBox.xsize, entity.boundingBox.zsize) / 2
|
||||
|
||||
val distance = entity.distanceTo(owner) - horizontalHitboxRadius
|
||||
if (distance > teleportDistance) {
|
||||
if (remainingCooldownTicks <= 0 && brain.distract(Distraction.FORCE)) {
|
||||
remainingCooldownTicks = ticksBetweenAttempts
|
||||
tryTeleport(entity, owner)
|
||||
return ObjectiveStatus.ACTIVE
|
||||
}
|
||||
return if (remainingCooldownTicks <= 0) ObjectiveStatus.BLOCKED else ObjectiveStatus.ACTIVE
|
||||
} else if (distance > maxDistance) {
|
||||
if (remainingCooldownTicks <= 0 && brain.distract(comfortDistraction)) {
|
||||
remainingCooldownTicks = ticksBetweenAttempts
|
||||
val movementTracker = RegularMovementTracker.attempt(
|
||||
entity = entity,
|
||||
walkTarget = WalkTarget(
|
||||
owner,
|
||||
speedMultiplier,
|
||||
maxOf(completionRange, ceil(horizontalHitboxRadius).toInt()) - 1
|
||||
)
|
||||
)
|
||||
if (movementTracker != null) {
|
||||
followTask = brain.fixate(
|
||||
fixation = FollowOwnerFixation(
|
||||
entity = entity,
|
||||
owner = owner,
|
||||
teleportDistance = teleportDistance,
|
||||
movementTracker = movementTracker
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
return ObjectiveStatus.ACTIVE
|
||||
}
|
||||
|
||||
private fun tryTeleport(entity: TamableAnimal, owner: Entity): Boolean {
|
||||
val blockPos = owner.blockPosition()
|
||||
for (i in 0..9) {
|
||||
val j = this.getRandomInt(entity.random, -3, 3)
|
||||
val k = this.getRandomInt(entity.random, -1, 1)
|
||||
val l = this.getRandomInt(entity.random, -3, 3)
|
||||
val succeeded = this.tryTeleportTo(entity, owner, blockPos.x + j, blockPos.y + k, blockPos.z + l)
|
||||
if (succeeded) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun tryTeleportTo(entity: TamableAnimal, owner: Entity, x: Int, y: Int, z: Int): Boolean {
|
||||
if (abs(x.toDouble() - owner.x) < 2.0 && abs(z - owner.z) < 2.0) {
|
||||
return false
|
||||
} else if (!this.canTeleportTo(entity, BlockPos(x, y, z))) {
|
||||
return false
|
||||
} else {
|
||||
entity.moveTo(
|
||||
x.toDouble() + 0.5, y.toDouble(), z.toDouble() + 0.5,
|
||||
entity.yRot,
|
||||
entity.xRot
|
||||
)
|
||||
entity.navigation.stop()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
private fun canTeleportTo(entity: TamableAnimal, pos: BlockPos): Boolean {
|
||||
val pathNodeType = WalkNodeEvaluator.getPathTypeStatic(entity, pos.mutable())
|
||||
if (pathNodeType != PathType.WALKABLE) {
|
||||
return false // Could be more complex to support fliers and swimmers
|
||||
} else {
|
||||
val blockPos = pos.subtract(entity.blockPosition())
|
||||
return entity.level().noCollision(entity, entity.boundingBox.move(blockPos))
|
||||
}
|
||||
}
|
||||
|
||||
private fun getRandomInt(random: RandomSource, min: Int, max: Int) = random.nextInt(max - min + 1) + min
|
||||
|
||||
class FollowOwnerFixation(
|
||||
entity: PathfinderMob,
|
||||
val owner: LivingEntity,
|
||||
val teleportDistance: Float,
|
||||
override val movementTracker: MovementTracker,
|
||||
): EntityFixation, LookingTrackerFixation, MovementTrackerFixation {
|
||||
|
||||
// Look at the owner for some amount of time after reaching them. For cuteness!
|
||||
var lingeringStareTicks = (20..40).random()
|
||||
|
||||
override val lookingTracker: LookingTracker = LookingTracker(entity = entity, lookTarget = EntityTracker(owner, true))
|
||||
override fun getDescription(entity: LivingEntity) = "Moving to owner"
|
||||
override fun canDistract(distraction: Distraction, entity: LivingEntity): Boolean {
|
||||
// Easily distracted once we've reached the player.
|
||||
return distraction.isDamage || distraction.isForce || movementTracker.lastStatus == MovementStatus.COMPLETED
|
||||
}
|
||||
|
||||
override fun onDistract(distraction: Distraction, entity: LivingEntity) {
|
||||
movementTracker.stop()
|
||||
}
|
||||
|
||||
override fun tick(entity: LivingEntity, brain: BiggerBrain): Boolean {
|
||||
lookingTracker.tick()
|
||||
|
||||
if (entity.distanceTo(owner) > teleportDistance) {
|
||||
// Too far away, give up, the teleport will handle it
|
||||
return false
|
||||
}
|
||||
|
||||
if (movementTracker.lastStatus == MovementStatus.COMPLETED) {
|
||||
lingeringStareTicks--
|
||||
return lingeringStareTicks > 0
|
||||
}
|
||||
|
||||
return tickMovementFixation() != MovementStatus.FAILED
|
||||
}
|
||||
}
|
||||
|
||||
class Config : ObjectiveConfig {
|
||||
val ticksBetweenAttempts: ExpressionOrEntityVariable = Either.left("60".asExpression())
|
||||
val completionRange: ExpressionOrEntityVariable = Either.left("4.0".asExpression())
|
||||
val maxDistance: ExpressionOrEntityVariable = Either.left("10.0".asExpression())
|
||||
val teleportDistance: ExpressionOrEntityVariable = Either.left("24.0".asExpression())
|
||||
val speedMultiplier: ExpressionOrEntityVariable = Either.left("0.4".asExpression())
|
||||
|
||||
override fun getVariables(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
) = listOf(
|
||||
ticksBetweenAttempts,
|
||||
completionRange,
|
||||
maxDistance,
|
||||
teleportDistance,
|
||||
speedMultiplier
|
||||
).asVariables()
|
||||
|
||||
override fun create(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): Objective {
|
||||
val ticksBetweenAttempts = ticksBetweenAttempts.resolveInt(behaviourConfigurationContext.runtime)
|
||||
return StayNearOwner(
|
||||
ticksBetweenAttempts = ticksBetweenAttempts,
|
||||
completionRange = completionRange.asSimplifiedExpression(entity),
|
||||
maxDistance = maxDistance.asSimplifiedExpression(entity),
|
||||
teleportDistance = teleportDistance.asSimplifiedExpression(entity),
|
||||
speedMultiplier = speedMultiplier.asSimplifiedExpression(entity)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
+210
@@ -0,0 +1,210 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.objective
|
||||
|
||||
import com.bedrockk.molang.Expression
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
|
||||
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
|
||||
import com.cobblemon.mod.common.api.ai.asSimplifiedExpression
|
||||
import com.cobblemon.mod.common.api.ai.asVariables
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.LookingTracker
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.RegularMovementTracker
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.WanderType
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.DistractionType
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.idleBoredomDistraction
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.EntityFixation
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.LookAndMoveFixation
|
||||
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
|
||||
import com.cobblemon.mod.common.api.ai.resolveInt
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
|
||||
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
|
||||
import com.cobblemon.mod.common.util.asExpression
|
||||
import com.cobblemon.mod.common.util.getMemorySafely
|
||||
import com.cobblemon.mod.common.util.mainThreadRuntime
|
||||
import com.cobblemon.mod.common.util.resolveBoolean
|
||||
import com.cobblemon.mod.common.util.resolveFloat
|
||||
import com.cobblemon.mod.common.util.resolveInt
|
||||
import com.cobblemon.mod.common.util.withQueryValue
|
||||
import com.mojang.datafixers.util.Either
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.PathfinderMob
|
||||
|
||||
/*
|
||||
* I've scuffed the hell out of this thing. The logic for what types of wandering to do is unclear, and I haven't
|
||||
* really decided whether it should be one mega function for finding a wander target and figuring out the type of
|
||||
* goal in the process or if it should be cleanly separated for each type of wander target. I've done a bit of both
|
||||
* and it's not pretty.
|
||||
*/
|
||||
|
||||
class WanderAround(
|
||||
val condition: Expression = "true".asExpression(),
|
||||
val cooldownTicks: Int = 80,
|
||||
val allowLand: Expression = "(q.entity.behaviour.moving.walk.can_walk && !q.entity.behaviour.moving.walk.avoids_land) || q.entity.behaviour.moving.fly.can_fly".asExpression(),
|
||||
val allowWater: Expression = "q.entity.behaviour.moving.swim.can_swim_in_water && !q.entity.behaviour.moving.swim.avoids_water".asExpression(),
|
||||
val allowAir: Expression = "q.entity.behaviour.moving.fly.can_fly && !q.entity.behaviour.moving.walk.can_walk".asExpression(),
|
||||
val wanderChance: Expression = "1/(20*6)".asExpression(),
|
||||
val horizontalRange: Expression = "10".asExpression(),
|
||||
val verticalRange: Expression = "5".asExpression(),
|
||||
val speedMultiplier: Expression = "0.35".asExpression(),
|
||||
val avoidTargetingAir: Expression = "true".asExpression(),
|
||||
val minimumHeight: Expression = "0".asExpression(),
|
||||
val maximumHeight: Expression = "999".asExpression()
|
||||
) : Objective {
|
||||
var fixation: EntityFixation? = null
|
||||
var remainingCooldownTicks = 10
|
||||
|
||||
override fun getDescription(entity: LivingEntity) = "Wander around"
|
||||
|
||||
override fun perform(
|
||||
entity: LivingEntity,
|
||||
brain: BiggerBrain,
|
||||
activeObjectiveTags: MutableSet<String>
|
||||
): ObjectiveStatus {
|
||||
if (brain.isFixation(fixation)) {
|
||||
activeObjectiveTags.add(Objective.OBJECTIVE_TAG_WANDERING)
|
||||
return ObjectiveStatus.FIXATED
|
||||
} else if (entity !is PathfinderMob) {
|
||||
return ObjectiveStatus.INVALID
|
||||
} else if (Objective.OBJECTIVE_TAG_WANDERING in activeObjectiveTags) {
|
||||
return ObjectiveStatus.BLOCKED
|
||||
} else if (!brain.canDistract(idleBoredomDistraction)) {
|
||||
return ObjectiveStatus.BLOCKED
|
||||
}
|
||||
|
||||
fixation = null
|
||||
|
||||
val runtime = mainThreadRuntime
|
||||
runtime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
|
||||
|
||||
if (!runtime.resolveBoolean(condition)) {
|
||||
return ObjectiveStatus.INACTIVE
|
||||
} else if (Objective.OBJECTIVE_TAG_WANDERING in activeObjectiveTags) {
|
||||
return ObjectiveStatus.BLOCKED
|
||||
}
|
||||
|
||||
if (remainingCooldownTicks > 0) {
|
||||
remainingCooldownTicks--
|
||||
return ObjectiveStatus.INACTIVE
|
||||
}
|
||||
|
||||
val wanderControl = entity.brain.getMemorySafely(CobblemonMemories.WANDER_CONTROL).orElse(null)
|
||||
val wanderChance = runtime.resolveFloat(this.wanderChance) * (wanderControl?.wanderChanceMultiplier ?: 1.0F)
|
||||
if (wanderChance <= 1.0F && entity.level().random.nextFloat() > wanderChance) {
|
||||
return ObjectiveStatus.ACTIVE
|
||||
}
|
||||
|
||||
val allowLand = wanderControl?.allowLand ?: runtime.resolveBoolean(allowLand)
|
||||
val allowWater = wanderControl?.allowWater ?: runtime.resolveBoolean(allowWater)
|
||||
val allowAir = wanderControl?.allowAir ?: runtime.resolveBoolean(allowAir)
|
||||
|
||||
val types = mutableSetOf<WanderType>()
|
||||
if ((allowLand || allowAir) && WanderType.LAND.isValid(entity)) {
|
||||
types.add(WanderType.LAND)
|
||||
}
|
||||
if (allowWater && WanderType.WATER.isValid(entity)) {
|
||||
types.add(WanderType.WATER)
|
||||
}
|
||||
|
||||
if (types.isEmpty()) {
|
||||
return ObjectiveStatus.INVALID
|
||||
}
|
||||
|
||||
remainingCooldownTicks = cooldownTicks
|
||||
val walkTarget = types.firstNotNullOfOrNull {
|
||||
it.generateWalkTarget(
|
||||
entity = entity,
|
||||
horizontalRange = runtime.resolveInt(horizontalRange),
|
||||
verticalRange = runtime.resolveInt(verticalRange),
|
||||
minimumHeight = runtime.resolveInt(minimumHeight),
|
||||
maximumHeight = runtime.resolveInt(maximumHeight),
|
||||
avoidTargetingAir = runtime.resolveBoolean(avoidTargetingAir),
|
||||
speedMultiplier = runtime.resolveFloat(speedMultiplier),
|
||||
center = wanderControl?.center,
|
||||
maxAttempts = wanderControl?.maxAttempts ?: 4
|
||||
)
|
||||
} ?: return ObjectiveStatus.ACTIVE
|
||||
|
||||
val movementTracker = RegularMovementTracker.attempt(entity, walkTarget)
|
||||
?: return ObjectiveStatus.ACTIVE
|
||||
|
||||
|
||||
if (brain.distract(idleBoredomDistraction)) {
|
||||
movementTracker.tick()
|
||||
fixation = brain.fixate(
|
||||
LookAndMoveFixation(
|
||||
description = "Wandering",
|
||||
distractionTypes = setOf(DistractionType.DAMAGE, DistractionType.FORCE),
|
||||
lookingTracker = LookingTracker.fromNavigationPathOrWalkTarget(entity, walkTarget),
|
||||
movementTracker = movementTracker
|
||||
)
|
||||
)
|
||||
activeObjectiveTags.add(Objective.OBJECTIVE_TAG_WANDERING) // unsure if we need this
|
||||
return ObjectiveStatus.FIXATED
|
||||
}
|
||||
|
||||
return ObjectiveStatus.BLOCKED
|
||||
}
|
||||
|
||||
class Config : ObjectiveConfig {
|
||||
val condition: ExpressionOrEntityVariable = Either.left("true".asExpression())
|
||||
val cooldownTicks: ExpressionOrEntityVariable = Either.left("80".asExpression())
|
||||
val allowLand: ExpressionOrEntityVariable = Either.left("(q.entity.behaviour.moving.walk.can_walk && !q.entity.behaviour.moving.walk.avoids_land) || q.entity.behaviour.moving.fly.can_fly".asExpression())
|
||||
val allowWater: ExpressionOrEntityVariable = Either.left("q.entity.behaviour.moving.swim.can_swim_in_water && !q.entity.behaviour.moving.swim.avoids_water".asExpression())
|
||||
val allowAir: ExpressionOrEntityVariable = Either.left("q.entity.behaviour.moving.fly.can_fly && !q.entity.behaviour.moving.walk.can_walk".asExpression())
|
||||
val wanderChance: ExpressionOrEntityVariable = Either.left("1/(20*6)".asExpression())
|
||||
val horizontalRange: ExpressionOrEntityVariable = Either.left("10".asExpression())
|
||||
val verticalRange: ExpressionOrEntityVariable = Either.left("5".asExpression())
|
||||
val speedMultiplier: ExpressionOrEntityVariable = Either.left("0.35".asExpression())
|
||||
val avoidTargetingAir: ExpressionOrEntityVariable = Either.left("true".asExpression())
|
||||
val minimumHeight: ExpressionOrEntityVariable = Either.left("0".asExpression())
|
||||
val maximumHeight: ExpressionOrEntityVariable = Either.left("999".asExpression())
|
||||
|
||||
override fun getVariables(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
): List<MoLangConfigVariable> {
|
||||
return listOf(
|
||||
condition,
|
||||
cooldownTicks,
|
||||
allowLand,
|
||||
allowWater,
|
||||
allowAir,
|
||||
wanderChance,
|
||||
horizontalRange,
|
||||
verticalRange,
|
||||
speedMultiplier,
|
||||
avoidTargetingAir,
|
||||
minimumHeight,
|
||||
maximumHeight
|
||||
).asVariables()
|
||||
}
|
||||
|
||||
override fun create(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
): Objective {
|
||||
return WanderAround(
|
||||
condition = condition.asSimplifiedExpression(entity),
|
||||
cooldownTicks = cooldownTicks.resolveInt(behaviourConfigurationContext.runtime),
|
||||
allowLand = allowLand.asSimplifiedExpression(entity),
|
||||
allowWater = allowWater.asSimplifiedExpression(entity),
|
||||
allowAir = allowAir.asSimplifiedExpression(entity),
|
||||
wanderChance = wanderChance.asSimplifiedExpression(entity),
|
||||
horizontalRange = horizontalRange.asSimplifiedExpression(entity),
|
||||
verticalRange = verticalRange.asSimplifiedExpression(entity),
|
||||
speedMultiplier = speedMultiplier.asSimplifiedExpression(entity),
|
||||
avoidTargetingAir = avoidTargetingAir.asSimplifiedExpression(entity),
|
||||
minimumHeight = minimumHeight.asSimplifiedExpression(entity),
|
||||
maximumHeight = maximumHeight.asSimplifiedExpression(entity)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.sense
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
|
||||
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
|
||||
import com.cobblemon.mod.common.api.ai.asVariables
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
|
||||
import com.cobblemon.mod.common.api.ai.config.sense.SenseConfig
|
||||
import com.cobblemon.mod.common.api.ai.resolveInt
|
||||
import com.cobblemon.mod.common.battles.BattleRegistry
|
||||
import com.cobblemon.mod.common.entity.npc.NPCEntity
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.util.asExpression
|
||||
import com.mojang.datafixers.util.Either
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.ai.memory.MemoryModuleType
|
||||
|
||||
/**
|
||||
* Senses whether the entity is battling or not. Maintains the BATTLING_POKEMON memory.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since December 29th, 2025
|
||||
*/
|
||||
class BattlingPokemonSense(
|
||||
var cooldownTicks: Int = 2
|
||||
) : Sense {
|
||||
override var ticksUntilNextExecution: Int = 10
|
||||
override val memories: Set<MemoryModuleType<*>> = setOf(CobblemonMemories.BATTLING_POKEMON)
|
||||
|
||||
override fun merge(other: Sense): Boolean {
|
||||
if (other is BattlingPokemonSense) {
|
||||
this.cooldownTicks = minOf(this.cooldownTicks, other.cooldownTicks)
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
override fun tick(entity: LivingEntity, brain: BiggerBrain) {
|
||||
val battles = when (entity) {
|
||||
is PokemonEntity -> entity.battleId?.let { BattleRegistry.getBattle(it) }?.let { listOf(it) } ?: emptyList()
|
||||
is NPCEntity -> entity.battleIds.mapNotNull(BattleRegistry::getBattle)
|
||||
else -> emptyList()
|
||||
}
|
||||
|
||||
val entityUUIDs = if (entity is PokemonEntity) { // Pokemon can look at opponents, never allies
|
||||
battles.flatMap { battle ->
|
||||
val actor = battle.getActor(entity)
|
||||
if (actor != null) {
|
||||
actor.getSide().getOppositeSide().activePokemon.mapNotNull { it.battlePokemon?.entity?.uuid }
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
} else if (entity is NPCEntity) { // NPCs can look at any of the pokemon in play
|
||||
battles.flatMap { it.activePokemon }.mapNotNull { it.battlePokemon?.entity?.uuid }
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
|
||||
brain.setMemory(CobblemonMemories.BATTLING_POKEMON, entityUUIDs)
|
||||
if (entityUUIDs.isEmpty() && entity is PokemonEntity) {
|
||||
brain.eraseMemoryIfPresent(CobblemonMemories.TARGETED_BATTLE_POKEMON)
|
||||
}
|
||||
|
||||
ticksUntilNextExecution = cooldownTicks
|
||||
}
|
||||
|
||||
class Config : SenseConfig {
|
||||
var cooldownTicks: ExpressionOrEntityVariable = Either.left("2".asExpression())
|
||||
|
||||
override fun getVariables(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
) = listOf(cooldownTicks).asVariables()
|
||||
|
||||
override fun create(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): Sense {
|
||||
val cooldownTicks = cooldownTicks.resolveInt(behaviourConfigurationContext.runtime)
|
||||
return BattlingPokemonSense(cooldownTicks = cooldownTicks)
|
||||
}
|
||||
}
|
||||
}
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.sense
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
|
||||
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
|
||||
import com.cobblemon.mod.common.api.ai.asVariables
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
|
||||
import com.cobblemon.mod.common.api.ai.config.sense.SenseConfig
|
||||
import com.cobblemon.mod.common.api.ai.resolveInt
|
||||
import com.cobblemon.mod.common.api.pokemon.status.Statuses
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.util.asExpression
|
||||
import com.mojang.datafixers.util.Either
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.ai.memory.MemoryModuleType
|
||||
|
||||
/**
|
||||
* Senses when the Pokémon is in a situation that would make it ready to sleep.
|
||||
* Either by choice (nighttime) or by force (battle/spawning with sleep status)
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since March 23rd, 2024
|
||||
*/
|
||||
class DrowsySense(
|
||||
var cooldownTicks: Int = 20
|
||||
) : Sense {
|
||||
override var ticksUntilNextExecution: Int = 20
|
||||
override val memories = setOf(CobblemonMemories.POKEMON_DROWSY)
|
||||
|
||||
override fun merge(other: Sense): Boolean {
|
||||
if (other is DrowsySense) {
|
||||
this.cooldownTicks = minOf(this.cooldownTicks, other.cooldownTicks)
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
override fun tick(entity: LivingEntity, brain: BiggerBrain) {
|
||||
if (entity !is PokemonEntity) {
|
||||
return
|
||||
}
|
||||
|
||||
val world = entity.level()
|
||||
val rest = entity.behaviour.resting
|
||||
val isDrowsy = brain.getMemory(CobblemonMemories.POKEMON_DROWSY) ?: false
|
||||
val hurtLately = brain.hasMemoryValue(MemoryModuleType.HURT_BY)
|
||||
val shouldBeDrowsy = rest.canSleep && !hurtLately && (world.dayTime.toInt() % 24000) in rest.times && !brain.hasMemoryValue(MemoryModuleType.ANGRY_AT)
|
||||
val forcedAsleep = entity.pokemon.status?.status == Statuses.SLEEP
|
||||
|
||||
if (entity.isBattling) {
|
||||
if (!isDrowsy && forcedAsleep) {
|
||||
brain.setMemory(CobblemonMemories.POKEMON_DROWSY, true)
|
||||
} else if (isDrowsy && !forcedAsleep) {
|
||||
brain.eraseMemory(CobblemonMemories.POKEMON_DROWSY)
|
||||
}
|
||||
} else {
|
||||
if (!isDrowsy && shouldBeDrowsy && entity.random.nextFloat() < rest.drowsyChance) {
|
||||
brain.setMemory(CobblemonMemories.POKEMON_DROWSY, true)
|
||||
} else if (isDrowsy && !shouldBeDrowsy) {
|
||||
brain.eraseMemory(CobblemonMemories.POKEMON_DROWSY)
|
||||
} else if (isDrowsy && entity.random.nextFloat() < rest.rouseChance) {
|
||||
brain.eraseMemory(CobblemonMemories.POKEMON_DROWSY)
|
||||
}
|
||||
}
|
||||
|
||||
ticksUntilNextExecution = cooldownTicks
|
||||
}
|
||||
|
||||
class Config : SenseConfig {
|
||||
var cooldownTicks: ExpressionOrEntityVariable = Either.left("20".asExpression())
|
||||
|
||||
override fun getVariables(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
) = listOf(cooldownTicks).asVariables()
|
||||
|
||||
override fun create(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): Sense {
|
||||
val cooldownTicks = cooldownTicks.resolveInt(behaviourConfigurationContext.runtime)
|
||||
return DrowsySense(cooldownTicks = cooldownTicks)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.sense
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonItemComponents
|
||||
import com.cobblemon.mod.common.CobblemonItems
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
|
||||
import com.cobblemon.mod.common.api.ai.config.sense.SenseConfig
|
||||
import com.cobblemon.mod.common.api.text.green
|
||||
import com.cobblemon.mod.common.block.entity.NestBlockEntity
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.server.level.ServerLevel
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.ai.memory.MemoryModuleType
|
||||
import net.minecraft.world.entity.item.ItemEntity
|
||||
import net.minecraft.world.entity.player.Player
|
||||
|
||||
class EggSense(
|
||||
private var cooldownTicks: Int = 100
|
||||
) : Sense {
|
||||
override var ticksUntilNextExecution: Int = 100
|
||||
override val memories = setOf(
|
||||
CobblemonMemories.PATERNAL_NEST_LOCATION,
|
||||
CobblemonMemories.EGG_IN_NEST,
|
||||
CobblemonMemories.EGG_LOCATION,
|
||||
CobblemonMemories.EGG_THIEF,
|
||||
CobblemonMemories.EGG_THIEF_LAST_SEEN_TICK,
|
||||
CobblemonMemories.EGG_SEARCH_COUNTDOWN,
|
||||
MemoryModuleType.ATTACK_TARGET,
|
||||
MemoryModuleType.ANGRY_AT,
|
||||
MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES
|
||||
)
|
||||
|
||||
override fun merge(other: Sense): Boolean {
|
||||
if (other is EggSense) {
|
||||
this.cooldownTicks = minOf(this.cooldownTicks, other.cooldownTicks)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun tick(entity: LivingEntity, brain: BiggerBrain) {
|
||||
ticksUntilNextExecution = cooldownTicks
|
||||
if (entity !is PokemonEntity) return
|
||||
|
||||
val level = entity.level() as? ServerLevel ?: return
|
||||
val nestPos = brain.getMemory(CobblemonMemories.PATERNAL_NEST_LOCATION) ?: return
|
||||
|
||||
val blockEntity = level.getBlockEntity(nestPos)
|
||||
if (blockEntity is NestBlockEntity) {
|
||||
val nestItem = blockEntity.getItem()
|
||||
if (!nestItem.isEmpty && nestItem.item.asItem() == CobblemonItems.POKEMON_EGG.asItem()) {
|
||||
brain.sendDebugMessage("[EggSense] Egg confirmed in nest at $nestPos.")
|
||||
brain.setMemory(CobblemonMemories.EGG_LOCATION, nestPos)
|
||||
brain.setMemory(CobblemonMemories.EGG_IN_NEST, true)
|
||||
brain.eraseMemory(CobblemonMemories.EGG_SEARCH_COUNTDOWN)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
brain.sendDebugMessage("[EggSense] Egg missing from nest at $nestPos; searching.")
|
||||
brain.setMemory(CobblemonMemories.EGG_IN_NEST, false)
|
||||
brain.eraseMemory(CobblemonMemories.EGG_LOCATION)
|
||||
|
||||
val nearbyEggItems = level.getEntitiesOfClass(ItemEntity::class.java, entity.boundingBox.inflate(20.0)) { itemEntity ->
|
||||
val itemStack = itemEntity.item
|
||||
if (itemStack.item.asItem() != CobblemonItems.POKEMON_EGG.asItem()) return@getEntitiesOfClass false
|
||||
val eggComponent = itemStack.get(CobblemonItemComponents.EGG_DATA) ?: return@getEntitiesOfClass false
|
||||
eggComponent.egg?.let { egg ->
|
||||
egg.parentOne?.uuid == entity.pokemon.uuid || egg.parentTwo?.uuid == entity.pokemon.uuid
|
||||
} == true
|
||||
}
|
||||
|
||||
val existingEggThief = brain.getMemory(CobblemonMemories.EGG_THIEF) as? Player
|
||||
val lastSeenTick = brain.getMemory(CobblemonMemories.EGG_THIEF_LAST_SEEN_TICK)
|
||||
val currentTick = level.gameTime
|
||||
|
||||
if (existingEggThief != null) {
|
||||
if (isEntityTargetable(entity, existingEggThief)) {
|
||||
brain.setMemory(CobblemonMemories.EGG_THIEF_LAST_SEEN_TICK, currentTick)
|
||||
brain.sendDebugMessage("[EggSense] Egg thief still in sight: ${existingEggThief.name.string}.")
|
||||
brain.eraseMemory(CobblemonMemories.EGG_LOCATION)
|
||||
brain.eraseMemory(CobblemonMemories.EGG_SEARCH_COUNTDOWN)
|
||||
return
|
||||
}
|
||||
|
||||
val lastSeen = lastSeenTick ?: currentTick
|
||||
if (currentTick - lastSeen > THIEF_LOST_SIGHT_TICKS) {
|
||||
brain.sendDebugMessage("[EggSense] Lost sight of egg thief for too long; clearing.")
|
||||
brain.eraseMemory(CobblemonMemories.EGG_THIEF)
|
||||
brain.eraseMemory(CobblemonMemories.EGG_THIEF_LAST_SEEN_TICK)
|
||||
} else {
|
||||
brain.sendDebugMessage("[EggSense] Egg thief out of sight; holding target.")
|
||||
brain.eraseMemory(CobblemonMemories.EGG_LOCATION)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (nearbyEggItems.isNotEmpty()) {
|
||||
brain.sendDebugMessage("[EggSense] Egg item found on ground near ${nearbyEggItems[0].blockPosition()}.")
|
||||
level.players().forEach { player ->
|
||||
player.displayClientMessage("An egg has been found on the ground!".green(), true)
|
||||
}
|
||||
if (brain.hasMemoryValue(MemoryModuleType.ATTACK_TARGET)) brain.eraseMemory(MemoryModuleType.ATTACK_TARGET)
|
||||
if (brain.hasMemoryValue(MemoryModuleType.ANGRY_AT)) brain.eraseMemory(MemoryModuleType.ANGRY_AT)
|
||||
|
||||
brain.eraseMemory(CobblemonMemories.EGG_SEARCH_COUNTDOWN)
|
||||
brain.setMemory(CobblemonMemories.EGG_LOCATION, nearbyEggItems[0].blockPosition())
|
||||
brain.eraseMemory(CobblemonMemories.EGG_THIEF)
|
||||
} else {
|
||||
brain.sendDebugMessage("[EggSense] No egg or thief found; starting search timer.")
|
||||
brain.eraseMemory(CobblemonMemories.EGG_LOCATION)
|
||||
brain.eraseMemory(CobblemonMemories.EGG_THIEF)
|
||||
brain.eraseMemory(CobblemonMemories.EGG_THIEF_LAST_SEEN_TICK)
|
||||
|
||||
if (!brain.hasMemoryValue(CobblemonMemories.EGG_SEARCH_COUNTDOWN)) {
|
||||
brain.setMemory(CobblemonMemories.EGG_SEARCH_COUNTDOWN, true, EGG_SEARCH_TIME)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun isEntityTargetable(entity: LivingEntity, player: Player): Boolean {
|
||||
return entity.hasLineOfSight(player) && !player.isInvisible
|
||||
}
|
||||
|
||||
class Config : SenseConfig {
|
||||
override fun create(entity: LivingEntity, behaviourConfigurationContext: com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext): Sense? {
|
||||
return EggSense()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val EGG_SEARCH_TIME = 2400L
|
||||
private const val THIEF_LOST_SIGHT_TICKS = 1200L
|
||||
}
|
||||
}
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.sense
|
||||
|
||||
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
|
||||
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
|
||||
import com.cobblemon.mod.common.api.ai.asVariables
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.HurtDistraction
|
||||
import com.cobblemon.mod.common.api.ai.config.sense.SenseConfig
|
||||
import com.cobblemon.mod.common.api.ai.resolveInt
|
||||
import com.cobblemon.mod.common.util.asExpression
|
||||
import com.mojang.datafixers.util.Either
|
||||
import net.minecraft.world.damagesource.DamageSource
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.ai.memory.MemoryModuleType
|
||||
|
||||
/**
|
||||
* Maintains memory of the last entity that hurt this entity.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since December 29th, 2025
|
||||
*/
|
||||
class HurtBySense(
|
||||
var cooldownTicks: Int = 2
|
||||
) : Sense {
|
||||
var lastDamageSource: DamageSource? = null
|
||||
|
||||
override var ticksUntilNextExecution: Int = 1
|
||||
override val memories = setOf(MemoryModuleType.HURT_BY, MemoryModuleType.HURT_BY_ENTITY)
|
||||
|
||||
override fun merge(other: Sense): Boolean {
|
||||
if (other is HurtBySense) {
|
||||
this.cooldownTicks = minOf(this.cooldownTicks, other.cooldownTicks)
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
override fun tick(entity: LivingEntity, brain: BiggerBrain) {
|
||||
val damageSource = entity.lastDamageSource
|
||||
if (damageSource === lastDamageSource) {
|
||||
ticksUntilNextExecution = cooldownTicks
|
||||
return
|
||||
}
|
||||
|
||||
if (damageSource != null) {
|
||||
brain.setMemory(MemoryModuleType.HURT_BY, entity.lastDamageSource)
|
||||
val attacker = damageSource.entity
|
||||
if (attacker is LivingEntity) {
|
||||
brain.setMemory(MemoryModuleType.HURT_BY_ENTITY, attacker)
|
||||
}
|
||||
} else {
|
||||
brain.eraseMemoryIfPresent(MemoryModuleType.HURT_BY)
|
||||
}
|
||||
|
||||
// so apparently it remembers the entity that hurt them even if it was forever ago? fair enough I guess. that's vanilla for you.
|
||||
brain.getMemory(MemoryModuleType.HURT_BY_ENTITY)?.let { livingEntity ->
|
||||
if (!livingEntity.isAlive || livingEntity.level() != entity.level()) {
|
||||
brain.eraseMemory(MemoryModuleType.HURT_BY_ENTITY)
|
||||
}
|
||||
}
|
||||
|
||||
if (damageSource != null && lastDamageSource !== damageSource) {
|
||||
lastDamageSource = damageSource
|
||||
brain.distract(HurtDistraction(damageSource))
|
||||
}
|
||||
|
||||
ticksUntilNextExecution = cooldownTicks
|
||||
}
|
||||
|
||||
class Config : SenseConfig {
|
||||
var cooldownTicks: ExpressionOrEntityVariable = Either.left("2".asExpression())
|
||||
|
||||
override fun getVariables(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
) = listOf(cooldownTicks).asVariables()
|
||||
|
||||
override fun create(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): Sense {
|
||||
val cooldownTicks = cooldownTicks.resolveInt(behaviourConfigurationContext.runtime)
|
||||
return HurtBySense(cooldownTicks = cooldownTicks)
|
||||
}
|
||||
}
|
||||
}
|
||||
+187
@@ -0,0 +1,187 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.sense
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
|
||||
import com.cobblemon.mod.common.api.ai.config.sense.SenseConfig
|
||||
import com.cobblemon.mod.common.api.nest.WildNestType
|
||||
import com.cobblemon.mod.common.api.nest.WildNestTypes
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.server.level.ServerLevel
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.PathfinderMob
|
||||
import net.minecraft.world.entity.ai.memory.MemoryModuleType
|
||||
import net.minecraft.world.level.block.Blocks
|
||||
import kotlin.jvm.optionals.getOrNull
|
||||
|
||||
class IdealNestPlacementSense(
|
||||
private var cooldownTicks: Int = 100
|
||||
) : Sense {
|
||||
override var ticksUntilNextExecution: Int = 100
|
||||
override val memories = setOf(
|
||||
CobblemonMemories.IDEAL_NEST_LOCATION,
|
||||
CobblemonMemories.READY_TO_BUILD_NEST,
|
||||
CobblemonMemories.POTENTIAL_MATE
|
||||
)
|
||||
|
||||
override fun merge(other: Sense): Boolean {
|
||||
if (other is IdealNestPlacementSense) {
|
||||
this.cooldownTicks = minOf(this.cooldownTicks, other.cooldownTicks)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun tick(entity: LivingEntity, brain: BiggerBrain) {
|
||||
ticksUntilNextExecution = cooldownTicks
|
||||
if (entity !is PokemonEntity) return
|
||||
val world = entity.level() as? ServerLevel ?: return
|
||||
|
||||
val mate = brain.getMemory(CobblemonMemories.POTENTIAL_MATE) as? PokemonEntity
|
||||
val combinedTypes = mate?.let { WildNestTypes.combinedTypes(entity, it) } ?: emptyList()
|
||||
if (mate == null || combinedTypes.isEmpty()) {
|
||||
debug(brain, "No mate or no combined nest types; clearing ideal nest memories.")
|
||||
brain.eraseMemory(CobblemonMemories.IDEAL_NEST_LOCATION)
|
||||
brain.eraseMemory(CobblemonMemories.READY_TO_BUILD_NEST)
|
||||
mate?.biggerBrain?.eraseMemory(CobblemonMemories.IDEAL_NEST_LOCATION)
|
||||
mate?.biggerBrain?.eraseMemory(CobblemonMemories.READY_TO_BUILD_NEST)
|
||||
return
|
||||
}
|
||||
|
||||
if (brain.getMemory(CobblemonMemories.READY_TO_BUILD_NEST) != true) {
|
||||
debug(brain, "Ready to build set true for both parents.")
|
||||
brain.setMemory(CobblemonMemories.READY_TO_BUILD_NEST, true)
|
||||
mate.biggerBrain.setMemory(CobblemonMemories.READY_TO_BUILD_NEST, true)
|
||||
}
|
||||
|
||||
val storedNestPos = brain.getMemory(CobblemonMemories.IDEAL_NEST_LOCATION)
|
||||
if (storedNestPos != null) {
|
||||
if (isValidNestPlacement(world, entity, mate, storedNestPos, combinedTypes)) {
|
||||
debug(brain, "Stored ideal nest still valid at $storedNestPos.")
|
||||
mate.biggerBrain.setMemory(CobblemonMemories.IDEAL_NEST_LOCATION, storedNestPos)
|
||||
return
|
||||
} else {
|
||||
debug(brain, "Stored ideal nest invalid at $storedNestPos; clearing.")
|
||||
brain.eraseMemory(CobblemonMemories.IDEAL_NEST_LOCATION)
|
||||
mate.biggerBrain.eraseMemory(CobblemonMemories.IDEAL_NEST_LOCATION)
|
||||
}
|
||||
}
|
||||
|
||||
val entityPos = entity.blockPosition()
|
||||
val validNestPosition = findValidNestPlacement(world, entity, mate, entityPos, combinedTypes)
|
||||
|
||||
if (validNestPosition != null) {
|
||||
debug(brain, "Found ideal nest position at $validNestPosition.")
|
||||
brain.setMemory(CobblemonMemories.IDEAL_NEST_LOCATION, validNestPosition)
|
||||
mate.biggerBrain.setMemory(CobblemonMemories.IDEAL_NEST_LOCATION, validNestPosition)
|
||||
} else {
|
||||
debug(brain, "No valid ideal nest position found.")
|
||||
brain.eraseMemory(CobblemonMemories.IDEAL_NEST_LOCATION)
|
||||
mate.biggerBrain.eraseMemory(CobblemonMemories.IDEAL_NEST_LOCATION)
|
||||
}
|
||||
}
|
||||
|
||||
private fun findValidNestPlacement(world: ServerLevel, entity: PokemonEntity, mate: PokemonEntity?, entityPos: BlockPos, sharedTypes: List<WildNestType>): BlockPos? {
|
||||
return BlockPos.betweenClosed(
|
||||
entityPos.offset(-DETECTION_RADIUS, -5, -DETECTION_RADIUS),
|
||||
entityPos.offset(DETECTION_RADIUS, 5, DETECTION_RADIUS)
|
||||
).firstOrNull { pos ->
|
||||
val blockBelowState = world.getBlockState(pos)
|
||||
val nestPos = pos.above()
|
||||
val blockAbove = world.getBlockState(nestPos).block
|
||||
val blockAbove2 = world.getBlockState(nestPos.above()).block
|
||||
val placement = WildNestTypes.resolvePlacement(sharedTypes, blockBelowState, world.getFluidState(nestPos)) ?: return@firstOrNull false
|
||||
|
||||
if (!isWaterGroupCompatible(entity, mate, placement.waterlogged)) return@firstOrNull false
|
||||
|
||||
if (placement.waterlogged) {
|
||||
if (!(blockAbove == Blocks.WATER && blockAbove2 == Blocks.WATER)) return@firstOrNull false
|
||||
} else {
|
||||
if (!(blockAbove == Blocks.AIR && blockAbove2 == Blocks.AIR)) return@firstOrNull false
|
||||
}
|
||||
|
||||
if (isNearExistingNest(world, nestPos)) return@firstOrNull false
|
||||
if (!canPathfindToTarget(entity, nestPos)) return@firstOrNull false
|
||||
|
||||
return@firstOrNull true
|
||||
}?.above()
|
||||
}
|
||||
|
||||
private fun isValidNestPlacement(world: ServerLevel, entity: PokemonEntity, mate: PokemonEntity?, pos: BlockPos, sharedTypes: List<WildNestType>): Boolean {
|
||||
val blockBelowState = world.getBlockState(pos.below())
|
||||
val blockAbove = world.getBlockState(pos.above()).block
|
||||
val blockAbove2 = world.getBlockState(pos.above(2)).block
|
||||
val placement = WildNestTypes.resolvePlacement(sharedTypes, blockBelowState, world.getFluidState(pos)) ?: return false
|
||||
|
||||
if (!isWaterGroupCompatible(entity, mate, placement.waterlogged)) return false
|
||||
|
||||
if (placement.waterlogged) {
|
||||
if (!(blockAbove == Blocks.WATER && blockAbove2 == Blocks.WATER)) return false
|
||||
} else {
|
||||
if (!(blockAbove == Blocks.AIR && blockAbove2 == Blocks.AIR)) return false
|
||||
}
|
||||
|
||||
if (isNearExistingNest(world, pos)) return false
|
||||
if (!canPathfindToTarget(entity, pos)) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private fun canPathfindToTarget(entity: PathfinderMob, targetPos: BlockPos): Boolean {
|
||||
val path = entity.navigation.createPath(targetPos, 0)
|
||||
return path != null && path.distToTarget < 1.75f
|
||||
}
|
||||
|
||||
private fun isNearExistingNest(world: ServerLevel, pos: BlockPos): Boolean {
|
||||
return BlockPos.betweenClosed(
|
||||
pos.offset(-MIN_NEST_DISTANCE, -MIN_NEST_DISTANCE, -MIN_NEST_DISTANCE),
|
||||
pos.offset(MIN_NEST_DISTANCE, MIN_NEST_DISTANCE, MIN_NEST_DISTANCE)
|
||||
).any { checkPos ->
|
||||
world.getBlockEntity(checkPos) is com.cobblemon.mod.common.block.entity.NestBlockEntity
|
||||
}
|
||||
}
|
||||
|
||||
private fun isWaterGroupCompatible(
|
||||
entity: PokemonEntity,
|
||||
mate: PokemonEntity?,
|
||||
isNestWaterlogged: Boolean
|
||||
): Boolean {
|
||||
val waterEggGroups = setOf("water_1", "water_2", "water_3")
|
||||
val entityEggGroups = entity.pokemon.species.eggGroups.map { it.name.lowercase() }
|
||||
val entityHasWater = entityEggGroups.any { it in waterEggGroups }
|
||||
val entityOnlyWater = entityEggGroups.all { it in waterEggGroups }
|
||||
|
||||
val mateEggGroups = mate?.pokemon?.species?.eggGroups?.map { it.name.lowercase() } ?: emptyList()
|
||||
val mateHasWater = mateEggGroups.any { it in waterEggGroups }
|
||||
val mateOnlyWater = mateEggGroups.isNotEmpty() && mateEggGroups.all { it in waterEggGroups }
|
||||
|
||||
return if (isNestWaterlogged) {
|
||||
entityHasWater && mateHasWater
|
||||
} else {
|
||||
!(entityOnlyWater || mateOnlyWater)
|
||||
}
|
||||
}
|
||||
|
||||
private fun debug(brain: BiggerBrain, message: String) {
|
||||
brain.sendDebugMessage("[IdealNestPlacementSense] $message")
|
||||
}
|
||||
|
||||
class Config : SenseConfig {
|
||||
override fun create(entity: LivingEntity, behaviourConfigurationContext: com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext): Sense? {
|
||||
return IdealNestPlacementSense()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val DETECTION_RADIUS = 10
|
||||
private const val MIN_NEST_DISTANCE = 10
|
||||
}
|
||||
}
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.sense
|
||||
|
||||
import com.bedrockk.molang.Expression
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
|
||||
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
|
||||
import com.cobblemon.mod.common.api.ai.asSimplifiedExpression
|
||||
import com.cobblemon.mod.common.api.ai.asVariables
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
|
||||
import com.cobblemon.mod.common.api.ai.config.sense.SenseConfig
|
||||
import com.cobblemon.mod.common.api.ai.resolveInt
|
||||
import com.cobblemon.mod.common.breeding.BreedingLogicManager
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.util.asExpression
|
||||
import com.mojang.datafixers.util.Either
|
||||
import net.minecraft.server.level.ServerLevel
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.ai.memory.MemoryModuleType
|
||||
import net.minecraft.world.phys.AABB
|
||||
|
||||
class MateSense(
|
||||
private var cooldownTicks: Int = 1200
|
||||
) : Sense {
|
||||
override var ticksUntilNextExecution: Int = 1200
|
||||
override val memories = setOf(
|
||||
CobblemonMemories.POTENTIAL_MATE,
|
||||
CobblemonMemories.MATE_SCAN_COOLDOWN,
|
||||
CobblemonMemories.PATERNAL_NEST_LOCATION
|
||||
)
|
||||
|
||||
override fun merge(other: Sense): Boolean {
|
||||
if (other is MateSense) {
|
||||
this.cooldownTicks = minOf(this.cooldownTicks, other.cooldownTicks)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun tick(entity: LivingEntity, brain: BiggerBrain) {
|
||||
ticksUntilNextExecution = cooldownTicks
|
||||
if (entity !is PokemonEntity) return
|
||||
|
||||
if (brain.getMemory(CobblemonMemories.PATERNAL_NEST_LOCATION) != null) {
|
||||
return
|
||||
}
|
||||
|
||||
if (brain.getMemory(CobblemonMemories.MATE_SCAN_COOLDOWN) == true) {
|
||||
return
|
||||
}
|
||||
|
||||
brain.eraseMemory(CobblemonMemories.POTENTIAL_MATE)
|
||||
|
||||
if (entity.pokemon.breedingCooldown != 0) return
|
||||
|
||||
val world = entity.level() as? ServerLevel ?: return
|
||||
|
||||
val potentialMate = world.getEntitiesOfClass(
|
||||
PokemonEntity::class.java,
|
||||
AABB(entity.blockPosition()).inflate(DETECTION_RADIUS)
|
||||
) { nearbyEntity ->
|
||||
nearbyEntity != entity && isValidMate(entity, nearbyEntity)
|
||||
}.firstOrNull()
|
||||
|
||||
brain.setMemory(CobblemonMemories.MATE_SCAN_COOLDOWN, true, MATE_SCAN_COOLDOWN)
|
||||
|
||||
if (potentialMate != null) {
|
||||
brain.setMemory(CobblemonMemories.POTENTIAL_MATE, potentialMate)
|
||||
potentialMate.biggerBrain.setMemory(CobblemonMemories.POTENTIAL_MATE, entity)
|
||||
potentialMate.biggerBrain.setMemory(CobblemonMemories.MATE_SCAN_COOLDOWN, true, MATE_SCAN_COOLDOWN)
|
||||
} else {
|
||||
brain.eraseMemory(CobblemonMemories.POTENTIAL_MATE)
|
||||
}
|
||||
}
|
||||
|
||||
private fun isValidMate(entity: PokemonEntity, mate: PokemonEntity): Boolean {
|
||||
if (entity.isBattling || entity.isBattleClone() || (!entity.pokemon.isWild() && entity.tethering?.tetheringId == null)) return false
|
||||
if (mate.pokemon.breedingCooldown != 0) return false
|
||||
|
||||
val canBreed = BreedingLogicManager.canBreed(entity.pokemon, mate.pokemon)
|
||||
if (!canBreed) return false
|
||||
|
||||
val isCompatible = BreedingLogicManager.checkCompatibility(entity.pokemon, mate.pokemon)
|
||||
val bothWild = (entity.pokemon.isWild() && mate.pokemon.isWild())
|
||||
val bothOwned = (!entity.pokemon.isWild() && !mate.pokemon.isWild())
|
||||
val mateHasMate = mate.biggerBrain.getMemory(CobblemonMemories.POTENTIAL_MATE) != null
|
||||
|
||||
return !mateHasMate && isCompatible && (bothWild || bothOwned)
|
||||
}
|
||||
|
||||
class Config : SenseConfig {
|
||||
var cooldownTicks: ExpressionOrEntityVariable = Either.left("1200".asExpression())
|
||||
|
||||
override fun getVariables(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
) = listOf(cooldownTicks).asVariables()
|
||||
|
||||
override fun create(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): Sense {
|
||||
val cooldownTicks = cooldownTicks.resolveInt(behaviourConfigurationContext.runtime)
|
||||
return MateSense(cooldownTicks = cooldownTicks)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val DETECTION_RADIUS = 10.0
|
||||
private const val MATE_SCAN_COOLDOWN = 1200L
|
||||
}
|
||||
}
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.sense
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
|
||||
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
|
||||
import com.cobblemon.mod.common.api.ai.asVariables
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
|
||||
import com.cobblemon.mod.common.api.ai.config.sense.SenseConfig
|
||||
import com.cobblemon.mod.common.api.ai.resolveInt
|
||||
import com.cobblemon.mod.common.entity.npc.NPCEntity
|
||||
import com.cobblemon.mod.common.util.asExpression
|
||||
import com.mojang.datafixers.util.Either
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
|
||||
/**
|
||||
* A sense that checks if an NPC is currently in a battle. Maintains the NPC_BATTLING memory.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since December 29th, 2025
|
||||
*/
|
||||
class NPCBattlingSense(
|
||||
var cooldownTicks: Int = 10
|
||||
) : Sense {
|
||||
override var ticksUntilNextExecution: Int = 20
|
||||
override val memories = setOf(CobblemonMemories.NPC_BATTLING)
|
||||
|
||||
override fun merge(other: Sense): Boolean {
|
||||
if (other is NPCBattlingSense) {
|
||||
this.cooldownTicks = minOf(this.cooldownTicks, other.cooldownTicks)
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
override fun tick(entity: LivingEntity, brain: BiggerBrain) {
|
||||
if (entity !is NPCEntity) {
|
||||
return // Surely they didn't put this on a non-NPC
|
||||
}
|
||||
|
||||
val isBattling = entity.isInBattle()
|
||||
if (isBattling) {
|
||||
brain.setMemoryIfChanged(CobblemonMemories.NPC_BATTLING, value = true)
|
||||
} else {
|
||||
brain.eraseMemoryIfPresent(CobblemonMemories.NPC_BATTLING)
|
||||
}
|
||||
|
||||
ticksUntilNextExecution = cooldownTicks
|
||||
}
|
||||
|
||||
class Config : SenseConfig {
|
||||
var cooldownTicks: ExpressionOrEntityVariable = Either.left("2".asExpression())
|
||||
|
||||
override fun getVariables(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
) = listOf(cooldownTicks).asVariables()
|
||||
|
||||
override fun create(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): Sense {
|
||||
val cooldownTicks = cooldownTicks.resolveInt(behaviourConfigurationContext.runtime)
|
||||
return NPCBattlingSense(cooldownTicks = cooldownTicks)
|
||||
}
|
||||
}
|
||||
}
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.sense
|
||||
|
||||
import com.bedrockk.molang.Expression
|
||||
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
|
||||
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
|
||||
import com.cobblemon.mod.common.api.ai.asSimplifiedExpression
|
||||
import com.cobblemon.mod.common.api.ai.asVariables
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
|
||||
import com.cobblemon.mod.common.api.ai.config.sense.SenseConfig
|
||||
import com.cobblemon.mod.common.api.ai.resolveDouble
|
||||
import com.cobblemon.mod.common.util.asExpression
|
||||
import com.cobblemon.mod.common.util.resolveBoolean
|
||||
import com.mojang.datafixers.util.Either
|
||||
import java.util.function.Predicate
|
||||
import java.util.function.ToDoubleFunction
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.ai.memory.MemoryModuleType
|
||||
import net.minecraft.world.entity.ai.memory.NearestVisibleLivingEntities
|
||||
|
||||
/**
|
||||
* Senses nearby entities. Maintains the NEAREST_LIVING_ENTITIES and NEAREST_VISIBLE_LIVING_ENTITIES memories.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since December 29th, 2025
|
||||
*/
|
||||
class NearbyEntitiesSense(
|
||||
var condition: Expression,
|
||||
var radiusXZ: Double = 16.0,
|
||||
var radiusY: Double = 16.0,
|
||||
var cooldownTicks: Int = 20 * 5
|
||||
) : Sense {
|
||||
override var ticksUntilNextExecution: Int = 50
|
||||
override val memories = setOf(MemoryModuleType.NEAREST_LIVING_ENTITIES, MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES)
|
||||
|
||||
override fun merge(other: Sense): Boolean {
|
||||
if (other is NearbyEntitiesSense) {
|
||||
this.condition = other.condition
|
||||
this.cooldownTicks = minOf(this.cooldownTicks, other.cooldownTicks)
|
||||
this.radiusXZ = other.radiusXZ
|
||||
this.radiusY = other.radiusY
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
override fun tick(entity: LivingEntity, brain: BiggerBrain) {
|
||||
ticksUntilNextExecution = cooldownTicks
|
||||
if (!brain.runtime.resolveBoolean(condition)) {
|
||||
return
|
||||
}
|
||||
val level = entity.level()
|
||||
val aABB = entity.boundingBox.inflate(radiusXZ, radiusY, radiusXZ)
|
||||
val list = level.getEntitiesOfClass(LivingEntity::class.java, aABB, Predicate { it !== entity && it!!.isAlive })
|
||||
list.sortWith(Comparator.comparingDouble(ToDoubleFunction { it.distanceToSqr(entity) }))
|
||||
brain.setMemory(MemoryModuleType.NEAREST_LIVING_ENTITIES, list)
|
||||
brain.setMemory(MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES, NearestVisibleLivingEntities(entity, list))
|
||||
}
|
||||
|
||||
class Config : SenseConfig {
|
||||
var condition: ExpressionOrEntityVariable = Either.left("true".asExpression())
|
||||
var radiusXZ: ExpressionOrEntityVariable = Either.left("16.0".asExpression())
|
||||
var radiusY: ExpressionOrEntityVariable = Either.left("16.0".asExpression())
|
||||
var cooldownTicks: ExpressionOrEntityVariable = Either.left("60".asExpression())
|
||||
|
||||
override fun getVariables(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
) = listOf(
|
||||
condition,
|
||||
radiusXZ,
|
||||
radiusY,
|
||||
cooldownTicks
|
||||
).asVariables()
|
||||
|
||||
override fun create(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): Sense {
|
||||
return NearbyEntitiesSense(
|
||||
condition = condition.asSimplifiedExpression(entity),
|
||||
radiusXZ = radiusXZ.resolveDouble(behaviourConfigurationContext.runtime),
|
||||
radiusY = radiusY.resolveDouble(behaviourConfigurationContext.runtime),
|
||||
cooldownTicks = cooldownTicks.resolveDouble(behaviourConfigurationContext.runtime).toInt()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
+204
@@ -0,0 +1,204 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.sense
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
|
||||
import com.cobblemon.mod.common.api.ai.config.sense.SenseConfig
|
||||
import com.cobblemon.mod.common.api.nest.WildNestType
|
||||
import com.cobblemon.mod.common.api.nest.WildNestTypes
|
||||
import com.cobblemon.mod.common.block.WildNestBlock
|
||||
import com.cobblemon.mod.common.block.entity.NestBlockEntity
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.server.level.ServerLevel
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.PathfinderMob
|
||||
import net.minecraft.world.entity.ai.memory.MemoryModuleType
|
||||
import net.minecraft.world.item.ItemStack
|
||||
import net.minecraft.world.level.block.entity.BlockEntity
|
||||
import kotlin.jvm.optionals.getOrNull
|
||||
|
||||
class NestSense(
|
||||
private var cooldownTicks: Int = 200
|
||||
) : Sense {
|
||||
override var ticksUntilNextExecution: Int = 200
|
||||
override val memories = setOf(
|
||||
CobblemonMemories.NEST_LOCATION,
|
||||
CobblemonMemories.PATERNAL_NEST_LOCATION,
|
||||
CobblemonMemories.EGG_IN_NEST,
|
||||
CobblemonMemories.EGG_LOCATION,
|
||||
CobblemonMemories.NEST_DESPAWN_IMMUNE_UNTIL_TICK
|
||||
)
|
||||
|
||||
override fun merge(other: Sense): Boolean {
|
||||
if (other is NestSense) {
|
||||
this.cooldownTicks = minOf(this.cooldownTicks, other.cooldownTicks)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun tick(entity: LivingEntity, brain: BiggerBrain) {
|
||||
ticksUntilNextExecution = cooldownTicks
|
||||
if (entity !is PokemonEntity) return
|
||||
val world = entity.level() as? ServerLevel ?: return
|
||||
|
||||
val paternalNestLocation = brain.getMemory(CobblemonMemories.PATERNAL_NEST_LOCATION)
|
||||
|
||||
if (paternalNestLocation != null) {
|
||||
val blockAtLocation = world.getBlockEntity(paternalNestLocation)
|
||||
if (blockAtLocation !is NestBlockEntity) {
|
||||
debug(brain, "Paternal nest missing at $paternalNestLocation; clearing memories.")
|
||||
brain.eraseMemory(CobblemonMemories.PATERNAL_NEST_LOCATION)
|
||||
brain.eraseMemory(CobblemonMemories.EGG_IN_NEST)
|
||||
brain.eraseMemory(CobblemonMemories.EGG_LOCATION)
|
||||
} else if (blockAtLocation.parents.first == entity.uuid || blockAtLocation.parents.second == entity.uuid) {
|
||||
debug(brain, "Paternal nest confirmed at $paternalNestLocation.")
|
||||
brain.setMemory(CobblemonMemories.PATERNAL_NEST_LOCATION, paternalNestLocation)
|
||||
brain.setMemory(CobblemonMemories.NEST_LOCATION, paternalNestLocation)
|
||||
|
||||
if (blockAtLocation.getItem().item == ItemStack.EMPTY.item) {
|
||||
brain.setMemory(CobblemonMemories.EGG_IN_NEST, false)
|
||||
} else {
|
||||
brain.setMemory(CobblemonMemories.EGG_IN_NEST, true)
|
||||
}
|
||||
return
|
||||
} else {
|
||||
debug(brain, "Paternal nest no longer owned by this Pokémon; clearing memories.")
|
||||
brain.eraseMemory(CobblemonMemories.PATERNAL_NEST_LOCATION)
|
||||
brain.setMemory(CobblemonMemories.EGG_IN_NEST, false)
|
||||
brain.eraseMemory(CobblemonMemories.EGG_LOCATION)
|
||||
}
|
||||
}
|
||||
|
||||
val nearbyNest = findNearbyNest(entity, brain, world, entity.blockPosition())
|
||||
if (nearbyNest != null) {
|
||||
debug(brain, "Nearby nest selected at $nearbyNest.")
|
||||
brain.setMemory(CobblemonMemories.NEST_LOCATION, nearbyNest)
|
||||
} else {
|
||||
debug(brain, "No valid nearby nest found.")
|
||||
brain.eraseMemory(CobblemonMemories.NEST_LOCATION)
|
||||
}
|
||||
}
|
||||
|
||||
private fun findNearbyNest(pokemonEntity: PokemonEntity, brain: BiggerBrain, world: ServerLevel, entityPos: BlockPos): BlockPos? {
|
||||
return BlockPos.betweenClosed(
|
||||
entityPos.offset(-20, -5, -20),
|
||||
entityPos.offset(20, 5, 20)
|
||||
).firstOrNull { pos ->
|
||||
val blockEntity: BlockEntity? = world.getBlockEntity(pos)
|
||||
if (blockEntity !is NestBlockEntity) return@firstOrNull false
|
||||
|
||||
if (pokemonEntity.isBattling || pokemonEntity.isBattleClone() || (!pokemonEntity.pokemon.isWild() && pokemonEntity.tethering?.tetheringId == null)) return@firstOrNull false
|
||||
|
||||
val nestBlockEntity = blockEntity
|
||||
val allowedTypes = allowedNestTypes(pokemonEntity, brain)
|
||||
val nestType = (nestBlockEntity.blockState.block as? WildNestBlock)?.type
|
||||
if (nestType == null) {
|
||||
debug(brain, "Rejecting nest at $pos: missing WildNestBlock type.")
|
||||
return@firstOrNull false
|
||||
}
|
||||
if (!isNestTypeAllowed(nestType, pokemonEntity, allowedTypes)) {
|
||||
debug(brain, "Rejecting nest at $pos: type=$nestType not in $allowedTypes.")
|
||||
return@firstOrNull false
|
||||
}
|
||||
if (!canPathfindToTarget(pokemonEntity, nestBlockEntity.blockPos)) return@firstOrNull false
|
||||
|
||||
val isWildNest = nestBlockEntity.ownerUUID == null
|
||||
val isOwnedNest = nestBlockEntity.ownerUUID != null
|
||||
val isClaimed = nestBlockEntity.nestClaimedBy.first != null || nestBlockEntity.nestClaimedBy.second != null
|
||||
val isEggless = nestBlockEntity.getItem(0) == ItemStack.EMPTY
|
||||
val isNestWaterlogged = nestBlockEntity.blockState.getOptionalValue(net.minecraft.world.level.block.state.properties.BlockStateProperties.WATERLOGGED).orElse(false)
|
||||
val mate = brain.getMemory(CobblemonMemories.POTENTIAL_MATE) as? PokemonEntity
|
||||
val nestClaimerOne = nestBlockEntity.nestClaimedBy.first?.let { world.getEntity(it) }
|
||||
val nestClaimerTwo = nestBlockEntity.nestClaimedBy.second?.let { world.getEntity(it) }
|
||||
val isClaimerOne = pokemonEntity.uuid == nestClaimerOne?.uuid
|
||||
val isClaimerTwo = pokemonEntity.uuid == nestClaimerTwo?.uuid
|
||||
val nestParentOneActive = nestBlockEntity.parents.first?.let { world.getEntity(it) }
|
||||
val nestParentTwoActive = nestBlockEntity.parents.second?.let { world.getEntity(it) }
|
||||
|
||||
if (isWildNest && isEggless && ((nestParentOneActive != null || nestParentTwoActive != null) || (isClaimed && (nestClaimerOne != null && !isClaimerOne && nestClaimerTwo != null && !isClaimerTwo)))) {
|
||||
debug(brain, "Rejecting nest at $pos: claimed or active parents present.")
|
||||
return@firstOrNull false
|
||||
}
|
||||
|
||||
if (!isWaterGroupCompatible(pokemonEntity, mate, isNestWaterlogged)) {
|
||||
debug(brain, "Rejecting nest at $pos: water-group compatibility failed (waterlogged=$isNestWaterlogged).")
|
||||
return@firstOrNull false
|
||||
}
|
||||
|
||||
return@firstOrNull when {
|
||||
pokemonEntity.tethering?.tetheringId != null -> isOwnedNest && isEggless
|
||||
else -> isWildNest && isEggless
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun canPathfindToTarget(entity: PathfinderMob, targetPos: BlockPos): Boolean {
|
||||
val path = entity.navigation.createPath(targetPos, 0)
|
||||
return path != null && path.distToTarget < 1.75f
|
||||
}
|
||||
|
||||
private fun isWaterGroupCompatible(
|
||||
entity: PokemonEntity,
|
||||
mate: PokemonEntity?,
|
||||
isNestWaterlogged: Boolean
|
||||
): Boolean {
|
||||
val waterEggGroups = setOf("water_1", "water_2", "water_3")
|
||||
val entityEggGroups = entity.pokemon.species.eggGroups.map { it.name.lowercase() }
|
||||
val entityHasWater = entityEggGroups.any { it in waterEggGroups }
|
||||
val entityOnlyWater = entityEggGroups.all { it in waterEggGroups }
|
||||
|
||||
val mateEggGroups = mate?.pokemon?.species?.eggGroups?.map { it.name.lowercase() } ?: emptyList()
|
||||
val mateHasWater = mateEggGroups.any { it in waterEggGroups }
|
||||
val mateOnlyWater = mateEggGroups.isNotEmpty() && mateEggGroups.all { it in waterEggGroups }
|
||||
|
||||
return if (isNestWaterlogged) {
|
||||
entityHasWater && mateHasWater
|
||||
} else {
|
||||
!(entityOnlyWater || mateOnlyWater)
|
||||
}
|
||||
}
|
||||
|
||||
private fun allowedNestTypes(entity: PokemonEntity, brain: BiggerBrain): Set<WildNestType> {
|
||||
val mate = brain.getMemory(CobblemonMemories.POTENTIAL_MATE) as? PokemonEntity
|
||||
val types = if (mate != null) {
|
||||
WildNestTypes.combinedTypes(entity, mate)
|
||||
} else {
|
||||
WildNestTypes.typesFor(entity.pokemon.species)
|
||||
}
|
||||
val mutable = types.toMutableSet()
|
||||
if (!entity.pokemon.isWild() && mate != null && !mate.pokemon.isWild()) {
|
||||
mutable.add(WildNestType.PLAYER)
|
||||
}
|
||||
return mutable
|
||||
}
|
||||
|
||||
private fun isNestTypeAllowed(
|
||||
nestType: WildNestType,
|
||||
entity: PokemonEntity,
|
||||
allowedTypes: Set<WildNestType>
|
||||
): Boolean {
|
||||
if (nestType == WildNestType.PLAYER) {
|
||||
return !entity.pokemon.isWild() && allowedTypes.contains(WildNestType.PLAYER)
|
||||
}
|
||||
return allowedTypes.contains(nestType)
|
||||
}
|
||||
|
||||
private fun debug(brain: BiggerBrain, message: String) {
|
||||
brain.sendDebugMessage("[NestSense] $message")
|
||||
}
|
||||
|
||||
class Config : SenseConfig {
|
||||
override fun create(entity: LivingEntity, behaviourConfigurationContext: com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext): Sense? {
|
||||
return NestSense()
|
||||
}
|
||||
}
|
||||
}
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.sense
|
||||
|
||||
import com.bedrockk.molang.Expression
|
||||
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
|
||||
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
|
||||
import com.cobblemon.mod.common.api.ai.asSimplifiedExpression
|
||||
import com.cobblemon.mod.common.api.ai.asVariables
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
|
||||
import com.cobblemon.mod.common.api.ai.config.sense.SenseConfig
|
||||
import com.cobblemon.mod.common.api.ai.resolveInt
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.util.asExpression
|
||||
import com.cobblemon.mod.common.util.resolveBoolean
|
||||
import com.mojang.datafixers.util.Either
|
||||
import net.minecraft.world.entity.AgeableMob
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.ai.memory.MemoryModuleType
|
||||
import net.minecraft.world.entity.ai.memory.NearestVisibleLivingEntities
|
||||
|
||||
/**
|
||||
* Senses when the Pokémon is near an adult of the same species. Maintains the NEAREST_VISIBLE_ADULT memory.
|
||||
*
|
||||
* @author broccoli
|
||||
* @since May 15th, 2024
|
||||
*/
|
||||
class PokemonAdultSense(
|
||||
var condition: Expression,
|
||||
var cooldownTicks: Int = 100
|
||||
) : Sense {
|
||||
override var ticksUntilNextExecution: Int = 100
|
||||
override val memories = setOf(MemoryModuleType.NEAREST_VISIBLE_ADULT, MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES)
|
||||
|
||||
override fun merge(other: Sense): Boolean {
|
||||
if (other is PokemonAdultSense) {
|
||||
this.cooldownTicks = minOf(this.cooldownTicks, other.cooldownTicks)
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
override fun tick(entity: LivingEntity, brain: BiggerBrain) {
|
||||
ticksUntilNextExecution = cooldownTicks
|
||||
if (entity !is PokemonEntity || !brain.runtime.resolveBoolean(condition)) {
|
||||
return
|
||||
}
|
||||
|
||||
brain.getMemory(MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES)?.let { visibleMobs ->
|
||||
setNearestPokemonAdult(entity, visibleMobs, brain)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setNearestPokemonAdult(entity: PokemonEntity, visibleMobs: NearestVisibleLivingEntities, brain: BiggerBrain) {
|
||||
val nearestAdult = visibleMobs.findClosest { mob ->
|
||||
targetIsValidForChild(entity, mob)
|
||||
}.map { mob -> mob as AgeableMob }.orElse(null)
|
||||
|
||||
if (nearestAdult == null && brain.hasMemoryValue(MemoryModuleType.NEAREST_VISIBLE_ADULT)) {
|
||||
brain.eraseMemory(MemoryModuleType.NEAREST_VISIBLE_ADULT)
|
||||
} else if (nearestAdult != null && brain.getMemory(MemoryModuleType.NEAREST_VISIBLE_ADULT) != nearestAdult) {
|
||||
brain.setMemory(MemoryModuleType.NEAREST_VISIBLE_ADULT, nearestAdult)
|
||||
}
|
||||
}
|
||||
|
||||
private fun targetIsValidForChild(child: PokemonEntity, entity: LivingEntity): Boolean {
|
||||
val potentialParent = entity as? PokemonEntity ?: return false
|
||||
val childSpecies = child.pokemon.species.resourceIdentifier
|
||||
|
||||
var preEvolution = potentialParent.pokemon.preEvolution
|
||||
|
||||
while (preEvolution != null) {
|
||||
if (childSpecies == preEvolution.species.resourceIdentifier) {
|
||||
return true
|
||||
}
|
||||
|
||||
preEvolution = preEvolution.species.preEvolution
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
class Config : SenseConfig {
|
||||
var condition: ExpressionOrEntityVariable = Either.left("true".asExpression())
|
||||
var cooldownTicks: ExpressionOrEntityVariable = Either.left("40".asExpression())
|
||||
|
||||
override fun getVariables(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
) = listOf(cooldownTicks).asVariables()
|
||||
|
||||
override fun create(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): Sense {
|
||||
val cooldownTicks = cooldownTicks.resolveInt(behaviourConfigurationContext.runtime)
|
||||
return PokemonAdultSense(
|
||||
condition = condition.asSimplifiedExpression(entity),
|
||||
cooldownTicks = cooldownTicks
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.sense
|
||||
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
|
||||
import com.cobblemon.mod.common.api.ai.config.sense.SenseConfig
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.server.level.ServerLevel
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.ai.memory.MemoryModuleType
|
||||
|
||||
class PokemonDisturbanceSense(
|
||||
private var cooldownTicks: Int = 20
|
||||
) : Sense {
|
||||
override var ticksUntilNextExecution: Int = 20
|
||||
override val memories = setOf(MemoryModuleType.DISTURBANCE_LOCATION)
|
||||
|
||||
override fun merge(other: Sense): Boolean {
|
||||
if (other is PokemonDisturbanceSense) {
|
||||
this.cooldownTicks = minOf(this.cooldownTicks, other.cooldownTicks)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun tick(entity: LivingEntity, brain: BiggerBrain) {
|
||||
ticksUntilNextExecution = cooldownTicks
|
||||
val world = entity.level() as? ServerLevel ?: return
|
||||
|
||||
val nearestPlayer = world.players()
|
||||
.filter { it.isAlive && entity.distanceToSqr(it) <= 16 * 16 }
|
||||
.minByOrNull { entity.distanceToSqr(it) }
|
||||
|
||||
nearestPlayer?.let {
|
||||
brain.setMemory(MemoryModuleType.DISTURBANCE_LOCATION, BlockPos(it.blockPosition()))
|
||||
}
|
||||
}
|
||||
|
||||
class Config : SenseConfig {
|
||||
override fun create(entity: LivingEntity, behaviourConfigurationContext: com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext): Sense? {
|
||||
return PokemonDisturbanceSense()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.sense
|
||||
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
|
||||
import com.cobblemon.mod.common.entity.pokemon.ai.sensors.SweetBerryBushSensor
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.ai.memory.MemoryModuleType
|
||||
|
||||
/**
|
||||
* A sense that an entity can use to gather information and store it in its brain's memory. This is an analog to a
|
||||
* Minecraft SensorType. Unlike SensorTypes, Senses are instantiated per entity and can handle cooldowns in a more
|
||||
* bespoke way. The remaining cooldown time is something that is set by the sense itself rather than it being forced,
|
||||
* so the cooldown can differ.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since December 29th, 2025
|
||||
*/
|
||||
interface Sense {
|
||||
companion object {
|
||||
val types = mutableMapOf(
|
||||
cobblemonResource("battling_pokemon") to BattlingPokemonSense::class.java,
|
||||
cobblemonResource("drowsy") to DrowsySense::class.java,
|
||||
cobblemonResource("nearby_entities") to NearbyEntitiesSense::class.java,
|
||||
cobblemonResource("npc_battling") to NPCBattlingSense::class.java,
|
||||
cobblemonResource("pokemon_adult") to PokemonAdultSense::class.java,
|
||||
cobblemonResource("hurt_by") to HurtBySense::class.java,
|
||||
cobblemonResource("mate_sensor") to MateSense::class.java,
|
||||
cobblemonResource("nest_sensor") to NestSense::class.java,
|
||||
cobblemonResource("egg_sensor") to EggSense::class.java,
|
||||
cobblemonResource("ideal_nest_location_sensor") to IdealNestPlacementSense::class.java,
|
||||
cobblemonResource("pokemon_disturbance") to PokemonDisturbanceSense::class.java,
|
||||
cobblemonResource("hurt_by") to HurtBySense::class.java,
|
||||
cobblemonResource("nearby_sweet_berry_bush") to SweetBerryBushSense::class.java
|
||||
)
|
||||
}
|
||||
|
||||
/** The ticks until it will next be ticked. This is also displayed in the debug overlay. */
|
||||
var ticksUntilNextExecution: Int
|
||||
/** The set of memories that this sense may write to or read from. This ensures they are registered in the brain. */
|
||||
val memories: Set<MemoryModuleType<*>>
|
||||
/** Performs the sensing logic. Alter [ticksUntilNextExecution] to apply a cooldown if necessary. */
|
||||
fun tick(entity: LivingEntity, brain: BiggerBrain)
|
||||
/** Attempts to merge the [other] Sense into this one. Returns true if it was merged and therefore should not be added to the brain separately. */
|
||||
fun merge(other: Sense): Boolean
|
||||
}
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.sense
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
|
||||
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
|
||||
import com.cobblemon.mod.common.api.ai.asVariables
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
|
||||
import com.cobblemon.mod.common.api.ai.config.sense.SenseConfig
|
||||
import com.cobblemon.mod.common.api.ai.resolveInt
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.util.asExpression
|
||||
import com.cobblemon.mod.common.util.getMemorySafely
|
||||
import com.mojang.datafixers.util.Either
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.server.level.ServerLevel
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.level.block.Blocks
|
||||
import net.minecraft.world.level.block.CaveVines
|
||||
import net.minecraft.world.level.block.SweetBerryBushBlock
|
||||
import net.minecraft.world.level.block.state.BlockState
|
||||
import java.util.Optional
|
||||
|
||||
class SweetBerryBushSense(
|
||||
private var cooldownTicks: Int = SCAN_INTERVAL
|
||||
) : Sense {
|
||||
override var ticksUntilNextExecution: Int = SCAN_INTERVAL
|
||||
override val memories = setOf(
|
||||
CobblemonMemories.NEARBY_SWEET_BERRY_BUSH,
|
||||
CobblemonMemories.DISABLE_WALK_TO_BERRY_BUSH,
|
||||
)
|
||||
|
||||
override fun merge(other: Sense): Boolean {
|
||||
if (other is SweetBerryBushSense) {
|
||||
this.cooldownTicks = minOf(this.cooldownTicks, other.cooldownTicks)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun tick(entity: LivingEntity, brain: BiggerBrain) {
|
||||
ticksUntilNextExecution = cooldownTicks
|
||||
if (entity !is PokemonEntity) return
|
||||
val world = entity.level() as? ServerLevel ?: return
|
||||
|
||||
entity.brain.getMemorySafely(CobblemonMemories.DISABLE_WALK_TO_BERRY_BUSH).orElse(false) ?: return
|
||||
|
||||
val nearestBush = findNearestBush(world, entity.blockPosition())
|
||||
|
||||
if (!nearestBush.isEmpty) {
|
||||
entity.brain.setMemory(CobblemonMemories.NEARBY_SWEET_BERRY_BUSH, nearestBush)
|
||||
} else {
|
||||
entity.brain.eraseMemory(CobblemonMemories.NEARBY_SWEET_BERRY_BUSH)
|
||||
}
|
||||
}
|
||||
|
||||
private fun findNearestBush(level: ServerLevel, origin: BlockPos): Optional<BlockPos> {
|
||||
var closest: BlockPos? = null
|
||||
var closestDistSq = Double.MAX_VALUE
|
||||
|
||||
for (pos in BlockPos.betweenClosed(origin.offset(-SCAN_RADIUS_HORIZONTAL, 1 - SCAN_RADIUS_VERTICAL, -SCAN_RADIUS_HORIZONTAL),
|
||||
origin.offset(SCAN_RADIUS_HORIZONTAL, SCAN_RADIUS_VERTICAL - 1, SCAN_RADIUS_HORIZONTAL))) {
|
||||
val state: BlockState = level.getBlockState(pos)
|
||||
if ((state.`is`(Blocks.SWEET_BERRY_BUSH) && state.getValue(SweetBerryBushBlock.AGE) >= 2)
|
||||
|| (CaveVines.hasGlowBerries(state))) {
|
||||
val distSq = pos.distSqr(origin)
|
||||
if (distSq < closestDistSq) {
|
||||
closest = pos.immutable()
|
||||
closestDistSq = distSq
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Optional.ofNullable(closest)
|
||||
}
|
||||
|
||||
class Config : SenseConfig {
|
||||
var condition: ExpressionOrEntityVariable = Either.left("true".asExpression())
|
||||
var cooldownTicks: ExpressionOrEntityVariable = Either.left("40".asExpression())
|
||||
|
||||
override fun getVariables(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
) = listOf(cooldownTicks).asVariables()
|
||||
|
||||
override fun create(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): Sense {
|
||||
val cooldownTicks = cooldownTicks.resolveInt(behaviourConfigurationContext.runtime)
|
||||
return SweetBerryBushSense(
|
||||
// condition = condition.asSimplifiedExpression(entity),
|
||||
cooldownTicks = cooldownTicks
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val SCAN_RADIUS_HORIZONTAL = 10
|
||||
const val SCAN_RADIUS_VERTICAL = 1
|
||||
const val SCAN_INTERVAL = 40
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.config
|
||||
|
||||
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
|
||||
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
|
||||
import com.cobblemon.mod.common.api.ai.asVariables
|
||||
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
|
||||
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
|
||||
import com.cobblemon.mod.common.util.asExpression
|
||||
import com.mojang.datafixers.util.Either
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
|
||||
class AddObjectivesConfig : BehaviourConfig {
|
||||
val condition: ExpressionOrEntityVariable = Either.left("true".asExpression())
|
||||
val objectivesByPriority: Map<Int, List<ObjectiveConfig>> = mapOf()
|
||||
|
||||
override fun getVariables(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
): List<MoLangConfigVariable> {
|
||||
return listOf(condition).asVariables() + objectivesByPriority
|
||||
.values
|
||||
.flatten()
|
||||
.flatMap { it.getVariables(entity, behaviourConfigurationContext) }
|
||||
}
|
||||
|
||||
override fun configure(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext){
|
||||
if (!checkCondition(behaviourConfigurationContext, condition)) return
|
||||
|
||||
for ((priority, objectiveConfigs) in objectivesByPriority) {
|
||||
val objectives = objectiveConfigs.mapNotNull { it.create(entity, behaviourConfigurationContext) }
|
||||
val priorityObjectives = behaviourConfigurationContext.objectives.getOrPut(priority) { mutableListOf() }
|
||||
priorityObjectives.addAll(objectives)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.config
|
||||
|
||||
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
|
||||
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
|
||||
import com.cobblemon.mod.common.api.ai.asVariables
|
||||
import com.cobblemon.mod.common.api.ai.config.sense.SenseConfig
|
||||
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
|
||||
import com.cobblemon.mod.common.util.asExpression
|
||||
import com.mojang.datafixers.util.Either
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
|
||||
class AddSensesConfig : BehaviourConfig {
|
||||
val condition: ExpressionOrEntityVariable = Either.left("true".asExpression())
|
||||
val senses: List<SenseConfig> = listOf()
|
||||
|
||||
override fun getVariables(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
): List<MoLangConfigVariable> {
|
||||
return listOf(condition).asVariables() + senses.flatMap { it.getVariables(entity, behaviourConfigurationContext) }
|
||||
}
|
||||
|
||||
override fun configure(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
) {
|
||||
if (!checkCondition(behaviourConfigurationContext, condition)) {
|
||||
return
|
||||
}
|
||||
behaviourConfigurationContext.addSenses(senses.mapNotNull { it.create(entity, behaviourConfigurationContext) })
|
||||
}
|
||||
}
|
||||
@@ -35,9 +35,11 @@ import net.minecraft.world.entity.LivingEntity
|
||||
*/
|
||||
interface BehaviourConfig {
|
||||
companion object {
|
||||
val types = mutableMapOf<ResourceLocation, Class<out BehaviourConfig>>(
|
||||
val types = mutableMapOf(
|
||||
cobblemonResource("script") to ScriptBehaviourConfig::class.java,
|
||||
cobblemonResource("add_tasks_to_activity") to AddTasksToActivity::class.java,
|
||||
cobblemonResource("add_objectives") to AddObjectivesConfig::class.java,
|
||||
cobblemonResource("add_senses") to AddSensesConfig::class.java,
|
||||
cobblemonResource("apply_behaviours") to ApplyBehaviours::class.java,
|
||||
cobblemonResource("set_default_activity") to SetDefaultActivity::class.java,
|
||||
cobblemonResource("set_core_activities") to SetCoreActivities::class.java,
|
||||
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.config.objective
|
||||
|
||||
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.AttackTarget
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.EatGrass
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.FocusDuringBattle
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.GoToSleep
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.HarvestSweetBerryBush
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.Herd
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.LookAtNearbyEntities
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.MaintainFluidY
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.MoveIntoWater
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.Objective
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.StayAfloat
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.StayNearOwner
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.MoveOutOfWater
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.MoveToRestingPlace
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.WanderAround
|
||||
import com.cobblemon.mod.common.api.ai.config.task.AttackEggThiefTaskConfig
|
||||
import com.cobblemon.mod.common.api.ai.config.task.BreedAtNestTaskConfig
|
||||
import com.cobblemon.mod.common.api.ai.config.task.BuildNestTaskConfig
|
||||
import com.cobblemon.mod.common.api.ai.config.task.GuardNestTaskConfig
|
||||
import com.cobblemon.mod.common.api.ai.config.task.PathToIdealNestLocationTaskConfig
|
||||
import com.cobblemon.mod.common.api.ai.config.task.PathToNestTaskConfig
|
||||
import com.cobblemon.mod.common.api.ai.config.task.PickUpEggTaskConfig
|
||||
import com.cobblemon.mod.common.api.ai.config.task.PlaceEggInNestTaskConfig
|
||||
import com.cobblemon.mod.common.api.ai.config.task.RetrieveEggTaskConfig
|
||||
import com.cobblemon.mod.common.api.ai.config.task.SearchForEggTaskConfig
|
||||
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
|
||||
interface ObjectiveConfig {
|
||||
companion object {
|
||||
val types: MutableMap<ResourceLocation, Class<out ObjectiveConfig>> = mutableMapOf(
|
||||
cobblemonResource("focus_during_battle") to FocusDuringBattle.Config::class.java,
|
||||
cobblemonResource("look_at_nearby_entities") to LookAtNearbyEntities.Config::class.java,
|
||||
cobblemonResource("stay_near_owner") to StayNearOwner.Config::class.java,
|
||||
cobblemonResource("wander_around") to WanderAround.Config::class.java,
|
||||
cobblemonResource("eat_grass") to EatGrass.Config::class.java,
|
||||
cobblemonResource("herd") to Herd.Config::class.java,
|
||||
cobblemonResource("stay_afloat") to StayAfloat.Config::class.java,
|
||||
cobblemonResource("maintain_fluid_y") to MaintainFluidY.Config::class.java,
|
||||
cobblemonResource("move_out_of_water") to MoveOutOfWater.Config::class.java,
|
||||
cobblemonResource("move_into_water") to MoveIntoWater.Config::class.java,
|
||||
cobblemonResource("move_to_resting_place") to MoveToRestingPlace.Config::class.java,
|
||||
cobblemonResource("go_to_sleep") to GoToSleep.Config::class.java,
|
||||
cobblemonResource("attack_target") to AttackTarget.Config::class.java,
|
||||
cobblemonResource("path_to_nest") to PathToNestTaskConfig::class.java,
|
||||
cobblemonResource("breed_at_nest") to BreedAtNestTaskConfig::class.java,
|
||||
cobblemonResource("guard_nest") to GuardNestTaskConfig::class.java,
|
||||
cobblemonResource("search_for_egg") to SearchForEggTaskConfig::class.java,
|
||||
cobblemonResource("retrieve_egg") to RetrieveEggTaskConfig::class.java,
|
||||
cobblemonResource("pick_up_egg") to PickUpEggTaskConfig::class.java,
|
||||
cobblemonResource("place_egg_in_nest") to PlaceEggInNestTaskConfig::class.java,
|
||||
cobblemonResource("attack_egg_thief") to AttackEggThiefTaskConfig::class.java,
|
||||
cobblemonResource("path_to_ideal_nest_location") to PathToIdealNestLocationTaskConfig::class.java,
|
||||
cobblemonResource("build_nest") to BuildNestTaskConfig::class.java,
|
||||
cobblemonResource("harvest_sweet_berry_bush") to HarvestSweetBerryBush.Config::class.java,
|
||||
)
|
||||
}
|
||||
|
||||
/** The variables that this objective config uses. These are used to declare variables on the entity cleanly. */
|
||||
fun getVariables(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): List<MoLangConfigVariable>
|
||||
fun create(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): Objective?
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.config.sense
|
||||
|
||||
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.sense.BattlingPokemonSense
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.sense.DrowsySense
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.sense.EggSense
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.sense.HurtBySense
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.sense.IdealNestPlacementSense
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.sense.MateSense
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.sense.NPCBattlingSense
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.sense.NestSense
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.sense.NearbyEntitiesSense
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.sense.PokemonDisturbanceSense
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.sense.PokemonAdultSense
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.sense.Sense
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.sense.SweetBerryBushSense
|
||||
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
|
||||
/**
|
||||
* Creates a [Sense] for an entity.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since December 29th, 2025
|
||||
*/
|
||||
interface SenseConfig {
|
||||
companion object {
|
||||
val types = mutableMapOf(
|
||||
cobblemonResource("nearby_entities") to NearbyEntitiesSense.Config::class.java,
|
||||
cobblemonResource("drowsy") to DrowsySense.Config::class.java,
|
||||
cobblemonResource("hurt_by") to HurtBySense.Config::class.java,
|
||||
cobblemonResource("battling_pokemon") to BattlingPokemonSense.Config::class.java,
|
||||
cobblemonResource("npc_battling") to NPCBattlingSense.Config::class.java,
|
||||
cobblemonResource("pokemon_adult") to PokemonAdultSense.Config::class.java,
|
||||
cobblemonResource("mate_sensor") to MateSense.Config::class.java,
|
||||
cobblemonResource("nest_sensor") to NestSense.Config::class.java,
|
||||
cobblemonResource("egg_sensor") to EggSense.Config::class.java,
|
||||
cobblemonResource("ideal_nest_location_sensor") to IdealNestPlacementSense.Config::class.java,
|
||||
cobblemonResource("pokemon_disturbance") to PokemonDisturbanceSense.Config::class.java,
|
||||
cobblemonResource("pokemon_adult") to PokemonAdultSense.Config::class.java,
|
||||
cobblemonResource("nearby_sweet_berry_bush") to SweetBerryBushSense.Config::class.java
|
||||
)
|
||||
}
|
||||
|
||||
/** The variables that this sense config uses. These are used to declare variables on the entity cleanly. */
|
||||
fun getVariables(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): List<MoLangConfigVariable> = emptyList()
|
||||
fun create(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): Sense?
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.config.task
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
|
||||
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.Objective
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.AttackEggThiefTask
|
||||
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.ai.memory.MemoryModuleType
|
||||
|
||||
class AttackEggThiefTaskConfig : ObjectiveConfig {
|
||||
override fun getVariables(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): List<MoLangConfigVariable> = emptyList()
|
||||
|
||||
override fun create(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): Objective? {
|
||||
behaviourConfigurationContext.addMemories(
|
||||
CobblemonMemories.EGG_THIEF,
|
||||
CobblemonMemories.EGG_SEARCH_COUNTDOWN,
|
||||
MemoryModuleType.ANGRY_AT,
|
||||
MemoryModuleType.ATTACK_TARGET
|
||||
)
|
||||
return AttackEggThiefTask()
|
||||
}
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.config.task
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
|
||||
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
|
||||
import com.cobblemon.mod.common.api.ai.asVariables
|
||||
import com.cobblemon.mod.common.api.ai.resolveBoolean
|
||||
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.Objective
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.BreedAtNestTask
|
||||
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
|
||||
import com.cobblemon.mod.common.util.asExpression
|
||||
import com.mojang.datafixers.util.Either
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
|
||||
class BreedAtNestTaskConfig : ObjectiveConfig {
|
||||
companion object {
|
||||
const val BREED = "breed_at_nest"
|
||||
}
|
||||
|
||||
private val condition: ExpressionOrEntityVariable = Either.left("true".asExpression())
|
||||
|
||||
override fun getVariables(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): List<MoLangConfigVariable> {
|
||||
return listOf(condition).asVariables()
|
||||
}
|
||||
|
||||
override fun create(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): Objective? {
|
||||
if (!condition.resolveBoolean(behaviourConfigurationContext.runtime)) return null
|
||||
behaviourConfigurationContext.addMemories(
|
||||
CobblemonMemories.NEST_LOCATION,
|
||||
CobblemonMemories.PATERNAL_NEST_LOCATION,
|
||||
CobblemonMemories.POTENTIAL_MATE,
|
||||
CobblemonMemories.EGG_IN_NEST
|
||||
)
|
||||
return BreedAtNestTask()
|
||||
}
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.config.task
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
|
||||
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
|
||||
import com.cobblemon.mod.common.api.ai.asVariables
|
||||
import com.cobblemon.mod.common.api.ai.resolveBoolean
|
||||
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.Objective
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.BuildNestTask
|
||||
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
|
||||
import com.cobblemon.mod.common.util.asExpression
|
||||
import com.mojang.datafixers.util.Either
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.ai.memory.MemoryModuleType
|
||||
|
||||
class BuildNestTaskConfig : ObjectiveConfig {
|
||||
companion object {
|
||||
const val BREED = "breed"
|
||||
}
|
||||
|
||||
private val condition: ExpressionOrEntityVariable = Either.left("true".asExpression())
|
||||
|
||||
override fun getVariables(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): List<MoLangConfigVariable> {
|
||||
return listOf(condition).asVariables()
|
||||
}
|
||||
|
||||
override fun create(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): Objective? {
|
||||
if (!condition.resolveBoolean(behaviourConfigurationContext.runtime)) return null
|
||||
behaviourConfigurationContext.addMemories(
|
||||
CobblemonMemories.NEST_LOCATION,
|
||||
CobblemonMemories.PATERNAL_NEST_LOCATION,
|
||||
CobblemonMemories.POTENTIAL_MATE,
|
||||
CobblemonMemories.READY_TO_BUILD_NEST,
|
||||
CobblemonMemories.IDEAL_NEST_LOCATION,
|
||||
MemoryModuleType.ATTACK_TARGET
|
||||
)
|
||||
return BuildNestTask()
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -49,7 +49,7 @@ class EatGrassTaskConfig : SingleTaskConfig {
|
||||
return null
|
||||
}
|
||||
|
||||
behaviourConfigurationContext.addMemories(MemoryModuleType.WALK_TARGET, CobblemonMemories.RECENTLY_ATE_GRASS)
|
||||
// behaviourConfigurationContext.addMemories(MemoryModuleType.WALK_TARGET, CobblemonMemories.RECENTLY_ATE_GRASS)
|
||||
|
||||
return WrapperLivingEntityTask(
|
||||
EatGrassTask(
|
||||
|
||||
-1
@@ -8,7 +8,6 @@
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.config.task
|
||||
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
|
||||
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
|
||||
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.config.task
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
|
||||
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
|
||||
import com.cobblemon.mod.common.api.ai.asVariables
|
||||
import com.cobblemon.mod.common.api.ai.resolveBoolean
|
||||
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.Objective
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.GuardNestTask
|
||||
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
|
||||
import com.cobblemon.mod.common.util.asExpression
|
||||
import com.mojang.datafixers.util.Either
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.ai.memory.MemoryModuleType
|
||||
|
||||
class GuardNestTaskConfig : ObjectiveConfig {
|
||||
companion object {
|
||||
const val BREED = "breed"
|
||||
}
|
||||
|
||||
private val condition: ExpressionOrEntityVariable = Either.left("true".asExpression())
|
||||
|
||||
override fun getVariables(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): List<MoLangConfigVariable> {
|
||||
return listOf(condition).asVariables()
|
||||
}
|
||||
|
||||
override fun create(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): Objective? {
|
||||
if (!condition.resolveBoolean(behaviourConfigurationContext.runtime)) return null
|
||||
behaviourConfigurationContext.addMemories(
|
||||
CobblemonMemories.PATERNAL_NEST_LOCATION,
|
||||
CobblemonMemories.EGG_IN_NEST,
|
||||
CobblemonMemories.EGG_LOCATION,
|
||||
CobblemonMemories.EGG_THIEF,
|
||||
CobblemonMemories.IRRITATED_AT,
|
||||
MemoryModuleType.ANGRY_AT,
|
||||
MemoryModuleType.ATTACK_TARGET,
|
||||
MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES
|
||||
)
|
||||
return GuardNestTask()
|
||||
}
|
||||
}
|
||||
+149
@@ -0,0 +1,149 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.config.task
|
||||
|
||||
/*
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.CobblemonFlows.runtime
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
|
||||
import com.cobblemon.mod.common.api.ai.asVariables
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
|
||||
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.util.withQueryValue
|
||||
import com.mojang.datafixers.kinds.IdF
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.server.level.ServerLevel
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.PathfinderMob
|
||||
import net.minecraft.world.entity.ai.behavior.BehaviorControl
|
||||
import net.minecraft.world.entity.ai.behavior.BlockPosTracker
|
||||
import net.minecraft.world.entity.ai.behavior.declarative.BehaviorBuilder
|
||||
import net.minecraft.world.entity.ai.behavior.declarative.Trigger
|
||||
import net.minecraft.world.entity.ai.memory.MemoryModuleType
|
||||
import net.minecraft.world.entity.ai.memory.WalkTarget
|
||||
import net.minecraft.world.entity.player.Player
|
||||
import net.minecraft.world.phys.Vec3
|
||||
import java.util.UUID
|
||||
|
||||
class HuntPlayerTaskConfig : SingleTaskConfig {
|
||||
// todo make new variables
|
||||
companion object {
|
||||
const val BREED = "breed"
|
||||
}
|
||||
|
||||
val condition = booleanVariable(BREED, "breeds", true).asExpressible()
|
||||
val speedMultiplier = numberVariable(BREED, "speed_multiplier", 0.6).asExpressible()
|
||||
|
||||
override fun getVariables(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext) = listOf(
|
||||
condition,
|
||||
speedMultiplier
|
||||
).asVariables()
|
||||
|
||||
override fun createTask(
|
||||
entity: LivingEntity,
|
||||
brainConfigurationContext: BehaviourConfigurationContext
|
||||
): BehaviorControl<in LivingEntity>? {
|
||||
runtime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
|
||||
if (!condition.resolveBoolean() || entity !is PokemonEntity) return null
|
||||
|
||||
return BehaviorBuilder.create {
|
||||
it.group(
|
||||
it.present(MemoryModuleType.LOOK_TARGET),
|
||||
it.absent(MemoryModuleType.WALK_TARGET),
|
||||
it.present(MemoryModuleType.NEAREST_PLAYERS), // this will be null most of the time
|
||||
it.present(MemoryModuleType.ANGRY_AT),
|
||||
it.registered(MemoryModuleType.ATTACK_TARGET),
|
||||
it.registered(MemoryModuleType.DISTURBANCE_LOCATION),
|
||||
).apply(it) { lookTarget, walkTarget, nearestPlayers, angryAt, attackTarget, disturbanceLocation ->
|
||||
Trigger { world, entity, time ->
|
||||
if (entity !is PathfinderMob || !entity.isAlive || entity !is PokemonEntity) return@Trigger false
|
||||
|
||||
runtime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
|
||||
|
||||
val angryAtUuid = (angryAt.value() as? IdF<UUID?>)?.value() ?: return@Trigger false
|
||||
val disturbancePos = (disturbanceLocation.value() as? IdF<BlockPos>)?.value()
|
||||
|
||||
val players = (nearestPlayers.value() as? IdF<List<Player?>?>)?.value()
|
||||
val targetPlayer = players?.find { it?.uuid == angryAtUuid }
|
||||
|
||||
if (targetPlayer != null) {
|
||||
// If the angry player is nearby, set them as the attack target
|
||||
entity.cry()
|
||||
attackTarget.set(targetPlayer)
|
||||
return@Trigger true
|
||||
}
|
||||
|
||||
// If the angry player is not nearby, search around the Pokémon
|
||||
val maxTicksToSearch = 200L
|
||||
if (time > maxTicksToSearch) {
|
||||
// Timeout after 200 ticks
|
||||
return@Trigger false
|
||||
}
|
||||
|
||||
// If no disturbance location or it is caused by a nearby player, skip
|
||||
if (disturbancePos != null) {
|
||||
val disturberCausingDisturbance = players?.any {
|
||||
it?.position()?.distanceToSqr(Vec3.atCenterOf(disturbancePos))!! <= 4.0 * 4.0
|
||||
}
|
||||
disturberCausingDisturbance?.let { it1 ->
|
||||
if (!it1) {
|
||||
// Pathfind toward disturbance location
|
||||
walkTarget.set(WalkTarget(Vec3.atCenterOf(disturbancePos), speedMultiplier.resolveFloat(), 1))
|
||||
lookTarget.set(BlockPosTracker(Vec3.atCenterOf(disturbancePos)))
|
||||
return@Trigger true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If no valid disturbance location, pathfind randomly within proximity
|
||||
val validPosition = findValidWalkablePosition(world, entity.position(), entity, 8)
|
||||
if (validPosition == null) {
|
||||
return@Trigger false // No valid position found
|
||||
}
|
||||
|
||||
walkTarget.set(WalkTarget(validPosition, speedMultiplier.resolveFloat(), 1))
|
||||
lookTarget.set(BlockPosTracker(validPosition.add(0.0, entity.eyeHeight.toDouble(), 0.0)))
|
||||
|
||||
return@Trigger true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun findValidWalkablePosition(
|
||||
world: ServerLevel,
|
||||
center: Vec3,
|
||||
entity: PathfinderMob,
|
||||
radius: Int
|
||||
): Vec3? {
|
||||
val random = world.random
|
||||
|
||||
// Try a number of random positions within the radius
|
||||
repeat(20) { // Attempt up to 20 random positions
|
||||
val offsetX = random.nextDouble() * 2 * radius - radius
|
||||
val offsetY = random.nextDouble() * 2 * radius - radius
|
||||
val offsetZ = random.nextDouble() * 2 * radius - radius
|
||||
val candidate = center.add(offsetX, offsetY, offsetZ)
|
||||
|
||||
// Convert to block position
|
||||
val blockPos = BlockPos(candidate.x.toInt(), candidate.y.toInt(), candidate.z.toInt())
|
||||
|
||||
// Check if the position is walkable and not inside a solid block
|
||||
if (world.getBlockState(blockPos).isSolidRender(world, blockPos).not() &&
|
||||
entity.navigation.isStableDestination(blockPos)
|
||||
) {
|
||||
return Vec3.atCenterOf(blockPos) // Return the valid position
|
||||
}
|
||||
}
|
||||
|
||||
return null // No valid position found
|
||||
}
|
||||
}*/
|
||||
-1
@@ -8,7 +8,6 @@
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.config.task
|
||||
|
||||
import com.bedrockk.molang.runtime.MoLangRuntime
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.CobblemonSensors
|
||||
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
|
||||
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.config.task
|
||||
|
||||
import com.bedrockk.molang.Expression
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
|
||||
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
|
||||
import com.cobblemon.mod.common.api.ai.asSimplifiedExpression
|
||||
import com.cobblemon.mod.common.api.ai.asVariables
|
||||
import com.cobblemon.mod.common.api.ai.resolveBoolean
|
||||
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.Objective
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.PathToIdealNestLocationTask
|
||||
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
|
||||
import com.cobblemon.mod.common.util.asExpression
|
||||
import com.mojang.datafixers.util.Either
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
|
||||
class PathToIdealNestLocationTaskConfig : ObjectiveConfig {
|
||||
companion object {
|
||||
const val BREED = "breed"
|
||||
}
|
||||
|
||||
private val condition: ExpressionOrEntityVariable = Either.left("true".asExpression())
|
||||
private val speedMultiplier: ExpressionOrEntityVariable = Either.left("0.6".asExpression())
|
||||
|
||||
override fun getVariables(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): List<MoLangConfigVariable> {
|
||||
return listOf(condition, speedMultiplier).asVariables()
|
||||
}
|
||||
|
||||
override fun create(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): Objective? {
|
||||
if (!condition.resolveBoolean(behaviourConfigurationContext.runtime)) return null
|
||||
behaviourConfigurationContext.addMemories(
|
||||
CobblemonMemories.IDEAL_NEST_LOCATION,
|
||||
CobblemonMemories.READY_TO_BUILD_NEST,
|
||||
CobblemonMemories.PATERNAL_NEST_LOCATION,
|
||||
CobblemonMemories.NEST_LOCATION,
|
||||
CobblemonMemories.POTENTIAL_MATE
|
||||
)
|
||||
val speedExpression: Expression = speedMultiplier.asSimplifiedExpression(entity)
|
||||
return PathToIdealNestLocationTask(speedExpression)
|
||||
}
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.config.task
|
||||
|
||||
import com.bedrockk.molang.Expression
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
|
||||
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
|
||||
import com.cobblemon.mod.common.api.ai.asSimplifiedExpression
|
||||
import com.cobblemon.mod.common.api.ai.asVariables
|
||||
import com.cobblemon.mod.common.api.ai.resolveBoolean
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.Objective
|
||||
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
|
||||
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.PathToNestTask
|
||||
import com.cobblemon.mod.common.util.asExpression
|
||||
import com.mojang.datafixers.util.Either
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
|
||||
class PathToNestTaskConfig : ObjectiveConfig {
|
||||
companion object {
|
||||
const val BREED = "breed"
|
||||
}
|
||||
|
||||
private val condition: ExpressionOrEntityVariable = Either.left("true".asExpression())
|
||||
private val speedMultiplier: ExpressionOrEntityVariable = Either.left("0.6".asExpression())
|
||||
|
||||
override fun getVariables(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): List<MoLangConfigVariable> {
|
||||
return listOf(condition, speedMultiplier).asVariables()
|
||||
}
|
||||
|
||||
override fun create(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): Objective? {
|
||||
if (!condition.resolveBoolean(behaviourConfigurationContext.runtime)) return null
|
||||
behaviourConfigurationContext.addMemories(
|
||||
CobblemonMemories.POTENTIAL_MATE,
|
||||
CobblemonMemories.NEST_LOCATION,
|
||||
CobblemonMemories.PATERNAL_NEST_LOCATION,
|
||||
CobblemonMemories.EGG_IN_NEST,
|
||||
CobblemonMemories.EGG_LOCATION,
|
||||
CobblemonMemories.HOLDING_EGG
|
||||
)
|
||||
val speedExpression: Expression = speedMultiplier.asSimplifiedExpression(entity)
|
||||
return PathToNestTask(speedExpression)
|
||||
}
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.config.task
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
|
||||
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.Objective
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.PickUpEggTask
|
||||
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
|
||||
class PickUpEggTaskConfig : ObjectiveConfig {
|
||||
override fun getVariables(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): List<MoLangConfigVariable> = emptyList()
|
||||
|
||||
override fun create(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): Objective? {
|
||||
behaviourConfigurationContext.addMemories(
|
||||
CobblemonMemories.PATERNAL_NEST_LOCATION,
|
||||
CobblemonMemories.READY_TO_GRAB_EGG,
|
||||
CobblemonMemories.EGG_IN_NEST,
|
||||
CobblemonMemories.EGG_LOCATION,
|
||||
CobblemonMemories.HOLDING_EGG,
|
||||
CobblemonMemories.EGG_SEARCH_COUNTDOWN
|
||||
)
|
||||
return PickUpEggTask()
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.config.task
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
|
||||
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.Objective
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.PlaceEggInNestTask
|
||||
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
|
||||
class PlaceEggInNestTaskConfig : ObjectiveConfig {
|
||||
override fun getVariables(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): List<MoLangConfigVariable> = emptyList()
|
||||
|
||||
override fun create(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): Objective? {
|
||||
behaviourConfigurationContext.addMemories(
|
||||
CobblemonMemories.PATERNAL_NEST_LOCATION,
|
||||
CobblemonMemories.HOLDING_EGG,
|
||||
CobblemonMemories.EGG_IN_NEST
|
||||
)
|
||||
return PlaceEggInNestTask()
|
||||
}
|
||||
}
|
||||
-2
@@ -43,8 +43,6 @@ class PokemonWanderControlTaskConfig : SingleTaskConfig {
|
||||
val wanderControl = entity.brain.getMemorySafely(CobblemonMemories.WANDER_CONTROL).orElse(null)
|
||||
?: CobblemonWanderControl()
|
||||
|
||||
wanderControl.reset()
|
||||
|
||||
wanderControl.allowLand = (entity.behaviour.moving.walk.canWalk && !entity.behaviour.moving.walk.avoidsLand) || entity.behaviour.moving.fly.canFly
|
||||
wanderControl.allowWater = entity.behaviour.moving.swim.canSwimInWater && !entity.behaviour.moving.swim.avoidsWater
|
||||
wanderControl.allowAir = entity.behaviour.moving.fly.canFly && !entity.behaviour.moving.walk.canWalk
|
||||
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.config.task
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
|
||||
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
|
||||
import com.cobblemon.mod.common.api.ai.asVariables
|
||||
import com.cobblemon.mod.common.api.ai.resolveBoolean
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.Objective
|
||||
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
|
||||
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.RetrieveEggTask
|
||||
import com.cobblemon.mod.common.util.asExpression
|
||||
import com.mojang.datafixers.util.Either
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
|
||||
class RetrieveEggTaskConfig : ObjectiveConfig {
|
||||
companion object {
|
||||
const val BREED = "breed_at_nest"
|
||||
}
|
||||
|
||||
private val condition: ExpressionOrEntityVariable = Either.left("true".asExpression())
|
||||
|
||||
override fun getVariables(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): List<MoLangConfigVariable> {
|
||||
return listOf(condition).asVariables()
|
||||
}
|
||||
|
||||
override fun create(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): Objective? {
|
||||
if (!condition.resolveBoolean(behaviourConfigurationContext.runtime)) return null
|
||||
behaviourConfigurationContext.addMemories(
|
||||
CobblemonMemories.PATERNAL_NEST_LOCATION,
|
||||
CobblemonMemories.EGG_IN_NEST,
|
||||
CobblemonMemories.EGG_LOCATION,
|
||||
CobblemonMemories.READY_TO_GRAB_EGG
|
||||
)
|
||||
return RetrieveEggTask()
|
||||
}
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.config.task
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
|
||||
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
|
||||
import com.cobblemon.mod.common.api.ai.asVariables
|
||||
import com.cobblemon.mod.common.api.ai.resolveBoolean
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.Objective
|
||||
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
|
||||
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.SearchForEggTask
|
||||
import com.cobblemon.mod.common.util.asExpression
|
||||
import com.mojang.datafixers.util.Either
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.ai.memory.MemoryModuleType
|
||||
|
||||
class SearchForEggTaskConfig : ObjectiveConfig {
|
||||
companion object {
|
||||
const val BREED = "breed_at_nest"
|
||||
}
|
||||
|
||||
private val condition: ExpressionOrEntityVariable = Either.left("true".asExpression())
|
||||
|
||||
override fun getVariables(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): List<MoLangConfigVariable> {
|
||||
return listOf(condition).asVariables()
|
||||
}
|
||||
|
||||
override fun create(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): Objective? {
|
||||
if (!condition.resolveBoolean(behaviourConfigurationContext.runtime)) return null
|
||||
behaviourConfigurationContext.addMemories(
|
||||
CobblemonMemories.PATERNAL_NEST_LOCATION,
|
||||
CobblemonMemories.EGG_IN_NEST,
|
||||
CobblemonMemories.EGG_LOCATION,
|
||||
CobblemonMemories.HOLDING_EGG,
|
||||
CobblemonMemories.EGG_THIEF,
|
||||
CobblemonMemories.EGG_SEARCH_COUNTDOWN,
|
||||
MemoryModuleType.DISTURBANCE_LOCATION
|
||||
)
|
||||
return SearchForEggTask()
|
||||
}
|
||||
}
|
||||
@@ -96,6 +96,9 @@ interface TaskConfig {
|
||||
cobblemonResource("eat_grass") to EatGrassTaskConfig::class.java,
|
||||
cobblemonResource("find_air") to FindAirTaskConfig::class.java,
|
||||
cobblemonResource("go_to_land") to GoToLandTaskConfig::class.java,
|
||||
cobblemonResource("go_to_land") to GoToLandTaskConfig::class.java,
|
||||
/*cobblemonResource("hunt_player") to HuntPlayerTaskConfig::class.java,*/
|
||||
cobblemonResource("go_to_land") to GoToLandTaskConfig::class.java,
|
||||
cobblemonResource("path_to_hive") to PathToBeeHiveTaskConfig::class.java,
|
||||
cobblemonResource("place_honey_in_hive") to PlaceHoneyInHiveTaskConfig::class.java,
|
||||
cobblemonResource("place_honey_in_sacc_leaves") to PlaceHoneyInSaccLeavesTaskConfig::class.java,
|
||||
@@ -108,7 +111,7 @@ interface TaskConfig {
|
||||
cobblemonResource("defend_owner") to DefendOwnerTaskConfig::class.java,
|
||||
cobblemonResource("move_to_sweet_berry_bush") to MoveToSweetBerryBushTaskConfig::class.java,
|
||||
cobblemonResource("stop_moving_to_sweet_berry_bush") to StopTryingToReachSweetBerryBushTaskConfig::class.java,
|
||||
cobblemonResource("harvest_sweet_berry_bush") to HarvestSweetBerryBushTaskConfig::class.java,
|
||||
// cobblemonResource("harvest_sweet_berry_bush") to HarvestSweetBerryBushTaskConfig::class.java,
|
||||
cobblemonResource("eat_held_item") to EatHeldItemTaskConfig::class.java,
|
||||
cobblemonResource("move_to_item") to MoveToItemTaskConfig::class.java,
|
||||
cobblemonResource("stop_moving_to_item") to StopTryingToReachWantedItemTaskConfig::class.java,
|
||||
@@ -175,4 +178,4 @@ interface TaskConfig {
|
||||
fun getVariables(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): List<MoLangConfigVariable>
|
||||
/** Given the entity in construction, returns a list of tasks. */
|
||||
fun createTasks(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): List<BehaviorControl<in LivingEntity>>
|
||||
}
|
||||
}
|
||||
|
||||
+5
-14
@@ -143,13 +143,13 @@ class WanderTaskConfig : SingleTaskConfig {
|
||||
if (wanderChance <= 0 || world.random.nextFloat() > wanderChance) {
|
||||
return@Trigger false
|
||||
}
|
||||
pathCooldown.setWithExpiry(true, wanderControl.pathCooldownTicks.toLong())
|
||||
pathCooldown.setWithExpiry(true, 40L)
|
||||
val minimumHeight = minimumHeight.resolveInt(mainThreadRuntime)
|
||||
val maximumHeight = maximumHeight.resolveInt(mainThreadRuntime)
|
||||
|
||||
var attempts = 0
|
||||
var pos: BlockPos? = null
|
||||
while (attempts++ < wanderControl.maxAttempts && pos == null) {
|
||||
while (attempts++ < (wanderControl.maxAttempts ?: 4) && pos == null) {
|
||||
// val targetVec = getLandTarget(entity) ?: return@Trigger true
|
||||
val targetVec = if (maximumHeight != -1) {
|
||||
HoverRandomPos.getPos(
|
||||
@@ -189,7 +189,7 @@ class WanderTaskConfig : SingleTaskConfig {
|
||||
minimumHeight = minimumHeight,
|
||||
maximumHeight = maximumHeight,
|
||||
world = world
|
||||
).takeIf(wanderControl::isSuitable)
|
||||
)//.takeIf(wanderControl::isSuitable)
|
||||
}
|
||||
|
||||
if (pos == null) {
|
||||
@@ -201,17 +201,8 @@ class WanderTaskConfig : SingleTaskConfig {
|
||||
pos = pos,
|
||||
speedModifier = speedMultiplier.resolveFloat(mainThreadRuntime),
|
||||
completionRange = 0,
|
||||
nodeTypeFilter = { nodeType ->
|
||||
entity.canBreatheUnderwater() || nodeType !in listOf(
|
||||
PathType.WATER,
|
||||
PathType.WATER_BORDER
|
||||
)
|
||||
},
|
||||
destinationNodeTypeFilter = { nodeType ->
|
||||
!avoidsTargetingAir || nodeType !in listOf(
|
||||
PathType.OPEN
|
||||
)
|
||||
}
|
||||
nodeTypeFilter = { nodeType -> entity.canBreatheUnderwater() || nodeType !in listOf(PathType.WATER, PathType.WATER_BORDER) },
|
||||
destinationNodeTypeFilter = { nodeType -> !avoidsTargetingAir || nodeType !in listOf(PathType.OPEN) }
|
||||
)
|
||||
)
|
||||
lookTarget.set(BlockPosTracker(pos.toVec3d().add(0.0, entity.eyeHeight.toDouble(), 0.0)))
|
||||
|
||||
+4
-4
@@ -75,7 +75,7 @@ class WaterWanderTaskConfig : SingleTaskConfig {
|
||||
}
|
||||
|
||||
val wanderControl = it.tryGet(wanderControlData).orElse(null) ?: CobblemonWanderControl()
|
||||
if (!wanderControl.allowWater) {
|
||||
if (wanderControl.allowWater != true) {
|
||||
return@Trigger false
|
||||
}
|
||||
|
||||
@@ -85,17 +85,17 @@ class WaterWanderTaskConfig : SingleTaskConfig {
|
||||
return@Trigger false
|
||||
}
|
||||
|
||||
pathCooldown.setWithExpiry(true, wanderControl.pathCooldownTicks.toLong())
|
||||
pathCooldown.setWithExpiry(true, 40L)
|
||||
|
||||
var pos: BlockPos? = null
|
||||
var target: Vec3? = null
|
||||
var attempts = 0
|
||||
|
||||
while (attempts < wanderControl.maxAttempts && pos == null) {
|
||||
while (attempts < wanderControl.maxAttempts ?: 4 && pos == null) {
|
||||
attempts++
|
||||
target = BehaviorUtils.getRandomSwimmablePos(entity, horizontalRange.resolveInt(mainThreadRuntime), verticalRange.resolveInt(mainThreadRuntime))
|
||||
?: continue
|
||||
pos = BlockPos.containing(target).takeIf(wanderControl::isSuitable)
|
||||
// pos = BlockPos.containing(target).takeIf(wanderControl::isSuitable) im migrating shit
|
||||
}
|
||||
|
||||
if (pos == null || target == null) {
|
||||
|
||||
@@ -192,6 +192,11 @@ open class PokemonBattle(
|
||||
*/
|
||||
fun getActor(npc: NPCEntity) = actors.firstOrNull { it is NPCBattleActor && it.npc == npc }
|
||||
|
||||
/**
|
||||
* Gets the first battle actor who has or is the given [PokemonEntity]
|
||||
*/
|
||||
fun getActor(pokemonEntity: PokemonEntity) = actors.firstOrNull { it.uuid == pokemonEntity.uuid || it.activePokemon.any { it.battlePokemon?.entity == pokemonEntity } }
|
||||
|
||||
/**
|
||||
* Gets a [BattleActor] and an [ActiveBattlePokemon] from a pnx key, e.g. p2a
|
||||
*
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user