mirror of
https://gitlab.com/cable-mc/cobblemon.git
synced 2026-10-20 22:26:24 -04:00
Compare commits
343
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
48ff865c55 | ||
|
|
e0abdb900d | ||
|
|
1b74c2906d | ||
|
|
d3a616e308 | ||
|
|
9a06463871 | ||
|
|
46b5a046ed | ||
|
|
ab811ead56 | ||
|
|
09a549ffcd | ||
|
|
b393293025 | ||
|
|
2f87d53af6 | ||
|
|
542839ba69 | ||
|
|
efab1bba08 | ||
|
|
8f986bc326 | ||
|
|
e155d32aaf | ||
|
|
743e0b7d66 | ||
|
|
1518d703f9 | ||
|
|
af9d851d67 | ||
|
|
20ca91f1c9 | ||
|
|
6fb3c0e49a | ||
|
|
5cb4ff5f5f | ||
|
|
d035c08a5c | ||
|
|
fda7b13f50 | ||
|
|
f595724971 | ||
|
|
0511ba147b | ||
|
|
760f48d850 | ||
|
|
bf296b4921 | ||
|
|
49886389cf | ||
|
|
643bf9068b | ||
|
|
53f3258246 | ||
|
|
1e9895ec84 | ||
|
|
52dacb03ae | ||
|
|
85b0f3f393 | ||
|
|
68a760a595 | ||
|
|
a04a91bf24 | ||
|
|
5e814f7ab5 | ||
|
|
2780f491d6 | ||
|
|
2d3ec7aff1 | ||
|
|
cb7baf2696 | ||
|
|
5cc069df78 | ||
|
|
9222476c1f | ||
|
|
f220755f99 | ||
|
|
d55bbf237c | ||
|
|
83a9d44c3a | ||
|
|
c5a802eaf6 | ||
|
|
2e27875460 | ||
|
|
cd66887faa | ||
|
|
d665c87c21 | ||
|
|
044d3102b6 | ||
|
|
c421a93b06 | ||
|
|
2865aa2272 | ||
|
|
7c4aae3e9d | ||
|
|
d9f671f2a8 | ||
|
|
69733b530b | ||
|
|
d4471c30bc | ||
|
|
185f7e03f5 | ||
|
|
a45eb0eb21 | ||
|
|
ed67142379 | ||
|
|
13169c6848 | ||
|
|
ae7bc6e681 | ||
|
|
8be134ee2f | ||
|
|
ed007fe429 | ||
|
|
8c3206a413 | ||
|
|
4d4c071b95 | ||
|
|
b82c27754f | ||
|
|
63afec184e | ||
|
|
65699f045e | ||
|
|
d02441a273 | ||
|
|
f76ab784b5 | ||
|
|
a566e36754 | ||
|
|
ef1479fde6 | ||
|
|
d6c7b271e4 | ||
|
|
df9acfa0d1 | ||
|
|
9d7fc8435e | ||
|
|
ab4492ac5c | ||
|
|
cb78dc8d88 | ||
|
|
020adabe26 | ||
|
|
08aca7a274 | ||
|
|
2a9ef2b9fe | ||
|
|
90dc489547 | ||
|
|
5ec5cfbc4a | ||
|
|
1ef65449a8 | ||
|
|
c6ff1ea72e | ||
|
|
cbfa8d3e78 | ||
|
|
5098ff3cfd | ||
|
|
7c32b07d3f | ||
|
|
b2142fed1d | ||
|
|
fef21516e9 | ||
|
|
6fba0f55c1 | ||
|
|
95d43de79f | ||
|
|
0be617b8ab | ||
|
|
c02d22f449 | ||
|
|
846b645856 | ||
|
|
25d93a771c | ||
|
|
f16c782336 | ||
|
|
ed0f315e41 | ||
|
|
d20b03f4c5 | ||
|
|
a3920413ac | ||
|
|
92bccf620f | ||
|
|
15f8a9ee71 | ||
|
|
dfb9ee755e | ||
|
|
68f4117e29 | ||
|
|
f74a24a056 | ||
|
|
e5750cbba0 | ||
|
|
c6482c82cd | ||
|
|
b4c23a9132 | ||
|
|
c470894bd3 | ||
|
|
a940e32f8d | ||
|
|
b6b59eccad | ||
|
|
b520b94fae | ||
|
|
9af99d16f7 | ||
|
|
2cc17d3280 | ||
|
|
20b2bcaa5a | ||
|
|
d1ada605fd | ||
|
|
d37f73626d | ||
|
|
73a94cb02e | ||
|
|
5a48945e50 | ||
|
|
476448a2ce | ||
|
|
bcba4f7fb7 | ||
|
|
20e9608ff3 | ||
|
|
3e5733ceaf | ||
|
|
af29c99b03 | ||
|
|
60d596a24f | ||
|
|
3f993e0466 | ||
|
|
9e3d22ee54 | ||
|
|
e86cb1981f | ||
|
|
e94f265adb | ||
|
|
b3fc35240a | ||
|
|
d74618bddb | ||
|
|
8b54a8d459 | ||
|
|
c17104a0fd | ||
|
|
88ca3ebbf3 | ||
|
|
4003456e5c | ||
|
|
1d21a52d28 | ||
|
|
2d49c62f85 | ||
|
|
20fac724f6 | ||
|
|
0807d73646 | ||
|
|
3013302f35 | ||
|
|
879e7bef21 | ||
|
|
557623dfe2 | ||
|
|
a28d1e176d | ||
|
|
f3c52db70d | ||
|
|
525575b0ba | ||
|
|
a55efeb378 | ||
|
|
da18b082a6 | ||
|
|
7511facebc | ||
|
|
1486f5cfc2 | ||
|
|
f8a16e5081 | ||
|
|
558e2d89ff | ||
|
|
ea7b688655 | ||
|
|
fdafa3435c | ||
|
|
f17c90f6ea | ||
|
|
1dc45669bb | ||
|
|
f3658614a4 | ||
|
|
cad39d8802 | ||
|
|
422f60f587 | ||
|
|
8190ddf0e9 | ||
|
|
af281d0bde | ||
|
|
00905b8c32 | ||
|
|
79b3cb8611 | ||
|
|
797e908621 | ||
|
|
3df3c395de | ||
|
|
b2f682ebb1 | ||
|
|
754e9bc158 | ||
|
|
2df8504139 | ||
|
|
2838f97c56 | ||
|
|
68c75deeeb | ||
|
|
3bc2659740 | ||
|
|
89bba8a67a | ||
|
|
cbd5cdaf92 | ||
|
|
5b9d5cbb55 | ||
|
|
6d6e13be83 | ||
|
|
899bec77b6 | ||
|
|
6dd8325772 | ||
|
|
e9bffd3ad7 | ||
|
|
ff890307c2 | ||
|
|
dda9d3e790 | ||
|
|
1acc02a2ec | ||
|
|
460c472c55 | ||
|
|
69c3b6142d | ||
|
|
9a036a4c01 | ||
|
|
0fbe206893 | ||
|
|
3cea6dd175 | ||
|
|
2fa053f833 | ||
|
|
ea4f858919 | ||
|
|
2aa4ab4b52 | ||
|
|
113977990e | ||
|
|
d86021e2a1 | ||
|
|
79faadef59 | ||
|
|
9b6629db7f | ||
|
|
d151a1ce65 | ||
|
|
d9ca7b483d | ||
|
|
708f7fc1b8 | ||
|
|
621db85845 | ||
|
|
b09066c580 | ||
|
|
7c45f28827 | ||
|
|
9c1b2cae22 | ||
|
|
16b2f81bd4 | ||
|
|
1969af65b9 | ||
|
|
3bd3ce5688 | ||
|
|
108f0e3d88 | ||
|
|
4344c127eb | ||
|
|
881a87216b | ||
|
|
1d473b82af | ||
|
|
6e0c005587 | ||
|
|
5955547c6f | ||
|
|
7a3a1cb8de | ||
|
|
1674cb0913 | ||
|
|
152ce4e923 | ||
|
|
3a6d024217 | ||
|
|
3b722068b0 | ||
|
|
f2fcc5ba79 | ||
|
|
db0fb8e287 | ||
|
|
dae36a67d1 | ||
|
|
6f99862288 | ||
|
|
0377747e1e | ||
|
|
6bc974f90f | ||
|
|
aca53de259 | ||
|
|
41bde38727 | ||
|
|
a4b7ee3b38 | ||
|
|
154b653c57 | ||
|
|
04489bc969 | ||
|
|
25499b3e7a | ||
|
|
bb6bda54dc | ||
|
|
9f3dbdc2d8 | ||
|
|
932148338c | ||
|
|
e0d0c38232 | ||
|
|
309f674853 | ||
|
|
dc705c7720 | ||
|
|
9bfc577444 | ||
|
|
217d4cda42 | ||
|
|
08daa43aae | ||
|
|
184d889831 | ||
|
|
c6e0f431ae | ||
|
|
93daeab840 | ||
|
|
f132b297c6 | ||
|
|
905d63e098 | ||
|
|
1ea8ab7dcc | ||
|
|
a45d21b279 | ||
|
|
3ca6d350fe | ||
|
|
2997ce8457 | ||
|
|
ff279d8310 | ||
|
|
91d6fb62ba | ||
|
|
8f4a5b2015 | ||
|
|
07604c9a3b | ||
|
|
c0a524a718 | ||
|
|
efe8f34413 | ||
|
|
6bb23582c4 | ||
|
|
56ccb43997 | ||
|
|
f445c05333 | ||
|
|
a62485fc1f | ||
|
|
044f3080ff | ||
|
|
28b0cab906 | ||
|
|
82f34d651c | ||
|
|
5fbbcb5707 | ||
|
|
92b0ef84e9 | ||
|
|
dc8d18384c | ||
|
|
44c056ecd5 | ||
|
|
dcc707c20d | ||
|
|
580a24eac3 | ||
|
|
07b1b22c2c | ||
|
|
23078eccba | ||
|
|
8a82f35edf | ||
|
|
bec664c073 | ||
|
|
3e61424d8f | ||
|
|
c1606c6fc9 | ||
|
|
e0108cc99f | ||
|
|
c0de8a2591 | ||
|
|
b8733eed52 | ||
|
|
4aa30efec7 | ||
|
|
7930ec52fa | ||
|
|
57eb306e4f | ||
|
|
bd0511a9fb | ||
|
|
c14f3292aa | ||
|
|
8fb2b1cf3d | ||
|
|
5c7d1cbe10 | ||
|
|
3348bfc362 | ||
|
|
8db9252434 | ||
|
|
d9d4fe15cc | ||
|
|
8453e2d514 | ||
|
|
4d3070c0c2 | ||
|
|
15f273a9ef | ||
|
|
007c079cd6 | ||
|
|
9851b1e8ad | ||
|
|
c65c2610cd | ||
|
|
2be08a3fcc | ||
|
|
4cdf440eee | ||
|
|
1545ee8fc2 | ||
|
|
a009fabfe4 | ||
|
|
46e4bfdd13 | ||
|
|
e24c82860d | ||
|
|
1291dee1e1 | ||
|
|
69732be09b | ||
|
|
8ce4ed7dee | ||
|
|
33d3cc0104 | ||
|
|
33b6b519ed | ||
|
|
5ec849f4e3 | ||
|
|
6f4a2d2cd5 | ||
|
|
0bca89e77d | ||
|
|
8f9f162011 | ||
|
|
ac3bea3d5e | ||
|
|
178d4f199b | ||
|
|
933819380d | ||
|
|
d68131e1b9 | ||
|
|
ffbc379c0b | ||
|
|
7053d12508 | ||
|
|
d41442200f | ||
|
|
d2564c343e | ||
|
|
d1c19e8837 | ||
|
|
0ec3bfabad | ||
|
|
43ffb04621 | ||
|
|
eeaedf76a9 | ||
|
|
120fb4829a | ||
|
|
12b93e53a7 | ||
|
|
00b17b9764 | ||
|
|
06192f809d | ||
|
|
d09eb0d3ff | ||
|
|
416321679c | ||
|
|
72c4e6e54f | ||
|
|
d3738e3424 | ||
|
|
2e183db903 | ||
|
|
fa174b88dd | ||
|
|
0b4abe6d16 | ||
|
|
759fb0ecd8 | ||
|
|
429e927c3f | ||
|
|
643aed9133 | ||
|
|
e701642de2 | ||
|
|
a3a03e3da6 | ||
|
|
5dd3d5b552 | ||
|
|
1bae246e6c | ||
|
|
c94ef10189 | ||
|
|
dfacf1f9df | ||
|
|
06c5e34f48 | ||
|
|
1ab8b35e98 | ||
|
|
261b61ee6c | ||
|
|
5abf8e1bab | ||
|
|
7c0b463637 | ||
|
|
e156ab2432 | ||
|
|
0b46a7163b | ||
|
|
21b76c008a | ||
|
|
b7e9f8248d | ||
|
|
8b21e76a36 | ||
|
|
515471185f | ||
|
|
628e8f4e5e |
Generated
BIN
Binary file not shown.
|
After Width: | Height: | Size: 2.8 KiB |
@@ -1,11 +0,0 @@
|
||||
### Additions
|
||||
|
||||
### Changes
|
||||
- Replaced the entity AI for Pokémon and NPCs with an entirely custom one. Yeah we changed the entity AI in 1.7 too, you're remembering right.
|
||||
- Improved performance of entity AI.
|
||||
|
||||
### Fixes
|
||||
|
||||
### Developer
|
||||
|
||||
### Molang & Datapacks
|
||||
@@ -1,8 +0,0 @@
|
||||
|
||||
### Additions
|
||||
- Added Pokémon breeding for Pokémon left in pasture blocks.
|
||||
- Added nest blocks that Pokémon can lay eggs in.
|
||||
|
||||
### Developer
|
||||
- Spawning Influences now have the context of what the other buckets are when adjusting bucket weights. This will break existing influences that do bucket weight adjustment.
|
||||
- Renamed heaps of things in the spawning system to make more sense.
|
||||
+19
-73
@@ -1,101 +1,47 @@
|
||||
# Changelog
|
||||
## [1.8.2 (MONTH xth, 2026)](#1-8-2)
|
||||
## [1.8.3 (MONTH xth, 2026)](#1-8-3)
|
||||
|
||||
### Additions
|
||||
- The following items can now be placed on the ground as blocks:
|
||||
- King's Rock, Galarica Cuff, Unremarkable Teacup, Masterpiece Teacup, Chipped Pot, Cracked Pot, Sachet, Metal Powder, Quick Powder, Bright Powder, Icy Rock, Heat Rock, Smooth Rock, Damp Rock, Destiny Knot, Luminous Moss, Never-Melt Ice, Wide Lens, Scope Lens, and Zoom Lens.
|
||||
- Iron Ball, Metal Coat, Throat Spray, HP Up, Protein, Iron, Carbos, Calcium, Zinc, PP Up, PP Max, Upgrade, Dubious Disc, and Loaded Dice.
|
||||
- The following items can now be placed on various surfaces:
|
||||
- Galarica Wreath, Magnet, Flame Orb, Life Orb, Toxic Orb, Light Ball, and Smoke Ball.
|
||||
- The following items can now be placed as blocks and have additional functionality:
|
||||
- Ring Target, Metronome, and Eject Button.
|
||||
- The following items can now be worn by Players and Pokémon:
|
||||
- Galarica Wreath, Cleanse Tag, Spell Tag, Sharp Beak, Covert Cloak, Shell Helmet, and Never-Melt Ice.
|
||||
- Wearable items can now be equipped by a player by holding it and right-clicking (or rebound use/interact)
|
||||
- Wearable items that can be placed down are exempt from this behaviour.
|
||||
- You can now drag to select multiple Pokémon inside the PC menu.
|
||||
|
||||
### Pokémon Added
|
||||
#### Generation 4
|
||||
- Snover
|
||||
- Abomasnow
|
||||
- Burmy
|
||||
- Wormadam
|
||||
- Mothim
|
||||
|
||||
#### Generation 5
|
||||
- Tympole
|
||||
- Palpitoad
|
||||
- Seismitoad
|
||||
|
||||
#### Generation 6
|
||||
- Helioptile
|
||||
- Heliolisk
|
||||
|
||||
#### Generation 7
|
||||
- Grubbin
|
||||
- Charjabug
|
||||
- Vikavolt
|
||||
|
||||
#### Generation 8
|
||||
|
||||
#### Generation 9
|
||||
- Greavard
|
||||
- Houndstone
|
||||
|
||||
### Model updates for the following Pokémon
|
||||
- Cacnea
|
||||
- Cacturne
|
||||
- Binacle
|
||||
- Barbaracle
|
||||
- Glimmet
|
||||
- Glimmora
|
||||
- Deerling
|
||||
- Sawsbuck (All forms)
|
||||
- Absol
|
||||
|
||||
### Animation updates for the following Pokémon
|
||||
- Cacnea
|
||||
- Cacturne
|
||||
|
||||
### Changes
|
||||
- Updated various recipes to display in Recipe Groups within the Recipe Book
|
||||
- Updated most recipes to display more logically/consistently within the Recipe Book's categories
|
||||
- Make ride rotations less jittery in multiplayer.
|
||||
- Changes tm shelf discs to use textures instead of tinting and overlays to line up with item display
|
||||
- Blank TM can now be inserted into the Disc Shelf
|
||||
|
||||
### Fixes
|
||||
- Correctly grants "They're Minerals!" advancement when planting a type gem
|
||||
- Type Gem Blocks now have advancements to give you the recipe in the recipe book (the data for the advancements were inside the recipes somehow??)
|
||||
- Some other recipes without advancements have also been corrected
|
||||
- Corrected the TM Recipes for Charge Beam and Payday
|
||||
- Type Gem blocks now properly generate clusters off of the block.
|
||||
- Starter Screen Categories will no longer stop rendering if they are bigger than the viewport, and you are not viewing top or bottom edges
|
||||
- Random Starter option now properly handled in height calculation, so now it won't run off into other categories or be un-accessible due to scroll limits if it causes a new row
|
||||
- Mochi and Feathers no longer drop glass bottles on use
|
||||
- Fixed a rare set of concurrency bugs related to reloading packs on NeoForge; fixes a ModernFix issue
|
||||
- Removed unnecessary conditions for Mimikyu's standing pose. Hopefully fixes the rare instance of it using its shoulder animations during battle in multiplayer scenarios.
|
||||
- Fixed Rockruff line not blinking
|
||||
- Fixed Flygon sometimes using its running animation instead of walking
|
||||
- Fixed Bramblin always sleeping outside of battle
|
||||
- Fixed Dragonair using its battle idle as a normal idle
|
||||
- Fixed Lycanroc's head movement
|
||||
- Added head movement to Archaludon
|
||||
- Fixed Duraludon line not having any sleep animations
|
||||
- Fixed Diglett line not having their faint animations
|
||||
- Fixed Kingambit moving its legs while walking
|
||||
- Various Structure fixes
|
||||
- Fixed shiny pokemon under certain circumstances forgetting taught TMs
|
||||
- Fixed datapacks with item interaction evolutions that don't specify an item crashing the world on load.
|
||||
- Fixed the Pokédex evolution tree crashing when a datapack's dex entry is missing an evolution's form.
|
||||
- Fixed the Pokédex evolution tree crashing with a stack overflow on evolutions that loop back on themselves.
|
||||
- Fixed the Pokédex evolution tree crashing when looking at a dex that is not part of the "National Dex" aggregate.
|
||||
- Fixed evolution trees not building properly when a pokemon has several pokedex entries for different regions.
|
||||
- Fixed form-changing evolutions sometimes resolving to the source form in the Pokédex evolution tree.
|
||||
- Broken datapack evolutions are now logged and skipped in the Pokédex evolution tree instead of crashing.
|
||||
- Fixed friendship booster items not being recognised as such
|
||||
- Fixed conflicts with other mods that would make use of recipe books in certain ways (Farmer's Delight Cooking Pot)
|
||||
- Fixed Pokémon battles stalling when a sidemod cancels the `THROWN_POKEBALL_HIT` hit event
|
||||
- Fixed choice feature tab completion in `spawnpokemon/pokegive/pokeedit` commands
|
||||
|
||||
### Developer
|
||||
- Fixed wrong order of operations in the code flow prior to the `FOSSIL_REVIVED` event causing pokemon to appear without entity link
|
||||
- Fixed TestPartySlot command not correctly comparing the provided spec with the target Pokémon
|
||||
- Bumped NeoForge to `21.1.195` to get access to MixinExtras `0.5` and the expressions annotation
|
||||
|
||||
### Molang & Datapacks
|
||||
- A new `negate` variant for evolution requirements has been added, which will negate (invert) the provided inner condition.
|
||||
- Added Molang functions for individual Pokédex ids, wherein a subdex is passed in as a string to `dex_caught_count` etc.
|
||||
|
||||
### Particles
|
||||
|
||||
#### New Move Animations
|
||||
- Fixed `q.pokemon.nature` returning class instance instead of proper string value (`cobblemon:adamant`)
|
||||
- Fixed `q.entity.is_looking_at` result being always `1` for whatever entity passed as target
|
||||
|
||||
### Localization
|
||||
- Updated translations for:
|
||||
- Updated translations for:
|
||||
|
||||
@@ -1,223 +0,0 @@
|
||||
# Habitat Block GUI Editor - Developer Instructions
|
||||
These instructions are brought to you by AI slop with Hiroku reviewing it afterwards. It does seem fairly accurate
|
||||
though.
|
||||
|
||||
## Overview
|
||||
The Habitat Block is a spawning tool that allows creative mode players to configure custom Pokémon spawning behavior. We need a GUI editor so read below to understand what it needs to do.
|
||||
|
||||
Phases might need a bit of explanation. The idea is that each habitat block will have a current "phase", which changes every day. The number of different phases is something that can be configured per block, as well as what order the phase will move through when it changes.
|
||||
|
||||
In the case of 3 phases, the different phase orders do the following:
|
||||
SIMPLE: 1-2-3-1-2-3-1-2-3
|
||||
FIXED_RANDOM: 1-3-2-1-3-2-1-3-2 (or some other repeating pattern, based on the position of the habitat block)
|
||||
FULL_RANDOM: 1-3-1-2-3-1-3-1-2 (completely random value each day)
|
||||
|
||||
The idea is like, if you had set 3 phases for a habitat block, you could make Rattata spawn in phase 1, Patrat in phase 2, and Zigzagoon in phase 3.
|
||||
|
||||
## Entry Point
|
||||
- GUI opens when a creative mode player right-clicks a Habitat Block
|
||||
- Packet: `OpenHabitatBlockEditorPacket` sends current settings to client
|
||||
- When you're done you send a `SaveHabitatBlockSettingsPacket` back to the server with the same settings DTO instance (but modified, one would assume)
|
||||
- Settings are applied via `HabitatBlockEntity.applySettings(HabitatSettingsDTO)`
|
||||
|
||||
## Data Structure
|
||||
|
||||
### Root: `HabitatSettingsDTO`
|
||||
All settings are contained in this DTO which is sent between client and server.
|
||||
|
||||
#### Common Settings (always present)
|
||||
```kotlin
|
||||
numberOfPhases: Int = 1 // Number of spawn phases (TODO: not yet implemented)
|
||||
phaseOrder: HabitatPhaseOrder // How the passage of phases will work with each day
|
||||
levelRange: IntRange // Min..Max level for spawned Pokémon
|
||||
modifiers: String // PokemonProperties string (e.g., "shiny gender=male")
|
||||
mimicId: ResourceLocation // Block appearance (e.g., "minecraft:stone")
|
||||
```
|
||||
|
||||
#### Spawning Mode Toggle
|
||||
```kotlin
|
||||
isActivatedSpawning: Boolean // true = Activated mode, false = Natural mode
|
||||
```
|
||||
|
||||
Based on this boolean, EITHER `activatedSettings` OR `naturalSettings` will be populated (never both).
|
||||
|
||||
---
|
||||
|
||||
## Spawning Modes
|
||||
|
||||
### Mode 1: Activated Spawning (`ActivatedHabitatSettingsDTO`)
|
||||
Triggered spawning system - spawns occur based on specific triggers.
|
||||
|
||||
```kotlin
|
||||
trigger: Enum // REDSTONE, TICK, or RANDOM_TICK
|
||||
chance: Float = 1.0 // Probability (0.0-1.0) spawn attempt occurs on trigger
|
||||
cancelledNaturalSpawningRange: Int // Radius (blocks) where natural spawning is disabled
|
||||
spawnRange: Int // Radius (blocks) where spawning can occur
|
||||
maxSpawns: Int = -1 // Max concurrent spawns from this block (-1 = infinite)
|
||||
maxSpawnsPerActivation: Int = 1 // Max spawns per single trigger event
|
||||
habitatPool: HabitatPoolSettingsDTO // What Pokémon can spawn (see below)
|
||||
```
|
||||
|
||||
**Trigger Types:**
|
||||
- `REDSTONE`: Spawns on redstone pulse (rising edge)
|
||||
- `TICK`: Attempts spawn every game tick (very frequent)
|
||||
- `RANDOM_TICK`: Attempts spawn on random tick events
|
||||
|
||||
### Mode 2: Natural Spawning (`NaturalHabitatSettingsDTO`)
|
||||
Modifies the natural spawning system in the area.
|
||||
|
||||
```kotlin
|
||||
replaceSpawns: Boolean // true = only this pool spawns, false = adds to natural spawns
|
||||
rangeOfInfluence: Int // Radius (blocks) where this influences spawning
|
||||
habitatPool: HabitatPoolSettingsDTO // What Pokémon can spawn (see below)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Habitat Pool Settings (`HabitatPoolSettingsDTO`)
|
||||
|
||||
Defines what Pokémon can spawn. Can reference existing pools or define custom ones.
|
||||
|
||||
```kotlin
|
||||
id: ResourceLocation // Pool identifier
|
||||
isReference: Boolean // true = use existing pool, false = custom pool
|
||||
isActivated: Boolean // Must match parent spawning mode
|
||||
|
||||
// Only present when isReference = false:
|
||||
name: String? // Display name for custom pool
|
||||
spawns: List<HabitatSpawn>? // List of spawn entries
|
||||
```
|
||||
|
||||
**Pool Types:**
|
||||
- **Reference Pool**: `isReference = true` - Uses a pre-defined pool by ID (e.g., `cobblemon:forest_day`)
|
||||
- **Custom Pool**: `isReference = false` - Defines spawns inline with `name` and `spawns` list
|
||||
|
||||
**Custom Pool IDs**: Must start with `custom__` prefix (e.g., `cobblemon:custom__my_pool`)
|
||||
|
||||
---
|
||||
|
||||
## GUI Requirements
|
||||
|
||||
### Layout Sections
|
||||
|
||||
#### 1. Common Settings Panel
|
||||
- **Number of Phases**: Integer input greater than zero (default: 1)
|
||||
- **Phase Order**: Habitat Phase Order dropdown (default: SIMPLE)
|
||||
- **Level Range**: Two integer inputs (Min/Max) - Default: 1 to config max Pokémon level
|
||||
- **Modifiers**: Text input for PokemonProperties string
|
||||
- Examples: `shiny`, `gender=male`, `shiny gender=female level=50`
|
||||
- **Mimic Block**: Block selector/dropdown
|
||||
- Default: `minecraft:stone`
|
||||
- See `HabitatBlockEntity.mimicCycle` for suggested blocks
|
||||
|
||||
#### 2. Spawning Mode Selector
|
||||
Radio buttons or toggle:
|
||||
- **Activated Spawning** (trigger-based)
|
||||
- **Natural Spawning** (world spawning influenced)
|
||||
|
||||
#### 3. Mode-Specific Settings
|
||||
|
||||
**If Activated Spawning:**
|
||||
- **Trigger**: Dropdown (REDSTONE / TICK / RANDOM_TICK)
|
||||
- **Chance**: Float slider (0.0 - 1.0, default: 1.0)
|
||||
- **Cancel Natural Spawning Range**: Integer input (blocks)
|
||||
- **Spawn Range**: Integer input (blocks)
|
||||
- **Max Spawns**: Integer input (-1 for infinite, default: -1)
|
||||
- **Max Spawns Per Activation**: Integer input (default: 1)
|
||||
- **Habitat Pool**: Pool selector (see below)
|
||||
|
||||
**If Natural Spawning:**
|
||||
- **Replace Spawns**: Checkbox (true = exclusive, false = additive)
|
||||
- **Range of Influence**: Integer input (blocks)
|
||||
- **Habitat Pool**: Pool selector (see below)
|
||||
|
||||
#### 4. Habitat Pool Selector
|
||||
Two options:
|
||||
- **Use Existing Pool**: Dropdown of registered pools
|
||||
- Set `isReference = true`
|
||||
- Set `id` to selected pool
|
||||
- Ensure `isActivated` matches spawning mode
|
||||
- **Create Custom Pool**: Opens pool editor
|
||||
- Set `isReference = false`
|
||||
- Generate ID with `custom__` prefix
|
||||
- Set `name` and `spawns` list
|
||||
- User defines spawn entries (implementation depends on `HabitatSpawn` structure)
|
||||
|
||||
---
|
||||
|
||||
## Validation Rules
|
||||
|
||||
1. **Level Range**: Min must be ≥ 1, Max must be ≤ config max, Min ≤ Max
|
||||
2. **Number of Phases**: Must be greater than zero
|
||||
3. **Chance**: Must be between 0.0 and 1.0
|
||||
4. **Ranges**: All range values must be ≥ 0
|
||||
5. **Pool Validation**:
|
||||
- If `isActivated = true`, pool must be `ActivatedHabitatPool`
|
||||
- If `isActivated = false`, pool must be `NaturalHabitatPool`
|
||||
- If reference pool not found, server logs error and rejects settings
|
||||
6. **Custom Pool IDs**: Must start with `custom__`
|
||||
|
||||
---
|
||||
|
||||
## Network Flow
|
||||
|
||||
1. **Opening GUI**: Server sends `OpenHabitatBlockEditorPacket` with current `HabitatSettingsDTO`
|
||||
2. **Saving Changes**: Client sends updated `HabitatSettingsDTO` back to server
|
||||
3. **Server Processing**:
|
||||
- `HabitatBlockEntity.applySettings()` validates and applies changes
|
||||
- Calls `refreshFromSettings()` to regenerate spawn details
|
||||
- Marks block entity as changed and syncs to clients
|
||||
|
||||
---
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- **Creative Mode Only**: GUI should only be accessible to creative mode players
|
||||
|
||||
---
|
||||
|
||||
## Example Configurations
|
||||
|
||||
### Example 1: Redstone-Triggered Shiny Spawner
|
||||
```kotlin
|
||||
isActivatedSpawning = true
|
||||
phaseOrder = FIXED_RANDOM
|
||||
numberOfPhases = 4
|
||||
levelRange = 50..60
|
||||
modifiers = "shiny"
|
||||
mimicId = "minecraft:diamond_block"
|
||||
|
||||
activatedSettings:
|
||||
trigger = REDSTONE
|
||||
chance = 1.0
|
||||
spawnRange = 10
|
||||
maxSpawns = 1
|
||||
maxSpawnsPerActivation = 1
|
||||
habitatPool = (reference to existing pool)
|
||||
```
|
||||
|
||||
### Example 2: Natural Spawn Replacement Zone
|
||||
```kotlin
|
||||
isActivatedSpawning = false
|
||||
levelRange = 1..100
|
||||
phaseOrder = SIMPLE
|
||||
numberOfPhases = 1
|
||||
modifiers = ""
|
||||
mimicId = "minecraft:moss_block"
|
||||
|
||||
naturalSettings:
|
||||
replaceSpawns = true
|
||||
rangeOfInfluence = 32
|
||||
habitatPool = (custom pool with specific Pokémon)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Related Files
|
||||
- `HabitatBlockEntity.kt` - Main block entity logic
|
||||
- `HabitatSettingsDTO.kt` - Root settings container
|
||||
- `ActivatedHabitatSettingsDTO.kt` - Activated mode settings
|
||||
- `NaturalHabitatSettingsDTO.kt` - Natural mode settings
|
||||
- `HabitatPoolSettingsDTO.kt` - Pool configuration
|
||||
- `ActivatedHabitatSpawning.kt` - Activated spawning implementation
|
||||
- `NaturalHabitatSpawning.kt` - Natural spawning implementation
|
||||
@@ -1,9 +0,0 @@
|
||||
IVs layer
|
||||
Move layer
|
||||
Remove nature layer in favour of modification layer
|
||||
Reconsider form layer. Its timing seems more important than anything else
|
||||
Remove cosmetic layer
|
||||
Add shiny layer
|
||||
|
||||
Fix the way the client is the one deciding when hatching will happen from that TriggerEggHatchHandler
|
||||
Clean up duplication in hatchPokemon logic for nest and incubator, should be a common function in EggComponent probably for hatching into a player's party
|
||||
@@ -36,6 +36,7 @@
|
||||
- Added `cobblemon:catch_alpha_pokemon` advancement trigger and `totalAlphaCaptureCount` stat for captured Alpha Pokémon.
|
||||
|
||||
### New rideable Pokémon
|
||||
- Pidgeot
|
||||
- Crobat
|
||||
- Skarmory
|
||||
- Milotic
|
||||
@@ -106,7 +107,9 @@
|
||||
- Avalugg (Hisuian Form)
|
||||
|
||||
### Model updates for the following Pokémon
|
||||
|
||||
- Pidgey
|
||||
- Pidgeotto
|
||||
- Pidgeot
|
||||
- Sandshrew
|
||||
- Sandslash
|
||||
- Nidoking
|
||||
|
||||
@@ -0,0 +1,211 @@
|
||||
# Changelog
|
||||
## [1.8.2 (October 11th, 2026)](#1-8-2)
|
||||
|
||||
### Additions
|
||||
- The following items can now be placed on the ground as blocks:
|
||||
- King's Rock, Galarica Cuff, Unremarkable Teacup, Masterpiece Teacup, Chipped Pot, Cracked Pot, Sachet, Metal Powder, Quick Powder, Bright Powder, Soft Sand, Electirizer, Magmarizer, Icy Rock, Heat Rock, Smooth Rock, Damp Rock, Destiny Knot, Luminous Moss, Never-Melt Ice, Wide Lens, Scope Lens, and Zoom Lens.
|
||||
- I wasn't finished. Iron Ball, Metal Coat, Throat Spray, Terrain Extender, Medicinal Brew, Moomoo Milk, HP Up, Protein, Iron, Carbos, Calcium, Zinc, PP Up, PP Max, Upgrade, Dubious Disc, and Loaded Dice.
|
||||
- The following items can now be placed on various surfaces:
|
||||
- Galarica Wreath, Magnet, Flame Orb, Life Orb, Toxic Orb, Light Ball, and Smoke Ball.
|
||||
- The following items can now be placed as blocks and have additional functionality:
|
||||
- Ring Target, Metronome, and Eject Button.
|
||||
- The following items can now be worn by Players and Pokémon:
|
||||
- Galarica Wreath, Cleanse Tag, Spell Tag, Sharp Beak, Covert Cloak, Shell Helmet, and Never-Melt Ice.
|
||||
- Wearable items can now be equipped by a player by holding it and right-clicking (or whatever you've rebound use/interact to).
|
||||
- Wearable items that can be placed down are exempt from this behaviour.
|
||||
- Mint Leaves can now be placed on the ground as Leaf Litter, or in Flower Pots.
|
||||
- The recall animation now renders the ball in hand with the player's arm raised.
|
||||
- Added Oricorio Flowers.
|
||||
- Added Terrain Plants, four plants grown from Electric, Grassy, Misty, and Psychic Seeds, which glow under specific environmental conditions and can be farmed for more of their seeds.
|
||||
- Berries can now be placed in Flower Pots.
|
||||
- The evolution tree for a Pokémon can now be viewed in the Pokédex, showing the Pokémon's full family line and what's needed to evolve into each stage.
|
||||
- Added custom sounds to a bunch of items that can be placed as blocks: liquid bottles, pill bottles, powders, and dice.
|
||||
- Added paper as a new cosmetic item for Pikachu.
|
||||
- Burmy changes its cloak after a battle it took part in, based on the biome.
|
||||
- Trash Cloak in the Deep Dark, Nether and End.
|
||||
- Plant Cloak in lush biomes such as forests, jungles, plains and swamps.
|
||||
- Sandy Cloak in sandy biomes, beaches and caves.
|
||||
|
||||
### Pokémon Added
|
||||
#### Generation 4
|
||||
- Snover
|
||||
- Abomasnow
|
||||
- Burmy
|
||||
- Wormadam
|
||||
- Mothim
|
||||
|
||||
#### Generation 5
|
||||
- Tympole
|
||||
- Palpitoad
|
||||
- Seismitoad
|
||||
|
||||
#### Generation 6
|
||||
- Helioptile
|
||||
- Heliolisk
|
||||
|
||||
#### Generation 7
|
||||
- Grubbin
|
||||
- Charjabug
|
||||
- Vikavolt
|
||||
- Minior
|
||||
|
||||
#### Generation 8
|
||||
- Eternatus
|
||||
|
||||
#### Generation 9
|
||||
- Greavard
|
||||
- Houndstone
|
||||
|
||||
### Model updates for the following Pokémon
|
||||
- Sudowoodo
|
||||
- Spheal
|
||||
- Cacnea
|
||||
- Cacturne
|
||||
- Binacle
|
||||
- Barbaracle
|
||||
- Glimmet
|
||||
- Glimmora
|
||||
- Deerling
|
||||
- Sawsbuck (All forms)
|
||||
- Absol
|
||||
- Poliwag
|
||||
- Politoed
|
||||
- Ekans
|
||||
- Oddish
|
||||
- Gloom
|
||||
- Vileplume
|
||||
- Bellossom
|
||||
- Shroomish
|
||||
- Chespin
|
||||
- Quilladin
|
||||
- Chesnaught
|
||||
- Fennekin
|
||||
- Braixen
|
||||
- Delphox
|
||||
- Froakie
|
||||
- Frogadier
|
||||
- Greninja
|
||||
- Shinx
|
||||
- Luxio
|
||||
- Luxray
|
||||
- Slakoth
|
||||
- Riolu
|
||||
- Lucario
|
||||
- Corsola
|
||||
- Mime Jr.
|
||||
|
||||
### Animation updates for the following Pokémon
|
||||
- Treecko (New Shoulder Mount!)
|
||||
- Bonsly (New Shoulder Mount!)
|
||||
- Sudowoodo
|
||||
- Cacnea
|
||||
- Cacturne
|
||||
- Ekans
|
||||
- Arbok
|
||||
- Oddish
|
||||
- Gloom
|
||||
- Vileplume
|
||||
- Bellossom
|
||||
- Pichu
|
||||
- Pikachu
|
||||
- Raichu (+ Alolan)
|
||||
- Greninja
|
||||
- Slakoth
|
||||
|
||||
### Changes
|
||||
- Slowed the speed of Type Gem growth, and revamped their generation to grow in a more crystal-like shape.
|
||||
- Terrain Seeds can now be planted.
|
||||
- Terrain Seeds are now consumed during PvP and NPC battles.
|
||||
- Glow Item Frames can be crafted using Bright Powder as an alternative to Glow Ink Sacs.
|
||||
- Bright Powder can be used on Signs and Hanging Signs to make text glow, as an alternative to Glow Ink Sacs.
|
||||
- Updated various recipes to display in Recipe Groups within the Recipe Book.
|
||||
- Updated most recipes to display more logically and consistently within the Recipe Book's categories.
|
||||
- Updated the following recipes
|
||||
- Never-Melt Ice, Focus Sash, Blunder Policy, Weakness Policy, Blank TM.
|
||||
- Made ride rotations less jittery in multiplayer.
|
||||
- Changes Disc Shelf TMs to use textures instead of tinting and overlays to line up with item display.
|
||||
- Blank TM can now be inserted into the Disc Shelf.
|
||||
- Added a TM creative tab.
|
||||
- Made regional and Vivillon split-evo locations mutually exclusive.
|
||||
- Vivillon's patterns are now viewable as forms with form names and unique Pokédex entries displayed.
|
||||
- Item evolutions now unlock **all** locked evolutions whose requirements are met instead of stopping after the first.
|
||||
- Exceptions being "forced" evolutions which stop after the first one.
|
||||
- Removed Mega Drain as a TM.
|
||||
- PokeSnacks (and other area spawns) will now start the vertical offset checking from the center rather than base coordinate
|
||||
- This will help PokeSnacks to work inside caves or tighter spaces better.
|
||||
|
||||
### Fixes
|
||||
- Type Gem blocks now properly generate clusters off of the block.
|
||||
- Correctly grant "They're Minerals!" advancement when planting a Type Gem.
|
||||
- Shoulder mounted Pokémon now follow player body animations, improving their positioning with custom player animations and default actions.
|
||||
- Shoulder mounted Pokémon now sit slightly higher with chest armor equipped for better alignment, with a configurable blacklist.
|
||||
- Type Gem Blocks now have advancements to give you the recipe in the recipe book (the data for the advancements were inside the recipes somehow??).
|
||||
- Some other recipes without advancements have also been corrected.
|
||||
- Corrected the TM Recipes for Charge Beam and Payday.
|
||||
- Starter Screen Categories will no longer stop rendering if they are bigger than the viewport and you are not viewing the top or bottom edges.
|
||||
- The Random Starter option no longer runs off into other categories or becomes unaccessible due to scroll limits if it causes a new row.
|
||||
- Mochi and Feathers no longer drop glass bottles on use.
|
||||
- Fixed a rare set of concurrency bugs related to reloading packs on NeoForge; fixes a ModernFix compatibility issue.
|
||||
- Removed unnecessary conditions for Mimikyu's standing pose. Hopefully fixes the rare instance of it using its shoulder animations during battle in multiplayer scenarios.
|
||||
- Fixed Rockruff line not blinking.
|
||||
- Fixed Flygon sometimes using its running animation instead of walking.
|
||||
- Fixed Bramblin always sleeping outside of battle.
|
||||
- Fixed Dragonair using its battle idle as a normal idle.
|
||||
- Fixed Lycanroc's head movement.
|
||||
- Added head movement to Archaludon.
|
||||
- Fixed Duraludon line not having any sleep animations.
|
||||
- Fixed Diglett line not having their faint animations.
|
||||
- Fixed Kingambit moving its legs while walking.
|
||||
- Fixed Alolan Diglett's cry not playing.
|
||||
- Fixed shiny alpha Snorlax using the non-shiny texture.
|
||||
- Fixed Crabominable's head breaking when looking around.
|
||||
- Fixed Shiny Dipplin and Hydrapple's textures.
|
||||
- Various structure fixes.
|
||||
- Fixed shiny Pokémon under certain circumstances forgetting taught TMs.
|
||||
- Fixed certain Pokémon sounds sometimes being doubled when played at specific times in their animations.
|
||||
- Fixed ball capture beam origin coming from behind the ball.
|
||||
- Fixed Safari Ball catch rate outside of battle.
|
||||
- Fixed alpha eye (and other) particles not respecting the client particle density setting in video options.
|
||||
- Fixed the TM Machine UI not closing when player is moving away from it.
|
||||
- Moved the config category for alpha/shiny chance config fields for fossil machine and honey log slathering to Spawning.
|
||||
- Fixed crashes for addon species with non-Cobblemon namespaces in evolution, fishing bait, and Pokédex lookups.
|
||||
- Bees will now grow Apricorns and the Galarica Nut Bush, which were in the bee growables tag but never handled, and correctly spend their nectar when they do.
|
||||
- Saccharine Leaves can now be bone mealed.
|
||||
- Fixed equipping a stack of wearable items instead of one item of a stack.
|
||||
- Fixed TM stack consumption in TM Machine screen when a player teaches a move to a Pokémon.
|
||||
- Fixed every Miltank giving mushroom stew, not only mushroom variants.
|
||||
- Medicinal Leeks can now be eaten when they aren't being planted, matching their food properties.
|
||||
- Sweeping attacks no longer set owned Pokémon on fire (e.g. with Fire Aspect) or knock them back. Friendly Fire is now off.
|
||||
- Fixed Pokémon not losing their previous form's signature moves when changing forms (e.g. Rotom keeping Overheat after leaving its Heat form).
|
||||
- Form-specific moves gained from a form change are now only granted if the Pokémon is high enough level to learn them, and no longer create duplicates in benched moves.
|
||||
- Spinda now learns Teeter Dance at level 32 instead of via TM.
|
||||
- Fixed Oricorio Pa'u form description in the Pokédex.
|
||||
- Fixed PC playing sound on close when opened via command.
|
||||
|
||||
### Developer
|
||||
- Evolution requirements can now set `pokedexIcon` and `pokedexTooltip` directly in their JSON to control how they're shown on the Pokédex's evolution tree, for example:
|
||||
```json
|
||||
{
|
||||
"variant": "attack_defence_ratio",
|
||||
"pokedexIcon": "minecraft:diamond_sword",
|
||||
"pokedexTooltip": "cobblemon.ui.pokedex.requirement.attack_defence_ratio.tooltip"
|
||||
}
|
||||
```
|
||||
- Fixed the wrong order of operations in the code flow prior to the `FOSSIL_REVIVED` event causing Pokémon to appear without the entity link.
|
||||
- Fixed TestPartySlot command not correctly comparing the provided spec with the target Pokémon.
|
||||
|
||||
### Molang & Datapacks
|
||||
- A new `negate` variant for evolution requirements has been added, which will negate (invert) the provided inner condition.
|
||||
- Added Molang functions for individual Pokédex ids, wherein a subdex is passed in as a string to `dex_caught_count` etc.
|
||||
- Added a `battle_ended` callback, backed by the new `BATTLE_ENDED` event, which fires whenever a battle ends for any reason, alongside a multitude of flags not previously covered by `BATTLE_VICTORY`.
|
||||
- Added `battle_pokemon` to battle actor structs and `has_faced_opponents` to battle Pokémon structs.
|
||||
- Added the `cobblemon:form_change/bagworm_cloak/plant_cloak`, `sandy_cloak` and `trash_cloak` biome tags, which control Burmy's cloak after battle.
|
||||
|
||||
### Localization
|
||||
- Updated translations for:
|
||||
- Brazilian Portuguese
|
||||
- Canadian French
|
||||
- Japanese
|
||||
- Korean
|
||||
- Russian
|
||||
- Ukrainian
|
||||
@@ -8,54 +8,42 @@
|
||||
|
||||
package com.cobblemon.mod.common.mixin;
|
||||
|
||||
import com.cobblemon.mod.common.block.SaccharineLeafBlock;
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.entity.animal.Bee;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.BonemealableBlock;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.ModifyVariable;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
|
||||
|
||||
|
||||
@Mixin(targets = "net.minecraft.world.entity.animal.Bee$BeeGrowCropGoal")
|
||||
public abstract class BeeEntityMixin {
|
||||
|
||||
@Unique
|
||||
private BlockState cobblemon$result = null;
|
||||
|
||||
@Inject(method = "tick()V", at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/world/level/block/BonemealableBlock;performBonemeal(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/util/RandomSource;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/block/state/BlockState;)V",
|
||||
shift = At.Shift.BY,
|
||||
by = 2
|
||||
),
|
||||
locals = LocalCapture.CAPTURE_FAILHARD
|
||||
)
|
||||
private void injectCustomGrowth(CallbackInfo ci, int i, BlockPos blockPos, BlockState blockState, Block block) {
|
||||
if (block instanceof SaccharineLeafBlock) {
|
||||
|
||||
int age = blockState.getValue(SaccharineLeafBlock.Companion.getAGE());
|
||||
boolean waterlogged = blockState.getValue(BlockStateProperties.WATERLOGGED);
|
||||
|
||||
if (age < 2 && !waterlogged) {
|
||||
this.cobblemon$result = blockState.setValue(SaccharineLeafBlock.Companion.getAGE(), age + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@Shadow(aliases = {"this$0", "field_20373"}) @Final private Bee bee;
|
||||
|
||||
// Grows bonemealable blocks vanilla's hardcoded checks miss, returning the state so it handles the rest.
|
||||
@ModifyVariable(method = "tick", at = @At(value = "LOAD", ordinal = 0), index = 5)
|
||||
private BlockState applyCustomBlockState(BlockState before) {
|
||||
if(this.cobblemon$result != null && before == null) {
|
||||
BlockState result = this.cobblemon$result;
|
||||
this.cobblemon$result = null;
|
||||
return result;
|
||||
private BlockState cobblemon$growBonemealableBlocks(BlockState grownState, @Local(ordinal = 0) BlockState blockState, @Local BlockPos pos) {
|
||||
if (grownState != null || blockState.is(Blocks.CAVE_VINES) || blockState.is(Blocks.CAVE_VINES_PLANT)) {
|
||||
return grownState;
|
||||
}
|
||||
|
||||
return before;
|
||||
if (!(blockState.getBlock() instanceof BonemealableBlock bonemealable)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
ServerLevel level = (ServerLevel) this.bee.level();
|
||||
if (!bonemealable.isValidBonemealTarget(level, pos, blockState)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
bonemealable.performBonemeal(level, this.bee.getRandom(), pos, blockState);
|
||||
return level.getBlockState(pos);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,14 +67,14 @@ public abstract class BeeOccupantMixin {
|
||||
if (state.is(BlockTags.BEEHIVES)) {
|
||||
// position in front of the entrance, face away from the block
|
||||
var facing = state.getValue(HorizontalDirectionalBlock.FACING);
|
||||
newPos = pos.relative(facing).getBottomCenter();
|
||||
newPos = pos.relative(facing).getCenter();
|
||||
newYaw = facing.toYRot();
|
||||
if (brain.checkMemory(CobblemonMemories.HIVE_LOCATION, MemoryStatus.REGISTERED)) {
|
||||
brain.setMemory(CobblemonMemories.HIVE_LOCATION, pos); // This needs to be set in the case that the hive was picked up and moved.
|
||||
}
|
||||
} else {
|
||||
// Block has likely been destroyed
|
||||
var center = pos.getBottomCenter();
|
||||
var center = pos.getCenter();
|
||||
newPos = new Vec3(
|
||||
center.x + level.random.nextFloat() * 0.6 - 0.3,
|
||||
center.y + level.random.nextFloat() * 0.6 - 0.3,
|
||||
|
||||
@@ -15,6 +15,7 @@ import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
|
||||
import com.mojang.serialization.MapCodec;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Supplier;
|
||||
import net.minecraft.SharedConstants;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.IntTag;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
@@ -45,4 +46,24 @@ public class ChunkStorageMixin {
|
||||
int curVersion = vanillaFixed.contains(VERSION_KEY) ? vanillaFixed.getInt(VERSION_KEY) : 0;
|
||||
return DataFixTypes.CHUNK.update(CobblemonSchemas.getDATA_FIXER(), vanillaFixed, curVersion, CobblemonSchemas.DATA_VERSION);
|
||||
}
|
||||
|
||||
// Our update on the same MC version still needs its own fixes so run them here in that case.
|
||||
@Inject(
|
||||
method = "upgradeChunkTag(Lnet/minecraft/resources/ResourceKey;Ljava/util/function/Supplier;Lnet/minecraft/nbt/CompoundTag;Ljava/util/Optional;)Lnet/minecraft/nbt/CompoundTag;",
|
||||
at = @At("HEAD"),
|
||||
cancellable = true
|
||||
)
|
||||
private void cobblemon$doChunkFixOnCurrentVersion(
|
||||
ResourceKey<Level> levelKey,
|
||||
Supplier<DimensionDataStorage> storage,
|
||||
CompoundTag chunkData,
|
||||
Optional<ResourceKey<MapCodec<? extends ChunkGenerator>>> chunkGeneratorKey,
|
||||
CallbackInfoReturnable<CompoundTag> cir
|
||||
) {
|
||||
if (ChunkStorage.getVersion(chunkData) != SharedConstants.getCurrentVersion().getDataVersion().getVersion()) {
|
||||
return;
|
||||
}
|
||||
int curVersion = chunkData.contains(VERSION_KEY) ? chunkData.getInt(VERSION_KEY) : 0;
|
||||
cir.setReturnValue(DataFixTypes.CHUNK.update(CobblemonSchemas.getDATA_FIXER(), chunkData, curVersion, CobblemonSchemas.DATA_VERSION));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.mixin;
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonItemComponents;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.ItemModelShaper;
|
||||
import net.minecraft.client.resources.model.BakedModel;
|
||||
import net.minecraft.client.resources.model.ModelResourceLocation;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
@Mixin(ItemModelShaper.class)
|
||||
public class ItemModelShaperMixin {
|
||||
|
||||
@Inject(method = "getItemModel(Lnet/minecraft/world/item/ItemStack;)Lnet/minecraft/client/resources/model/BakedModel;", at = @At("HEAD"), cancellable = true)
|
||||
public void getItemModel(ItemStack stack, CallbackInfoReturnable<BakedModel> cir) {
|
||||
String eggModel = stack.get(CobblemonItemComponents.EGG_MODEL);
|
||||
if (eggModel == null) return;
|
||||
|
||||
ModelResourceLocation modelResourceLocation = ModelResourceLocation.inventory(ResourceLocation.parse("cobblemon:egg/" + eggModel));
|
||||
BakedModel bakedModel = Minecraft.getInstance().getModelManager().getModel(modelResourceLocation);
|
||||
cir.setReturnValue(bakedModel);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.mixin;
|
||||
|
||||
import net.minecraft.client.color.block.BlockColors;
|
||||
import net.minecraft.client.renderer.block.model.BlockModel;
|
||||
import net.minecraft.client.resources.model.BlockStateModelLoader;
|
||||
import net.minecraft.client.resources.model.ModelBakery;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.util.profiling.ProfilerFiller;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Mixin(ModelBakery.class)
|
||||
public abstract class ModelBakeryMixin {
|
||||
|
||||
@Final
|
||||
@Shadow
|
||||
private Map<ResourceLocation, BlockModel> modelResources;
|
||||
|
||||
@Shadow
|
||||
protected abstract void loadItemModelAndDependencies(ResourceLocation modelLocation);
|
||||
|
||||
@Inject(method = "<init>", at = @At("TAIL"))
|
||||
private void cobblemon$loadEggModels(
|
||||
BlockColors blockColors,
|
||||
ProfilerFiller profilerFiller,
|
||||
Map<ResourceLocation, BlockModel> modelResources,
|
||||
Map<ResourceLocation, List<BlockStateModelLoader.LoadedJson>> blockStateResources,
|
||||
CallbackInfo ci
|
||||
) {
|
||||
for (ResourceLocation fileLocation : this.modelResources.keySet()) {
|
||||
if (!fileLocation.getNamespace().equals("cobblemon")) continue;
|
||||
String path = fileLocation.getPath();
|
||||
|
||||
if (!path.startsWith("models/item/egg/")) continue;
|
||||
String modelPath = path.replace("models/item/", "").replace(".json", "");
|
||||
ResourceLocation modelId = new ResourceLocation("cobblemon", modelPath);
|
||||
this.loadItemModelAndDependencies(modelId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,13 +35,16 @@ import com.cobblemon.mod.common.util.DataKeys;
|
||||
import com.cobblemon.mod.common.world.gamerules.CobblemonGameRules;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.Tag;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.damagesource.DamageSource;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.EntityDimensions;
|
||||
import net.minecraft.world.entity.EntityType;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
@@ -50,6 +53,7 @@ import net.minecraft.world.entity.item.ItemEntity;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.food.FoodProperties;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.enchantment.EnchantmentHelper;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -371,4 +375,33 @@ public abstract class PlayerMixin extends LivingEntity implements ScannableEntit
|
||||
this.cobblemon$rideEyePos = rideEyePos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sweeping attacks apply post-attack enchantment effects (e.g. Fire Aspect) to every entity caught
|
||||
* in the sweep, even if the damage itself was blocked. Skip them for Pokémon immune to the attack.
|
||||
*/
|
||||
@WrapOperation(
|
||||
method = "attack",
|
||||
at = @At(value = "INVOKE", target = "Lnet/minecraft/world/item/enchantment/EnchantmentHelper;doPostAttackEffects(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/world/entity/Entity;Lnet/minecraft/world/damagesource/DamageSource;)V")
|
||||
)
|
||||
private void cobblemon$skipPostAttackEffectsOnImmunePokemon(ServerLevel level, Entity target, DamageSource source, Operation<Void> original) {
|
||||
if (target instanceof PokemonEntity pokemonEntity && pokemonEntity.isInvulnerableTo(source)) {
|
||||
return;
|
||||
}
|
||||
original.call(level, target, source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sweeping attacks also knock back every entity caught in the sweep regardless of whether it took damage.
|
||||
*/
|
||||
@WrapOperation(
|
||||
method = "attack",
|
||||
at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;knockback(DDD)V")
|
||||
)
|
||||
private void cobblemon$skipKnockbackOnImmunePokemon(LivingEntity target, double strength, double x, double z, Operation<Void> original, @Local DamageSource source) {
|
||||
if (target instanceof PokemonEntity pokemonEntity && pokemonEntity.isInvulnerableTo(source)) {
|
||||
return;
|
||||
}
|
||||
original.call(target, strength, x, z);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.mixin.client;
|
||||
|
||||
import com.cobblemon.mod.common.client.CobblemonClient;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
@Mixin(Minecraft.class)
|
||||
public class AIDebugGlowMixin {
|
||||
|
||||
@Inject(method = "shouldEntityAppearGlowing", at = @At("HEAD"), cancellable = true)
|
||||
public void cobblemon$debugEntityGlow(Entity entity, CallbackInfoReturnable<Boolean> cir) {
|
||||
if (entity.getId() == CobblemonClient.INSTANCE.getDebuggedEntityId()) {
|
||||
cir.setReturnValue(true);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+7
-17
@@ -16,6 +16,7 @@ import com.cobblemon.mod.common.item.crafting.CookingPotShapelessRecipe;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import net.minecraft.client.ClientRecipeBook;
|
||||
import net.minecraft.client.RecipeBookCategories;
|
||||
import net.minecraft.client.gui.screens.recipebook.RecipeCollection;
|
||||
@@ -63,23 +64,12 @@ public abstract class ClientRecipeBookMixin {
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "setupCollections", at = @At("RETURN"))
|
||||
private void addCustomAggregateCategories(Iterable<RecipeHolder<?>> recipes, RegistryAccess registryAccess, CallbackInfo ci) {
|
||||
Map<RecipeBookCategories, List<List<RecipeHolder<?>>>> map = categorizeAndGroupRecipes(recipes);
|
||||
Map<RecipeBookCategories, List<RecipeCollection>> map2 = Maps.newHashMap();
|
||||
ImmutableList.Builder<RecipeCollection> builder = ImmutableList.builder();
|
||||
|
||||
map.forEach((recipeBookCategories, list) -> {
|
||||
List<RecipeCollection> collections = list.stream()
|
||||
.map(listx -> new RecipeCollection(registryAccess, listx))
|
||||
.peek(builder::add)
|
||||
.collect(ImmutableList.toImmutableList());
|
||||
map2.put(recipeBookCategories, collections);
|
||||
});
|
||||
RecipeBookCategories.AGGREGATE_CATEGORIES.forEach((recipeBookCategories, list) -> map2.put(recipeBookCategories, (List) list.stream().flatMap((recipeBookCategoriesx) -> ((List) map2.getOrDefault(recipeBookCategoriesx, ImmutableList.of())).stream()).collect(ImmutableList.toImmutableList())));
|
||||
@Inject(method = "setupCollections",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lcom/google/common/collect/ImmutableMap;copyOf(Ljava/util/Map;)Lcom/google/common/collect/ImmutableMap;"
|
||||
))
|
||||
private void addCustomAggregateCategories(Iterable<RecipeHolder<?>> recipes, RegistryAccess registryAccess, CallbackInfo ci, @Local(name = "map2") Map<RecipeBookCategories, List<RecipeCollection>> map2) {
|
||||
CobblemonRecipeCategories.Companion.getCustomAggregateCategories().forEach((recipeBookCategories, list) -> map2.put(recipeBookCategories, (List) list.stream().flatMap((recipeBookCategoriesx) -> ((List) map2.getOrDefault(recipeBookCategoriesx, ImmutableList.of())).stream()).collect(ImmutableList.toImmutableList())));
|
||||
|
||||
this.collectionsByTab = ImmutableMap.copyOf(map2);
|
||||
this.allCollections = builder.build();
|
||||
}
|
||||
}
|
||||
@@ -10,11 +10,13 @@ package com.cobblemon.mod.common.mixin.client;
|
||||
|
||||
import com.cobblemon.mod.common.OrientationControllable;
|
||||
import com.cobblemon.mod.common.client.render.player.MountedPlayerRenderer;
|
||||
import com.cobblemon.mod.common.client.render.player.RecallArmAnimationTracker;
|
||||
import com.cobblemon.mod.common.duck.RidePassenger;
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
|
||||
import net.minecraft.client.model.HumanoidModel;
|
||||
import net.minecraft.client.model.geom.ModelPart;
|
||||
import net.minecraft.client.player.AbstractClientPlayer;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
@@ -96,4 +98,14 @@ public class HumanoidModelMixin {
|
||||
MountedPlayerRenderer.INSTANCE.animate((HumanoidModel<AbstractClientPlayer>)(Object)this, pokemonEntity, player, netHeadYaw, headPitch, ageInTicks, limbSwing, limbSwingAmount);
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "setupAnim*", at = @At("TAIL"))
|
||||
public void cobblemon$poseRecallArm(LivingEntity entity, float limbSwing, float limbSwingAmount, float ageInTicks, float netHeadYaw, float headPitch, CallbackInfo ci) {
|
||||
if (!(entity instanceof AbstractClientPlayer) || entity.getVehicle() instanceof PokemonEntity) return;
|
||||
float raise = RecallArmAnimationTracker.INSTANCE.raiseFactor(entity.getUUID());
|
||||
if (raise <= 0f) return;
|
||||
this.rightArm.xRot = Mth.lerp(raise, this.rightArm.xRot, (float) (-Math.PI / 2.0));
|
||||
this.rightArm.yRot = Mth.lerp(raise, this.rightArm.yRot, 0f);
|
||||
this.rightArm.zRot = Mth.lerp(raise, this.rightArm.zRot, 0f);
|
||||
}
|
||||
}
|
||||
@@ -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.mixin.client;
|
||||
|
||||
import com.cobblemon.mod.common.client.render.player.RecallArmAnimationTracker;
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.client.renderer.entity.layers.ItemInHandLayer;
|
||||
import net.minecraft.world.entity.HumanoidArm;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.item.ItemDisplayContext;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(ItemInHandLayer.class)
|
||||
public class ItemInHandLayerMixin {
|
||||
@Inject(method = "renderArmWithItem", at = @At("HEAD"), cancellable = true)
|
||||
private void cobblemon$hideRecallHandItem(LivingEntity entity, ItemStack itemStack, ItemDisplayContext itemDisplayContext, HumanoidArm humanoidArm, PoseStack poseStack, MultiBufferSource multiBufferSource, int light, CallbackInfo ci) {
|
||||
if (humanoidArm == HumanoidArm.RIGHT && RecallArmAnimationTracker.INSTANCE.isRecalling(entity.getUUID())) {
|
||||
ci.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
+4
@@ -9,6 +9,7 @@
|
||||
package com.cobblemon.mod.common.mixin.client;
|
||||
|
||||
import com.cobblemon.mod.common.OrientationControllable;
|
||||
import com.cobblemon.mod.common.client.render.player.FirstPersonRecallBallRenderer;
|
||||
import com.cobblemon.mod.common.item.PokedexItem;
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import net.minecraft.client.Minecraft;
|
||||
@@ -31,6 +32,9 @@ public class ItemInHandRendererMixin {
|
||||
if (abstractClientPlayer.isUsingItem() && abstractClientPlayer.getUseItem().getItem() instanceof PokedexItem) {
|
||||
ci.cancel();
|
||||
}
|
||||
else if (interactionHand == InteractionHand.MAIN_HAND && FirstPersonRecallBallRenderer.INSTANCE.render(poseStack, multiBufferSource, j, f)) {
|
||||
ci.cancel();
|
||||
}
|
||||
else if (abstractClientPlayer instanceof OrientationControllable controllable && controllable.getOrientationController().isActive()) {
|
||||
|
||||
}
|
||||
|
||||
@@ -12,11 +12,9 @@ import com.cobblemon.mod.common.Cobblemon;
|
||||
import com.cobblemon.mod.common.CobblemonItemComponents;
|
||||
import com.cobblemon.mod.common.CobblemonItems;
|
||||
import com.cobblemon.mod.common.ModAPI;
|
||||
import com.cobblemon.mod.common.api.breeding.EggTextures;
|
||||
import com.cobblemon.mod.common.item.PokeBallItem;
|
||||
import com.cobblemon.mod.common.item.PokedexItem;
|
||||
import com.cobblemon.mod.common.item.WearableItem;
|
||||
import com.cobblemon.mod.common.item.components.EggComponent;
|
||||
import com.cobblemon.mod.common.item.components.TMMoveComponent;
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import net.minecraft.client.multiplayer.ClientLevel;
|
||||
@@ -58,29 +56,20 @@ public abstract class ItemRendererMixin {
|
||||
at = @At("HEAD"),
|
||||
cancellable = true
|
||||
)
|
||||
private void cobblemon$overrideItemModel(ItemStack stack, ItemDisplayContext renderMode, boolean leftHanded, PoseStack matrices, MultiBufferSource multiBufferSource, int light, int overlay, BakedModel model, CallbackInfo ci) throws Exception {
|
||||
boolean shouldBe2d = renderMode == ItemDisplayContext.GUI || renderMode == ItemDisplayContext.FIXED || stack.getItem() == CobblemonItems.POKEMON_EGG || stack.getItem() == CobblemonItems.TECHNICAL_MACHINE;
|
||||
private void cobblemon$overrideItemModel(ItemStack stack, ItemDisplayContext renderMode, boolean leftHanded, PoseStack matrices, MultiBufferSource multiBufferSource, int light, int overlay, BakedModel model, CallbackInfo ci) {
|
||||
boolean shouldBe2d = renderMode == ItemDisplayContext.GUI || renderMode == ItemDisplayContext.FIXED || stack.getItem() == CobblemonItems.TECHNICAL_MACHINE;
|
||||
ResourceLocation resourceLocation = null;
|
||||
if (shouldBe2d) {
|
||||
if (stack.getItem() instanceof PokeBallItem pokeBallItem) {
|
||||
resourceLocation = pokeBallItem.getPokeBall().getModel2d();
|
||||
} else if (stack.getItem() instanceof PokedexItem pokedexItem) {
|
||||
resourceLocation = pokedexItem.getType().getItemSpritePath();
|
||||
} else if (stack.getItem() == CobblemonItems.POKEMON_EGG) {
|
||||
EggComponent component = stack.get(CobblemonItemComponents.EGG_DATA);
|
||||
if (component != null) {
|
||||
resourceLocation = EggTextures.getEggTexture(component.getIdentifier(), component.getVariant());
|
||||
}
|
||||
} else if (stack.getItem() == CobblemonItems.TECHNICAL_MACHINE) {
|
||||
if (stack.getItem() instanceof PokeBallItem pokeBallItem) resourceLocation = pokeBallItem.getPokeBall().getModel2d();
|
||||
else if (stack.getItem() instanceof PokedexItem pokedexItem) resourceLocation = pokedexItem.getType().getItemSpritePath();
|
||||
else if (stack.getItem() == CobblemonItems.TECHNICAL_MACHINE) {
|
||||
TMMoveComponent component = stack.get(CobblemonItemComponents.TM_MOVE);
|
||||
if (component != null) {
|
||||
resourceLocation = new ResourceLocation(Cobblemon.MODID, "tm/" + component.getMove().getElementalType().showdownId() + "_tm");
|
||||
}
|
||||
}
|
||||
}
|
||||
if (renderMode != ItemDisplayContext.HEAD && stack.getItem() instanceof WearableItem wearableItem) {
|
||||
resourceLocation = wearableItem.getModel2d();
|
||||
}
|
||||
if (renderMode != ItemDisplayContext.HEAD && stack.getItem() instanceof WearableItem wearableItem) resourceLocation = wearableItem.getModel2d();
|
||||
|
||||
if (resourceLocation != null) {
|
||||
BakedModel replacementModel = this.itemModelShaper.getModelManager().getModel(new ModelResourceLocation(resourceLocation, "inventory"));
|
||||
|
||||
+13
-43
@@ -9,55 +9,25 @@
|
||||
package com.cobblemon.mod.common.mixin.client;
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonRecipeCategories;
|
||||
import net.minecraft.client.ClientRecipeBook;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import com.llamalad7.mixinextras.expression.Definition;
|
||||
import com.llamalad7.mixinextras.expression.Expression;
|
||||
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import net.minecraft.client.RecipeBookCategories;
|
||||
import net.minecraft.client.gui.screens.recipebook.RecipeBookComponent;
|
||||
import net.minecraft.client.gui.screens.recipebook.RecipeBookPage;
|
||||
import net.minecraft.client.gui.screens.recipebook.RecipeBookTabButton;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(RecipeBookComponent.class)
|
||||
public class RecipeBookComponentMixin {
|
||||
|
||||
@Shadow private ClientRecipeBook book;
|
||||
|
||||
@Shadow protected Minecraft minecraft;
|
||||
|
||||
@Shadow private int width;
|
||||
|
||||
@Shadow private int xOffset;
|
||||
|
||||
@Shadow private int height;
|
||||
|
||||
@Shadow @Final private RecipeBookPage recipeBookPage;
|
||||
|
||||
@Inject(method = "updateTabs", at = @At("HEAD"), cancellable = true)
|
||||
private void customUpdateTabs(CallbackInfo ci) {
|
||||
RecipeBookComponent instance = (RecipeBookComponent) (Object) this;
|
||||
int i = (width - 147) / 2 - xOffset - 30;
|
||||
int j = (height - 166) / 2 + 3;
|
||||
int k = 27;
|
||||
int l = 0;
|
||||
|
||||
for (RecipeBookTabButton recipeBookTabButton : instance.tabButtons) {
|
||||
RecipeBookCategories recipeBookCategories = recipeBookTabButton.getCategory();
|
||||
if (recipeBookCategories != RecipeBookCategories.CRAFTING_SEARCH && recipeBookCategories != RecipeBookCategories.FURNACE_SEARCH && recipeBookCategories != CobblemonRecipeCategories.COOKING_POT_SEARCH.toVanillaCategory()) {
|
||||
if (recipeBookTabButton.updateVisibility(book)) {
|
||||
recipeBookTabButton.setPosition(i, j + 27 * l++);
|
||||
recipeBookTabButton.startAnimation(minecraft);
|
||||
}
|
||||
} else {
|
||||
recipeBookTabButton.visible = true;
|
||||
recipeBookTabButton.setPosition(i, j + k * l++);
|
||||
}
|
||||
}
|
||||
|
||||
ci.cancel();
|
||||
@Definition(id = "recipeBookCategories", local = @Local(name = "recipeBookCategories"))
|
||||
@Definition(id = "FURNACE_SEARCH", field = "Lnet/minecraft/client/RecipeBookCategories;FURNACE_SEARCH:Lnet/minecraft/client/RecipeBookCategories;")
|
||||
@Expression("recipeBookCategories != FURNACE_SEARCH")
|
||||
@ModifyExpressionValue(
|
||||
method = "updateTabs",
|
||||
at = @At(value = "MIXINEXTRAS:EXPRESSION")
|
||||
)
|
||||
private boolean notCookingPotSearch(boolean original, @Local(name = "recipeBookCategories") RecipeBookCategories recipeBookCategories) {
|
||||
return original && recipeBookCategories != CobblemonRecipeCategories.COOKING_POT_SEARCH.toVanillaCategory();
|
||||
}
|
||||
}
|
||||
@@ -54,10 +54,6 @@ import com.cobblemon.mod.common.api.storage.adapter.database.MongoDBStoreAdapter
|
||||
import com.cobblemon.mod.common.api.storage.adapter.flatfile.FileStoreAdapter
|
||||
import com.cobblemon.mod.common.api.storage.adapter.flatfile.JSONStoreAdapter
|
||||
import com.cobblemon.mod.common.api.storage.adapter.flatfile.NBTStoreAdapter
|
||||
import com.cobblemon.mod.common.api.storage.egg.EggStoreManager
|
||||
import com.cobblemon.mod.common.api.storage.egg.JsonEggStoreBackend
|
||||
import com.cobblemon.mod.common.api.storage.egg.MongoEggStoreBackend
|
||||
import com.cobblemon.mod.common.api.storage.egg.NbtEggStoreBackend
|
||||
import com.cobblemon.mod.common.api.storage.factory.FileBackedPokemonStoreFactory
|
||||
import com.cobblemon.mod.common.api.storage.molang.NbtMoLangDataStoreFactory
|
||||
import com.cobblemon.mod.common.api.storage.pc.PCStore
|
||||
@@ -179,7 +175,6 @@ object Cobblemon {
|
||||
var storage = PokemonStoreManager()
|
||||
var molangData = NbtMoLangDataStoreFactory
|
||||
lateinit var playerDataManager: PlayerInstancedDataStoreManager
|
||||
lateinit var eggStoreManager: EggStoreManager
|
||||
lateinit var starterConfig: StarterConfig
|
||||
val dataProvider: DataProvider = CobblemonDataProvider
|
||||
var permissionValidator: PermissionValidator by Delegates.observable(LaxPermissionValidator().also { it.initialize() }) { _, _, newValue -> newValue.initialize() }
|
||||
@@ -188,8 +183,6 @@ object Cobblemon {
|
||||
var wallpapers = mutableMapOf<UUID, Set<ResourceLocation>>()
|
||||
|
||||
val serverPlayerStructs = mutableMapOf<UUID, ObjectValue<Player>>()
|
||||
/** Player UUID to entity UUID that they're brain debuggin' */
|
||||
val serverPlayerDebuggingPokemonBrainEntities = mutableMapOf<UUID, UUID>()
|
||||
|
||||
val statistics: CobblemonStats = CobblemonStats
|
||||
|
||||
@@ -212,8 +205,6 @@ object Cobblemon {
|
||||
this.LOGGER.info(" - Branch: ${CobblemonBuildDetails.BRANCH}")
|
||||
}
|
||||
|
||||
this.loadConfig()
|
||||
|
||||
implementation.registerRecipeSerializers()
|
||||
implementation.registerRecipeTypes()
|
||||
implementation.registerPermissionValidator()
|
||||
@@ -242,6 +233,7 @@ object Cobblemon {
|
||||
ExperienceGroups.registerDefaults()
|
||||
CaptureCalculators.registerDefaults()
|
||||
|
||||
this.loadConfig()
|
||||
// CobblemonBlockPredicates.touch()
|
||||
CobblemonOrePlacedFeatures.register()
|
||||
CobblemonPlacedFeatures.register()
|
||||
@@ -267,7 +259,6 @@ object Cobblemon {
|
||||
playerDataManager.onPlayerDisconnect(it.player)
|
||||
RequestManager.onLogoff(it.player)
|
||||
serverPlayerStructs.remove(it.player.uuid)
|
||||
serverPlayerDebuggingPokemonBrainEntities.remove(it.player.uuid)
|
||||
}
|
||||
PlatformEvents.PLAYER_DEATH.subscribe {
|
||||
PCLinkManager.removeLink(it.player.uuid)
|
||||
@@ -384,12 +375,6 @@ object Cobblemon {
|
||||
playerDataManager = PlayerInstancedDataStoreManager().also { it.setup(server) }
|
||||
HabitatPools.onServerLoading(server)
|
||||
|
||||
val eggBackend = when (config.storageFormat) {
|
||||
"nbt" -> NbtEggStoreBackend()
|
||||
"json" -> JsonEggStoreBackend()
|
||||
else -> null // MongoDB handled below
|
||||
}
|
||||
|
||||
val mongoClient: MongoClient?
|
||||
|
||||
val pokemonStoreRoot = server.getWorldPath(LevelResource.ROOT).resolve("pokemon").toFile()
|
||||
@@ -441,10 +426,6 @@ object Cobblemon {
|
||||
playerDataManager.setFactory(generalMongoFactory, PlayerInstancedDataStoreTypes.GENERAL)
|
||||
playerDataManager.setFactory(pokedexMongoFactory, PlayerInstancedDataStoreTypes.POKEDEX)
|
||||
playerDataManager.setFactory(tmMovesMongoFactory, PlayerInstancedDataStoreTypes.TM_MOVES)
|
||||
|
||||
eggStoreManager = EggStoreManager(MongoEggStoreBackend(mongoClient, config.mongoDBDatabaseName))
|
||||
eggStoreManager.setup(server)
|
||||
|
||||
MongoDBStoreAdapter(mongoClient, config.mongoDBDatabaseName)
|
||||
} catch (e: ClassNotFoundException) {
|
||||
LOGGER.error("MongoDB driver not found.")
|
||||
@@ -458,11 +439,6 @@ object Cobblemon {
|
||||
}
|
||||
.with(ReforgedConversion(server.getWorldPath(LevelResource.ROOT))) as FileStoreAdapter<*>
|
||||
|
||||
if (eggBackend != null) {
|
||||
eggStoreManager = EggStoreManager(eggBackend)
|
||||
eggStoreManager.setup(server)
|
||||
}
|
||||
|
||||
storage.registerFactory(
|
||||
priority = Priority.LOWEST,
|
||||
factory = FileBackedPokemonStoreFactory(
|
||||
@@ -475,7 +451,6 @@ object Cobblemon {
|
||||
|
||||
PlatformEvents.SERVER_STOPPED.subscribe {
|
||||
storage.unregisterAll(it.server.registryAccess())
|
||||
eggStoreManager.shutdown()
|
||||
playerDataManager.saveAllStores()
|
||||
playerDataManager.saveExecutor.shutdown()
|
||||
playerDataManager.saveExecutor.awaitTermination(30L, TimeUnit.SECONDS)
|
||||
|
||||
@@ -12,8 +12,6 @@ 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
|
||||
@@ -28,8 +26,6 @@ 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
|
||||
@@ -59,8 +55,6 @@ object CobblemonBehaviours : JsonDataRegistry<CobblemonBehaviour> {
|
||||
.registerTypeAdapter(SensorType::class.java, SensorTypeAdapter)
|
||||
.registerTypeAdapter(BehaviourConfig::class.java, BehaviourConfigAdapter)
|
||||
.registerTypeAdapter(TaskConfig::class.java, TaskConfigAdapter)
|
||||
.registerTypeAdapter(ObjectiveConfig::class.java, ObjectiveConfigAdapter)
|
||||
.registerTypeAdapter(SenseConfig::class.java, SenseConfigAdapter)
|
||||
.registerTypeAdapter(
|
||||
TypeToken.getParameterized(Either::class.java, Expression::class.java, MoLangConfigVariable::class.java).type,
|
||||
ExpressionOrEntityVariableAdapter
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
package com.cobblemon.mod.common
|
||||
|
||||
import com.cobblemon.mod.common.block.entity.*
|
||||
import com.cobblemon.mod.common.block.entity.DisplayCaseBlockEntity
|
||||
import com.cobblemon.mod.common.block.habitat.HabitatBlockEntity
|
||||
import com.cobblemon.mod.common.block.multiblock.FossilMultiblockBuilder
|
||||
import com.cobblemon.mod.common.platform.PlatformRegistry
|
||||
@@ -47,10 +46,12 @@ object CobblemonBlockEntities : PlatformRegistry<Registry<BlockEntityType<*>>, R
|
||||
val HANGING_SIGN: BlockEntityType<CobblemonHangingSignBlockEntity> = this.create("hanging_sign", BlockEntityType.Builder.of(::CobblemonHangingSignBlockEntity, CobblemonBlocks.APRICORN_HANGING_SIGN, CobblemonBlocks.APRICORN_WALL_HANGING_SIGN, CobblemonBlocks.SACCHARINE_HANGING_SIGN, CobblemonBlocks.SACCHARINE_WALL_HANGING_SIGN).build(null))
|
||||
@JvmField
|
||||
val TM_MACHINE: BlockEntityType<TMMachineBlockEntity> = this.create("tm_machine", BlockEntityType.Builder.of(::TMMachineBlockEntity, CobblemonBlocks.TM_MACHINE).build(null))
|
||||
|
||||
@JvmField
|
||||
val HABITAT_BLOCK: BlockEntityType<HabitatBlockEntity> = this.create("habitat_block",
|
||||
BlockEntityType.Builder.of(::HabitatBlockEntity, CobblemonBlocks.HABITAT_BLOCK).build(null)
|
||||
)
|
||||
|
||||
@JvmField
|
||||
val GILDED_CHEST: BlockEntityType<GildedChestBlockEntity> = this.create("chest", BlockEntityType.Builder.of(::GildedChestBlockEntity,
|
||||
CobblemonBlocks.GILDED_CHEST,
|
||||
@@ -84,38 +85,6 @@ object CobblemonBlockEntities : PlatformRegistry<Registry<BlockEntityType<*>>, R
|
||||
).build(null)
|
||||
)
|
||||
|
||||
val NEST: BlockEntityType<NestBlockEntity> = this.create("nest",
|
||||
BlockEntityType.Builder.of(
|
||||
::NestBlockEntity,
|
||||
CobblemonBlocks.NEST_FIERY,
|
||||
CobblemonBlocks.NEST_GRASSY,
|
||||
CobblemonBlocks.NEST_LEAFY,
|
||||
CobblemonBlocks.NEST_ICY,
|
||||
CobblemonBlocks.NEST_ROCKY,
|
||||
CobblemonBlocks.NEST_SANDY,
|
||||
CobblemonBlocks.NEST_SHROOMY,
|
||||
CobblemonBlocks.NEST_TWIGGY,
|
||||
CobblemonBlocks.NEST_HAY,
|
||||
CobblemonBlocks.NEST_KELP,
|
||||
CobblemonBlocks.NEST_WATERY
|
||||
).build(null))
|
||||
|
||||
val EGG: BlockEntityType<EggBlockEntity> = this.create("egg",
|
||||
BlockEntityType.Builder.of(
|
||||
::EggBlockEntity,
|
||||
CobblemonBlocks.EGG
|
||||
).build(null)
|
||||
)
|
||||
|
||||
@JvmField
|
||||
val INCUBATOR: BlockEntityType<IncubatorBlockEntity> = this.create("incubator",
|
||||
BlockEntityType.Builder.of(
|
||||
::IncubatorBlockEntity,
|
||||
CobblemonBlocks.INCUBATOR
|
||||
).build(null)
|
||||
)
|
||||
|
||||
|
||||
@JvmField
|
||||
val DISPLAY_CASE: BlockEntityType<DisplayCaseBlockEntity> = this.create("display_case",
|
||||
BlockEntityType.Builder.of(::DisplayCaseBlockEntity, CobblemonBlocks.DISPLAY_CASE).build(null)
|
||||
@@ -140,4 +109,21 @@ object CobblemonBlockEntities : PlatformRegistry<Registry<BlockEntityType<*>>, R
|
||||
"poke_snack",
|
||||
BlockEntityType.Builder.of(::PokeSnackBlockEntity, CobblemonBlocks.POKE_SNACK, CobblemonBlocks.POKE_CAKE).build(null)
|
||||
)
|
||||
|
||||
@JvmField
|
||||
val TERRAIN_PLANT: BlockEntityType<TerrainPlantBlockEntity> = this.create(
|
||||
"terrain_plant",
|
||||
BlockEntityType.Builder.of(::TerrainPlantBlockEntity,
|
||||
CobblemonBlocks.VOLTACONIA,
|
||||
CobblemonBlocks.LUCIBELL,
|
||||
CobblemonBlocks.VERDROOT
|
||||
).build(null)
|
||||
)
|
||||
@JvmField
|
||||
val MISTLEAF_FLOWER: BlockEntityType<MistleafFlowerBlockEntity> = this.create(
|
||||
"mistleaf",
|
||||
BlockEntityType.Builder.of(::MistleafFlowerBlockEntity,
|
||||
CobblemonBlocks.MISTLEAF
|
||||
).build(null)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -9,58 +9,14 @@
|
||||
package com.cobblemon.mod.common
|
||||
|
||||
import com.cobblemon.mod.common.api.apricorn.Apricorn
|
||||
import com.cobblemon.mod.common.api.nest.WildNestType
|
||||
import com.cobblemon.mod.common.api.nest.WildNestVariant
|
||||
import com.cobblemon.mod.common.api.pokemon.stats.Stats
|
||||
import com.cobblemon.mod.common.block.ApricornBlock
|
||||
import com.cobblemon.mod.common.block.ApricornSaplingBlock
|
||||
import com.cobblemon.mod.common.block.BerryBlock
|
||||
import com.cobblemon.mod.common.block.BigRootBlock
|
||||
import com.cobblemon.mod.common.block.CoinPouchBlock
|
||||
import com.cobblemon.mod.common.block.DamagedMonitorBlock
|
||||
import com.cobblemon.mod.common.block.DecorativeItemBlock
|
||||
import com.cobblemon.mod.common.block.DiscShelfBlock
|
||||
import com.cobblemon.mod.common.block.DisplayCaseBlock
|
||||
import com.cobblemon.mod.common.block.EggBlock
|
||||
import com.cobblemon.mod.common.block.EjectButtonBlock
|
||||
import com.cobblemon.mod.common.block.EnergyRootBlock
|
||||
import com.cobblemon.mod.common.block.FossilAnalyzerBlock
|
||||
import com.cobblemon.mod.common.block.GalaricaWreathBlock
|
||||
import com.cobblemon.mod.common.block.HealingMachineBlock
|
||||
import com.cobblemon.mod.common.block.HeartyGrainsBlock
|
||||
import com.cobblemon.mod.common.block.IncubatorBlock
|
||||
import com.cobblemon.mod.common.block.*
|
||||
import com.cobblemon.mod.common.block.LecternBlock
|
||||
import com.cobblemon.mod.common.block.MagnetBlock
|
||||
import com.cobblemon.mod.common.block.MedicinalLeekBlock
|
||||
import com.cobblemon.mod.common.block.MintBlock
|
||||
import com.cobblemon.mod.common.block.MintBlock.MintType
|
||||
import com.cobblemon.mod.common.block.MonitorBlock
|
||||
import com.cobblemon.mod.common.block.NestBlock
|
||||
import com.cobblemon.mod.common.block.NutBushBlock
|
||||
import com.cobblemon.mod.common.block.OrbBlock
|
||||
import com.cobblemon.mod.common.block.PCBlock
|
||||
import com.cobblemon.mod.common.block.PastureBlock
|
||||
import com.cobblemon.mod.common.block.PlaqueBlock
|
||||
import com.cobblemon.mod.common.block.PokeSnackBlock
|
||||
import com.cobblemon.mod.common.block.RestorationTankBlock
|
||||
import com.cobblemon.mod.common.block.RevivalHerbBlock
|
||||
import com.cobblemon.mod.common.block.SaccharineLeafBlock
|
||||
import com.cobblemon.mod.common.block.SaccharineLogBlock
|
||||
import com.cobblemon.mod.common.block.SaccharineLogSlatheredBlock
|
||||
import com.cobblemon.mod.common.block.ScarekrowBlock
|
||||
import com.cobblemon.mod.common.block.StackableItemBlock
|
||||
import com.cobblemon.mod.common.block.TumblestoneBlock
|
||||
import com.cobblemon.mod.common.block.TypeGemClusterBlock
|
||||
import com.cobblemon.mod.common.block.TypeGemCoreBlock
|
||||
import com.cobblemon.mod.common.block.VivichokeBlock
|
||||
import com.cobblemon.mod.common.block.WallAttachedDirectionalShapeBlock
|
||||
import com.cobblemon.mod.common.block.WallAttachedStackableItemBlock
|
||||
import com.cobblemon.mod.common.block.WildNestBlock
|
||||
import com.cobblemon.mod.common.block.campfirepot.CampfireBlock
|
||||
import com.cobblemon.mod.common.block.campfirepot.CampfirePotBlock
|
||||
import com.cobblemon.mod.common.block.chest.GildedChestBlock
|
||||
import com.cobblemon.mod.common.block.decor.ActivatableDecorationBlock
|
||||
import com.cobblemon.mod.common.block.decor.RingTargetBlock
|
||||
import com.cobblemon.mod.common.block.decor.*
|
||||
import com.cobblemon.mod.common.block.general.BaleBlock
|
||||
import com.cobblemon.mod.common.block.general.HorizontalRotationCarpetBlock
|
||||
import com.cobblemon.mod.common.block.general.HorizontalRotationalBlock
|
||||
@@ -71,6 +27,7 @@ import com.cobblemon.mod.common.block.sign.CobblemonSignBlock
|
||||
import com.cobblemon.mod.common.block.sign.CobblemonWallHangingSignBlock
|
||||
import com.cobblemon.mod.common.block.sign.CobblemonWallSignBlock
|
||||
import com.cobblemon.mod.common.block.tmmachine.TMMachineBlock
|
||||
import com.cobblemon.mod.common.block.general.MintLitterBlock
|
||||
import com.cobblemon.mod.common.mixin.invoker.BlocksInvoker
|
||||
import com.cobblemon.mod.common.mixin.invoker.DoorBlockInvoker
|
||||
import com.cobblemon.mod.common.mixin.invoker.FireBlockInvoker
|
||||
@@ -673,7 +630,7 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
|
||||
val GALARICA_WREATH = create("galarica_wreath", GalaricaWreathBlock(BlockBehaviour.Properties.of().sound(SoundType.AZALEA_LEAVES).mapColor(MapColor.NONE).noOcclusion().noCollission()))
|
||||
|
||||
@JvmField
|
||||
val FLAME_ORB = create("flame_orb",OrbBlock(BlockBehaviour.Properties.of().sound(SoundType.GLASS).mapColor(MapColor.NONE).noOcclusion().lightLevel { 15 }))
|
||||
val FLAME_ORB = create("flame_orb", OrbBlock(BlockBehaviour.Properties.of().sound(SoundType.GLASS).mapColor(MapColor.NONE).noOcclusion().lightLevel{15}))
|
||||
@JvmField
|
||||
val TOXIC_ORB = create("toxic_orb", OrbBlock(BlockBehaviour.Properties.of().sound(SoundType.GLASS).mapColor(MapColor.NONE).noOcclusion()))
|
||||
@JvmField
|
||||
@@ -696,15 +653,21 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
|
||||
val CHIPPED_POT = create("chipped_pot", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.DECORATED_POT).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.TEAPOT))
|
||||
@JvmField
|
||||
val CRACKED_POT = create("cracked_pot", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.DECORATED_POT).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.TEAPOT))
|
||||
@JvmField
|
||||
val ELECTIRIZER = create("electirizer", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.IZER))
|
||||
@JvmField
|
||||
val MAGMARIZER = create("magmarizer", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.IZER))
|
||||
|
||||
@JvmField
|
||||
val SACHET = create("sachet", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.WOOL).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.POUCH))
|
||||
@JvmField
|
||||
val METAL_POWDER = create("metal_powder", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.WOOL).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.POUCH))
|
||||
val METAL_POWDER = create("metal_powder", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_POWDER_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.POUCH))
|
||||
@JvmField
|
||||
val QUICK_POWDER = create("quick_powder", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.WOOL).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.POUCH))
|
||||
val QUICK_POWDER = create("quick_powder", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_POWDER_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.POUCH))
|
||||
@JvmField
|
||||
val BRIGHT_POWDER = create("bright_powder", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.WOOL).mapColor(MapColor.NONE).noOcclusion().lightLevel{3}, DecorativeItemBlock.DecorativeItemBlockType.POUCH))
|
||||
val BRIGHT_POWDER = create("bright_powder", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_POWDER_SOUNDS).mapColor(MapColor.NONE).noOcclusion().lightLevel{3}, DecorativeItemBlock.DecorativeItemBlockType.POUCH))
|
||||
@JvmField
|
||||
val SOFT_SAND = create("soft_sand", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_POWDER_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.POUCH))
|
||||
|
||||
@JvmField
|
||||
val NEVER_MELT_ICE = create("never_melt_ice", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.GLASS).mapColor(MapColor.NONE).noOcclusion().friction(0.989F), DecorativeItemBlock.DecorativeItemBlockType.NEVERMELT))
|
||||
@@ -734,7 +697,7 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
|
||||
val EJECT_BUTTON = create("eject_button", EjectButtonBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.NONE).pushReaction(PushReaction.DESTROY).noOcclusion().strength(0.125f), 40))
|
||||
|
||||
@JvmField
|
||||
val LOADED_DICE = create("loaded_dice", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.DICE))
|
||||
val LOADED_DICE = create("loaded_dice", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_DICE_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.DICE))
|
||||
@JvmField
|
||||
val IRON_BALL = create("iron_ball", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.IRON_BALL))
|
||||
@JvmField
|
||||
@@ -745,40 +708,46 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
|
||||
val DUBIOUS_DISC = create("dubious_disc", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.UPGRADE))
|
||||
|
||||
@JvmField
|
||||
val THROAT_SPRAY = create("throat_spray", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.THROAT_SPRAY))
|
||||
val THROAT_SPRAY = create("throat_spray", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.THROAT_SPRAY))
|
||||
@JvmField
|
||||
val TERRAIN_EXTENDER = create("terrain_extender", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_METAL_CAN_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.EXTENDER))
|
||||
@JvmField
|
||||
val MOOMOO_MILK = create("moomoo_milk", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_JAR_GLASS_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.GLASS_BOTTLE))
|
||||
@JvmField
|
||||
val MEDICINAL_BREW = create("medicinal_brew", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_JAR_GLASS_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.GLASS_BOTTLE))
|
||||
|
||||
@JvmField
|
||||
val FULL_HEAL = create("full_heal", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
|
||||
val FULL_HEAL = create("full_heal", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
|
||||
@JvmField
|
||||
val ANTIDOTE = create("antidote", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
|
||||
val ANTIDOTE = create("antidote", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
|
||||
@JvmField
|
||||
val AWAKENING = create("awakening", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
|
||||
val AWAKENING = create("awakening", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
|
||||
@JvmField
|
||||
val BURN_HEAL = create("burn_heal", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
|
||||
val BURN_HEAL = create("burn_heal", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
|
||||
@JvmField
|
||||
val ICE_HEAL = create("ice_heal", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
|
||||
val ICE_HEAL = create("ice_heal", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
|
||||
@JvmField
|
||||
val PARALYZE_HEAL = create("paralyze_heal", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
|
||||
val PARALYZE_HEAL = create("paralyze_heal", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
|
||||
|
||||
@JvmField
|
||||
val ETHER = create("ether", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.ETHER))
|
||||
val ETHER = create("ether", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.ETHER))
|
||||
@JvmField
|
||||
val MAX_ETHER = create("max_ether", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.ETHER))
|
||||
val MAX_ETHER = create("max_ether", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.ETHER))
|
||||
@JvmField
|
||||
val ELIXIR = create("elixir", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.ETHER))
|
||||
val ELIXIR = create("elixir", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.ETHER))
|
||||
@JvmField
|
||||
val MAX_ELIXIR = create("max_elixir", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.ETHER))
|
||||
val MAX_ELIXIR = create("max_elixir", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.ETHER))
|
||||
|
||||
@JvmField
|
||||
val POTION = create("potion", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.POTION))
|
||||
val POTION = create("potion", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.POTION))
|
||||
@JvmField
|
||||
val SUPER_POTION = create("super_potion", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.POTION))
|
||||
val SUPER_POTION = create("super_potion", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.POTION))
|
||||
@JvmField
|
||||
val HYPER_POTION = create("hyper_potion", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.POTION))
|
||||
val HYPER_POTION = create("hyper_potion", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.POTION))
|
||||
@JvmField
|
||||
val MAX_POTION = create("max_potion", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.MAX_POTION))
|
||||
val MAX_POTION = create("max_potion", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.MAX_POTION))
|
||||
@JvmField
|
||||
val FULL_RESTORE = create("full_restore", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.MAX_POTION))
|
||||
val FULL_RESTORE = create("full_restore", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.MAX_POTION))
|
||||
|
||||
@JvmField
|
||||
val DIRE_HIT = create("dire_hit", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.BATTLE_ITEM))
|
||||
@@ -798,21 +767,21 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
|
||||
val X_SPEED = create("x_${Stats.SPEED.identifier.path}", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.BATTLE_ITEM))
|
||||
|
||||
@JvmField
|
||||
val HP_UP = create("hp_up", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
|
||||
val HP_UP = create("hp_up", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_PILLS_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
|
||||
@JvmField
|
||||
val PROTEIN = create("protein", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
|
||||
val PROTEIN = create("protein", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_PILLS_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
|
||||
@JvmField
|
||||
val IRON = create("iron", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
|
||||
val IRON = create("iron", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_PILLS_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
|
||||
@JvmField
|
||||
val CARBOS = create("carbos", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
|
||||
val CARBOS = create("carbos", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_PILLS_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
|
||||
@JvmField
|
||||
val CALCIUM = create("calcium", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
|
||||
val CALCIUM = create("calcium", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_PILLS_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
|
||||
@JvmField
|
||||
val ZINC = create("zinc", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
|
||||
val ZINC = create("zinc", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_PILLS_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
|
||||
@JvmField
|
||||
val PP_UP = create("pp_up", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
|
||||
val PP_UP = create("pp_up", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_PILLS_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
|
||||
@JvmField
|
||||
val PP_MAX = create("pp_max", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
|
||||
val PP_MAX = create("pp_max", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_PILLS_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
|
||||
|
||||
@JvmField
|
||||
val MONITOR = create("monitor", MonitorBlock(BlockBehaviour.Properties.of().mapColor(MapColor.METAL).sound(SoundType.METAL).pushReaction(PushReaction.BLOCK).requiresCorrectToolForDrops().strength(5.0F, 6.0F).lightLevel { if (it.getValue(MonitorBlock.SCREEN) != MonitorBlock.MonitorScreen.OFF) 13 else 0 }))
|
||||
@@ -844,16 +813,7 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
|
||||
|
||||
|
||||
@JvmField
|
||||
val LECTERN = create("lectern", LecternBlock(
|
||||
BlockBehaviour.Properties
|
||||
.of()
|
||||
.mapColor(MapColor.WOOD)
|
||||
.instrument(NoteBlockInstrument.BASS)
|
||||
.sound(SoundType.WOOD)
|
||||
.strength(2.5F)
|
||||
.ignitedByLava()
|
||||
.lightLevel { if (it.getValue(LecternBlock.EMIT_LIGHT) as Boolean) 13 else 0 }
|
||||
))
|
||||
val LECTERN = create("lectern", LecternBlock(BlockBehaviour.Properties.of().mapColor(MapColor.WOOD).instrument(NoteBlockInstrument.BASS).sound(SoundType.WOOD).strength(2.5F).ignitedByLava().lightLevel { if (it.getValue(LecternBlock.EMIT_LIGHT) as Boolean) 13 else 0 }))
|
||||
|
||||
@JvmField
|
||||
val DISC_SHELF = create("disc_shelf", DiscShelfBlock(BlockBehaviour.Properties.of().mapColor(MapColor.WOOD).instrument(NoteBlockInstrument.BASS).sound(SoundType.METAL).strength(2.5F)))
|
||||
@@ -909,6 +869,64 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
|
||||
@JvmField
|
||||
val POTTED_SACCHARINE_SAPLING = this.create("potted_saccharine_sapling", BlocksInvoker.createFlowerPotBlock(SACCHARINE_SAPLING))
|
||||
|
||||
@JvmField
|
||||
val RED_MINT_LEAF_LITTER = this.mintLitter("red_mint_leaf_litter", MapColor.COLOR_RED)
|
||||
@JvmField
|
||||
val POTTED_RED_MINT = this.pottedPlant("potted_red_mint", RED_MINT_LEAF_LITTER)
|
||||
@JvmField
|
||||
val BLUE_MINT_LEAF_LITTER = this.mintLitter("blue_mint_leaf_litter", MapColor.COLOR_BLUE)
|
||||
@JvmField
|
||||
val POTTED_BLUE_MINT = this.pottedPlant("potted_blue_mint", BLUE_MINT_LEAF_LITTER)
|
||||
@JvmField
|
||||
val CYAN_MINT_LEAF_LITTER = this.mintLitter("cyan_mint_leaf_litter", MapColor.COLOR_CYAN)
|
||||
@JvmField
|
||||
val POTTED_CYAN_MINT = this.pottedPlant("potted_cyan_mint", CYAN_MINT_LEAF_LITTER)
|
||||
@JvmField
|
||||
val PINK_MINT_LEAF_LITTER = this.mintLitter("pink_mint_leaf_litter", MapColor.COLOR_PINK)
|
||||
@JvmField
|
||||
val POTTED_PINK_MINT = this.pottedPlant("potted_pink_mint", PINK_MINT_LEAF_LITTER)
|
||||
@JvmField
|
||||
val GREEN_MINT_LEAF_LITTER = this.mintLitter("green_mint_leaf_litter", MapColor.COLOR_GREEN)
|
||||
@JvmField
|
||||
val POTTED_GREEN_MINT = this.pottedPlant("potted_green_mint", GREEN_MINT_LEAF_LITTER)
|
||||
@JvmField
|
||||
val WHITE_MINT_LEAF_LITTER = this.mintLitter("white_mint_leaf_litter", MapColor.SNOW)
|
||||
@JvmField
|
||||
val POTTED_WHITE_MINT = this.pottedPlant("potted_white_mint", WHITE_MINT_LEAF_LITTER)
|
||||
|
||||
@JvmField
|
||||
val LOBAILA = this.create("lobaila", LobailaFlowerBlock(BlockBehaviour.Properties.of().mapColor(MapColor.PLANT).noCollission().instabreak().sound(SoundType.GRASS).offsetType(BlockBehaviour.OffsetType.XZ).ignitedByLava().pushReaction(PushReaction.DESTROY)))
|
||||
@JvmField
|
||||
val POMOHANA = this.create("pomohana", PomohanaFlowerBlock(BlockBehaviour.Properties.of().mapColor(MapColor.PLANT).noCollission().instabreak().sound(SoundType.GRASS).ignitedByLava().pushReaction(PushReaction.DESTROY)))
|
||||
@JvmField
|
||||
val PAUVILLEA = this.create("pauvillea",PauvilleaFlowerBlock(BlockBehaviour.Properties.of().mapColor(MapColor.PLANT).noCollission().sound(SoundType.GRASS).instabreak().ignitedByLava().pushReaction(PushReaction.DESTROY)))
|
||||
@JvmField
|
||||
val WISTENSU = this.create("wistensu", WistensuFlowerHeadBlock(BlockBehaviour.Properties.of().mapColor(MapColor.PLANT).noCollission().sound(SoundType.GRASS).instabreak().ignitedByLava().pushReaction(PushReaction.DESTROY).lightLevel{11}))
|
||||
@JvmField
|
||||
val WISTENSU_VINE = this.create("wistensu_vine", WistensuFlowerVineBlock(BlockBehaviour.Properties.of().mapColor(MapColor.PLANT).noCollission().sound(SoundType.GRASS).instabreak().ignitedByLava().pushReaction(PushReaction.DESTROY)))
|
||||
|
||||
@JvmField
|
||||
val ELECTRIC_SEED = this.terrainSeed("electric_seed", TerrainSeedBlock.TerrainSeedType.ELECTRIC)
|
||||
@JvmField
|
||||
val PSYCHIC_SEED = this.terrainSeed("psychic_seed", TerrainSeedBlock.TerrainSeedType.PSYCHIC)
|
||||
@JvmField
|
||||
val MISTY_SEED = this.terrainSeed("misty_seed", TerrainSeedBlock.TerrainSeedType.MISTY)
|
||||
@JvmField
|
||||
val GRASSY_SEED = this.terrainSeed("grassy_seed", TerrainSeedBlock.TerrainSeedType.GRASSY)
|
||||
@JvmField
|
||||
val VOLTACONIA = this.terrainPlant("voltaconia") { state -> if (state.getValue(TerrainPlantBlock.GLOWING)) 8 else 0 }
|
||||
@JvmField
|
||||
val VERDROOT = this.terrainPlant("verdroot") { state -> if (state.getValue(TerrainPlantBlock.GLOWING)) 5 else 0 }
|
||||
@JvmField
|
||||
val LUCIBELL = this.terrainPlant("lucibell") { state -> if (state.getValue(TerrainPlantBlock.GLOWING)) 8 else 0 }
|
||||
@JvmField
|
||||
val MISTLEAF = this.create(
|
||||
"mistleaf",
|
||||
MistleafFlowerBlock {
|
||||
if ((it.getValue(MistleafFlowerBlock.GLOWING) >= 1)) it.getValue(MistleafFlowerBlock.GLOWING) + 2 else 0
|
||||
}
|
||||
)
|
||||
|
||||
@JvmField
|
||||
val HEARTY_GRAIN_BALE = this.create("hearty_grain_bale", BaleBlock(BlockBehaviour.Properties.of().mapColor(MapColor.PLANT).instrument(NoteBlockInstrument.BANJO).strength(0.5F).sound(CobblemonSounds.HEARTY_GRAIN_BALE_SOUNDS)))
|
||||
@JvmField
|
||||
@@ -1085,90 +1103,146 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
|
||||
val YACHE_BERRY = this.berryBlock("yache")
|
||||
//val BINDING_SOIL = this.create("binding_soil", BindingSoilBlock(BlockBehaviour.Properties.of()))
|
||||
|
||||
val NEST_FIERY = this.create(
|
||||
"nest_fiery",
|
||||
WildNestBlock.create(
|
||||
WildNestType.FIERY,
|
||||
BlockBehaviour.Properties.of()
|
||||
.noOcclusion()
|
||||
.pushReaction(PushReaction.DESTROY)
|
||||
.lightLevel { state ->
|
||||
val variant = (state.block as? WildNestBlock)?.variantProperty?.let { state.getValue(it) }
|
||||
when (variant) {
|
||||
WildNestVariant.FIERY_NETHERRACK -> 15
|
||||
WildNestVariant.FIERY_SOUL_SAND -> 10
|
||||
else -> 3
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
val NEST_GRASSY = this.create(
|
||||
"nest_grassy",
|
||||
WildNestBlock.create(WildNestType.GRASSY, BlockBehaviour.Properties.of().noOcclusion().pushReaction(PushReaction.DESTROY))
|
||||
)
|
||||
val NEST_LEAFY = this.create(
|
||||
"nest_leafy",
|
||||
WildNestBlock.create(WildNestType.LEAFY, BlockBehaviour.Properties.of().noOcclusion().pushReaction(PushReaction.DESTROY))
|
||||
)
|
||||
val NEST_ICY = this.create(
|
||||
"nest_icy",
|
||||
WildNestBlock.create(WildNestType.ICY, BlockBehaviour.Properties.of().noOcclusion().pushReaction(PushReaction.DESTROY))
|
||||
)
|
||||
val NEST_HAY = this.create(
|
||||
"nest_hay",
|
||||
WildNestBlock.create(WildNestType.PLAYER, BlockBehaviour.Properties.of().noOcclusion().pushReaction(PushReaction.DESTROY))
|
||||
)
|
||||
val NEST_KELP = this.create(
|
||||
"nest_kelp",
|
||||
WildNestBlock.create(WildNestType.KELP, BlockBehaviour.Properties.of().noOcclusion().pushReaction(PushReaction.DESTROY))
|
||||
)
|
||||
val NEST_ROCKY = this.create(
|
||||
"nest_rocky",
|
||||
WildNestBlock.create(WildNestType.ROCKY, BlockBehaviour.Properties.of().noOcclusion().pushReaction(PushReaction.DESTROY))
|
||||
)
|
||||
val NEST_SANDY = this.create(
|
||||
"nest_sandy",
|
||||
WildNestBlock.create(WildNestType.SANDY, BlockBehaviour.Properties.of().noOcclusion().pushReaction(PushReaction.DESTROY))
|
||||
)
|
||||
val NEST_SHROOMY = this.create(
|
||||
"nest_shroomy",
|
||||
WildNestBlock.create(WildNestType.SHROOMY, BlockBehaviour.Properties.of().noOcclusion().pushReaction(PushReaction.DESTROY))
|
||||
)
|
||||
val NEST_TWIGGY = this.create(
|
||||
"nest_twiggy",
|
||||
WildNestBlock.create(WildNestType.TWIGGY, BlockBehaviour.Properties.of().noOcclusion().pushReaction(PushReaction.DESTROY))
|
||||
)
|
||||
val NEST_WATERY = this.create(
|
||||
"nest_watery",
|
||||
WildNestBlock.create(WildNestType.WATERY, BlockBehaviour.Properties.of().noOcclusion().pushReaction(PushReaction.DESTROY))
|
||||
)
|
||||
|
||||
val EGG = this.create("egg", EggBlock(BlockBehaviour.Properties
|
||||
.ofFullCopy(Blocks.DRAGON_EGG)
|
||||
.pushReaction(PushReaction.BLOCK)
|
||||
.sound(SoundType.BONE_BLOCK)
|
||||
))
|
||||
|
||||
@JvmField
|
||||
val INCUBATOR = this.create("incubator", IncubatorBlock(BlockBehaviour.Properties.of()
|
||||
.mapColor(MapColor.COLOR_LIGHT_GRAY)
|
||||
.sound(SoundType.METAL)
|
||||
.strength(Blocks.COAL_BLOCK.defaultDestroyTime(), Blocks.DIAMOND_ORE.explosionResistance)
|
||||
.noOcclusion()
|
||||
.pushReaction(PushReaction.BLOCK)
|
||||
.lightLevel { state ->
|
||||
if (state.getValue(IncubatorBlock.HAS_EGG) && !state.getValue(IncubatorBlock.READY)) 8 else 0 // emit light only when warming the egg
|
||||
}
|
||||
))
|
||||
|
||||
val POTTED_AGUAV_BERRY = this.create("potted_aguav_berry", BlocksInvoker.createFlowerPotBlock(AGUAV_BERRY))
|
||||
@JvmField
|
||||
val SCAREKROW = this.create("scarekrow", ScarekrowBlock(
|
||||
BlockBehaviour.Properties.of()
|
||||
.pushReaction(PushReaction.DESTROY)
|
||||
.sound(SoundType.GRASS)
|
||||
.strength(0.1f)
|
||||
.noOcclusion()
|
||||
))
|
||||
val POTTED_APICOT_BERRY = this.create("potted_apicot_berry", BlocksInvoker.createFlowerPotBlock(APICOT_BERRY))
|
||||
@JvmField
|
||||
val POTTED_ASPEAR_BERRY = this.create("potted_aspear_berry", BlocksInvoker.createFlowerPotBlock(ASPEAR_BERRY))
|
||||
@JvmField
|
||||
val POTTED_BABIRI_BERRY = this.create("potted_babiri_berry", BlocksInvoker.createFlowerPotBlock(BABIRI_BERRY))
|
||||
@JvmField
|
||||
val POTTED_BELUE_BERRY = this.create("potted_belue_berry", BlocksInvoker.createFlowerPotBlock(BELUE_BERRY))
|
||||
@JvmField
|
||||
val POTTED_BLUK_BERRY = this.create("potted_bluk_berry", BlocksInvoker.createFlowerPotBlock(BLUK_BERRY))
|
||||
@JvmField
|
||||
val POTTED_CHARTI_BERRY = this.create("potted_charti_berry", BlocksInvoker.createFlowerPotBlock(CHARTI_BERRY))
|
||||
@JvmField
|
||||
val POTTED_CHERI_BERRY = this.create("potted_cheri_berry", BlocksInvoker.createFlowerPotBlock(CHERI_BERRY))
|
||||
@JvmField
|
||||
val POTTED_CHESTO_BERRY = this.create("potted_chesto_berry", BlocksInvoker.createFlowerPotBlock(CHESTO_BERRY))
|
||||
@JvmField
|
||||
val POTTED_CHILAN_BERRY = this.create("potted_chilan_berry", BlocksInvoker.createFlowerPotBlock(CHILAN_BERRY))
|
||||
@JvmField
|
||||
val POTTED_CHOPLE_BERRY = this.create("potted_chople_berry", BlocksInvoker.createFlowerPotBlock(CHOPLE_BERRY))
|
||||
@JvmField
|
||||
val POTTED_COBA_BERRY = this.create("potted_coba_berry", BlocksInvoker.createFlowerPotBlock(COBA_BERRY))
|
||||
@JvmField
|
||||
val POTTED_COLBUR_BERRY = this.create("potted_colbur_berry", BlocksInvoker.createFlowerPotBlock(COLBUR_BERRY))
|
||||
@JvmField
|
||||
val POTTED_CORNN_BERRY = this.create("potted_cornn_berry", BlocksInvoker.createFlowerPotBlock(CORNN_BERRY))
|
||||
@JvmField
|
||||
val POTTED_CUSTAP_BERRY = this.create("potted_custap_berry", BlocksInvoker.createFlowerPotBlock(CUSTAP_BERRY))
|
||||
@JvmField
|
||||
val POTTED_DURIN_BERRY = this.create("potted_durin_berry", BlocksInvoker.createFlowerPotBlock(DURIN_BERRY))
|
||||
@JvmField
|
||||
val POTTED_EGGANT_BERRY = this.create("potted_eggant_berry", BlocksInvoker.createFlowerPotBlock(EGGANT_BERRY))
|
||||
@JvmField
|
||||
val POTTED_ENIGMA_BERRY = this.create("potted_enigma_berry", BlocksInvoker.createFlowerPotBlock(ENIGMA_BERRY))
|
||||
@JvmField
|
||||
val POTTED_FIGY_BERRY = this.create("potted_figy_berry", BlocksInvoker.createFlowerPotBlock(FIGY_BERRY))
|
||||
@JvmField
|
||||
val POTTED_GANLON_BERRY = this.create("potted_ganlon_berry", BlocksInvoker.createFlowerPotBlock(GANLON_BERRY))
|
||||
@JvmField
|
||||
val POTTED_GREPA_BERRY = this.create("potted_grepa_berry", BlocksInvoker.createFlowerPotBlock(GREPA_BERRY))
|
||||
@JvmField
|
||||
val POTTED_HABAN_BERRY = this.create("potted_haban_berry", BlocksInvoker.createFlowerPotBlock(HABAN_BERRY))
|
||||
@JvmField
|
||||
val POTTED_HONDEW_BERRY = this.create("potted_hondew_berry", BlocksInvoker.createFlowerPotBlock(HONDEW_BERRY))
|
||||
@JvmField
|
||||
val POTTED_HOPO_BERRY = this.create("potted_hopo_berry", BlocksInvoker.createFlowerPotBlock(HOPO_BERRY))
|
||||
@JvmField
|
||||
val POTTED_IAPAPA_BERRY = this.create("potted_iapapa_berry", BlocksInvoker.createFlowerPotBlock(IAPAPA_BERRY))
|
||||
@JvmField
|
||||
val POTTED_JABOCA_BERRY = this.create("potted_jaboca_berry", BlocksInvoker.createFlowerPotBlock(JABOCA_BERRY))
|
||||
@JvmField
|
||||
val POTTED_KASIB_BERRY = this.create("potted_kasib_berry", BlocksInvoker.createFlowerPotBlock(KASIB_BERRY))
|
||||
@JvmField
|
||||
val POTTED_KEBIA_BERRY = this.create("potted_kebia_berry", BlocksInvoker.createFlowerPotBlock(KEBIA_BERRY))
|
||||
@JvmField
|
||||
val POTTED_KEE_BERRY = this.create("potted_kee_berry", BlocksInvoker.createFlowerPotBlock(KEE_BERRY))
|
||||
@JvmField
|
||||
val POTTED_KELPSY_BERRY = this.create("potted_kelpsy_berry", BlocksInvoker.createFlowerPotBlock(KELPSY_BERRY))
|
||||
@JvmField
|
||||
val POTTED_LANSAT_BERRY = this.create("potted_lansat_berry", BlocksInvoker.createFlowerPotBlock(LANSAT_BERRY))
|
||||
@JvmField
|
||||
val POTTED_LEPPA_BERRY = this.create("potted_leppa_berry", BlocksInvoker.createFlowerPotBlock(LEPPA_BERRY))
|
||||
@JvmField
|
||||
val POTTED_LIECHI_BERRY = this.create("potted_liechi_berry", BlocksInvoker.createFlowerPotBlock(LIECHI_BERRY))
|
||||
@JvmField
|
||||
val POTTED_LUM_BERRY = this.create("potted_lum_berry", BlocksInvoker.createFlowerPotBlock(LUM_BERRY))
|
||||
@JvmField
|
||||
val POTTED_MAGO_BERRY = this.create("potted_mago_berry", BlocksInvoker.createFlowerPotBlock(MAGO_BERRY))
|
||||
@JvmField
|
||||
val POTTED_MAGOST_BERRY = this.create("potted_magost_berry", BlocksInvoker.createFlowerPotBlock(MAGOST_BERRY))
|
||||
@JvmField
|
||||
val POTTED_MARANGA_BERRY = this.create("potted_maranga_berry", BlocksInvoker.createFlowerPotBlock(MARANGA_BERRY))
|
||||
@JvmField
|
||||
val POTTED_MICLE_BERRY = this.create("potted_micle_berry", BlocksInvoker.createFlowerPotBlock(MICLE_BERRY))
|
||||
@JvmField
|
||||
val POTTED_NANAB_BERRY = this.create("potted_nanab_berry", BlocksInvoker.createFlowerPotBlock(NANAB_BERRY))
|
||||
@JvmField
|
||||
val POTTED_NOMEL_BERRY = this.create("potted_nomel_berry", BlocksInvoker.createFlowerPotBlock(NOMEL_BERRY))
|
||||
@JvmField
|
||||
val POTTED_OCCA_BERRY = this.create("potted_occa_berry", BlocksInvoker.createFlowerPotBlock(OCCA_BERRY))
|
||||
@JvmField
|
||||
val POTTED_ORAN_BERRY = this.create("potted_oran_berry", BlocksInvoker.createFlowerPotBlock(ORAN_BERRY))
|
||||
@JvmField
|
||||
val POTTED_PAMTRE_BERRY = this.create("potted_pamtre_berry", BlocksInvoker.createFlowerPotBlock(PAMTRE_BERRY))
|
||||
@JvmField
|
||||
val POTTED_PASSHO_BERRY = this.create("potted_passho_berry", BlocksInvoker.createFlowerPotBlock(PASSHO_BERRY))
|
||||
@JvmField
|
||||
val POTTED_PAYAPA_BERRY = this.create("potted_payapa_berry", BlocksInvoker.createFlowerPotBlock(PAYAPA_BERRY))
|
||||
@JvmField
|
||||
val POTTED_PECHA_BERRY = this.create("potted_pecha_berry", BlocksInvoker.createFlowerPotBlock(PECHA_BERRY))
|
||||
@JvmField
|
||||
val POTTED_PERSIM_BERRY = this.create("potted_persim_berry", BlocksInvoker.createFlowerPotBlock(PERSIM_BERRY))
|
||||
@JvmField
|
||||
val POTTED_PETAYA_BERRY = this.create("potted_petaya_berry", BlocksInvoker.createFlowerPotBlock(PETAYA_BERRY))
|
||||
@JvmField
|
||||
val POTTED_PINAP_BERRY = this.create("potted_pinap_berry", BlocksInvoker.createFlowerPotBlock(PINAP_BERRY))
|
||||
@JvmField
|
||||
val POTTED_POMEG_BERRY = this.create("potted_pomeg_berry", BlocksInvoker.createFlowerPotBlock(POMEG_BERRY))
|
||||
@JvmField
|
||||
val POTTED_QUALOT_BERRY = this.create("potted_qualot_berry", BlocksInvoker.createFlowerPotBlock(QUALOT_BERRY))
|
||||
@JvmField
|
||||
val POTTED_RABUTA_BERRY = this.create("potted_rabuta_berry", BlocksInvoker.createFlowerPotBlock(RABUTA_BERRY))
|
||||
@JvmField
|
||||
val POTTED_RAWST_BERRY = this.create("potted_rawst_berry", BlocksInvoker.createFlowerPotBlock(RAWST_BERRY))
|
||||
@JvmField
|
||||
val POTTED_RAZZ_BERRY = this.create("potted_razz_berry", BlocksInvoker.createFlowerPotBlock(RAZZ_BERRY))
|
||||
@JvmField
|
||||
val POTTED_RINDO_BERRY = this.create("potted_rindo_berry", BlocksInvoker.createFlowerPotBlock(RINDO_BERRY))
|
||||
@JvmField
|
||||
val POTTED_ROSELI_BERRY = this.create("potted_roseli_berry", BlocksInvoker.createFlowerPotBlock(ROSELI_BERRY))
|
||||
@JvmField
|
||||
val POTTED_ROWAP_BERRY = this.create("potted_rowap_berry", BlocksInvoker.createFlowerPotBlock(ROWAP_BERRY))
|
||||
@JvmField
|
||||
val POTTED_SALAC_BERRY = this.create("potted_salac_berry", BlocksInvoker.createFlowerPotBlock(SALAC_BERRY))
|
||||
@JvmField
|
||||
val POTTED_SHUCA_BERRY = this.create("potted_shuca_berry", BlocksInvoker.createFlowerPotBlock(SHUCA_BERRY))
|
||||
@JvmField
|
||||
val POTTED_SITRUS_BERRY = this.create("potted_sitrus_berry", BlocksInvoker.createFlowerPotBlock(SITRUS_BERRY))
|
||||
@JvmField
|
||||
val POTTED_SPELON_BERRY = this.create("potted_spelon_berry", BlocksInvoker.createFlowerPotBlock(SPELON_BERRY))
|
||||
@JvmField
|
||||
val POTTED_STARF_BERRY = this.create("potted_starf_berry", BlocksInvoker.createFlowerPotBlock(STARF_BERRY))
|
||||
@JvmField
|
||||
val POTTED_TAMATO_BERRY = this.create("potted_tamato_berry", BlocksInvoker.createFlowerPotBlock(TAMATO_BERRY))
|
||||
@JvmField
|
||||
val POTTED_TANGA_BERRY = this.create("potted_tanga_berry", BlocksInvoker.createFlowerPotBlock(TANGA_BERRY))
|
||||
@JvmField
|
||||
val POTTED_TOUGA_BERRY = this.create("potted_touga_berry", BlocksInvoker.createFlowerPotBlock(TOUGA_BERRY))
|
||||
@JvmField
|
||||
val POTTED_WACAN_BERRY = this.create("potted_wacan_berry", BlocksInvoker.createFlowerPotBlock(WACAN_BERRY))
|
||||
@JvmField
|
||||
val POTTED_WATMEL_BERRY = this.create("potted_watmel_berry", BlocksInvoker.createFlowerPotBlock(WATMEL_BERRY))
|
||||
@JvmField
|
||||
val POTTED_WEPEAR_BERRY = this.create("potted_wepear_berry", BlocksInvoker.createFlowerPotBlock(WEPEAR_BERRY))
|
||||
@JvmField
|
||||
val POTTED_WIKI_BERRY = this.create("potted_wiki_berry", BlocksInvoker.createFlowerPotBlock(WIKI_BERRY))
|
||||
@JvmField
|
||||
val POTTED_YACHE_BERRY = this.create("potted_yache_berry", BlocksInvoker.createFlowerPotBlock(YACHE_BERRY))
|
||||
|
||||
init {
|
||||
/**
|
||||
@@ -1210,12 +1284,6 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
|
||||
return block
|
||||
}
|
||||
|
||||
private fun nestBlock(variant: NestBlock.NestVariant): NestBlock {
|
||||
val identifier = cobblemonResource(variant.serializedName)
|
||||
val block = NestBlock(variant, BlockBehaviour.Properties.of().noOcclusion().pushReaction(PushReaction.DESTROY))
|
||||
return this.create(identifier.path, block)
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls helper method from Vanilla
|
||||
*/
|
||||
@@ -1277,4 +1345,48 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
|
||||
}
|
||||
|
||||
fun typeGemClusters() = typeGemClusters.toMap()
|
||||
|
||||
private fun pottedPlant(name: String, flower: Block) = this.create(name, BlocksInvoker.createFlowerPotBlock(flower))
|
||||
|
||||
private fun mintLitter(name: String, color: MapColor) = this.create(
|
||||
name,
|
||||
MintLitterBlock(
|
||||
BlockBehaviour.Properties.of()
|
||||
.sound(CobblemonSounds.MINT_SOUNDS)
|
||||
.mapColor(color)
|
||||
.noOcclusion()
|
||||
.noCollission()
|
||||
.pushReaction(PushReaction.DESTROY)
|
||||
)
|
||||
)
|
||||
|
||||
private fun terrainSeed(name: String, type: TerrainSeedBlock.TerrainSeedType) = this.create(
|
||||
name,
|
||||
TerrainSeedBlock(
|
||||
type,
|
||||
BlockBehaviour.Properties.of()
|
||||
.pushReaction(PushReaction.DESTROY)
|
||||
.ignitedByLava()
|
||||
.mapColor(MapColor.PLANT)
|
||||
.offsetType(BlockBehaviour.OffsetType.XZ)
|
||||
.noCollission()
|
||||
.randomTicks()
|
||||
.instabreak()
|
||||
)
|
||||
)
|
||||
|
||||
private fun terrainPlant(name: String, lightLevel: (BlockState) -> Int) = this.create(
|
||||
name,
|
||||
TerrainPlantBlock(
|
||||
BlockBehaviour.Properties.of()
|
||||
.mapColor(MapColor.PLANT)
|
||||
.noCollission()
|
||||
.instabreak()
|
||||
.sound(SoundType.GRASS)
|
||||
.offsetType(BlockBehaviour.OffsetType.XZ)
|
||||
.ignitedByLava()
|
||||
.pushReaction(PushReaction.DESTROY)
|
||||
.lightLevel(lightLevel)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -61,8 +61,6 @@ object CobblemonCommands {
|
||||
ChangeBoxWallpaperCommand.register(dispatcher)
|
||||
RunMolangCommand.register(dispatcher)
|
||||
RunMolangScriptCommand.register(dispatcher)
|
||||
GivePokemonEggCommand.register(dispatcher)
|
||||
GiveAllPokemonEggCommand.register(dispatcher)
|
||||
SpectateBattleCommand.register(dispatcher)
|
||||
CobblemonConfigCommand.register(dispatcher)
|
||||
UnlockPCBoxWallpaperCommand.register(dispatcher)
|
||||
@@ -75,11 +73,9 @@ object CobblemonCommands {
|
||||
PcTakeCommand.register(dispatcher)
|
||||
GiveTmCommand.register(dispatcher)
|
||||
TmCommand.register(dispatcher)
|
||||
BreedPokemonCommand.register(dispatcher)
|
||||
|
||||
// Possibly lock down registration if and only if under dev environment or running in an environment
|
||||
// with a certain system environment variable set
|
||||
CobblemonInfoCommand.register(dispatcher)
|
||||
DebugCobblemonAICommand.register(dispatcher)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,8 +26,6 @@ import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.resources.ResourceKey
|
||||
import net.minecraft.world.entity.MobCategory
|
||||
import net.minecraft.world.entity.ai.attributes.AttributeSupplier
|
||||
import com.cobblemon.mod.common.entity.RealtimeMoveProjectile
|
||||
import com.example.mod.entity.RealtimeMoveAreaEffect
|
||||
|
||||
object CobblemonEntities : PlatformRegistry<Registry<EntityType<*>>, ResourceKey<Registry<EntityType<*>>>, EntityType<*>>() {
|
||||
|
||||
@@ -89,25 +87,6 @@ object CobblemonEntities : PlatformRegistry<Registry<EntityType<*>>, ResourceKey
|
||||
.build(GENERIC_BEDROCK_ENTITY_KEY.toString())
|
||||
)
|
||||
|
||||
@JvmField
|
||||
val REALTIME_MOVE_PROJECTILE_ENTITY_KEY = cobblemonResource("realtime_move_projectile")
|
||||
@JvmField
|
||||
val REALTIME_MOVE_PROJECTILE_ENTITY: EntityType<RealtimeMoveProjectile> = this.create(
|
||||
REALTIME_MOVE_PROJECTILE_ENTITY_KEY.path,
|
||||
EntityType.Builder.of({ _, world -> RealtimeMoveProjectile(world) }, MobCategory.MISC)
|
||||
.build(REALTIME_MOVE_PROJECTILE_ENTITY_KEY.toString())
|
||||
)
|
||||
|
||||
@JvmField
|
||||
val REALTIME_MOVE_PROJECTILE_AOE_KEY = cobblemonResource("realtime_move_aoe")
|
||||
@JvmField
|
||||
val REALTIME_MOVE_PROJECTILE_AOE: EntityType<RealtimeMoveAreaEffect> = this.create(
|
||||
REALTIME_MOVE_PROJECTILE_AOE_KEY.path,
|
||||
EntityType.Builder.of({ _, world -> RealtimeMoveAreaEffect(world) }, MobCategory.MISC)
|
||||
.build(REALTIME_MOVE_PROJECTILE_AOE_KEY.toString())
|
||||
)
|
||||
|
||||
|
||||
@JvmField
|
||||
val NPC_KEY = cobblemonResource("npc")
|
||||
@JvmField
|
||||
|
||||
@@ -18,7 +18,6 @@ import net.minecraft.core.registries.Registries
|
||||
import net.minecraft.network.codec.ByteBufCodecs
|
||||
import net.minecraft.resources.ResourceKey
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.util.ExtraCodecs
|
||||
|
||||
object CobblemonItemComponents : PlatformRegistry<Registry<DataComponentType<*>>, ResourceKey<Registry<DataComponentType<*>>>, DataComponentType<*>>() {
|
||||
|
||||
@@ -69,16 +68,6 @@ object CobblemonItemComponents : PlatformRegistry<Registry<DataComponentType<*>>
|
||||
.persistent(FoodColourComponent.CODEC)
|
||||
.networkSynchronized(FoodColourComponent.PACKET_CODEC)
|
||||
.build())
|
||||
@JvmField
|
||||
val EGG_DATA: DataComponentType<EggComponent> = create("egg", DataComponentType.builder<EggComponent>()
|
||||
.persistent(EggComponent.CODEC) // For saving/loading
|
||||
.networkSynchronized(EggComponent.PACKET_CODEC) // For network synchronization
|
||||
.build())
|
||||
@JvmField
|
||||
val EGG_MODEL: DataComponentType<String> = create("egg_model", DataComponentType.builder<String>()
|
||||
.persistent(ExtraCodecs.NON_EMPTY_STRING)
|
||||
.networkSynchronized(ByteBufCodecs.STRING_UTF8)
|
||||
.build())
|
||||
|
||||
@JvmField
|
||||
val TM_MOVE: DataComponentType<TMMoveComponent> = create("tm_move", DataComponentType.builder<TMMoveComponent>()
|
||||
@@ -122,8 +111,6 @@ object CobblemonItemComponents : PlatformRegistry<Registry<DataComponentType<*>>
|
||||
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:ingredient"), INGREDIENT)
|
||||
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:food"), FOOD)
|
||||
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:mob_effects"), MOB_EFFECTS)
|
||||
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:egg_data"), EGG_DATA)
|
||||
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:egg_model"), EGG_MODEL)
|
||||
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:held_item_effect"), HELD_ITEM_EFFECT)
|
||||
}
|
||||
|
||||
|
||||
@@ -205,8 +205,6 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
@JvmField
|
||||
val CAMPFIRE_POT_WHITE = campfirePotItem(CobblemonBlocks.WHITE_CAMPFIRE_POT, CampfirePotColor.WHITE)
|
||||
|
||||
@JvmField
|
||||
val SCAREKROW = blockItem("scarekrow", CobblemonBlocks.SCAREKROW)
|
||||
@JvmField
|
||||
val HEARTY_GRAINS = compostableItem("hearty_grains", ItemNameBlockItem(CobblemonBlocks.HEARTY_GRAINS, Properties().rarity(Rarity.COMMON)))
|
||||
@JvmField
|
||||
@@ -479,8 +477,8 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
@JvmField val DUBIOUS_DISC = itemNameBlockItem("dubious_disc", CobblemonBlocks.DUBIOUS_DISC)
|
||||
@JvmField val DEEP_SEA_SCALE = noSettingsItem("deep_sea_scale")
|
||||
@JvmField val DEEP_SEA_TOOTH = noSettingsItem("deep_sea_tooth")
|
||||
@JvmField val ELECTIRIZER = noSettingsItem("electirizer")
|
||||
@JvmField val MAGMARIZER = noSettingsItem("magmarizer")
|
||||
@JvmField val ELECTIRIZER = itemNameBlockItem("electirizer", CobblemonBlocks.ELECTIRIZER)
|
||||
@JvmField val MAGMARIZER = itemNameBlockItem("magmarizer", CobblemonBlocks.MAGMARIZER)
|
||||
@JvmField val OVAL_STONE = noSettingsItem("oval_stone")
|
||||
@JvmField val PROTECTOR = noSettingsItem("protector")
|
||||
@JvmField val REAPER_CLOTH = noSettingsItem("reaper_cloth")
|
||||
@@ -604,22 +602,6 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
|
||||
@JvmField val BERRY_JUICE = this.create("berry_juice", BerryJuiceItem())
|
||||
|
||||
val NEST_GRASSY = blockItem("nest_grassy", CobblemonBlocks.NEST_GRASSY)
|
||||
val NEST_LEAFY = blockItem("nest_leafy", CobblemonBlocks.NEST_LEAFY)
|
||||
val NEST_FIERY = blockItem("nest_fiery", CobblemonBlocks.NEST_FIERY)
|
||||
val NEST_ICY = blockItem("nest_icy", CobblemonBlocks.NEST_ICY)
|
||||
val NEST_HAY = blockItem("nest_hay", CobblemonBlocks.NEST_HAY)
|
||||
val NEST_KELP = blockItem("nest_kelp", CobblemonBlocks.NEST_KELP)
|
||||
val NEST_ROCKY = blockItem("nest_rocky", CobblemonBlocks.NEST_ROCKY)
|
||||
val NEST_SANDY = blockItem("nest_sandy", CobblemonBlocks.NEST_SANDY)
|
||||
val NEST_SHROOMY = blockItem("nest_shroomy", CobblemonBlocks.NEST_SHROOMY)
|
||||
val NEST_TWIGGY = blockItem("nest_twiggy", CobblemonBlocks.NEST_TWIGGY)
|
||||
val NEST_WATERY = blockItem("nest_watery", CobblemonBlocks.NEST_WATERY)
|
||||
|
||||
|
||||
@JvmField
|
||||
val INCUBATOR = blockItem("incubator", CobblemonBlocks.INCUBATOR)
|
||||
|
||||
@JvmField
|
||||
val HABITAT_BLOCK = blockItem("habitat_block", CobblemonBlocks.HABITAT_BLOCK)
|
||||
|
||||
@@ -729,7 +711,7 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
@JvmField
|
||||
val PEP_UP_FLOWER = compostableBlockItem("pep_up_flower", CobblemonBlocks.PEP_UP_FLOWER)
|
||||
@JvmField
|
||||
val MEDICINAL_BREW = noSettingsItem("medicinal_brew")
|
||||
val MEDICINAL_BREW = itemNameBlockItem("medicinal_brew", CobblemonBlocks.MEDICINAL_BREW)
|
||||
@JvmField
|
||||
val REMEDY = compostableItem("remedy", RemedyItem(RemedyItem.NORMAL), 0.65f)
|
||||
@JvmField
|
||||
@@ -737,7 +719,7 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
@JvmField
|
||||
val SUPERB_REMEDY = compostableItem("superb_remedy", RemedyItem(RemedyItem.SUPERB), 1f)
|
||||
@JvmField
|
||||
val MOOMOO_MILK = create("moomoo_milk", MoomooMilk(Item.Properties().craftRemainder(Items.GLASS_BOTTLE).stacksTo((16))))
|
||||
val MOOMOO_MILK = create("moomoo_milk", MoomooMilk(Item.Properties().craftRemainder(Items.GLASS_BOTTLE).stacksTo((16)), CobblemonBlocks.MOOMOO_MILK))
|
||||
|
||||
@JvmField
|
||||
val POTION = create("potion", PotionItem(PotionType.POTION, CobblemonBlocks.POTION))
|
||||
@@ -766,27 +748,27 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
@JvmField
|
||||
val RED_MINT_SEEDS = mintSeed("red", MintType.RED.getCropBlock())
|
||||
@JvmField
|
||||
val RED_MINT_LEAF = mintLeaf("red", MintLeafItem(MintType.RED))
|
||||
val RED_MINT_LEAF = compostableItemNameBlockItem("red_mint_leaf", CobblemonBlocks.RED_MINT_LEAF_LITTER, 0.50f)
|
||||
@JvmField
|
||||
val BLUE_MINT_SEEDS = mintSeed("blue", MintType.BLUE.getCropBlock())
|
||||
@JvmField
|
||||
val BLUE_MINT_LEAF = mintLeaf("blue", MintLeafItem(MintType.BLUE))
|
||||
val BLUE_MINT_LEAF = compostableItemNameBlockItem("blue_mint_leaf", CobblemonBlocks.BLUE_MINT_LEAF_LITTER, 0.50f)
|
||||
@JvmField
|
||||
val CYAN_MINT_SEEDS = mintSeed("cyan", MintType.CYAN.getCropBlock())
|
||||
@JvmField
|
||||
val CYAN_MINT_LEAF = mintLeaf("cyan", MintLeafItem(MintType.CYAN))
|
||||
val CYAN_MINT_LEAF = compostableItemNameBlockItem("cyan_mint_leaf", CobblemonBlocks.CYAN_MINT_LEAF_LITTER, 0.50f)
|
||||
@JvmField
|
||||
val PINK_MINT_SEEDS = mintSeed("pink", MintType.PINK.getCropBlock())
|
||||
@JvmField
|
||||
val PINK_MINT_LEAF = mintLeaf("pink", MintLeafItem(MintType.PINK))
|
||||
val PINK_MINT_LEAF = compostableItemNameBlockItem("pink_mint_leaf", CobblemonBlocks.PINK_MINT_LEAF_LITTER, 0.50f)
|
||||
@JvmField
|
||||
val GREEN_MINT_SEEDS = mintSeed("green", MintType.GREEN.getCropBlock())
|
||||
@JvmField
|
||||
val GREEN_MINT_LEAF = mintLeaf("green", MintLeafItem(MintType.GREEN))
|
||||
val GREEN_MINT_LEAF = compostableItemNameBlockItem("green_mint_leaf", CobblemonBlocks.GREEN_MINT_LEAF_LITTER, 0.50f)
|
||||
@JvmField
|
||||
val WHITE_MINT_SEEDS = mintSeed("white", MintType.WHITE.getCropBlock())
|
||||
@JvmField
|
||||
val WHITE_MINT_LEAF = mintLeaf("white", MintLeafItem(MintType.WHITE))
|
||||
val WHITE_MINT_LEAF = compostableItemNameBlockItem("white_mint_leaf", CobblemonBlocks.WHITE_MINT_LEAF_LITTER, 0.50f)
|
||||
|
||||
val mints = mutableMapOf<String, MintItem>()
|
||||
|
||||
@@ -979,7 +961,7 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
@JvmField
|
||||
val CLEAR_AMULET = heldItem("clear_amulet")
|
||||
@JvmField
|
||||
val DESTINY_KNOT = create("destiny_knot", ItemNameBlockItem(CobblemonBlocks.DESTINY_KNOT, Item.Properties().durability(256)))
|
||||
val DESTINY_KNOT = itemNameBlockItem("destiny_knot", CobblemonBlocks.DESTINY_KNOT)
|
||||
@JvmField
|
||||
val DRAGON_FANG = heldItem("dragon_fang")
|
||||
@JvmField
|
||||
@@ -987,7 +969,7 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
@JvmField
|
||||
val EJECT_PACK = heldItem("eject_pack")
|
||||
@JvmField
|
||||
val ELECTRIC_SEED = heldItem("electric_seed")
|
||||
val ELECTRIC_SEED = itemNameBlockItem("electric_seed", CobblemonBlocks.ELECTRIC_SEED)
|
||||
@JvmField
|
||||
val EVERSTONE = heldItem("everstone")
|
||||
@JvmField
|
||||
@@ -1003,7 +985,7 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
@JvmField
|
||||
val FOCUS_SASH = heldItem("focus_sash")
|
||||
@JvmField
|
||||
val GRASSY_SEED = heldItem("grassy_seed")
|
||||
val GRASSY_SEED = itemNameBlockItem("grassy_seed", CobblemonBlocks.GRASSY_SEED)
|
||||
@JvmField
|
||||
val GRIP_CLAW = heldItem("grip_claw")
|
||||
@JvmField
|
||||
@@ -1051,7 +1033,7 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
@JvmField
|
||||
val POWER_WEIGHT = heldItem("power_weight")
|
||||
@JvmField
|
||||
val PSYCHIC_SEED = heldItem("psychic_seed")
|
||||
val PSYCHIC_SEED = itemNameBlockItem("psychic_seed", CobblemonBlocks.PSYCHIC_SEED)
|
||||
@JvmField
|
||||
val PROTECTIVE_PADS = heldItem("protective_pads")
|
||||
@JvmField
|
||||
@@ -1075,7 +1057,7 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
@JvmField
|
||||
val SILVER_POWDER = heldItem("silver_powder")
|
||||
@JvmField
|
||||
val SOFT_SAND = heldItem("soft_sand")
|
||||
val SOFT_SAND = itemNameBlockItem("soft_sand", CobblemonBlocks.SOFT_SAND)
|
||||
@JvmField
|
||||
val SMOKE_BALL = itemNameBlockItem("smoke_ball", CobblemonBlocks.SMOKE_BALL)
|
||||
@JvmField
|
||||
@@ -1083,7 +1065,7 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
@JvmField
|
||||
val STICKY_BARB = heldItem("sticky_barb")
|
||||
@JvmField
|
||||
val TERRAIN_EXTENDER = heldItem("terrain_extender")
|
||||
val TERRAIN_EXTENDER = itemNameBlockItem("terrain_extender", CobblemonBlocks.TERRAIN_EXTENDER)
|
||||
@JvmField
|
||||
val THROAT_SPRAY = itemNameBlockItem("throat_spray", CobblemonBlocks.THROAT_SPRAY)
|
||||
@JvmField
|
||||
@@ -1103,13 +1085,13 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
@JvmField
|
||||
val MIRROR_HERB = compostableHeldItem("mirror_herb", null, 0.85F)
|
||||
@JvmField
|
||||
val MISTY_SEED = heldItem("misty_seed")
|
||||
val MISTY_SEED = itemNameBlockItem("misty_seed", CobblemonBlocks.MISTY_SEED)
|
||||
@JvmField
|
||||
val POWER_HERB = compostableHeldItem("power_herb", null, 0.85F)
|
||||
@JvmField
|
||||
val WHITE_HERB = compostableHeldItem("white_herb", null, 0.85F)
|
||||
@JvmField
|
||||
val BRIGHT_POWDER = itemNameBlockItem("bright_powder", CobblemonBlocks.BRIGHT_POWDER)
|
||||
val BRIGHT_POWDER = glowItem("bright_powder", CobblemonBlocks.BRIGHT_POWDER)
|
||||
@JvmField
|
||||
val METAL_POWDER = itemNameBlockItem("metal_powder", CobblemonBlocks.METAL_POWDER)
|
||||
@JvmField
|
||||
@@ -1575,10 +1557,25 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
|
||||
//@JvmField
|
||||
//val BINDING_SOIL = blockItem("binding_soil", CobblemonBlocks.BINDING_SOIL)
|
||||
@JvmField
|
||||
val POKEMON_EGG = eggItem("egg", CobblemonBlocks.EGG, Item.Properties().stacksTo(1))
|
||||
|
||||
private fun eggItem(name: String, block: Block, settings: Item.Properties = Item.Properties()): BlockItem = this.create(name, BlockItem(block, settings))
|
||||
// Plants, Flora, and Flowers
|
||||
@JvmField
|
||||
val LOBAILA = compostableBlockItem("lobaila", CobblemonBlocks.LOBAILA, 0.65f)
|
||||
@JvmField
|
||||
val POMOHANA = compostableBlockItem("pomohana", CobblemonBlocks.POMOHANA, 0.3f)
|
||||
@JvmField
|
||||
val PAUVILLEA = compostableBlockItem("pauvillea", CobblemonBlocks.PAUVILLEA, 0.65f)
|
||||
@JvmField
|
||||
val WISTENSU = compostableBlockItem("wistensu", CobblemonBlocks.WISTENSU, 0.5f)
|
||||
|
||||
@JvmField
|
||||
val VOLTACONIA = compostableBlockItem("voltaconia", CobblemonBlocks.VOLTACONIA, 0.85f)
|
||||
@JvmField
|
||||
val VERDROOT = compostableBlockItem("verdroot", CobblemonBlocks.VERDROOT, 0.85f)
|
||||
@JvmField
|
||||
val MISTLEAF = compostableBlockItem("mistleaf", CobblemonBlocks.MISTLEAF, 0.85f)
|
||||
@JvmField
|
||||
val LUCIBELL = compostableBlockItem("lucibell", CobblemonBlocks.LUCIBELL, 0.85f)
|
||||
|
||||
private fun blockItem(name: String, block: Block, rarity: Rarity = Rarity.COMMON): BlockItem = this.create(name, BlockItem(block, Item.Properties().rarity(rarity)))
|
||||
|
||||
@@ -1677,9 +1674,9 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
return create(name, RegionalFoodItem(properties))
|
||||
}
|
||||
|
||||
private fun heldItem(name: String, remappedName: String? = null, settings:Item.Properties = Item.Properties()): CobblemonItem = create(
|
||||
private fun heldItem(name: String, remappedName: String? = null): CobblemonItem = create(
|
||||
name,
|
||||
CobblemonItem(settings).also {
|
||||
CobblemonItem(Item.Properties()).also {
|
||||
if (remappedName != null) {
|
||||
CobblemonHeldItemManager.registerRemap(it, remappedName)
|
||||
}
|
||||
@@ -1782,4 +1779,6 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
private fun foodItem(nutrition: Int, saturationModifier: Float): Item {
|
||||
return CobblemonItem(Item.Properties().food(FoodProperties.Builder().nutrition(nutrition).saturationModifier(saturationModifier).build()))
|
||||
}
|
||||
|
||||
private fun glowItem(name: String, block: Block): GlowItem = this.create(name, GlowItem(block, Item.Properties()))
|
||||
}
|
||||
|
||||
@@ -12,17 +12,15 @@ 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 net.minecraft.world.entity.player.Player
|
||||
import java.util.*
|
||||
|
||||
object CobblemonMemories {
|
||||
val memories = mutableMapOf<String, MemoryModuleType<*>>()
|
||||
@@ -41,14 +39,6 @@ object CobblemonMemories {
|
||||
@JvmField
|
||||
val ACTIVE_ACTION_EFFECT = register<ActionEffectContext>("active_action_effect")
|
||||
@JvmField
|
||||
val POTENTIAL_MATE = register<PokemonEntity>("potential_mate")
|
||||
@JvmField
|
||||
val MATE_SCAN_COOLDOWN = register("mate_scan_cooldown", PrimitiveCodec.BOOL)
|
||||
@JvmField
|
||||
val NEST_LOCATION = register<BlockPos>("nest_location")
|
||||
@JvmField
|
||||
val PATERNAL_NEST_LOCATION = register<BlockPos>("paternal_nest", BlockPos.CODEC)
|
||||
@JvmField
|
||||
val POKEMON_FLYING = register("pokemon_flying", PrimitiveCodec.BOOL)
|
||||
@JvmField
|
||||
val POKEMON_DROWSY = register("pokemon_drowsy", PrimitiveCodec.BOOL)
|
||||
@@ -65,28 +55,16 @@ object CobblemonMemories {
|
||||
@JvmField
|
||||
val NEARBY_GROWABLE_CROPS = register<BlockPos>("nearby_growable_crops")
|
||||
@JvmField
|
||||
val EGG_THIEF = register<Player>("egg_thief") // Stores the player stealing the egg
|
||||
@JvmField
|
||||
val EGG_THIEF_LAST_SEEN_TICK = register("egg_thief_last_seen_tick", PrimitiveCodec.LONG)
|
||||
@JvmField
|
||||
val EGG_LOCATION = register<BlockPos>("egg_location") // Stores the last known location of the egg
|
||||
@JvmField
|
||||
val EGG_IN_NEST = register("egg_in_nest", PrimitiveCodec.BOOL)
|
||||
@JvmField
|
||||
val HOLDING_EGG = register("holding_egg", PrimitiveCodec.BOOL)
|
||||
@JvmField
|
||||
val EGG_SEARCH_COUNTDOWN = register ("egg_search_countdown", PrimitiveCodec.BOOL)
|
||||
@JvmField
|
||||
val IDEAL_NEST_LOCATION = register<BlockPos>("ideal_nest_location")
|
||||
@JvmField
|
||||
val NEST_DESPAWN_IMMUNE_UNTIL_TICK = register("nest_despawn_immune_until_tick", PrimitiveCodec.LONG)
|
||||
@JvmField
|
||||
val NEARBY_SWEET_BERRY_BUSH = register<BlockPos>("nearest_sweet_berry_bush")
|
||||
@JvmField
|
||||
val DISABLE_WALK_TO_BERRY_BUSH = register<Boolean>("disable_walk_to_berry_bush")
|
||||
@JvmField
|
||||
val TIME_TRYING_TO_REACH_BERRY_BUSH = register<Int>("time_trying_to_reach_berry_bush")
|
||||
@JvmField
|
||||
val IS_CONSUMING_ITEM = register<Boolean>("is_consuming_item")
|
||||
@JvmField
|
||||
val RECENTLY_ATE_GRASS = register<Boolean>("recently_ate_grass")
|
||||
@JvmField
|
||||
val HIVE_LOCATION = register<BlockPos>("hive_location", BlockPos.CODEC)
|
||||
@JvmField
|
||||
val HIVE_BLACKLIST = register<List<BlockPos>>("hive_blacklist")
|
||||
@@ -114,8 +92,6 @@ object CobblemonMemories {
|
||||
val DISABLE_WALK_TO_WANTED_ITEM = register<Boolean>("disable_walk_to_wanted_item")
|
||||
@JvmField
|
||||
val TIME_TRYING_TO_REACH_WANTED_ITEM = register<Int>("time_trying_to_wanted_item")
|
||||
@JvmField
|
||||
val NEARBY_SCAREKROW = register<BlockPos>("nearby_scarekrow", BlockPos.CODEC)
|
||||
|
||||
@JvmStatic
|
||||
fun <U> register(id: String, codec: Codec<U>): MemoryModuleType<U> {
|
||||
@@ -130,4 +106,4 @@ object CobblemonMemories {
|
||||
memories[id] = memoryModule
|
||||
return memoryModule
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,6 @@ import com.cobblemon.mod.common.client.net.data.CobblemonMechanicsSyncHandler
|
||||
import com.cobblemon.mod.common.client.net.data.DataRegistrySyncPacketHandler
|
||||
import com.cobblemon.mod.common.client.net.data.RideSettingsSyncHandler
|
||||
import com.cobblemon.mod.common.client.net.debug.OpenRidingStatsDebugGUIHandler
|
||||
import com.cobblemon.mod.common.client.net.debug.UpdateTrackedBrainDataHandler
|
||||
import com.cobblemon.mod.common.client.net.dialogue.DialogueClosedHandler
|
||||
import com.cobblemon.mod.common.client.net.dialogue.DialogueOpenedHandler
|
||||
import com.cobblemon.mod.common.client.net.effect.PokeSnackBlockParticlesHandler
|
||||
@@ -85,7 +84,6 @@ import com.cobblemon.mod.common.net.messages.client.cooking.SeasoningRegistrySyn
|
||||
import com.cobblemon.mod.common.net.messages.client.cooking.ToggleCookingPotLidPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.data.*
|
||||
import com.cobblemon.mod.common.net.messages.client.debug.RequestOpenRidingStatsDebugGUIPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.debug.UpdateTrackedBrainDataPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.dialogue.DialogueClosedPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.dialogue.DialogueOpenedPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.effect.PokeSnackBlockParticlesPacket
|
||||
@@ -168,7 +166,6 @@ import com.cobblemon.mod.common.net.messages.server.behaviour.DamageOnCollisionP
|
||||
import com.cobblemon.mod.common.net.messages.server.behaviour.SetEntityBehaviourPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.block.AdjustBlockEntityViewerCountPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.block.TMMachineTeachMovePacket
|
||||
import com.cobblemon.mod.common.net.messages.server.breeding.TriggerEggHatchPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.callback.move.MoveSelectCancelledPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.callback.move.MoveSelectedPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.callback.party.PartyPokemonSelectedPacket
|
||||
@@ -227,7 +224,6 @@ import com.cobblemon.mod.common.net.serverhandling.behaviour.DamageOnCollisionPa
|
||||
import com.cobblemon.mod.common.net.serverhandling.behaviour.SetEntityBehaviourHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.block.AdjustBlockEntityViewerCountHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.block.TMMachineTeachMoveHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.breeding.TriggerEggHatchHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.callback.move.MoveSelectCancelledHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.callback.move.MoveSelectedHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.callback.party.PartyPokemonSelectedHandler
|
||||
@@ -429,6 +425,7 @@ object CobblemonNetwork {
|
||||
list.add(PacketRegisterInfo(MovesRegistrySyncPacket.ID, MovesRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
|
||||
list.add(PacketRegisterInfo(BerryRegistrySyncPacket.ID, BerryRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
|
||||
list.add(PacketRegisterInfo(SpeciesRegistrySyncPacket.ID, SpeciesRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
|
||||
list.add(PacketRegisterInfo(DexEvolutionDisplaySyncPacket.ID, DexEvolutionDisplaySyncPacket::decode, DataRegistrySyncPacketHandler()))
|
||||
list.add(PacketRegisterInfo(PropertiesCompletionRegistrySyncPacket.ID, PropertiesCompletionRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
|
||||
list.add(PacketRegisterInfo(StandardSpeciesFeatureSyncPacket.ID, StandardSpeciesFeatureSyncPacket::decode, DataRegistrySyncPacketHandler()))
|
||||
list.add(PacketRegisterInfo(GlobalSpeciesFeatureSyncPacket.ID, GlobalSpeciesFeatureSyncPacket::decode, DataRegistrySyncPacketHandler()))
|
||||
@@ -520,8 +517,6 @@ object CobblemonNetwork {
|
||||
|
||||
// Debug
|
||||
list.add(PacketRegisterInfo(OpenRidingStatsDebugGUIPacket.ID, OpenRidingStatsDebugGUIPacket::decode, OpenRidingStatsDebugGUIHandler))
|
||||
list.add(PacketRegisterInfo(UpdateTrackedBrainDataPacket.ID, UpdateTrackedBrainDataPacket::decode, UpdateTrackedBrainDataHandler))
|
||||
|
||||
|
||||
|
||||
return list
|
||||
@@ -571,6 +566,7 @@ object CobblemonNetwork {
|
||||
list.add(PacketRegisterInfo(SwapPartyPokemonPacket.ID, SwapPartyPokemonPacket::decode, SwapPartyPokemonHandler))
|
||||
|
||||
list.add(PacketRegisterInfo(MovePCPokemonPacket.ID, MovePCPokemonPacket::decode, MovePCPokemonHandler))
|
||||
list.add(PacketRegisterInfo(MovePCPokemonBatchPacket.ID, MovePCPokemonBatchPacket::decode, MovePCPokemonBatchHandler))
|
||||
list.add(PacketRegisterInfo(MovePartyPokemonPacket.ID, MovePartyPokemonPacket::decode, MovePartyPokemonHandler))
|
||||
|
||||
list.add(PacketRegisterInfo(SwapPCPartyPokemonPacket.ID, SwapPCPartyPokemonPacket::decode, SwapPCPartyPokemonHandler))
|
||||
@@ -645,9 +641,6 @@ object CobblemonNetwork {
|
||||
list.add(PacketRegisterInfo(SetActiveTMPacket.ID, SetActiveTMPacket::decode, SetActiveTMPacketHandler))
|
||||
list.add(PacketRegisterInfo(SetTMMachineContainerDataPacket.ID, SetTMMachineContainerDataPacket::decode, SetTMMachineContainerDataPacketHandler))
|
||||
|
||||
// Breeding Packets
|
||||
list.add(PacketRegisterInfo(TriggerEggHatchPacket.ID, TriggerEggHatchPacket::decode, TriggerEggHatchHandler))
|
||||
|
||||
// Behaviour Packets
|
||||
list.add(PacketRegisterInfo(SetEntityBehaviourPacket.ID, SetEntityBehaviourPacket::decode, SetEntityBehaviourHandler))
|
||||
list.add(PacketRegisterInfo(DamageOnCollisionPacket.ID, DamageOnCollisionPacket::decode, DamageOnCollisionPacketHandler))
|
||||
|
||||
@@ -18,7 +18,6 @@ import net.minecraft.resources.ResourceKey
|
||||
import net.minecraft.world.entity.ai.village.poi.PoiType
|
||||
import net.minecraft.world.level.block.Block
|
||||
import net.minecraft.world.level.block.state.BlockState
|
||||
import net.minecraft.world.level.block.Blocks
|
||||
|
||||
object CobblemonPoiTypes: PlatformRegistry<Registry<PoiType>, ResourceKey<Registry<PoiType>>, PoiType>() {
|
||||
override val registry: Registry<PoiType> = BuiltInRegistries.POINT_OF_INTEREST_TYPE
|
||||
@@ -35,8 +34,6 @@ object CobblemonPoiTypes: PlatformRegistry<Registry<PoiType>, ResourceKey<Regist
|
||||
val HABITAT_BLOCK_KEY: ResourceKey<PoiType> = createKey("habitat_block", CobblemonBlocks.HABITAT_BLOCK, 0, 1)
|
||||
@JvmField
|
||||
val HABITAT_BLOCK = create(HABITAT_BLOCK_KEY.location().path, PoiType(getBlockStates(CobblemonBlocks.HABITAT_BLOCK), 0, 1))
|
||||
@JvmField
|
||||
val JUKEBOX_KEY = createKey("vanilla_jukebox", Blocks.JUKEBOX, 0, 1)
|
||||
|
||||
private fun createKey(string: String, block: Block, maxTickets: Int, validRange: Int ): ResourceKey<PoiType> =
|
||||
ResourceKey.create(Registries.POINT_OF_INTEREST_TYPE, cobblemonResource(string)).also { resourceKey ->
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common
|
||||
|
||||
import com.bedrockk.molang.Expression
|
||||
import com.cobblemon.mod.common.api.data.JsonDataRegistry
|
||||
import com.cobblemon.mod.common.api.molang.ExpressionLike
|
||||
import com.cobblemon.mod.common.api.reactive.SimpleObservable
|
||||
import com.cobblemon.mod.common.api.types.ElementalType
|
||||
import com.cobblemon.mod.common.api.types.adapters.ElementalTypeAdapter
|
||||
import com.cobblemon.mod.common.pokemon.ai.moves.RealTimeMove
|
||||
import com.cobblemon.mod.common.pokemon.ai.moves.events.MoveEventAction
|
||||
import com.cobblemon.mod.common.util.adapters.ExpressionAdapter
|
||||
import com.cobblemon.mod.common.util.adapters.ExpressionLikeAdapter
|
||||
import com.cobblemon.mod.common.util.adapters.IdentifierAdapter
|
||||
import com.cobblemon.mod.common.util.adapters.RealTimeMoveConfigAdapter
|
||||
import com.cobblemon.mod.common.util.adapters.RealTimeMoveEventActionConfigAdapter
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import net.minecraft.server.packs.PackType
|
||||
|
||||
object CobblemonRealTimeMoves : JsonDataRegistry<RealTimeMove> {
|
||||
override val type = PackType.SERVER_DATA
|
||||
override val observable = SimpleObservable<CobblemonRealTimeMoves>()
|
||||
override val typeToken = TypeToken.get(RealTimeMove::class.java)
|
||||
override val resourcePath: String = "real_time_moves"
|
||||
override val id: ResourceLocation = cobblemonResource(resourcePath)
|
||||
override val gson: Gson = GsonBuilder()
|
||||
.setPrettyPrinting()
|
||||
.registerTypeAdapter(ResourceLocation::class.java, IdentifierAdapter)
|
||||
.registerTypeAdapter(ExpressionLike::class.java, ExpressionLikeAdapter)
|
||||
.registerTypeAdapter(Expression::class.java, ExpressionAdapter)
|
||||
.registerTypeAdapter(RealTimeMove::class.java, RealTimeMoveConfigAdapter)
|
||||
.registerTypeAdapter(MoveEventAction::class.java, RealTimeMoveEventActionConfigAdapter)
|
||||
.registerTypeAdapter(ElementalType::class.java, ElementalTypeAdapter)
|
||||
.create()
|
||||
|
||||
val realTimeMoves = mutableMapOf<ResourceLocation, RealTimeMove>()
|
||||
|
||||
override fun reload(data: Map<ResourceLocation, RealTimeMove>) {
|
||||
realTimeMoves.clear()
|
||||
data.forEach { location, move -> move.id = location }
|
||||
realTimeMoves.putAll(data)
|
||||
}
|
||||
|
||||
override fun sync(player: ServerPlayer) {
|
||||
// nah
|
||||
}
|
||||
}
|
||||
@@ -9,13 +9,13 @@
|
||||
package com.cobblemon.mod.common
|
||||
|
||||
import com.cobblemon.mod.common.entity.pokemon.ai.sensors.*
|
||||
import com.cobblemon.mod.common.pokemon.ai.PokemonDisturbancesSensor
|
||||
import com.cobblemon.mod.common.entity.sensor.BattlingPokemonSensor
|
||||
import com.cobblemon.mod.common.entity.sensor.NPCBattlingSensor
|
||||
import com.cobblemon.mod.common.pokemon.ai.PokemonDisturbancesSensor
|
||||
import java.util.function.Supplier
|
||||
import net.minecraft.world.entity.Entity
|
||||
import net.minecraft.world.entity.ai.sensing.Sensor
|
||||
import net.minecraft.world.entity.ai.sensing.SensorType
|
||||
import java.util.function.Supplier
|
||||
|
||||
object CobblemonSensors {
|
||||
val sensors = mutableMapOf<String, SensorType<*>>()
|
||||
@@ -49,6 +49,9 @@ object CobblemonSensors {
|
||||
@JvmField
|
||||
val NEARBY_FLOWER = register("nearby_flower", ::FlowerSensor)
|
||||
|
||||
@JvmField
|
||||
val NEARBY_SWEET_BERRY_BUSH = register("nearby_sweet_berry_bush", ::SweetBerryBushSensor)
|
||||
|
||||
@JvmField
|
||||
val POKEMON_NEARBY_WANTED_ITEM = register("pokemon_nearby_wanted_item", ::PokemonItemSensor)
|
||||
|
||||
@@ -61,4 +64,4 @@ object CobblemonSensors {
|
||||
sensors[id] = sensor
|
||||
return sensor
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -94,9 +94,6 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, ResourceKey<Regi
|
||||
@JvmField
|
||||
val POKEDEX_SCAN_ZOOM_INCREMENT = this.create("item.pokedex.scan_zoom_increment")
|
||||
|
||||
@JvmField
|
||||
val BREEDING_EGG_HATCH = this.create("breeding.egg_hatch")
|
||||
|
||||
@JvmField
|
||||
val ITEM_USE = this.create("item.use")
|
||||
@JvmField
|
||||
@@ -151,6 +148,9 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, ResourceKey<Regi
|
||||
@JvmField
|
||||
val MONITOR_GLITCHING = this.create("block.monitor.glitching")
|
||||
|
||||
@JvmField
|
||||
val BRIGHT_POWDER_USE = this.create("block.bright_powder.use")
|
||||
|
||||
@JvmField
|
||||
val FOSSIL_MACHINE_ACTIVATE = this.create("block.fossil_machine.activate")
|
||||
@JvmField
|
||||
@@ -660,5 +660,95 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, ResourceKey<Regi
|
||||
SoundEvents.WOOL_FALL
|
||||
)
|
||||
|
||||
@JvmField
|
||||
val ITEM_BLOCK_POWDER_PLACE = this.create("block.item_block.powder.place")
|
||||
@JvmField
|
||||
val ITEM_BLOCK_POWDER_BREAK = this.create("block.item_block.powder.break")
|
||||
@JvmField
|
||||
val ITEM_BLOCK_POWDER_STEP = this.create("block.item_block.powder.step")
|
||||
@JvmField
|
||||
val ITEM_BLOCK_POWDER_SOUNDS = SoundType(1f, 1.1f,
|
||||
ITEM_BLOCK_POWDER_BREAK,
|
||||
ITEM_BLOCK_POWDER_STEP,
|
||||
ITEM_BLOCK_POWDER_PLACE,
|
||||
ITEM_BLOCK_POWDER_STEP,
|
||||
ITEM_BLOCK_POWDER_STEP
|
||||
)
|
||||
|
||||
@JvmField
|
||||
val ITEM_BLOCK_BOTTLE_LIQUID_PLACE = this.create("block.item_block.bottle_liquid.place")
|
||||
@JvmField
|
||||
val ITEM_BLOCK_BOTTLE_LIQUID_BREAK = this.create("block.item_block.bottle_liquid.break")
|
||||
@JvmField
|
||||
val ITEM_BLOCK_BOTTLE_LIQUID_STEP = this.create("block.item_block.bottle_liquid.step")
|
||||
@JvmField
|
||||
val ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS = SoundType(1f, 1.1f,
|
||||
ITEM_BLOCK_BOTTLE_LIQUID_BREAK,
|
||||
ITEM_BLOCK_BOTTLE_LIQUID_STEP,
|
||||
ITEM_BLOCK_BOTTLE_LIQUID_PLACE,
|
||||
ITEM_BLOCK_BOTTLE_LIQUID_PLACE,
|
||||
ITEM_BLOCK_BOTTLE_LIQUID_PLACE
|
||||
)
|
||||
|
||||
@JvmField
|
||||
val ITEM_BLOCK_BOTTLE_PILLS_PLACE = this.create("block.item_block.bottle_pills.place")
|
||||
@JvmField
|
||||
val ITEM_BLOCK_BOTTLE_PILLS_BREAK = this.create("block.item_block.bottle_pills.break")
|
||||
@JvmField
|
||||
val ITEM_BLOCK_BOTTLE_PILLS_STEP = this.create("block.item_block.bottle_pills.step")
|
||||
@JvmField
|
||||
val ITEM_BLOCK_BOTTLE_PILLS_SOUNDS = SoundType(1f, 1.1f,
|
||||
ITEM_BLOCK_BOTTLE_PILLS_BREAK,
|
||||
ITEM_BLOCK_BOTTLE_PILLS_STEP,
|
||||
ITEM_BLOCK_BOTTLE_PILLS_PLACE,
|
||||
ITEM_BLOCK_BOTTLE_PILLS_PLACE,
|
||||
ITEM_BLOCK_BOTTLE_PILLS_PLACE
|
||||
)
|
||||
|
||||
@JvmField
|
||||
val ITEM_BLOCK_DICE_PLACE = this.create("block.item_block.dice.place")
|
||||
@JvmField
|
||||
val ITEM_BLOCK_DICE_BREAK = this.create("block.item_block.dice.break")
|
||||
@JvmField
|
||||
val ITEM_BLOCK_DICE_STEP = this.create("block.item_block.dice.step")
|
||||
@JvmField
|
||||
val ITEM_BLOCK_DICE_SOUNDS = SoundType(1f, 1.1f,
|
||||
ITEM_BLOCK_DICE_BREAK,
|
||||
ITEM_BLOCK_DICE_STEP,
|
||||
ITEM_BLOCK_DICE_PLACE,
|
||||
ITEM_BLOCK_DICE_STEP,
|
||||
ITEM_BLOCK_DICE_STEP
|
||||
)
|
||||
|
||||
@JvmField
|
||||
val ITEM_BLOCK_METAL_CAN_PLACE = this.create("block.item_block.metal_can.place")
|
||||
@JvmField
|
||||
val ITEM_BLOCK_METAL_CAN_BREAK = this.create("block.item_block.metal_can.break")
|
||||
@JvmField
|
||||
val ITEM_BLOCK_METAL_CAN_STEP = this.create("block.item_block.metal_can.step")
|
||||
@JvmField
|
||||
val ITEM_BLOCK_METAL_CAN_SOUNDS = SoundType(1f, 1.1f,
|
||||
ITEM_BLOCK_METAL_CAN_BREAK,
|
||||
ITEM_BLOCK_METAL_CAN_STEP,
|
||||
ITEM_BLOCK_METAL_CAN_PLACE,
|
||||
ITEM_BLOCK_METAL_CAN_STEP,
|
||||
ITEM_BLOCK_METAL_CAN_STEP
|
||||
)
|
||||
|
||||
@JvmField
|
||||
val ITEM_BLOCK_JAR_GLASS_LIQUID_PLACE = this.create("block.item_block.jar_glass_liquid.place")
|
||||
@JvmField
|
||||
val ITEM_BLOCK_JAR_GLASS_LIQUID_BREAK = this.create("block.item_block.jar_glass_liquid.break")
|
||||
@JvmField
|
||||
val ITEM_BLOCK_JAR_GLASS_LIQUID_STEP = this.create("block.item_block.jar_glass_liquid.step")
|
||||
@JvmField
|
||||
val ITEM_BLOCK_JAR_GLASS_LIQUID_SOUNDS = SoundType(1f, 1.1f,
|
||||
ITEM_BLOCK_JAR_GLASS_LIQUID_BREAK,
|
||||
ITEM_BLOCK_JAR_GLASS_LIQUID_STEP,
|
||||
ITEM_BLOCK_JAR_GLASS_LIQUID_PLACE,
|
||||
ITEM_BLOCK_JAR_GLASS_LIQUID_STEP,
|
||||
ITEM_BLOCK_JAR_GLASS_LIQUID_STEP
|
||||
)
|
||||
|
||||
private fun create(name: String): SoundEvent = this.create(name, SoundEvent.createVariableRangeEvent(cobblemonResource(name)))
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ package com.cobblemon.mod.common.api
|
||||
open class PrioritizedList<T> : Iterable<T> {
|
||||
protected val priorityMap = mutableMapOf<Priority, MutableList<T>>()
|
||||
// Please don't change arraylist, we need insertion order
|
||||
// MutableList doesn't necessarily preserve insertion order apparently
|
||||
protected val ordered = arrayListOf<T>()
|
||||
|
||||
val mapping: Map<Priority, List<T>>
|
||||
@@ -27,8 +26,8 @@ open class PrioritizedList<T> : Iterable<T> {
|
||||
|
||||
private fun reorder() {
|
||||
ordered.clear()
|
||||
Priority.entries.forEach {
|
||||
priorityMap[it]?.let(ordered::addAll)
|
||||
Priority.values().forEach {
|
||||
priorityMap[it]?.let { ordered.addAll(it) }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,51 +11,28 @@ package com.cobblemon.mod.common.api.abilities
|
||||
import com.cobblemon.mod.common.Cobblemon.LOGGER
|
||||
import com.cobblemon.mod.common.api.PrioritizedList
|
||||
import com.cobblemon.mod.common.api.Priority
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.addAbilityPoolFunctions
|
||||
import com.cobblemon.mod.common.api.molang.ObjectValue
|
||||
import com.cobblemon.mod.common.pokemon.Pokemon
|
||||
import com.cobblemon.mod.common.pokemon.Species
|
||||
|
||||
/**
|
||||
* A pool of potential abilities, as a [PrioritizedList]. The added logic of this subclass
|
||||
* is that it has selection logic. Given a species and a set of aspects, it will go through
|
||||
* each priority group, and if it can then it will pick from that group and return it. It also
|
||||
* returns the index of that ability within the type band it is.
|
||||
* each priority group, and if it can then it will pick from that group and return it.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since July 28th, 2022
|
||||
*/
|
||||
open class AbilityPool : PrioritizedList<PotentialAbility>() {
|
||||
val struct = ObjectValue(this)
|
||||
.also { it.addAbilityPoolFunctions(this) }
|
||||
|
||||
fun getInheritedAbility(parent: Pokemon): Ability? {
|
||||
if (parent.ability.forced) {
|
||||
return null
|
||||
}
|
||||
|
||||
val parentAbilityType = parent.form.abilities.find { it.template == parent.ability.template }?.type ?: return null // Should be impossible
|
||||
val matchingTypes = ordered.filter { it.type == parentAbilityType }
|
||||
val desiredIndex = parent.ability.index
|
||||
val matchingPotentialAbility = matchingTypes.getOrNull(desiredIndex) ?: return null
|
||||
return matchingPotentialAbility.template.create().also { it.index = desiredIndex }
|
||||
}
|
||||
|
||||
fun select(species: Species, aspects: Set<String>): Ability {
|
||||
val typeMap = ordered.groupBy { it.type }
|
||||
fun select(species: Species, aspects: Set<String>): Pair<Ability, Priority> {
|
||||
for (priority in Priority.entries) {
|
||||
val potentialAbilities = priorityMap[priority]?.filter { it.isSatisfiedBy(aspects) } ?: continue
|
||||
if (potentialAbilities.isNotEmpty()) {
|
||||
val selected = potentialAbilities.random()
|
||||
val ability = selected.template.create(priority = selected.priority)
|
||||
ability.index = typeMap[selected.type]!!.indexOf(selected)
|
||||
return ability
|
||||
return potentialAbilities.random().template.create() to priority
|
||||
}
|
||||
}
|
||||
|
||||
LOGGER.error("Unable to select an ability from the pool for $species and aspects: ${aspects.joinToString()}")
|
||||
LOGGER.error("Please show this to the Cobblemon developers!")
|
||||
Exception().printStackTrace()
|
||||
return Abilities.first().create()
|
||||
return Abilities.first().create() to Priority.LOWEST
|
||||
}
|
||||
}
|
||||
+3
-44
@@ -12,16 +12,13 @@ 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
|
||||
@@ -49,15 +46,8 @@ class BehaviourConfigurationContext {
|
||||
val memories = mutableSetOf<MemoryModuleType<*>>()
|
||||
val sensors = mutableSetOf<SensorType<*>>()
|
||||
|
||||
val objectives = mutableMapOf<Int, MutableList<Objective>>()
|
||||
val senses = mutableSetOf<Sense>()
|
||||
|
||||
val onAdd = mutableListOf<ExpressionLike>()
|
||||
|
||||
init {
|
||||
memories.add(CobblemonMemories.NEST_DESPAWN_IMMUNE_UNTIL_TICK)
|
||||
}
|
||||
|
||||
fun getOrCreateActivity(activity: Activity): ActivityConfigurationContext {
|
||||
return activities.firstOrNull { it.activity == activity } ?: ActivityConfigurationContext(activity).also(activities::add)
|
||||
}
|
||||
@@ -78,22 +68,6 @@ class BehaviourConfigurationContext {
|
||||
this.sensors.addAll(sensors)
|
||||
}
|
||||
|
||||
fun addSenses(vararg senses: Sense) {
|
||||
addSenses(senses.toList())
|
||||
}
|
||||
|
||||
fun addSenses(senses: Collection<Sense>) {
|
||||
outer@for (sense in senses) {
|
||||
for (existingSense in this.senses) {
|
||||
if (existingSense.merge(sense)) {
|
||||
continue@outer
|
||||
}
|
||||
}
|
||||
this.senses.add(sense)
|
||||
this.addMemories(sense.memories)
|
||||
}
|
||||
}
|
||||
|
||||
fun addOnAddScript(script: ExpressionLike) {
|
||||
onAdd.add(script)
|
||||
}
|
||||
@@ -114,23 +88,8 @@ class BehaviourConfigurationContext {
|
||||
var brain = entity.brain
|
||||
// Apply the brain config
|
||||
if (entity is MoLangScriptingEntity) {
|
||||
val useBiggerBrain = objectives.isNotEmpty() || senses.isNotEmpty()
|
||||
brain = entity.assignNewBrainWithMemoriesAndSensors(dynamic, memories, if (useBiggerBrain) emptySet() else sensors)
|
||||
brain = entity.assignNewBrainWithMemoriesAndSensors(dynamic, memories, sensors)
|
||||
onAdd.forEach { runtime.resolve(it) }
|
||||
if (useBiggerBrain) {
|
||||
val wereDebugging = entity.biggerBrain.debuggingPlayers ?: emptySet()
|
||||
val biggerBrain = BiggerBrain(entity)
|
||||
entity.biggerBrain = biggerBrain
|
||||
addSenses(biggerBrain.senses)
|
||||
val orderedObjectives = objectives.entries.sortedBy { it.key }.flatMap { it.value }
|
||||
orderedObjectives.forEach(biggerBrain::registerObjective)
|
||||
biggerBrain.senses.addAll(senses)
|
||||
biggerBrain.debuggingPlayers.addAll(wereDebugging)
|
||||
return
|
||||
} else {
|
||||
val biggerBrain = BiggerBrain(entity)
|
||||
entity.biggerBrain = biggerBrain
|
||||
}
|
||||
}
|
||||
activities.forEach { it.apply(entity) }
|
||||
brain.setCoreActivities(coreActivities)
|
||||
@@ -177,4 +136,4 @@ class BehaviourConfigurationContext {
|
||||
}
|
||||
return struct
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -42,8 +42,7 @@ class CobblemonAttackTargetData(
|
||||
/** An expression that returns false when the target is no longer worth attacking. */
|
||||
var shouldContinue: Expression = "true".asExpression(),
|
||||
var hitsUntilDisengage: Int = -1, // -1 means no limit
|
||||
var disengageAction: (entity: Entity) -> Unit = {},
|
||||
var hitsLanded : Int = 0
|
||||
var disengageAction: (entity: Entity) -> Unit = {}
|
||||
) {
|
||||
val struct = ObjectValue<CobblemonAttackTargetData>(this).also {
|
||||
it.addFunction("hits_until_disengage") { DoubleValue(hitsUntilDisengage.toDouble()) }
|
||||
|
||||
@@ -9,40 +9,44 @@
|
||||
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. Anything left null
|
||||
* will fall back to defaults specified by the logic of the WanderAround objective.
|
||||
* Used to maintain some settings around how the entity should wander this tick. The idea is you
|
||||
* reset this with some script task at priority 0 then some number of steps before it gets to the wander
|
||||
* tasks you can have other conditional tasks jump in to configure parts of this.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since July 18, 2025
|
||||
*/
|
||||
class CobblemonWanderControl(
|
||||
var center: WanderCenter? = null,
|
||||
var maxAttempts: Int? = null, // How many times to try to find a suitable point before giving up on that tick.
|
||||
var allowLand: Boolean? = null,
|
||||
var allowWater: Boolean? = null,
|
||||
var allowAir: Boolean? = null,
|
||||
var wanderSpeed: Float? = null,
|
||||
var wanderChanceMultiplier: Float? = null
|
||||
var maxAttempts: Int = 4, // How many times to try to find a suitable point before giving up on that tick.
|
||||
var allowLand: Boolean = true,
|
||||
var allowWater: Boolean = false,
|
||||
var allowAir: Boolean = false,
|
||||
var pathCooldownTicks: Int = 20 * 5, // Used to mitigate the cost of pathfinding.
|
||||
var wanderSpeed: Float = 0.35F,
|
||||
) {
|
||||
class WanderCenter(val x: Number, val y: Number, val z: Number, val minRange: Float, val maxRange: Float)
|
||||
|
||||
@Transient
|
||||
val struct: QueryStruct = QueryStruct(hashMapOf())
|
||||
.addFunction("center_x") { DoubleValue(center?.x ?: 0.0) }
|
||||
.addFunction("center_y") { DoubleValue(center?.y ?: 0.0) }
|
||||
.addFunction("center_z") { DoubleValue(center?.z ?: 0.0) }
|
||||
.addFunction("max_attempts") { DoubleValue(maxAttempts?.toDouble() ?: 0.0) }
|
||||
.addFunction("min_range") { DoubleValue(center?.minRange ?: 0.0) }
|
||||
.addFunction("max_range") { DoubleValue(center?.maxRange ?: 0.0) }
|
||||
.addFunction("allow_land") { DoubleValue(if (allowLand == true) 1.0 else 0.0) }
|
||||
.addFunction("allow_water") { DoubleValue(if (allowWater == true) 1.0 else 0.0) }
|
||||
.addFunction("allow_air") { DoubleValue(if (allowAir == true) 1.0 else 0.0) }
|
||||
.addFunction("wander_speed") { DoubleValue(wanderSpeed ?: 0.0) }
|
||||
.addFunction("center_x") { center?.x ?: 0.0 }
|
||||
.addFunction("center_y") { center?.y ?: 0.0 }
|
||||
.addFunction("center_z") { center?.z ?: 0.0 }
|
||||
.addFunction("max_attempts") { maxAttempts.toDouble() }
|
||||
.addFunction("min_range") { center?.minRange ?: 0.0 }
|
||||
.addFunction("max_range") { center?.maxRange ?: 0.0 }
|
||||
.addFunction("allow_land") { if (allowLand) 1.0 else 0.0 }
|
||||
.addFunction("allow_water") { if (allowWater) 1.0 else 0.0 }
|
||||
.addFunction("allow_air") { if (allowAir) 1.0 else 0.0 }
|
||||
.addFunction("path_cooldown_ticks") { pathCooldownTicks.toDouble() }
|
||||
.addFunction("wander_speed") { wanderSpeed }
|
||||
.addFunction("set_center") { params ->
|
||||
if (params.params.size < 3) {
|
||||
throw IllegalArgumentException("set_center requires at least 3 parameters: x, y, z")
|
||||
@@ -64,6 +68,26 @@ class CobblemonWanderControl(
|
||||
.addFunction("set_allow_land") { params -> allowLand = params.getBooleanOrNull(0) ?: true }
|
||||
.addFunction("set_allow_water") { params -> allowWater = params.getBooleanOrNull(0) ?: false }
|
||||
.addFunction("set_allow_air") { params -> allowAir = params.getBooleanOrNull(0) ?: false }
|
||||
.addFunction("set_path_cooldown_ticks") { params -> pathCooldownTicks = params.getDoubleOrNull(0)?.toInt() ?: (20 * 5) }
|
||||
.addFunction("set_wander_speed") { params -> wanderSpeed = params.getDoubleOrNull(0)?.toFloat() ?: 0.35F }
|
||||
.addFunction("reset") { reset() }
|
||||
|
||||
fun isSuitable(position: BlockPos): Boolean {
|
||||
val center = center ?: return true
|
||||
val centerPos = BlockPos(center.x.toInt(), center.y.toInt(), center.z.toInt())
|
||||
val distance = sqrt(centerPos.distSqr(position))
|
||||
val suitable = distance >= center.minRange && distance <= center.maxRange
|
||||
return suitable
|
||||
}
|
||||
|
||||
fun reset() {
|
||||
center = null
|
||||
allowLand = false
|
||||
allowWater = false
|
||||
allowAir = false
|
||||
pathCooldownTicks = 20 * 5
|
||||
wanderSpeed = 0.35F
|
||||
maxAttempts = 4
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,23 +9,9 @@
|
||||
package com.cobblemon.mod.common.api.ai
|
||||
|
||||
import com.bedrockk.molang.Expression
|
||||
import com.bedrockk.molang.ast.NumberExpression
|
||||
import com.bedrockk.molang.ast.StringExpression
|
||||
import com.bedrockk.molang.runtime.MoLangRuntime
|
||||
import com.bedrockk.molang.runtime.value.DoubleValue
|
||||
import com.bedrockk.molang.runtime.value.StringValue
|
||||
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
|
||||
import com.cobblemon.mod.common.entity.MoLangScriptingEntity
|
||||
import com.cobblemon.mod.common.util.asExpression
|
||||
import com.cobblemon.mod.common.util.lang
|
||||
import com.cobblemon.mod.common.util.resolveBoolean
|
||||
import com.cobblemon.mod.common.util.resolveDouble
|
||||
import com.cobblemon.mod.common.util.resolveFloat
|
||||
import com.cobblemon.mod.common.util.resolveInt
|
||||
import com.cobblemon.mod.common.util.resolveString
|
||||
import com.mojang.datafixers.util.Either
|
||||
import kotlin.jvm.optionals.getOrNull
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
|
||||
/**
|
||||
* The issue in a lot of the behaviour datapacking is that there are times when a property should be exposed to
|
||||
@@ -45,50 +31,6 @@ import net.minecraft.world.entity.LivingEntity
|
||||
*/
|
||||
typealias ExpressionOrEntityVariable = Either<Expression, MoLangConfigVariable>
|
||||
|
||||
fun ExpressionOrEntityVariable.asExpression() = map({ it }, { "q.entity.config.${it.variableName}".asExpression() })
|
||||
fun ExpressionOrEntityVariable.resolveString(runtime: MoLangRuntime) = runtime.resolveString(asExpression())
|
||||
fun ExpressionOrEntityVariable.resolveBoolean(runtime: MoLangRuntime) = runtime.resolveBoolean(asExpression())
|
||||
fun ExpressionOrEntityVariable.resolveInt(runtime: MoLangRuntime) = runtime.resolveInt(asExpression())
|
||||
fun ExpressionOrEntityVariable.resolveDouble(runtime: MoLangRuntime) = runtime.resolveDouble(asExpression())
|
||||
fun ExpressionOrEntityVariable.resolveFloat(runtime: MoLangRuntime) = runtime.resolveFloat(asExpression())
|
||||
|
||||
private fun variable(category: String, name: String, type: MoLangConfigVariable.MoLangVariableType, default: String) = MoLangConfigVariable(
|
||||
variableName = name,
|
||||
category = lang("entity.variable.category.$category"),
|
||||
displayName = lang("entity.variable.$name.name"),
|
||||
description = lang("entity.variable.$name.desc"),
|
||||
type = type,
|
||||
defaultValue = default
|
||||
)
|
||||
|
||||
fun stringVariable(category: String, name: String, default: String) = variable(category = category, name = name, type = MoLangConfigVariable.MoLangVariableType.TEXT, default = default)
|
||||
fun numberVariable(category: String, name: String, default: Number) = variable(category = category, name = name, type = MoLangConfigVariable.MoLangVariableType.NUMBER, default = default.toString())
|
||||
fun booleanVariable(category: String, name: String, default: Boolean) = variable(category = category, name = name, type = MoLangConfigVariable.MoLangVariableType.BOOLEAN, default = default.toString())
|
||||
|
||||
fun getVariableExpression(name: String) = "q.entity.config.$name".asExpression()
|
||||
fun resolveBooleanVariable(name: String, runtime: MoLangRuntime) = runtime.resolveBoolean(getVariableExpression(name))
|
||||
|
||||
fun checkCondition(runtime: MoLangRuntime, expressionOrEntityVariable: ExpressionOrEntityVariable): Boolean {
|
||||
return expressionOrEntityVariable.resolveBoolean(runtime)
|
||||
}
|
||||
|
||||
fun ExpressionOrEntityVariable.asSimplifiedExpression(entity: LivingEntity): Expression {
|
||||
return map(
|
||||
{ it },
|
||||
{
|
||||
if (entity is MoLangScriptingEntity) {
|
||||
val variable = entity.config.map[it.variableName]
|
||||
if (variable is DoubleValue) {
|
||||
return@map NumberExpression(variable.value)
|
||||
} else if (variable is StringValue) {
|
||||
return@map StringExpression(variable)
|
||||
}
|
||||
}
|
||||
return@map "q.entity.config.${it.variableName}".asExpression()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* In the case of [ExpressionOrEntityVariable] the right side is the only one that requires visibility for a user,
|
||||
* this function is a shorthand for finding all of the values in the list that have something to show the user to
|
||||
|
||||
@@ -1,193 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain
|
||||
|
||||
import com.bedrockk.molang.runtime.MoLangRuntime
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.CobblemonNetwork.sendPacket
|
||||
import com.cobblemon.mod.common.api.PrioritizedList
|
||||
import com.cobblemon.mod.common.api.Priority
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.Distraction
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.EntityFixation
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.Objective
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.ObjectiveStatus
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.sense.Sense
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.setup
|
||||
import com.cobblemon.mod.common.api.text.text
|
||||
import com.cobblemon.mod.common.net.messages.client.debug.UpdateTrackedBrainDataPacket
|
||||
import com.cobblemon.mod.common.util.getMemorySafely
|
||||
import com.cobblemon.mod.common.util.getPlayer
|
||||
import com.cobblemon.mod.common.util.withQueryValue
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.ai.memory.MemoryModuleType
|
||||
import net.minecraft.world.entity.ai.memory.MemoryStatus
|
||||
import java.util.UUID
|
||||
|
||||
/**
|
||||
* A better* version of Minecraft's [net.minecraft.world.entity.ai.Brain] system that makes development more
|
||||
* straightforward and allows easier debugging.
|
||||
*
|
||||
* We could name this thing better.
|
||||
*
|
||||
* There are three main principles inside this system:
|
||||
*
|
||||
* 1: Senses
|
||||
*
|
||||
* Senses are simple ticking objects that are instantiated per entity and are expected to just update memories based
|
||||
* on properties about the entity or its surroundings. This is an analogue of SensorType from Brains.
|
||||
*
|
||||
* 2: Objectives
|
||||
*
|
||||
* Objectives are also ticking objects that are instantiated per entity. They represent a goal for the entity and
|
||||
* run in a fixed order.
|
||||
*
|
||||
* 3: Fixations
|
||||
*
|
||||
* Fixations are a task that can be set on the brain that represents a more total commitment to a certain task. Only
|
||||
* a single fixation can be active at a time, and they can be 'distracted' by other objectives or potentially external
|
||||
* factors.
|
||||
*
|
||||
* These can also be debugged using the /debugcobblemonai command while looking at a Pokémon or NPC entity.
|
||||
*
|
||||
* *that remains to be seen
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since December 29th, 2025
|
||||
*/
|
||||
class BiggerBrain(val entity: LivingEntity) {
|
||||
val runtime = MoLangRuntime().setup().withQueryValue("entity", entity.asMostSpecificMoLangValue())
|
||||
|
||||
val senses = mutableListOf<Sense>()
|
||||
val objectives = mutableListOf<Objective>()
|
||||
val subscriptions = mutableMapOf<MemoryModuleType<*>, PrioritizedList<MemorySubscription<*>>>()
|
||||
|
||||
class MemorySubscription<U>(
|
||||
val subscription: (U?, labels: MutableSet<String>) -> Boolean
|
||||
) {
|
||||
fun handle(value: Any?, labels: MutableSet<String>): Boolean {
|
||||
val castValue = value as? U
|
||||
return subscription(castValue, labels)
|
||||
}
|
||||
}
|
||||
|
||||
fun <U, M : MemoryModuleType<U>> subscribe(priority: Priority = Priority.NORMAL, memory: M, handler: (U?, labels: MutableSet<String>) -> Boolean) {
|
||||
subscriptions
|
||||
.getOrPut(memory, ::PrioritizedList)
|
||||
.add(priority, MemorySubscription(handler))
|
||||
}
|
||||
|
||||
var fixation: EntityFixation? = null
|
||||
|
||||
var debuggingPlayers = mutableSetOf<UUID>()
|
||||
|
||||
fun registerObjective(objective: Objective) {
|
||||
objectives.add(objective)
|
||||
objective.onRegister(entity, this)
|
||||
}
|
||||
|
||||
fun isFixation(fixation: EntityFixation?): Boolean {
|
||||
return fixation != null && this.fixation == fixation
|
||||
}
|
||||
|
||||
fun sendDebugMessage(message: String) {
|
||||
debuggingPlayers.forEach {
|
||||
it.getPlayer()?.sendSystemMessage(message.text())
|
||||
}
|
||||
}
|
||||
|
||||
fun tick() {
|
||||
senses.forEach {
|
||||
it.ticksUntilNextExecution--
|
||||
if (it.ticksUntilNextExecution <= 0) {
|
||||
it.tick(entity, this)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
val labels = mutableSetOf<String>()
|
||||
subscriptions[memory]?.forEach { it.handle(value = value, labels = labels) }
|
||||
}
|
||||
|
||||
fun <T> setMemoryIfChanged(memory: MemoryModuleType<T>, value: T, expiry: Long? = null) {
|
||||
if (entity.brain.getMemorySafely(memory).orElse(null) != value) {
|
||||
setMemory(memory, value, expiry)
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> eraseMemory(memory: MemoryModuleType<T>) {
|
||||
entity.brain.eraseMemory(memory)
|
||||
val labels = mutableSetOf<String>()
|
||||
subscriptions[memory]?.forEach { it.handle(value = null, labels = labels) }
|
||||
}
|
||||
|
||||
fun <T> eraseMemoryIfPresent(memory: MemoryModuleType<T>) {
|
||||
if (entity.brain.hasMemoryValue(memory)) {
|
||||
eraseMemory(memory)
|
||||
}
|
||||
}
|
||||
|
||||
fun hasMemoryValue(memory: MemoryModuleType<*>): Boolean {
|
||||
return entity.brain.checkMemory(memory, MemoryStatus.VALUE_PRESENT)
|
||||
}
|
||||
|
||||
fun <T> getMemory(memory: MemoryModuleType<T>): T? {
|
||||
return entity.brain.getMemorySafely(memory).orElse(null)
|
||||
}
|
||||
}
|
||||
-74
@@ -1,74 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain
|
||||
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.Objective
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.ObjectiveStatus
|
||||
import kotlin.reflect.jvm.jvmName
|
||||
import net.minecraft.network.FriendlyByteBuf
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
|
||||
/**
|
||||
* A DTO of sorts that is sent to debugging players to keep them informed about the state of an entity's Cobblemon AI.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since December 29th, 2025
|
||||
*/
|
||||
class BiggerBrainState(
|
||||
val senses: List<SenseState>,
|
||||
val objectives: List<ObjectiveState>,
|
||||
val fixation: String? = null
|
||||
) {
|
||||
class SenseState(val className: String, val ticksUntilNextExecution: Int) {
|
||||
fun encode(buffer: FriendlyByteBuf) {
|
||||
buffer.writeUtf(className)
|
||||
buffer.writeInt(ticksUntilNextExecution)
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun decode(buffer: FriendlyByteBuf): SenseState {
|
||||
return SenseState(buffer.readUtf(), buffer.readInt())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ObjectiveState(val description: String, val status: ObjectiveStatus) {
|
||||
fun encode(buffer: FriendlyByteBuf) {
|
||||
buffer.writeUtf(description)
|
||||
buffer.writeEnum(status)
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun decode(buffer: FriendlyByteBuf): ObjectiveState {
|
||||
return ObjectiveState(buffer.readUtf(), buffer.readEnum(ObjectiveStatus::class.java))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
constructor(entity: LivingEntity, brain: BiggerBrain, objectiveStatuses: Map<Objective, ObjectiveStatus>): this(
|
||||
senses = brain.senses.map { SenseState(className = it::class.simpleName ?: it::class.jvmName, it.ticksUntilNextExecution) },
|
||||
objectives = objectiveStatuses.entries.map { ObjectiveState(it.key.getDescription(entity), it.value) },
|
||||
fixation = brain.fixation?.getDescription(entity)
|
||||
)
|
||||
|
||||
fun encode(buffer: FriendlyByteBuf) {
|
||||
buffer.writeCollection(senses) { _, sense -> sense.encode(buffer) }
|
||||
buffer.writeCollection(objectives) { _, objective -> objective.encode(buffer) }
|
||||
buffer.writeNullable(fixation) { _, fixation -> buffer.writeUtf(fixation) }
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun decode(buffer: FriendlyByteBuf): BiggerBrainState {
|
||||
val senses = buffer.readList { SenseState.decode(it) }
|
||||
val objectives = buffer.readList { ObjectiveState.decode(it) }
|
||||
val fixation = buffer.readNullable { buffer.readUtf() }
|
||||
return BiggerBrainState(senses, objectives, fixation)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain
|
||||
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.PathfinderMob
|
||||
import net.minecraft.world.entity.ai.behavior.BlockPosTracker
|
||||
import net.minecraft.world.entity.ai.behavior.EntityTracker
|
||||
import net.minecraft.world.entity.ai.behavior.PositionTracker
|
||||
import net.minecraft.world.entity.ai.memory.WalkTarget
|
||||
|
||||
/**
|
||||
* A trivial wrapper around an entity and a position tracker to make the entity look at the position each tick.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since December 29th, 2025
|
||||
*/
|
||||
class LookingTracker(val entity: PathfinderMob, val lookTarget: PositionTracker) {
|
||||
companion object {
|
||||
fun forTargetEntity(entity: PathfinderMob, target: LivingEntity, trackEyeHeight: Boolean = true): LookingTracker {
|
||||
return LookingTracker(entity, EntityTracker(target, trackEyeHeight))
|
||||
}
|
||||
|
||||
fun fromNavigationPathOrWalkTarget(entity: PathfinderMob, walkTarget: WalkTarget): LookingTracker {
|
||||
val destinationPoint = entity.navigation.path?.endNode?.asVec3() ?: walkTarget.target.currentPosition()
|
||||
return LookingTracker(entity, BlockPosTracker(destinationPoint.add(0.0, entity.eyeHeight.toDouble(), 0.0)))
|
||||
}
|
||||
}
|
||||
fun tick() {
|
||||
entity.lookControl.setLookAt(lookTarget.currentPosition())
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain
|
||||
|
||||
/**
|
||||
* A simple enum representing the status of a movement action.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since December 29th, 2025
|
||||
*/
|
||||
enum class MovementStatus {
|
||||
/** Represents when the entity is still en route to the destination. */
|
||||
PENDING,
|
||||
/** Represents when the entity has successfully reached the destination. */
|
||||
COMPLETED,
|
||||
/**
|
||||
* Represents when the entity has failed to reach the destination. This can be due to
|
||||
* a change in surroundings or taking too long or all sorts of things.
|
||||
*/
|
||||
FAILED
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain
|
||||
|
||||
import net.minecraft.world.entity.ai.memory.WalkTarget
|
||||
|
||||
/**
|
||||
* A wrapping around logic that maintains an entity's journey towards a specific destination. Mainly used by fixations
|
||||
* to keep track of when pathing is done, and whether it failed or succeeded. Can also be told to stop to do any
|
||||
* necessary clean up. Implemented only by [RegularMovementTracker] which has me questioning if it should be an interface
|
||||
* or just a simple class like the [LookingTracker].
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since December 29th, 2025
|
||||
*/
|
||||
interface MovementTracker {
|
||||
val walkTarget: WalkTarget
|
||||
/** The last known status of this movement tracker. */
|
||||
var lastStatus: MovementStatus
|
||||
/** Performs a tick of movement tracking logic. Returns the current [MovementStatus]. */
|
||||
fun tick(): MovementStatus
|
||||
/** Stops the movement and does any necessary clean up. */
|
||||
fun stop()
|
||||
}
|
||||
-209
@@ -1,209 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain
|
||||
|
||||
import com.cobblemon.mod.common.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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,223 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain
|
||||
|
||||
import com.cobblemon.mod.common.api.ai.CobblemonWanderControl.WanderCenter
|
||||
import com.cobblemon.mod.common.api.ai.config.task.WanderTaskConfig.Companion.MAX_LOOK_DOWN_DISTANCE
|
||||
import com.cobblemon.mod.common.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
@@ -1,42 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.distraction
|
||||
|
||||
import net.minecraft.world.damagesource.DamageSource
|
||||
|
||||
/**
|
||||
* Something that may distract an entity from its current fixation. The majority of fixations
|
||||
* will rely on the types of distraction to determine if they should be distracted.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since December 29th, 2025
|
||||
*/
|
||||
open class Distraction(val types: Set<DistractionType>) {
|
||||
constructor(vararg types: DistractionType) : this(types.toSet())
|
||||
|
||||
val isIdleBoredom = DistractionType.IDLE_BOREDOM in types
|
||||
val isForce = DistractionType.FORCE in types
|
||||
val isComfort = DistractionType.COMFORT in types
|
||||
val isDamage = DistractionType.DAMAGE in types
|
||||
|
||||
fun hasAnyOf(vararg distractionTypes: DistractionType): Boolean {
|
||||
return distractionTypes.any { it in types }
|
||||
}
|
||||
|
||||
companion object {
|
||||
val FORCE = Distraction(DistractionType.FORCE)
|
||||
}
|
||||
}
|
||||
|
||||
/** Reusable basic distraction for a COMFORT type of distraction. */
|
||||
val comfortDistraction = Distraction(DistractionType.COMFORT)
|
||||
/** Reusable basic distraction for an IDLE_BOREDOM type of distraction. */
|
||||
val idleBoredomDistraction = Distraction(DistractionType.IDLE_BOREDOM)
|
||||
|
||||
open class HurtDistraction(val damageSource: DamageSource) : Distraction(DistractionType.DAMAGE)
|
||||
-28
@@ -1,28 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.distraction
|
||||
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.EntityFixation
|
||||
|
||||
/**
|
||||
* A categorisation of distractions that can be attempted on an [EntityFixation].
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since December 29th, 2025
|
||||
*/
|
||||
enum class DistractionType {
|
||||
/** Represents a distraction that the entity is not particularly motivated by but would consider doing out of boredom. */
|
||||
IDLE_BOREDOM,
|
||||
/** Represents a distraction that is for the sake of the entity's comfort because it isn't comfortable where it is. */
|
||||
COMFORT,
|
||||
/** Represents a distraction motivated by being hurt. */
|
||||
DAMAGE,
|
||||
/** A forceful distraction. Just about everything should respect this but it's conceivable that something not. */
|
||||
FORCE,
|
||||
}
|
||||
-161
@@ -1,161 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.fixation
|
||||
|
||||
import com.bedrockk.molang.Expression
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.LookingTracker
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.MovementStatus
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.MovementTracker
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.RegularMovementTracker
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.Distraction
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.DistractionType
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.pokemon.ai.moves.RealTimeMove
|
||||
import com.cobblemon.mod.common.pokemon.ai.moves.RealTimeMoveInstance
|
||||
import com.cobblemon.mod.common.util.asExpression
|
||||
import com.cobblemon.mod.common.util.mainThreadRuntime
|
||||
import com.cobblemon.mod.common.util.resolveFloat
|
||||
import com.cobblemon.mod.common.util.withQueryValue
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.ai.behavior.EntityTracker
|
||||
import net.minecraft.world.entity.ai.memory.MemoryModuleType
|
||||
import net.minecraft.world.entity.ai.memory.WalkTarget
|
||||
import net.minecraft.world.entity.player.Player
|
||||
import kotlin.collections.set
|
||||
|
||||
class AttackFixation(
|
||||
val targetEntity: LivingEntity,
|
||||
/** Distraction types that will be respected. */
|
||||
private val speedMultiplier: Expression = "1.0".asExpression(),
|
||||
private val availableMoves: List<RealTimeMove> = emptyList(),
|
||||
val distractionTypes: Set<DistractionType>,
|
||||
) : EntityFixation {
|
||||
|
||||
private val globalAttackCooldownTicks: Int = 20 // TODO: pull from pokemon stats
|
||||
var movementTracker: MovementTracker? = null
|
||||
var lookingTracker: LookingTracker? = null
|
||||
var activeMoveInstance: RealTimeMoveInstance? = null
|
||||
override fun getDescription(entity: LivingEntity): String {
|
||||
return "Attacking Target"
|
||||
}
|
||||
|
||||
override fun canDistract(distraction: Distraction, entity: LivingEntity): Boolean {
|
||||
return distraction.types.any(distractionTypes::contains)
|
||||
}
|
||||
|
||||
override fun tick(entity: LivingEntity, brain: BiggerBrain): Boolean {
|
||||
if (entity !is PokemonEntity || !entity.isAlive) return false
|
||||
entity as PokemonEntity // Intellij and my brain says this is redundant but I'm not getting smart casting later on sooo
|
||||
val targetedEntity = brain.getMemory(MemoryModuleType.ATTACK_TARGET)
|
||||
?: return false
|
||||
|
||||
if (targetedEntity.isRemoved || targetedEntity.isDeadOrDying || brain.getMemory(MemoryModuleType.AVOID_TARGET) != null) {
|
||||
brain.eraseMemory(MemoryModuleType.ATTACK_TARGET)
|
||||
brain.eraseMemory(MemoryModuleType.ANGRY_AT)
|
||||
return false
|
||||
}
|
||||
|
||||
if (targetedEntity is Player && (targetedEntity.isCreative || targetedEntity.isSpectator)) {
|
||||
brain.eraseMemory(MemoryModuleType.ATTACK_TARGET)
|
||||
brain.eraseMemory(MemoryModuleType.ANGRY_AT)
|
||||
return false
|
||||
}
|
||||
|
||||
// If we have a current target, check if it's still valid and within roam range (if tethered)
|
||||
if (targetedEntity.isAlive) {
|
||||
val tethering = (entity).tethering
|
||||
if (tethering != null && !tethering.canRoamTo(targetedEntity.blockPosition())) {
|
||||
// Target moved out of range let's kill the memory >:)
|
||||
brain.distract(Distraction.FORCE)
|
||||
entity.brain.eraseMemory(MemoryModuleType.ATTACK_TARGET)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if (activeMoveInstance != null && activeMoveInstance!!.completed) {
|
||||
activeMoveInstance = null
|
||||
}
|
||||
|
||||
var runtime = mainThreadRuntime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
|
||||
runtime = runtime.withQueryValue("target", targetedEntity.asMostSpecificMoLangValue())
|
||||
val speed = runtime.resolveFloat(speedMultiplier)
|
||||
|
||||
var selectedMoveIndex = -1
|
||||
var distanceToTarget = -1.0f
|
||||
|
||||
if (!(entity.biggerBrain.getMemory(MemoryModuleType.ATTACK_COOLING_DOWN) ?: false)) {
|
||||
// Not blocked by global cooldown, check if any available moves can be performed
|
||||
var highestPriority = Int.MAX_VALUE
|
||||
distanceToTarget = entity.distanceTo(targetedEntity)
|
||||
availableMoves.forEachIndexed { index, move ->
|
||||
// check for cooldown ticks, priority, and range
|
||||
// TODO: Account for hitbox sizes when doing range checks
|
||||
if (move.id !in entity.pokemon.realTimeMoveCooldowns && move.priority < highestPriority
|
||||
&& (distanceToTarget <= move.maxRange || entity.isWithinMeleeAttackRange(targetedEntity))
|
||||
&& (distanceToTarget >= move.minRange)
|
||||
) {
|
||||
selectedMoveIndex = index
|
||||
highestPriority = move.priority
|
||||
}
|
||||
}
|
||||
if (selectedMoveIndex != -1) {
|
||||
val selectedMove = availableMoves[selectedMoveIndex]
|
||||
activeMoveInstance = selectedMove.createInstance(entity, targetedEntity)
|
||||
activeMoveInstance?.start()
|
||||
// Reset cooldowns
|
||||
entity.pokemon.realTimeMoveCooldowns[selectedMove.id] = selectedMove.cooldownTicks
|
||||
brain.setMemory(MemoryModuleType.ATTACK_COOLING_DOWN, true, globalAttackCooldownTicks.toLong())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (lookingTracker != null) {
|
||||
lookingTracker!!.tick()
|
||||
}
|
||||
|
||||
if (movementTracker != null) {
|
||||
val movementStatus = movementTracker!!.tick()
|
||||
if (movementStatus != MovementStatus.PENDING) {
|
||||
movementTracker!!.stop()
|
||||
movementTracker = null
|
||||
}
|
||||
} else {
|
||||
// Not moving right now and we need to figure out to what distance to the enemy we want to move to
|
||||
var idealDistanceToTarget = Float.MAX_VALUE
|
||||
var highestPriority = Int.MAX_VALUE
|
||||
availableMoves.forEachIndexed { index, move ->
|
||||
// We want the highest priority move that's not on cooldown
|
||||
if (move.id !in entity.pokemon.realTimeMoveCooldowns && move.priority < highestPriority) {
|
||||
highestPriority = move.priority
|
||||
idealDistanceToTarget = move.maxRange
|
||||
}
|
||||
}
|
||||
|
||||
// Get distance to target if we didn't initialize it in the earlier block
|
||||
if (distanceToTarget < 0) {
|
||||
distanceToTarget = entity.distanceTo(targetedEntity)
|
||||
}
|
||||
if (idealDistanceToTarget < distanceToTarget) {
|
||||
val walkTarget = WalkTarget(EntityTracker(targetedEntity, true), speed, idealDistanceToTarget.toInt())
|
||||
movementTracker = RegularMovementTracker.attempt(entity, walkTarget)
|
||||
lookingTracker = LookingTracker.forTargetEntity(entity, targetedEntity, trackEyeHeight = true)
|
||||
} else if (lookingTracker == null) {
|
||||
// Fixate at looking at the target so we don't wander off
|
||||
lookingTracker = LookingTracker.forTargetEntity(entity, targetedEntity, trackEyeHeight = true)
|
||||
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
-29
@@ -1,29 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.fixation
|
||||
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.Distraction
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
|
||||
/**
|
||||
* A fixation that rejects all non-force distractions and does nothing, lasting until
|
||||
* the provided [condition] is no longer true.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since January 16th, 2026
|
||||
*/
|
||||
open class ConditionalLockFixation(
|
||||
val description: String,
|
||||
val condition: () -> Boolean
|
||||
): EntityFixation, LockFixation {
|
||||
override fun canDistract(distraction: Distraction, entity: LivingEntity) = distraction.isForce
|
||||
override fun getDescription(entity: LivingEntity) = description
|
||||
override fun tick(entity: LivingEntity, brain: BiggerBrain) = condition()
|
||||
}
|
||||
-31
@@ -1,31 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.fixation
|
||||
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.Distraction
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
|
||||
/**
|
||||
* A fixation that rejects all non-force distractions and does nothing for some
|
||||
* number of ticks.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since January 16th, 2026
|
||||
*/
|
||||
open class DurationLockFixation(
|
||||
val description: String,
|
||||
var durationTicks: Int
|
||||
) : EntityFixation, LockFixation {
|
||||
override fun canDistract(distraction: Distraction, entity: LivingEntity) = distraction.isForce
|
||||
override fun getDescription(entity: LivingEntity) = description.replace("{ticks}", durationTicks.toString())
|
||||
override fun tick(entity: LivingEntity, brain: BiggerBrain): Boolean {
|
||||
return --durationTicks > 0
|
||||
}
|
||||
}
|
||||
-39
@@ -1,39 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.fixation
|
||||
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.Distraction
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
|
||||
/**
|
||||
* An entity's fixation, representing a task that takes a central role in the entity's behaviour
|
||||
* and excludes all other possible fixations.
|
||||
*
|
||||
* The role of the fixation is to do an action which is the primary focus of the entity at that time,
|
||||
* and which may be distracted from under some circumstances. An entity may be fixated on moving from
|
||||
* A to B, and then be distracted upon being hurt. Or maybe it's an attack fixation, in which case
|
||||
* damage doesn't distract it at all.
|
||||
*
|
||||
* The difference between a fixation and an objective is that objectives are capable of running in parallel,
|
||||
* and a fixation may even be in the pursuit of an objective, but the fixation is central.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since December 28th, 2025
|
||||
*/
|
||||
interface EntityFixation {
|
||||
/** Returns true if the given distraction can stop this fixation. */
|
||||
fun canDistract(distraction: Distraction, entity: LivingEntity): Boolean
|
||||
/** Notifies the fixation that it has been distracted by the given distraction so it can do clean up. */
|
||||
fun onDistract(distraction: Distraction, entity: LivingEntity) {}
|
||||
/** Continues doing whatever the fixation does. Returns true if the fixation should continue. */
|
||||
fun tick(entity: LivingEntity, brain: BiggerBrain): Boolean
|
||||
/** Gets a current description of the fixation for the debug screen. */
|
||||
fun getDescription(entity: LivingEntity): String
|
||||
}
|
||||
-18
@@ -1,18 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.fixation
|
||||
|
||||
/**
|
||||
* Represents a fixation that blocks battling, capture, etc. for Pokémon. This is
|
||||
* used to control the isBusy property on Pokémon.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since January 16th, 2026
|
||||
*/
|
||||
interface LockFixation
|
||||
-43
@@ -1,43 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.fixation
|
||||
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.LookingTracker
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.Distraction
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.DistractionType
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.PathfinderMob
|
||||
|
||||
class LookAndLockFixation(
|
||||
override val lookingTracker: LookingTracker?,
|
||||
/** Distraction types that will be respected. */
|
||||
val distractionTypes: Set<DistractionType>,
|
||||
val condition: () -> Boolean
|
||||
) : EntityFixation, LookingTrackerFixation {
|
||||
|
||||
|
||||
override fun getDescription(entity: LivingEntity): String {
|
||||
return "Looking at a nearby entity (Locked)"
|
||||
}
|
||||
|
||||
override fun canDistract(distraction: Distraction, entity: LivingEntity): Boolean {
|
||||
return distraction.types.any(distractionTypes::contains)
|
||||
}
|
||||
|
||||
override fun tick(entity: LivingEntity, brain: BiggerBrain): Boolean {
|
||||
val targetEntity = lookingTracker?.entity ?: return false
|
||||
if ((targetEntity.isRemoved) || entity !is PathfinderMob) {
|
||||
return false
|
||||
}
|
||||
val lookingTracker = lookingTracker
|
||||
lookingTracker.tick()
|
||||
return condition()
|
||||
}
|
||||
}
|
||||
-52
@@ -1,52 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.fixation
|
||||
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.LookingTracker
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.MovementStatus
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.MovementTracker
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.Distraction
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.DistractionType
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
|
||||
/**
|
||||
* A simple fixation that uses both a looking tracker and a movement tracker.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since December 29th, 2025
|
||||
*/
|
||||
open class LookAndMoveFixation(
|
||||
/** Distraction types that will be respected. */
|
||||
val distractionTypes: Set<DistractionType>,
|
||||
/** How to describe the fixation in the debug overlay. */
|
||||
val description: String,
|
||||
override val lookingTracker: LookingTracker,
|
||||
override val movementTracker: MovementTracker,
|
||||
val onMovementComplete: ((entity: LivingEntity, brain: BiggerBrain) -> Unit)? = null
|
||||
): EntityFixation, LookingTrackerFixation, MovementTrackerFixation {
|
||||
override fun getDescription(entity: LivingEntity) = description
|
||||
|
||||
override fun canDistract(distraction: Distraction, entity: LivingEntity): Boolean {
|
||||
return distraction.types.any(distractionTypes::contains)
|
||||
}
|
||||
|
||||
override fun onDistract(distraction: Distraction, entity: LivingEntity) {
|
||||
movementTracker.stop()
|
||||
}
|
||||
|
||||
override fun tick(entity: LivingEntity, brain: BiggerBrain): Boolean {
|
||||
lookingTracker.tick()
|
||||
val continueFixation = tickMovementFixation() == MovementStatus.PENDING
|
||||
if (!continueFixation && onMovementComplete != null) {
|
||||
onMovementComplete.invoke(entity, brain)
|
||||
}
|
||||
return continueFixation
|
||||
}
|
||||
}
|
||||
-27
@@ -1,27 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.fixation
|
||||
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.LookingTracker
|
||||
|
||||
/**
|
||||
* Applied to fixations which may have looking associated with them. This is useful
|
||||
* in the case that an objective needs to know if the current fixation intends on (or is)
|
||||
* controlling their looking direction.
|
||||
*
|
||||
* Example: An objective that makes Pokémon bob their heads up and down while music is playing.
|
||||
* Said objective would not warrant a full fixation, but would still like to ensure that the
|
||||
* current fixation does not intend on using a looking tracker.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since December 29th, 2025
|
||||
*/
|
||||
interface LookingTrackerFixation {
|
||||
val lookingTracker: LookingTracker?
|
||||
}
|
||||
-35
@@ -1,35 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.fixation
|
||||
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.MovementStatus
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.MovementTracker
|
||||
|
||||
/**
|
||||
* Applied to fixations which may have movement associated with them. This is useful
|
||||
* in the case that an objective needs to know if the current fixation intends on (or is)
|
||||
* controlling their movement.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since December 29th, 2025
|
||||
*/
|
||||
interface MovementTrackerFixation {
|
||||
/** The movement tracker at the moment. */
|
||||
val movementTracker: MovementTracker?
|
||||
|
||||
/** Ticks the movement tracker in a way that skips finalized movement trackers. */
|
||||
fun tickMovementFixation(): MovementStatus? {
|
||||
val movementTracker = this.movementTracker ?: return null
|
||||
return if (movementTracker.lastStatus != MovementStatus.PENDING) {
|
||||
movementTracker.lastStatus
|
||||
} else {
|
||||
movementTracker.tick()
|
||||
}
|
||||
}
|
||||
}
|
||||
-168
@@ -1,168 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.objective
|
||||
|
||||
import com.bedrockk.molang.Expression
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.api.Priority
|
||||
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
|
||||
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
|
||||
import com.cobblemon.mod.common.api.ai.asSimplifiedExpression
|
||||
import com.cobblemon.mod.common.api.ai.asVariables
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.Distraction
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.DistractionType
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.AttackFixation
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.EntityFixation
|
||||
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
|
||||
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.util.asExpression
|
||||
import com.cobblemon.mod.common.util.mainThreadRuntime
|
||||
import com.cobblemon.mod.common.util.resolveInt
|
||||
import com.cobblemon.mod.common.util.resolveString
|
||||
import com.cobblemon.mod.common.util.withQueryValue
|
||||
import com.mojang.datafixers.util.Either
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.ai.memory.MemoryModuleType
|
||||
|
||||
class AggressiveBehaviour(
|
||||
speedMultiplier: Expression = "0.5".asExpression(), // How do we transfer speed to the attack behaviour? Do we want to?
|
||||
engageRange: Expression = "15.0".asExpression(),
|
||||
disengageRange: Expression = "20.0".asExpression(),
|
||||
condition: Expression = "true".asExpression(), // Self/environment condition check
|
||||
targetCondition: Expression = "true".asExpression(), // target condition (will be checked on entity interaction)
|
||||
description: Expression = "'Aggressive Behaviour'".asExpression(),
|
||||
val maxAttackDuration: Expression = "300".asExpression(),
|
||||
) : EntityResponseObjective(
|
||||
speedMultiplier = speedMultiplier, // How do we transfer speed to the attack behaviour? Do we want to?
|
||||
engageRange = engageRange,
|
||||
disengageRange = disengageRange,
|
||||
condition = condition, // Self/environment condition check
|
||||
targetCondition = targetCondition, // target condition (will be checked on entity interaction)
|
||||
description = description,
|
||||
) {
|
||||
|
||||
private var remainingCooldownTicks = 0
|
||||
private var attackFixation : EntityFixation? = null
|
||||
override val observedMemories = listOf(MemoryModuleType.HURT_BY_ENTITY, CobblemonMemories.ATTACK_TARGET_DATA)
|
||||
|
||||
|
||||
override fun perform(
|
||||
entity: LivingEntity,
|
||||
brain: BiggerBrain,
|
||||
activeObjectiveTags: MutableSet<String>
|
||||
): ObjectiveStatus {
|
||||
if (entity !is PokemonEntity || !entity.isAlive) return ObjectiveStatus.INVALID
|
||||
if (brain.getMemory(MemoryModuleType.AVOID_TARGET) != null) {
|
||||
return ObjectiveStatus.BLOCKED
|
||||
}
|
||||
val attackTarget = brain.getMemory(MemoryModuleType.ATTACK_TARGET)
|
||||
if (trackedEntity != null) {
|
||||
// has active target
|
||||
if (attackTarget != null && trackedEntity?.uuid == attackTarget.uuid) {
|
||||
// Check if we continue
|
||||
if (shouldContinue(entity, brain)) {
|
||||
return ObjectiveStatus.ACTIVE
|
||||
} else {
|
||||
brain.distract(Distraction.FORCE)
|
||||
attackFixation = null
|
||||
brain.eraseMemory(MemoryModuleType.ATTACK_TARGET)
|
||||
brain.eraseMemory(CobblemonMemories.ATTACK_TARGET_DATA)
|
||||
return ObjectiveStatus.INACTIVE
|
||||
}
|
||||
} else {
|
||||
// Something has overridden us and independently set the attack target
|
||||
trackedEntity = null
|
||||
attackFixation = null
|
||||
return ObjectiveStatus.BLOCKED
|
||||
}
|
||||
} else {
|
||||
// No active target
|
||||
if (remainingCooldownTicks > 0) {
|
||||
remainingCooldownTicks--
|
||||
return ObjectiveStatus.INACTIVE
|
||||
} else {
|
||||
remainingCooldownTicks = 20
|
||||
}
|
||||
val targetEntity = scanForValidTargets(entity, brain)
|
||||
if (targetEntity != null && brain.distract(Distraction.FORCE)) {
|
||||
val runtime = mainThreadRuntime
|
||||
val attackDuration = runtime.resolveInt(maxAttackDuration)
|
||||
trackedEntity = targetEntity
|
||||
brain.setMemory(MemoryModuleType.ATTACK_TARGET, targetEntity, attackDuration.toLong())
|
||||
attackFixation = brain.fixate(
|
||||
AttackFixation(
|
||||
targetEntity = trackedEntity!!,
|
||||
speedMultiplier = speedMultiplier,
|
||||
distractionTypes = setOf(DistractionType.FORCE),
|
||||
availableMoves = getRealTimeMoves(entity)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
return if (trackedEntity != null) ObjectiveStatus.ACTIVE else ObjectiveStatus.INACTIVE
|
||||
}
|
||||
|
||||
override fun onRegister(entity: LivingEntity, brain: BiggerBrain) {
|
||||
brain.subscribe(
|
||||
priority = Priority.LOW,
|
||||
memory = CobblemonMemories.ATTACK_TARGET_DATA
|
||||
) { attackData, labels ->
|
||||
val targetEntity = brain.getMemory(MemoryModuleType.ATTACK_TARGET) ?: return@subscribe true
|
||||
if (trackedEntity?.uuid == targetEntity.uuid) {
|
||||
// We smacked them, refresh our attack timer
|
||||
val runtime = mainThreadRuntime.withQueryValue("entity", (targetEntity).asMostSpecificMoLangValue())
|
||||
val attackDuration = runtime.resolveInt(maxAttackDuration)
|
||||
brain.setMemory(MemoryModuleType.ATTACK_TARGET, targetEntity, attackDuration.toLong())
|
||||
}
|
||||
return@subscribe true
|
||||
}
|
||||
}
|
||||
|
||||
class Config : ObjectiveConfig {
|
||||
val speedMultiplier: ExpressionOrEntityVariable = Either.left("0.5".asExpression())
|
||||
val engageRange: ExpressionOrEntityVariable = Either.left("15.0".asExpression())
|
||||
val disengageRange: ExpressionOrEntityVariable = Either.left("20.0".asExpression())
|
||||
val condition: ExpressionOrEntityVariable = Either.left("true".asExpression())
|
||||
val targetCondition: ExpressionOrEntityVariable = Either.left("false".asExpression())
|
||||
val maxAttackDuration: ExpressionOrEntityVariable = Either.left("300".asExpression())
|
||||
val description: ExpressionOrEntityVariable = Either.left("'Aggressive Behaviour'".asExpression())
|
||||
override fun getVariables(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
): List<MoLangConfigVariable> {
|
||||
return listOf(
|
||||
condition,
|
||||
targetCondition,
|
||||
speedMultiplier,
|
||||
engageRange,
|
||||
disengageRange,
|
||||
maxAttackDuration,
|
||||
description
|
||||
).asVariables()
|
||||
}
|
||||
|
||||
override fun create(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
): Objective {
|
||||
return AggressiveBehaviour(
|
||||
condition = condition.asSimplifiedExpression(entity),
|
||||
targetCondition = targetCondition.asSimplifiedExpression(entity),
|
||||
speedMultiplier = speedMultiplier.asSimplifiedExpression(entity),
|
||||
engageRange = engageRange.asSimplifiedExpression(entity),
|
||||
disengageRange = disengageRange.asSimplifiedExpression(entity),
|
||||
maxAttackDuration = maxAttackDuration.asSimplifiedExpression(entity),
|
||||
description = description.asSimplifiedExpression(entity)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
-170
@@ -1,170 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.objective
|
||||
|
||||
import com.bedrockk.molang.Expression
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.api.Priority
|
||||
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
|
||||
import com.cobblemon.mod.common.api.ai.CobblemonAttackTargetData
|
||||
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
|
||||
import com.cobblemon.mod.common.api.ai.asSimplifiedExpression
|
||||
import com.cobblemon.mod.common.api.ai.asVariables
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.LookingTracker
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.RegularMovementTracker
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.Distraction
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.DistractionType
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.EntityFixation
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.LookAndMoveFixation
|
||||
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
|
||||
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.util.asExpression
|
||||
import com.cobblemon.mod.common.util.mainThreadRuntime
|
||||
import com.cobblemon.mod.common.util.resolveFloat
|
||||
import com.cobblemon.mod.common.util.resolveInt
|
||||
import com.cobblemon.mod.common.util.withQueryValue
|
||||
import com.mojang.datafixers.util.Either
|
||||
import net.minecraft.world.InteractionHand
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.ai.behavior.EntityTracker
|
||||
import net.minecraft.world.entity.ai.memory.MemoryModuleType
|
||||
import net.minecraft.world.entity.ai.memory.WalkTarget
|
||||
import net.minecraft.world.entity.player.Player
|
||||
|
||||
class AttackTargetMelee(
|
||||
private val speedMultiplier: Expression = "1.0".asExpression(),
|
||||
private val attackRange: Expression = "2.0".asExpression(),
|
||||
private val attackCooldownTicks: Int = 20
|
||||
) : Objective {
|
||||
private var movementTask: EntityFixation? = null
|
||||
private var remainingCooldownTicks = 0
|
||||
|
||||
override val observedMemories = listOf(MemoryModuleType.ATTACK_TARGET)
|
||||
|
||||
override fun getDescription(entity: LivingEntity) = "Attack target melee"
|
||||
|
||||
override fun perform(
|
||||
entity: LivingEntity,
|
||||
brain: BiggerBrain,
|
||||
activeObjectiveTags: MutableSet<String>
|
||||
): ObjectiveStatus {
|
||||
if (entity !is PokemonEntity || !entity.isAlive) return ObjectiveStatus.INVALID
|
||||
|
||||
val target = brain.getMemory(MemoryModuleType.ATTACK_TARGET)
|
||||
?: return ObjectiveStatus.INACTIVE
|
||||
|
||||
if (target.isRemoved || target.isDeadOrDying) {
|
||||
brain.eraseMemory(MemoryModuleType.ATTACK_TARGET)
|
||||
brain.eraseMemory(MemoryModuleType.ANGRY_AT)
|
||||
return ObjectiveStatus.INACTIVE
|
||||
}
|
||||
|
||||
if (target is Player && (target.isCreative || target.isSpectator)) {
|
||||
brain.eraseMemory(MemoryModuleType.ATTACK_TARGET)
|
||||
brain.eraseMemory(MemoryModuleType.ANGRY_AT)
|
||||
return ObjectiveStatus.INACTIVE
|
||||
}
|
||||
|
||||
if (remainingCooldownTicks > 0) {
|
||||
remainingCooldownTicks--
|
||||
}
|
||||
|
||||
val runtime = mainThreadRuntime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
|
||||
val range = runtime.resolveFloat(attackRange)
|
||||
val speed = runtime.resolveFloat(speedMultiplier)
|
||||
|
||||
// This is melee thing, but not everything will do melee in the future
|
||||
if (entity.distanceToSqr(target) <= range * range && entity.hasLineOfSight(target)) {
|
||||
if (remainingCooldownTicks <= 0) {
|
||||
if (entity.doHurtTarget(target)) {
|
||||
entity.swing(InteractionHand.MAIN_HAND)
|
||||
var attackTargetData = brain.getMemory(CobblemonMemories.ATTACK_TARGET_DATA)
|
||||
if (attackTargetData != null) {
|
||||
attackTargetData.hitsLanded += 1
|
||||
|
||||
} else {
|
||||
attackTargetData = CobblemonAttackTargetData()
|
||||
attackTargetData.hitsLanded = 1
|
||||
}
|
||||
brain.setMemory(CobblemonMemories.ATTACK_TARGET_DATA, attackTargetData)
|
||||
}
|
||||
remainingCooldownTicks = attackCooldownTicks
|
||||
}
|
||||
return ObjectiveStatus.ACTIVE
|
||||
}
|
||||
|
||||
// If we have a current target, check if it's still valid and within roam range (if tethered)
|
||||
if (target.isAlive) {
|
||||
val tethering = (entity).tethering
|
||||
if (tethering != null && !tethering.canRoamTo(target.blockPosition())) {
|
||||
// Target moved out of range let's kill the memory >:)
|
||||
brain.distract(Distraction.FORCE)
|
||||
entity.brain.eraseMemory(MemoryModuleType.ATTACK_TARGET)
|
||||
}
|
||||
}
|
||||
|
||||
if (brain.isFixation(movementTask)) {
|
||||
return ObjectiveStatus.FIXATED
|
||||
}
|
||||
|
||||
if (!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(LookAndMoveFixation(
|
||||
distractionTypes = setOf(DistractionType.FORCE),
|
||||
lookingTracker = LookingTracker.forTargetEntity(entity, target, trackEyeHeight = true),
|
||||
movementTracker = movementTracker,
|
||||
description = "Moving to attack target"))
|
||||
return ObjectiveStatus.FIXATED
|
||||
}
|
||||
|
||||
override fun onRegister(entity: LivingEntity, brain: BiggerBrain) {
|
||||
brain.subscribe(
|
||||
priority = Priority.LOW,
|
||||
memory = MemoryModuleType.ATTACK_TARGET
|
||||
) { value, labels ->
|
||||
if (value == null) {
|
||||
if (brain.isFixation(movementTask)) {
|
||||
brain.distract(Distraction.FORCE)
|
||||
movementTask = null
|
||||
}
|
||||
}
|
||||
return@subscribe true
|
||||
}
|
||||
}
|
||||
|
||||
class Config : ObjectiveConfig {
|
||||
val speedMultiplier: ExpressionOrEntityVariable = Either.left("1.0".asExpression())
|
||||
val attackRange: ExpressionOrEntityVariable = Either.left("2.0".asExpression())
|
||||
val attackCooldownTicks: ExpressionOrEntityVariable = Either.left("20".asExpression())
|
||||
|
||||
override fun getVariables(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
): List<MoLangConfigVariable> {
|
||||
return listOf(speedMultiplier, attackRange, attackCooldownTicks).asVariables()
|
||||
}
|
||||
|
||||
override fun create(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
): Objective {
|
||||
return AttackTargetMelee(
|
||||
speedMultiplier = speedMultiplier.asSimplifiedExpression(entity),
|
||||
attackRange = attackRange.asSimplifiedExpression(entity),
|
||||
attackCooldownTicks = behaviourConfigurationContext.runtime.resolveInt(
|
||||
attackCooldownTicks.asSimplifiedExpression(entity)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
-173
@@ -1,173 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.objective
|
||||
|
||||
import com.bedrockk.molang.Expression
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.api.Priority
|
||||
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
|
||||
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
|
||||
import com.cobblemon.mod.common.api.ai.asSimplifiedExpression
|
||||
import com.cobblemon.mod.common.api.ai.asVariables
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.LookingTracker
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.RegularMovementTracker
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.Distraction
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.DistractionType
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.ConditionalLockFixation
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.EntityFixation
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.LookAndMoveFixation
|
||||
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
|
||||
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
|
||||
import com.cobblemon.mod.common.api.tags.CobblemonBlockTags
|
||||
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.sendParticlesServer
|
||||
import com.cobblemon.mod.common.util.toVec3d
|
||||
import com.cobblemon.mod.common.util.withQueryValue
|
||||
import com.mojang.datafixers.util.Either
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.core.particles.ParticleTypes
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.ai.memory.MemoryModuleType
|
||||
import net.minecraft.world.entity.ai.memory.WalkTarget
|
||||
import net.minecraft.world.entity.ai.util.LandRandomPos
|
||||
import net.minecraft.world.phys.Vec3
|
||||
|
||||
class AvoidScarekrow(
|
||||
private val speedMultiplier: Expression = "0.5".asExpression(),
|
||||
) : Objective {
|
||||
private var movementTask: EntityFixation? = null
|
||||
private var remainingCooldownTicks = 0
|
||||
|
||||
override val observedMemories = listOf(CobblemonMemories.NEARBY_SCAREKROW)
|
||||
|
||||
override fun getDescription(entity: LivingEntity) = "Avoiding target block"
|
||||
|
||||
override fun perform(
|
||||
entity: LivingEntity,
|
||||
brain: BiggerBrain,
|
||||
activeObjectiveTags: MutableSet<String>
|
||||
): ObjectiveStatus {
|
||||
if (entity !is PokemonEntity || !entity.isAlive) return ObjectiveStatus.INVALID
|
||||
|
||||
val target = brain.getMemory(CobblemonMemories.NEARBY_SCAREKROW)
|
||||
?: return ObjectiveStatus.INACTIVE
|
||||
|
||||
// if the scarekrow is gone we dont need to be scared anymore
|
||||
if (!entity.level().getBlockState(target).`is`(CobblemonBlockTags.SCAREKROWS)) {
|
||||
brain.eraseMemory(CobblemonMemories.NEARBY_SCAREKROW)
|
||||
return ObjectiveStatus.INACTIVE
|
||||
}
|
||||
|
||||
// finish existing movement task
|
||||
if (brain.isFixation(movementTask)) {
|
||||
return ObjectiveStatus.FIXATED
|
||||
}
|
||||
|
||||
// aggro against the player should take priority
|
||||
if (brain.getMemory(MemoryModuleType.ATTACK_TARGET) != null) {
|
||||
return ObjectiveStatus.BLOCKED
|
||||
}
|
||||
|
||||
if (remainingCooldownTicks > 0) {
|
||||
remainingCooldownTicks--
|
||||
}
|
||||
val runtime = mainThreadRuntime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
|
||||
val speed = runtime.resolveFloat(speedMultiplier)
|
||||
|
||||
if (!brain.distract(Distraction.FORCE)) return ObjectiveStatus.BLOCKED
|
||||
var targetPos : BlockPos? = null
|
||||
|
||||
// fixate on running from the scarekrow until it is out of range
|
||||
brain.fixate(ConditionalLockFixation("Avoiding scarekrow") {
|
||||
brain.getMemory(CobblemonMemories.NEARBY_SCAREKROW) != null
|
||||
})
|
||||
|
||||
// find position
|
||||
val direction = entity.position().subtract(target.toVec3d())
|
||||
for (j in 0..9) {
|
||||
val targetVec3 = LandRandomPos.getPosAway(entity, (Cobblemon.config.scarekrowRadius * 1.5).toInt(), 7, direction)
|
||||
if (targetVec3 != null) {
|
||||
targetPos = BlockPos.containing(targetVec3)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Start a look-and-move fixation only when navigation accepts the selected position.
|
||||
if (targetPos != null) {
|
||||
val movementTracker = RegularMovementTracker.attempt(
|
||||
entity = entity,
|
||||
walkTarget = WalkTarget(
|
||||
targetPos,
|
||||
speed,
|
||||
0
|
||||
)
|
||||
)
|
||||
entity.level().sendParticlesServer(ParticleTypes.HAPPY_VILLAGER, targetPos.above().toVec3d(), 5, Vec3(0.0, 0.0, 0.0), 5.0)
|
||||
if (movementTracker != null) {
|
||||
movementTask = brain.fixate(
|
||||
LookAndMoveFixation(
|
||||
distractionTypes = setOf(
|
||||
DistractionType.FORCE
|
||||
),
|
||||
lookingTracker = LookingTracker.fromNavigationPathOrWalkTarget(
|
||||
entity,
|
||||
movementTracker.walkTarget
|
||||
),
|
||||
movementTracker = movementTracker,
|
||||
description = "Moving away from target block",
|
||||
)
|
||||
)
|
||||
return ObjectiveStatus.FIXATED
|
||||
}
|
||||
}
|
||||
|
||||
return ObjectiveStatus.FIXATED
|
||||
}
|
||||
|
||||
override fun onRegister(entity: LivingEntity, brain: BiggerBrain) {
|
||||
brain.subscribe(
|
||||
priority = Priority.LOW,
|
||||
memory = CobblemonMemories.NEARBY_SCAREKROW,
|
||||
) { value, labels ->
|
||||
if (value == null) {
|
||||
if (brain.isFixation(movementTask)) {
|
||||
brain.distract(Distraction.FORCE)
|
||||
movementTask = null
|
||||
}
|
||||
}
|
||||
return@subscribe true
|
||||
}
|
||||
}
|
||||
|
||||
class Config : ObjectiveConfig {
|
||||
val speedMultiplier: ExpressionOrEntityVariable = Either.left("0.5".asExpression())
|
||||
|
||||
override fun getVariables(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
): List<MoLangConfigVariable> {
|
||||
return listOf(speedMultiplier).asVariables()
|
||||
}
|
||||
|
||||
override fun create(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
): Objective {
|
||||
return AvoidScarekrow(
|
||||
speedMultiplier = speedMultiplier.asSimplifiedExpression(entity),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
-175
@@ -1,175 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.objective
|
||||
|
||||
import com.bedrockk.molang.Expression
|
||||
import com.cobblemon.mod.common.api.Priority
|
||||
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
|
||||
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
|
||||
import com.cobblemon.mod.common.api.ai.asSimplifiedExpression
|
||||
import com.cobblemon.mod.common.api.ai.asVariables
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.LookingTracker
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.RegularMovementTracker
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.Distraction
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.DistractionType
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.EntityFixation
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.LookAndMoveFixation
|
||||
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
|
||||
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.util.asExpression
|
||||
import com.cobblemon.mod.common.util.mainThreadRuntime
|
||||
import com.cobblemon.mod.common.util.resolveFloat
|
||||
import com.cobblemon.mod.common.util.withQueryValue
|
||||
import com.mojang.datafixers.util.Either
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.ai.memory.MemoryModuleType
|
||||
import net.minecraft.world.entity.ai.memory.WalkTarget
|
||||
import net.minecraft.world.entity.ai.util.LandRandomPos
|
||||
import net.minecraft.world.entity.player.Player
|
||||
|
||||
class AvoidTarget(
|
||||
private val speedMultiplier: Expression = "0.5".asExpression(),
|
||||
) : Objective {
|
||||
private var movementTask: EntityFixation? = null
|
||||
private var remainingCooldownTicks = 0
|
||||
|
||||
override val observedMemories = listOf(MemoryModuleType.AVOID_TARGET)
|
||||
|
||||
override fun getDescription(entity: LivingEntity) = "Avoiding target"
|
||||
|
||||
override fun perform(
|
||||
entity: LivingEntity,
|
||||
brain: BiggerBrain,
|
||||
activeObjectiveTags: MutableSet<String>
|
||||
): ObjectiveStatus {
|
||||
if (entity !is PokemonEntity || !entity.isAlive) return ObjectiveStatus.INVALID
|
||||
|
||||
val target = brain.getMemory(MemoryModuleType.AVOID_TARGET)
|
||||
?: return ObjectiveStatus.INACTIVE
|
||||
|
||||
if (target.isRemoved || target.isDeadOrDying) {
|
||||
brain.eraseMemory(MemoryModuleType.AVOID_TARGET)
|
||||
return ObjectiveStatus.INACTIVE
|
||||
}
|
||||
|
||||
if (target is Player && (target.isCreative || target.isSpectator)) {
|
||||
brain.eraseMemory(MemoryModuleType.AVOID_TARGET)
|
||||
return ObjectiveStatus.INACTIVE
|
||||
}
|
||||
|
||||
if (brain.isFixation(movementTask)) {
|
||||
return ObjectiveStatus.FIXATED
|
||||
}
|
||||
|
||||
if (remainingCooldownTicks > 0) {
|
||||
remainingCooldownTicks--
|
||||
}
|
||||
|
||||
val runtime = mainThreadRuntime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
|
||||
val speed = runtime.resolveFloat(speedMultiplier)
|
||||
|
||||
|
||||
// If we have a current target, check if it's still valid and within roam range (if tethered)
|
||||
if (target.isAlive) {
|
||||
val tethering = (entity).tethering
|
||||
if (tethering != null && !tethering.canRoamTo(target.blockPosition())) {
|
||||
// Target moved out of range let's kill the memory >:)
|
||||
brain.distract(Distraction.FORCE)
|
||||
entity.brain.eraseMemory(MemoryModuleType.AVOID_TARGET)
|
||||
}
|
||||
}
|
||||
|
||||
if (brain.isFixation(movementTask)) {
|
||||
return ObjectiveStatus.FIXATED
|
||||
}
|
||||
|
||||
if (!brain.distract(Distraction.FORCE)) return ObjectiveStatus.BLOCKED
|
||||
var targetPos : BlockPos? = null
|
||||
|
||||
// TODO: utilize wander target rules to find a valid target position
|
||||
val direction = entity.position().subtract(target.position())
|
||||
for (j in 0..9) {
|
||||
val targetVec3 = LandRandomPos.getPosAway(entity, 16, 7, direction)
|
||||
if (targetVec3 != null) {
|
||||
targetPos = BlockPos.containing(targetVec3)
|
||||
break
|
||||
}
|
||||
}
|
||||
// val walkTarget = WanderAround.generateWalkTarget(entity)
|
||||
if (targetPos != null) {
|
||||
val movementTracker = RegularMovementTracker.attempt(
|
||||
entity = entity,
|
||||
walkTarget = WalkTarget(
|
||||
targetPos,
|
||||
speed,
|
||||
0
|
||||
)
|
||||
)
|
||||
if (movementTracker != null) {
|
||||
movementTask = brain.fixate(
|
||||
LookAndMoveFixation(
|
||||
distractionTypes = setOf(
|
||||
DistractionType.FORCE
|
||||
),
|
||||
lookingTracker = LookingTracker.fromNavigationPathOrWalkTarget(
|
||||
entity,
|
||||
movementTracker.walkTarget
|
||||
),
|
||||
movementTracker = movementTracker,
|
||||
description = "Moving away from target entity",
|
||||
)
|
||||
)
|
||||
return ObjectiveStatus.FIXATED
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
return ObjectiveStatus.ACTIVE
|
||||
}
|
||||
|
||||
override fun onRegister(entity: LivingEntity, brain: BiggerBrain) {
|
||||
brain.subscribe(
|
||||
priority = Priority.LOW,
|
||||
memory = MemoryModuleType.AVOID_TARGET
|
||||
) { value, labels ->
|
||||
if (value == null) {
|
||||
if (brain.isFixation(movementTask)) {
|
||||
brain.distract(Distraction.FORCE)
|
||||
movementTask = null
|
||||
}
|
||||
}
|
||||
return@subscribe true
|
||||
}
|
||||
}
|
||||
|
||||
class Config : ObjectiveConfig {
|
||||
val speedMultiplier: ExpressionOrEntityVariable = Either.left("0.5".asExpression())
|
||||
|
||||
override fun getVariables(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
): List<MoLangConfigVariable> {
|
||||
return listOf(speedMultiplier).asVariables()
|
||||
}
|
||||
|
||||
override fun create(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
): Objective {
|
||||
return AvoidTarget(
|
||||
speedMultiplier = speedMultiplier.asSimplifiedExpression(entity),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
-306
@@ -1,306 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.objective
|
||||
|
||||
import com.bedrockk.molang.Expression
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.CobblemonItems
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
|
||||
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
|
||||
import com.cobblemon.mod.common.api.ai.asSimplifiedExpression
|
||||
import com.cobblemon.mod.common.api.ai.asVariables
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.LookingTracker
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.RegularMovementTracker
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.Distraction
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.DistractionType
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.comfortDistraction
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.DurationLockFixation
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.EntityFixation
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.LookAndMoveFixation
|
||||
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
|
||||
import com.cobblemon.mod.common.api.ai.resolveInt as resolveVariableInt
|
||||
import com.cobblemon.mod.common.api.breeding.CobblemonBreeding
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
|
||||
import com.cobblemon.mod.common.api.nest.WildNestType
|
||||
import com.cobblemon.mod.common.api.nest.WildNestTypes
|
||||
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
|
||||
import com.cobblemon.mod.common.block.WildNestBlock
|
||||
import com.cobblemon.mod.common.block.entity.NestBlockEntity
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.util.asExpression
|
||||
import com.cobblemon.mod.common.util.mainThreadRuntime
|
||||
import com.cobblemon.mod.common.util.resolveBoolean
|
||||
import com.cobblemon.mod.common.util.resolveFloat
|
||||
import com.cobblemon.mod.common.util.withQueryValue
|
||||
import com.mojang.datafixers.util.Either
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.core.particles.ParticleTypes
|
||||
import net.minecraft.server.level.ServerLevel
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.ai.memory.MemoryModuleType
|
||||
import net.minecraft.world.entity.ai.memory.WalkTarget
|
||||
import net.minecraft.world.level.block.state.properties.BlockStateProperties
|
||||
import net.minecraft.world.phys.Vec3
|
||||
|
||||
class Breed(
|
||||
private val speedMultiplier: Expression = "0.4".asExpression(),
|
||||
private val condition: Expression = "true".asExpression(),
|
||||
private val layDurationTicks: Int = 40
|
||||
) : Objective {
|
||||
private var fixation: EntityFixation? = null
|
||||
private var mateFixation: EntityFixation? = null
|
||||
|
||||
override fun getDescription(entity: LivingEntity) = "Breed"
|
||||
|
||||
override fun perform(entity: LivingEntity, brain: BiggerBrain, activeObjectiveTags: MutableSet<String>): ObjectiveStatus {
|
||||
if (entity !is PokemonEntity || !entity.isAlive)
|
||||
return ObjectiveStatus.INVALID
|
||||
val world = entity.level() as? ServerLevel ?: return ObjectiveStatus.INVALID
|
||||
val mate = brain.getMemory(CobblemonMemories.POTENTIAL_MATE) as? PokemonEntity ?: return ObjectiveStatus.INACTIVE
|
||||
if (!mate.isAlive || mate.level() != world)
|
||||
return ObjectiveStatus.INACTIVE
|
||||
// ignore wild pokemon who are already parents of a nest
|
||||
if (entity.pokemon.isWild() && brain.hasMemoryValue(CobblemonMemories.PATERNAL_NEST_LOCATION))
|
||||
return ObjectiveStatus.INACTIVE
|
||||
// make sure either parent doesn't have an active breeding cooldown (do we want to only do this for the females?)
|
||||
if (entity.pokemon.breedingCooldown != 0 || mate.pokemon.breedingCooldown != 0)
|
||||
return ObjectiveStatus.INACTIVE
|
||||
// is breeding disabled from any preset conditions
|
||||
if (!mainThreadRuntime.withQueryValue("entity", entity.asMostSpecificMoLangValue()).resolveBoolean(condition))
|
||||
return ObjectiveStatus.INACTIVE
|
||||
// try to get one parent to lead the other one and have the other one not do this stuff
|
||||
if (entity.uuid > mate.uuid)
|
||||
return ObjectiveStatus.INACTIVE
|
||||
|
||||
val nestPos = brain.getMemory(CobblemonMemories.NEST_LOCATION)
|
||||
if (nestPos != null)
|
||||
return breedAtNest(entity, brain, mate, world, nestPos)
|
||||
val idealNestPos = brain.getMemory(CobblemonMemories.IDEAL_NEST_LOCATION) ?: return ObjectiveStatus.INACTIVE
|
||||
return buildNest(entity, brain, mate, world, idealNestPos)
|
||||
}
|
||||
|
||||
// get both parents to go to the nest and breed once within range
|
||||
private fun breedAtNest(entity: PokemonEntity, brain: BiggerBrain, mate: PokemonEntity, world: ServerLevel, nestPos: BlockPos): ObjectiveStatus {
|
||||
val nest = world.getBlockEntity(nestPos) as? NestBlockEntity ?: return ObjectiveStatus.INACTIVE
|
||||
if (!canUseNest(entity, mate, world, nest))
|
||||
return ObjectiveStatus.INACTIVE
|
||||
if (!nest.getItem().isEmpty)
|
||||
return ObjectiveStatus.INACTIVE
|
||||
// make sure these lovers claim the nest because others may try to at the same time
|
||||
nest.nestClaimedBy = Pair(entity.uuid, mate.uuid)
|
||||
|
||||
val nestCenter = Vec3.atCenterOf(nestPos)
|
||||
val speed = mainThreadRuntime.withQueryValue("entity", entity.asMostSpecificMoLangValue()).resolveFloat(speedMultiplier)
|
||||
// path to the nest site with mate until close enough (big bois had issues so we are also going to try to make it easier for them)
|
||||
if (entity.position().distanceTo(nestCenter) > closeEnough(entity) || mate.position().distanceTo(nestCenter) > closeEnough(mate)) {
|
||||
bringMate(mate, nestCenter, speed)
|
||||
return walkToNest(entity, brain, nestCenter, speed, "in Breed fixation: going to nest")
|
||||
}
|
||||
return layEgg(entity, brain, mate, world, nest, nestPos)
|
||||
}
|
||||
|
||||
// have both mates go to a ideal spot to make a nest
|
||||
private fun buildNest(entity: PokemonEntity, brain: BiggerBrain, mate: PokemonEntity, world: ServerLevel, nestPos: BlockPos): ObjectiveStatus {
|
||||
// probably should stop trying to build nest if there is an attack target
|
||||
if (brain.hasMemoryValue(MemoryModuleType.ATTACK_TARGET))
|
||||
return ObjectiveStatus.INACTIVE
|
||||
// find out what kinds of nests this couple can make togethr
|
||||
val types = WildNestTypes.combinedTypes(entity, mate)
|
||||
if (types.isEmpty())
|
||||
return ObjectiveStatus.INACTIVE
|
||||
|
||||
val nestCenter = Vec3.atCenterOf(nestPos)
|
||||
val speed = mainThreadRuntime.withQueryValue("entity", entity.asMostSpecificMoLangValue()).resolveFloat(speedMultiplier)
|
||||
// keep going until both mates are not too far from the nest
|
||||
if (entity.position().distanceTo(nestCenter) > closeEnough(entity) || mate.position().distanceTo(nestCenter) > closeEnough(mate)) {
|
||||
bringMate(mate, nestCenter, speed)
|
||||
return walkToNest(entity, brain, nestCenter, speed, "in Breed fixation: going to nest")
|
||||
}
|
||||
|
||||
// get our nest they are about to build and place it
|
||||
val nestToBuild = WildNestTypes.resolvePlacement(types, world.getBlockState(nestPos.below()), world.getFluidState(nestPos)) ?: return ObjectiveStatus.INACTIVE
|
||||
val nestBlock = WildNestTypes.blockFor(nestToBuild.type) as? WildNestBlock ?: return ObjectiveStatus.INACTIVE
|
||||
var nestState = nestBlock.defaultBlockState().setValue(BlockStateProperties.WATERLOGGED, nestToBuild.waterlogged)
|
||||
nestBlock.variantProperty?.let { nestState = nestState.setValue(it, nestToBuild.variant) }
|
||||
world.setBlock(nestPos, nestState, 3)
|
||||
|
||||
// set the parents of the nest (hope this isn't too early to do this, might need to be after egg laying instead..)
|
||||
(world.getBlockEntity(nestPos) as? NestBlockEntity)?.nestClaimedBy = Pair(entity.uuid, mate.uuid)
|
||||
|
||||
for (parentBrain in listOf(brain, mate.biggerBrain)) {
|
||||
parentBrain.eraseMemory(CobblemonMemories.IDEAL_NEST_LOCATION)
|
||||
// give our newly made nest to the parents now taht it exists
|
||||
parentBrain.setMemory(CobblemonMemories.NEST_LOCATION, nestPos)
|
||||
}
|
||||
return ObjectiveStatus.ACTIVE
|
||||
}
|
||||
|
||||
// make da baby and tell the parents about their new responsibility in life
|
||||
private fun layEgg(entity: PokemonEntity, brain: BiggerBrain, mate: PokemonEntity, world: ServerLevel, nest: NestBlockEntity, nestPos: BlockPos): ObjectiveStatus {
|
||||
if (!brain.distract(Distraction.FORCE))
|
||||
return ObjectiveStatus.BLOCKED
|
||||
val egg = CobblemonBreeding.createEgg(entity.pokemon, mate.pokemon) ?: return ObjectiveStatus.INACTIVE
|
||||
nest.setItem(egg)
|
||||
|
||||
// wild nests are probably the only ones where we need to record the parents onto the nest
|
||||
if (nest.ownerUUID == null) {
|
||||
nest.parents = Pair(entity.uuid, mate.uuid)
|
||||
nest.nestClaimedBy = Pair(entity.uuid, mate.uuid)
|
||||
}
|
||||
|
||||
for (parent in listOf(entity, mate)) {
|
||||
// might want to do the destiny knot damaging here for each parent that has one
|
||||
if (Cobblemon.config.canDestinyKnotBreak)
|
||||
if (parent.pokemon.heldItem.item == CobblemonItems.DESTINY_KNOT.asItem()) {
|
||||
parent.pokemon.heldItemNoCopy().damageValue += 1
|
||||
}
|
||||
|
||||
parent.pokemon.breedingCooldown = CobblemonBreeding.resetBreedingCooldown(parent.pokemon)
|
||||
parent.biggerBrain.setMemory(CobblemonMemories.PATERNAL_NEST_LOCATION, nestPos)
|
||||
parent.biggerBrain.setMemory(CobblemonMemories.EGG_IN_NEST, true)
|
||||
parent.biggerBrain.eraseMemory(CobblemonMemories.IDEAL_NEST_LOCATION)
|
||||
|
||||
// todo do we want to use our own particles? look at this later
|
||||
world.sendParticles(
|
||||
ParticleTypes.HEART,
|
||||
parent.getRandomX(1.0),
|
||||
parent.getRandomY() + 0.5,
|
||||
parent.getRandomZ(1.0),
|
||||
4 + parent.random.nextInt(2),
|
||||
0.2 * parent.random.nextGaussian(),
|
||||
0.1 * parent.random.nextGaussian(),
|
||||
0.2 * parent.random.nextGaussian(),
|
||||
0.02
|
||||
)
|
||||
}
|
||||
// try to keep them in one spot for a bit to give time for the magic to happen
|
||||
fixation = brain.fixate(DurationLockFixation("Breed: laying egg", layDurationTicks))
|
||||
if (mate.biggerBrain.distract(Distraction.FORCE)) {
|
||||
mateFixation = mate.biggerBrain.fixate(DurationLockFixation("Breed: laying egg as the mate", layDurationTicks))
|
||||
}
|
||||
return ObjectiveStatus.FIXATED
|
||||
}
|
||||
|
||||
// path to the nest location
|
||||
private fun walkToNest(entity: PokemonEntity, brain: BiggerBrain, target: Vec3, speed: Float, description: String): ObjectiveStatus {
|
||||
if (brain.isFixation(fixation))
|
||||
return ObjectiveStatus.FIXATED
|
||||
// wait for mate to get here
|
||||
if (entity.position().distanceTo(target) <= closeEnough(entity))
|
||||
return ObjectiveStatus.ACTIVE
|
||||
if (!brain.distract(comfortDistraction))
|
||||
return ObjectiveStatus.BLOCKED
|
||||
val tracker = RegularMovementTracker.attempt(entity, WalkTarget(target, speed, closeEnough(entity).toInt())) ?: return ObjectiveStatus.ACTIVE
|
||||
fixation = brain.fixate(
|
||||
LookAndMoveFixation(
|
||||
distractionTypes = setOf(DistractionType.DAMAGE, DistractionType.FORCE),
|
||||
description = description,
|
||||
lookingTracker = LookingTracker.fromNavigationPathOrWalkTarget(entity, tracker.walkTarget),
|
||||
movementTracker = tracker
|
||||
)
|
||||
)
|
||||
return ObjectiveStatus.FIXATED
|
||||
}
|
||||
|
||||
// have the other mates be told where to go
|
||||
private fun bringMate(mate: PokemonEntity, target: Vec3, speed: Float) {
|
||||
val mateBrain = mate.biggerBrain
|
||||
if (mate.position().distanceTo(target) <= closeEnough(mate) || mateBrain.isFixation(mateFixation))
|
||||
return
|
||||
if (!mateBrain.distract(comfortDistraction))
|
||||
return
|
||||
val tracker = RegularMovementTracker.attempt(mate, WalkTarget(target, speed, closeEnough(mate).toInt())) ?: return
|
||||
mateFixation = mateBrain.fixate(
|
||||
LookAndMoveFixation(
|
||||
distractionTypes = setOf(DistractionType.DAMAGE, DistractionType.FORCE),
|
||||
description = "Breed: folow mate",
|
||||
lookingTracker = LookingTracker.fromNavigationPathOrWalkTarget(mate, tracker.walkTarget),
|
||||
movementTracker = tracker
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// checks for what nests are valid for the mates
|
||||
private fun canUseNest(entity: PokemonEntity, mate: PokemonEntity, world: ServerLevel, nest: NestBlockEntity): Boolean {
|
||||
val nestType = (nest.blockState.block as? WildNestBlock)?.type ?: return false
|
||||
if (!isNestTypeAllowed(nestType, entity, mate))
|
||||
return false
|
||||
val waterlogged = nest.blockState.getOptionalValue(BlockStateProperties.WATERLOGGED).orElse(false)
|
||||
if (!isWaterGroupCompatible(entity, mate, waterlogged))
|
||||
return false
|
||||
|
||||
// check if the nest is claimed by other pokemon that exist and maybe on their way to it (CRAB NOTE: This helps prevent swarms of mons going to the same nest which looked weird)
|
||||
val (existingParent1, existingParent2) = nest.nestClaimedBy
|
||||
val claimedByMate1 = existingParent1 != null && existingParent1 != entity.uuid && world.getEntity(existingParent1) != null
|
||||
val claimedByMate2 = existingParent2 != null && existingParent2 != entity.uuid && world.getEntity(existingParent2) != null
|
||||
if (claimedByMate1 && claimedByMate2)
|
||||
return false
|
||||
return true
|
||||
}
|
||||
|
||||
// make sure the nest type works for the mates and that wild use wild nests and owned pokemon use player placed nests
|
||||
private fun isNestTypeAllowed(nestType: WildNestType, entity: PokemonEntity, mate: PokemonEntity): Boolean {
|
||||
val allowed = WildNestTypes.combinedTypes(entity, mate).toMutableSet()
|
||||
if (!entity.pokemon.isWild() && !mate.pokemon.isWild()) {
|
||||
allowed.add(WildNestType.PLAYER)
|
||||
allowed.add(WildNestType.KELP)
|
||||
}
|
||||
if (nestType == WildNestType.PLAYER || nestType == WildNestType.KELP)
|
||||
return !entity.pokemon.isWild() && nestType in allowed
|
||||
return nestType in allowed
|
||||
}
|
||||
|
||||
// waterlogged nests only allow parents that both have a water egg group and land nests dont work for mates that only have water egg groups
|
||||
private fun isWaterGroupCompatible(entity: PokemonEntity, mate: PokemonEntity, nestWaterlogged: Boolean): Boolean {
|
||||
val mate1Groups = entity.pokemon.species.eggGroups.map { it.name.lowercase() }
|
||||
val mate2Groups = mate.pokemon.species.eggGroups.map { it.name.lowercase() }
|
||||
val mate1HasWaterGroup = mate1Groups.any { it in WATER_EGG_GROUPS || it == "ditto" }
|
||||
val mate2HasWaterGroup = mate2Groups.any { it in WATER_EGG_GROUPS || it == "ditto" }
|
||||
val mate1OnlyWaterGroup = mate1Groups.all { it in WATER_EGG_GROUPS }
|
||||
val mate2OnlyWaterGroup = mate2Groups.all { it in WATER_EGG_GROUPS }
|
||||
|
||||
return if (nestWaterlogged) mate1HasWaterGroup && mate2HasWaterGroup else !(mate1OnlyWaterGroup || mate2OnlyWaterGroup)
|
||||
}
|
||||
|
||||
class Config : ObjectiveConfig {
|
||||
val speedMultiplier: ExpressionOrEntityVariable = Either.left("0.4".asExpression())
|
||||
val condition: ExpressionOrEntityVariable = Either.left("true".asExpression())
|
||||
val layDurationTicks: ExpressionOrEntityVariable = Either.left("40".asExpression())
|
||||
|
||||
override fun getVariables(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): List<MoLangConfigVariable> {
|
||||
return listOf(speedMultiplier, condition, layDurationTicks).asVariables()
|
||||
}
|
||||
|
||||
override fun create(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): Objective {
|
||||
behaviourConfigurationContext.addMemories(
|
||||
CobblemonMemories.POTENTIAL_MATE,
|
||||
CobblemonMemories.NEST_LOCATION,
|
||||
CobblemonMemories.PATERNAL_NEST_LOCATION,
|
||||
CobblemonMemories.IDEAL_NEST_LOCATION,
|
||||
CobblemonMemories.EGG_IN_NEST,
|
||||
MemoryModuleType.ATTACK_TARGET
|
||||
)
|
||||
return Breed(
|
||||
speedMultiplier = speedMultiplier.asSimplifiedExpression(entity),
|
||||
condition = condition.asSimplifiedExpression(entity),
|
||||
layDurationTicks = layDurationTicks.resolveVariableInt(behaviourConfigurationContext.runtime)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// lets help those with more cushion for the pushing reach those nests to breed
|
||||
private fun closeEnough(entity: PokemonEntity) = CLOSE_ENOUGH_DISTANCE + entity.bbWidth / 2
|
||||
|
||||
companion object {
|
||||
private const val CLOSE_ENOUGH_DISTANCE = 1.5 // we will have this be our baseline and then scale based on hitbox (like above) for the bigger mons
|
||||
private val WATER_EGG_GROUPS = setOf("water_1", "water_2", "water_3")
|
||||
}
|
||||
}
|
||||
-167
@@ -1,167 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.objective
|
||||
|
||||
|
||||
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
|
||||
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
|
||||
import com.cobblemon.mod.common.api.ai.asVariables
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.MovementTracker
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.Distraction
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.idleBoredomDistraction
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.EntityFixation
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.MovementTrackerFixation
|
||||
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
|
||||
import com.cobblemon.mod.common.api.ai.resolveFloat
|
||||
import com.cobblemon.mod.common.api.ai.resolveInt
|
||||
import com.cobblemon.mod.common.entity.PoseType
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.entity.pokemon.ai.PokemonMoveControl
|
||||
import com.cobblemon.mod.common.util.asExpression
|
||||
import com.mojang.datafixers.util.Either
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.ai.memory.MemoryModuleType
|
||||
|
||||
/**
|
||||
* Swim or fly in a circle objective
|
||||
*/
|
||||
class CircleAround(
|
||||
val poseTypes: Set<PoseType>,
|
||||
val minTurnAngleDegrees: Float,
|
||||
val maxTurnAngleDegrees: Float,
|
||||
val verticalSpeed: Float,
|
||||
val speed: Float,
|
||||
val minDurationTicks: Int = 60,
|
||||
val maxDurationTicks: Int = 180
|
||||
) : Objective {
|
||||
var chosenTurnAngle = 0F
|
||||
var chosenDuration = 0
|
||||
var fixation: EntityFixation? = null
|
||||
var moveControl: PokemonMoveControl? = null
|
||||
|
||||
override fun getDescription(entity: LivingEntity) = "Circling"
|
||||
override fun perform(
|
||||
entity: LivingEntity,
|
||||
brain: BiggerBrain,
|
||||
activeObjectiveTags: MutableSet<String>
|
||||
): ObjectiveStatus {
|
||||
if (entity !is PokemonEntity) {
|
||||
return ObjectiveStatus.INVALID
|
||||
} else if (brain.isFixation(fixation)) {
|
||||
return ObjectiveStatus.FIXATED
|
||||
} else if (entity.getCurrentPoseType() !in poseTypes) {
|
||||
return ObjectiveStatus.BLOCKED
|
||||
} else if (brain.fixation != null) {
|
||||
fixation = null
|
||||
return ObjectiveStatus.BLOCKED // Since this doesn't distract anything then if the brain is fixated at all we are blocked
|
||||
}
|
||||
|
||||
// TODO: should this not be able to distract anything? relates to the above fixation comment
|
||||
// if (!brain.distract(idleBoredomDistraction)) {
|
||||
// return ObjectiveStatus.BLOCKED
|
||||
// }
|
||||
|
||||
entity.brain.eraseMemory(MemoryModuleType.WALK_TARGET)
|
||||
entity.navigation.stop()
|
||||
|
||||
// Gather a random turn angle and random duration
|
||||
this.chosenTurnAngle = entity.random.nextFloat() * (maxTurnAngleDegrees - minTurnAngleDegrees) + minTurnAngleDegrees
|
||||
if (entity.random.nextBoolean()) {
|
||||
chosenTurnAngle = -chosenTurnAngle
|
||||
}
|
||||
chosenDuration = entity.random.nextIntBetweenInclusive(minDurationTicks, maxDurationTicks)
|
||||
|
||||
// Grab moveControl off the entity to use during fixation
|
||||
this.moveControl = entity.moveControl as PokemonMoveControl?
|
||||
|
||||
|
||||
// Become fixated
|
||||
fixation = brain.fixate(
|
||||
CircleAroundFixation(
|
||||
this,
|
||||
null,
|
||||
chosenDuration
|
||||
)
|
||||
)
|
||||
return ObjectiveStatus.ACTIVE //TODO: should this have a cooldown? Currently just having it as the pure fallback and able to be distracted by anything. I guess fishes should always swim... probably not but I'll sort that later
|
||||
}
|
||||
|
||||
|
||||
class CircleAroundFixation(
|
||||
val objective: CircleAround,
|
||||
override val movementTracker: MovementTracker?,
|
||||
var timer: Int
|
||||
) : EntityFixation, MovementTrackerFixation {
|
||||
override fun getDescription(entity: LivingEntity) = "Circling around at ${this.objective.chosenTurnAngle} degrees for ${this.timer} more ticks"
|
||||
override fun onDistract(distraction: Distraction, entity: LivingEntity) {
|
||||
super.onDistract(distraction, entity)
|
||||
objective.moveControl?.stopBanking()
|
||||
}
|
||||
|
||||
// can be distracted by anything
|
||||
override fun canDistract(distraction: Distraction, entity: LivingEntity): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun tick(entity: LivingEntity, brain: BiggerBrain): Boolean {
|
||||
objective.moveControl?.startBanking(
|
||||
forwardBlocksPerTick = objective.speed,
|
||||
upwardsBlocksPerTick = objective.verticalSpeed,
|
||||
rightDegreesPerTick = objective.chosenTurnAngle,
|
||||
durationTicks = 2 //TODO: is it odd we have the check in multiple spots?
|
||||
)
|
||||
this.timer--
|
||||
if (this.timer < 0 || (entity as PokemonEntity).getCurrentPoseType() !in objective.poseTypes) {
|
||||
objective.moveControl?.stopBanking()
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
class Config : ObjectiveConfig {
|
||||
val poseTypes: Set<PoseType> = setOf(PoseType.FLY, PoseType.HOVER)
|
||||
val minAngularVelocityDegrees: ExpressionOrEntityVariable = Either.left("0.0".asExpression())
|
||||
val maxAngularVelocityDegrees: ExpressionOrEntityVariable = Either.left("1.0".asExpression())
|
||||
val speed: ExpressionOrEntityVariable = Either.left("0.6".asExpression())
|
||||
val verticalSpeed: ExpressionOrEntityVariable = Either.left("0.0".asExpression())
|
||||
val minDurationTicks: ExpressionOrEntityVariable = Either.left("60".asExpression())
|
||||
val maxDurationTicks: ExpressionOrEntityVariable = Either.left("180".asExpression())
|
||||
|
||||
|
||||
override fun getVariables(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
) = listOf(
|
||||
minAngularVelocityDegrees,
|
||||
maxAngularVelocityDegrees,
|
||||
speed,
|
||||
verticalSpeed,
|
||||
minDurationTicks,
|
||||
maxDurationTicks
|
||||
).asVariables()
|
||||
|
||||
override fun create(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
): Objective? {
|
||||
if (entity !is PokemonEntity) return null
|
||||
return CircleAround(
|
||||
poseTypes = poseTypes,
|
||||
minTurnAngleDegrees = minAngularVelocityDegrees.resolveFloat(behaviourConfigurationContext.runtime),
|
||||
maxTurnAngleDegrees = maxAngularVelocityDegrees.resolveFloat(behaviourConfigurationContext.runtime),
|
||||
speed = speed.resolveFloat(behaviourConfigurationContext.runtime),
|
||||
verticalSpeed = verticalSpeed.resolveFloat(behaviourConfigurationContext.runtime),
|
||||
minDurationTicks = minDurationTicks.resolveInt(behaviourConfigurationContext.runtime),
|
||||
maxDurationTicks = maxDurationTicks.resolveInt(behaviourConfigurationContext.runtime)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
-158
@@ -1,158 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.objective
|
||||
|
||||
|
||||
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
|
||||
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
|
||||
import com.cobblemon.mod.common.api.ai.asVariables
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.MovementStatus
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.MovementTracker
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.Distraction
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.idleBoredomDistraction
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.EntityFixation
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.MovementTrackerFixation
|
||||
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
|
||||
import com.cobblemon.mod.common.api.ai.resolveFloat
|
||||
import com.cobblemon.mod.common.api.ai.resolveInt
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonBehaviourFlag
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.net.messages.client.effect.RunPosableMoLangPacket
|
||||
import com.cobblemon.mod.common.util.asExpression
|
||||
import com.cobblemon.mod.common.util.getMemorySafely
|
||||
import com.mojang.datafixers.util.Either
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.ai.memory.MemoryModuleType
|
||||
|
||||
/**
|
||||
* Stops movement and plays a Pokemon's dance animation, if available.
|
||||
*
|
||||
*/
|
||||
class Dance(
|
||||
val minDuration: Int,
|
||||
val danceChance: Float
|
||||
) : Objective {
|
||||
var fixation: EntityFixation? = null
|
||||
var remainingCooldownTicks = 0
|
||||
override fun getDescription(entity: LivingEntity) = "Dancing"
|
||||
override fun perform(
|
||||
entity: LivingEntity,
|
||||
brain: BiggerBrain,
|
||||
activeObjectiveTags: MutableSet<String>
|
||||
): ObjectiveStatus {
|
||||
if (entity !is PokemonEntity) {
|
||||
return ObjectiveStatus.INVALID
|
||||
} else if (brain.isFixation(fixation)) {
|
||||
remainingCooldownTicks = ticksBetweenAttempts
|
||||
return ObjectiveStatus.FIXATED
|
||||
} else if (brain.fixation != null) {
|
||||
fixation = null
|
||||
}
|
||||
|
||||
if (remainingCooldownTicks > 0) {
|
||||
remainingCooldownTicks--
|
||||
return ObjectiveStatus.INACTIVE
|
||||
}
|
||||
|
||||
// Only dance if we feel like it
|
||||
val danceMemory = brain.getMemory(MemoryModuleType.DANCING) ?: false
|
||||
if (!danceMemory || entity.random.nextFloat() > danceChance || entity.ticksLived < 4 * 20) {
|
||||
return ObjectiveStatus.INACTIVE
|
||||
}
|
||||
|
||||
if (!brain.distract(idleBoredomDistraction)) {
|
||||
return ObjectiveStatus.BLOCKED
|
||||
}
|
||||
|
||||
entity.brain.eraseMemory(MemoryModuleType.WALK_TARGET)
|
||||
entity.navigation.stop()
|
||||
|
||||
|
||||
// Become fixated
|
||||
fixation = brain.fixate(
|
||||
DanceFixation(
|
||||
this,
|
||||
null,
|
||||
minDuration
|
||||
)
|
||||
)
|
||||
return ObjectiveStatus.ACTIVE
|
||||
}
|
||||
|
||||
|
||||
class DanceFixation(
|
||||
val objective: Dance,
|
||||
override val movementTracker: MovementTracker?,
|
||||
val minDuration: Int
|
||||
) : EntityFixation, MovementTrackerFixation {
|
||||
var ticksSinceStarted = 0
|
||||
override fun getDescription(entity: LivingEntity) = "Dancing"
|
||||
override fun tickMovementFixation() = MovementStatus.PENDING
|
||||
|
||||
override fun onDistract(distraction: Distraction, entity: LivingEntity) {
|
||||
super.onDistract(distraction, entity)
|
||||
stopDancing(entity)
|
||||
}
|
||||
|
||||
override fun canDistract(distraction: Distraction, entity: LivingEntity): Boolean {
|
||||
if (distraction.isIdleBoredom) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
fun stopDancing(entity: LivingEntity) {
|
||||
val pokemon = entity as? PokemonEntity ?: return
|
||||
pokemon.setBehaviourFlag(PokemonBehaviourFlag.DANCING, false)
|
||||
}
|
||||
|
||||
override fun tick(entity: LivingEntity, brain: BiggerBrain): Boolean {
|
||||
if (ticksSinceStarted == 0) { // Makes make me want a Start() for fixations
|
||||
val pokemon = entity as? PokemonEntity ?: return false
|
||||
RunPosableMoLangPacket(entity.id, setOf("v.bpm = 100;")).sendToPlayersAround(entity, 64.0)
|
||||
pokemon.setBehaviourFlag(PokemonBehaviourFlag.DANCING, true)
|
||||
}
|
||||
ticksSinceStarted++
|
||||
if (ticksSinceStarted > minDuration && !entity.brain.getMemorySafely(MemoryModuleType.DANCING).orElse(false)) {
|
||||
stopDancing(entity)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
class Config : ObjectiveConfig {
|
||||
var danceChance: ExpressionOrEntityVariable = Either.left("1/2".asExpression())
|
||||
var minDuration: ExpressionOrEntityVariable = Either.left("60".asExpression())
|
||||
|
||||
override fun getVariables(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
) = listOf(
|
||||
minDuration,
|
||||
danceChance
|
||||
).asVariables()
|
||||
|
||||
override fun create(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
): Objective {
|
||||
val minDuration = minDuration.resolveInt(behaviourConfigurationContext.runtime)
|
||||
val danceChance = danceChance.resolveFloat(behaviourConfigurationContext.runtime)
|
||||
return Dance(
|
||||
minDuration,
|
||||
danceChance)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val ticksBetweenAttempts = 20
|
||||
}
|
||||
}
|
||||
-384
@@ -1,384 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.objective
|
||||
|
||||
import com.bedrockk.molang.Expression
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.api.Priority
|
||||
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
|
||||
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
|
||||
import com.cobblemon.mod.common.api.ai.asSimplifiedExpression
|
||||
import com.cobblemon.mod.common.api.ai.asVariables
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.LookingTracker
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.RegularMovementTracker
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.Distraction
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.DistractionType
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.comfortDistraction
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.AttackFixation
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.EntityFixation
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.LookAndLockFixation
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.LookAndMoveFixation
|
||||
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
|
||||
import com.cobblemon.mod.common.api.ai.resolveInt as resolveVariableInt
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
|
||||
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.util.asExpression
|
||||
import com.cobblemon.mod.common.util.isInBattle
|
||||
import com.cobblemon.mod.common.util.mainThreadRuntime
|
||||
import com.cobblemon.mod.common.util.resolveBoolean
|
||||
import com.cobblemon.mod.common.util.resolveFloat
|
||||
import com.cobblemon.mod.common.util.resolveInt
|
||||
import com.cobblemon.mod.common.util.withQueryValue
|
||||
import com.mojang.datafixers.util.Either
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.core.particles.ParticleTypes
|
||||
import net.minecraft.server.level.ServerLevel
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.ai.memory.MemoryModuleType
|
||||
import net.minecraft.world.entity.ai.memory.WalkTarget
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import net.minecraft.world.entity.player.Player
|
||||
import net.minecraft.world.phys.Vec3
|
||||
|
||||
class DefendNest(
|
||||
speedMultiplier: Expression = "0.5".asExpression(),
|
||||
warnRange: Expression = "15.0".asExpression(),
|
||||
private val attackRange: Expression = "8.0".asExpression(),
|
||||
disengageRange: Expression = "20.0".asExpression(),
|
||||
condition: Expression = "true".asExpression(),
|
||||
targetCondition: Expression = "true".asExpression(),
|
||||
description: Expression = "'Defend Nest'".asExpression(),
|
||||
private val maxAttackDuration: Expression = "300".asExpression(),
|
||||
private val patrolMinRadius: Int = 4,
|
||||
private val patrolMaxRadius: Int = 8
|
||||
) : EntityResponseObjective(
|
||||
speedMultiplier = speedMultiplier,
|
||||
engageRange = warnRange,
|
||||
disengageRange = disengageRange,
|
||||
condition = condition,
|
||||
targetCondition = targetCondition,
|
||||
description = description
|
||||
) {
|
||||
private var fixation: EntityFixation? = null
|
||||
private var scanCooldownTicks = 0
|
||||
private var cryCooldownTicks = 0
|
||||
private var patrolCooldownTicks = 0
|
||||
|
||||
override fun perform(entity: LivingEntity, brain: BiggerBrain, activeObjectiveTags: MutableSet<String>): ObjectiveStatus {
|
||||
if (entity !is PokemonEntity || !entity.isAlive)
|
||||
return ObjectiveStatus.INVALID
|
||||
// so only wild mons will guard the nest (unless we want them to later on..)
|
||||
if (!entity.pokemon.isWild())
|
||||
return ObjectiveStatus.INACTIVE
|
||||
// if the mon is battling then don't worry about defending (this was causing crying and particle spam lol)
|
||||
if (entity.isBattling)
|
||||
return ObjectiveStatus.INACTIVE
|
||||
|
||||
val nestPos = brain.getMemory(CobblemonMemories.PATERNAL_NEST_LOCATION)
|
||||
if (nestPos == null) {
|
||||
// mates used to give up attacking players if the egg and nest was gone but we should have them chase them to the ends of the earth for their crimes
|
||||
val thief = brain.getMemory(CobblemonMemories.EGG_THIEF)
|
||||
if (thief != null && thief.isAlive && entity.hasLineOfSight(thief))
|
||||
return attack(entity, brain, thief)
|
||||
return stopDefending(brain)
|
||||
}
|
||||
val eggInNest = brain.getMemory(CobblemonMemories.EGG_IN_NEST) == true
|
||||
val thief = brain.getMemory(CobblemonMemories.EGG_THIEF)
|
||||
// if we have no egg to watch over and no thief then we should not defend the nest anymore and need to switch to a different task
|
||||
if (!eggInNest && thief == null)
|
||||
return stopDefending(brain)
|
||||
|
||||
if (scanCooldownTicks > 0)
|
||||
scanCooldownTicks--
|
||||
if (cryCooldownTicks > 0)
|
||||
cryCooldownTicks--
|
||||
val nestCenter = Vec3.atCenterOf(nestPos)
|
||||
|
||||
val attackTarget = brain.getMemory(MemoryModuleType.ATTACK_TARGET)
|
||||
if (attackTarget != null && attackTarget.uuid != trackedEntity?.uuid) {
|
||||
trackedEntity = null
|
||||
return ObjectiveStatus.BLOCKED
|
||||
}
|
||||
|
||||
val tracked = trackedEntity
|
||||
if (tracked != null) {
|
||||
if (!keepTrackingPlayer(entity, brain, tracked, nestCenter)) {
|
||||
brain.eraseMemory(MemoryModuleType.ATTACK_TARGET)
|
||||
brain.eraseMemory(MemoryModuleType.ANGRY_AT)
|
||||
trackedEntity = null
|
||||
fixation = null
|
||||
return ObjectiveStatus.ACTIVE
|
||||
}
|
||||
return respondToIntruder(entity, brain, tracked, nestCenter)
|
||||
}
|
||||
|
||||
if (scanCooldownTicks <= 0) {
|
||||
scanCooldownTicks = SCAN_INTERVAL_TICKS
|
||||
trackedEntity = findIntruder(entity, brain, nestCenter)
|
||||
if (trackedEntity != null) {
|
||||
cryCooldownTicks = 0
|
||||
return ObjectiveStatus.ACTIVE
|
||||
}
|
||||
}
|
||||
return patrol(entity, brain, nestCenter, activeObjectiveTags)
|
||||
}
|
||||
|
||||
override fun onRegister(entity: LivingEntity, brain: BiggerBrain) {
|
||||
// Go after any known egg thieves
|
||||
brain.subscribe(Priority.HIGH, CobblemonMemories.EGG_THIEF) { thief, _ ->
|
||||
if (entity is PokemonEntity && thief != null) {
|
||||
if (brain.fixation !is AttackFixation) {
|
||||
trackedEntity = thief
|
||||
attack(entity, brain, thief)
|
||||
}
|
||||
} else if (thief == null && fixation is AttackFixation && brain.isFixation(fixation)) {
|
||||
brain.distract(Distraction.FORCE)
|
||||
brain.eraseMemory(MemoryModuleType.ATTACK_TARGET)
|
||||
brain.eraseMemory(MemoryModuleType.ANGRY_AT)
|
||||
trackedEntity = null
|
||||
fixation = null
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
// stop defending the nest and remove the fixation
|
||||
private fun stopDefending(brain: BiggerBrain): ObjectiveStatus {
|
||||
if (trackedEntity != null) {
|
||||
brain.eraseMemory(MemoryModuleType.ATTACK_TARGET)
|
||||
brain.eraseMemory(MemoryModuleType.ANGRY_AT)
|
||||
trackedEntity = null
|
||||
fixation = null
|
||||
}
|
||||
if (fixation is LookAndLockFixation && brain.isFixation(fixation))
|
||||
brain.distract(Distraction.FORCE)
|
||||
return ObjectiveStatus.INACTIVE
|
||||
}
|
||||
|
||||
// various checks on if a player should be tracked still
|
||||
private fun keepTrackingPlayer(entity: PokemonEntity, brain: BiggerBrain, tracked: LivingEntity, nestCenter: Vec3): Boolean {
|
||||
if (tracked.isRemoved || !tracked.isAlive)
|
||||
return false
|
||||
if (tracked is Player && (tracked.isCreative || tracked.isSpectator))
|
||||
return false
|
||||
if (tracked is ServerPlayer && tracked.isInBattle())
|
||||
return false
|
||||
if (brain.getMemory(CobblemonMemories.EGG_THIEF)?.uuid == tracked.uuid)
|
||||
return true
|
||||
val runtime = mainThreadRuntime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
|
||||
return tracked.position().distanceTo(nestCenter) <= runtime.resolveFloat(disengageRange)
|
||||
}
|
||||
|
||||
// warn players that get too close or that steal an egg
|
||||
private fun respondToIntruder(entity: PokemonEntity, brain: BiggerBrain, tracked: LivingEntity, nestCenter: Vec3): ObjectiveStatus {
|
||||
val runtime = mainThreadRuntime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
|
||||
val distanceToNest = tracked.position().distanceTo(nestCenter)
|
||||
val isThief = brain.getMemory(CobblemonMemories.EGG_THIEF)?.uuid == tracked.uuid
|
||||
return when {
|
||||
isThief || distanceToNest <= runtime.resolveFloat(attackRange) -> attack(entity, brain, tracked) // stop you have violated the law!
|
||||
distanceToNest <= runtime.resolveFloat(engageRange) -> warn(entity, brain, tracked, nestCenter) // almost too close!
|
||||
else -> ObjectiveStatus.ACTIVE
|
||||
}
|
||||
}
|
||||
|
||||
// force an attack fixation
|
||||
private fun attack(entity: PokemonEntity, brain: BiggerBrain, target: LivingEntity): ObjectiveStatus {
|
||||
if (fixation is AttackFixation && brain.isFixation(fixation))
|
||||
return ObjectiveStatus.FIXATED
|
||||
if (!brain.distract(Distraction.FORCE))
|
||||
return ObjectiveStatus.BLOCKED
|
||||
val runtime = mainThreadRuntime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
|
||||
entity.cry()
|
||||
// todo we can use our own particles later
|
||||
(entity.level() as? ServerLevel)?.sendParticles(
|
||||
ParticleTypes.ANGRY_VILLAGER,
|
||||
entity.getRandomX(1.0),
|
||||
entity.getRandomY() + 0.5,
|
||||
entity.getRandomZ(1.0),
|
||||
4 + entity.random.nextInt(2),
|
||||
0.2 * entity.random.nextGaussian(),
|
||||
0.1 * entity.random.nextGaussian(),
|
||||
0.2 * entity.random.nextGaussian(),
|
||||
0.02
|
||||
)
|
||||
|
||||
brain.setMemory(MemoryModuleType.ATTACK_TARGET, target, runtime.resolveInt(maxAttackDuration).toLong())
|
||||
brain.setMemory(MemoryModuleType.ANGRY_AT, target.uuid)
|
||||
brain.eraseMemory(CobblemonMemories.EGG_SEARCH_COUNTDOWN)
|
||||
fixation = brain.fixate(
|
||||
AttackFixation(
|
||||
targetEntity = target,
|
||||
speedMultiplier = speedMultiplier,
|
||||
availableMoves = getRealTimeMoves(entity), // idk how this works but looks like Jazz lets us do this now
|
||||
distractionTypes = setOf(DistractionType.FORCE)
|
||||
)
|
||||
)
|
||||
return ObjectiveStatus.FIXATED
|
||||
}
|
||||
|
||||
// cry at players that get too close to the nest
|
||||
private fun warn(entity: PokemonEntity, brain: BiggerBrain, target: LivingEntity, nestCenter: Vec3): ObjectiveStatus {
|
||||
if (cryCooldownTicks <= 0) {
|
||||
entity.cry()
|
||||
// todo replace these particles later here too
|
||||
(entity.level() as? ServerLevel)?.sendParticles(
|
||||
ParticleTypes.ANGRY_VILLAGER,
|
||||
entity.getRandomX(1.0),
|
||||
entity.getRandomY() + 0.5,
|
||||
entity.getRandomZ(1.0),
|
||||
4 + entity.random.nextInt(2),
|
||||
0.2 * entity.random.nextGaussian(),
|
||||
0.1 * entity.random.nextGaussian(),
|
||||
0.2 * entity.random.nextGaussian(),
|
||||
0.02
|
||||
)
|
||||
cryCooldownTicks = CRY_COOLDOWN_TICKS + entity.random.nextInt(CRY_COOLDOWN_RANDOM_TICKS)
|
||||
}
|
||||
if (fixation is LookAndLockFixation && brain.isFixation(fixation))
|
||||
return ObjectiveStatus.FIXATED
|
||||
if (!brain.distract(Distraction.FORCE))
|
||||
return ObjectiveStatus.BLOCKED
|
||||
val runtime = mainThreadRuntime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
|
||||
val warnRange = runtime.resolveFloat(engageRange)
|
||||
val attackRange = runtime.resolveFloat(attackRange)
|
||||
fixation = brain.fixate(
|
||||
LookAndLockFixation(
|
||||
lookingTracker = LookingTracker.forTargetEntity(entity, target),
|
||||
distractionTypes = setOf(DistractionType.DAMAGE, DistractionType.FORCE),
|
||||
condition = {
|
||||
val distance = target.position().distanceTo(nestCenter)
|
||||
trackedEntity?.uuid == target.uuid && distance <= warnRange && distance > attackRange
|
||||
}
|
||||
)
|
||||
)
|
||||
return ObjectiveStatus.FIXATED
|
||||
}
|
||||
|
||||
// looks for player within the warn range and too close to the nest
|
||||
private fun findIntruder(entity: PokemonEntity, brain: BiggerBrain, nestCenter: Vec3): LivingEntity? {
|
||||
val runtime = mainThreadRuntime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
|
||||
if (!runtime.resolveBoolean(condition))
|
||||
return null
|
||||
val warnRange = runtime.resolveFloat(engageRange)
|
||||
val visible = brain.getMemory(MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES) ?: return null
|
||||
|
||||
// todo how should we handle creative players?
|
||||
return visible.findAll { candidate ->
|
||||
candidate is Player &&
|
||||
!(candidate is ServerPlayer && candidate.isInBattle()) &&
|
||||
candidate.position().distanceTo(nestCenter) <= warnRange &&
|
||||
runtime.withQueryValue("target", candidate.asMostSpecificMoLangValue()).resolveBoolean(targetCondition)
|
||||
}.minByOrNull { it.position().distanceToSqr(nestCenter) }
|
||||
}
|
||||
|
||||
// pathfind around various spots around the nest
|
||||
private fun patrol(entity: PokemonEntity, brain: BiggerBrain, nestCenter: Vec3, activeObjectiveTags: MutableSet<String>): ObjectiveStatus {
|
||||
activeObjectiveTags.add(Objective.OBJECTIVE_TAG_WANDERING)
|
||||
if (fixation is LookAndMoveFixation && brain.isFixation(fixation))
|
||||
return ObjectiveStatus.FIXATED
|
||||
if (patrolCooldownTicks > 0) {
|
||||
patrolCooldownTicks--
|
||||
return ObjectiveStatus.ACTIVE
|
||||
}
|
||||
val world = entity.level() as? ServerLevel ?: return ObjectiveStatus.INVALID
|
||||
val target = findPatrolPosition(world, nestCenter, entity) ?: return ObjectiveStatus.ACTIVE
|
||||
if (!brain.distract(comfortDistraction))
|
||||
return ObjectiveStatus.BLOCKED
|
||||
val speed = mainThreadRuntime.withQueryValue("entity", entity.asMostSpecificMoLangValue()).resolveFloat(speedMultiplier)
|
||||
val tracker = RegularMovementTracker.attempt(entity, WalkTarget(target, speed, 1)) ?: return ObjectiveStatus.ACTIVE
|
||||
fixation = brain.fixate(
|
||||
LookAndMoveFixation(
|
||||
distractionTypes = setOf(DistractionType.DAMAGE, DistractionType.FORCE, DistractionType.COMFORT),
|
||||
description = "patrol method in DefendNest objective",
|
||||
lookingTracker = LookingTracker.fromNavigationPathOrWalkTarget(entity, tracker.walkTarget),
|
||||
movementTracker = tracker,
|
||||
onMovementComplete = { _, _ -> patrolCooldownTicks = PATROL_PAUSE_TICKS + entity.random.nextInt(PATROL_PAUSE_RANDOM_TICKS) }
|
||||
)
|
||||
)
|
||||
return ObjectiveStatus.FIXATED
|
||||
}
|
||||
|
||||
// find pathfindable spot to patrol to
|
||||
private fun findPatrolPosition(world: ServerLevel, center: Vec3, entity: PokemonEntity): Vec3? {
|
||||
repeat(PATROL_ATTEMPTS) {
|
||||
val angle = world.random.nextDouble() * Math.PI * 2
|
||||
val radius = patrolMinRadius + world.random.nextInt(patrolMaxRadius - patrolMinRadius + 1)
|
||||
val candidate = center.add(Math.cos(angle) * radius, world.random.nextDouble() * 2 - 1, Math.sin(angle) * radius)
|
||||
val blockPos = BlockPos.containing(candidate)
|
||||
if (!world.getBlockState(blockPos).isSolidRender(world, blockPos) && entity.navigation.isStableDestination(blockPos)) {
|
||||
return Vec3.atCenterOf(blockPos)
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
class Config : ObjectiveConfig {
|
||||
val speedMultiplier: ExpressionOrEntityVariable = Either.left("0.5".asExpression())
|
||||
val warnRange: ExpressionOrEntityVariable = Either.left("15.0".asExpression())
|
||||
val attackRange: ExpressionOrEntityVariable = Either.left("8.0".asExpression())
|
||||
val disengageRange: ExpressionOrEntityVariable = Either.left("20.0".asExpression())
|
||||
val condition: ExpressionOrEntityVariable = Either.left("true".asExpression())
|
||||
val targetCondition: ExpressionOrEntityVariable = Either.left("true".asExpression())
|
||||
val maxAttackDuration: ExpressionOrEntityVariable = Either.left("300".asExpression())
|
||||
val description: ExpressionOrEntityVariable = Either.left("'Defend Nest'".asExpression())
|
||||
val patrolMinRadius: ExpressionOrEntityVariable = Either.left("4".asExpression())
|
||||
val patrolMaxRadius: ExpressionOrEntityVariable = Either.left("8".asExpression())
|
||||
|
||||
override fun getVariables(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): List<MoLangConfigVariable> {
|
||||
return listOf(
|
||||
speedMultiplier,
|
||||
warnRange,
|
||||
attackRange,
|
||||
disengageRange,
|
||||
condition,
|
||||
targetCondition,
|
||||
maxAttackDuration,
|
||||
description,
|
||||
patrolMinRadius,
|
||||
patrolMaxRadius
|
||||
).asVariables()
|
||||
}
|
||||
|
||||
override fun create(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): Objective {
|
||||
// I don't think we need any other memory than these
|
||||
behaviourConfigurationContext.addMemories(
|
||||
CobblemonMemories.PATERNAL_NEST_LOCATION,
|
||||
CobblemonMemories.EGG_IN_NEST,
|
||||
CobblemonMemories.EGG_THIEF,
|
||||
CobblemonMemories.EGG_SEARCH_COUNTDOWN,
|
||||
MemoryModuleType.ANGRY_AT,
|
||||
MemoryModuleType.ATTACK_TARGET,
|
||||
MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES
|
||||
)
|
||||
return DefendNest(
|
||||
speedMultiplier = speedMultiplier.asSimplifiedExpression(entity),
|
||||
warnRange = warnRange.asSimplifiedExpression(entity),
|
||||
attackRange = attackRange.asSimplifiedExpression(entity),
|
||||
disengageRange = disengageRange.asSimplifiedExpression(entity),
|
||||
condition = condition.asSimplifiedExpression(entity),
|
||||
targetCondition = targetCondition.asSimplifiedExpression(entity),
|
||||
description = description.asSimplifiedExpression(entity),
|
||||
maxAttackDuration = maxAttackDuration.asSimplifiedExpression(entity),
|
||||
patrolMinRadius = patrolMinRadius.resolveVariableInt(behaviourConfigurationContext.runtime),
|
||||
patrolMaxRadius = patrolMaxRadius.resolveVariableInt(behaviourConfigurationContext.runtime)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val SCAN_INTERVAL_TICKS = 20
|
||||
private const val CRY_COOLDOWN_TICKS = 75
|
||||
private const val CRY_COOLDOWN_RANDOM_TICKS = 50
|
||||
private const val PATROL_PAUSE_TICKS = 40
|
||||
private const val PATROL_PAUSE_RANDOM_TICKS = 40
|
||||
private const val PATROL_ATTEMPTS = 20
|
||||
}
|
||||
}
|
||||
-76
@@ -1,76 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.objective
|
||||
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
|
||||
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
|
||||
import com.cobblemon.mod.common.api.ai.asVariables
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.LookingTracker
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.MovementTracker
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.RegularMovementTracker
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.Distraction
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.DistractionType
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.comfortDistraction
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.idleBoredomDistraction
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.EntityFixation
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.LookAndMoveFixation
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.MovementTrackerFixation
|
||||
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
|
||||
import com.cobblemon.mod.common.api.ai.resolveFloat
|
||||
import com.cobblemon.mod.common.api.ai.resolveInt
|
||||
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
|
||||
import com.cobblemon.mod.common.entity.PoseType
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.entity.pokemon.ai.PokemonMoveControl
|
||||
import com.cobblemon.mod.common.util.asExpression
|
||||
import com.mojang.datafixers.util.Either
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.PathfinderMob
|
||||
import net.minecraft.world.entity.ai.behavior.EntityTracker
|
||||
import net.minecraft.world.entity.ai.memory.MemoryModuleType
|
||||
import net.minecraft.world.entity.ai.memory.WalkTarget
|
||||
|
||||
/**
|
||||
* Defend owner objective
|
||||
* Handles transition of nearest visible attacker to a valid attack target when wanting to defend owner.
|
||||
* Could possibly be more configurable in the future
|
||||
*/
|
||||
class DefendOwner() : Objective {
|
||||
var cooldownTicks = 0
|
||||
|
||||
override fun getDescription(entity: LivingEntity) = "Following parent species"
|
||||
|
||||
override fun perform(
|
||||
entity: LivingEntity,
|
||||
brain: BiggerBrain,
|
||||
activeObjectiveTags: MutableSet<String>
|
||||
): ObjectiveStatus {
|
||||
if (entity !is PokemonEntity) return ObjectiveStatus.INVALID
|
||||
if (brain.hasMemoryValue(MemoryModuleType.ATTACK_TARGET)) return ObjectiveStatus.INACTIVE
|
||||
|
||||
val attacker = brain.getMemory(CobblemonMemories.NEAREST_VISIBLE_ATTACKER)
|
||||
?: return ObjectiveStatus.INACTIVE
|
||||
|
||||
brain.setMemory(MemoryModuleType.ATTACK_TARGET, attacker)
|
||||
return ObjectiveStatus.ACTIVE
|
||||
}
|
||||
|
||||
class Config : ObjectiveConfig {
|
||||
override fun getVariables(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext) =
|
||||
emptyList<MoLangConfigVariable>()
|
||||
|
||||
override fun create(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): Objective? {
|
||||
if (entity !is PokemonEntity) return null
|
||||
return DefendOwner()
|
||||
}
|
||||
}
|
||||
}
|
||||
-171
@@ -1,171 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.objective
|
||||
|
||||
import com.bedrockk.molang.Expression
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.api.Priority
|
||||
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
|
||||
import com.cobblemon.mod.common.api.ai.CobblemonAttackTargetData
|
||||
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
|
||||
import com.cobblemon.mod.common.api.ai.asSimplifiedExpression
|
||||
import com.cobblemon.mod.common.api.ai.asVariables
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
|
||||
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
|
||||
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.util.asExpression
|
||||
import com.cobblemon.mod.common.util.mainThreadRuntime
|
||||
import com.cobblemon.mod.common.util.resolveBoolean
|
||||
import com.cobblemon.mod.common.util.resolveFloat
|
||||
import com.cobblemon.mod.common.util.resolveInt
|
||||
import com.cobblemon.mod.common.util.withQueryValue
|
||||
import com.mojang.datafixers.util.Either
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.ai.memory.MemoryModuleType
|
||||
import net.minecraft.world.entity.ai.memory.NearestVisibleLivingEntities
|
||||
|
||||
|
||||
class Defensive(
|
||||
private val speedMultiplier: Expression = "0.5".asExpression(), // How do we transfer speed to the attack behaviour? Do we want to?
|
||||
private val engageRange: Expression = "5.0".asExpression(),
|
||||
private val disengageRange: Expression = "20.0".asExpression(),
|
||||
private val condition: Expression = "true".asExpression(),
|
||||
private val maxAttackDuration: Expression = "300".asExpression(),
|
||||
private val retaliationHitCount: Expression = "1".asExpression()
|
||||
) : Objective {
|
||||
private var remainingCooldownTicks = 0
|
||||
private var trackedEntity: LivingEntity? = null
|
||||
override val observedMemories = listOf(MemoryModuleType.HURT_BY_ENTITY, CobblemonMemories.ATTACK_TARGET_DATA)
|
||||
|
||||
override fun getDescription(entity: LivingEntity) = "Defensive Behavior"
|
||||
|
||||
override fun perform(
|
||||
entity: LivingEntity,
|
||||
brain: BiggerBrain,
|
||||
activeObjectiveTags: MutableSet<String>
|
||||
): ObjectiveStatus {
|
||||
if (entity !is PokemonEntity || !entity.isAlive) return ObjectiveStatus.INVALID
|
||||
|
||||
val attackTarget = brain.getMemory(MemoryModuleType.ATTACK_TARGET)
|
||||
val avoidTarget = brain.getMemory(MemoryModuleType.AVOID_TARGET)
|
||||
|
||||
if (trackedEntity != null) {
|
||||
if (attackTarget != null && trackedEntity?.uuid == attackTarget.uuid) {
|
||||
// Check if we continue
|
||||
return ObjectiveStatus.ACTIVE
|
||||
} else if (avoidTarget != null && trackedEntity?.uuid === avoidTarget.uuid) {
|
||||
// Check if we continue
|
||||
val runtime = mainThreadRuntime.withQueryValue("entity", (trackedEntity!!).asMostSpecificMoLangValue())
|
||||
val disengageDistance = runtime.resolveFloat(disengageRange)
|
||||
if (disengageDistance < entity.distanceTo(trackedEntity!!)) {
|
||||
brain.eraseMemory(MemoryModuleType.AVOID_TARGET)
|
||||
trackedEntity = null
|
||||
}
|
||||
} else {
|
||||
// Something has overrided us
|
||||
trackedEntity = null
|
||||
}
|
||||
} else {
|
||||
// Scan visible livingEntities
|
||||
val entities: NearestVisibleLivingEntities =
|
||||
brain.getMemory(MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES) ?: return ObjectiveStatus.INACTIVE
|
||||
|
||||
val entity = entities.findClosest { it ->
|
||||
val runtime = mainThreadRuntime.withQueryValue("entity", (it).asMostSpecificMoLangValue())
|
||||
runtime.resolveBoolean(condition) && runtime.resolveFloat(engageRange) > entity.distanceTo(it)
|
||||
}.orElse(null)
|
||||
if (entity != null) {
|
||||
trackedEntity = entity
|
||||
brain.setMemory(MemoryModuleType.AVOID_TARGET, entity)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (remainingCooldownTicks > 0) {
|
||||
remainingCooldownTicks--
|
||||
}
|
||||
|
||||
return if (trackedEntity != null) ObjectiveStatus.ACTIVE else ObjectiveStatus.INACTIVE
|
||||
}
|
||||
|
||||
override fun onRegister(entity: LivingEntity, brain: BiggerBrain) {
|
||||
brain.subscribe(
|
||||
priority = Priority.LOW,
|
||||
memory = MemoryModuleType.HURT_BY_ENTITY
|
||||
) { value, labels -> onHurtByEntityChanged(brain, value, labels) }
|
||||
|
||||
brain.subscribe(
|
||||
priority = Priority.LOW,
|
||||
memory = CobblemonMemories.ATTACK_TARGET_DATA
|
||||
) { value, labels -> onAttackTargetDataChanged(brain, value, labels) }
|
||||
}
|
||||
|
||||
fun onHurtByEntityChanged(brain: BiggerBrain, livingEntity: LivingEntity?, labels: MutableSet<String>): Boolean {
|
||||
if (livingEntity != null) {
|
||||
// Something just smacked us, see if we should get mad about it.
|
||||
val livingEntity = livingEntity as LivingEntity
|
||||
val runtime = mainThreadRuntime.withQueryValue("entity", (livingEntity).asMostSpecificMoLangValue())
|
||||
val shouldTarget = runtime.resolveBoolean(condition)
|
||||
if (shouldTarget) {
|
||||
brain.setMemory(MemoryModuleType.ATTACK_TARGET, livingEntity,
|
||||
runtime.resolveInt(maxAttackDuration).toLong()
|
||||
)
|
||||
brain.eraseMemory(MemoryModuleType.AVOID_TARGET)
|
||||
trackedEntity = livingEntity
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
fun onAttackTargetDataChanged(brain: BiggerBrain, attackData: CobblemonAttackTargetData?, labels: MutableSet<String>): Boolean {
|
||||
if (attackData != null) {
|
||||
// We smacked them, check if it's time for tactical retreat
|
||||
val livingEntity = brain.getMemory(MemoryModuleType.ATTACK_TARGET) ?: return true
|
||||
val runtime = mainThreadRuntime.withQueryValue("entity", (livingEntity).asMostSpecificMoLangValue())
|
||||
if (attackData.hitsLanded >= runtime.resolveInt(retaliationHitCount)) {
|
||||
// Justice served, carry on
|
||||
brain.eraseMemory(MemoryModuleType.ATTACK_TARGET)
|
||||
brain.eraseMemory(CobblemonMemories.ATTACK_TARGET_DATA)
|
||||
brain.setMemory(MemoryModuleType.AVOID_TARGET, trackedEntity)
|
||||
}
|
||||
}
|
||||
return true // Other listeners can handle it that's fine
|
||||
}
|
||||
|
||||
class Config : ObjectiveConfig {
|
||||
val speedMultiplier: ExpressionOrEntityVariable = Either.left("0.5".asExpression())
|
||||
val engageRange: ExpressionOrEntityVariable = Either.left("10.0".asExpression())
|
||||
val disengageRange: ExpressionOrEntityVariable = Either.left("10.0".asExpression())
|
||||
val condition: ExpressionOrEntityVariable = Either.left("10.0".asExpression())
|
||||
val maxAttackDuration: ExpressionOrEntityVariable = Either.left("300".asExpression())
|
||||
val retaliationHitCount: ExpressionOrEntityVariable = Either.left("1".asExpression())
|
||||
override fun getVariables(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
): List<MoLangConfigVariable> {
|
||||
return listOf(condition, speedMultiplier, engageRange, disengageRange, maxAttackDuration, retaliationHitCount).asVariables()
|
||||
}
|
||||
|
||||
override fun create(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
): Objective {
|
||||
return Defensive(
|
||||
condition = condition.asSimplifiedExpression(entity),
|
||||
speedMultiplier = speedMultiplier.asSimplifiedExpression(entity),
|
||||
engageRange = engageRange.asSimplifiedExpression(entity),
|
||||
disengageRange = disengageRange.asSimplifiedExpression(entity),
|
||||
maxAttackDuration = maxAttackDuration.asSimplifiedExpression(entity),
|
||||
retaliationHitCount = retaliationHitCount.asSimplifiedExpression(entity),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
-147
@@ -1,147 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.objective
|
||||
|
||||
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
|
||||
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
|
||||
import com.cobblemon.mod.common.api.ai.asVariables
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.Distraction
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.comfortDistraction
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.EntityFixation
|
||||
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
|
||||
import com.cobblemon.mod.common.api.ai.resolveFloat
|
||||
import com.cobblemon.mod.common.api.ai.resolveInt
|
||||
import com.cobblemon.mod.common.api.pokemon.feature.FlagSpeciesFeature
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.util.DataKeys
|
||||
import com.cobblemon.mod.common.util.asExpression
|
||||
import com.mojang.datafixers.util.Either
|
||||
import net.minecraft.server.level.ServerLevel
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.level.GameRules
|
||||
import net.minecraft.world.level.block.Block
|
||||
import net.minecraft.world.level.block.Blocks
|
||||
import net.minecraft.world.level.block.state.predicate.BlockStatePredicate
|
||||
|
||||
/**
|
||||
* The baa's eat the graa's
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since January 9th, 2025
|
||||
*/
|
||||
class EatGrass(
|
||||
val cooldownTicks: Int,
|
||||
val eatGrassChance: Float
|
||||
) : Objective {
|
||||
var fixation: EntityFixation? = null
|
||||
val grassPredicate = BlockStatePredicate.forBlock(Blocks.GRASS_BLOCK)
|
||||
var remainingCooldownTicks = cooldownTicks
|
||||
|
||||
override fun getDescription(entity: LivingEntity) = "Eat grass"
|
||||
|
||||
override fun perform(
|
||||
entity: LivingEntity,
|
||||
brain: BiggerBrain,
|
||||
activeObjectiveTags: MutableSet<String>
|
||||
): ObjectiveStatus {
|
||||
if (brain.isFixation(fixation)) {
|
||||
return ObjectiveStatus.FIXATED
|
||||
} else if (entity !is PokemonEntity) {
|
||||
return ObjectiveStatus.INVALID
|
||||
} else if (!brain.canDistract(comfortDistraction)) {
|
||||
return ObjectiveStatus.BLOCKED
|
||||
} else if (remainingCooldownTicks > 0) {
|
||||
remainingCooldownTicks--
|
||||
return ObjectiveStatus.ACTIVE
|
||||
}
|
||||
|
||||
val world = entity.level() as ServerLevel
|
||||
|
||||
entity.pokemon.getFeature<FlagSpeciesFeature>(DataKeys.HAS_BEEN_SHEARED)?.enabled?.takeIf { it } ?: run {
|
||||
return ObjectiveStatus.INACTIVE
|
||||
}
|
||||
|
||||
if (world.random.nextFloat() > eatGrassChance) {
|
||||
return ObjectiveStatus.ACTIVE
|
||||
} else {
|
||||
val blockPos = entity.blockPosition()
|
||||
|
||||
// Are we not even on a grass block? Alright don't bother
|
||||
if (!grassPredicate.test(world.getBlockState(blockPos)) && !world.getBlockState(blockPos.below()).`is`(Blocks.GRASS_BLOCK)) {
|
||||
return ObjectiveStatus.ACTIVE
|
||||
}
|
||||
|
||||
fixation = brain.fixate(EatGrassFixation(this))
|
||||
entity.playAnimation("eat")
|
||||
world.broadcastEntityEvent(entity, 10.toByte())
|
||||
return ObjectiveStatus.FIXATED
|
||||
}
|
||||
}
|
||||
|
||||
class EatGrassFixation(val objective: EatGrass) : EntityFixation {
|
||||
var timer = 40
|
||||
override fun getDescription(entity: LivingEntity) = "Eating grass"
|
||||
override fun canDistract(distraction: Distraction, entity: LivingEntity) = distraction.isComfort || distraction.isDamage || distraction.isForce
|
||||
override fun tick(entity: LivingEntity, brain: BiggerBrain): Boolean {
|
||||
timer--
|
||||
if (timer <= 0) {
|
||||
entity as PokemonEntity
|
||||
val world = entity.level() as ServerLevel
|
||||
val blockPos = entity.blockPosition()
|
||||
if (objective.grassPredicate.test(world.getBlockState(blockPos)) ) {
|
||||
if (world.gameRules.getBoolean(GameRules.RULE_MOBGRIEFING)) {
|
||||
world.destroyBlock(blockPos, false)
|
||||
}
|
||||
entity.ate()
|
||||
objective.remainingCooldownTicks = objective.cooldownTicks
|
||||
} else if (world.getBlockState(blockPos.below()).`is`(Blocks.GRASS_BLOCK)) {
|
||||
val below = blockPos.below()
|
||||
if (world.gameRules.getBoolean(GameRules.RULE_MOBGRIEFING)) {
|
||||
world.levelEvent(2001, below, Block.getId(Blocks.GRASS_BLOCK.defaultBlockState()))
|
||||
world.setBlock(below, Blocks.DIRT.defaultBlockState(), 2)
|
||||
}
|
||||
|
||||
entity.ate()
|
||||
objective.remainingCooldownTicks = objective.cooldownTicks
|
||||
}
|
||||
}
|
||||
return timer > 0
|
||||
}
|
||||
}
|
||||
|
||||
class Config : ObjectiveConfig {
|
||||
var cooldownTicks: ExpressionOrEntityVariable = Either.left("400".asExpression())
|
||||
var eatGrassChance: ExpressionOrEntityVariable = Either.left("1/400".asExpression())
|
||||
|
||||
override fun getVariables(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
) = listOf(
|
||||
cooldownTicks,
|
||||
eatGrassChance
|
||||
).asVariables()
|
||||
|
||||
override fun create(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
): Objective? {
|
||||
val cooldownTicks = cooldownTicks.resolveInt(behaviourConfigurationContext.runtime)
|
||||
val eatGrassChance = eatGrassChance.resolveFloat(behaviourConfigurationContext.runtime)
|
||||
return if (eatGrassChance == 0F) {
|
||||
null
|
||||
} else {
|
||||
EatGrass(
|
||||
cooldownTicks = cooldownTicks,
|
||||
eatGrassChance = eatGrassChance
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-170
@@ -1,170 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.objective
|
||||
|
||||
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
|
||||
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
|
||||
import com.cobblemon.mod.common.api.ai.asVariables
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.Distraction
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.EntityFixation
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.PickupItem.Companion.dropItem
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.sense.PokemonItemSense.Companion.PICKUP_ITEMS
|
||||
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
|
||||
import com.cobblemon.mod.common.api.ai.resolveInt
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.pokemon.ai.ObtainableItem
|
||||
import com.cobblemon.mod.common.util.asExpression
|
||||
import com.cobblemon.mod.common.util.findMatchingEntry
|
||||
import com.cobblemon.mod.common.util.getObjectList
|
||||
import com.cobblemon.mod.common.util.mainThreadRuntime
|
||||
import com.cobblemon.mod.common.util.resolve
|
||||
import com.cobblemon.mod.common.util.withQueryValue
|
||||
import com.mojang.datafixers.util.Either
|
||||
import net.minecraft.core.component.DataComponents
|
||||
import net.minecraft.core.particles.ItemParticleOption
|
||||
import net.minecraft.core.particles.ParticleTypes
|
||||
import net.minecraft.core.registries.BuiltInRegistries
|
||||
import net.minecraft.server.level.ServerLevel
|
||||
import net.minecraft.sounds.SoundEvents
|
||||
import net.minecraft.world.entity.Entity
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.item.ItemEntity
|
||||
import net.minecraft.world.item.ItemStack
|
||||
|
||||
/**
|
||||
* Eat the currently held item, apply the effect, and drop if it's an undesired item
|
||||
*
|
||||
* @author JazzMcNade
|
||||
* @since February 16th, 2026
|
||||
*/
|
||||
class EatHeldItem(
|
||||
val cooldownTicks: Int,
|
||||
val pickupItems: List<ObtainableItem>
|
||||
) : Objective {
|
||||
var fixation: EntityFixation? = null
|
||||
var remainingCooldownTicks = cooldownTicks
|
||||
|
||||
override fun getDescription(entity: LivingEntity) = "Eat Held Item"
|
||||
|
||||
override fun perform(
|
||||
entity: LivingEntity,
|
||||
brain: BiggerBrain,
|
||||
activeObjectiveTags: MutableSet<String>
|
||||
): ObjectiveStatus {
|
||||
if (brain.isFixation(fixation)) {
|
||||
return ObjectiveStatus.FIXATED
|
||||
} else if (entity !is PokemonEntity) {
|
||||
return ObjectiveStatus.INVALID
|
||||
} else if (remainingCooldownTicks > 0) {
|
||||
remainingCooldownTicks--
|
||||
return ObjectiveStatus.INACTIVE
|
||||
}
|
||||
val itemStack: ItemStack = entity.pokemon.heldItem()
|
||||
if (canEat(itemStack, entity)) {
|
||||
fixation = brain.fixate(EatHeldItemFixation(this))
|
||||
remainingCooldownTicks = 120
|
||||
return ObjectiveStatus.FIXATED
|
||||
}
|
||||
return ObjectiveStatus.INACTIVE
|
||||
}
|
||||
|
||||
fun canEat(item: ItemStack, entity: PokemonEntity): Boolean {
|
||||
return item.has(DataComponents.FOOD) || pickupItems.findMatchingEntry(entity.registryAccess(), item)?.onUseEffect != null
|
||||
}
|
||||
|
||||
class EatHeldItemFixation(val objective: EatHeldItem) : EntityFixation {
|
||||
var ticksRemaining = 40
|
||||
override fun getDescription(entity: LivingEntity) = "Eating held item"
|
||||
override fun canDistract(distraction: Distraction, entity: LivingEntity) = distraction.isComfort || distraction.isDamage || distraction.isForce
|
||||
override fun tick(entity: LivingEntity, brain: BiggerBrain): Boolean {
|
||||
val world = entity.level()
|
||||
if (entity is PokemonEntity && entity.isAlive) {
|
||||
val itemStack: ItemStack = entity.pokemon.heldItem()
|
||||
val itemConfig = objective.pickupItems.findMatchingEntry(entity.registryAccess(), itemStack)
|
||||
if (objective.canEat(itemStack, entity)) {
|
||||
if (ticksRemaining < 0) {
|
||||
var resultItemStack = itemStack.finishUsingItem(world, entity)
|
||||
val configuredReturnItem = itemConfig?.returnItem
|
||||
resultItemStack = if (configuredReturnItem != ItemStack.EMPTY) ItemStack(BuiltInRegistries.ITEM.get(configuredReturnItem))
|
||||
else if (resultItemStack.item == itemStack.item)
|
||||
ItemStack.EMPTY // Items that aren't normally edible return themselves
|
||||
else
|
||||
resultItemStack
|
||||
entity.pokemon.swapHeldItem(resultItemStack)
|
||||
val onUseEffect = itemConfig?.onUseEffect
|
||||
if (onUseEffect != null) {
|
||||
mainThreadRuntime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
|
||||
mainThreadRuntime.resolve(onUseEffect)
|
||||
}
|
||||
if (itemConfig != null && itemConfig.fullnessValue > 0) {
|
||||
entity.pokemon.feedPokemon(itemConfig.fullnessValue, false)
|
||||
}
|
||||
// Check if the result item is something that should be dropped
|
||||
resultItemStack = entity.pokemon.heldItem()
|
||||
if ((objective.pickupItems.findMatchingEntry(entity.registryAccess(), resultItemStack)?.pickupPriority ?: 0) < 0) {
|
||||
// Drop item
|
||||
resultItemStack = entity.pokemon.swapHeldItem(resultItemStack)
|
||||
if (!resultItemStack.isEmpty && !entity.level().isClientSide) {
|
||||
dropItem(entity, resultItemStack)
|
||||
}
|
||||
}
|
||||
return false
|
||||
} else if ((itemConfig?.fullnessValue ?: 0) > 0 && entity.random.nextFloat() < 0.4f) {
|
||||
ticksRemaining--
|
||||
entity.playSound(entity.getEatingSound(itemStack), 1.0f, 1.0f)
|
||||
world.broadcastEntityEvent(entity, 45.toByte())
|
||||
spawnFoodParticles(entity, itemStack)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun spawnFoodParticles(entity: LivingEntity, itemStack: ItemStack) {
|
||||
val serverLevel = entity.level() as ServerLevel
|
||||
//TODO: Figure out how to this with snowstorm so we can use mouth/face/head locators
|
||||
// The issue as of this writing is we don't have a clean way to utilize the item texture for the particle in snowstorm
|
||||
serverLevel.sendParticles(
|
||||
ItemParticleOption(ParticleTypes.ITEM, itemStack),
|
||||
entity.x, entity.y + 0.5, entity.z, // particle position (centered on entity)
|
||||
5, // count
|
||||
0.1, 0.1, 0.1, // x, y, z offset spread
|
||||
0.05 // speed
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class Config : ObjectiveConfig {
|
||||
var cooldownTicks: ExpressionOrEntityVariable = Either.left("120".asExpression())
|
||||
override fun getVariables(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
) = listOf(
|
||||
cooldownTicks
|
||||
).asVariables()
|
||||
|
||||
override fun create(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
): Objective? {
|
||||
var pickupItems = emptyList<ObtainableItem>()
|
||||
if (entity is PokemonEntity) {
|
||||
pickupItems = entity.config.getObjectList<ObtainableItem>(PICKUP_ITEMS)
|
||||
}
|
||||
val cooldownTicks = cooldownTicks.resolveInt(behaviourConfigurationContext.runtime)
|
||||
return EatHeldItem(
|
||||
cooldownTicks = cooldownTicks,
|
||||
pickupItems = pickupItems
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
-349
@@ -1,349 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.objective
|
||||
|
||||
import com.bedrockk.molang.Expression
|
||||
import com.cobblemon.mod.common.CobblemonItemComponents
|
||||
import com.cobblemon.mod.common.CobblemonItems
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.api.Priority
|
||||
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
|
||||
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
|
||||
import com.cobblemon.mod.common.api.ai.asSimplifiedExpression
|
||||
import com.cobblemon.mod.common.api.ai.asVariables
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.LookingTracker
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.MovementStatus
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.MovementTracker
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.RegularMovementTracker
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.Distraction
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.DistractionType
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.comfortDistraction
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.EntityFixation
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.LockFixation
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.LookAndMoveFixation
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.LookingTrackerFixation
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.MovementTrackerFixation
|
||||
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
|
||||
import com.cobblemon.mod.common.api.ai.resolveInt as resolveVariableInt
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
|
||||
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
|
||||
import com.cobblemon.mod.common.block.entity.NestBlockEntity
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.util.asExpression
|
||||
import com.cobblemon.mod.common.util.mainThreadRuntime
|
||||
import com.cobblemon.mod.common.util.resolveFloat
|
||||
import com.cobblemon.mod.common.util.withQueryValue
|
||||
import com.mojang.datafixers.util.Either
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.server.level.ServerLevel
|
||||
import net.minecraft.world.InteractionHand
|
||||
import net.minecraft.world.entity.Entity
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.ai.memory.WalkTarget
|
||||
import net.minecraft.world.entity.item.ItemEntity
|
||||
import net.minecraft.world.item.ItemStack
|
||||
import net.minecraft.world.phys.Vec3
|
||||
|
||||
class EggRecovery(
|
||||
private val speedMultiplier: Expression = "0.4".asExpression(),
|
||||
private val searchRadius: Int = 20,
|
||||
private val searchIntervalTicks: Int = 20
|
||||
) : Objective {
|
||||
private var fixation: EntityFixation? = null
|
||||
private var searchCooldownTicks = 0
|
||||
|
||||
override fun getDescription(entity: LivingEntity) = "Egg Recovery"
|
||||
|
||||
override fun perform(entity: LivingEntity, brain: BiggerBrain, activeObjectiveTags: MutableSet<String>): ObjectiveStatus {
|
||||
if (entity !is PokemonEntity || !entity.isAlive)
|
||||
return ObjectiveStatus.INVALID
|
||||
// so only wild mons recover their eggs
|
||||
if (!entity.pokemon.isWild())
|
||||
return ObjectiveStatus.INACTIVE
|
||||
|
||||
val world = entity.level() as? ServerLevel ?: return ObjectiveStatus.INVALID
|
||||
// get the nest in memory if it exists and check to see if it has an egg
|
||||
val nestPos = brain.getMemory(CobblemonMemories.PATERNAL_NEST_LOCATION) ?: return ObjectiveStatus.INACTIVE
|
||||
if (brain.getMemory(CobblemonMemories.EGG_IN_NEST) != false)
|
||||
return ObjectiveStatus.INACTIVE
|
||||
// if there is a theif they are probably being chased so nothing to do about recovery yet
|
||||
if (brain.hasMemoryValue(CobblemonMemories.EGG_THIEF))
|
||||
return ObjectiveStatus.INACTIVE
|
||||
|
||||
if (brain.isFixation(fixation))
|
||||
return ObjectiveStatus.FIXATED
|
||||
// if egg is held it needs to go back to the nest
|
||||
if (brain.getMemory(CobblemonMemories.HOLDING_EGG) == true)
|
||||
return returnEgg(entity, brain, world, nestPos)
|
||||
// if we have a known egg position then go get it
|
||||
val eggPos = brain.getMemory(CobblemonMemories.EGG_LOCATION)
|
||||
if (eggPos != null)
|
||||
return fetchEgg(entity, brain, world, eggPos)
|
||||
// if any of the above isnt needed then we probably have searching for the egg left
|
||||
return searchForEgg(entity, brain, world, nestPos, activeObjectiveTags)
|
||||
}
|
||||
|
||||
override fun onRegister(entity: LivingEntity, brain: BiggerBrain) {
|
||||
// start with finding the egg if the location is known when we start.
|
||||
// should we have an if statement above this to check if we should search for the egg first or go to the egg? idk
|
||||
brain.subscribe(Priority.LOW, CobblemonMemories.EGG_LOCATION) { eggPos, _ ->
|
||||
if (eggPos != null && fixation is LookAndMoveFixation && brain.isFixation(fixation)) {
|
||||
brain.distract(Distraction.FORCE)
|
||||
fixation = null
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
// look for the egg a while in an area until giving up
|
||||
private fun searchForEgg(entity: PokemonEntity, brain: BiggerBrain, world: ServerLevel, nestPos: BlockPos, activeObjectiveTags: MutableSet<String>): ObjectiveStatus {
|
||||
activeObjectiveTags.add(Objective.OBJECTIVE_TAG_WANDERING)
|
||||
// if the countdown is up then clear objective
|
||||
if (shouldGiveUpSearch(entity, brain, world, nestPos))
|
||||
return ObjectiveStatus.INACTIVE
|
||||
// if our cooldown is active then tick it down
|
||||
if (searchCooldownTicks > 0) {
|
||||
searchCooldownTicks--
|
||||
return ObjectiveStatus.ACTIVE
|
||||
}
|
||||
|
||||
// set the cooldown so it can begin
|
||||
searchCooldownTicks = searchIntervalTicks
|
||||
|
||||
// choose a random spot around the nest to check for the egg todo should we have it also consider last seen egg position? or reincorporate player disturbances?
|
||||
val searchPos = chooseRandomPos(entity, Vec3.atCenterOf(nestPos)) ?: return ObjectiveStatus.ACTIVE
|
||||
if (!brain.distract(comfortDistraction))
|
||||
return ObjectiveStatus.BLOCKED
|
||||
val tracker = RegularMovementTracker.attempt(entity, WalkTarget(searchPos, mainThreadRuntime.withQueryValue("entity", entity.asMostSpecificMoLangValue()).resolveFloat(speedMultiplier), 1)) ?: return ObjectiveStatus.ACTIVE
|
||||
fixation = brain.fixate(
|
||||
LookAndMoveFixation(
|
||||
distractionTypes = setOf(DistractionType.DAMAGE, DistractionType.FORCE, DistractionType.COMFORT),
|
||||
description = "Egg Recovery: searching for the egg",
|
||||
lookingTracker = LookingTracker.fromNavigationPathOrWalkTarget(entity, tracker.walkTarget),
|
||||
movementTracker = tracker
|
||||
)
|
||||
)
|
||||
return ObjectiveStatus.FIXATED
|
||||
}
|
||||
|
||||
// has the search countdown ended and should the nest be abandoned?
|
||||
private fun shouldGiveUpSearch(entity: PokemonEntity, brain: BiggerBrain, world: ServerLevel, nestPos: BlockPos): Boolean {
|
||||
// if there is an active countdown and it isnt at the give up timeframe then don't wipe memories yet
|
||||
if (brain.getMemory(CobblemonMemories.EGG_SEARCH_COUNTDOWN) != true)
|
||||
return false
|
||||
if (entity.brain.getTimeUntilExpiry(CobblemonMemories.EGG_SEARCH_COUNTDOWN) > GIVE_UP_SEARCH_TIME)
|
||||
return false
|
||||
brain.eraseMemory(CobblemonMemories.EGG_SEARCH_COUNTDOWN)
|
||||
brain.eraseMemory(CobblemonMemories.EGG_LOCATION)
|
||||
brain.eraseMemory(CobblemonMemories.PATERNAL_NEST_LOCATION)
|
||||
brain.eraseMemory(CobblemonMemories.EGG_IN_NEST)
|
||||
brain.eraseMemory(CobblemonMemories.POTENTIAL_MATE)
|
||||
val nest = world.getBlockEntity(nestPos) as? NestBlockEntity
|
||||
// clear the parent info on the nest itself if it is still around
|
||||
if (nest != null) {
|
||||
nest.parents = Pair(null, null)
|
||||
nest.nestClaimedBy = Pair(null, null)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// path to the egg and pick it up
|
||||
private fun fetchEgg(entity: PokemonEntity, brain: BiggerBrain, world: ServerLevel, eggPos: BlockPos): ObjectiveStatus {
|
||||
val eggCenter = Vec3.atCenterOf(eggPos)
|
||||
if (entity.position().distanceTo(eggCenter) <= CLOSE_ENOUGH_DISTANCE) {
|
||||
pickUpEgg(entity, brain, world)
|
||||
return ObjectiveStatus.ACTIVE
|
||||
}
|
||||
if (!brain.distract(comfortDistraction))
|
||||
return ObjectiveStatus.BLOCKED
|
||||
val tracker = RegularMovementTracker.attempt(entity, WalkTarget(eggCenter, mainThreadRuntime.withQueryValue("entity", entity.asMostSpecificMoLangValue()).resolveFloat(speedMultiplier), 1)) ?: return ObjectiveStatus.ACTIVE
|
||||
fixation = brain.fixate(FetchEggFixation(this, tracker, LookingTracker.fromNavigationPathOrWalkTarget(entity, tracker.walkTarget)))
|
||||
return ObjectiveStatus.FIXATED
|
||||
}
|
||||
|
||||
// pickup egg and put it in "hand" of the mon
|
||||
private fun pickUpEgg(entity: PokemonEntity, brain: BiggerBrain, world: ServerLevel): Boolean {
|
||||
// get any egg item entities that are from the parent and within their pickup radius
|
||||
val eggItem = world.getEntitiesOfClass(ItemEntity::class.java, entity.boundingBox.inflate(PICKUP_REACH)) { item ->
|
||||
item.item.item == CobblemonItems.POKEMON_EGG && item.item.get(CobblemonItemComponents.EGG_DATA)?.isParent(entity.pokemon.uuid) == true
|
||||
}.minByOrNull { it.distanceToSqr(entity) }
|
||||
|
||||
if (eggItem == null) {
|
||||
// the egg isn't where it was last seen so clear location memory
|
||||
brain.eraseMemory(CobblemonMemories.EGG_LOCATION)
|
||||
return false
|
||||
}
|
||||
entity.setItemInHand(InteractionHand.MAIN_HAND, eggItem.item.copy())
|
||||
eggItem.remove(Entity.RemovalReason.DISCARDED)
|
||||
brain.setMemory(CobblemonMemories.HOLDING_EGG, true)
|
||||
brain.eraseMemory(CobblemonMemories.EGG_LOCATION)
|
||||
brain.eraseMemory(CobblemonMemories.EGG_SEARCH_COUNTDOWN)
|
||||
return true
|
||||
}
|
||||
|
||||
// carry the egg back to the nest
|
||||
private fun returnEgg(entity: PokemonEntity, brain: BiggerBrain, world: ServerLevel, nestPos: BlockPos): ObjectiveStatus {
|
||||
// if the nest is gone when trying to bring it back we should maybe drop the egg and stop the objective.... todo or should they just hold the egg forever? build a new nest?
|
||||
if (world.getBlockEntity(nestPos) !is NestBlockEntity) {
|
||||
val heldEgg = entity.getItemInHand(InteractionHand.MAIN_HAND)
|
||||
if (!heldEgg.isEmpty)
|
||||
entity.spawnAtLocation(heldEgg.copy())
|
||||
entity.setItemInHand(InteractionHand.MAIN_HAND, ItemStack.EMPTY)
|
||||
brain.eraseMemory(CobblemonMemories.HOLDING_EGG)
|
||||
|
||||
return ObjectiveStatus.INACTIVE
|
||||
}
|
||||
val nestCenter = Vec3.atCenterOf(nestPos)
|
||||
if (entity.position().distanceTo(nestCenter) <= CLOSE_ENOUGH_DISTANCE) {
|
||||
placeEgg(entity, brain, world, nestPos)
|
||||
return ObjectiveStatus.ACTIVE
|
||||
}
|
||||
// make sure only forced distactions stop the parent from bringing the egg back
|
||||
if (!brain.distract(Distraction.FORCE))
|
||||
return ObjectiveStatus.BLOCKED
|
||||
val tracker = RegularMovementTracker.attempt(entity, WalkTarget(nestCenter, mainThreadRuntime.withQueryValue("entity", entity.asMostSpecificMoLangValue()).resolveFloat(speedMultiplier), 1)) ?: return ObjectiveStatus.ACTIVE
|
||||
fixation = brain.fixate(ReturnEggFixation(this, nestPos, tracker, LookingTracker.fromNavigationPathOrWalkTarget(entity, tracker.walkTarget)))
|
||||
return ObjectiveStatus.FIXATED
|
||||
}
|
||||
|
||||
// place egg in the nest
|
||||
private fun placeEgg(entity: PokemonEntity, brain: BiggerBrain, world: ServerLevel, nestPos: BlockPos): Boolean {
|
||||
val nest = world.getBlockEntity(nestPos) as? NestBlockEntity ?: return false
|
||||
val heldEgg = entity.getItemInHand(InteractionHand.MAIN_HAND)
|
||||
|
||||
// if for some reason the parent is not holding the egg anymore
|
||||
if (heldEgg.isEmpty || heldEgg.item != CobblemonItems.POKEMON_EGG) {
|
||||
brain.eraseMemory(CobblemonMemories.HOLDING_EGG)
|
||||
return false
|
||||
}
|
||||
|
||||
// don't place the egg if the nest is not empty
|
||||
if (!nest.getItem().isEmpty)
|
||||
return false
|
||||
|
||||
nest.setItem(heldEgg.copy())
|
||||
entity.setItemInHand(InteractionHand.MAIN_HAND, ItemStack.EMPTY)
|
||||
brain.eraseMemory(CobblemonMemories.HOLDING_EGG)
|
||||
brain.setMemory(CobblemonMemories.EGG_IN_NEST, true)
|
||||
return true
|
||||
}
|
||||
|
||||
private fun chooseRandomPos(entity: PokemonEntity, center: Vec3): Vec3? {
|
||||
// is this bad for performance? At low numbers I assume it won't be too bad but will need to be tested
|
||||
repeat(SEARCH_ATTEMPTS) {
|
||||
val offsetX = entity.random.nextDouble() * 2 * searchRadius - searchRadius
|
||||
val offsetZ = entity.random.nextDouble() * 2 * searchRadius - searchRadius
|
||||
val blockPos = BlockPos.containing(center.add(offsetX, 0.0, offsetZ))
|
||||
if (entity.navigation.createPath(blockPos, 0) != null)
|
||||
return Vec3.atCenterOf(blockPos)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
// fixation for walking to the egg and picking it up
|
||||
class FetchEggFixation(
|
||||
private val objective: EggRecovery,
|
||||
override val movementTracker: MovementTracker,
|
||||
override val lookingTracker: LookingTracker
|
||||
) : EntityFixation, MovementTrackerFixation, LookingTrackerFixation {
|
||||
override fun getDescription(entity: LivingEntity) = "Egg Recovery: going to get egg"
|
||||
|
||||
// if they are hurt or forced they should stop being fixated
|
||||
override fun canDistract(distraction: Distraction, entity: LivingEntity) = distraction.isDamage || distraction.isForce
|
||||
|
||||
// we should stop the moving tracker when distracted (do we need to stop anything else?)
|
||||
override fun onDistract(distraction: Distraction, entity: LivingEntity) {
|
||||
movementTracker.stop()
|
||||
}
|
||||
|
||||
override fun tick(entity: LivingEntity, brain: BiggerBrain): Boolean {
|
||||
if (entity !is PokemonEntity)
|
||||
return false
|
||||
|
||||
val world = entity.level() as? ServerLevel ?: return false
|
||||
|
||||
lookingTracker.tick()
|
||||
when (movementTracker.tick()) {
|
||||
MovementStatus.PENDING -> return true
|
||||
// movement is done (hopefully at the right spot) so try to pickup the egg
|
||||
MovementStatus.COMPLETED -> objective.pickUpEgg(entity, brain, world)
|
||||
// if anything happens try to get a new egg location
|
||||
MovementStatus.FAILED -> brain.eraseMemory(CobblemonMemories.EGG_LOCATION)
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// path back to nest and place the egg
|
||||
class ReturnEggFixation(
|
||||
private val objective: EggRecovery,
|
||||
private val nestPos: BlockPos,
|
||||
override val movementTracker: MovementTracker,
|
||||
override val lookingTracker: LookingTracker
|
||||
) : EntityFixation, MovementTrackerFixation, LookingTrackerFixation, LockFixation {
|
||||
override fun getDescription(entity: LivingEntity) = "Egg Recovery: taking egg back to nest"
|
||||
|
||||
// only force for now but we might want to distract if damaged later
|
||||
override fun canDistract(distraction: Distraction, entity: LivingEntity) = distraction.isForce
|
||||
|
||||
// same as above stop movement if distracted
|
||||
override fun onDistract(distraction: Distraction, entity: LivingEntity) {
|
||||
movementTracker.stop()
|
||||
}
|
||||
|
||||
override fun tick(entity: LivingEntity, brain: BiggerBrain): Boolean {
|
||||
if (entity !is PokemonEntity)
|
||||
return false
|
||||
|
||||
val world = entity.level() as? ServerLevel ?: return false
|
||||
|
||||
lookingTracker.tick()
|
||||
if (movementTracker.tick() == MovementStatus.PENDING)
|
||||
return true
|
||||
if (movementTracker.lastStatus == MovementStatus.COMPLETED)
|
||||
objective.placeEgg(entity, brain, world, nestPos)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
class Config : ObjectiveConfig {
|
||||
val speedMultiplier: ExpressionOrEntityVariable = Either.left("0.4".asExpression())
|
||||
val searchRadius: ExpressionOrEntityVariable = Either.left("20".asExpression())
|
||||
val searchIntervalTicks: ExpressionOrEntityVariable = Either.left("20".asExpression())
|
||||
|
||||
override fun getVariables(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): List<MoLangConfigVariable> {
|
||||
return listOf(speedMultiplier, searchRadius, searchIntervalTicks).asVariables()
|
||||
}
|
||||
|
||||
override fun create(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): Objective {
|
||||
behaviourConfigurationContext.addMemories(
|
||||
CobblemonMemories.PATERNAL_NEST_LOCATION,
|
||||
CobblemonMemories.EGG_IN_NEST,
|
||||
CobblemonMemories.EGG_LOCATION,
|
||||
CobblemonMemories.EGG_THIEF,
|
||||
CobblemonMemories.EGG_SEARCH_COUNTDOWN,
|
||||
CobblemonMemories.HOLDING_EGG,
|
||||
CobblemonMemories.POTENTIAL_MATE
|
||||
)
|
||||
return EggRecovery(
|
||||
speedMultiplier = speedMultiplier.asSimplifiedExpression(entity),
|
||||
searchRadius = searchRadius.resolveVariableInt(behaviourConfigurationContext.runtime),
|
||||
searchIntervalTicks = searchIntervalTicks.resolveVariableInt(behaviourConfigurationContext.runtime)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val CLOSE_ENOUGH_DISTANCE = 1.5 // should this be bigger for the bigger mons or be based off of hitbox?
|
||||
private const val PICKUP_REACH = 1.5 // slight radius around the mon so they don't have to physically be on top of the egg
|
||||
private const val SEARCH_ATTEMPTS = 10
|
||||
private const val GIVE_UP_SEARCH_TIME = 20L // is this enough time for search? Needs playtesting
|
||||
}
|
||||
}
|
||||
-88
@@ -1,88 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.objective
|
||||
|
||||
import com.bedrockk.molang.Expression
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
|
||||
import com.cobblemon.mod.common.api.pokemon.status.Statuses
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.pokemon.ai.moves.RealTimeMove
|
||||
import com.cobblemon.mod.common.util.asExpression
|
||||
import com.cobblemon.mod.common.util.mainThreadRuntime
|
||||
import com.cobblemon.mod.common.util.resolveBoolean
|
||||
import com.cobblemon.mod.common.util.resolveFloat
|
||||
import com.cobblemon.mod.common.util.resolveString
|
||||
import com.cobblemon.mod.common.util.withQueryValue
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.ai.memory.MemoryModuleType
|
||||
import net.minecraft.world.entity.ai.memory.NearestVisibleLivingEntities
|
||||
|
||||
|
||||
abstract class EntityResponseObjective(
|
||||
val speedMultiplier: Expression = "0.5".asExpression(), // How do we transfer speed to the attack behaviour? Do we want to?
|
||||
val engageRange: Expression = "15.0".asExpression(),
|
||||
val disengageRange: Expression = "20.0".asExpression(),
|
||||
val condition: Expression = "true".asExpression(), // Self/environment condition check
|
||||
val targetCondition: Expression = "true".asExpression(), // target condition (will be checked on entity interaction)
|
||||
description: Expression = "'EntityResponseObjective'".asExpression(),
|
||||
) : Objective {
|
||||
var trackedEntity: LivingEntity? = null
|
||||
private val objectiveDescription: String = mainThreadRuntime.resolveString(description)
|
||||
|
||||
override fun getDescription(entity: LivingEntity) = objectiveDescription
|
||||
|
||||
|
||||
open fun scanForValidTargets(entity: LivingEntity, brain: BiggerBrain): LivingEntity? {
|
||||
|
||||
//TODO: need to apply some sort of awareness modifier
|
||||
// Sleep obviously should dramatically affect this but it's not hard to imagine other things that could do this
|
||||
if (entity.isSleeping || (entity is PokemonEntity && entity.pokemon.status?.status == Statuses.SLEEP)) {
|
||||
// Also why doesn't entity.isSleeping for PokemonEntities return true if the sleep status is applied?
|
||||
return null
|
||||
}
|
||||
|
||||
// Check if we're in the mood to fight
|
||||
if (mainThreadRuntime.withQueryValue("entity", (entity).asMostSpecificMoLangValue())
|
||||
.resolveBoolean(condition)
|
||||
) {
|
||||
// Scan visible livingEntities
|
||||
val entities: NearestVisibleLivingEntities =
|
||||
brain.getMemory(MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES) ?: return null
|
||||
val targetEntity = entities.findClosest { it ->
|
||||
val runtime = mainThreadRuntime.withQueryValue("target", (it).asMostSpecificMoLangValue())
|
||||
val isValidTarget =
|
||||
runtime.resolveBoolean(targetCondition) && runtime.resolveFloat(engageRange) > entity.distanceTo(it)
|
||||
isValidTarget
|
||||
}.orElse(null)
|
||||
if (targetEntity != null) return targetEntity
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
open fun shouldContinue(entity: LivingEntity, brain: BiggerBrain): Boolean {
|
||||
trackedEntity ?: return false
|
||||
if (trackedEntity!!.isDeadOrDying) return false
|
||||
val runtime = mainThreadRuntime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
|
||||
.withQueryValue("target", (trackedEntity!!).asMostSpecificMoLangValue())
|
||||
return (runtime.resolveBoolean(condition) // self condition
|
||||
&& runtime.resolveBoolean(targetCondition) // target condition
|
||||
&& (runtime.resolveFloat(disengageRange) > entity.distanceTo(trackedEntity!!)))
|
||||
}
|
||||
|
||||
open fun getRealTimeMoves(entity: LivingEntity) : List<RealTimeMove> {
|
||||
var moves = if (entity is PokemonEntity) entity.pokemon.species.behaviour.combat.moves else emptyList()
|
||||
if (moves.isEmpty() && entity is PokemonEntity) {
|
||||
moves = listOf(
|
||||
if (entity.pokemon.attack > entity.pokemon.specialAttack) RealTimeMove.defaultMelee else RealTimeMove.defaultRange
|
||||
)
|
||||
}
|
||||
return moves
|
||||
}
|
||||
}
|
||||
-250
@@ -1,250 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.objective
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.api.Priority
|
||||
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
|
||||
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
|
||||
import com.cobblemon.mod.common.api.ai.asVariables
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.EntityFixation
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.LookingTracker
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.Distraction
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.DistractionType
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.HurtDistraction
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.LockFixation
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.LookingTrackerFixation
|
||||
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
|
||||
import com.cobblemon.mod.common.api.ai.resolveBoolean
|
||||
import com.cobblemon.mod.common.api.ai.resolveInt
|
||||
import com.cobblemon.mod.common.api.scheduling.afterOnServer
|
||||
import com.cobblemon.mod.common.battles.BattleRegistry
|
||||
import com.cobblemon.mod.common.entity.npc.NPCEntity
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonBehaviourFlag
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.util.asExpression
|
||||
import com.mojang.datafixers.util.Either
|
||||
import net.minecraft.server.level.ServerLevel
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.ai.behavior.EntityTracker
|
||||
import net.minecraft.world.entity.ai.memory.MemoryModuleType
|
||||
|
||||
/**
|
||||
* An objective that ensures the entity, whether it's a PokemonEntity or an NPCEntity, will pay attention to its
|
||||
* opponents in battle. They will not move, and will look at its opponents. For Pokémon, they will focus on the
|
||||
* targeted opponent if one exists; otherwise, they will randomly look at one of their opponent Pokémon. For NPCs,
|
||||
* they will randomly look at one of their opponent Pokémon.
|
||||
*
|
||||
* If the entity gets hurt and [stopBattleIfHurt] is true, this objective stops the battle.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since December 29th, 2025
|
||||
*/
|
||||
class FocusDuringBattle(
|
||||
var stopBattleIfHurt: Boolean = true,
|
||||
var minDurationTicks: Int = 40,
|
||||
var maxDurationTicks: Int = 80
|
||||
) : Objective {
|
||||
var fixation: EntityFixation? = null
|
||||
|
||||
override fun getDescription(entity: LivingEntity) = "Look at battle opponents"
|
||||
override fun perform(entity: LivingEntity, brain: BiggerBrain, activeObjectiveTags: MutableSet<String>): ObjectiveStatus {
|
||||
if (entity !is NPCEntity && entity !is PokemonEntity) {
|
||||
return ObjectiveStatus.INVALID
|
||||
}
|
||||
|
||||
if (stopBattleIfHurt && brain.hasMemoryValue(MemoryModuleType.HURT_BY)) {
|
||||
if (brain.isFixation(fixation)) {
|
||||
if (entity is PokemonEntity) {
|
||||
entity.battle?.stop()
|
||||
} else if (entity is NPCEntity) {
|
||||
entity.battleIds.mapNotNull { BattleRegistry.getBattle(it) }.forEach { it.stop() }
|
||||
}
|
||||
brain.distract(Distraction.FORCE)
|
||||
}
|
||||
return ObjectiveStatus.INACTIVE
|
||||
}
|
||||
|
||||
val isInBattle = isInBattle(entity)
|
||||
if (brain.isFixation(fixation)) {
|
||||
if (isInBattle) {
|
||||
// Ok this is fine
|
||||
return ObjectiveStatus.FIXATED
|
||||
} else {
|
||||
// Alright my work here is done, going back to inactive.
|
||||
fixation = null
|
||||
brain.fixation = null
|
||||
return ObjectiveStatus.INACTIVE
|
||||
}
|
||||
} else if (!isInBattle) {
|
||||
// Ok we don't need to do anything
|
||||
fixation = null
|
||||
return ObjectiveStatus.INACTIVE
|
||||
} else {
|
||||
// We need to start focusing
|
||||
if (brain.distract(Distraction.FORCE)) {
|
||||
fixation = if (entity is NPCEntity) NPCFocusFixation(this) else PokemonFocusFixation(this)
|
||||
// Handle kick starting flight for flying pokemon over water
|
||||
if (entity is PokemonEntity && entity.exposedForm.behaviour.moving.fly.canFly) {
|
||||
if (entity.ticksLived >= 0 && entity.navigation.isAirborne(entity.level(), entity.blockPosition())) {
|
||||
// Let flyers fly in battle if they're in the air
|
||||
entity.setBehaviourFlag(PokemonBehaviourFlag.FLYING, true)
|
||||
afterOnServer(0.1F) {
|
||||
entity.navigation.moveTo(entity.x, entity.y + 0.2, entity.z, 1.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
brain.fixation = fixation
|
||||
return ObjectiveStatus.FIXATED
|
||||
} else {
|
||||
return ObjectiveStatus.BLOCKED
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onRegister(entity: LivingEntity, brain: BiggerBrain) {
|
||||
brain.subscribe(
|
||||
priority = Priority.LOW,
|
||||
memory = MemoryModuleType.HURT_BY
|
||||
) { value, labels ->
|
||||
if (brain.isFixation(fixation) && value != null && stopBattleIfHurt) {
|
||||
brain.distract(HurtDistraction(value))
|
||||
}
|
||||
return@subscribe true
|
||||
}
|
||||
}
|
||||
|
||||
fun isInBattle(entity: LivingEntity): Boolean {
|
||||
return when (entity) {
|
||||
is PokemonEntity -> entity.isBattling
|
||||
is NPCEntity -> entity.isInBattle()
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
class NPCFocusFixation(val objective: FocusDuringBattle) : EntityFixation, LookingTrackerFixation {
|
||||
override var lookingTracker: LookingTracker? = null
|
||||
var ticksUntilReacquireTarget: Int = 0
|
||||
|
||||
override fun getDescription(entity: LivingEntity): String {
|
||||
return if (lookingTracker == null) {
|
||||
"Looking for an opponent Pokémon to look at"
|
||||
} else {
|
||||
"Looking at targeted or opponent Pokémon"
|
||||
}
|
||||
}
|
||||
|
||||
override fun canDistract(distraction: Distraction, entity: LivingEntity) = distraction.isForce
|
||||
|
||||
fun acquireLookTarget(entity: NPCEntity, brain: BiggerBrain) {
|
||||
val battlingPokemon = brain.getMemory(CobblemonMemories.BATTLING_POKEMON) ?: return
|
||||
val randomBattlingPokemon = battlingPokemon.randomOrNull()?.let { (entity.level() as ServerLevel).getEntity(it) }
|
||||
if (randomBattlingPokemon != null) {
|
||||
lookingTracker = LookingTracker(entity, EntityTracker(randomBattlingPokemon, true))
|
||||
}
|
||||
}
|
||||
|
||||
override fun tick(entity: LivingEntity, brain: BiggerBrain): Boolean {
|
||||
if (entity !is NPCEntity) {
|
||||
return false
|
||||
}
|
||||
|
||||
val battlingPokemon = brain.getMemory(CobblemonMemories.BATTLING_POKEMON) ?: return false
|
||||
|
||||
val lookingTracker = lookingTracker
|
||||
if (lookingTracker != null && lookingTracker.entity.uuid !in battlingPokemon) {
|
||||
this.lookingTracker = null
|
||||
ticksUntilReacquireTarget = 0
|
||||
}
|
||||
|
||||
if (ticksUntilReacquireTarget <= 0) {
|
||||
acquireLookTarget(entity, brain)
|
||||
ticksUntilReacquireTarget = (objective.minDurationTicks..objective.maxDurationTicks).random()
|
||||
} else {
|
||||
ticksUntilReacquireTarget--
|
||||
}
|
||||
|
||||
lookingTracker?.tick()
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
class PokemonFocusFixation(val objective: FocusDuringBattle) : EntityFixation, LookingTrackerFixation, LockFixation {
|
||||
var ticksUntilReacquireTarget: Int = 0
|
||||
override var lookingTracker: LookingTracker? = null
|
||||
|
||||
override fun getDescription(entity: LivingEntity): String {
|
||||
return if (lookingTracker == null) {
|
||||
"Looking for an opponent Pokémon to look at"
|
||||
} else {
|
||||
"Looking at targeted or opponent Pokémon"
|
||||
}
|
||||
}
|
||||
|
||||
override fun canDistract(distraction: Distraction, entity: LivingEntity) = distraction.isForce
|
||||
|
||||
override fun tick(entity: LivingEntity, brain: BiggerBrain): Boolean {
|
||||
if (entity !is PokemonEntity) {
|
||||
return false
|
||||
}
|
||||
|
||||
val battlingPokemon = brain.getMemory(CobblemonMemories.BATTLING_POKEMON) ?: return false
|
||||
val targetedBattlePokemon = brain.getMemory(CobblemonMemories.TARGETED_BATTLE_POKEMON)
|
||||
|
||||
val lookingTracker = lookingTracker
|
||||
// If there is a targetedBattlePokemon then that is the highest priority, and we should not look away
|
||||
// Otherwise, look at one of the entities for a while and occasionally reacquire the target.
|
||||
if (targetedBattlePokemon != null && (lookingTracker == null || lookingTracker.entity.uuid != targetedBattlePokemon)) {
|
||||
// We should be targeting the targetedBattlePokemon
|
||||
val targetedPokemon = (entity.level() as ServerLevel).getEntity(targetedBattlePokemon) ?: return false
|
||||
this.lookingTracker = LookingTracker(entity, EntityTracker(targetedPokemon, true))
|
||||
ticksUntilReacquireTarget = (objective.minDurationTicks..objective.maxDurationTicks).random()
|
||||
} else if (targetedBattlePokemon == null && ticksUntilReacquireTarget <= 0) {
|
||||
// Choose someone to look at
|
||||
val randomBattlingPokemon = battlingPokemon.randomOrNull()?.let { (entity.level() as ServerLevel).getEntity(it) }
|
||||
if (randomBattlingPokemon != null) {
|
||||
this.lookingTracker = LookingTracker(entity, EntityTracker(randomBattlingPokemon, true))
|
||||
ticksUntilReacquireTarget = (objective.minDurationTicks..objective.maxDurationTicks).random()
|
||||
}
|
||||
}
|
||||
|
||||
ticksUntilReacquireTarget--
|
||||
this.lookingTracker?.tick()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
class Config : ObjectiveConfig {
|
||||
var stopBattleIfHurt: ExpressionOrEntityVariable = Either.left("true".asExpression())
|
||||
var minDurationTicks: ExpressionOrEntityVariable = Either.left("40".asExpression())
|
||||
var maxDurationTicks: ExpressionOrEntityVariable = Either.left("80".asExpression())
|
||||
|
||||
override fun getVariables(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
) = listOf(
|
||||
stopBattleIfHurt,
|
||||
minDurationTicks,
|
||||
maxDurationTicks
|
||||
).asVariables()
|
||||
|
||||
override fun create(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): Objective {
|
||||
val stopBattleIfHurt = stopBattleIfHurt.resolveBoolean(behaviourConfigurationContext.runtime)
|
||||
val minDurationTicks = minDurationTicks.resolveInt(behaviourConfigurationContext.runtime)
|
||||
val maxDurationTicks = maxDurationTicks.resolveInt(behaviourConfigurationContext.runtime)
|
||||
return FocusDuringBattle(
|
||||
stopBattleIfHurt = stopBattleIfHurt,
|
||||
minDurationTicks = minDurationTicks,
|
||||
maxDurationTicks = maxDurationTicks
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
-258
@@ -1,258 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.objective
|
||||
|
||||
import com.bedrockk.molang.Expression
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.api.Priority
|
||||
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
|
||||
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
|
||||
import com.cobblemon.mod.common.api.ai.asSimplifiedExpression
|
||||
import com.cobblemon.mod.common.api.ai.asVariables
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.*
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.Distraction
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.DistractionType
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.comfortDistraction
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.idleBoredomDistraction
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.EntityFixation
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.LookAndMoveFixation
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.LookingTrackerFixation
|
||||
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
|
||||
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.util.*
|
||||
import com.mojang.datafixers.util.Either
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.PathfinderMob
|
||||
import net.minecraft.world.entity.ai.behavior.EntityTracker
|
||||
import net.minecraft.world.entity.ai.memory.MemoryModuleType
|
||||
import net.minecraft.world.entity.ai.memory.NearestVisibleLivingEntities
|
||||
import net.minecraft.world.entity.ai.memory.WalkTarget
|
||||
|
||||
|
||||
class FriendlyBehaviour(
|
||||
speedMultiplier: Expression = "0.5".asExpression(), // How do we transfer speed to the attack behaviour? Do we want to?
|
||||
engageRange: Expression = "10.0".asExpression(),
|
||||
disengageRange: Expression = "20.0".asExpression(),
|
||||
condition: Expression = "true".asExpression(),
|
||||
targetCondition: Expression = "false".asExpression(),
|
||||
private val maxDuration: Expression = "300".asExpression(),
|
||||
private val description: Expression = "Friendly Behaviour".asExpression(),
|
||||
) : EntityResponseObjective(
|
||||
speedMultiplier = speedMultiplier, // How do we transfer speed to the attack behaviour? Do we want to?
|
||||
engageRange = engageRange,
|
||||
disengageRange = disengageRange,
|
||||
condition = condition, // Self/environment condition check
|
||||
targetCondition = targetCondition, // target condition (will be checked on entity interaction)
|
||||
description = description,
|
||||
) {
|
||||
private var remainingCooldownTicks = 0
|
||||
private var fixation: EntityFixation? = null
|
||||
override val observedMemories = listOf(MemoryModuleType.HURT_BY_ENTITY)
|
||||
private val closeEnoughDistance = 1.5
|
||||
private var remainingTicks = 0
|
||||
|
||||
|
||||
override fun perform(
|
||||
entity: LivingEntity,
|
||||
brain: BiggerBrain,
|
||||
activeObjectiveTags: MutableSet<String>
|
||||
): ObjectiveStatus {
|
||||
if (entity !is PokemonEntity || !entity.isAlive) return ObjectiveStatus.INVALID
|
||||
|
||||
if (remainingCooldownTicks > 0) {
|
||||
remainingCooldownTicks--
|
||||
return if (brain.isFixation(fixation)) ObjectiveStatus.FIXATED else ObjectiveStatus.INACTIVE
|
||||
} else {
|
||||
remainingCooldownTicks = 5
|
||||
}
|
||||
|
||||
if (brain.isFixation(fixation)) {
|
||||
if (remainingTicks > 0) {
|
||||
remainingTicks--
|
||||
} else {
|
||||
// Got bored, move on
|
||||
brain.distract(Distraction.FORCE)
|
||||
trackedEntity = null
|
||||
fixation = null
|
||||
}
|
||||
if (trackedEntity != null) {
|
||||
// Check if we continue
|
||||
return attemptFixation(entity, brain)
|
||||
|
||||
} else {
|
||||
trackedEntity = null
|
||||
fixation = null
|
||||
brain.distract(comfortDistraction)
|
||||
}
|
||||
return ObjectiveStatus.FIXATED
|
||||
} else {
|
||||
// Scan visible livingEntities
|
||||
val targetEntity = scanForValidTargets(entity, brain)
|
||||
|
||||
if (targetEntity != null) {
|
||||
trackedEntity = targetEntity
|
||||
val status = attemptFixation(entity, brain)
|
||||
if (status == ObjectiveStatus.FIXATED) {
|
||||
remainingTicks = mainThreadRuntime.resolveInt(maxDuration)
|
||||
}
|
||||
return status
|
||||
}
|
||||
|
||||
return if (trackedEntity != null) ObjectiveStatus.ACTIVE else ObjectiveStatus.INACTIVE
|
||||
}
|
||||
}
|
||||
|
||||
fun attemptFixation(entity: PathfinderMob, brain: BiggerBrain) : ObjectiveStatus {
|
||||
if (trackedEntity != null) {
|
||||
// Check if we continue
|
||||
val runtime = mainThreadRuntime.withQueryValue("entity", (entity).asMostSpecificMoLangValue())
|
||||
.withQueryValue("target", trackedEntity!!.asMostSpecificMoLangValue())
|
||||
val distance = entity.distanceTo(trackedEntity!!)
|
||||
if (runtime.resolveFloat(disengageRange) < distance || !shouldContinue(entity, brain)) {
|
||||
brain.distract(comfortDistraction)
|
||||
trackedEntity = null
|
||||
fixation = null
|
||||
return ObjectiveStatus.INACTIVE
|
||||
} else if (distance <= closeEnoughDistance && !(brain.isFixation(fixation) && fixation is IdleLookFixation)) {
|
||||
// transition to look
|
||||
val distraction = if(brain.isFixation(fixation)) Distraction.FORCE else idleBoredomDistraction
|
||||
if (brain.distract(distraction)) {
|
||||
this.fixation = brain.fixate(
|
||||
IdleLookFixation(
|
||||
LookingTracker(
|
||||
entity,
|
||||
EntityTracker(trackedEntity!!, true)
|
||||
),
|
||||
remainingTicks
|
||||
)
|
||||
)
|
||||
return ObjectiveStatus.FIXATED
|
||||
} else {
|
||||
// could not distract
|
||||
return ObjectiveStatus.BLOCKED
|
||||
}
|
||||
} else if (distance > closeEnoughDistance && !(brain.isFixation(fixation) && fixation is LookAndMoveFixation)) {
|
||||
// transition to walk
|
||||
val distraction = if(brain.isFixation(fixation)) Distraction.FORCE else idleBoredomDistraction
|
||||
if (brain.distract(distraction)) {
|
||||
val walkTarget =
|
||||
WalkTarget(EntityTracker(trackedEntity!!, true), runtime.resolveFloat(speedMultiplier), 0)
|
||||
val movementTracker = RegularMovementTracker.attempt(entity, walkTarget)
|
||||
?: return ObjectiveStatus.INACTIVE
|
||||
this.fixation = brain.fixate(
|
||||
LookAndMoveFixation(
|
||||
description = "Following targeted entity",
|
||||
distractionTypes = setOf(
|
||||
DistractionType.COMFORT,
|
||||
DistractionType.DAMAGE,
|
||||
DistractionType.FORCE
|
||||
),
|
||||
lookingTracker = LookingTracker.forTargetEntity(entity, trackedEntity!!),
|
||||
movementTracker = movementTracker,
|
||||
onMovementComplete = { _, _ -> attemptFixation(entity, brain) } // maintains fixation between moving and looking
|
||||
)
|
||||
)
|
||||
return ObjectiveStatus.FIXATED
|
||||
} else {
|
||||
return ObjectiveStatus.BLOCKED
|
||||
}
|
||||
|
||||
} else {
|
||||
return ObjectiveStatus.FIXATED
|
||||
}
|
||||
}
|
||||
return ObjectiveStatus.INACTIVE
|
||||
}
|
||||
|
||||
class IdleLookFixation(
|
||||
override val lookingTracker: LookingTracker?,
|
||||
var remainingTicks: Int,
|
||||
) : EntityFixation, LookingTrackerFixation {
|
||||
|
||||
|
||||
override fun getDescription(entity: LivingEntity): String {
|
||||
return "Looking at a nearby entity ($remainingTicks ticks left)"
|
||||
}
|
||||
|
||||
override fun canDistract(distraction: Distraction, entity: LivingEntity): Boolean {
|
||||
return !distraction.isIdleBoredom
|
||||
}
|
||||
|
||||
override fun tick(entity: LivingEntity, brain: BiggerBrain): Boolean {
|
||||
val targetEntity = lookingTracker?.entity ?: return false
|
||||
if ((targetEntity.isRemoved) || entity !is PathfinderMob) {
|
||||
return false
|
||||
}
|
||||
val lookingTracker = lookingTracker
|
||||
remainingTicks--
|
||||
lookingTracker.tick()
|
||||
return remainingTicks > 0
|
||||
}
|
||||
}
|
||||
|
||||
override fun onRegister(entity: LivingEntity, brain: BiggerBrain) {
|
||||
brain.subscribe(
|
||||
priority = Priority.LOW,
|
||||
memory = CobblemonMemories.ATTACK_TARGET_DATA
|
||||
) { value, labels ->
|
||||
if (value != null) {
|
||||
val livingEntity = brain.getMemory(MemoryModuleType.ATTACK_TARGET) ?: return@subscribe true
|
||||
if (trackedEntity?.uuid == livingEntity.uuid) {
|
||||
// We smacked them, refresh our attack timer
|
||||
val runtime = mainThreadRuntime.withQueryValue("entity", (livingEntity).asMostSpecificMoLangValue())
|
||||
val attackDuration = runtime.resolveInt(maxDuration)
|
||||
brain.setMemory(MemoryModuleType.ATTACK_TARGET, livingEntity, attackDuration.toLong())
|
||||
}
|
||||
}
|
||||
return@subscribe true
|
||||
}
|
||||
}
|
||||
|
||||
class Config : ObjectiveConfig {
|
||||
val speedMultiplier: ExpressionOrEntityVariable = Either.left("0.5".asExpression())
|
||||
val engageRange: ExpressionOrEntityVariable = Either.left("10.0".asExpression())
|
||||
val disengageRange: ExpressionOrEntityVariable = Either.left("20.0".asExpression())
|
||||
val condition: ExpressionOrEntityVariable = Either.left("true".asExpression())
|
||||
val targetCondition: ExpressionOrEntityVariable = Either.left("true".asExpression())
|
||||
val maxDuration: ExpressionOrEntityVariable = Either.left("300".asExpression())
|
||||
val description: ExpressionOrEntityVariable = Either.left("Test".asExpression())
|
||||
|
||||
override fun getVariables(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
): List<MoLangConfigVariable> {
|
||||
return listOf(
|
||||
condition,
|
||||
targetCondition,
|
||||
speedMultiplier,
|
||||
engageRange,
|
||||
disengageRange,
|
||||
maxDuration,
|
||||
description,
|
||||
).asVariables()
|
||||
}
|
||||
|
||||
override fun create(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
): Objective {
|
||||
return FriendlyBehaviour(
|
||||
condition = condition.asSimplifiedExpression(entity),
|
||||
targetCondition = targetCondition.asSimplifiedExpression(entity),
|
||||
speedMultiplier = speedMultiplier.asSimplifiedExpression(entity),
|
||||
engageRange = engageRange.asSimplifiedExpression(entity),
|
||||
disengageRange = disengageRange.asSimplifiedExpression(entity),
|
||||
maxDuration = maxDuration.asSimplifiedExpression(entity),
|
||||
description = description.asSimplifiedExpression(entity)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
-508
@@ -1,508 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.objective
|
||||
|
||||
import com.bedrockk.molang.Expression
|
||||
import com.cobblemon.mod.common.CobblemonBlocks
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
|
||||
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
|
||||
import com.cobblemon.mod.common.api.ai.asSimplifiedExpression
|
||||
import com.cobblemon.mod.common.api.ai.asVariables
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.LookingTracker
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.RegularMovementTracker
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.Distraction
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.DistractionType
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.comfortDistraction
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.idleBoredomDistraction
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.EntityFixation
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.LookAndMoveFixation
|
||||
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
|
||||
import com.cobblemon.mod.common.api.ai.config.task.PollinateFlowerTaskConfig
|
||||
import com.cobblemon.mod.common.api.ai.resolveInt
|
||||
import com.cobblemon.mod.common.block.SaccharineLeafBlock
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.util.asExpression
|
||||
import com.cobblemon.mod.common.util.distanceTo
|
||||
import com.cobblemon.mod.common.util.getMemorySafely
|
||||
import com.cobblemon.mod.common.util.mainThreadRuntime
|
||||
import com.cobblemon.mod.common.util.resolveFloat
|
||||
import com.mojang.datafixers.util.Either
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.sounds.SoundEvents
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.PathfinderMob
|
||||
import net.minecraft.world.entity.ai.memory.MemoryStatus
|
||||
import net.minecraft.world.entity.ai.memory.WalkTarget
|
||||
import net.minecraft.world.level.block.BeehiveBlock
|
||||
import net.minecraft.world.level.block.entity.BeehiveBlockEntity
|
||||
import net.minecraft.world.phys.Vec3
|
||||
|
||||
/**
|
||||
* Mimics the vanilla bee behavior, with added interactions with Saccharine leaves.
|
||||
* Only wild bees are allowed to enter hives because of dupe risks with pasture blocks.
|
||||
*
|
||||
* @author JazzMcNade
|
||||
* @since February 23th, 2026
|
||||
*/
|
||||
class GatherNectar(
|
||||
val ticksBetweenAttempts: Int = 60,
|
||||
val maxDistance: Expression = "14.0".asExpression(),
|
||||
val speedMultiplier: Expression = "0.2".asExpression()
|
||||
) : Objective {
|
||||
var workerFixation: EntityFixation? = null
|
||||
var shelterFixation: EntityFixation? = null
|
||||
var remainingCooldownTicks = ticksBetweenAttempts
|
||||
|
||||
override fun getDescription(entity: LivingEntity) = "Worker Bee"
|
||||
override fun perform(
|
||||
entity: LivingEntity,
|
||||
brain: BiggerBrain,
|
||||
activeObjectiveTags: MutableSet<String>
|
||||
): ObjectiveStatus {
|
||||
if (entity !is PokemonEntity) {
|
||||
return ObjectiveStatus.INVALID
|
||||
} else if (brain.isFixation(shelterFixation)) {
|
||||
// Never preempt the shelter fixation
|
||||
remainingCooldownTicks = ticksBetweenAttempts
|
||||
return ObjectiveStatus.FIXATED
|
||||
} else if (!entity.biggerBrain.hasMemoryValue(CobblemonMemories.HIVE_LOCATION) && !entity.biggerBrain.hasMemoryValue(CobblemonMemories.NEARBY_SACC_LEAVES)) {
|
||||
return ObjectiveStatus.INACTIVE
|
||||
}
|
||||
|
||||
val world = entity.level()
|
||||
|
||||
if ((world.isRaining || world.isNight) ) {
|
||||
if (!brain.distract(comfortDistraction)) {
|
||||
return ObjectiveStatus.BLOCKED
|
||||
}
|
||||
// Return to hive or do nothing
|
||||
if (entity.pokemon.isWild()) {
|
||||
// Only wilds can path to hives
|
||||
val hivePos = entity.biggerBrain.getMemory(CobblemonMemories.HIVE_LOCATION)
|
||||
if (hivePos != null && wantsToEnterHive(entity)) {
|
||||
// Deliver to hive
|
||||
if (!tryEnterHive(entity)) { // Attempt to enter the hive, if we fail, probably too far away
|
||||
val hiveEntrance = getHiveEntrance(hivePos, entity)
|
||||
if (hiveEntrance != null) {
|
||||
// Move to hive
|
||||
shelterFixation = tryMovementFixation(entity, hiveEntrance, "Sheltering in Hive", ::onHiveMovementComplete)
|
||||
} else {
|
||||
// Blacklist the hive
|
||||
dropAndBlackListHive(entity)
|
||||
}
|
||||
|
||||
} // no else the entity is gone and is stored in the hive
|
||||
}
|
||||
} else {
|
||||
return ObjectiveStatus.INACTIVE
|
||||
}
|
||||
|
||||
} else if (brain.isFixation(workerFixation)) {
|
||||
// Already fixated on the work
|
||||
remainingCooldownTicks = ticksBetweenAttempts
|
||||
return ObjectiveStatus.FIXATED
|
||||
} else {
|
||||
// Perform nectar gathering/delivery
|
||||
if (!brain.distract(idleBoredomDistraction)) {
|
||||
return ObjectiveStatus.BLOCKED
|
||||
}
|
||||
if (entity.biggerBrain.hasMemoryValue(CobblemonMemories.HAS_NECTAR) ) {
|
||||
// Deliver nectar to hive or Sacc leaves
|
||||
val hivePos = entity.biggerBrain.getMemory(CobblemonMemories.HIVE_LOCATION)
|
||||
if (hivePos != null && wantsToEnterHive(entity) && entity.pokemon.isWild()) {
|
||||
// Deliver to hive
|
||||
if (!tryEnterHive(entity)) { // Attempt to enter the hive, if we fail, probably too far away
|
||||
val hiveEntrance = getHiveEntrance(hivePos, entity)
|
||||
if (hiveEntrance != null) {
|
||||
// Move to hive
|
||||
workerFixation = tryMovementFixation(entity, hiveEntrance, "Delivering nectar to hive", ::onHiveMovementComplete)
|
||||
} else {
|
||||
// Blacklist the hive
|
||||
dropAndBlackListHive(entity)
|
||||
}
|
||||
|
||||
} // no else the entity is gone and is stored in the hive
|
||||
} else {
|
||||
// Deliver to Saccharine Leaves
|
||||
val sacLeafPos : BlockPos = entity.biggerBrain.getMemory(CobblemonMemories.NEARBY_SACC_LEAVES)
|
||||
?: return ObjectiveStatus.INACTIVE
|
||||
if (entity.blockPosition() != sacLeafPos) {
|
||||
workerFixation = tryMovementFixation(entity, sacLeafPos, "Delivering nectar to Saccharine Leaves")
|
||||
return ObjectiveStatus.ACTIVE
|
||||
} else {
|
||||
// Do nectar deposit fixation
|
||||
workerFixation = brain.fixate(DepositNectarInSaccharineLeavesFixation())
|
||||
return ObjectiveStatus.ACTIVE
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
// Gather Nectar
|
||||
val flowerPos =
|
||||
entity.biggerBrain.getMemory(CobblemonMemories.NEARBY_FLOWER) ?: return ObjectiveStatus.INACTIVE
|
||||
val distance = entity.distanceTo(flowerPos)
|
||||
val maxDistance = mainThreadRuntime.resolveFloat(maxDistance)
|
||||
if (distance < 1.0) {
|
||||
// Pollinate/Gather nectar from the flower
|
||||
if (workerFixation !is PollinateFlowerFixation && brain.distract(idleBoredomDistraction)) {
|
||||
workerFixation = brain.fixate(PollinateFlowerFixation())
|
||||
return ObjectiveStatus.FIXATED
|
||||
}
|
||||
|
||||
} else if (distance < maxDistance) {
|
||||
// Move to the flower
|
||||
if (remainingCooldownTicks <= 0 && brain.distract(idleBoredomDistraction) && !brain.hasMemoryValue(
|
||||
CobblemonMemories.PATH_TO_NEARBY_FLOWER_COOLDOWN
|
||||
)
|
||||
) {
|
||||
remainingCooldownTicks = ticksBetweenAttempts
|
||||
workerFixation = tryMovementFixation(entity, flowerPos, "Moving to flower")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (remainingCooldownTicks > 0) {
|
||||
remainingCooldownTicks--
|
||||
}
|
||||
|
||||
return ObjectiveStatus.ACTIVE
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* This Fixation has movement, but it doesn't utilize pathfinding, and it's more about flavor than accomplishing a goal
|
||||
*
|
||||
*
|
||||
*/
|
||||
class PollinateFlowerFixation(
|
||||
) : EntityFixation {
|
||||
var successfulPollinationTicks = 0
|
||||
var lastSoundPlayedTick = 0
|
||||
var hoverPos: Vec3? = null
|
||||
override fun getDescription(entity: LivingEntity) = "Pollinating Flower"
|
||||
override fun canDistract(distraction: Distraction, entity: LivingEntity): Boolean {
|
||||
return distraction.isDamage || distraction.isForce || distraction.isComfort || entity.brain.hasMemoryValue(
|
||||
CobblemonMemories.HAS_NECTAR
|
||||
)
|
||||
}
|
||||
|
||||
override fun onDistract(distraction: Distraction, entity: LivingEntity) {
|
||||
hoverPos = null
|
||||
if (successfulPollinationTicks > REQUIRED_SUCCESSFUL_POLLINATION_TICKS) {
|
||||
entity.brain.setMemory(CobblemonMemories.HAS_NECTAR, true)
|
||||
}
|
||||
successfulPollinationTicks = 0
|
||||
entity.brain.setMemoryWithExpiry(
|
||||
CobblemonMemories.PATH_TO_NEARBY_FLOWER_COOLDOWN, true,
|
||||
PollinateFlowerTaskConfig.Companion.PATH_TO_FLOWER_COOLDOWN_TICKS
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
override fun tick(entity: LivingEntity, brain: BiggerBrain): Boolean {
|
||||
if (entity !is PokemonEntity) return false
|
||||
val flowerPos = entity.brain.getMemorySafely(CobblemonMemories.NEARBY_FLOWER).orElse(null)
|
||||
|
||||
if (!(flowerPos != null && Vec3.atCenterOf(flowerPos).distanceTo(entity.position()) <= 1.0)) {
|
||||
onStop(entity)
|
||||
return false
|
||||
}
|
||||
++successfulPollinationTicks
|
||||
|
||||
if (successfulPollinationTicks > REQUIRED_SUCCESSFUL_POLLINATION_TICKS) {
|
||||
onStop(entity)
|
||||
return false
|
||||
}
|
||||
|
||||
if (entity.random.nextFloat() < 0.05f && successfulPollinationTicks > this.lastSoundPlayedTick + 60) {
|
||||
this.lastSoundPlayedTick = successfulPollinationTicks
|
||||
entity.playSound(SoundEvents.BEE_POLLINATE, 1.0f, 1.0f)
|
||||
}
|
||||
|
||||
|
||||
val bl = hoverPos == null || entity.position().distanceTo(hoverPos!!) <= 0.1
|
||||
val bl3 = entity.random.nextInt(40) == 0
|
||||
if (bl && bl3) {
|
||||
// mimics the wriggling over a flower the vanilla bees do
|
||||
if (hoverPos == null) {
|
||||
hoverPos = flowerPos.bottomCenter.add(0.0, 0.4, 0.0)
|
||||
}
|
||||
|
||||
hoverPos = flowerPos.bottomCenter.add(
|
||||
Vec3(
|
||||
(entity.random.nextDouble() * 2.0 - 1.0) * (1.0 / 5.0),
|
||||
0.3 + (entity.random.nextDouble() * 2.0 - 1.0) * (1.0 / 7.0),
|
||||
(entity.random.nextDouble() * 2.0 - 1.0) * (1.0 / 5.0)
|
||||
)
|
||||
)
|
||||
entity.getMoveControl()
|
||||
.setWantedPosition(hoverPos!!.x(), hoverPos!!.y(), hoverPos!!.z(), 0.35)
|
||||
entity.lookControl.setLookAt(flowerPos.bottomCenter.add(0.0, 0.5, 0.0))
|
||||
} else if (!bl) {
|
||||
entity.getMoveControl()
|
||||
.setWantedPosition(hoverPos!!.x(), hoverPos!!.y(), hoverPos!!.z(), 0.35)
|
||||
}
|
||||
entity.lookControl.setLookAt(flowerPos.bottomCenter.add(0.0, 0.5, 0.0))
|
||||
return true
|
||||
}
|
||||
|
||||
fun onStop(entity: LivingEntity) {
|
||||
hoverPos = null
|
||||
if (successfulPollinationTicks > REQUIRED_SUCCESSFUL_POLLINATION_TICKS) {
|
||||
entity.brain.setMemory(CobblemonMemories.HAS_NECTAR, true)
|
||||
}
|
||||
successfulPollinationTicks = 0
|
||||
entity.brain.setMemoryWithExpiry(
|
||||
CobblemonMemories.PATH_TO_NEARBY_FLOWER_COOLDOWN, true,
|
||||
PATH_TO_FLOWER_COOLDOWN_TICKS
|
||||
)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
class DepositNectarInSaccharineLeavesFixation() : EntityFixation {
|
||||
var successfulPollinationTicks = 0
|
||||
var lastSoundPlayedTick = 0
|
||||
var hoverPos: Vec3? = null
|
||||
override fun getDescription(entity: LivingEntity) = "Depositing nectar in Saccharine Leaves"
|
||||
override fun canDistract(distraction: Distraction, entity: LivingEntity): Boolean {
|
||||
return distraction.isDamage || distraction.isForce || distraction.isComfort || entity.brain.hasMemoryValue(
|
||||
CobblemonMemories.HAS_NECTAR
|
||||
)
|
||||
}
|
||||
|
||||
override fun onDistract(distraction: Distraction, entity: LivingEntity) {
|
||||
hoverPos = null
|
||||
if (successfulPollinationTicks > REQUIRED_SUCCESSFUL_POLLINATION_TICKS) {
|
||||
entity.brain.setMemory(CobblemonMemories.HAS_NECTAR, true)
|
||||
}
|
||||
successfulPollinationTicks = 0
|
||||
entity.brain.setMemoryWithExpiry(
|
||||
CobblemonMemories.PATH_TO_NEARBY_FLOWER_COOLDOWN, true,
|
||||
PollinateFlowerTaskConfig.Companion.PATH_TO_FLOWER_COOLDOWN_TICKS
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
override fun tick(entity: LivingEntity, brain: BiggerBrain): Boolean {
|
||||
if (entity !is PokemonEntity) return false
|
||||
++successfulPollinationTicks
|
||||
|
||||
if (successfulPollinationTicks > REQUIRED_SUCCESSFUL_POLLINATION_TICKS) {
|
||||
val blockPos = brain.getMemory(CobblemonMemories.NEARBY_SACC_LEAVES)
|
||||
blockPos?.let {
|
||||
val world = entity.level()
|
||||
val blockState = world.getBlockState(blockPos)
|
||||
if (blockState.block == CobblemonBlocks.SACCHARINE_LEAVES) {
|
||||
val saccAge = blockState.getValue( SaccharineLeafBlock.AGE)
|
||||
if (saccAge < SaccharineLeafBlock.MAX_AGE) {
|
||||
brain.eraseMemory(CobblemonMemories.HAS_NECTAR)
|
||||
val newBlockState = blockState.setValue(SaccharineLeafBlock.AGE, saccAge + 1)
|
||||
world.setBlock(blockPos, newBlockState, 3)
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
if (entity.random.nextFloat() < 0.05f && successfulPollinationTicks > this.lastSoundPlayedTick + 60) {
|
||||
this.lastSoundPlayedTick = successfulPollinationTicks
|
||||
entity.playSound(SoundEvents.BEEHIVE_DRIP, 1.0f, 1.0f)
|
||||
}
|
||||
val leavesPos = brain.getMemory(CobblemonMemories.NEARBY_SACC_LEAVES) ?: return false
|
||||
|
||||
val bl = hoverPos == null || entity.position().distanceTo(hoverPos!!) <= 0.1
|
||||
val bl3 = entity.random.nextInt(40) == 0
|
||||
if (bl && bl3) {
|
||||
// mimics the wriggling over a flower the vanilla bees do
|
||||
if (hoverPos == null) {
|
||||
hoverPos = leavesPos.bottomCenter.add(0.0, 0.4, 0.0)
|
||||
}
|
||||
hoverPos = leavesPos.bottomCenter.add(Vec3((entity.random.nextDouble() * 2.0 - 1.0) * (1.0/5.0), 0.3 + (entity.random.nextDouble() * 2.0 - 1.0) * (1.0/7.0), (entity.random.nextDouble() * 2.0 - 1.0) * (1.0/5.0)))
|
||||
entity.getMoveControl()
|
||||
.setWantedPosition(hoverPos!!.x(), hoverPos!!.y(), hoverPos!!.z(), 0.35)
|
||||
entity.lookControl.setLookAt(leavesPos.bottomCenter.add(0.0,0.5,0.0))
|
||||
} else if (!bl) {
|
||||
entity.getMoveControl()
|
||||
.setWantedPosition(hoverPos!!.x(), hoverPos!!.y(), hoverPos!!.z(), 0.35)
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
class Config : ObjectiveConfig {
|
||||
val ticksBetweenAttempts: ExpressionOrEntityVariable = Either.left("60".asExpression())
|
||||
val maxDistance: ExpressionOrEntityVariable = Either.left("10.0".asExpression())
|
||||
val speedMultiplier: ExpressionOrEntityVariable = Either.left("0.4".asExpression())
|
||||
|
||||
override fun getVariables(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
) = listOf(
|
||||
ticksBetweenAttempts,
|
||||
maxDistance,
|
||||
speedMultiplier
|
||||
).asVariables()
|
||||
|
||||
override fun create(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
): Objective {
|
||||
val ticksBetweenAttempts = ticksBetweenAttempts.resolveInt(behaviourConfigurationContext.runtime)
|
||||
return GatherNectar(
|
||||
ticksBetweenAttempts = ticksBetweenAttempts,
|
||||
maxDistance = maxDistance.asSimplifiedExpression(entity),
|
||||
speedMultiplier = speedMultiplier.asSimplifiedExpression(entity)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun onHiveMovementComplete(entity: LivingEntity, brain: BiggerBrain) {
|
||||
if (entity !is PokemonEntity) return
|
||||
if(!tryEnterHive(entity)) {
|
||||
dropAndBlackListHive(entity)
|
||||
}
|
||||
}
|
||||
|
||||
fun tryMovementFixation(
|
||||
entity: PokemonEntity,
|
||||
blockPos: BlockPos,
|
||||
description : String,
|
||||
onMovementComplete : ((entity: LivingEntity, brain: BiggerBrain) -> Unit)? = null,
|
||||
) : EntityFixation? {
|
||||
val speedMultiplier = mainThreadRuntime.resolveFloat(speedMultiplier)
|
||||
|
||||
val movementTracker = RegularMovementTracker.attempt(
|
||||
entity = entity,
|
||||
walkTarget = WalkTarget(
|
||||
blockPos,
|
||||
speedMultiplier,
|
||||
0
|
||||
)
|
||||
)
|
||||
if (movementTracker != null) {
|
||||
val fixation = entity.biggerBrain.fixate(
|
||||
LookAndMoveFixation(
|
||||
distractionTypes = setOf(
|
||||
DistractionType.DAMAGE,
|
||||
DistractionType.COMFORT,
|
||||
DistractionType.FORCE
|
||||
),
|
||||
lookingTracker = LookingTracker.fromNavigationPathOrWalkTarget(
|
||||
entity,
|
||||
movementTracker.walkTarget
|
||||
),
|
||||
movementTracker = movementTracker,
|
||||
description = description,
|
||||
onMovementComplete = onMovementComplete
|
||||
)
|
||||
)
|
||||
return fixation
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun getHiveEntrance(hiveLocation: BlockPos, entity: LivingEntity) : BlockPos? {
|
||||
val sidesByClosest = listOf(
|
||||
hiveLocation.north(),
|
||||
hiveLocation.south(),
|
||||
hiveLocation.east(),
|
||||
hiveLocation.west()
|
||||
).sortedBy { it.distSqr(entity.blockPosition()) }
|
||||
|
||||
val world = entity.level()
|
||||
var openSide = BlockPos.ZERO
|
||||
for (side in sidesByClosest) {
|
||||
if (world.getBlockState(side).isAir) {
|
||||
openSide = side
|
||||
break
|
||||
}
|
||||
}
|
||||
if (openSide == BlockPos.ZERO) {
|
||||
if (world.getBlockState(hiveLocation.above()).isAir) {
|
||||
// If the above position is also air, we can use it
|
||||
openSide = hiveLocation.above() // Fallback to above if no open sides found
|
||||
} else if (world.getBlockState(hiveLocation.below()).isAir) {
|
||||
// If the below position is also air, we can use it
|
||||
openSide = hiveLocation.below() // Fallback to below if no open sides found
|
||||
} else {
|
||||
// If no open sides or above/below positions are found, we cannot proceed
|
||||
return null
|
||||
}
|
||||
}
|
||||
return openSide
|
||||
}
|
||||
|
||||
fun tryEnterHive(entity: LivingEntity): Boolean {
|
||||
if (entity !is PokemonEntity || !wantsToEnterHive(entity)) {
|
||||
return false
|
||||
}
|
||||
|
||||
val hiveLocation = entity.biggerBrain.getMemory(CobblemonMemories.HIVE_LOCATION) ?: return false
|
||||
val targetVec = Vec3.atCenterOf(hiveLocation)
|
||||
|
||||
// if we are not close to it then end early
|
||||
if (entity.distanceToSqr(targetVec) > 1.6 * 1.6) {
|
||||
return false
|
||||
}
|
||||
|
||||
val state = entity.level().getBlockState(hiveLocation)
|
||||
val block = state.block
|
||||
if (block is BeehiveBlock) {
|
||||
val blockEntity = entity.level().getBlockEntity(hiveLocation)
|
||||
if (blockEntity !is BeehiveBlockEntity) return false
|
||||
if (blockEntity.isFull) {
|
||||
// Erase Hive memory and give the bee a moment to locate a new hive
|
||||
dropAndBlackListHive(entity)
|
||||
} else {
|
||||
blockEntity.addOccupant(entity)
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun wantsToEnterHive(entity: LivingEntity): Boolean {
|
||||
val result = !entity.brain.checkMemory(CobblemonMemories.HIVE_COOLDOWN, MemoryStatus.VALUE_PRESENT)
|
||||
&& (entity.level().isRaining || entity.level().isNight || entity.brain.getMemory(CobblemonMemories.HAS_NECTAR)
|
||||
.orElse(false))
|
||||
if (result) {
|
||||
val blockPos = entity.brain.getMemorySafely(CobblemonMemories.HIVE_LOCATION).orElse(null)
|
||||
val blockEntity = entity.level().getBlockEntity(blockPos)
|
||||
return blockEntity is BeehiveBlockEntity && !blockEntity.isFireNearby
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun dropAndBlackListHive(entity: PokemonEntity) {
|
||||
val hivePos = entity.biggerBrain.getMemory(CobblemonMemories.HIVE_LOCATION)
|
||||
if (hivePos == null) return
|
||||
val blackList : MutableList<BlockPos> = entity.biggerBrain.getMemory(CobblemonMemories.HIVE_BLACKLIST)?.toMutableList() ?: emptyList<BlockPos>().toMutableList()
|
||||
|
||||
blackList.add(hivePos)
|
||||
if (blackList.count() > 3) {
|
||||
blackList.removeFirst()
|
||||
}
|
||||
entity.brain.eraseMemory(CobblemonMemories.HIVE_LOCATION)
|
||||
entity.brain.setMemory(CobblemonMemories.HIVE_BLACKLIST, blackList)
|
||||
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val PATH_TO_FLOWER_COOLDOWN_TICKS: Long = 200
|
||||
const val HAS_NECTAR_ASPECT = "has_nectar"
|
||||
const val MIN_DURATION: Int = 300
|
||||
const val MAX_DURATION: Int = 400
|
||||
const val REQUIRED_SUCCESSFUL_POLLINATION_TICKS = 250
|
||||
}
|
||||
}
|
||||
-155
@@ -1,155 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.objective
|
||||
|
||||
import com.bedrockk.molang.Expression
|
||||
import com.cobblemon.mod.common.CobblemonActivities
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
|
||||
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
|
||||
import com.cobblemon.mod.common.api.ai.asSimplifiedExpression
|
||||
import com.cobblemon.mod.common.api.ai.asVariables
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.MovementStatus
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.MovementTracker
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.EntityFixation
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.Distraction
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.MovementTrackerFixation
|
||||
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
|
||||
import com.cobblemon.mod.common.api.pokemon.status.Statuses
|
||||
import com.cobblemon.mod.common.api.storage.party.PartyStore
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.pokemon.status.PersistentStatusContainer
|
||||
import com.cobblemon.mod.common.util.asExpression
|
||||
import com.cobblemon.mod.common.util.getMemorySafely
|
||||
import com.cobblemon.mod.common.util.mainThreadRuntime
|
||||
import com.cobblemon.mod.common.util.resolveBoolean
|
||||
import com.mojang.datafixers.util.Either
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.ai.memory.MemoryModuleType
|
||||
|
||||
/**
|
||||
* Sleeps the entity.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since December 29th, 2025
|
||||
*/
|
||||
class GoToSleep(
|
||||
val onlyFromStatus: Expression = "false".asExpression(),
|
||||
) : Objective {
|
||||
var fixation: EntityFixation? = null
|
||||
|
||||
override fun getDescription(entity: LivingEntity) = "Sleeping"
|
||||
override fun perform(
|
||||
entity: LivingEntity,
|
||||
brain: BiggerBrain,
|
||||
activeObjectiveTags: MutableSet<String>
|
||||
): ObjectiveStatus {
|
||||
if (entity !is PokemonEntity) {
|
||||
return ObjectiveStatus.INVALID
|
||||
} else if (brain.isFixation(fixation)) {
|
||||
return ObjectiveStatus.FIXATED
|
||||
} else if (brain.fixation != null) {
|
||||
fixation = null
|
||||
return ObjectiveStatus.BLOCKED
|
||||
}
|
||||
|
||||
// Don't sleep if we're not tired
|
||||
brain.getMemory(CobblemonMemories.POKEMON_DROWSY)
|
||||
?: return ObjectiveStatus.INACTIVE
|
||||
|
||||
val hasSleepStatus = entity.pokemon.status?.status === Statuses.SLEEP
|
||||
if (entity.behaviour.resting.canSleep && entity.canSleepAt(entity.blockPosition().below()) || hasSleepStatus) {
|
||||
if (!hasSleepStatus) {
|
||||
if (mainThreadRuntime.resolveBoolean(onlyFromStatus)) {
|
||||
return ObjectiveStatus.BLOCKED
|
||||
}
|
||||
entity.pokemon.status = PersistentStatusContainer(Statuses.SLEEP)
|
||||
}
|
||||
entity.brain.eraseMemory(MemoryModuleType.WALK_TARGET)
|
||||
entity.navigation.stop()
|
||||
entity.brain.setActiveActivityToFirstValid(listOf(CobblemonActivities.POKEMON_SLEEPING_ACTIVITY))
|
||||
entity.brain.setMemory(CobblemonMemories.POKEMON_SLEEPING, true)
|
||||
|
||||
// Become fixated
|
||||
fixation = brain.fixate(
|
||||
SleepFixation(
|
||||
this,
|
||||
null
|
||||
)
|
||||
)
|
||||
return ObjectiveStatus.FIXATED
|
||||
}
|
||||
return ObjectiveStatus.BLOCKED
|
||||
}
|
||||
|
||||
|
||||
|
||||
class SleepFixation(
|
||||
val objective: GoToSleep,
|
||||
override val movementTracker: MovementTracker?,
|
||||
) : EntityFixation, MovementTrackerFixation {
|
||||
|
||||
override fun getDescription(entity: LivingEntity): String {
|
||||
return "Sleeping"
|
||||
}
|
||||
override fun tickMovementFixation(): MovementStatus? {
|
||||
return MovementStatus.PENDING
|
||||
}
|
||||
|
||||
override fun canDistract(distraction: Distraction, entity: LivingEntity): Boolean {
|
||||
if (distraction.isIdleBoredom) {
|
||||
return false
|
||||
}
|
||||
wakeUp(entity)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun tick(entity: LivingEntity, brain: BiggerBrain): Boolean {
|
||||
if (!entity.brain.getMemorySafely(CobblemonMemories.POKEMON_DROWSY).orElse(false)) {
|
||||
wakeUp(entity)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
fun wakeUp(entity: LivingEntity) {
|
||||
if (entity !is PokemonEntity) return
|
||||
val hasSleepStatus = entity.pokemon.status?.status == Statuses.SLEEP
|
||||
if (hasSleepStatus && entity.pokemon.storeCoordinates.get()?.store is PartyStore) {
|
||||
return // You can't wake up if you're in the party with the sleep status.
|
||||
}
|
||||
if (hasSleepStatus) {
|
||||
entity.pokemon.status = null
|
||||
}
|
||||
entity.brain.eraseMemory(CobblemonMemories.POKEMON_SLEEPING)
|
||||
entity.brain.useDefaultActivity()
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
class Config : ObjectiveConfig {
|
||||
|
||||
val onlyFromStatus: ExpressionOrEntityVariable = Either.left("false".asExpression())
|
||||
override fun getVariables(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
) = listOf(
|
||||
onlyFromStatus
|
||||
).asVariables()
|
||||
|
||||
override fun create(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
): Objective {
|
||||
return GoToSleep(
|
||||
onlyFromStatus = onlyFromStatus.asSimplifiedExpression(entity)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
-204
@@ -1,204 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.objective
|
||||
|
||||
import com.bedrockk.molang.Expression
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
|
||||
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
|
||||
import com.cobblemon.mod.common.api.ai.asSimplifiedExpression
|
||||
import com.cobblemon.mod.common.api.ai.asVariables
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.LookingTracker
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.MovementStatus
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.MovementTracker
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.RegularMovementTracker
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.Distraction
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.idleBoredomDistraction
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.EntityFixation
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.LookingTrackerFixation
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.MovementTrackerFixation
|
||||
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
|
||||
import com.cobblemon.mod.common.api.ai.resolveInt
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.util.asExpression
|
||||
import com.cobblemon.mod.common.util.distanceTo
|
||||
import com.cobblemon.mod.common.util.getMemorySafely
|
||||
import com.cobblemon.mod.common.util.mainThreadRuntime
|
||||
import com.cobblemon.mod.common.util.resolveFloat
|
||||
import com.cobblemon.mod.common.util.withQueryValue
|
||||
import com.mojang.datafixers.util.Either
|
||||
import net.minecraft.sounds.SoundEvents
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.PathfinderMob
|
||||
import net.minecraft.world.entity.ai.memory.WalkTarget
|
||||
import net.minecraft.world.item.ItemStack
|
||||
import net.minecraft.world.item.Items
|
||||
import net.minecraft.world.level.GameRules
|
||||
import net.minecraft.world.level.block.Block
|
||||
import net.minecraft.world.level.block.Blocks
|
||||
import net.minecraft.world.level.block.CaveVines
|
||||
import net.minecraft.world.level.block.SweetBerryBushBlock
|
||||
import net.minecraft.world.level.block.state.BlockState
|
||||
import net.minecraft.world.level.gameevent.GameEvent
|
||||
|
||||
/**
|
||||
* Paths to ripened sweet berry bushes AND glowberry vines with berries.
|
||||
* Upon reaching the target harvests the berries and stashes one in its
|
||||
* held item slot if possible.
|
||||
* Requires NEARBY_SWEET_BERRY_BUSH memory
|
||||
*
|
||||
* @author JazzMcNade
|
||||
* @since February 8th, 2026
|
||||
*/
|
||||
class HarvestSweetBerryBush(
|
||||
val ticksBetweenAttempts: Int = 60,
|
||||
val maxDistance: Expression = "14.0".asExpression(),
|
||||
val speedMultiplier: Expression = "0.4".asExpression()
|
||||
) : Objective {
|
||||
var harvestTask: EntityFixation? = null
|
||||
var remainingCooldownTicks = ticksBetweenAttempts
|
||||
|
||||
override fun getDescription(entity: LivingEntity) = "Harvesting sweet/glow berry"
|
||||
override fun perform(
|
||||
entity: LivingEntity,
|
||||
brain: BiggerBrain,
|
||||
activeObjectiveTags: MutableSet<String>
|
||||
): ObjectiveStatus {
|
||||
if (entity !is PokemonEntity) {
|
||||
return ObjectiveStatus.INVALID
|
||||
} else if (brain.isFixation(harvestTask)) {
|
||||
remainingCooldownTicks = ticksBetweenAttempts
|
||||
return ObjectiveStatus.FIXATED
|
||||
}
|
||||
|
||||
val berryPosition = entity.biggerBrain.getMemory(CobblemonMemories.NEARBY_SWEET_BERRY_BUSH) ?: return ObjectiveStatus.INACTIVE
|
||||
|
||||
if (remainingCooldownTicks > 0) {
|
||||
remainingCooldownTicks--
|
||||
return ObjectiveStatus.ACTIVE
|
||||
}
|
||||
|
||||
mainThreadRuntime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
|
||||
val maxDistance = mainThreadRuntime.resolveFloat(maxDistance)
|
||||
val speedMultiplier = mainThreadRuntime.resolveFloat(speedMultiplier)
|
||||
|
||||
val distance = entity.distanceTo(berryPosition)
|
||||
if (distance < maxDistance) {
|
||||
if (remainingCooldownTicks <= 0 && brain.distract(idleBoredomDistraction)) {
|
||||
remainingCooldownTicks = ticksBetweenAttempts
|
||||
val movementTracker = RegularMovementTracker.attempt(
|
||||
entity = entity,
|
||||
walkTarget = WalkTarget(
|
||||
berryPosition,
|
||||
speedMultiplier,
|
||||
1
|
||||
)
|
||||
)
|
||||
if (movementTracker != null) {
|
||||
harvestTask = brain.fixate(
|
||||
fixation = HarvestSweetBerryBushFixation(
|
||||
entity = entity,
|
||||
movementTracker = movementTracker
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
return ObjectiveStatus.ACTIVE
|
||||
}
|
||||
|
||||
|
||||
class HarvestSweetBerryBushFixation(
|
||||
entity: PathfinderMob,
|
||||
override val movementTracker: MovementTracker,
|
||||
): EntityFixation, LookingTrackerFixation, MovementTrackerFixation {
|
||||
|
||||
|
||||
override val lookingTracker: LookingTracker = LookingTracker(entity = entity, lookTarget = movementTracker.walkTarget.target)
|
||||
override fun getDescription(entity: LivingEntity) = "Harvesting sweet/glow berry"
|
||||
override fun canDistract(distraction: Distraction, entity: LivingEntity): Boolean {
|
||||
return distraction.isDamage || distraction.isForce || (movementTracker.lastStatus == MovementStatus.COMPLETED && entity.brain.getMemorySafely(
|
||||
CobblemonMemories.NEARBY_SWEET_BERRY_BUSH).orElse(null) != null)
|
||||
}
|
||||
|
||||
override fun onDistract(distraction: Distraction, entity: LivingEntity) {
|
||||
movementTracker.stop()
|
||||
}
|
||||
|
||||
override fun tick(entity: LivingEntity, brain: BiggerBrain): Boolean {
|
||||
lookingTracker.tick()
|
||||
movementTracker.tick()
|
||||
|
||||
if (movementTracker.lastStatus == MovementStatus.COMPLETED) {
|
||||
val world = entity.level()
|
||||
|
||||
if (entity !is PokemonEntity) return false
|
||||
|
||||
if (!world.gameRules.getRule(GameRules.RULE_MOBGRIEFING).get()) {
|
||||
// Not allowed to alter the world
|
||||
return false
|
||||
}
|
||||
val blockPos = entity.brain.getMemory(CobblemonMemories.NEARBY_SWEET_BERRY_BUSH).orElse(null) ?: return false
|
||||
val blockState = world.getBlockState(blockPos)
|
||||
if (CaveVines.hasGlowBerries(blockState)) { // Glow Berries Case
|
||||
CaveVines.use(entity, blockState, world, blockPos)
|
||||
} else if (blockState.`is`(Blocks.SWEET_BERRY_BUSH)) { // Sweet Berries Case
|
||||
blockState.setValue(SweetBerryBushBlock.AGE, 1)
|
||||
var j: Int = 1 + entity.level().random.nextInt(2) + if (blockState.getValue<Int>(SweetBerryBushBlock.AGE) == 3) 1 else 0
|
||||
val itemStack: ItemStack = entity.pokemon.heldItem
|
||||
if (itemStack.isEmpty) {
|
||||
entity.pokemon.swapHeldItem(ItemStack(Items.SWEET_BERRIES))
|
||||
--j
|
||||
}
|
||||
if (j > 0) {
|
||||
Block.popResource(entity.level(), blockPos, ItemStack(Items.SWEET_BERRIES, j))
|
||||
}
|
||||
entity.playSound(SoundEvents.SWEET_BERRY_BUSH_PICK_BERRIES, 1.0f, 1.0f)
|
||||
entity.level().setBlock(blockPos, blockState.setValue(SweetBerryBushBlock.AGE, 1) as BlockState, 2)
|
||||
entity.level().gameEvent(GameEvent.BLOCK_CHANGE, blockPos, GameEvent.Context.of(entity))
|
||||
}
|
||||
entity.brain.eraseMemory(CobblemonMemories.NEARBY_SWEET_BERRY_BUSH)
|
||||
return false
|
||||
} else if (movementTracker.lastStatus == MovementStatus.FAILED) {
|
||||
// Cannot reach, give up for a awhile
|
||||
entity.brain.setMemoryWithExpiry(CobblemonMemories.DISABLE_WALK_TO_BERRY_BUSH, true, 200)
|
||||
entity.brain.eraseMemory(CobblemonMemories.NEARBY_SWEET_BERRY_BUSH)
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
class Config : ObjectiveConfig {
|
||||
val ticksBetweenAttempts: ExpressionOrEntityVariable = Either.left("60".asExpression())
|
||||
val maxDistance: ExpressionOrEntityVariable = Either.left("10.0".asExpression())
|
||||
val speedMultiplier: ExpressionOrEntityVariable = Either.left("0.4".asExpression())
|
||||
|
||||
override fun getVariables(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
) = listOf(
|
||||
ticksBetweenAttempts,
|
||||
maxDistance,
|
||||
speedMultiplier
|
||||
).asVariables()
|
||||
|
||||
override fun create(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): Objective {
|
||||
val ticksBetweenAttempts = ticksBetweenAttempts.resolveInt(behaviourConfigurationContext.runtime)
|
||||
return HarvestSweetBerryBush(
|
||||
ticksBetweenAttempts = ticksBetweenAttempts,
|
||||
maxDistance = maxDistance.asSimplifiedExpression(entity),
|
||||
speedMultiplier = speedMultiplier.asSimplifiedExpression(entity)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,409 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.objective
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
|
||||
import com.cobblemon.mod.common.api.ai.CobblemonWanderControl
|
||||
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
|
||||
import com.cobblemon.mod.common.api.ai.asVariables
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.LookingTracker
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.MovementStatus
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.RegularMovementTracker
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.Distraction
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.DistractionType
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.comfortDistraction
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.EntityFixation
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.LookAndMoveFixation
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.MovementTrackerFixation
|
||||
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
|
||||
import com.cobblemon.mod.common.api.ai.resolveFloat
|
||||
import com.cobblemon.mod.common.api.ai.resolveInt
|
||||
import com.cobblemon.mod.common.api.pokemon.status.Statuses
|
||||
import com.cobblemon.mod.common.api.storage.party.PartyStore
|
||||
import com.cobblemon.mod.common.entity.PoseType
|
||||
import com.cobblemon.mod.common.entity.ai.CobblemonWalkTarget
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.entity.pokemon.ai.PokemonMoveControl
|
||||
import com.cobblemon.mod.common.pokemon.ai.ToleratedHerdLeader
|
||||
import com.cobblemon.mod.common.pokemon.status.PersistentStatusContainer
|
||||
import com.cobblemon.mod.common.util.asExpression
|
||||
import com.cobblemon.mod.common.util.toBlockPos
|
||||
import com.mojang.datafixers.util.Either
|
||||
import java.util.UUID
|
||||
import kotlin.math.cos
|
||||
import kotlin.math.sin
|
||||
import net.minecraft.server.level.ServerLevel
|
||||
import net.minecraft.util.Mth
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.ai.memory.MemoryModuleType
|
||||
import net.minecraft.world.entity.ai.memory.NearestVisibleLivingEntities
|
||||
import net.minecraft.world.entity.ai.memory.WalkTarget
|
||||
import net.minecraft.world.phys.Vec3
|
||||
|
||||
class Herd(
|
||||
var checkTicks: Int = 60,
|
||||
var tooFar: Float = 8F,
|
||||
var closeEnough: Float = 4F,
|
||||
/** Chance per tick of following the herd leader. */
|
||||
val chance: Float = 1 / 60F,
|
||||
val pathingCooldownTicks: Int = 80
|
||||
) : Objective {
|
||||
var wanderCenter: CobblemonWanderControl.WanderCenter? = null
|
||||
|
||||
var fixation: EntityFixation? = null
|
||||
var leader: PokemonEntity? = null
|
||||
var ticksUntilMaintainLeader: Int = checkTicks
|
||||
var remainingPathingCooldownTicks = 0
|
||||
|
||||
override fun getDescription(entity: LivingEntity) = "Herd"
|
||||
override fun perform(
|
||||
entity: LivingEntity,
|
||||
brain: BiggerBrain,
|
||||
activeObjectiveTags: MutableSet<String>
|
||||
): ObjectiveStatus {
|
||||
entity as PokemonEntity
|
||||
val moveControl = entity.moveControl as? PokemonMoveControl ?: return ObjectiveStatus.INVALID
|
||||
|
||||
if (remainingPathingCooldownTicks > 0) {
|
||||
remainingPathingCooldownTicks--
|
||||
}
|
||||
|
||||
val level = entity.level() as ServerLevel
|
||||
if (leader == null && brain.hasMemoryValue(CobblemonMemories.HERD_LEADER)) {
|
||||
leader = level.getEntity(brain.getMemory(CobblemonMemories.HERD_LEADER).let(UUID::fromString)) as? PokemonEntity
|
||||
if (leader == null) {
|
||||
brain.eraseMemory(CobblemonMemories.HERD_LEADER)
|
||||
}
|
||||
}
|
||||
|
||||
if (ticksUntilMaintainLeader > 0) {
|
||||
ticksUntilMaintainLeader--
|
||||
} else if (ticksUntilMaintainLeader == 0) {
|
||||
if (brain.fixation == null || brain.isFixation(fixation)) {
|
||||
// Don't maintain the herd leader if we're busy with something else
|
||||
maintainHerdLeader(entity, brain)
|
||||
}
|
||||
ticksUntilMaintainLeader = checkTicks
|
||||
}
|
||||
|
||||
if (leader?.level() != level) {
|
||||
this.leader = null
|
||||
}
|
||||
|
||||
val leader = leader
|
||||
if (leader == null && brain.isFixation(fixation)) {
|
||||
brain.distract(Distraction.FORCE)
|
||||
}
|
||||
|
||||
val wanderControl = brain.getMemory(CobblemonMemories.WANDER_CONTROL)
|
||||
?: CobblemonWanderControl()
|
||||
|
||||
if (this.wanderCenter != null && leader == null) {
|
||||
if (wanderControl.center == this.wanderCenter) {
|
||||
wanderControl.center = null
|
||||
wanderControl.wanderChanceMultiplier = null
|
||||
brain.setMemory(CobblemonMemories.WANDER_CONTROL, wanderControl)
|
||||
}
|
||||
this.wanderCenter = null
|
||||
} else if (leader != null) {
|
||||
wanderControl.center = CobblemonWanderControl.WanderCenter(
|
||||
x = leader.x,
|
||||
y = leader.y,
|
||||
z = leader.z,
|
||||
minRange = 1F,
|
||||
maxRange = tooFar
|
||||
)
|
||||
this.wanderCenter = wanderControl.center
|
||||
wanderControl.wanderChanceMultiplier = 0.25F
|
||||
brain.setMemory(CobblemonMemories.WANDER_CONTROL, wanderControl)
|
||||
}
|
||||
|
||||
leader ?: return ObjectiveStatus.INACTIVE
|
||||
|
||||
if (brain.isFixation(fixation) && fixation is MovementTrackerFixation) {
|
||||
return ObjectiveStatus.FIXATED
|
||||
}
|
||||
|
||||
val leaderFixation = leader.biggerBrain.fixation
|
||||
|
||||
val canMove = brain.canDistract(comfortDistraction) || brain.isFixation(fixation)
|
||||
val isMovingCloser = brain.isFixation(fixation) && fixation is LookAndMoveFixation
|
||||
|
||||
val leaderMoveControl = leader.moveControl as? PokemonMoveControl ?: return ObjectiveStatus.INVALID
|
||||
if (leader.distanceTo(entity) > tooFar) {
|
||||
return when {
|
||||
isMovingCloser -> ObjectiveStatus.FIXATED // Already have the fixation to move closer
|
||||
remainingPathingCooldownTicks > 0 -> manageFixation(brain) // Still cooling down from last pathing attempt
|
||||
!canMove -> ObjectiveStatus.BLOCKED // I'm not able to distract the brain and move closer
|
||||
else -> tryMovingCloser(entity, brain, level, leader) // let's give it a fair shake then
|
||||
}
|
||||
} else if (leader.isFlying() && !entity.isFlying() && !isMovingCloser) {
|
||||
if (!entity.canFly()) {
|
||||
entity.brain.eraseMemory(CobblemonMemories.HERD_LEADER) // We can't go where this guy's going
|
||||
this.leader = null
|
||||
return ObjectiveStatus.INACTIVE
|
||||
} else if (!canMove) {
|
||||
return ObjectiveStatus.BLOCKED
|
||||
}
|
||||
|
||||
return tryMovingCloser(entity, brain, level, leader)
|
||||
} else if (leaderMoveControl.banking && !isMovingCloser) {
|
||||
if (!canMove) {
|
||||
return ObjectiveStatus.BLOCKED
|
||||
}
|
||||
|
||||
if (leader.isInLiquid && !entity.isInLiquid) {
|
||||
if (remainingPathingCooldownTicks > 0) {
|
||||
return manageFixation(brain) // Still cooling down from last pathing attempt
|
||||
}
|
||||
|
||||
// We should get into water asap so we can mimic the banking
|
||||
return tryMovingCloser(entity, brain, level, leader, completionRange = 1)
|
||||
} else {
|
||||
removeSleep(entity, brain)
|
||||
entity.yRot = leader.yRot
|
||||
moveControl.startBanking(
|
||||
forwardBlocksPerTick = leaderMoveControl.bankForwardBlocksPerTick,
|
||||
upwardsBlocksPerTick = leaderMoveControl.bankUpwardsBlocksPerTick,
|
||||
durationTicks = 2,
|
||||
rightDegreesPerTick = 0F // Relying on the above yRot = leader.yRot because we dunno when we first started mimicking the banking
|
||||
)
|
||||
return ObjectiveStatus.FIXATED
|
||||
}
|
||||
} else if (leaderFixation is MovementTrackerFixation && leaderFixation.movementTracker?.lastStatus == MovementStatus.PENDING) {
|
||||
|
||||
if (entity.random.nextFloat() > chance) {
|
||||
return manageFixation(brain) // Not this tick
|
||||
} else if (!canMove) {
|
||||
return ObjectiveStatus.BLOCKED
|
||||
} else if (isMovingCloser) {
|
||||
return ObjectiveStatus.FIXATED // Already moving
|
||||
}
|
||||
|
||||
val leaderWalkTarget = leaderFixation.movementTracker?.walkTarget ?: return manageFixation(brain)
|
||||
|
||||
|
||||
// We're already closer to the walk target than the leader is, so no need to move. If anything we should get out of the way
|
||||
if (entity.distanceToSqr(leaderWalkTarget.target.currentPosition()) + closeEnough < leader.distanceToSqr(leaderWalkTarget.target.currentPosition())) {
|
||||
return manageFixation(brain)
|
||||
}
|
||||
|
||||
// Go to the leader's walk target, roughly.
|
||||
val walkTarget = CobblemonWalkTarget(
|
||||
pos = leaderWalkTarget.target.currentPosition().add(getRandomOffset(entity, level)).toBlockPos(),
|
||||
speedModifier = leaderWalkTarget.speedModifier,
|
||||
nodeTypeFilter = (leaderWalkTarget as? CobblemonWalkTarget)?.nodeTypeFilter ?: { true },
|
||||
completionRange = closeEnough.toInt()
|
||||
)
|
||||
|
||||
return tryMovingCloser(entity, brain, level, leader, walkTarget = walkTarget)
|
||||
} else if (leader.getCurrentPoseType() == PoseType.SLEEP) {
|
||||
// Leader is sleeping, if I'm drowsy and can sleep here then I will sleep too.
|
||||
if (entity.canSleepAt(entity.blockPosition().offset(0, -1, 0)) && brain.getMemory(CobblemonMemories.POKEMON_DROWSY) == true) {
|
||||
if (entity.random.nextFloat() < 1 / 40F) {
|
||||
applySleep(entity, brain)
|
||||
}
|
||||
}
|
||||
} else if (brain.getMemory(CobblemonMemories.POKEMON_SLEEPING) == true) {
|
||||
// Leader is awake, so we should be awake too. Maybe.
|
||||
if (entity.random.nextFloat() < 1 / 15F) {
|
||||
removeSleep(entity, brain)
|
||||
}
|
||||
}
|
||||
|
||||
return manageFixation(brain)
|
||||
}
|
||||
|
||||
fun manageFixation(brain: BiggerBrain): ObjectiveStatus {
|
||||
if (brain.isFixation(fixation)) {
|
||||
return ObjectiveStatus.FIXATED
|
||||
} else if (leader != null) {
|
||||
return ObjectiveStatus.ACTIVE
|
||||
} else {
|
||||
return ObjectiveStatus.INACTIVE
|
||||
}
|
||||
}
|
||||
|
||||
fun tryMovingCloser(
|
||||
entity: PokemonEntity,
|
||||
brain: BiggerBrain,
|
||||
level: ServerLevel,
|
||||
leader: PokemonEntity,
|
||||
walkTarget: WalkTarget? = null,
|
||||
completionRange: Int = closeEnough.toInt()
|
||||
): ObjectiveStatus {
|
||||
val walkTarget = walkTarget ?: CobblemonWalkTarget(
|
||||
pos = leader.position().add(getRandomOffset(entity, level)).toBlockPos(),
|
||||
speedModifier = 0.4F,
|
||||
completionRange = completionRange
|
||||
)
|
||||
|
||||
val movementTracker = RegularMovementTracker.attempt(entity, walkTarget)
|
||||
|
||||
remainingPathingCooldownTicks = pathingCooldownTicks
|
||||
if (movementTracker == null) {
|
||||
// Couldn't find a path, wait a bit before trying again
|
||||
return manageFixation(brain)
|
||||
} else if (!(brain.isFixation(fixation) && brain.distract(Distraction.FORCE)) && !brain.distract(comfortDistraction)) {
|
||||
return manageFixation(brain) // Not likely based on checks run on callers to this function.
|
||||
}
|
||||
|
||||
brain.sendDebugMessage("Moving closer, to ${movementTracker.walkTarget.target.currentBlockPosition()}")
|
||||
|
||||
removeSleep(entity, brain)
|
||||
fixation = brain.fixate(
|
||||
LookAndMoveFixation(
|
||||
distractionTypes = setOf(DistractionType.DAMAGE, DistractionType.COMFORT, DistractionType.FORCE),
|
||||
lookingTracker = LookingTracker.forTargetEntity(entity, leader, trackEyeHeight = true),
|
||||
movementTracker = movementTracker,
|
||||
description = "Moving towards herd leader"
|
||||
)
|
||||
)
|
||||
|
||||
return ObjectiveStatus.FIXATED
|
||||
}
|
||||
|
||||
fun maintainHerdLeader(entity: PokemonEntity, brain: BiggerBrain) {
|
||||
val world = entity.level() as ServerLevel
|
||||
val leader = brain.getMemory(CobblemonMemories.HERD_LEADER)?.let(UUID::fromString).let(world::getEntity) as? PokemonEntity
|
||||
val currentLeaderDefinition = leader?.let { leader ->
|
||||
entity.behaviour.herd.toleratedLeaders.find { it.pokemon.matches(leader) }
|
||||
}
|
||||
if (leader == null || currentLeaderDefinition == null) {
|
||||
entity.brain.eraseMemory(CobblemonMemories.HERD_LEADER)
|
||||
this.leader = null
|
||||
}
|
||||
|
||||
val bestLeader = getBestHerdLeader(
|
||||
currentLeader = if (leader == null || currentLeaderDefinition == null) null else (leader to currentLeaderDefinition),
|
||||
nearbyEntities = brain.getMemory(MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES) ?: return,
|
||||
entity = entity
|
||||
)
|
||||
|
||||
if (bestLeader != null && bestLeader != leader) {
|
||||
bestLeader.brain.eraseMemory(CobblemonMemories.HERD_LEADER)
|
||||
entity.brain.setMemory(CobblemonMemories.HERD_LEADER, bestLeader.uuid.toString())
|
||||
bestLeader.adjustHerdSize(1)
|
||||
leader?.adjustHerdSize(-1)
|
||||
this.leader = bestLeader
|
||||
}
|
||||
}
|
||||
|
||||
fun getRandomOffset(entity: PokemonEntity, level: ServerLevel): Vec3 {
|
||||
val leader = leader ?: return Vec3.ZERO
|
||||
|
||||
val minRange = closeEnough.toDouble()
|
||||
val maxRange = (tooFar - closeEnough / 2).toDouble()
|
||||
val randomDistance = Mth.nextDouble(level.random, minRange, maxRange)
|
||||
val randomAngle = Mth.nextDouble(level.random, 0.0, 2 * Math.PI)
|
||||
|
||||
var offset = Vec3(cos(randomAngle) * randomDistance, 0.0, sin(randomAngle) * randomDistance)
|
||||
|
||||
if ((entity.isInLiquid && leader.isInLiquid) || (entity.isFlying() && leader.isFlying())) {
|
||||
val verticalRange = randomDistance * 0.5
|
||||
val randomY = Mth.nextDouble(level.random, -verticalRange, verticalRange)
|
||||
offset = offset.add(0.0, randomY, 0.0)
|
||||
}
|
||||
|
||||
return offset
|
||||
}
|
||||
|
||||
|
||||
fun applySleep(entity: PokemonEntity, brain: BiggerBrain) {
|
||||
brain.setMemory(CobblemonMemories.POKEMON_SLEEPING, true)
|
||||
entity.pokemon.status = PersistentStatusContainer(Statuses.SLEEP)
|
||||
}
|
||||
|
||||
fun removeSleep(entity: PokemonEntity, brain: BiggerBrain) {
|
||||
if (brain.hasMemoryValue(CobblemonMemories.POKEMON_SLEEPING)) {
|
||||
brain.eraseMemory(CobblemonMemories.POKEMON_SLEEPING)
|
||||
val status = entity.pokemon.status
|
||||
if (status != null && status.status == Statuses.SLEEP) {
|
||||
entity.pokemon.status = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Logic used to choose the best herd leader from nearby visible entities.
|
||||
*
|
||||
* - Higher tier leaders are always preferred.
|
||||
* - Same-tier leaders that are closer are preferred except for when they're the same tier as the current leader.
|
||||
* - We don't want to exceed the target's herd size limit.
|
||||
* - We don't want to choose a leader that is themselves following a leader.
|
||||
* - Some leader definitions allow lower-level Pokémon to lead them.
|
||||
* - Leaders just have to not be in a party - pastured and wild Pokémon can mingle.
|
||||
*/
|
||||
fun getBestHerdLeader(
|
||||
currentLeader: Pair<PokemonEntity, ToleratedHerdLeader>?,
|
||||
nearbyEntities: NearestVisibleLivingEntities,
|
||||
entity: PokemonEntity,
|
||||
): PokemonEntity? {
|
||||
var bestLeader: PokemonEntity? = null
|
||||
val (currentLeaderEntity, currentLeaderDefinition) = currentLeader ?: Pair(null, null)
|
||||
var bestTier = currentLeaderDefinition?.tier?.let { it + 1 } ?: 0 // Only upgrade to a higher tier leader
|
||||
var bestDistance = Float.MAX_VALUE
|
||||
nearbyEntities.findAll {
|
||||
it is PokemonEntity &&
|
||||
it != currentLeader &&
|
||||
it.pokemon.storeCoordinates.get()?.store !is PartyStore &&
|
||||
it.getHerdSize() < it.behaviour.herd.maxSize &&
|
||||
!it.brain.hasMemoryValue(CobblemonMemories.POKEMON_DROWSY)
|
||||
}.forEach { possibleLeader ->
|
||||
possibleLeader as PokemonEntity
|
||||
val bestMatchingHerdLeader = entity.behaviour.herd.bestMatchLeader(entity, possibleLeader) ?: return@forEach
|
||||
val possibleLeaderIsAlreadyALeader = possibleLeader.brain.hasMemoryValue(CobblemonMemories.HERD_SIZE)
|
||||
val currentLeaderHerdTier = currentLeaderEntity?.getHerdTier() ?: entity.getHerdTier()
|
||||
if (currentLeaderHerdTier >= bestMatchingHerdLeader.tier) {
|
||||
return@forEach // This leader is following a leader of a higher tier, we can't replace them
|
||||
}
|
||||
val distance = entity.distanceTo(possibleLeader)
|
||||
if (bestMatchingHerdLeader.tier < bestTier) {
|
||||
return@forEach // No need to check further if this leader is worse than the best found so far
|
||||
} else if ((distance > bestDistance && !possibleLeaderIsAlreadyALeader) && bestMatchingHerdLeader.tier == bestTier) {
|
||||
return@forEach // No need to check further if this leader is farther than the best found so far, unless they are already a leader - I'd prefer to join a pack
|
||||
} else {
|
||||
bestLeader = possibleLeader
|
||||
bestDistance = distance
|
||||
bestTier = bestMatchingHerdLeader.tier
|
||||
}
|
||||
}
|
||||
return bestLeader ?: currentLeaderEntity
|
||||
}
|
||||
|
||||
class Config : ObjectiveConfig {
|
||||
// How frequently to check for whether it should herd. Probably isn't that expensive but might use this for chance.
|
||||
val checkTicks: ExpressionOrEntityVariable = Either.left("60".asExpression())
|
||||
val tooFar: ExpressionOrEntityVariable = Either.left("8".asExpression())
|
||||
val closeEnough: ExpressionOrEntityVariable = Either.left("4".asExpression())
|
||||
val chance: ExpressionOrEntityVariable = Either.left("1 / 60".asExpression())
|
||||
|
||||
override fun getVariables(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
) = listOf(checkTicks, tooFar, closeEnough, chance).asVariables()
|
||||
|
||||
override fun create(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
): Objective {
|
||||
behaviourConfigurationContext.addMemories(MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES)
|
||||
behaviourConfigurationContext.addMemories(CobblemonMemories.HERD_LEADER)
|
||||
behaviourConfigurationContext.addMemories(CobblemonMemories.HERD_SIZE)
|
||||
return Herd(
|
||||
checkTicks = checkTicks.resolveInt(behaviourConfigurationContext.runtime),
|
||||
tooFar = tooFar.resolveFloat(behaviourConfigurationContext.runtime),
|
||||
closeEnough = closeEnough.resolveFloat(behaviourConfigurationContext.runtime),
|
||||
chance = chance.resolveFloat(behaviourConfigurationContext.runtime)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
-166
@@ -1,166 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.objective
|
||||
|
||||
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
|
||||
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
|
||||
import com.cobblemon.mod.common.api.ai.asVariables
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.MovementTracker
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.Distraction
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.idleBoredomDistraction
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.EntityFixation
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.MovementTrackerFixation
|
||||
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
|
||||
import com.cobblemon.mod.common.api.ai.resolveInt
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.util.asExpression
|
||||
import com.mojang.datafixers.util.Either
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.sounds.SoundEvents
|
||||
import net.minecraft.tags.FluidTags
|
||||
import net.minecraft.util.Mth
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.level.material.FluidState
|
||||
import kotlin.math.atan2
|
||||
|
||||
/**
|
||||
* Swimming Pokemon perform a cool little jump out of the water if they're near the surface
|
||||
*
|
||||
*/
|
||||
class JumpOutOfWater(
|
||||
val ticksBetweenAttempts: Int = 60,
|
||||
) : Objective {
|
||||
var inWater = false
|
||||
var remainingCooldownTicks = ticksBetweenAttempts
|
||||
var fixation: EntityFixation? = null
|
||||
override fun getDescription(entity: LivingEntity) = "Jump out of water"
|
||||
override fun perform(
|
||||
entity: LivingEntity,
|
||||
brain: BiggerBrain,
|
||||
activeObjectiveTags: MutableSet<String>
|
||||
): ObjectiveStatus {
|
||||
if (entity !is PokemonEntity) {
|
||||
return ObjectiveStatus.INVALID
|
||||
} else if (brain.isFixation(fixation)) {
|
||||
remainingCooldownTicks = ticksBetweenAttempts
|
||||
return ObjectiveStatus.FIXATED
|
||||
}
|
||||
|
||||
if (remainingCooldownTicks > 0) {
|
||||
remainingCooldownTicks--
|
||||
return ObjectiveStatus.ACTIVE
|
||||
}
|
||||
|
||||
if (entity.random.nextInt(CHANCE) != 0) {
|
||||
return ObjectiveStatus.INACTIVE
|
||||
} else {
|
||||
if (!brain.distract(idleBoredomDistraction)) return ObjectiveStatus.BLOCKED
|
||||
val direction = entity.motionDirection
|
||||
val x = direction.stepX
|
||||
val z = direction.stepZ
|
||||
val blockPos = entity.blockPosition()
|
||||
val offsets = OFFSET_MULTIPLIERS
|
||||
for (offset in offsets) {
|
||||
if (!this.isWater(entity, blockPos, x, z, offset) || !this.isAirAbove(entity, blockPos, x, z, offset)) {
|
||||
return ObjectiveStatus.ACTIVE
|
||||
}
|
||||
}
|
||||
|
||||
val newVelocity = entity.deltaMovement.add(direction.stepX.toDouble() * 0.6, 0.7, direction.stepZ.toDouble() * 0.6)
|
||||
entity.deltaMovement = newVelocity
|
||||
entity.getNavigation().stop()
|
||||
fixation = brain.fixate(
|
||||
fixation = JumpOutOfWaterFixation(
|
||||
this,
|
||||
movementTracker = null
|
||||
)
|
||||
)
|
||||
}
|
||||
return ObjectiveStatus.ACTIVE
|
||||
}
|
||||
|
||||
|
||||
class JumpOutOfWaterFixation(
|
||||
val objective: JumpOutOfWater,
|
||||
override val movementTracker: MovementTracker?,
|
||||
) : EntityFixation, MovementTrackerFixation {
|
||||
|
||||
override fun getDescription(entity: LivingEntity) = "Jumping out of water"
|
||||
override fun canDistract(distraction: Distraction, entity: LivingEntity): Boolean {
|
||||
return distraction.isDamage || distraction.isForce
|
||||
}
|
||||
|
||||
override fun onDistract(distraction: Distraction, entity: LivingEntity) {}
|
||||
|
||||
override fun tick(entity: LivingEntity, brain: BiggerBrain): Boolean {
|
||||
val bl: Boolean = objective.inWater
|
||||
if (!bl) {
|
||||
val fluidState: FluidState = entity.level().getFluidState(entity.blockPosition())
|
||||
objective.inWater = fluidState.`is`(FluidTags.WATER)
|
||||
}
|
||||
|
||||
if (objective.inWater && !bl) {
|
||||
// Transition tick from in water to out of water
|
||||
entity.playSound(SoundEvents.DOLPHIN_JUMP, 1.0f, 1.0f)
|
||||
}
|
||||
|
||||
val vec3d = entity.deltaMovement
|
||||
if (vec3d.y * vec3d.y < 0.029999999329447746 && entity.xRot != 0.0f) {
|
||||
entity.xRot = Mth.rotLerp(0.2f, entity.xRot, 0.0f)
|
||||
} else if (vec3d.length() > 9.999999747378752E-6) {
|
||||
val d = vec3d.horizontalDistance()
|
||||
val e = atan2(-vec3d.y, d) * 57.2957763671875
|
||||
entity.xRot = e.toFloat()
|
||||
}
|
||||
|
||||
val d: Double = vec3d.y
|
||||
return (!(d * d < 0.029999999329447746) || entity.xRot == 0.0f || !(Math.abs(entity.xRot) < 10.0f) || !entity.isInWater) && !entity.onGround()
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
class Config : ObjectiveConfig {
|
||||
val ticksBetweenAttempts: ExpressionOrEntityVariable = Either.left("60".asExpression())
|
||||
|
||||
override fun getVariables(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
) = listOf(
|
||||
ticksBetweenAttempts,
|
||||
).asVariables()
|
||||
|
||||
override fun create(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
): Objective {
|
||||
val ticksBetweenAttempts = ticksBetweenAttempts.resolveInt(behaviourConfigurationContext.runtime)
|
||||
return JumpOutOfWater(
|
||||
ticksBetweenAttempts = ticksBetweenAttempts,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun isWater(entity: PokemonEntity, pos: BlockPos, offsetX: Int, offsetZ: Int, multiplier: Int): Boolean {
|
||||
val blockPos = pos.offset(offsetX * multiplier, 0, offsetZ * multiplier)
|
||||
return entity.level().getFluidState(blockPos).`is`(FluidTags.WATER) && !entity.level().getBlockState(blockPos)
|
||||
.blocksMotion()
|
||||
}
|
||||
|
||||
private fun isAirAbove(entity: PokemonEntity, pos: BlockPos, offsetX: Int, offsetZ: Int, multiplier: Int): Boolean {
|
||||
return entity.level().getBlockState(pos.offset(offsetX * multiplier, 1, offsetZ * multiplier))
|
||||
.isAir && entity.level().getBlockState(pos.offset(offsetX * multiplier, 2, offsetZ * multiplier))
|
||||
.isAir
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val CHANCE = 10
|
||||
private val OFFSET_MULTIPLIERS = intArrayOf(0, 1, 4, 5, 6, 7)
|
||||
}
|
||||
}
|
||||
-136
@@ -1,136 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.objective
|
||||
|
||||
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
|
||||
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
|
||||
import com.cobblemon.mod.common.api.ai.asVariables
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.EntityFixation
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.LookingTracker
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.Distraction
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.LookingTrackerFixation
|
||||
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
|
||||
import com.cobblemon.mod.common.api.ai.resolveDouble
|
||||
import com.cobblemon.mod.common.api.ai.resolveInt
|
||||
import com.cobblemon.mod.common.util.asExpression
|
||||
import com.mojang.datafixers.util.Either
|
||||
import kotlin.math.pow
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.PathfinderMob
|
||||
import net.minecraft.world.entity.ai.behavior.EntityTracker
|
||||
import net.minecraft.world.entity.ai.memory.MemoryModuleType
|
||||
|
||||
/**
|
||||
* Randomly looks at nearby entities out of curiosity. Very easily distracted once doing so.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since December 29th, 2025
|
||||
*/
|
||||
class LookAtNearbyEntities(
|
||||
val maxDistance: Double = 10.0,
|
||||
val minDuration: Int = 20,
|
||||
val maxDuration: Int = 80
|
||||
) : Objective {
|
||||
var fixation: EntityFixation? = null
|
||||
|
||||
override fun getDescription(entity: LivingEntity) = "Look at nearby entities"
|
||||
override fun perform(
|
||||
entity: LivingEntity,
|
||||
brain: BiggerBrain,
|
||||
activeObjectiveTags: MutableSet<String>
|
||||
): ObjectiveStatus {
|
||||
if (brain.isFixation(fixation)) {
|
||||
return ObjectiveStatus.FIXATED
|
||||
} else if (brain.fixation != null) {
|
||||
fixation = null
|
||||
return ObjectiveStatus.BLOCKED
|
||||
}
|
||||
|
||||
val nearestVisibleLivingEntities = brain.getMemory(MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES)
|
||||
?: return ObjectiveStatus.INVALID
|
||||
|
||||
val closest = nearestVisibleLivingEntities.findClosest(
|
||||
{ otherEntity -> otherEntity.distanceToSqr(entity) <= maxDistance.pow(2) && !entity.hasPassenger(otherEntity) }
|
||||
).orElse(null)
|
||||
|
||||
if (closest == null) {
|
||||
return ObjectiveStatus.INACTIVE
|
||||
} else {
|
||||
// Become fixated
|
||||
fixation = brain.fixate(
|
||||
IdleLookFixation(
|
||||
this,
|
||||
closest,
|
||||
(minDuration..maxDuration).random()
|
||||
)
|
||||
)
|
||||
return ObjectiveStatus.FIXATED
|
||||
}
|
||||
}
|
||||
|
||||
class IdleLookFixation(
|
||||
val objective: LookAtNearbyEntities,
|
||||
val targetEntity: LivingEntity,
|
||||
var remainingTicks: Int
|
||||
) : EntityFixation, LookingTrackerFixation {
|
||||
override var lookingTracker: LookingTracker? = null
|
||||
|
||||
override fun getDescription(entity: LivingEntity): String {
|
||||
return "Looking at a nearby entity ($remainingTicks ticks left)"
|
||||
}
|
||||
|
||||
override fun canDistract(distraction: Distraction, entity: LivingEntity): Boolean {
|
||||
// Bro literally anything will distract me
|
||||
return true
|
||||
}
|
||||
|
||||
override fun tick(entity: LivingEntity, brain: BiggerBrain): Boolean {
|
||||
if (targetEntity.isRemoved || entity !is PathfinderMob) {
|
||||
return false
|
||||
}
|
||||
if (lookingTracker == null) {
|
||||
lookingTracker = LookingTracker(entity, EntityTracker(targetEntity, true))
|
||||
}
|
||||
val lookingTracker = lookingTracker!!
|
||||
remainingTicks--
|
||||
lookingTracker.tick()
|
||||
return remainingTicks > 0
|
||||
}
|
||||
}
|
||||
|
||||
class Config : ObjectiveConfig {
|
||||
val maxDistance: ExpressionOrEntityVariable = Either.left("10.0".asExpression())
|
||||
val minDuration: ExpressionOrEntityVariable = Either.left("20".asExpression())
|
||||
val maxDuration: ExpressionOrEntityVariable = Either.left("80".asExpression())
|
||||
|
||||
override fun getVariables(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
) = listOf(
|
||||
maxDistance,
|
||||
minDuration,
|
||||
maxDuration
|
||||
).asVariables()
|
||||
|
||||
override fun create(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
): Objective {
|
||||
val maxDistance = maxDistance.resolveDouble(behaviourConfigurationContext.runtime)
|
||||
val minDuration = minDuration.resolveInt(behaviourConfigurationContext.runtime)
|
||||
val maxDuration = maxDuration.resolveInt(behaviourConfigurationContext.runtime)
|
||||
return LookAtNearbyEntities(
|
||||
maxDistance = maxDistance,
|
||||
minDuration = minDuration,
|
||||
maxDuration = maxDuration
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
-65
@@ -1,65 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.objective
|
||||
|
||||
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.MovementTrackerFixation
|
||||
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
|
||||
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
|
||||
import com.cobblemon.mod.common.entity.OmniPathingEntity
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
|
||||
class MaintainFluidY : Objective {
|
||||
override fun getDescription(entity: LivingEntity) = "Maintain Y in fluids"
|
||||
override fun perform(
|
||||
entity: LivingEntity,
|
||||
brain: BiggerBrain,
|
||||
activeObjectiveTags: MutableSet<String>
|
||||
): ObjectiveStatus {
|
||||
if (entity !is OmniPathingEntity) {
|
||||
return ObjectiveStatus.INVALID
|
||||
}
|
||||
|
||||
val fixation = brain.fixation
|
||||
if (fixation is MovementTrackerFixation) {
|
||||
return ObjectiveStatus.BLOCKED
|
||||
}
|
||||
|
||||
if (entity.isFlying()) {
|
||||
return ObjectiveStatus.INACTIVE
|
||||
}
|
||||
|
||||
if (entity.canSwimInWater() && entity.isUnderWater) {
|
||||
entity.isNoGravity = true
|
||||
} else if (entity.canSwimInLava() && entity.isInLava) {
|
||||
entity.isNoGravity = true
|
||||
} else {
|
||||
entity.isNoGravity = false
|
||||
}
|
||||
|
||||
return if (entity.isNoGravity) ObjectiveStatus.ACTIVE else ObjectiveStatus.INACTIVE
|
||||
}
|
||||
|
||||
class Config : ObjectiveConfig {
|
||||
override fun getVariables(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
): List<MoLangConfigVariable> {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
override fun create(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
): Objective {
|
||||
return MaintainFluidY()
|
||||
}
|
||||
}
|
||||
}
|
||||
-251
@@ -1,251 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.objective
|
||||
|
||||
import com.bedrockk.molang.Expression
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.api.ai.*
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.*
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.Distraction
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.comfortDistraction
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.EntityFixation
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.MovementTrackerFixation
|
||||
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
|
||||
import com.cobblemon.mod.common.entity.ai.CobblemonWalkTarget
|
||||
import com.cobblemon.mod.common.util.*
|
||||
import com.mojang.datafixers.util.Either
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.PathfinderMob
|
||||
import net.minecraft.world.entity.TamableAnimal
|
||||
import net.minecraft.world.entity.ai.behavior.BehaviorUtils
|
||||
import net.minecraft.world.entity.ai.util.RandomPos
|
||||
import net.minecraft.world.level.pathfinder.PathType
|
||||
import net.minecraft.world.phys.Vec3
|
||||
|
||||
|
||||
/**
|
||||
* Tries to path into water if the entity usually avoids land.
|
||||
* Will pathfind through land even if the entity usually filters out land nodes.
|
||||
* If water cannot be found in the entity's wander range it'll wander randomly in a spiral-ish pattern
|
||||
*
|
||||
* @author JazzMcNade
|
||||
* @since January 25th, 2026
|
||||
*/
|
||||
class MoveIntoWater(
|
||||
val ticksBetweenAttempts: Int = 60,
|
||||
val maxDistance: Expression = "14.0".asExpression(),
|
||||
val speedMultiplier: Expression = "0.4".asExpression()
|
||||
) : Objective {
|
||||
var movementTask: EntityFixation? = null
|
||||
var remainingCooldownTicks = ticksBetweenAttempts
|
||||
var wanderDirection: Vec3? = null
|
||||
var isWanderTurnDirectionClockwise = true
|
||||
var wanderTurnChance = BASE_TURN_CHANCE
|
||||
var failCount = 0
|
||||
override fun getDescription(entity: LivingEntity) = "Move into water if currently out of water"
|
||||
override fun perform(
|
||||
entity: LivingEntity,
|
||||
brain: BiggerBrain,
|
||||
activeObjectiveTags: MutableSet<String>
|
||||
): ObjectiveStatus {
|
||||
if (entity !is TamableAnimal) {
|
||||
return ObjectiveStatus.INVALID
|
||||
}
|
||||
|
||||
if (entity.isInWater) {
|
||||
// All good, nothing to do here
|
||||
return ObjectiveStatus.INACTIVE
|
||||
}
|
||||
|
||||
if (brain.isFixation(movementTask)) {
|
||||
remainingCooldownTicks = ticksBetweenAttempts
|
||||
return ObjectiveStatus.FIXATED
|
||||
}
|
||||
|
||||
if (remainingCooldownTicks > 0) {
|
||||
remainingCooldownTicks--
|
||||
return ObjectiveStatus.INACTIVE
|
||||
} else {
|
||||
remainingCooldownTicks = ticksBetweenAttempts
|
||||
}
|
||||
|
||||
if (brain.distract(comfortDistraction)) {
|
||||
|
||||
val maxDistance = mainThreadRuntime.resolveInt(maxDistance)
|
||||
val speedMultiplier = mainThreadRuntime.resolveFloat(speedMultiplier)
|
||||
|
||||
val runtime = mainThreadRuntime
|
||||
val wanderControl = entity.brain.getMemorySafely(CobblemonMemories.WANDER_CONTROL).orElse(null)
|
||||
val allowLand = wanderControl?.allowLand
|
||||
?: runtime.resolveBoolean("(q.entity.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
@@ -1,258 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.objective
|
||||
|
||||
import com.bedrockk.molang.Expression
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.api.ai.*
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.*
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.Distraction
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.comfortDistraction
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.EntityFixation
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.MovementTrackerFixation
|
||||
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
|
||||
import com.cobblemon.mod.common.entity.ai.CobblemonWalkTarget
|
||||
import com.cobblemon.mod.common.util.*
|
||||
import com.mojang.datafixers.util.Either
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.PathfinderMob
|
||||
import net.minecraft.world.entity.TamableAnimal
|
||||
import net.minecraft.world.entity.ai.util.LandRandomPos
|
||||
import net.minecraft.world.entity.ai.util.RandomPos
|
||||
import net.minecraft.world.level.pathfinder.PathType
|
||||
import net.minecraft.world.phys.Vec3
|
||||
|
||||
|
||||
/**
|
||||
* Tries to path out of water if the entity usually water to avoid water.
|
||||
* Will pathfind through water even if the entity usually filters out water path nodes.
|
||||
* If land cannot be found in the entity's wander range it'll wander randomly in a spiral-ish pattern
|
||||
*
|
||||
* @author JazzMcNade
|
||||
* @since January 17th, 2026
|
||||
*/
|
||||
class MoveOutOfWater(
|
||||
val ticksBetweenAttempts: Int = 60,
|
||||
val maxDistance: Expression = "14.0".asExpression(),
|
||||
val speedMultiplier: Expression = "0.4".asExpression()
|
||||
) : Objective {
|
||||
var movementTask: EntityFixation? = null
|
||||
var remainingCooldownTicks = ticksBetweenAttempts
|
||||
var wanderDirection: Vec3? = null
|
||||
var isWanderTurnDirectionClockwise = true
|
||||
var wanderTurnChance = BASE_TURN_CHANCE
|
||||
var failCount = 0
|
||||
override fun getDescription(entity: LivingEntity) = "Move out of water"
|
||||
override fun perform(
|
||||
entity: LivingEntity,
|
||||
brain: BiggerBrain,
|
||||
activeObjectiveTags: MutableSet<String>
|
||||
): ObjectiveStatus {
|
||||
if (entity !is TamableAnimal) {
|
||||
return ObjectiveStatus.INVALID
|
||||
}
|
||||
|
||||
if (brain.isFixation(movementTask)) {
|
||||
remainingCooldownTicks = ticksBetweenAttempts
|
||||
return ObjectiveStatus.FIXATED
|
||||
}
|
||||
if (!entity.isInWater || entity.isUnderWater) {
|
||||
// Focus on not drowning first
|
||||
// TODO: 100% boned if in an underwater cave
|
||||
return ObjectiveStatus.INACTIVE
|
||||
}
|
||||
|
||||
|
||||
if (remainingCooldownTicks > 0) {
|
||||
remainingCooldownTicks--
|
||||
return ObjectiveStatus.INACTIVE
|
||||
} else {
|
||||
remainingCooldownTicks = ticksBetweenAttempts
|
||||
}
|
||||
|
||||
val maxDistance = mainThreadRuntime.resolveInt(maxDistance)
|
||||
val speedMultiplier = mainThreadRuntime.resolveFloat(speedMultiplier)
|
||||
|
||||
val runtime = mainThreadRuntime
|
||||
val wanderControl = entity.brain.getMemorySafely(CobblemonMemories.WANDER_CONTROL).orElse(null)
|
||||
val allowLand = wanderControl?.allowLand
|
||||
?: runtime.resolveBoolean("(q.entity.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
@@ -1,166 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.objective
|
||||
|
||||
import com.bedrockk.molang.Expression
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.api.ai.*
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.*
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.Distraction
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.comfortDistraction
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.EntityFixation
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.MovementTrackerFixation
|
||||
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
|
||||
import com.cobblemon.mod.common.api.pokemon.status.Statuses
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.util.*
|
||||
import com.mojang.datafixers.util.Either
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.ai.memory.WalkTarget
|
||||
import net.minecraft.world.phys.AABB
|
||||
|
||||
|
||||
/**
|
||||
* Looks for a resting place if the Pokemon is drowsy, and if available paths to it.
|
||||
*
|
||||
* @author JazzMcNade
|
||||
* @since February 3rd, 2026
|
||||
*/
|
||||
class MoveToRestingPlace(
|
||||
val ticksBetweenAttempts: Int = 80,
|
||||
val horizontalSearchDistance: Expression = "14.0".asExpression(),
|
||||
val verticalSearchDistance: Expression = "6.0".asExpression(),
|
||||
val onlyFromStatus: Expression = "false".asExpression(),
|
||||
val speedMultiplier: Expression = "0.4".asExpression()
|
||||
) : Objective {
|
||||
var movementTask: EntityFixation? = null
|
||||
var remainingCooldownTicks = ticksBetweenAttempts
|
||||
override fun getDescription(entity: LivingEntity) = "Moving to a resting place"
|
||||
override fun perform(
|
||||
entity: LivingEntity,
|
||||
brain: BiggerBrain,
|
||||
activeObjectiveTags: MutableSet<String>
|
||||
): ObjectiveStatus {
|
||||
if (entity !is PokemonEntity) {
|
||||
return ObjectiveStatus.INVALID
|
||||
}
|
||||
brain.getMemory(CobblemonMemories.POKEMON_DROWSY)
|
||||
?: return ObjectiveStatus.INACTIVE
|
||||
|
||||
|
||||
if (brain.isFixation(movementTask)) {
|
||||
remainingCooldownTicks = ticksBetweenAttempts
|
||||
return ObjectiveStatus.FIXATED
|
||||
}
|
||||
|
||||
if (remainingCooldownTicks > 0) {
|
||||
remainingCooldownTicks--
|
||||
}
|
||||
|
||||
val hasSleepStatus = entity.pokemon.status?.status === Statuses.SLEEP
|
||||
if (hasSleepStatus) return ObjectiveStatus.INACTIVE // Already sleeping
|
||||
|
||||
if (entity.behaviour.resting.canSleep) {
|
||||
if (mainThreadRuntime.resolveBoolean(onlyFromStatus)) {
|
||||
return ObjectiveStatus.BLOCKED
|
||||
}
|
||||
|
||||
if (remainingCooldownTicks > 0 || entity.canSleepAt(entity.blockPosition().below())) {
|
||||
// Can sleep where we're at, no need to go anywhere
|
||||
return ObjectiveStatus.INACTIVE
|
||||
}
|
||||
|
||||
val hSearchDistance = mainThreadRuntime.resolveDouble(horizontalSearchDistance)
|
||||
val vSearchDistance = mainThreadRuntime.resolveDouble(verticalSearchDistance)
|
||||
|
||||
val positionList = entity.level()
|
||||
.getBlockPositions(AABB.ofSize(entity.position(), hSearchDistance, vSearchDistance, hSearchDistance))
|
||||
.filter(entity::canSleepAt)
|
||||
val position = if (positionList.isEmpty()) null else positionList.random(1).firstOrNull()
|
||||
|
||||
if (position != null && brain.distract(comfortDistraction)) {
|
||||
val speedMultiplier = mainThreadRuntime.resolveFloat(speedMultiplier)
|
||||
val movementTracker = RegularMovementTracker.attempt(
|
||||
entity = entity,
|
||||
walkTarget = WalkTarget(
|
||||
position,
|
||||
speedMultiplier,
|
||||
0
|
||||
)
|
||||
)
|
||||
if (movementTracker != null) {
|
||||
// Become fixated
|
||||
movementTask = brain.fixate(
|
||||
fixation = MoveToRestingPlaceFixation(
|
||||
movementTracker = movementTracker,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
} else {
|
||||
return ObjectiveStatus.INACTIVE
|
||||
}
|
||||
return ObjectiveStatus.ACTIVE
|
||||
}
|
||||
return ObjectiveStatus.INACTIVE
|
||||
}
|
||||
|
||||
|
||||
class MoveToRestingPlaceFixation(
|
||||
override val movementTracker: MovementTracker,
|
||||
) : EntityFixation, MovementTrackerFixation {
|
||||
override fun getDescription(entity: LivingEntity) = "Moving to a resting place"
|
||||
override fun canDistract(distraction: Distraction, entity: LivingEntity): Boolean {
|
||||
return distraction.isDamage || distraction.isForce || movementTracker.lastStatus == MovementStatus.COMPLETED
|
||||
}
|
||||
|
||||
override fun onDistract(distraction: Distraction, entity: LivingEntity) {
|
||||
movementTracker.stop()
|
||||
}
|
||||
|
||||
override fun tick(entity: LivingEntity, brain: BiggerBrain): Boolean {
|
||||
val movementStatus = tickMovementFixation()
|
||||
if (movementStatus == MovementStatus.COMPLETED) return false
|
||||
return movementStatus != MovementStatus.FAILED
|
||||
}
|
||||
}
|
||||
|
||||
class Config : ObjectiveConfig {
|
||||
val ticksBetweenAttempts: ExpressionOrEntityVariable = Either.left("60".asExpression())
|
||||
val horizontalSearchDistance: ExpressionOrEntityVariable = Either.left("10.0".asExpression())
|
||||
val verticalSearchDistance: ExpressionOrEntityVariable = Either.left("10.0".asExpression())
|
||||
val speedMultiplier: ExpressionOrEntityVariable = Either.left("0.4".asExpression())
|
||||
val onlyFromStatus: ExpressionOrEntityVariable = Either.left("false".asExpression())
|
||||
|
||||
override fun getVariables(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
) = listOf(
|
||||
ticksBetweenAttempts,
|
||||
horizontalSearchDistance,
|
||||
verticalSearchDistance,
|
||||
speedMultiplier,
|
||||
onlyFromStatus
|
||||
).asVariables()
|
||||
|
||||
override fun create(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
): Objective {
|
||||
val ticksBetweenAttempts = ticksBetweenAttempts.resolveInt(behaviourConfigurationContext.runtime)
|
||||
return MoveToRestingPlace(
|
||||
ticksBetweenAttempts = ticksBetweenAttempts,
|
||||
horizontalSearchDistance = horizontalSearchDistance.asSimplifiedExpression(entity),
|
||||
verticalSearchDistance = verticalSearchDistance.asSimplifiedExpression(entity),
|
||||
speedMultiplier = speedMultiplier.asSimplifiedExpression(entity),
|
||||
onlyFromStatus = onlyFromStatus.asSimplifiedExpression(entity)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
-177
@@ -1,177 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.objective
|
||||
|
||||
import com.bedrockk.molang.Expression
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.api.Priority
|
||||
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
|
||||
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
|
||||
import com.cobblemon.mod.common.api.ai.asSimplifiedExpression
|
||||
import com.cobblemon.mod.common.api.ai.asVariables
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.DistractionType
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.AttackFixation
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.EntityFixation
|
||||
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
|
||||
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.util.asExpression
|
||||
import com.cobblemon.mod.common.util.mainThreadRuntime
|
||||
import com.cobblemon.mod.common.util.resolveBoolean
|
||||
import com.cobblemon.mod.common.util.resolveFloat
|
||||
import com.cobblemon.mod.common.util.resolveInt
|
||||
import com.cobblemon.mod.common.util.withQueryValue
|
||||
import com.mojang.datafixers.util.Either
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.ai.memory.MemoryModuleType
|
||||
|
||||
|
||||
class NeutralBehaviour(
|
||||
speedMultiplier: Expression = "0.5".asExpression(), // How do we transfer speed to the attack behaviour? Do we want to?
|
||||
engageRange: Expression = "5.0".asExpression(),
|
||||
disengageRange: Expression = "20.0".asExpression(),
|
||||
condition: Expression = "true".asExpression(),
|
||||
targetCondition: Expression = "true".asExpression(), // target condition (will be checked on entity interaction)
|
||||
description: Expression = "'Neutral Behaviour'".asExpression(),
|
||||
private val maxAttackDuration: Expression = "300".asExpression(),
|
||||
private val retaliationHitCount: Expression = "1".asExpression()
|
||||
) : EntityResponseObjective(
|
||||
speedMultiplier = speedMultiplier, // How do we transfer speed to the attack behaviour? Do we want to?
|
||||
engageRange = engageRange,
|
||||
disengageRange = disengageRange,
|
||||
condition = condition, // Self/environment condition check
|
||||
targetCondition = targetCondition, // target condition (will be checked on entity interaction)
|
||||
description = description,
|
||||
) {
|
||||
override val observedMemories = listOf(MemoryModuleType.HURT_BY_ENTITY, CobblemonMemories.ATTACK_TARGET_DATA)
|
||||
private var fixation: EntityFixation? = null
|
||||
|
||||
override fun perform(
|
||||
entity: LivingEntity,
|
||||
brain: BiggerBrain,
|
||||
activeObjectiveTags: MutableSet<String>
|
||||
): ObjectiveStatus {
|
||||
if (entity !is PokemonEntity || !entity.isAlive) return ObjectiveStatus.INVALID
|
||||
|
||||
if (trackedEntity != null) {
|
||||
val attackTarget = brain.getMemory(MemoryModuleType.ATTACK_TARGET)
|
||||
val avoidTarget = brain.getMemory(MemoryModuleType.AVOID_TARGET)
|
||||
if (attackTarget != null && trackedEntity?.uuid == attackTarget.uuid) {
|
||||
// Check if we continue
|
||||
return ObjectiveStatus.ACTIVE
|
||||
} else if (avoidTarget != null && trackedEntity?.uuid === avoidTarget.uuid) {
|
||||
// Check if we continue
|
||||
val runtime = mainThreadRuntime.withQueryValue("entity", (trackedEntity!!).asMostSpecificMoLangValue())
|
||||
val disengageDistance = runtime.resolveFloat(disengageRange)
|
||||
if (disengageDistance < entity.distanceTo(trackedEntity!!)) {
|
||||
brain.eraseMemory(MemoryModuleType.AVOID_TARGET)
|
||||
trackedEntity = null
|
||||
}
|
||||
} else {
|
||||
// Something has overridden us
|
||||
trackedEntity = null
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return if (trackedEntity != null) ObjectiveStatus.ACTIVE else ObjectiveStatus.INACTIVE
|
||||
}
|
||||
|
||||
override fun onRegister(entity: LivingEntity, brain: BiggerBrain) {
|
||||
brain.subscribe(
|
||||
priority = Priority.LOW,
|
||||
memory = MemoryModuleType.HURT_BY_ENTITY
|
||||
) { value, labels ->
|
||||
if (value != null) {
|
||||
// Something just smacked us, see if we should get mad about it.
|
||||
val livingEntity = value
|
||||
val runtime = mainThreadRuntime.withQueryValue("entity", (livingEntity).asMostSpecificMoLangValue())
|
||||
val shouldTarget = runtime.resolveBoolean(condition)
|
||||
if (shouldTarget && brain.fixation !is AttackFixation) {
|
||||
brain.setMemory(
|
||||
MemoryModuleType.ATTACK_TARGET, livingEntity,
|
||||
runtime.resolveInt(maxAttackDuration).toLong()
|
||||
)
|
||||
brain.eraseMemory(MemoryModuleType.AVOID_TARGET)
|
||||
trackedEntity = livingEntity
|
||||
fixation = brain.fixate(
|
||||
AttackFixation(
|
||||
targetEntity = trackedEntity!!,
|
||||
speedMultiplier = speedMultiplier,
|
||||
distractionTypes = setOf(DistractionType.FORCE),
|
||||
availableMoves = getRealTimeMoves(entity)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
return@subscribe true
|
||||
}
|
||||
|
||||
brain.subscribe(
|
||||
priority = Priority.LOW,
|
||||
memory = CobblemonMemories.ATTACK_TARGET_DATA
|
||||
) { value, labels ->
|
||||
if (value != null) {
|
||||
// We smacked them, check if it's time for tactical retreat
|
||||
val attackData = value
|
||||
val livingEntity = brain.getMemory(MemoryModuleType.ATTACK_TARGET) ?: return@subscribe true
|
||||
val runtime = mainThreadRuntime.withQueryValue("entity", (livingEntity).asMostSpecificMoLangValue())
|
||||
if (attackData.hitsLanded >= runtime.resolveInt(retaliationHitCount)) {
|
||||
// Justice served, carry on
|
||||
brain.eraseMemory(MemoryModuleType.ATTACK_TARGET)
|
||||
brain.eraseMemory(CobblemonMemories.ATTACK_TARGET_DATA)
|
||||
brain.setMemory(MemoryModuleType.AVOID_TARGET, trackedEntity)
|
||||
}
|
||||
}
|
||||
return@subscribe true
|
||||
}
|
||||
}
|
||||
|
||||
class Config : ObjectiveConfig {
|
||||
val speedMultiplier: ExpressionOrEntityVariable = Either.left("0.5".asExpression())
|
||||
val engageRange: ExpressionOrEntityVariable = Either.left("10.0".asExpression())
|
||||
val disengageRange: ExpressionOrEntityVariable = Either.left("10.0".asExpression())
|
||||
val condition: ExpressionOrEntityVariable = Either.left("true".asExpression())
|
||||
val targetCondition: ExpressionOrEntityVariable = Either.left("false".asExpression())
|
||||
val maxAttackDuration: ExpressionOrEntityVariable = Either.left("300".asExpression())
|
||||
val retaliationHitCount: ExpressionOrEntityVariable = Either.left("1".asExpression())
|
||||
val description: ExpressionOrEntityVariable = Either.left("'Neutral Behaviour'".asExpression())
|
||||
override fun getVariables(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
): List<MoLangConfigVariable> {
|
||||
return listOf(
|
||||
condition,
|
||||
targetCondition,
|
||||
engageRange,
|
||||
disengageRange,
|
||||
retaliationHitCount,
|
||||
description
|
||||
).asVariables()
|
||||
}
|
||||
|
||||
override fun create(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
): Objective {
|
||||
return NeutralBehaviour(
|
||||
condition = condition.asSimplifiedExpression(entity),
|
||||
targetCondition = condition.asSimplifiedExpression(entity),
|
||||
speedMultiplier = speedMultiplier.asSimplifiedExpression(entity),
|
||||
engageRange = engageRange.asSimplifiedExpression(entity),
|
||||
disengageRange = disengageRange.asSimplifiedExpression(entity),
|
||||
maxAttackDuration = maxAttackDuration.asSimplifiedExpression(entity),
|
||||
description = description.asSimplifiedExpression(entity),
|
||||
retaliationHitCount = retaliationHitCount.asSimplifiedExpression(entity),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
-42
@@ -1,42 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.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
|
||||
/** Runs when an objective is added to a [BiggerBrain]. Prime opportunity to register memory subscriptions. */
|
||||
fun onRegister(entity: LivingEntity, brain: BiggerBrain) {}
|
||||
}
|
||||
-28
@@ -1,28 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.objective
|
||||
|
||||
/**
|
||||
* The current status of an [Objective]. Used for the debug overlay.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since December 29th, 2025
|
||||
*/
|
||||
enum class ObjectiveStatus {
|
||||
/** States that this objective owns the current fixation. */
|
||||
FIXATED,
|
||||
/** States that the objective's logic is ongoing and not impeded. */
|
||||
ACTIVE,
|
||||
/** States that the objective is not performing any meaningful logic right now but isn't actively blocked. */
|
||||
INACTIVE,
|
||||
/** States that there are other objectives, fixations, etc. in play which prevents the objective from being performed. */
|
||||
BLOCKED,
|
||||
/** States that the objective is not valid and shouldn't have been registered on the entity in the first place, such as when placed on the wrong entity type. */
|
||||
INVALID
|
||||
}
|
||||
-234
@@ -1,234 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.objective
|
||||
|
||||
import com.bedrockk.molang.Expression
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
|
||||
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
|
||||
import com.cobblemon.mod.common.api.ai.asSimplifiedExpression
|
||||
import com.cobblemon.mod.common.api.ai.asVariables
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.LookingTracker
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.MovementStatus
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.MovementTracker
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.RegularMovementTracker
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.Distraction
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.idleBoredomDistraction
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.EntityFixation
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.LookAndMoveFixation
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.LookingTrackerFixation
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.MovementTrackerFixation
|
||||
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
|
||||
import com.cobblemon.mod.common.api.ai.resolveInt
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.util.asExpression
|
||||
import com.cobblemon.mod.common.util.getMemorySafely
|
||||
import com.cobblemon.mod.common.util.mainThreadRuntime
|
||||
import com.cobblemon.mod.common.util.resolveDouble
|
||||
import com.cobblemon.mod.common.util.resolveFloat
|
||||
import com.cobblemon.mod.common.util.withQueryValue
|
||||
import com.mojang.datafixers.util.Either
|
||||
import net.minecraft.sounds.SoundEvents
|
||||
import net.minecraft.world.entity.Entity
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.PathfinderMob
|
||||
import net.minecraft.world.entity.ai.memory.MemoryModuleType
|
||||
import net.minecraft.world.entity.ai.memory.WalkTarget
|
||||
import net.minecraft.world.entity.item.ItemEntity
|
||||
import net.minecraft.world.item.ItemStack
|
||||
import net.minecraft.world.level.GameRules
|
||||
import kotlin.math.ceil
|
||||
import kotlin.math.min
|
||||
|
||||
/**
|
||||
* Paths to whatever item entity is marked by NEAREST_VISIBLE_WANTED_ITEM
|
||||
* Upon reaching the target attempts to place it in held item slot
|
||||
* held item slot if possible.
|
||||
* Requires NEARBY_SWEET_BERRY_BUSH memory
|
||||
*
|
||||
* @author JazzMcNade
|
||||
* @since February 8th, 2026
|
||||
*/
|
||||
class PickupItem(
|
||||
val ticksBetweenAttempts: Int = 60,
|
||||
val maxDistance: Expression = "14.0".asExpression(),
|
||||
val speedMultiplier: Expression = "0.4".asExpression(),
|
||||
val maxReach: Expression = "1.0".asExpression(),
|
||||
) : Objective {
|
||||
var collectItemTask: EntityFixation? = null
|
||||
var remainingCooldownTicks = ticksBetweenAttempts
|
||||
|
||||
override fun getDescription(entity: LivingEntity) = "Pickup item"
|
||||
override fun perform(
|
||||
entity: LivingEntity,
|
||||
brain: BiggerBrain,
|
||||
activeObjectiveTags: MutableSet<String>
|
||||
): ObjectiveStatus {
|
||||
if (entity !is PokemonEntity) {
|
||||
return ObjectiveStatus.INVALID
|
||||
} else if (brain.isFixation(collectItemTask)) {
|
||||
remainingCooldownTicks = ticksBetweenAttempts
|
||||
return ObjectiveStatus.FIXATED
|
||||
}
|
||||
|
||||
val itemEntity = entity.biggerBrain.getMemory(MemoryModuleType.NEAREST_VISIBLE_WANTED_ITEM) ?: return ObjectiveStatus.INACTIVE
|
||||
|
||||
if (remainingCooldownTicks > 0) {
|
||||
remainingCooldownTicks--
|
||||
return ObjectiveStatus.ACTIVE
|
||||
}
|
||||
|
||||
mainThreadRuntime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
|
||||
val speedMultiplier = mainThreadRuntime.resolveFloat(speedMultiplier)
|
||||
|
||||
// See if the item can be picked up without needing to path to it
|
||||
val success = tryPickupItem(entity, itemEntity)
|
||||
|
||||
if (!success) {
|
||||
// Item is too far away, need to path to it
|
||||
if (remainingCooldownTicks <= 0 && brain.distract(idleBoredomDistraction)) {
|
||||
remainingCooldownTicks = ticksBetweenAttempts
|
||||
val horizontalHitboxRadius = maxOf(entity.boundingBox.xsize, entity.boundingBox.zsize) / 2
|
||||
val movementTracker = RegularMovementTracker.attempt(
|
||||
entity = entity,
|
||||
walkTarget = WalkTarget(
|
||||
itemEntity,
|
||||
speedMultiplier,
|
||||
min(ceil(horizontalHitboxRadius).toInt() - 1, 1),
|
||||
)
|
||||
)
|
||||
|
||||
if (movementTracker != null) {
|
||||
val lookingTracker = LookingTracker(entity = entity, lookTarget = movementTracker.walkTarget.target)
|
||||
collectItemTask = brain.fixate(
|
||||
fixation = PickupItemFixation(
|
||||
objective = this,
|
||||
itemEntity = itemEntity,
|
||||
movementTracker = movementTracker,
|
||||
lookingTracker = lookingTracker,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
remainingCooldownTicks = ticksBetweenAttempts
|
||||
}
|
||||
return ObjectiveStatus.ACTIVE
|
||||
}
|
||||
|
||||
fun tryPickupItem(entity: PokemonEntity, itemEntity: ItemEntity) : Boolean {
|
||||
val maxReach = mainThreadRuntime.resolveDouble(maxReach) + maxOf(entity.boundingBox.xsize, entity.boundingBox.zsize) / 2
|
||||
if (itemEntity.distanceTo(entity) > maxReach || !entity.hasLineOfSight(itemEntity)) {
|
||||
return false
|
||||
}
|
||||
|
||||
entity.brain.eraseMemory(MemoryModuleType.NEAREST_VISIBLE_WANTED_ITEM)
|
||||
entity.take(itemEntity, 1) // This creates the effect of the item going into the entity along with the item pickup sound
|
||||
val stack = entity.pokemon.swapHeldItem(itemEntity.item)
|
||||
if (!stack.isEmpty && !entity.level().isClientSide) {
|
||||
dropItem(entity, stack)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
class PickupItemFixation(
|
||||
val objective: PickupItem,
|
||||
val itemEntity: ItemEntity,
|
||||
override val movementTracker: MovementTracker,
|
||||
override val lookingTracker : LookingTracker,
|
||||
): EntityFixation, MovementTrackerFixation, LookingTrackerFixation {
|
||||
|
||||
override fun getDescription(entity: LivingEntity) = "Moving to desired item"
|
||||
override fun canDistract(distraction: Distraction, entity: LivingEntity): Boolean {
|
||||
return distraction.isDamage || distraction.isForce || (movementTracker.lastStatus == MovementStatus.COMPLETED && entity.brain.getMemorySafely(
|
||||
CobblemonMemories.NEARBY_SWEET_BERRY_BUSH).orElse(null) != null)
|
||||
}
|
||||
|
||||
override fun onDistract(distraction: Distraction, entity: LivingEntity) {
|
||||
movementTracker.stop()
|
||||
}
|
||||
|
||||
override fun tick(entity: LivingEntity, brain: BiggerBrain): Boolean {
|
||||
lookingTracker.tick()
|
||||
movementTracker.tick()
|
||||
|
||||
if (movementTracker.lastStatus == MovementStatus.COMPLETED || movementTracker.lastStatus == MovementStatus.FAILED) {
|
||||
// May have failed but let's check if we're still close enoug to grab it
|
||||
val world = entity.level()
|
||||
|
||||
if (entity !is PokemonEntity) return false
|
||||
|
||||
if (!world.gameRules.getRule(GameRules.RULE_MOBGRIEFING).get()) {
|
||||
// Not allowed to alter the world
|
||||
return false
|
||||
}
|
||||
if (!itemEntity.isAlive || !entity.pokemon.canDropHeldItem) {
|
||||
return false
|
||||
}
|
||||
|
||||
val succeeded = objective.tryPickupItem(entity, itemEntity)
|
||||
|
||||
if (!succeeded && movementTracker.lastStatus == MovementStatus.FAILED) {
|
||||
// Cannot reach, give up for a while
|
||||
entity.brain.setMemoryWithExpiry(CobblemonMemories.DISABLE_WALK_TO_WANTED_ITEM, true, 200)
|
||||
entity.brain.eraseMemory(MemoryModuleType.NEAREST_VISIBLE_WANTED_ITEM)
|
||||
}
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
class Config : ObjectiveConfig {
|
||||
val ticksBetweenAttempts: ExpressionOrEntityVariable = Either.left("60".asExpression())
|
||||
val maxDistance: ExpressionOrEntityVariable = Either.left("10.0".asExpression())
|
||||
val speedMultiplier: ExpressionOrEntityVariable = Either.left("0.4".asExpression())
|
||||
|
||||
override fun getVariables(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
) = listOf(
|
||||
ticksBetweenAttempts,
|
||||
maxDistance,
|
||||
speedMultiplier
|
||||
).asVariables()
|
||||
|
||||
override fun create(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): Objective {
|
||||
val ticksBetweenAttempts = ticksBetweenAttempts.resolveInt(behaviourConfigurationContext.runtime)
|
||||
return PickupItem(
|
||||
ticksBetweenAttempts = ticksBetweenAttempts,
|
||||
maxDistance = maxDistance.asSimplifiedExpression(entity),
|
||||
speedMultiplier = speedMultiplier.asSimplifiedExpression(entity)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun dropItem(entity: Entity, stack: ItemStack) {
|
||||
val itemEntity = ItemEntity(
|
||||
entity.level(),
|
||||
entity.x + entity.lookAngle.x,
|
||||
entity.y + 1.0,
|
||||
entity.z + entity.lookAngle.z,
|
||||
stack
|
||||
)
|
||||
itemEntity.setPickUpDelay(40)
|
||||
itemEntity.setThrower(entity)
|
||||
entity.playSound(SoundEvents.FOX_SPIT, 1.0f, 1.0f) // TODO: customize this sound?
|
||||
entity.level().addFreshEntity(itemEntity)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
-134
@@ -1,134 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.objective
|
||||
|
||||
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
|
||||
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
|
||||
import com.cobblemon.mod.common.api.ai.asVariables
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.EntityFixation
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.LookingTracker
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.Distraction
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.LookingTrackerFixation
|
||||
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
|
||||
import com.cobblemon.mod.common.api.ai.resolveDouble
|
||||
import com.cobblemon.mod.common.api.ai.resolveInt
|
||||
import com.cobblemon.mod.common.util.asExpression
|
||||
import com.mojang.datafixers.util.Either
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.core.Direction
|
||||
import kotlin.math.pow
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.PathfinderMob
|
||||
import net.minecraft.world.entity.ai.behavior.BlockPosTracker
|
||||
import net.minecraft.world.entity.ai.behavior.EntityTracker
|
||||
import net.minecraft.world.entity.ai.behavior.PositionTracker
|
||||
import net.minecraft.world.entity.ai.memory.MemoryModuleType
|
||||
import net.minecraft.world.entity.ai.memory.MemoryStatus
|
||||
import net.minecraft.world.phys.Vec3
|
||||
|
||||
/**
|
||||
* Randomly points toward spawn. Very easily distracted once doing so.
|
||||
*
|
||||
*/
|
||||
class PointToSpawn(
|
||||
val minDuration: Int = 20,
|
||||
val maxDuration: Int = 200
|
||||
) : Objective {
|
||||
var fixation: EntityFixation? = null
|
||||
|
||||
override fun getDescription(entity: LivingEntity) = "Point towards spawn"
|
||||
override fun perform(
|
||||
entity: LivingEntity,
|
||||
brain: BiggerBrain,
|
||||
activeObjectiveTags: MutableSet<String>
|
||||
): ObjectiveStatus {
|
||||
if (brain.isFixation(fixation)) {
|
||||
return ObjectiveStatus.FIXATED
|
||||
} else if (brain.fixation != null) {
|
||||
fixation = null
|
||||
return ObjectiveStatus.BLOCKED
|
||||
} else if (!entity.brain.checkMemory(MemoryModuleType.LOOK_TARGET, MemoryStatus.REGISTERED)
|
||||
|| !entity.brain.checkMemory(MemoryModuleType.WALK_TARGET, MemoryStatus.VALUE_ABSENT)
|
||||
) {
|
||||
return ObjectiveStatus.BLOCKED
|
||||
}
|
||||
|
||||
val blockPos = Vec3.atCenterOf(entity.level().sharedSpawnPos).with(Direction.Axis.Y, entity.eyeY)
|
||||
brain.setMemory(
|
||||
MemoryModuleType.LOOK_TARGET,
|
||||
BlockPosTracker(blockPos)
|
||||
)
|
||||
|
||||
|
||||
// Become fixated
|
||||
fixation = brain.fixate(
|
||||
IdleLookFixation(
|
||||
this,
|
||||
blockPos,
|
||||
(minDuration..maxDuration).random()
|
||||
)
|
||||
)
|
||||
return ObjectiveStatus.FIXATED
|
||||
}
|
||||
|
||||
class IdleLookFixation(
|
||||
val objective: PointToSpawn,
|
||||
val targetPosition: Vec3,
|
||||
var remainingTicks: Int
|
||||
) : EntityFixation, LookingTrackerFixation {
|
||||
override var lookingTracker: LookingTracker? = null
|
||||
|
||||
override fun getDescription(entity: LivingEntity): String {
|
||||
return "Looking at spawn position ($remainingTicks ticks left)"
|
||||
}
|
||||
|
||||
override fun canDistract(distraction: Distraction, entity: LivingEntity): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun tick(entity: LivingEntity, brain: BiggerBrain): Boolean {
|
||||
if (entity !is PathfinderMob) {
|
||||
return false
|
||||
}
|
||||
if (lookingTracker == null) {
|
||||
lookingTracker = LookingTracker(entity, BlockPosTracker(targetPosition))
|
||||
}
|
||||
val lookingTracker = lookingTracker!!
|
||||
remainingTicks--
|
||||
lookingTracker.tick()
|
||||
return remainingTicks > 0
|
||||
}
|
||||
}
|
||||
|
||||
class Config : ObjectiveConfig {
|
||||
val minDuration: ExpressionOrEntityVariable = Either.left("20".asExpression())
|
||||
val maxDuration: ExpressionOrEntityVariable = Either.left("80".asExpression())
|
||||
|
||||
override fun getVariables(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
) = listOf(
|
||||
minDuration,
|
||||
maxDuration
|
||||
).asVariables()
|
||||
|
||||
override fun create(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
): Objective {
|
||||
val minDuration = minDuration.resolveInt(behaviourConfigurationContext.runtime)
|
||||
val maxDuration = maxDuration.resolveInt(behaviourConfigurationContext.runtime)
|
||||
return PointToSpawn(
|
||||
minDuration = minDuration,
|
||||
maxDuration = maxDuration
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
-113
@@ -1,113 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.objective
|
||||
|
||||
import com.bedrockk.molang.Expression
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
|
||||
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
|
||||
import com.cobblemon.mod.common.api.ai.asSimplifiedExpression
|
||||
import com.cobblemon.mod.common.api.ai.asVariables
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
|
||||
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
|
||||
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.mojang.datafixers.util.Either
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.ai.memory.MemoryModuleType
|
||||
|
||||
class SkittishBehaviour(
|
||||
engageRange: Expression = "5.0".asExpression(),
|
||||
disengageRange: Expression = "20.0".asExpression(),
|
||||
condition: Expression = "true".asExpression(),
|
||||
targetCondition: Expression = "false".asExpression(),
|
||||
description: Expression = "'Skittish Behaviour'".asExpression(),
|
||||
) : EntityResponseObjective(
|
||||
// speedMultiplier = speedMultiplier, // How do we transfer speed to the attack behaviour? Do we want to?
|
||||
engageRange = engageRange,
|
||||
disengageRange = disengageRange,
|
||||
condition = condition, // Self/environment condition check
|
||||
targetCondition = targetCondition, // target condition (will be checked on entity interaction)
|
||||
description = description,
|
||||
) {
|
||||
private var remainingCooldownTicks = 0
|
||||
override val observedMemories = listOf(MemoryModuleType.HURT_BY_ENTITY, CobblemonMemories.ATTACK_TARGET_DATA)
|
||||
|
||||
override fun perform(
|
||||
entity: LivingEntity,
|
||||
brain: BiggerBrain,
|
||||
activeObjectiveTags: MutableSet<String>
|
||||
): ObjectiveStatus {
|
||||
if (entity !is PokemonEntity || !entity.isAlive) return ObjectiveStatus.INVALID
|
||||
|
||||
val avoidTarget = brain.getMemory(MemoryModuleType.AVOID_TARGET)
|
||||
|
||||
if (trackedEntity != null) {
|
||||
if (avoidTarget != null && trackedEntity?.uuid === avoidTarget.uuid) {
|
||||
// Check if we continue
|
||||
if(!shouldContinue(entity, brain)) {
|
||||
brain.eraseMemory(MemoryModuleType.AVOID_TARGET)
|
||||
trackedEntity = null
|
||||
}
|
||||
} else {
|
||||
// Something has overridden us
|
||||
trackedEntity = null
|
||||
}
|
||||
} else {
|
||||
// Scan visible livingEntities
|
||||
val targetedEntity = scanForValidTargets(entity, brain)
|
||||
if (targetedEntity != null) {
|
||||
trackedEntity = targetedEntity
|
||||
brain.setMemory(MemoryModuleType.AVOID_TARGET, trackedEntity)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (remainingCooldownTicks > 0) {
|
||||
remainingCooldownTicks--
|
||||
}
|
||||
|
||||
return if (trackedEntity != null) ObjectiveStatus.ACTIVE else ObjectiveStatus.INACTIVE
|
||||
}
|
||||
|
||||
|
||||
class Config : ObjectiveConfig {
|
||||
val engageRange: ExpressionOrEntityVariable = Either.left("10.0".asExpression())
|
||||
val disengageRange: ExpressionOrEntityVariable = Either.left("10.0".asExpression())
|
||||
val condition: ExpressionOrEntityVariable = Either.left("true".asExpression())
|
||||
val targetCondition: ExpressionOrEntityVariable = Either.left("false".asExpression())
|
||||
val description: ExpressionOrEntityVariable = Either.left("'Skittish Behaviour'".asExpression())
|
||||
override fun getVariables(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
): List<MoLangConfigVariable> {
|
||||
return listOf(
|
||||
condition,
|
||||
targetCondition,
|
||||
engageRange,
|
||||
disengageRange,
|
||||
description,
|
||||
).asVariables()
|
||||
}
|
||||
|
||||
override fun create(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
): Objective {
|
||||
return SkittishBehaviour(
|
||||
condition = condition.asSimplifiedExpression(entity),
|
||||
targetCondition = targetCondition.asSimplifiedExpression(entity),
|
||||
engageRange = engageRange.asSimplifiedExpression(entity),
|
||||
disengageRange = disengageRange.asSimplifiedExpression(entity),
|
||||
description = description.asSimplifiedExpression(entity)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
-79
@@ -1,79 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.objective
|
||||
|
||||
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
|
||||
import com.cobblemon.mod.common.api.ai.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
@@ -1,240 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.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)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
-258
@@ -1,258 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.objective
|
||||
|
||||
import com.bedrockk.molang.Expression
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.api.Priority
|
||||
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
|
||||
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
|
||||
import com.cobblemon.mod.common.api.ai.asSimplifiedExpression
|
||||
import com.cobblemon.mod.common.api.ai.asVariables
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.LookingTracker
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.Distraction
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.DistractionType
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.AttackFixation
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.EntityFixation
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.LookAndLockFixation
|
||||
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
|
||||
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.util.asExpression
|
||||
import com.cobblemon.mod.common.util.mainThreadRuntime
|
||||
import com.cobblemon.mod.common.util.resolveBoolean
|
||||
import com.cobblemon.mod.common.util.resolveFloat
|
||||
import com.cobblemon.mod.common.util.resolveInt
|
||||
import com.cobblemon.mod.common.util.resolveString
|
||||
import com.cobblemon.mod.common.util.withQueryValue
|
||||
import com.mojang.datafixers.util.Either
|
||||
import com.sun.jna.Memory
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.ai.memory.MemoryModuleType
|
||||
|
||||
class TerritorialBehaviour(
|
||||
speedMultiplier: Expression = "0.5".asExpression(), // How do we transfer speed to the attack behaviour? Do we want to?
|
||||
val attackRange: Expression = "3.0".asExpression(),
|
||||
engageRange: Expression = "10.0".asExpression(),
|
||||
disengageRange: Expression = "15.0".asExpression(),
|
||||
condition: Expression = "true".asExpression(), // Self/environment condition check
|
||||
targetCondition: Expression = "true".asExpression(), // target condition (will be checked on entity interaction)
|
||||
description: Expression = "'Territorial Behaviour'".asExpression(),
|
||||
val maxAttackDuration: Expression = "300".asExpression(),
|
||||
) : EntityResponseObjective(
|
||||
speedMultiplier = speedMultiplier, // How do we transfer speed to the attack behaviour? Do we want to?
|
||||
engageRange = engageRange,
|
||||
disengageRange = disengageRange,
|
||||
condition = condition, // Self/environment condition check
|
||||
targetCondition = targetCondition, // target condition (will be checked on entity interaction)
|
||||
description = description,
|
||||
) {
|
||||
|
||||
private var remainingCooldownTicks = 0
|
||||
private val objectiveDescription: String = mainThreadRuntime.resolveString(description)
|
||||
override val observedMemories = listOf(MemoryModuleType.HURT_BY_ENTITY, CobblemonMemories.ATTACK_TARGET_DATA)
|
||||
private var cryCooldownTicks = 0
|
||||
private var proximityAttackCooldown = 0
|
||||
private var satiationHitCount = 999
|
||||
private var fixation : EntityFixation? = null
|
||||
|
||||
override fun getDescription(entity: LivingEntity) = objectiveDescription
|
||||
|
||||
override fun perform(
|
||||
entity: LivingEntity,
|
||||
brain: BiggerBrain,
|
||||
activeObjectiveTags: MutableSet<String>
|
||||
): ObjectiveStatus {
|
||||
if (entity !is PokemonEntity || entity.isDeadOrDying) return ObjectiveStatus.INVALID
|
||||
if (brain.getMemory(MemoryModuleType.AVOID_TARGET) != null) {
|
||||
return ObjectiveStatus.BLOCKED
|
||||
}
|
||||
|
||||
val attackTarget = brain.getMemory(MemoryModuleType.ATTACK_TARGET)
|
||||
if (trackedEntity != null) {
|
||||
if (shouldContinue(entity, brain)) {
|
||||
|
||||
// tick behaviour cooldowns
|
||||
if (proximityAttackCooldown > 0) proximityAttackCooldown--
|
||||
if (cryCooldownTicks > 0) cryCooldownTicks--
|
||||
|
||||
if (attackTarget == null) {
|
||||
// approach, cry, or fight
|
||||
val runtime = mainThreadRuntime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
|
||||
.withQueryValue("target", trackedEntity!!.asMostSpecificMoLangValue())
|
||||
val distanceToTarget = entity.distanceTo(trackedEntity!!)
|
||||
val _attackRange = runtime.resolveFloat(attackRange)
|
||||
if (_attackRange > distanceToTarget && proximityAttackCooldown <= 0) {
|
||||
// give em a warning shot
|
||||
val attackDuration = runtime.resolveInt(maxAttackDuration)
|
||||
satiationHitCount = 1
|
||||
brain.setMemory(MemoryModuleType.ATTACK_TARGET, trackedEntity, attackDuration.toLong())
|
||||
fixation = brain.fixate(
|
||||
AttackFixation(
|
||||
targetEntity = trackedEntity!!,
|
||||
speedMultiplier = speedMultiplier,
|
||||
distractionTypes = setOf(DistractionType.FORCE),
|
||||
availableMoves = getRealTimeMoves(entity)
|
||||
)
|
||||
)
|
||||
} else {
|
||||
// be unhappy about it.
|
||||
if (cryCooldownTicks <= 0) {
|
||||
entity.cry()
|
||||
cryCooldownTicks = CRY_COOLDOWN_TICKS + entity.level().random.nextInt(CRY_COOLDOWN_RANDOM_TICKS)
|
||||
}
|
||||
if (!brain.isFixation(fixation) && brain.fixation !is LookAndLockFixation) {
|
||||
if (brain.distract(Distraction.FORCE)) {
|
||||
fixation = brain.fixate(
|
||||
LookAndLockFixation(
|
||||
distractionTypes = setOf(DistractionType.FORCE, DistractionType.DAMAGE),
|
||||
lookingTracker = LookingTracker.forTargetEntity(entity, trackedEntity!!, trackEyeHeight = true),
|
||||
condition = { true }
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (attackTarget.uuid != trackedEntity?.uuid) {
|
||||
// Something has overridden us and independently set the attack target
|
||||
trackedEntity = null
|
||||
return ObjectiveStatus.BLOCKED
|
||||
}
|
||||
}
|
||||
} else {
|
||||
trackedEntity = null
|
||||
}
|
||||
} else {
|
||||
// No active target
|
||||
if (remainingCooldownTicks > 0) {
|
||||
remainingCooldownTicks--
|
||||
return ObjectiveStatus.INACTIVE
|
||||
} else {
|
||||
remainingCooldownTicks = 20
|
||||
}
|
||||
val targetEntity = scanForValidTargets(entity, brain)
|
||||
if (targetEntity != null) {
|
||||
cryCooldownTicks = 0
|
||||
proximityAttackCooldown = 0
|
||||
trackedEntity = targetEntity
|
||||
}
|
||||
|
||||
}
|
||||
return if (trackedEntity != null) ObjectiveStatus.ACTIVE else ObjectiveStatus.INACTIVE
|
||||
}
|
||||
|
||||
override fun onRegister(entity: LivingEntity, brain: BiggerBrain) {
|
||||
// TODO: handle conflicts between different behaviours
|
||||
brain.subscribe(
|
||||
priority = Priority.LOW,
|
||||
memory = CobblemonMemories.ATTACK_TARGET_DATA
|
||||
) { value, labels ->
|
||||
if (value != null) {
|
||||
val livingEntity = brain.getMemory(MemoryModuleType.ATTACK_TARGET) ?: return@subscribe true
|
||||
if (trackedEntity?.uuid == livingEntity.uuid) {
|
||||
val attackData = value
|
||||
if (attackData.hitsLanded >= satiationHitCount) {
|
||||
// Justice served, give the fool a chance to back off
|
||||
proximityAttackCooldown = PROXIMITY_AGGRO_COOLDOWN_TICKS
|
||||
brain.eraseMemory(CobblemonMemories.ATTACK_TARGET_DATA)
|
||||
brain.eraseMemory(MemoryModuleType.ATTACK_TARGET)
|
||||
} else {
|
||||
// We smacked them, refresh our attack timer
|
||||
val runtime = mainThreadRuntime.withQueryValue("entity", (livingEntity).asMostSpecificMoLangValue())
|
||||
val attackDuration = runtime.resolveInt(maxAttackDuration)
|
||||
brain.setMemory(MemoryModuleType.ATTACK_TARGET, livingEntity, attackDuration.toLong())
|
||||
}
|
||||
}
|
||||
}
|
||||
return@subscribe true
|
||||
}
|
||||
|
||||
brain.subscribe(
|
||||
priority = Priority.LOW,
|
||||
memory = MemoryModuleType.HURT_BY_ENTITY
|
||||
) { value, labels ->
|
||||
// Of course you know this means war.
|
||||
if (value != null) {
|
||||
// Something just smacked us, see if we should get mad about it.
|
||||
val livingEntity = value
|
||||
val runtime = mainThreadRuntime.withQueryValue("entity", (entity).asMostSpecificMoLangValue())
|
||||
.withQueryValue("target", livingEntity.asMostSpecificMoLangValue())
|
||||
val shouldTarget = runtime.resolveBoolean(condition) && runtime.resolveBoolean(targetCondition)
|
||||
if (shouldTarget && brain.distract(Distraction.FORCE)) {
|
||||
brain.setMemory(
|
||||
MemoryModuleType.ATTACK_TARGET, livingEntity,
|
||||
runtime.resolveInt(maxAttackDuration).toLong()
|
||||
)
|
||||
satiationHitCount = 999
|
||||
trackedEntity = livingEntity
|
||||
fixation = brain.fixate(
|
||||
AttackFixation(
|
||||
targetEntity = trackedEntity!!,
|
||||
speedMultiplier = speedMultiplier,
|
||||
distractionTypes = setOf(DistractionType.FORCE),
|
||||
availableMoves = getRealTimeMoves(entity)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
return@subscribe true
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val CRY_COOLDOWN_TICKS = 75
|
||||
val CRY_COOLDOWN_RANDOM_TICKS = 50
|
||||
val PROXIMITY_AGGRO_COOLDOWN_TICKS = 75
|
||||
}
|
||||
|
||||
class Config : ObjectiveConfig {
|
||||
val speedMultiplier: ExpressionOrEntityVariable = Either.left("0.5".asExpression())
|
||||
val attackRange: ExpressionOrEntityVariable = Either.left("7.0".asExpression())
|
||||
val engageRange: ExpressionOrEntityVariable = Either.left("13.0".asExpression())
|
||||
val disengageRange: ExpressionOrEntityVariable = Either.left("15.0".asExpression())
|
||||
val condition: ExpressionOrEntityVariable = Either.left("true".asExpression())
|
||||
val targetCondition: ExpressionOrEntityVariable = Either.left("false".asExpression())
|
||||
val maxAttackDuration: ExpressionOrEntityVariable = Either.left("300".asExpression())
|
||||
val description: ExpressionOrEntityVariable = Either.left("'Territorial Behaviour'".asExpression())
|
||||
override fun getVariables(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
): List<MoLangConfigVariable> {
|
||||
return listOf(
|
||||
condition,
|
||||
targetCondition,
|
||||
speedMultiplier,
|
||||
attackRange,
|
||||
engageRange,
|
||||
disengageRange,
|
||||
maxAttackDuration,
|
||||
description
|
||||
).asVariables()
|
||||
}
|
||||
|
||||
override fun create(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
): Objective {
|
||||
return TerritorialBehaviour(
|
||||
condition = condition.asSimplifiedExpression(entity),
|
||||
targetCondition = targetCondition.asSimplifiedExpression(entity),
|
||||
speedMultiplier = speedMultiplier.asSimplifiedExpression(entity),
|
||||
attackRange = attackRange.asSimplifiedExpression(entity),
|
||||
engageRange = engageRange.asSimplifiedExpression(entity),
|
||||
disengageRange = disengageRange.asSimplifiedExpression(entity),
|
||||
maxAttackDuration = maxAttackDuration.asSimplifiedExpression(entity),
|
||||
description = description.asSimplifiedExpression(entity)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
-127
@@ -1,127 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.objective
|
||||
|
||||
|
||||
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
|
||||
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
|
||||
import com.cobblemon.mod.common.api.ai.asVariables
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.LookingTracker
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.MovementTracker
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.RegularMovementTracker
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.Distraction
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.DistractionType
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.comfortDistraction
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.idleBoredomDistraction
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.EntityFixation
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.LookAndMoveFixation
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.MovementTrackerFixation
|
||||
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
|
||||
import com.cobblemon.mod.common.api.ai.resolveFloat
|
||||
import com.cobblemon.mod.common.api.ai.resolveInt
|
||||
import com.cobblemon.mod.common.entity.PoseType
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.entity.pokemon.ai.PokemonMoveControl
|
||||
import com.cobblemon.mod.common.util.asExpression
|
||||
import com.mojang.datafixers.util.Either
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.PathfinderMob
|
||||
import net.minecraft.world.entity.ai.behavior.EntityTracker
|
||||
import net.minecraft.world.entity.ai.memory.MemoryModuleType
|
||||
import net.minecraft.world.entity.ai.memory.WalkTarget
|
||||
|
||||
/**
|
||||
* Walk towards parent of same species objective
|
||||
*/
|
||||
class WalkTowardsParent(
|
||||
val minRange: Int,
|
||||
val maxRange: Int,
|
||||
val speed: Float
|
||||
) : Objective {
|
||||
var fixation: EntityFixation? = null
|
||||
var cooldownTicks = 0
|
||||
|
||||
override fun getDescription(entity: LivingEntity) = "Following parent species"
|
||||
|
||||
override fun perform(
|
||||
entity: LivingEntity,
|
||||
brain: BiggerBrain,
|
||||
activeObjectiveTags: MutableSet<String>
|
||||
): ObjectiveStatus {
|
||||
if (entity !is PokemonEntity) {
|
||||
return ObjectiveStatus.INVALID // its gotta be a pokemon to have a parent species
|
||||
} else if (brain.isFixation(fixation)) {
|
||||
return ObjectiveStatus.FIXATED // we are already fixated on this objectives fixation
|
||||
} else if (brain.fixation != null) {
|
||||
fixation = null // our brain has chosen another fixation
|
||||
}
|
||||
|
||||
// Check to see if the cooldown has been completed
|
||||
// TODO: should cooldown checks happen first? Do we only want them ticked down
|
||||
if (cooldownTicks > 0) {
|
||||
cooldownTicks--
|
||||
return ObjectiveStatus.INACTIVE
|
||||
}
|
||||
|
||||
// if no trusted adult is around then the objective is inactive
|
||||
val adult = brain.getMemory(MemoryModuleType.NEAREST_VISIBLE_ADULT) ?: return ObjectiveStatus.INACTIVE
|
||||
|
||||
// If we are too close or too far then don't follow
|
||||
// TODO: the too far doesn't make that much sense to me. We don't want it treating it as a herd?
|
||||
val dist = entity.distanceTo(adult)
|
||||
if (dist > maxRange + 1 || dist < minRange) return ObjectiveStatus.INACTIVE
|
||||
|
||||
// Can we comfort ourselves?
|
||||
if (!brain.distract(comfortDistraction)) return ObjectiveStatus.BLOCKED
|
||||
|
||||
// Get our walk target and movement tracker
|
||||
val walkTarget = WalkTarget(EntityTracker(adult, false), speed, minRange - 1)
|
||||
val movementTracker = RegularMovementTracker.attempt(entity as PathfinderMob, walkTarget)
|
||||
?: return ObjectiveStatus.INACTIVE
|
||||
|
||||
this.fixation = brain.fixate(
|
||||
LookAndMoveFixation(
|
||||
description = "Following parent",
|
||||
distractionTypes = setOf(DistractionType.DAMAGE, DistractionType.FORCE), // We are very fine to ignore this movement if forced or hurt
|
||||
lookingTracker = LookingTracker.forTargetEntity(entity, adult),
|
||||
movementTracker = movementTracker,
|
||||
onMovementComplete = { _, _ -> cooldownTicks = 60 }
|
||||
)
|
||||
)
|
||||
return ObjectiveStatus.FIXATED // We are now fixated on
|
||||
}
|
||||
|
||||
class Config : ObjectiveConfig {
|
||||
val minRange: ExpressionOrEntityVariable = Either.left("5".asExpression())
|
||||
val maxRange: ExpressionOrEntityVariable = Either.left("16".asExpression())
|
||||
val speed: ExpressionOrEntityVariable = Either.left("0.4".asExpression())
|
||||
|
||||
override fun getVariables(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
) = listOf(
|
||||
minRange,
|
||||
maxRange,
|
||||
speed
|
||||
).asVariables()
|
||||
|
||||
override fun create(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
): Objective? {
|
||||
if (entity !is PokemonEntity) return null
|
||||
return WalkTowardsParent(
|
||||
minRange = minRange.resolveInt(behaviourConfigurationContext.runtime),
|
||||
maxRange = maxRange.resolveInt(behaviourConfigurationContext.runtime),
|
||||
speed = speed.resolveFloat(behaviourConfigurationContext.runtime)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
-251
@@ -1,251 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.objective
|
||||
|
||||
import com.bedrockk.molang.Expression
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.api.ai.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
|
||||
import net.minecraft.world.entity.ai.memory.WalkTarget
|
||||
|
||||
/*
|
||||
* 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 = "false".asExpression(),
|
||||
val minimumHeight: Expression = "0".asExpression(),
|
||||
val maximumHeight: Expression = "6".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("false".asExpression())
|
||||
val minimumHeight: ExpressionOrEntityVariable = Either.left("0".asExpression())
|
||||
val maximumHeight: ExpressionOrEntityVariable = Either.left("6".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)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun generateWalkTarget(entity: LivingEntity) : WalkTarget? {
|
||||
if (entity !is PathfinderMob) return null
|
||||
val runtime = mainThreadRuntime
|
||||
|
||||
val wanderControl = entity.brain.getMemorySafely(CobblemonMemories.WANDER_CONTROL).orElse(null)
|
||||
runtime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
|
||||
|
||||
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 allowWater = wanderControl?.allowWater ?: runtime.resolveBoolean("q.entity.behaviour.moving.swim.can_swim_in_water && !q.entity.behaviour.moving.swim.avoids_water".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) && WanderType.LAND.isValid(entity)) {
|
||||
types.add(WanderType.LAND)
|
||||
}
|
||||
if (allowWater && WanderType.WATER.isValid(entity)) {
|
||||
types.add(WanderType.WATER)
|
||||
}
|
||||
|
||||
if (types.isEmpty()) {
|
||||
return null
|
||||
}
|
||||
|
||||
return types.firstNotNullOfOrNull {
|
||||
it.generateWalkTarget(
|
||||
entity = entity,
|
||||
horizontalRange = 10,
|
||||
verticalRange = 5,
|
||||
minimumHeight = 0,
|
||||
maximumHeight = 6,
|
||||
avoidTargetingAir = false, // runtime.resolveBoolean(avoidTargetingAir),
|
||||
speedMultiplier = 0.50f,
|
||||
center = wanderControl?.center,
|
||||
maxAttempts = wanderControl?.maxAttempts ?: 4
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-90
@@ -1,90 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.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)
|
||||
}
|
||||
}
|
||||
}
|
||||
-159
@@ -1,159 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.sense
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
|
||||
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
|
||||
import com.cobblemon.mod.common.api.ai.asVariables
|
||||
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
|
||||
import com.cobblemon.mod.common.api.ai.config.sense.SenseConfig
|
||||
import com.cobblemon.mod.common.api.ai.resolveInt
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.util.asExpression
|
||||
import com.mojang.datafixers.util.Either
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.core.Direction
|
||||
import net.minecraft.server.level.ServerLevel
|
||||
import net.minecraft.tags.PoiTypeTags
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.ai.village.poi.PoiManager
|
||||
import net.minecraft.world.entity.ai.village.poi.PoiRecord
|
||||
import net.minecraft.world.level.Level
|
||||
import net.minecraft.world.level.block.Blocks
|
||||
import net.minecraft.world.level.block.entity.BeehiveBlockEntity
|
||||
import net.minecraft.world.level.block.entity.BlockEntity
|
||||
import net.minecraft.world.level.block.state.BlockState
|
||||
|
||||
class BeeHiveSense(
|
||||
private var cooldownTicks: Int = SCAN_INTERVAL
|
||||
) : Sense {
|
||||
override var ticksUntilNextExecution: Int = cooldownTicks
|
||||
override val memories = setOf(
|
||||
CobblemonMemories.HIVE_LOCATION,
|
||||
CobblemonMemories.HIVE_BLACKLIST,
|
||||
)
|
||||
|
||||
override fun merge(other: Sense): Boolean {
|
||||
if (other is BeeHiveSense) {
|
||||
this.cooldownTicks = minOf(this.cooldownTicks, other.cooldownTicks)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun tick(entity: LivingEntity, brain: BiggerBrain) {
|
||||
ticksUntilNextExecution = cooldownTicks
|
||||
if (entity !is PokemonEntity) return
|
||||
val world = entity.level() as? ServerLevel ?: return
|
||||
|
||||
val brain = entity.brain
|
||||
val currentHive = brain.getMemory(CobblemonMemories.HIVE_LOCATION).orElse(null)
|
||||
|
||||
if (currentHive != null) {
|
||||
val state = world.getBlockState(currentHive)
|
||||
|
||||
if (!isValidHive(state) || !hasReachableAdjacentSide(world, currentHive)) {
|
||||
brain.eraseMemory(CobblemonMemories.HIVE_LOCATION)
|
||||
} else {
|
||||
return // We already have a valid hive, no need to search for another
|
||||
}
|
||||
}
|
||||
|
||||
var closestHivePos: BlockPos? = null
|
||||
|
||||
val list = findNearbyHivesWithSpace(entity)
|
||||
|
||||
if (!list.isEmpty()) {
|
||||
val blackList = brain.getMemory(CobblemonMemories.HIVE_BLACKLIST).orElse(emptyList())
|
||||
for (blockPos in list) {
|
||||
if (blackList.contains(blockPos)) continue
|
||||
if (hasReachableAdjacentSide(world, blockPos)) {
|
||||
closestHivePos = blockPos
|
||||
break
|
||||
}
|
||||
}
|
||||
if (closestHivePos == null) {
|
||||
brain.eraseMemory(CobblemonMemories.HIVE_BLACKLIST)
|
||||
closestHivePos = list.first()
|
||||
}
|
||||
}
|
||||
|
||||
if (closestHivePos != null) {
|
||||
brain.setMemory(CobblemonMemories.HIVE_LOCATION, closestHivePos)
|
||||
}
|
||||
}
|
||||
|
||||
private fun isValidHive(state: BlockState): Boolean {
|
||||
return isHiveBlock(state)
|
||||
}
|
||||
|
||||
|
||||
private fun isHiveBlock(state: BlockState): Boolean {
|
||||
return state.`is`(Blocks.BEEHIVE) || state.`is`(Blocks.BEE_NEST)
|
||||
}
|
||||
|
||||
private fun hasReachableAdjacentSide(world: ServerLevel, pos: BlockPos): Boolean {
|
||||
for (dir in Direction.entries) {
|
||||
val adjacentPos = pos.relative(dir)
|
||||
if (world.isEmptyBlock(adjacentPos)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun findNearbyHivesWithSpace(entity: PokemonEntity): List<BlockPos> {
|
||||
val blockPos = entity.blockPosition()
|
||||
val poiManager = (entity.level() as ServerLevel).poiManager
|
||||
val stream = poiManager.getInRange(
|
||||
{ holder -> holder.`is`(PoiTypeTags.BEE_HOME) },
|
||||
blockPos,
|
||||
20,
|
||||
PoiManager.Occupancy.ANY
|
||||
)
|
||||
return stream
|
||||
.map(PoiRecord::getPos)
|
||||
.filter { pos -> doesHiveHaveSpace(pos, entity.level()) }
|
||||
.sorted { pos1, pos2 -> pos1.distSqr(blockPos).toInt() - pos2.distSqr(blockPos).toInt() }
|
||||
.toList()
|
||||
}
|
||||
|
||||
private fun doesHiveHaveSpace(hivePos: BlockPos, world: Level): Boolean {
|
||||
val blockEntity: BlockEntity? = world.getBlockEntity(hivePos)
|
||||
if (blockEntity is BeehiveBlockEntity) {
|
||||
return !blockEntity.isFull
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
class Config : SenseConfig {
|
||||
var condition: ExpressionOrEntityVariable = Either.left("true".asExpression())
|
||||
var cooldownTicks: ExpressionOrEntityVariable = Either.left("40".asExpression())
|
||||
|
||||
override fun getVariables(
|
||||
entity: LivingEntity,
|
||||
behaviourConfigurationContext: BehaviourConfigurationContext
|
||||
) = listOf(cooldownTicks).asVariables()
|
||||
|
||||
override fun create(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): Sense {
|
||||
val cooldownTicks = cooldownTicks.resolveInt(behaviourConfigurationContext.runtime)
|
||||
return BeeHiveSense(
|
||||
cooldownTicks = cooldownTicks
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val SCAN_INTERVAL = 40
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
-84
@@ -1,84 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.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.server.level.ServerPlayer
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.Mob
|
||||
import net.minecraft.world.entity.ai.memory.MemoryModuleType
|
||||
import net.minecraft.world.entity.ai.memory.NearestVisibleLivingEntities
|
||||
|
||||
/**
|
||||
* Senses if there is an entity this enties owner needs defending from
|
||||
*
|
||||
* @author Jack
|
||||
* @since April 12th, 2026
|
||||
*/
|
||||
class DefendOwnerSense(
|
||||
var cooldownTicks: Int = 10
|
||||
) : Sense {
|
||||
override var ticksUntilNextExecution = cooldownTicks
|
||||
override val memories = setOf(CobblemonMemories.NEAREST_VISIBLE_ATTACKER, MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES)
|
||||
|
||||
override fun merge(other: Sense): Boolean {
|
||||
if (other is DefendOwnerSense) {
|
||||
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) return
|
||||
val owner = entity.owner ?: return
|
||||
brain.getMemory(MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES)?.let { visibleMobs ->
|
||||
setNearestAttacker(entity, visibleMobs, owner, brain)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setNearestAttacker(entity: PokemonEntity, visibleMobs: NearestVisibleLivingEntities, owner: LivingEntity, brain: BiggerBrain) {
|
||||
val nearestAttacker = visibleMobs.findClosest { livingEntity ->
|
||||
if (livingEntity is ServerPlayer) return@findClosest false
|
||||
if (livingEntity.lastHurtMob == owner) return@findClosest true
|
||||
if (livingEntity is Mob && livingEntity.target == owner) return@findClosest true
|
||||
val mobAttackTarget = livingEntity.brain.getMemorySafely(MemoryModuleType.ATTACK_TARGET)
|
||||
if (mobAttackTarget.isPresent && mobAttackTarget.get() == owner) return@findClosest true
|
||||
false
|
||||
}.orElse(null)
|
||||
|
||||
if (nearestAttacker != null) {
|
||||
brain.setMemory(CobblemonMemories.NEAREST_VISIBLE_ATTACKER, nearestAttacker)
|
||||
} else {
|
||||
brain.eraseMemoryIfPresent(CobblemonMemories.NEAREST_VISIBLE_ATTACKER)
|
||||
}
|
||||
}
|
||||
|
||||
class Config : SenseConfig {
|
||||
var cooldownTicks: ExpressionOrEntityVariable = Either.left("10".asExpression())
|
||||
|
||||
override fun getVariables(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext) =
|
||||
listOf(cooldownTicks).asVariables()
|
||||
|
||||
override fun create(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext) =
|
||||
DefendOwnerSense(cooldownTicks.resolveInt(behaviourConfigurationContext.runtime))
|
||||
}
|
||||
}
|
||||
-91
@@ -1,91 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.biggerbrain.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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user