forked from mirrors/cobblemon
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f70d7ce336 | ||
|
|
5b0a221ade | ||
|
|
af23b82ce5 | ||
|
|
36e78a5af2 | ||
|
|
55621b01b7 | ||
|
|
b828fe2b3a | ||
|
|
b7b10151e1 | ||
|
|
cac6ed843a | ||
|
|
8f5f40c360 | ||
|
|
0e586b4f38 | ||
|
|
62dd487448 | ||
|
|
04ecd9189c | ||
|
|
55be22e942 | ||
|
|
f2e1b3dab4 | ||
|
|
66f98ebf12 | ||
|
|
c1842e42b0 | ||
|
|
e6c2f241d4 | ||
|
|
0f9c282e1f | ||
|
|
7bd5d77b60 | ||
|
|
f7b4d4e65a | ||
|
|
e78f403198 | ||
|
|
4ca91cc8d8 | ||
|
|
1518181f6b | ||
|
|
b915237c8d | ||
|
|
bb41a72067 | ||
|
|
fbb8492c75 | ||
|
|
c07aef04b1 | ||
|
|
6ff4befb04 | ||
|
|
a858d4f532 | ||
|
|
28fc273b26 | ||
|
|
fc89757a09 | ||
|
|
243c9abfd1 | ||
|
|
b871645e9e | ||
|
|
cec0ee04e0 | ||
|
|
069bc91aa1 | ||
|
|
692a1bd6ba | ||
|
|
c79916d8a0 | ||
|
|
d503d4a1eb | ||
|
|
3d877496b3 | ||
|
|
1e4964d80c | ||
|
|
a0f75dd090 | ||
|
|
5eceaa849c | ||
|
|
0e0233993a | ||
|
|
56ed5a6412 | ||
|
|
92ecaa66fa | ||
|
|
4d3e6400ab | ||
|
|
f2e5ecef66 | ||
|
|
ad9e85923a | ||
|
|
60d0c490dd | ||
|
|
bf461297b2 | ||
|
|
3ec89cd84e | ||
|
|
6452521434 | ||
|
|
5cbf961ac2 | ||
|
|
6b473cffa3 | ||
|
|
f353548f9e | ||
|
|
bba995f3d2 | ||
|
|
5965d5a81b | ||
|
|
4e02ca310d | ||
|
|
3260271d3c | ||
|
|
a77863adab | ||
|
|
455cfeb894 | ||
|
|
46266c08ac | ||
|
|
19aa45e0af | ||
|
|
0fe8ebd008 | ||
|
|
3e393546a2 | ||
|
|
e5995a5491 | ||
|
|
7de083bf4f | ||
|
|
5f7451c284 | ||
|
|
fd3c614c2c | ||
|
|
454c2c60ab | ||
|
|
c581673ef1 | ||
|
|
09e29137fc | ||
|
|
fd1e5ef421 | ||
|
|
8de05006f4 | ||
|
|
7188978a66 | ||
|
|
f60fd26cbf | ||
|
|
3ba59d2165 | ||
|
|
cd80598857 | ||
|
|
0978febaba | ||
|
|
bb67629fa5 | ||
|
|
d21039953f | ||
|
|
682f931f0f | ||
|
|
3796997a12 | ||
|
|
b4cf49e1a8 | ||
|
|
2820989586 | ||
|
|
d37f6bc5a4 | ||
|
|
90d7fc3c2f | ||
|
|
e2779c55f2 | ||
|
|
349b4c28cc | ||
|
|
7f2540a48f | ||
|
|
367db9a368 | ||
|
|
4f3eb7e4ba | ||
|
|
c95f4cb9ad | ||
|
|
c0ccefd9b4 | ||
|
|
f45a1681fa | ||
|
|
12c820313a | ||
|
|
e6b287434b | ||
|
|
d39ee60158 | ||
|
|
d405ede256 | ||
|
|
bbaf4332c6 | ||
|
|
954e690a20 | ||
|
|
0dda070c4c | ||
|
|
e855e0f1ae | ||
|
|
0515b3204b | ||
|
|
049c0c308a | ||
|
|
e424bd76b5 | ||
|
|
0443aa5109 | ||
|
|
f9a3d2dc0e | ||
|
|
f817c18f4f | ||
|
|
bd801b1517 | ||
|
|
0f17d30ece | ||
|
|
f54aed0953 | ||
|
|
2b5a16d30f | ||
|
|
f1ed9b010e | ||
|
|
e8dcd994be | ||
|
|
da371fd940 | ||
|
|
0cdeab0800 | ||
|
|
eca3c582aa | ||
|
|
8dd09a2e14 | ||
|
|
a96880b8ed | ||
|
|
40de8b3ab9 | ||
|
|
6eb814a7f0 | ||
|
|
2f2fd3b17e | ||
|
|
4234f9be67 | ||
|
|
f5766cdad4 | ||
|
|
8c11ef9c00 | ||
|
|
78ac8542f6 | ||
|
|
8c1d03359a | ||
|
|
bb574e58d8 | ||
|
|
5c3c7b4195 | ||
|
|
ddec062e87 | ||
|
|
3322f3ffb6 | ||
|
|
ab1ec11c26 | ||
|
|
36aaa97029 | ||
|
|
6b3443fbd2 | ||
|
|
268a1ab311 | ||
|
|
40957b6eac | ||
|
|
d67be68b4b | ||
|
|
f6f34a2b10 | ||
|
|
b2f71d305f | ||
|
|
51a8c31997 | ||
|
|
67ae5ff155 | ||
|
|
9e0cf03d8e | ||
|
|
6a076d36f9 | ||
|
|
4314a0b506 | ||
|
|
77490b5084 | ||
|
|
109eecd4fe | ||
|
|
d6353aa2c7 | ||
|
|
fd0e741a25 | ||
|
|
b0e0a06e9a | ||
|
|
1c569ac5cf | ||
|
|
5ff5de8d14 | ||
|
|
881e1dc1a6 | ||
|
|
09b9fade87 | ||
|
|
acd3b7cade | ||
|
|
482a0015bc | ||
|
|
b881c96630 | ||
|
|
79cd1f5224 | ||
|
|
f369877c10 | ||
|
|
1f1ff0e4fd | ||
|
|
0a34e51e07 | ||
|
|
741530eb43 | ||
|
|
6c9c5a93f1 | ||
|
|
ddbf6d7345 | ||
|
|
0da09c767c | ||
|
|
fb39ff8657 | ||
|
|
dd516c153e | ||
|
|
739d31a9ca | ||
|
|
a5675aedcb | ||
|
|
196bf42fb6 | ||
|
|
1d01a2bf1f | ||
|
|
ce6087f368 | ||
|
|
f706dcda26 | ||
|
|
8e46a55cbd | ||
|
|
d3ae4cd3e7 | ||
|
|
b41b496b80 | ||
|
|
9cd3ded705 | ||
|
|
ca4b50d727 | ||
|
|
f6bc3f662a | ||
|
|
7ab621e5b9 | ||
|
|
4e63cb0bb3 | ||
|
|
671c7adb05 | ||
|
|
edbe539537 | ||
|
|
e20728faa0 | ||
|
|
5a2f948acb | ||
|
|
8a1faf3270 | ||
|
|
d0b148a91a | ||
|
|
f6f88f5eef | ||
|
|
3f36624ba7 | ||
|
|
7e256e8381 | ||
|
|
ad1aa9ad47 | ||
|
|
b4e403a701 | ||
|
|
08557913d8 | ||
|
|
830c409a06 | ||
|
|
5e2ef6b5fb | ||
|
|
a05f4edc82 | ||
|
|
97a3dc4d61 | ||
|
|
768789d998 | ||
|
|
bfa58e9fb9 | ||
|
|
9a514477c5 | ||
|
|
7623452f4a | ||
|
|
72635d0f2f | ||
|
|
34e581a470 | ||
|
|
5444d67c21 | ||
|
|
4c8c0fd063 | ||
|
|
73209ab6b5 | ||
|
|
539331bdc7 | ||
|
|
0d0598d68c | ||
|
|
08d621ca5e | ||
|
|
a1dcede488 | ||
|
|
aaec9ea397 | ||
|
|
6dfb1323b7 | ||
|
|
973e730b48 | ||
|
|
5011607415 | ||
|
|
580b0f2ccf | ||
|
|
fcd3951815 | ||
|
|
5caa6d5950 | ||
|
|
352ac4e15d | ||
|
|
e6d35e709e | ||
|
|
83cfcdce43 | ||
|
|
96ae28ea91 | ||
|
|
062fd2451a | ||
|
|
235de329ee | ||
|
|
aede012933 | ||
|
|
1613cd281d | ||
|
|
706fcc8ca4 | ||
|
|
5568bb6a95 | ||
|
|
d5f22e7394 | ||
|
|
0dc8456623 | ||
|
|
f5228db99f | ||
|
|
de8ebc9fcb | ||
|
|
093e0f2811 | ||
|
|
1a782e4185 | ||
|
|
2e054dd5e1 | ||
|
|
5e47102c10 | ||
|
|
e56a7d5325 | ||
|
|
9b8261d23b | ||
|
|
e1705056f2 | ||
|
|
76db87d9a5 | ||
|
|
bbb06677b9 | ||
|
|
8a5279741d | ||
|
|
780d9ece59 | ||
|
|
fc8b6fb54b | ||
|
|
9e22b662a4 | ||
|
|
d731bde915 | ||
|
|
6ecdf25821 | ||
|
|
4b55783785 | ||
|
|
77c3f12634 | ||
|
|
d13d3fb137 | ||
|
|
f436b4a738 | ||
|
|
9e1a1c957b | ||
|
|
e04155094b | ||
|
|
188115bf15 | ||
|
|
e3fca67d52 | ||
|
|
932cdaaad3 | ||
|
|
e3f9937e78 | ||
|
|
5b46d04c27 | ||
|
|
c23aeaba78 | ||
|
|
ecff621084 | ||
|
|
1d99037005 | ||
|
|
83ce63325d | ||
|
|
4f33612bbb | ||
|
|
4624faf63e | ||
|
|
187674afb6 | ||
|
|
fe5cf5a7d8 | ||
|
|
3b3219ba69 | ||
|
|
0e98b07b7b | ||
|
|
57d380e18f | ||
|
|
086cdcdbd1 | ||
|
|
2b796f7e4c | ||
|
|
b7d1f9bb2d | ||
|
|
ba0f0657b2 | ||
|
|
8f59ec871f | ||
|
|
b3d4306165 | ||
|
|
20ddab8722 | ||
|
|
5327ff54b5 | ||
|
|
b5f8181118 | ||
|
|
64b164bfca | ||
|
|
93cb0f40f3 | ||
|
|
20a56b1ff0 | ||
|
|
97dd08c35d | ||
|
|
25c64841fe | ||
|
|
dc5f616c90 | ||
|
|
ff9033f6c4 | ||
|
|
9f937f6b3b | ||
|
|
c5c6bc340a | ||
|
|
90c7e5ab61 | ||
|
|
b3028dc45d | ||
|
|
f64b9e2fe7 | ||
|
|
1e7ffedd36 | ||
|
|
e4fab76f0b | ||
|
|
5a7b186478 | ||
|
|
79d30f4fa5 | ||
|
|
56dab6c18b | ||
|
|
6546a5ae05 | ||
|
|
57c243bd34 | ||
|
|
f2cd3fc435 | ||
|
|
9b75b1fb93 | ||
|
|
81f4b6e680 | ||
|
|
de783b6948 | ||
|
|
53c6f707e6 | ||
|
|
23142c6ece | ||
|
|
1fa3170355 | ||
|
|
9025b7b825 | ||
|
|
1487ae1036 | ||
|
|
dab1240d19 | ||
|
|
feb6622762 | ||
|
|
3c4e1c59b1 | ||
|
|
3cd30681d7 | ||
|
|
308a4e9c06 | ||
|
|
34cc6da0a9 | ||
|
|
6f1b7edca4 | ||
|
|
fdc7a3d61a | ||
|
|
9798ba28ec | ||
|
|
c8522488be | ||
|
|
f951e93875 | ||
|
|
d51c623168 | ||
|
|
fe57312d4f | ||
|
|
ee148954c0 | ||
|
|
6ffc7d45c3 | ||
|
|
a0301c85e8 | ||
|
|
6928b9a017 | ||
|
|
ce0d32a857 | ||
|
|
717e861c1d | ||
|
|
541dd1e284 | ||
|
|
f68384a505 | ||
|
|
f406e6da6b | ||
|
|
c75c5e4901 | ||
|
|
b4aedf9ec9 | ||
|
|
cba00fba13 | ||
|
|
90403d111b | ||
|
|
47460ef884 | ||
|
|
e48d44c6e6 | ||
|
|
8e522e3dd2 | ||
|
|
d9e0429ff4 | ||
|
|
bd35fc3c9a | ||
|
|
80d882a437 | ||
|
|
610f9dccc1 | ||
|
|
bddb5b9f40 | ||
|
|
84fc94cf4b | ||
|
|
e1186ebf2b | ||
|
|
e56a778246 | ||
|
|
a2d89f320f | ||
|
|
7e11c4d47b | ||
|
|
cc70fd18d5 | ||
|
|
334b47a778 | ||
|
|
a0c6ec7918 | ||
|
|
665883474d | ||
|
|
e0fce5386e | ||
|
|
d00829e609 | ||
|
|
de5b1e33fe | ||
|
|
f2e6ca1439 | ||
|
|
974bf73516 | ||
|
|
3c8fe3bff7 | ||
|
|
795a14a291 | ||
|
|
0dca4a26e5 | ||
|
|
edb98ae078 | ||
|
|
02ba40facb | ||
|
|
6fc87fdafd | ||
|
|
d430498243 | ||
|
|
2a17f76869 | ||
|
|
d5adfb70ea | ||
|
|
23b422bed6 | ||
|
|
f346b8db0c | ||
|
|
a951bd8d25 | ||
|
|
0d7ff74bd2 | ||
|
|
f1fcf60924 | ||
|
|
a227ef1001 | ||
|
|
2d13b85488 | ||
|
|
a2edfcfdcb | ||
|
|
63ab04d3d8 | ||
|
|
21b6c1dc29 | ||
|
|
29a8522a86 | ||
|
|
73af1863ec | ||
|
|
9cd8f0e7c2 | ||
|
|
ec3458b717 | ||
|
|
1117792bf7 | ||
|
|
1649b9790b | ||
|
|
b23c9b2133 | ||
|
|
eaeb918b33 | ||
|
|
f05af3d427 | ||
|
|
a73c5fac86 | ||
|
|
79be2e64ae | ||
|
|
e66951debc | ||
|
|
1e8a8d0896 | ||
|
|
f9def3acf5 | ||
|
|
a7e6e638fe | ||
|
|
9ef3ea2932 | ||
|
|
38dcee3327 | ||
|
|
12b92d3f35 | ||
|
|
dacfb4f1ff | ||
|
|
4086ba73d3 |
+1
-29
@@ -1,10 +1,9 @@
|
||||
image: amazoncorretto:21-alpine
|
||||
image: amazoncorretto:21
|
||||
|
||||
before_script:
|
||||
- export GRADLE_USER_HOME=`pwd`/.gradle
|
||||
|
||||
stages:
|
||||
- merge
|
||||
- build
|
||||
- deploy
|
||||
|
||||
@@ -65,30 +64,3 @@ deploy:
|
||||
- ./*.jar
|
||||
# - neoforge/build/libs/*+*.jar
|
||||
# - fabric/build/libs/*+*.jar
|
||||
|
||||
merge_on_lang_change:
|
||||
stage: merge
|
||||
script:
|
||||
# Check for changes in the specified file or directory
|
||||
- |
|
||||
apk add git
|
||||
if git diff --name-only "$CI_COMMIT_BEFORE_SHA" "$CI_COMMIT_SHA" | grep -q "common/src/main/resources/assets/cobblemon/lang/en_us.json"; then
|
||||
echo "US locale changed! Updating lang branch..."
|
||||
|
||||
# Configure Git user for the merge commit
|
||||
git config --global user.email "noreply@weblate.org"
|
||||
git config --global user.name "Weblate"
|
||||
git remote set-url origin https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.com/cable-mc/cobblemon.git
|
||||
|
||||
# Fetch the target branch and merge main into it
|
||||
git fetch origin lang-updates
|
||||
git checkout lang-updates
|
||||
git merge origin/ci-test
|
||||
|
||||
# Push the merged branch back to the repository
|
||||
git push origin lang-updates:lang-updates-test
|
||||
else
|
||||
echo "No need to update lang"
|
||||
fi
|
||||
only:
|
||||
- ci-test
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
### Additions
|
||||
- Added the Poké Dex as a craftable item.
|
||||
- Poké Dexes can be placed on lecterns.
|
||||
@@ -1,6 +0,0 @@
|
||||
### Additions
|
||||
- Added `moves` option to PokemonProperties, allowing you to set the moves of a Pokémon in commands and spawn files.
|
||||
- Added /spawnnpc and /spawnnpcat commands.
|
||||
|
||||
### Fixes
|
||||
- Fixed sync issues with recently learned moves where their PP would not go down until you logout and in.
|
||||
@@ -1,24 +0,0 @@
|
||||
### Additions
|
||||
- Pokémon are now animated when seen in any GUI that isn't the party GUI.
|
||||
- Quirk animations can now occur for Pokémon that are shoulder mounted.
|
||||
|
||||
### Changes
|
||||
- Improved the performance of display cases that contain Pokémon photos.
|
||||
|
||||
### Fixes
|
||||
- Fixed some Pokémon photos in display cases flashing if the Pokémon has gender differences.
|
||||
- Fixed Bidoof and Jigglypuff sleep animations stopping after some time, causing them to be 'asleep' while T-posing menacingly.
|
||||
- Fixed entities not changing poses unless you're looking at them.
|
||||
- Fixed cries not playing on send-out if the Pokémon is off-screen.
|
||||
|
||||
### Developer
|
||||
- Rebuilt large swaths of the model animation code to simplify it.
|
||||
- Renamed a bunch of things from %Poseable% to %Posable% because spelling.
|
||||
- Renamed StatelessAnimation to PoseAnimation.
|
||||
- Renamed StatefulAnimation to ActiveAnimation.
|
||||
- Documented the animation system.
|
||||
|
||||
### Data Pack & Resource Pack Creators
|
||||
- Added MoLang compatibility in the isVisible property for transformed parts.
|
||||
- Added q.has_aspect('some_aspect') function to animations, posers, and entity particle effects.
|
||||
- Added support for conditional pose animations.
|
||||
+72
-37
@@ -1,13 +1,14 @@
|
||||
# Changelog
|
||||
## [1.6.0 - The Bass Pro Fishing Update (Month Xth, 2024)](#1-6-0)
|
||||
## [1.6.0 - The Record Catch Update (Month Xth, 2024)](#1-6-0)
|
||||
#### "Now that there's a fishing mechanic, the mod is actually good!"
|
||||
|
||||
### Additions
|
||||
- Added the Pokédex as a craftable item that can be placed on lecterns.
|
||||
- Added Pokémon (and item) fishing using modified fishing rods - Poké Rods! You'll need a Poké Rod smithing template, a fishing rod, and some type of Poké Ball. Each Poké Ball makes a differently themed rod. Why not?
|
||||
- Added Lure Ball functionality, increasing the catch rate of Pokémon that were caught on a fishing rod.
|
||||
- Added Repeat Ball functionality, increasing the catch rate of Pokémon that are already registered as caught in a player's Pokédex.
|
||||
- Added property chaining support for duplicate CustomPokemonPropertyType elements.
|
||||
- Added flat level battling with options to set all Pokemon to level 50, 100, or 5 for the duration of a battle. No experience or EVs are granted for a flat battle.
|
||||
- Added flat level battling with options to set all Pokémon to level 50, 100, or 5 for the duration of a battle. No experience or EVs are granted for a flat battle.
|
||||
- Added `aspect` and `unaspect` PokemonProperty arguments (which also includes commands such as `/pokemonedit`, `/spawnpokemon`, and `/givepokemon`) to allow forcing or un-forcing an aspect on a Pokémon.
|
||||
- Added `type` alternatively `elemental_type` PokemonProperty argument, this is only used for filtering and is not applied to Pokémon. Example `type=fire` would be true for Charmander but false for Squirtle.
|
||||
- Added support for Double Battles, Triple Battles, and Multi-battles.
|
||||
@@ -25,7 +26,7 @@
|
||||
- Added battle log messages for switching out Pokémon.
|
||||
- Added Evolution particles and sounds for Pokémon that are sent out when evolution is started.
|
||||
- Added a Nurse profession that can be unlocked by having villagers claim a healing machine block.
|
||||
- Added pokecenters to all 5 village types.
|
||||
- Added Pokémon Centers to all 5 village types.
|
||||
- Added a `natural` block state property for the healing machine block; when property is set to true, the block will have a different texture and drop an iron ingot instead of itself.
|
||||
- New config setting `displayEntityNameLabel` and `displayEntityLabelsWhenCrouchingOnly` to control what and when is displayed for the pokemon label
|
||||
- `/freezepokemon` command to pause a Pokémon's animation at a specific point in time.
|
||||
@@ -33,9 +34,25 @@
|
||||
- Added shiny Pokémon effects.
|
||||
- Added effects for the burn status effect.
|
||||
- Added effects for the moves: Psychic, Water Sport, and Mud Sport.
|
||||
- Added a new universal locator called "top".
|
||||
- Added a new universal locator called `top`.
|
||||
- Added `moves` option to Pokémon properties, allowing you to set the moves of a Pokémon in commands and spawn files using comma-separated move names.
|
||||
- Added shiny Pokémon effects.
|
||||
- Added effects for the burn status effect.
|
||||
- Added effects for the moves: Seismic Toss, Withdraw, Bite, Crunch, Super Fang, Hyper Fang, Pursuit, Mist, Haze, Lick, Kinesis, Psychic, Water Sport, and Mud Sport.
|
||||
- Added a new universal locator called `top`.
|
||||
- Added shiny Pokémon particles with sound effects.
|
||||
- Added animation for trading.
|
||||
- Added animation and sounds for trading.
|
||||
- Added icons for pending trade, team-up, and battle requests from other players.
|
||||
- Added `/spawnnpc` and `/spawnnpcat` commands.
|
||||
- Pokémon are now animated when seen in any GUI that isn't the party GUI.
|
||||
- Quirk animations can now occur for Pokémon that are shoulder mounted.
|
||||
- Added `fishing boat` structures that contain an Explorer Map leading to a shipwreck cove and a Poké Rod Smithing Template.
|
||||
- Added `submerged_shipwreck_cove` and `lush_shipwreck_cove` structures.
|
||||
- Added new sounds for Poké Balls bouncing off of Pokémon and landing on the ground during capture.
|
||||
- Added a unique set of sounds for Ancient Poké Balls.
|
||||
- Added a sound for using Exp Candy and Rare Candy items.
|
||||
- Added raft platforms for non-swimming, non-flying Pokémon to stand on during battles that take place on the water's surface. (Flying Pokémon will fly over water in battle, and water breathing Pokémon will swim in water during battle.)
|
||||
- Added `hiddenability=false` option to `/spawnpokemon` and `/pokemonedit` commands, allowing the Pokémon's ability to be reverted to a normal ability.
|
||||
|
||||
### Pokémon Added
|
||||
#### Gen 1
|
||||
@@ -54,10 +71,6 @@
|
||||
- Octillery
|
||||
- Mantine
|
||||
- Smeargle
|
||||
- Remoraid
|
||||
- Octillery
|
||||
- Mantine
|
||||
- Corsola
|
||||
|
||||
#### Gen 3
|
||||
- Slakoth
|
||||
@@ -75,18 +88,6 @@
|
||||
- Bagon
|
||||
- Shelgon
|
||||
- Salamence
|
||||
|
||||
#### Gen 4
|
||||
- Shellos [East Sea form]
|
||||
- Gastrodon [East Sea form]
|
||||
- Finneon
|
||||
- Lumineon
|
||||
- Mantyke
|
||||
- Feebas
|
||||
- Milotic
|
||||
- Spheal
|
||||
- Sealeo
|
||||
- Walrein
|
||||
- Kecleon
|
||||
|
||||
#### Gen 4
|
||||
@@ -122,12 +123,6 @@
|
||||
- Hatenna
|
||||
- Hattrem
|
||||
- Hatterene
|
||||
- Bruxish
|
||||
- Mareanie
|
||||
- Toxapex
|
||||
|
||||
#### Gen 8
|
||||
- Cramorant
|
||||
- Pincurchin
|
||||
|
||||
#### Gen 9
|
||||
@@ -144,6 +139,7 @@
|
||||
- Wiglett
|
||||
- Wugtrio
|
||||
- Flamigo
|
||||
- Dondozo
|
||||
|
||||
#### Unique Forms
|
||||
- Magikarp Jump Variants
|
||||
@@ -189,7 +185,7 @@
|
||||
- Hatenna, Hattrem, Hatterene
|
||||
- Pincurchin
|
||||
- Klawf
|
||||
- Finizen, Palafin [Zero], Palafin [Hero]
|
||||
- Finizen, Palafin \[Zero\], Palafin \[Hero\]
|
||||
- Dondozo
|
||||
|
||||
### Added shoulder mounts for the following Pokémon
|
||||
@@ -224,10 +220,10 @@
|
||||
- Shroodle
|
||||
- Tatsugiri
|
||||
- Glimmet
|
||||
- Gimmighoul [Roaming]
|
||||
- Gimmighoul \[Roaming\]
|
||||
|
||||
### Animation updates for the following Pokémon
|
||||
- Bellsprout, Weepinbel, Victreebel
|
||||
- Bellsprout, Weepinbell, Victreebel
|
||||
- Shellder, Cloyster
|
||||
- Porygon, Porygon2, Porygon-Z
|
||||
- Furret
|
||||
@@ -249,7 +245,7 @@
|
||||
- Dreepy, Drakloak
|
||||
- Kleavor
|
||||
- Scorbunny
|
||||
- Gimmighoul [Roaming]
|
||||
- Gimmighoul \[Roaming\]
|
||||
|
||||
### Model updates for the following Pokémon
|
||||
- Bulbasaur, Ivysaur, Venusaur
|
||||
@@ -332,11 +328,28 @@
|
||||
- Updated particles for status effects: Paralysis, Poison, and Sleep.
|
||||
- Updated particles on Gastly.
|
||||
- Revamped stat buff and de-buff particles.
|
||||
- Improved the performance of display cases that contain Pokémon photos.
|
||||
- Updated sounds for Poké Balls.
|
||||
- Updated the Poké Ball animation for Pokémon breaking out.
|
||||
- Improved the performance of display cases that contain Pokémon Model items.
|
||||
- Removed species Base Stats from the summary interface as it is now viewable within the Pokédex.
|
||||
- Changed summary tab text labels to icons.
|
||||
- Clicking the summary interface exit button while the swap moves or evolve screen is open will cause the interface to switch back to the party screen. The button will exit the interface otherwise.
|
||||
- Ancient Poke Balls now jump once rather than shaking 3 times when capturing Pokémon.
|
||||
- The jump height indicate the number of shakes that would have occurred.
|
||||
- A high wobbly jump indicates 1 shake.
|
||||
- A high jump indicates 2 shakes.
|
||||
- A medium jump indicates 3 shakes.
|
||||
- A short jump indicates that you caught the Pokémon.
|
||||
- Pokémon sent out during battle will spawn facing their opponent.
|
||||
- Pokémon sent out outside a battle will spawn facing their trainer.
|
||||
|
||||
### Fixes
|
||||
- Fixed Ability Patches not reverting Hidden Abilities back to Normal Abilities.
|
||||
- Fixed awarding Pokémon experience upon forfeiting battles.
|
||||
- Scaled down Amaura's fetus model to avoid clipping through the tank while animating.
|
||||
- Fixed Cubone's cry not having a sound.
|
||||
- Fixed the sendout sound erronously playing when a wild Pokémon breaks out of a Poké ball.
|
||||
- Fixed the sendout sound erroneously playing when a wild Pokémon breaks out of a Poké ball.
|
||||
- Flamethrower is no longer missing sounds.
|
||||
- Fixed the Seafloor spawning context not being a usable context.
|
||||
- Fixed Pokemon spawning in non-full blocks like slabs.
|
||||
@@ -344,11 +357,6 @@
|
||||
- Fixed Pokémon losing their Hidden Ability through evolution if the middle stage did not have a Hidden Ability.
|
||||
- Hidden Power no longer plays the water type action effect (It now plays the normal type action effect).
|
||||
- Fixed Chimchar and Monferno T-posing whenever they sleep.
|
||||
- Fixed the Magby line not having any placeholder walk animations.
|
||||
- Fixed Duskull and Dusclops using skylight levels for their nether spawn data. There is no sun in the Nether!
|
||||
- Fixed Hisuian Zoroark using base Zoroark stats.
|
||||
- Fixed Bellossom clipping into the player head when shoulder mounted.
|
||||
- Fixed Shroomish's look range to prevent it from looking higher than it should.
|
||||
- Fixed Cetitan's cry breaking its walk and sleep animations.
|
||||
- Fixed Crumbling Arch not blending in with the world.
|
||||
- Fixed Energy Root applying being usable on a fainted Pokémon.
|
||||
@@ -388,6 +396,7 @@
|
||||
- Fixed Grafaiai's walk speed to prevent model sliding.
|
||||
- Fixed Alolan Exeggutor's tail not showing in the party UI. It will now also stand in front of all Pokémon in the party menu :)
|
||||
- Fixed Timburr duplicating its log while fainting.
|
||||
- Fixed hitbox sizes for Grotle and Torterra being set to default values.
|
||||
- Fixed reviving items throwing an exception when used in battle.
|
||||
- Fixed messages for Focus Sash, Confusion, Mummy, Ice Face, Own Tempo, and Revive.
|
||||
- Improve error handling when loading spawn-sets to ensure invalid configurations don't crash the server.
|
||||
@@ -405,6 +414,19 @@
|
||||
- Fixed the Ice Face ability activation being displayed incorrectly in a battle.
|
||||
- Fixed edge case of F1 and R locking you in battle with no R functionality (requiring ESC to get out).
|
||||
- Fixed Poké Ball render orientation in battle interface when capturing.
|
||||
- Fixed sync issues with recently learned moves where their PP would not go down until you logout and in.
|
||||
- Fixed some Pokémon photos in display cases flashing if the Pokémon has gender differences.
|
||||
- Fixed Bidoof and Jigglypuff sleep animations stopping after some time, causing them to be 'asleep' while T-posing menacingly.
|
||||
- Fixed entities not changing poses unless you're looking at them.
|
||||
- Fixed cries not playing on send-out if the Pokémon is off-screen.
|
||||
- Fixed text row selection highlight in battle log.
|
||||
- Fixed top black border rendering in scroll interfaces in summary UI.
|
||||
- Fixed aspect tracking for Advancements.
|
||||
- Fixed illusion not copying aspects or caught ball.
|
||||
- Delay when successfully catching a Pokémon in an Ancient Poké Ball has been increased to account for the new sounds and particles.
|
||||
- Prevent summary stats tab from making sounds when clicking on an already open tab.
|
||||
- Fixed display case not being able to be fed items from underneath.
|
||||
- Fixed Pokémon battling in water continuously sinking to the bottom.
|
||||
|
||||
### Developer
|
||||
- `SpawnCause` is now an implementation of `SpawningInfluence`.
|
||||
@@ -435,6 +457,13 @@
|
||||
- Pokémon Heal (with context)
|
||||
- Added HealingSource, an interface applied to all sources of healing from player actions, for easier tracking of healing sources.
|
||||
- Move Change
|
||||
- Shiny Chance Calculation Event (with player context)
|
||||
- Rebuilt large swaths of the model animation code to simplify it.
|
||||
- Renamed a bunch of things from %Poseable% to %Posable% because spelling.
|
||||
- Renamed StatelessAnimation to PoseAnimation.
|
||||
- Renamed StatefulAnimation to ActiveAnimation.
|
||||
- Documented the animation system.
|
||||
- Allowed for SpawnSnowstormEntityParticleHandler to handle non-posable entities (due to being non-posable, locators are useless, to set the offset use the settings in the particle itself)
|
||||
|
||||
### Data Pack & Resource Pack Creators
|
||||
- Added support for "shedders" similar to Shedinja's evolution logic.
|
||||
@@ -451,6 +480,12 @@
|
||||
- Sound events for all blocks now start with "block."
|
||||
- Gimmighoul chest and item interaction sounds have been moved to where its cry is.
|
||||
- Unused sound files and sound events have been removed.
|
||||
- Poké Ball sounds are now in their animation files, making them more flexible to edit.
|
||||
- Added MoLang compatibility in the isVisible property for transformed parts.
|
||||
- Added q.has_aspect('some_aspect') function to animations, posers, and entity particle effects.
|
||||
- Added support for conditional pose animations.
|
||||
- Added a new universal locator called "top".
|
||||
- Added `eggs_collected` and `eggs_hatched` Advancement triggers.
|
||||
|
||||
### Localization
|
||||
- Updated translations for:
|
||||
|
||||
@@ -45,21 +45,4 @@ 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);
|
||||
}
|
||||
|
||||
@Inject(
|
||||
method = "upgradeChunkTag(Lnet/minecraft/resources/ResourceKey;Ljava/util/function/Supplier;Lnet/minecraft/nbt/CompoundTag;Ljava/util/Optional;)Lnet/minecraft/nbt/CompoundTag;",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/nbt/NbtUtils;addCurrentDataVersion(Lnet/minecraft/nbt/CompoundTag;)Lnet/minecraft/nbt/CompoundTag;"
|
||||
)
|
||||
)
|
||||
public void cobblemon$addChunkVersion(
|
||||
ResourceKey<Level> resourceKey,
|
||||
Supplier<DimensionDataStorage> supplier,
|
||||
CompoundTag compoundTag,
|
||||
Optional<ResourceKey<MapCodec<? extends ChunkGenerator>>> optional,
|
||||
CallbackInfoReturnable<CompoundTag> cir
|
||||
) {
|
||||
compoundTag.put(VERSION_KEY, IntTag.valueOf(CobblemonSchemas.DATA_VERSION));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.mixin;
|
||||
|
||||
import com.cobblemon.mod.common.entity.fishing.PokeRodFishingBobberEntity;
|
||||
import net.minecraft.client.multiplayer.ClientLevel;
|
||||
import net.minecraft.client.renderer.item.ItemProperties;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
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(ItemProperties.class)
|
||||
public class ItemPropertiesMixin {
|
||||
|
||||
// Mixins into the fishing rod's register() lambda expression
|
||||
// Without this, the fishing rod item model appears cast when using a PokeRod in the other hand
|
||||
@Inject(method = "method_27883", at = @At(value = "RETURN", ordinal = 1), cancellable = true)
|
||||
private static void cobblemon$preventRodMixup(ItemStack itemStack, ClientLevel clientLevel, LivingEntity livingEntity, int i, CallbackInfoReturnable<Float> cir) {
|
||||
if (!(livingEntity instanceof Player) || ((Player) livingEntity).fishing instanceof PokeRodFishingBobberEntity) {
|
||||
cir.setReturnValue(0.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,6 @@ package com.cobblemon.mod.common.mixin;
|
||||
|
||||
import com.cobblemon.mod.common.Cobblemon;
|
||||
import com.cobblemon.mod.common.ModAPI;
|
||||
import com.cobblemon.mod.common.client.CobblemonBakingOverrides;
|
||||
import com.cobblemon.mod.common.item.PokeBallItem;
|
||||
import com.cobblemon.mod.common.item.PokedexItem;
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
@@ -20,7 +19,9 @@ import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.client.renderer.entity.ItemRenderer;
|
||||
import net.minecraft.client.resources.model.BakedModel;
|
||||
import net.minecraft.client.resources.model.ModelResourceLocation;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.item.ItemDisplayContext;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.Level;
|
||||
@@ -52,25 +53,33 @@ public abstract class ItemRendererMixin {
|
||||
)
|
||||
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;
|
||||
if (!shouldBe2d && stack.getItem() instanceof PokedexItem) {
|
||||
BakedModel replacementModel = CobblemonBakingOverrides.INSTANCE.getPokedexOverride(((PokedexItem) stack.getItem()).getType()).getModel();
|
||||
if (!cobblemon$isSameModel(model, replacementModel)) {
|
||||
ci.cancel();
|
||||
render(stack, renderMode, leftHanded, matrices, multiBufferSource, light, overlay, replacementModel);
|
||||
}
|
||||
} else if (shouldBe2d && stack.getItem() instanceof PokeBallItem pokeBallItem) {
|
||||
BakedModel replacementModel = this.itemModelShaper.getModelManager().getModel(new ModelResourceLocation(pokeBallItem.getPokeBall().getModel2d(), "inventory"));
|
||||
if (!cobblemon$isSameModel(model, replacementModel)) {
|
||||
ci.cancel();
|
||||
render(stack, renderMode, leftHanded, matrices, multiBufferSource, light, overlay, replacementModel);
|
||||
if (shouldBe2d) {
|
||||
ResourceLocation resourceLocation = null;
|
||||
if (stack.getItem() instanceof PokeBallItem pokeBallItem) resourceLocation = pokeBallItem.getPokeBall().getModel2d();
|
||||
else if (stack.getItem() instanceof PokedexItem pokedexItem) resourceLocation = pokedexItem.getType().getItemSpritePath();
|
||||
|
||||
if (resourceLocation != null) {
|
||||
BakedModel replacementModel = this.itemModelShaper.getModelManager().getModel(new ModelResourceLocation(resourceLocation, "inventory"));
|
||||
if (!cobblemon$isSameModel(model, replacementModel)) {
|
||||
ci.cancel();
|
||||
render(stack, renderMode, leftHanded, matrices, multiBufferSource, light, overlay, replacementModel);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "getModel", at = @At("HEAD"), cancellable = true)
|
||||
private void cobblemon$bakePokeballModel(ItemStack stack, Level world, LivingEntity entity, int seed, CallbackInfoReturnable<BakedModel> cir) {
|
||||
if (stack.getItem() instanceof PokeBallItem pokeBallItem) {
|
||||
BakedModel model = this.itemModelShaper.getModelManager().getModel(new ModelResourceLocation(pokeBallItem.getPokeBall().getModel3d(), MODEL_PATH));
|
||||
private void cobblemon$getItemModel(ItemStack stack, Level world, LivingEntity entity, int seed, CallbackInfoReturnable<BakedModel> cir) {
|
||||
ResourceLocation resourceLocation = null;
|
||||
if (stack.getItem() instanceof PokeBallItem pokeBallItem) resourceLocation = pokeBallItem.getPokeBall().getModel3d();
|
||||
else if (stack.getItem() instanceof PokedexItem pokedexItem) {
|
||||
boolean isScanModel = entity instanceof Player && entity.getUseItem() == stack && entity.isUsingItem();
|
||||
boolean canOpenScreen = entity != null && ((entity.getOffhandItem() == stack && !(entity.getMainHandItem().getItem() instanceof PokedexItem)) || entity.getMainHandItem() == stack);
|
||||
resourceLocation = pokedexItem.getType().getItemModelPath(isScanModel ? "scanning" : (canOpenScreen ? null : "off"));
|
||||
}
|
||||
|
||||
if (resourceLocation != null) {
|
||||
BakedModel model = this.itemModelShaper.getModelManager().getModel(new ModelResourceLocation(resourceLocation, MODEL_PATH));
|
||||
ClientLevel clientWorld = world instanceof ClientLevel ? (ClientLevel) world : null;
|
||||
BakedModel overriddenModel = model.getOverrides().resolve(model, stack, clientWorld, entity, seed);
|
||||
cir.setReturnValue(overriddenModel == null ? this.itemModelShaper.getModelManager().getMissingModel() : overriddenModel);
|
||||
|
||||
@@ -52,10 +52,11 @@ public abstract class LecternBlockMixin {
|
||||
|
||||
if (lecternEntity instanceof LecternBlockEntity) {
|
||||
((LecternBlockEntity) lecternEntity).setItemStack((pokedexItemStack));
|
||||
world.playSound(null, blockPos, CobblemonSounds.POKEDEX_CLOSE, SoundSource.BLOCKS, 0.5F, 1.25F);
|
||||
cir.setReturnValue(ItemInteractionResult.SUCCESS);
|
||||
} else {
|
||||
cir.setReturnValue(itemStack.isEmpty() && interactionHand == InteractionHand.MAIN_HAND ? ItemInteractionResult.SKIP_DEFAULT_BLOCK_INTERACTION : ItemInteractionResult.PASS_TO_DEFAULT_BLOCK_INTERACTION);
|
||||
}
|
||||
|
||||
world.playSound(null, blockPos, CobblemonSounds.POKEDEX_CLOSE, SoundSource.BLOCKS, 0.5F, 1.25F);
|
||||
cir.setReturnValue(itemStack.isEmpty() && interactionHand == InteractionHand.MAIN_HAND ? ItemInteractionResult.SKIP_DEFAULT_BLOCK_INTERACTION : ItemInteractionResult.PASS_TO_DEFAULT_BLOCK_INTERACTION);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ public abstract class MinecraftDedicatedServerMixin implements ServerInterface {
|
||||
|
||||
@Inject(method = "shouldDiscardEntity", at = @At(value = "HEAD"), cancellable = true)
|
||||
public void cobblemon$allowPokemonSpawns(Entity entity, CallbackInfoReturnable<Boolean> callback) {
|
||||
if(entity instanceof PokemonEntity) {
|
||||
if (entity instanceof PokemonEntity) {
|
||||
callback.setReturnValue(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.mixin;
|
||||
|
||||
import static com.cobblemon.mod.common.datafixer.CobblemonSchemas.VERSION_KEY;
|
||||
|
||||
import com.cobblemon.mod.common.datafixer.CobblemonSchemas;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.NbtUtils;
|
||||
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(NbtUtils.class)
|
||||
public class NbtUtilsMixin {
|
||||
@Inject(
|
||||
method = "addCurrentDataVersion(Lnet/minecraft/nbt/CompoundTag;)Lnet/minecraft/nbt/CompoundTag;",
|
||||
at = @At("TAIL")
|
||||
)
|
||||
private static void addCobblemonDataVersion(
|
||||
CompoundTag tag,
|
||||
CallbackInfoReturnable<CompoundTag> cir
|
||||
) {
|
||||
tag.putInt(VERSION_KEY, CobblemonSchemas.DATA_VERSION);
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
package com.cobblemon.mod.common.mixin;
|
||||
|
||||
import com.cobblemon.mod.common.client.CobblemonClient;
|
||||
import com.cobblemon.mod.common.client.render.ClientPlayerIcon;
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import net.minecraft.client.player.AbstractClientPlayer;
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
@@ -20,11 +20,9 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(PlayerRenderer.class)
|
||||
public class PlayerRendererMixin {
|
||||
@Inject(
|
||||
method = "render(Lnet/minecraft/client/player/AbstractClientPlayer;FFLcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/MultiBufferSource;I)V",
|
||||
at = @At(value = "TAIL")
|
||||
)
|
||||
|
||||
@Inject(method = "render", at = @At(value = "TAIL"))
|
||||
public void renderPlayer(AbstractClientPlayer player, float f, float g, PoseStack poseStack, MultiBufferSource multiBufferSource, int i, CallbackInfo ci) {
|
||||
CobblemonClient.INSTANCE.getRequests().onRenderPlayer(player, g);
|
||||
ClientPlayerIcon.Companion.onRenderPlayer(player);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.mixin.client;
|
||||
|
||||
import com.cobblemon.mod.common.client.CobblemonClient;
|
||||
import net.minecraft.client.DeltaTracker;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.Gui;
|
||||
import net.minecraft.client.gui.GuiGraphics;
|
||||
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.Redirect;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
/**
|
||||
* Mixin to give us a hook to render the PartyOverlay below the Chat
|
||||
*
|
||||
* @author Qu
|
||||
* @since 2022-02-22
|
||||
*/
|
||||
@Mixin(Gui.class)
|
||||
public class GuiMixin {
|
||||
//This injects into the actual BooleanSupplier lambda for the "this.layers.add(layeredDraw, () -> !minecraft.options.hideGui)" call
|
||||
// sus af. You have to get the method name by looking at the bytecode of the class. Can do in intellij By clicking View -> Show Bytecode (top left)
|
||||
@Inject(method = "method_55797", at = @At("HEAD"), cancellable = true)
|
||||
private static void cobblemon$dontRenderUiInPokedex(Minecraft minecraft, CallbackInfoReturnable<Boolean> cir) {
|
||||
if (CobblemonClient.INSTANCE.getPokedexUsageContext().getScanningGuiOpen() && minecraft.options.getCameraType().isFirstPerson()) {
|
||||
cir.setReturnValue(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@ import com.cobblemon.mod.common.api.drop.DropEntry
|
||||
import com.cobblemon.mod.common.api.drop.ItemDropEntry
|
||||
import com.cobblemon.mod.common.api.events.CobblemonEvents
|
||||
import com.cobblemon.mod.common.api.events.CobblemonEvents.DATA_SYNCHRONIZED
|
||||
import com.cobblemon.mod.common.api.interaction.RequestManager
|
||||
import com.cobblemon.mod.common.api.permission.PermissionValidator
|
||||
import com.cobblemon.mod.common.api.pokeball.catching.calculators.CaptureCalculator
|
||||
import com.cobblemon.mod.common.api.pokeball.catching.calculators.CaptureCalculators
|
||||
@@ -80,6 +81,7 @@ import com.cobblemon.mod.common.config.constraint.IntConstraint
|
||||
import com.cobblemon.mod.common.config.starter.StarterConfig
|
||||
import com.cobblemon.mod.common.data.CobblemonDataProvider
|
||||
import com.cobblemon.mod.common.events.AdvancementHandler
|
||||
import com.cobblemon.mod.common.events.FlowHandler
|
||||
import com.cobblemon.mod.common.events.PokedexHandler
|
||||
import com.cobblemon.mod.common.events.ServerTickHandler
|
||||
import com.cobblemon.mod.common.net.messages.client.settings.ServerSettingsPacket
|
||||
@@ -213,7 +215,7 @@ object Cobblemon {
|
||||
BattleRegistry.onPlayerDisconnect(it.player)
|
||||
storage.onPlayerDisconnect(it.player)
|
||||
playerDataManager.onPlayerDisconnect(it.player)
|
||||
TradeManager.onLogoff(it.player)
|
||||
RequestManager.onLogoff(it.player)
|
||||
}
|
||||
PlatformEvents.PLAYER_DEATH.subscribe {
|
||||
PCLinkManager.removeLink(it.player.uuid)
|
||||
@@ -285,6 +287,10 @@ object Cobblemon {
|
||||
CustomPokemonProperty.register(FreezeFrameProperty)
|
||||
CustomPokemonProperty.register(NoAIProperty)
|
||||
|
||||
CobblemonEvents.POKEMON_PROPERTY_INITIALISED.emit(Unit)
|
||||
|
||||
FlowHandler.setup()
|
||||
|
||||
ifDedicatedServer {
|
||||
isDedicatedServer = true
|
||||
}
|
||||
@@ -385,6 +391,9 @@ object Cobblemon {
|
||||
}
|
||||
|
||||
registerEventHandlers()
|
||||
|
||||
CobblemonEvents.COBBLEMON_INITIALISED.emit(Unit)
|
||||
|
||||
}
|
||||
|
||||
fun registerEventHandlers() {
|
||||
|
||||
@@ -12,9 +12,8 @@ import net.minecraft.world.entity.schedule.Activity
|
||||
|
||||
object CobblemonActivities {
|
||||
val activities = mutableListOf<Activity>()
|
||||
val BATTLING_ACTIVITY = register(Activity("pokemon_battling"))
|
||||
val NPC_BATTLING = register(Activity("npc_battling"))
|
||||
val NPC_ACTION_EFFECT = register(Activity("npc_action_effect"))
|
||||
val BATTLING = register(Activity("battling"))
|
||||
val ACTION_EFFECT = register(Activity("action_effect"))
|
||||
val NPC_CHATTING = register(Activity("npc_chatting"))
|
||||
|
||||
fun register(activity: Activity): Activity {
|
||||
|
||||
@@ -52,6 +52,7 @@ object CobblemonCommands {
|
||||
BedrockParticleCommand.register(dispatcher)
|
||||
OpenDialogueCommand.register(dispatcher)
|
||||
NPCEditCommand.register(dispatcher)
|
||||
NPCDeleteCommand.register(dispatcher)
|
||||
AbandonMultiTeam.register(dispatcher)
|
||||
FreezePokemonCommand.register(dispatcher)
|
||||
ApplyPlayerTextureCommand.register(dispatcher)
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common
|
||||
|
||||
import com.bedrockk.molang.runtime.MoLangRuntime
|
||||
import com.bedrockk.molang.runtime.value.MoValue
|
||||
import com.cobblemon.mod.common.CobblemonNetwork.sendPacket
|
||||
import com.cobblemon.mod.common.api.data.DataRegistry
|
||||
import com.cobblemon.mod.common.api.molang.ExpressionLike
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.setup
|
||||
import com.cobblemon.mod.common.api.reactive.SimpleObservable
|
||||
import com.cobblemon.mod.common.api.scripting.CobblemonScripts
|
||||
import com.cobblemon.mod.common.net.messages.client.data.FlowRegistrySyncPacket
|
||||
import com.cobblemon.mod.common.util.asExpressionLike
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.cobblemon.mod.common.util.endsWith
|
||||
import java.util.concurrent.ExecutionException
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import net.minecraft.server.packs.PackType
|
||||
import net.minecraft.server.packs.resources.ResourceManager
|
||||
|
||||
/**
|
||||
* Holds all the flows that are loaded from the server's data packs. A flow is foldered under the event identifier,
|
||||
* which can be Cobblemon's namespace but can also be minecraft or any other namespace someone has added hooks for.
|
||||
* The value of the map is a list of MoLang scripts that will execute when that event occurs.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since February 24th, 2024
|
||||
*/
|
||||
object CobblemonFlows : DataRegistry {
|
||||
override val id = cobblemonResource("flows")
|
||||
override val observable = SimpleObservable<CobblemonFlows>()
|
||||
override val type = PackType.SERVER_DATA
|
||||
override fun sync(player: ServerPlayer) {
|
||||
player.sendPacket(FlowRegistrySyncPacket(clientFlows.entries))
|
||||
}
|
||||
|
||||
val runtime by lazy { MoLangRuntime().setup() } // Lazy for if someone adds to generalFunctions in MoLangFunctions
|
||||
|
||||
val clientFlows = hashMapOf<ResourceLocation, MutableList<ExpressionLike>>()
|
||||
val flows = hashMapOf<ResourceLocation, MutableList<ExpressionLike>>()
|
||||
|
||||
override fun reload(manager: ResourceManager) {
|
||||
val folderBeforeNameRegex = ".*\\/([^\\/]+)\\/[^\\/]+\$".toRegex()
|
||||
manager.listResources("flows") { path -> path.endsWith(CobblemonScripts.MOLANG_EXTENSION) }.forEach { (identifier, resource) ->
|
||||
resource.openAsReader().use { stream ->
|
||||
stream.buffered().use { reader ->
|
||||
try {
|
||||
val expression = reader.readText().asExpressionLike()
|
||||
val event = folderBeforeNameRegex.find(identifier.path)?.groupValues?.get(1)
|
||||
?: throw IllegalArgumentException("Invalid flow path: $identifier. Should have a folder structure that includes the name of the event being flowed.")
|
||||
if (identifier.path.startsWith("flows/client/")) {
|
||||
clientFlows.getOrPut(ResourceLocation.fromNamespaceAndPath(identifier.namespace, event)) { mutableListOf() }.add(expression)
|
||||
} else {
|
||||
flows.getOrPut(ResourceLocation.fromNamespaceAndPath(identifier.namespace, event)) { mutableListOf() }.add(expression)
|
||||
}
|
||||
} catch (exception: Exception) {
|
||||
throw ExecutionException("Error loading MoLang script for flow: $identifier", exception)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Cobblemon.LOGGER.info("Loaded ${CobblemonScripts.scripts.size} flows and ${CobblemonScripts.clientScripts.size} client flows")
|
||||
observable.emit(this)
|
||||
}
|
||||
|
||||
fun run(eventResourceLocation: ResourceLocation, context: Map<String, MoValue>) {
|
||||
flows[eventResourceLocation]?.forEach { it.resolve(runtime, context) }
|
||||
}
|
||||
}
|
||||
@@ -171,14 +171,6 @@ interface CobblemonImplementation {
|
||||
*/
|
||||
fun server(): MinecraftServer?
|
||||
|
||||
/**
|
||||
* Handles platform independent reloading of a [JsonDataRegistry].
|
||||
*
|
||||
* @param registry The [JsonDataRegistry] to reload.
|
||||
* @param manager The [ResourceManager] to reload from.
|
||||
*/
|
||||
fun <T> reloadJsonRegistry(registry: JsonDataRegistry<T>, manager: ResourceManager): HashMap<ResourceLocation, T>
|
||||
|
||||
/**
|
||||
* Registers an item to the [ComposterBlock].
|
||||
*
|
||||
|
||||
@@ -19,7 +19,7 @@ import com.cobblemon.mod.common.api.text.gray
|
||||
import com.cobblemon.mod.common.block.BerryBlock
|
||||
import com.cobblemon.mod.common.block.MintBlock
|
||||
import com.cobblemon.mod.common.block.MintBlock.MintType
|
||||
import com.cobblemon.mod.common.client.pokedex.PokedexTypes
|
||||
import com.cobblemon.mod.common.client.pokedex.PokedexType
|
||||
import com.cobblemon.mod.common.entity.boat.CobblemonBoatType
|
||||
import com.cobblemon.mod.common.item.*
|
||||
import com.cobblemon.mod.common.item.armor.CobblemonArmorTrims
|
||||
@@ -155,19 +155,19 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
|
||||
val pokedexes = mutableListOf<PokedexItem>()
|
||||
@JvmField
|
||||
val POKEDEX_BLACK = pokedexItem(PokedexTypes.BLACK)
|
||||
val POKEDEX_RED = pokedexItem(PokedexType.RED)
|
||||
@JvmField
|
||||
val POKEDEX_BLUE = pokedexItem(PokedexTypes.BLUE)
|
||||
val POKEDEX_YELLOW = pokedexItem(PokedexType.YELLOW)
|
||||
@JvmField
|
||||
val POKEDEX_GREEN = pokedexItem(PokedexTypes.GREEN)
|
||||
val POKEDEX_GREEN = pokedexItem(PokedexType.GREEN)
|
||||
@JvmField
|
||||
val POKEDEX_PINK = pokedexItem(PokedexTypes.PINK)
|
||||
val POKEDEX_BLUE = pokedexItem(PokedexType.BLUE)
|
||||
@JvmField
|
||||
val POKEDEX_RED = pokedexItem(PokedexTypes.RED)
|
||||
val POKEDEX_PINK = pokedexItem(PokedexType.PINK)
|
||||
@JvmField
|
||||
val POKEDEX_WHITE = pokedexItem(PokedexTypes.WHITE)
|
||||
val POKEDEX_BLACK = pokedexItem(PokedexType.BLACK)
|
||||
@JvmField
|
||||
val POKEDEX_YELLOW = pokedexItem(PokedexTypes.YELLOW)
|
||||
val POKEDEX_WHITE = pokedexItem(PokedexType.WHITE)
|
||||
|
||||
@JvmField
|
||||
val VIVICHOKE = compostableItem("vivichoke")
|
||||
@@ -1242,13 +1242,13 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
private fun candyItem(name: String, calculator: CandyItem.Calculator): CandyItem = this.create(name, CandyItem(calculator))
|
||||
|
||||
private fun pokerodItem(pokeRodId: ResourceLocation): PokerodItem {
|
||||
val settings = Item.Properties().stacksTo(1).durability(Items.FISHING_ROD.components().get(DataComponents.MAX_DAMAGE)!!)
|
||||
val settings = Item.Properties().stacksTo(1).durability(256)
|
||||
val item = create(pokeRodId.path, PokerodItem(pokeRodId, settings))
|
||||
pokeRods.add(item)
|
||||
return item
|
||||
}
|
||||
|
||||
private fun pokedexItem(type: PokedexTypes): PokedexItem {
|
||||
private fun pokedexItem(type: PokedexType): PokedexItem {
|
||||
val item = create("pokedex_${type.name.lowercase()}", PokedexItem(type))
|
||||
pokedexes.add(item)
|
||||
return item
|
||||
|
||||
@@ -54,6 +54,7 @@ import com.cobblemon.mod.common.client.net.toast.ToastPacketHandler
|
||||
import com.cobblemon.mod.common.client.net.trade.*
|
||||
import com.cobblemon.mod.common.net.PacketRegisterInfo
|
||||
import com.cobblemon.mod.common.net.messages.client.PlayerInteractOptionsPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.SetClientPlayerDataPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.animation.PlayPosableAnimationPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.*
|
||||
import com.cobblemon.mod.common.net.messages.client.callback.OpenMoveCallbackPacket
|
||||
@@ -74,6 +75,7 @@ import com.cobblemon.mod.common.net.messages.client.pasture.ClosePasturePacket
|
||||
import com.cobblemon.mod.common.net.messages.client.pasture.OpenPasturePacket
|
||||
import com.cobblemon.mod.common.net.messages.client.pasture.PokemonPasturedPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.pasture.PokemonUnpasturedPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.pokedex.ServerConfirmedRegisterPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.pokemon.update.*
|
||||
import com.cobblemon.mod.common.net.messages.client.pokemon.update.evolution.AddEvolutionPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.pokemon.update.evolution.ClearEvolutionsPacket
|
||||
@@ -85,7 +87,6 @@ import com.cobblemon.mod.common.net.messages.client.spawn.SpawnNPCPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.spawn.SpawnPokeballPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.spawn.SpawnPokemonPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.starter.OpenStarterUIPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.SetClientPlayerDataPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.storage.RemoveClientPokemonPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.storage.SwapClientPokemonPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.storage.party.InitializePartyPacket
|
||||
@@ -94,13 +95,13 @@ import com.cobblemon.mod.common.net.messages.client.storage.party.SetPartyPokemo
|
||||
import com.cobblemon.mod.common.net.messages.client.storage.party.SetPartyReferencePacket
|
||||
import com.cobblemon.mod.common.net.messages.client.storage.pc.*
|
||||
import com.cobblemon.mod.common.net.messages.client.toast.ToastPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.trade.*
|
||||
import com.cobblemon.mod.common.net.messages.client.ui.InteractPokemonUIPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.ui.PokedexUIPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.ui.SummaryUIPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.*
|
||||
import com.cobblemon.mod.common.net.messages.server.battle.BattleSelectActionsPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.battle.RemoveSpectatorPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.battle.SpectateBattlePacket
|
||||
import com.cobblemon.mod.common.net.messages.server.battle.*
|
||||
import com.cobblemon.mod.common.net.messages.server.block.AdjustBlockEntityViewerCountPacket
|
||||
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
|
||||
@@ -113,6 +114,7 @@ import com.cobblemon.mod.common.net.messages.server.npc.SaveNPCPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.pasture.PasturePokemonPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.pasture.UnpastureAllPokemonPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.pasture.UnpasturePokemonPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.pokedex.scanner.FinishScanningPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.pokedex.scanner.StartScanningPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.pokemon.interact.InteractPokemonPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.pokemon.update.SetNicknamePacket
|
||||
@@ -128,10 +130,10 @@ import com.cobblemon.mod.common.net.messages.server.storage.pc.MovePartyPokemonT
|
||||
import com.cobblemon.mod.common.net.messages.server.storage.pc.ReleasePCPokemonPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.storage.pc.SwapPCPokemonPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.storage.pc.UnlinkPlayerFromPCPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.trade.*
|
||||
import com.cobblemon.mod.common.net.serverhandling.*
|
||||
import com.cobblemon.mod.common.net.serverhandling.battle.BattleSelectActionsHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.battle.RemoveSpectatorHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.battle.SpectateBattleHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.battle.*
|
||||
import com.cobblemon.mod.common.net.serverhandling.block.AdjustBlockEntityViewerCountHandler
|
||||
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
|
||||
@@ -145,6 +147,8 @@ import com.cobblemon.mod.common.net.serverhandling.npc.SaveNPCHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.pasture.PasturePokemonHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.pasture.UnpastureAllPokemonHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.pasture.UnpasturePokemonHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.pokedex.scanner.FinishScanningHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.pokedex.scanner.StartScanningHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.pokemon.interact.InteractPokemonHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.pokemon.update.SetNicknameHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.starter.RequestStarterScreenHandler
|
||||
@@ -162,12 +166,6 @@ import com.cobblemon.mod.common.net.serverhandling.storage.pc.MovePartyPokemonTo
|
||||
import com.cobblemon.mod.common.net.serverhandling.storage.pc.ReleasePartyPokemonHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.storage.pc.SwapPCPokemonHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.storage.pc.UnlinkPlayerFromPCHandler
|
||||
import com.cobblemon.mod.common.net.messages.client.pokedex.ServerConfirmedRegisterPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.trade.*
|
||||
import com.cobblemon.mod.common.net.messages.server.pokedex.scanner.FinishScanningPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.trade.*
|
||||
import com.cobblemon.mod.common.net.serverhandling.pokedex.scanner.FinishScanningHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.pokedex.scanner.StartScanningHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.trade.*
|
||||
import com.cobblemon.mod.common.util.server
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
@@ -305,6 +303,7 @@ object CobblemonNetwork {
|
||||
list.add(PacketRegisterInfo(PokedexDexSyncPacket.ID, PokedexDexSyncPacket::decode, DataRegistrySyncPacketHandler()))
|
||||
list.add(PacketRegisterInfo(DexEntrySyncPacket.ID, DexEntrySyncPacket::decode, DataRegistrySyncPacketHandler()))
|
||||
list.add(PacketRegisterInfo(FishingBaitRegistrySyncPacket.ID, FishingBaitRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
|
||||
list.add(PacketRegisterInfo(FlowRegistrySyncPacket.ID, FlowRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
|
||||
|
||||
// Effects
|
||||
list.add(PacketRegisterInfo(SpawnSnowstormParticlePacket.ID, SpawnSnowstormParticlePacket::decode, SpawnSnowstormParticleHandler))
|
||||
@@ -355,7 +354,7 @@ object CobblemonNetwork {
|
||||
list.add(PacketRegisterInfo(CloseNPCEditorPacket.ID, CloseNPCEditorPacket::decode, CloseNPCEditorHandler))
|
||||
list.add(PacketRegisterInfo(OpenNPCEditorPacket.ID, OpenNPCEditorPacket::decode, OpenNPCEditorHandler))
|
||||
|
||||
//Pokedex scanning
|
||||
// Pokédex scanning
|
||||
list.add(PacketRegisterInfo(ServerConfirmedRegisterPacket.ID, ServerConfirmedRegisterPacket::decode, ServerConfirmedRegisterHandler))
|
||||
return list
|
||||
}
|
||||
@@ -413,7 +412,7 @@ object CobblemonNetwork {
|
||||
list.add(PacketRegisterInfo(OfferTradePacket.ID, OfferTradePacket::decode, OfferTradeHandler))
|
||||
list.add(PacketRegisterInfo(UpdateTradeOfferPacket.ID, UpdateTradeOfferPacket::decode, UpdateTradeOfferHandler))
|
||||
|
||||
// Pokedex scanning
|
||||
// Pokédex scanning
|
||||
list.add(PacketRegisterInfo(StartScanningPacket.ID, StartScanningPacket::decode, StartScanningHandler))
|
||||
list.add(PacketRegisterInfo(FinishScanningPacket.ID, FinishScanningPacket::decode, FinishScanningHandler))
|
||||
|
||||
@@ -422,6 +421,9 @@ object CobblemonNetwork {
|
||||
list.add(PacketRegisterInfo(UnpasturePokemonPacket.ID, UnpasturePokemonPacket::decode, UnpasturePokemonHandler))
|
||||
list.add(PacketRegisterInfo(UnpastureAllPokemonPacket.ID, UnpastureAllPokemonPacket::decode, UnpastureAllPokemonHandler))
|
||||
|
||||
// Block entity
|
||||
list.add(PacketRegisterInfo(AdjustBlockEntityViewerCountPacket.ID, AdjustBlockEntityViewerCountPacket::decode, AdjustBlockEntityViewerCountHandler))
|
||||
|
||||
// Move select packets
|
||||
list.add(PacketRegisterInfo(MoveSelectedPacket.ID, MoveSelectedPacket::decode, MoveSelectedHandler))
|
||||
list.add(PacketRegisterInfo(MoveSelectCancelledPacket.ID, MoveSelectCancelledPacket::decode, MoveSelectCancelledHandler))
|
||||
|
||||
@@ -46,16 +46,6 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, ResourceKey<Regi
|
||||
@JvmField
|
||||
val HEALING_MACHINE_ACTIVE = this.create("block.healing_machine.active")
|
||||
|
||||
@JvmField
|
||||
val POKE_BALL_CAPTURE_STARTED = this.create("poke_ball.capture_started")
|
||||
@JvmField
|
||||
val POKE_BALL_CAPTURE_SUCCEEDED = this.create("poke_ball.capture_succeeded")
|
||||
@JvmField
|
||||
val POKE_BALL_SHAKE = this.create("poke_ball.shake")
|
||||
@JvmField
|
||||
val POKE_BALL_OPEN = this.create("poke_ball.open")
|
||||
@JvmField
|
||||
val POKE_BALL_SHINY_OPEN = this.create("poke_ball.shiny_open")
|
||||
@JvmField
|
||||
val POKE_BALL_HIT = this.create("poke_ball.hit")
|
||||
@JvmField
|
||||
@@ -108,6 +98,8 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, ResourceKey<Regi
|
||||
@JvmField
|
||||
val PVW_BATTLE = this.create("battle.pvw.default")
|
||||
|
||||
@JvmField
|
||||
val MEDICINE_CANDY_USE = this.create("item.medicine.candy.use")
|
||||
@JvmField
|
||||
val MEDICINE_HERB_USE = this.create("item.medicine.herb.use")
|
||||
@JvmField
|
||||
@@ -266,6 +258,13 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, ResourceKey<Regi
|
||||
RELIC_COIN_SACK_STEP
|
||||
)
|
||||
|
||||
@JvmField
|
||||
val MOVE_QUICKATTACK_TARGET = this.create("move.quickattack.target")
|
||||
@JvmField
|
||||
val MOVE_PURSUIT_TARGET = this.create("move.pursuit.target")
|
||||
@JvmField
|
||||
val MOVE_PSYCHIC_TARGET = this.create("move.psychic.target")
|
||||
|
||||
@JvmField
|
||||
val IMPACT_NORMAL = this.create("impact.normal")
|
||||
@JvmField
|
||||
|
||||
@@ -77,7 +77,7 @@ object CobblemonTradeOffers {
|
||||
)
|
||||
VillagerProfession.FISHERMAN -> listOf(
|
||||
VillagerTradeOffer(VillagerProfession.FISHERMAN, 5, listOf(
|
||||
VillagerTrades.ItemsForEmeralds(CobblemonItems.POKEROD_SMITHING_TEMPLATE, 12, 3, 30)
|
||||
VillagerTrades.ItemsForEmeralds(CobblemonItems.POKEROD_SMITHING_TEMPLATE, 12, 1, 3, 30)
|
||||
))
|
||||
)
|
||||
else -> emptyList()
|
||||
|
||||
@@ -29,6 +29,8 @@ object CobblemonCriteria : PlatformRegistry<Registry<CriterionTrigger<*>>, Resou
|
||||
|
||||
val CATCH_SHINY_POKEMON = this.create("catch_shiny_pokemon", SimpleCriterionTrigger(CountableCriterion.CODEC))
|
||||
|
||||
val EGG_COLLECT = this.create("eggs_collected", SimpleCriterionTrigger(CountableCriterion.CODEC))
|
||||
|
||||
val EGG_HATCH = this.create("eggs_hatched", SimpleCriterionTrigger(CountableCriterion.CODEC))
|
||||
|
||||
val EVOLVE_POKEMON = this.create("pokemon_evolved", SimpleCriterionTrigger(EvolvePokemonCriterion.CODEC))
|
||||
@@ -58,4 +60,4 @@ object CobblemonCriteria : PlatformRegistry<Registry<CriterionTrigger<*>>, Resou
|
||||
|
||||
override val registry = BuiltInRegistries.TRIGGER_TYPES
|
||||
override val resourceKey = Registries.TRIGGER_TYPE
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ import net.minecraft.nbt.NbtOps
|
||||
* @author Qu
|
||||
* @since January 9th, 2022
|
||||
*/
|
||||
open class Ability internal constructor(var template: AbilityTemplate, forced: Boolean) {
|
||||
open class Ability internal constructor(var template: AbilityTemplate, forced: Boolean, priority: Priority) {
|
||||
|
||||
val name: String
|
||||
get() = template.name
|
||||
@@ -57,7 +57,7 @@ open class Ability internal constructor(var template: AbilityTemplate, forced: B
|
||||
*
|
||||
* @see [Pokemon.updateAbility].
|
||||
*/
|
||||
var priority = Priority.LOWEST
|
||||
var priority = priority
|
||||
internal set
|
||||
|
||||
@Deprecated("Please use the Codec instead", ReplaceWith("Ability.CODEC"))
|
||||
@@ -111,7 +111,7 @@ open class Ability internal constructor(var template: AbilityTemplate, forced: B
|
||||
Codec.BOOL.optionalFieldOf(DataKeys.POKEMON_ABILITY_FORCED, false).forGetter(Ability::forced),
|
||||
Codec.INT.optionalFieldOf(DataKeys.POKEMON_ABILITY_INDEX, -1).forGetter(Ability::index),
|
||||
Priority.CODEC.optionalFieldOf(DataKeys.POKEMON_ABILITY_PRIORITY, Priority.LOWEST).forGetter(Ability::priority)
|
||||
).apply(it) { template, forced, index, priority -> Ability(template, forced).apply {
|
||||
).apply(it) { template, forced, index, priority -> Ability(template, forced, priority).apply {
|
||||
this.index = index
|
||||
this.priority = priority
|
||||
} }
|
||||
|
||||
@@ -23,7 +23,7 @@ import com.cobblemon.mod.common.pokemon.Species
|
||||
*/
|
||||
open class AbilityPool : PrioritizedList<PotentialAbility>() {
|
||||
fun select(species: Species, aspects: Set<String>): Pair<Ability, Priority> {
|
||||
for (priority in Priority.values()) {
|
||||
for (priority in Priority.entries) {
|
||||
val potentialAbilities = priorityMap[priority]?.filter { it.isSatisfiedBy(aspects) } ?: continue
|
||||
if (potentialAbilities.isNotEmpty()) {
|
||||
return potentialAbilities.random().template.create() to priority
|
||||
@@ -31,7 +31,7 @@ open class AbilityPool : PrioritizedList<PotentialAbility>() {
|
||||
}
|
||||
|
||||
LOGGER.error("Unable to select an ability from the pool for $species and aspects: ${aspects.joinToString()}")
|
||||
LOGGER.error("Usually this happens when a client is doing logic it shouldn't. Please show this to the Cobblemon developers!")
|
||||
LOGGER.error("Please show this to the Cobblemon developers!")
|
||||
Exception().printStackTrace()
|
||||
return Abilities.first().create() to Priority.LOWEST
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
package com.cobblemon.mod.common.api.abilities
|
||||
|
||||
import com.cobblemon.mod.common.api.Priority
|
||||
import com.cobblemon.mod.common.util.codec.CodecUtils
|
||||
import com.google.gson.JsonObject
|
||||
import com.mojang.serialization.Codec
|
||||
@@ -21,14 +22,14 @@ import net.minecraft.nbt.CompoundTag
|
||||
*/
|
||||
class AbilityTemplate(
|
||||
val name: String = "",
|
||||
var builder: (AbilityTemplate, forced: Boolean) -> Ability = { template, forced -> Ability(template, forced) },
|
||||
var builder: (AbilityTemplate, forced: Boolean, priority: Priority) -> Ability = { template, forced, priority -> Ability(template, forced, priority) },
|
||||
val displayName: String = "cobblemon.ability.$name",
|
||||
val description: String = "cobblemon.ability.$name.desc"
|
||||
) {
|
||||
/**
|
||||
* Returns the Ability or if applicable the extension connected to this template
|
||||
*/
|
||||
fun create(forced: Boolean = false) = builder(this, forced)
|
||||
fun create(forced: Boolean = false, priority: Priority = Priority.LOWEST) = builder(this, forced, priority)
|
||||
|
||||
/**
|
||||
* Returns the Ability and loads the given NBT Tag into it.
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
# Brain Configuration
|
||||
|
||||
A "Brain" is a Java Edition AI concept for more modern entities. It has tasks that are listed inside of 'activities',
|
||||
and in some cases a schedule which determines which activities will occur during different times of the day. There is
|
||||
also a set of core activities, and a default activity. In Minecraft, the most complex usage of the Brain AI system
|
||||
is on villagers which have schedules that control their behaviour during the day and night as well as child villager play behaviours.
|
||||
|
||||
Cobblemon's NPCs use the Brain AI system. As part of that, Cobblemon added the concept of a Brain Configuration
|
||||
that allows Java Edition brains to be configured in a more data-driven way.
|
||||
|
||||
By default, without any brain configuration, the default activity will be `minecraft:idle` and the core activities will be `[minecraft:core]`.
|
||||
The activities would not have any tasks though.
|
||||
|
||||
## Possible Activities
|
||||
The activities that can be used in brains is determined by code and cannot be added to using datapacks. Note that except for
|
||||
when used as defaults and core activities, these are purely names and what you use them for is entirely up to you.
|
||||
|
||||
The activities that exist in Minecraft are:
|
||||
- `minecraft:core`: The core activity intended to be used for fundamental actions like floating in water and looking at look targets.
|
||||
- `minecraft:idle`: The idle activity is used when the entity is not doing anything else. This is typically used for things like wandering and other random actions.
|
||||
- `minecraft:work`: The work activity is used for actions that are related to the entity's job. For villagers, this is things like farming, smithing, and other job-related tasks.
|
||||
- `minecraft:play`
|
||||
- `minecraft:rest`: The rest activity is used in villagers for the activity that moves them to a bed and hops into it.
|
||||
- `minecraft:meet`: The meet activity is used in villagers for going to the meeting place and gossipping with other NPCs.
|
||||
- `minecraft:panic`: The panic activity is used in villagers for when they are panicking and fleeing, usually from an attacker.
|
||||
- `minecraft:raid`
|
||||
- `minecraft:pre_raid`: The raid activity is used in villagers for what they do when a raid is about to start, like running to a safe location during a raid.
|
||||
- `minecraft:hide`
|
||||
- `minecraft:fight`: Intended to be used when engaging in combat.
|
||||
- `minecraft:celebrate`
|
||||
- `minecraft:admire_item`
|
||||
- `minecraft:avoid`
|
||||
- `minecraft:ride`
|
||||
- `minecraft:play_dead`
|
||||
- `minecraft:long_jump`
|
||||
- `minecraft:ram`
|
||||
- `minecraft:tongue`
|
||||
- `minecraft:swim`
|
||||
- `minecraft:lay_spawn`
|
||||
- `minecraft:sniff`
|
||||
- `minecraft:investigate`
|
||||
- `minecraft:roar`
|
||||
- `minecraft:emerge`
|
||||
- `minecraft:dig`
|
||||
|
||||
The activities that were added by Cobblemon are:
|
||||
- `cobblemon:battling`: For when the entity is in a Pokémon battle.
|
||||
- `cobblemon:action_effect`: For when the entity is performing an action effect. Usually registered as an almost empty activity that simply prevents the entity from doing other things.
|
||||
- `cobblemon:npc_chatting`: Intended for NPC entities using dialogue so that they look at their chat partner and do nothing else.
|
||||
|
||||
## Brain Configuration Format
|
||||
A brain is configured using a series of "Brain Configurations". Each configuration is a JSON object that contains
|
||||
a "type" field which determines the action to perform when configuring a brain. Many different types are available.
|
||||
|
||||
### script
|
||||
The `script` type executes a MoLang script which will apply some changes to the brain.
|
||||
- `script`: The MoLang script to execute. This will be a resource location for the `molang` datapack folder. Something like `cobblemon:some_script`.
|
||||
- `condition`: A MoLang expression (with `q.entity` as the entity) that determines if the script should be executed. Defaults to `"true"`.
|
||||
|
||||
### add_tasks_to_activity
|
||||
The 'add_tasks_to_activity' type adds a list of tasks to an activity.
|
||||
- `activity`: The activity to add the tasks to. This must be a pre-existing activity - a brain configuration cannot
|
||||
create new activities.
|
||||
- `condition`: A MoLang expression (with `q.entity` as the entity) that determines if the tasks should be added. If this
|
||||
is not present, the tasks will always be added.
|
||||
- `tasksByPriority`: A map of task priorities to lists of tasks to add to the activity. Each task is a JSON object with a "type" field that determines the
|
||||
task to add or in simple cases can just be the type as a simple JSON primitive to use default properties. Many different types of task config are available. See [Task Configuration](./task/README.md).
|
||||
The priority of the task is used to determine the execution order each tick. This usually does not matter, but in some cases
|
||||
where the execution of one task will move to a different activity or otherwise prevent another task, the priority matters a lot.
|
||||
As an example, a task that moves to nearby healing machines will set a walk target which prevents the use of the healing machine,
|
||||
so the use task would need to have a lower priority or be earlier in the list so that it runs sooner.
|
||||
|
||||
Example, making sure `heal_using_healing_machine` comes before `go_to_healing_machine`:
|
||||
```json
|
||||
{
|
||||
"type": "add_tasks_to_activity",
|
||||
"activity": "minecraft:idle",
|
||||
"tasksByPriority": {
|
||||
"1": [
|
||||
{
|
||||
"type": "cobblemon:heal_using_healing_machine",
|
||||
"horizontalUseRange": "2"
|
||||
},
|
||||
"cobblemon:go_to_healing_machine"
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Note that when specifying tasks, you can either use an object format with the "type" property, or if you want to use only
|
||||
default parameters for the task you can simply state the type as a string. The above example uses both formats.
|
||||
|
||||
### set_core_activities
|
||||
The `set_core_activities` type sets the core activities of the brain. It has only one property, `activities`, which is a list of activities.
|
||||
|
||||
### set_default_activity
|
||||
The `set_default_activity` type sets the default activity of the brain. It has only one property, `activity`, which is the activity to set as the default.
|
||||
|
||||
### apply_preset
|
||||
The `apply_preset` type applies a preset to the brain. Brain presets are JSON arrays under the `brain_presets` datapack folder.
|
||||
This is a way to apply a set of configurations to the brain.
|
||||
- `preset`: The name of the preset to apply. This is the name of the file in the `brain_presets` folder without the `.json` extension.
|
||||
- `condition`: A MoLang expression (with `q.entity` as the entity) that determines if the preset should be applied. Defaults to `"true"`.
|
||||
|
||||
Example brain preset (`uses_healing_machine.json`):
|
||||
```json
|
||||
[
|
||||
{
|
||||
"type": "add_tasks_to_activity",
|
||||
"activity": "idle",
|
||||
"tasksByPriority": {
|
||||
"1": [
|
||||
"heal_using_healing_machine",
|
||||
"go_to_healing_machine"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.config.task
|
||||
|
||||
import com.bedrockk.molang.runtime.struct.QueryStruct
|
||||
import com.cobblemon.mod.common.api.ai.BrainConfigurationContext
|
||||
import com.cobblemon.mod.common.entity.PosableEntity
|
||||
import com.cobblemon.mod.common.util.asExpression
|
||||
import com.cobblemon.mod.common.util.resolveBoolean
|
||||
import com.cobblemon.mod.common.util.withQueryValue
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.ai.behavior.BehaviorControl
|
||||
import net.minecraft.world.entity.ai.behavior.VillagerCalmDown
|
||||
|
||||
class CalmDownTaskConfig : SingleTaskConfig {
|
||||
var condition = "true".asExpression()
|
||||
override fun createTask(
|
||||
entity: LivingEntity,
|
||||
brainConfigurationContext: BrainConfigurationContext
|
||||
): BehaviorControl<in LivingEntity>? {
|
||||
runtime.withQueryValue("entity", (entity as? PosableEntity)?.struct ?: QueryStruct(hashMapOf()))
|
||||
if (!runtime.resolveBoolean(condition)) return null
|
||||
return VillagerCalmDown.create()
|
||||
}
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.config.task
|
||||
|
||||
import com.bedrockk.molang.runtime.struct.QueryStruct
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.api.ai.BrainConfigurationContext
|
||||
import com.cobblemon.mod.common.battles.BattleRegistry
|
||||
import com.cobblemon.mod.common.entity.PosableEntity
|
||||
import com.cobblemon.mod.common.entity.npc.NPCEntity
|
||||
import com.cobblemon.mod.common.util.asExpression
|
||||
import com.cobblemon.mod.common.util.resolveBoolean
|
||||
import com.cobblemon.mod.common.util.withQueryValue
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.ai.behavior.BehaviorControl
|
||||
import net.minecraft.world.entity.ai.behavior.declarative.BehaviorBuilder
|
||||
import net.minecraft.world.entity.ai.behavior.declarative.Trigger
|
||||
import net.minecraft.world.entity.ai.memory.MemoryModuleType
|
||||
|
||||
class ExitBattleWhenHurtTaskConfig : SingleTaskConfig {
|
||||
var condition = "true".asExpression()
|
||||
var includePassiveDamage = true
|
||||
|
||||
override fun createTask(
|
||||
entity: LivingEntity,
|
||||
brainConfigurationContext: BrainConfigurationContext
|
||||
): BehaviorControl<in LivingEntity>? {
|
||||
runtime.withQueryValue("entity", (entity as? PosableEntity)?.struct ?: QueryStruct(hashMapOf()))
|
||||
if (!runtime.resolveBoolean(condition)) return null
|
||||
|
||||
if (entity is NPCEntity) {
|
||||
fun cancelNPCBattles(npcEntity: NPCEntity): Boolean {
|
||||
val battles = npcEntity.battleIds.mapNotNull(BattleRegistry::getBattle)
|
||||
battles.forEach { it.end() }
|
||||
return battles.isNotEmpty()
|
||||
}
|
||||
|
||||
return if (includePassiveDamage) {
|
||||
BehaviorBuilder.create {
|
||||
it.group(it.present(MemoryModuleType.HURT_BY), it.present(CobblemonMemories.NPC_BATTLING))
|
||||
.apply(it) { _, _ -> Trigger { world, entity, _ -> return@Trigger cancelNPCBattles(entity as NPCEntity) } }
|
||||
}
|
||||
} else {
|
||||
BehaviorBuilder.create {
|
||||
it.group(it.present(MemoryModuleType.HURT_BY_ENTITY), it.present(CobblemonMemories.NPC_BATTLING))
|
||||
.apply(it) { _, _ -> Trigger { world, entity, _ -> return@Trigger cancelNPCBattles(entity as NPCEntity) } }
|
||||
}
|
||||
}
|
||||
} else { return null }/* else if (entity is PokemonEntity) { TODO uncomment when pokemon have brain :)
|
||||
fun cancelPokemonBattle(pokemonEntity: PokemonEntity): Boolean {
|
||||
val battle = BattleRegistry.getBattle(pokemonEntity.battleId ?: return false)
|
||||
battle?.end()
|
||||
return battle != null
|
||||
}
|
||||
|
||||
return if (includePassiveDamage) {
|
||||
BehaviorBuilder.create {
|
||||
it.group(it.present(MemoryModuleType.HURT_BY), it.present(CobblemonMemories.POKEMON_BATTLING))
|
||||
.apply(it) { _, _ -> Trigger { world, entity, _ -> return@Trigger cancelPokemonBattle(entity as PokemonEntity) } }
|
||||
}
|
||||
} else {
|
||||
BehaviorBuilder.create {
|
||||
it.group(it.present(MemoryModuleType.HURT_BY_ENTITY), it.present(CobblemonMemories.POKEMON_BATTLING))
|
||||
.apply(it) { _, _ -> Trigger { world, entity, _ -> return@Trigger cancelPokemonBattle(entity as PokemonEntity) } }
|
||||
}
|
||||
}
|
||||
}*/
|
||||
}
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.config.task
|
||||
|
||||
import com.bedrockk.molang.runtime.struct.QueryStruct
|
||||
import com.cobblemon.mod.common.api.ai.BrainConfigurationContext
|
||||
import com.cobblemon.mod.common.api.ai.WrapperLivingEntityTask
|
||||
import com.cobblemon.mod.common.entity.PosableEntity
|
||||
import com.cobblemon.mod.common.util.asExpression
|
||||
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 net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.PathfinderMob
|
||||
import net.minecraft.world.entity.ai.behavior.BehaviorControl
|
||||
import net.minecraft.world.entity.ai.behavior.SetWalkTargetAwayFrom
|
||||
import net.minecraft.world.entity.ai.memory.MemoryModuleType
|
||||
|
||||
class FleeAttackerTaskConfig : SingleTaskConfig {
|
||||
var condition = "true".asExpression()
|
||||
var speedMultiplier = "0.5".asExpression()
|
||||
var desiredDistance = "9".asExpression()
|
||||
|
||||
override fun createTask(
|
||||
entity: LivingEntity,
|
||||
brainConfigurationContext: BrainConfigurationContext
|
||||
): BehaviorControl<in LivingEntity>? {
|
||||
runtime.withQueryValue("entity", (entity as? PosableEntity)?.struct ?: QueryStruct(hashMapOf()))
|
||||
if (!runtime.resolveBoolean(condition) || entity !is PathfinderMob) return null
|
||||
val speedMultiplier = runtime.resolveFloat(speedMultiplier)
|
||||
val desiredDistance = runtime.resolveInt(desiredDistance)
|
||||
return WrapperLivingEntityTask(
|
||||
SetWalkTargetAwayFrom.entity(MemoryModuleType.HURT_BY_ENTITY, speedMultiplier, desiredDistance, false),
|
||||
PathfinderMob::class.java
|
||||
)
|
||||
}
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.config.task
|
||||
|
||||
import com.bedrockk.molang.runtime.struct.QueryStruct
|
||||
import com.cobblemon.mod.common.api.ai.BrainConfigurationContext
|
||||
import com.cobblemon.mod.common.api.ai.WrapperLivingEntityTask
|
||||
import com.cobblemon.mod.common.entity.PosableEntity
|
||||
import com.cobblemon.mod.common.util.asExpression
|
||||
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 net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.PathfinderMob
|
||||
import net.minecraft.world.entity.ai.behavior.BehaviorControl
|
||||
import net.minecraft.world.entity.ai.behavior.SetWalkTargetAwayFrom
|
||||
import net.minecraft.world.entity.ai.memory.MemoryModuleType
|
||||
|
||||
class FleeNearestHostileTaskConfig : SingleTaskConfig {
|
||||
var condition = "true".asExpression()
|
||||
var speedMultiplier = "0.5".asExpression()
|
||||
var desiredDistance = "9".asExpression()
|
||||
|
||||
override fun createTask(
|
||||
entity: LivingEntity,
|
||||
brainConfigurationContext: BrainConfigurationContext
|
||||
): BehaviorControl<in LivingEntity>? {
|
||||
runtime.withQueryValue("entity", (entity as? PosableEntity)?.struct ?: QueryStruct(hashMapOf()))
|
||||
if (!runtime.resolveBoolean(condition) || entity !is PathfinderMob) return null
|
||||
val speedMultiplier = runtime.resolveFloat(speedMultiplier)
|
||||
val desiredDistance = runtime.resolveInt(desiredDistance)
|
||||
return WrapperLivingEntityTask(
|
||||
SetWalkTargetAwayFrom.entity(MemoryModuleType.NEAREST_HOSTILE, speedMultiplier, desiredDistance, false),
|
||||
PathfinderMob::class.java
|
||||
)
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -26,14 +26,14 @@ import net.minecraft.world.entity.ai.behavior.BehaviorControl
|
||||
|
||||
class FollowWalkTargetTaskConfig : SingleTaskConfig {
|
||||
val condition: ExpressionLike = "true".asExpressionLike()
|
||||
val minRunTime: Expression = "150".asExpression()
|
||||
val maxRunTime: Expression = "250".asExpression()
|
||||
val minRunTicks: Expression = "150".asExpression()
|
||||
val maxRunTicks: Expression = "250".asExpression()
|
||||
|
||||
override fun createTask(entity: LivingEntity, brainConfigurationContext: BrainConfigurationContext): BehaviorControl<LivingEntity>? {
|
||||
runtime.withQueryValue("entity", (entity as? PosableEntity)?.struct ?: QueryStruct(hashMapOf()))
|
||||
if (!runtime.resolveBoolean(condition)) return null
|
||||
return WrapperLivingEntityTask(
|
||||
FollowWalkTargetTask(runtime.resolveInt(minRunTime), runtime.resolveInt(maxRunTime)),
|
||||
FollowWalkTargetTask(runtime.resolveInt(minRunTicks), runtime.resolveInt(maxRunTicks)),
|
||||
PathfinderMob::class.java
|
||||
)
|
||||
}
|
||||
|
||||
+1
-1
@@ -25,7 +25,7 @@ class GoToHealingMachineTaskConfig(
|
||||
val condition: ExpressionLike = "true".asExpressionLike(),
|
||||
val horizontalSearchRange: Expression = "10".asExpression(),
|
||||
val verticalSearchRange: Expression = "5".asExpression(),
|
||||
val speedMultiplier: Expression = "0.33".asExpression(),
|
||||
val speedMultiplier: Expression = "0.35".asExpression(),
|
||||
val completionRange: Expression = "1".asExpression()
|
||||
) : SingleTaskConfig {
|
||||
override fun createTask(
|
||||
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.config.task
|
||||
|
||||
import com.bedrockk.molang.Expression
|
||||
import com.cobblemon.mod.common.api.ai.BrainConfigurationContext
|
||||
import com.cobblemon.mod.common.entity.ai.LookInDirectionTask
|
||||
import com.cobblemon.mod.common.util.asExpression
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
|
||||
class LookInDirectionTaskConfig : SingleTaskConfig {
|
||||
var yaw: Expression = "0".asExpression()
|
||||
var pitch: Expression = "0".asExpression()
|
||||
|
||||
override fun createTask(
|
||||
entity: LivingEntity,
|
||||
brainConfigurationContext: BrainConfigurationContext
|
||||
) = LookInDirectionTask(yaw, pitch)
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
# Task Configuration
|
||||
|
||||
A task configuration is responsible for producing one or multiple tasks as part of a
|
||||
[BrainConfiguration](../README.md). Tasks are the smallest unit of behaviour in a brain and are executed
|
||||
as part of an activity.
|
||||
|
||||
A task configuration can be a simple string that represents the type of task to create, or a JSON object
|
||||
which a `type` property that describes what type of task is being created and other properties that configure it.
|
||||
|
||||
### one_of
|
||||
The 'one_of' type creates a task that randomly selects one of the tasks in the list to execute each tick.
|
||||
- `condition`: A MoLang expression (with `q.entity` as the entity) that determines if this entire task should exist on the entity or not. This runs at the time of entity creation, not on tick.
|
||||
- `options`: A list of objects representing the different tasks that could run for a tick. Each object must have a `task`
|
||||
which is a task configuration. Optionally, you can also specify a `weight` which is a number that determines the
|
||||
probability of the task being selected. If no weight is specified, the task will have a weight of 1.
|
||||
|
||||
### all_of
|
||||
The 'all_of' type creates a task that executes all of the tasks in the list each tick.
|
||||
- `condition`: A MoLang expression (with `q.entity` as the entity) that determines if this entire task should exist on the entity or not. This runs at the time of entity creation, not on tick.
|
||||
- `tasks`: A list of task configurations to execute each tick.
|
||||
|
||||
### do_nothing
|
||||
The 'do_nothing' type creates a task that does nothing for some period of time.
|
||||
- `condition`: A MoLang expression (with `q.entity` as the entity) that determines if this entire task should exist on the entity or not. This runs at the time of entity creation, not on tick.
|
||||
- `minDurationTicks`: A MoLang expression that determines the minimum number of ticks to do nothing. Defaults to 40. Has `q.entity` as the entity.
|
||||
- `maxDurationTicks`: A MoLang expression that determines the maximum number of ticks to do nothing. Defaults to 80. Has `q.entity` as the entity.
|
||||
|
||||
### random
|
||||
The 'random' type creates a task that chooses one of the contained tasks when the entity is spawned. Note that this does not save forever,
|
||||
so if the entity is reloaded, it will choose a new task. To make persisting randomization you have to use MoLang conditions on other task
|
||||
configurations that use `q.entity.id_modulo(some_number)` to apply randomization based on the entity UUID.
|
||||
For example, `q.entity.id_modulo(2)` will return 0 or 1 for an entity but will always be the same for the same entity,
|
||||
so using `q.entity.id_modulo(2) == 0` as a task condition will make it occur for 50% of entities and remain after reloads.
|
||||
`q.entity.id_modulo(3)` will return 0 or 1 or 2, and so on.
|
||||
|
||||
### follow_walk_target
|
||||
The 'follow_walk_target' type creates a task that makes the entity follow a walk target. This does not choose the walk target,
|
||||
it is only responsible for ensuring the entity will follow a path to whatever the current walk target is. Note that the target
|
||||
does not necessarily need to be a land target.
|
||||
- `condition`: A MoLang expression (with `q.entity` as the entity) that determines if this entire task should exist on the entity or not. This runs at the time of entity creation, not on tick.
|
||||
- `minRunTicks`: A MoLang expression that determines the minimum number of ticks to run to the target before stopping. Defaults to 150.
|
||||
- `maxRunTicks`: A MoLang expression that determines the maximum number of ticks to run to the target before giving up. Defaults to 250.
|
||||
|
||||
### look_at_target
|
||||
The 'look_at_target' type creates a task that makes the entity look at a target. This does not choose the target, it is only
|
||||
responsible for ensuring the entity will look at the target position.
|
||||
- `condition`: A MoLang expression (with `q.entity` as the entity) that determines if this entire task should exist on the entity or not. This runs at the time of entity creation, not on tick.
|
||||
- `minDurationTicks`: A MoLang expression that determines the minimum number of ticks that the entity will look at the target. Defaults to 40.
|
||||
- `maxDurationTicks`: A MoLang expression that determines the maximum number of ticks that the entity will look at the target. Defaults to 80.
|
||||
|
||||
### stay_afloat
|
||||
The 'stay_afloat' type creates a task that makes the entity stay afloat in water instead of quietly sinking and drowning.
|
||||
- `condition`: A MoLang expression (with `q.entity` as the entity) that determines if this entire task should exist on the entity or not. This runs at the time of entity creation, not on tick.
|
||||
- `chance`: A MoLang expression that determines the chance of the entity trying to stay afloat. The higher it is, the more desperately it stays above the water. Defaults to 0.01. You don't need to change this, honestly.
|
||||
|
||||
### wander
|
||||
The 'wander' type creates a task that makes the entity wander around randomly.
|
||||
- `condition`: A MoLang expression (with `q.entity` as the entity) that determines if this entire task should exist on the entity or not. This runs at the time of entity creation, not on tick.
|
||||
- `shouldWander`: A MoLang expression that determines if the entity should wander. Defaults to `math.random(0, 20*8) <= 1`, which makes it wander approximately once every 8 seconds.
|
||||
- `horizontalRange`: A MoLang expression that determines the maximum distance the entity will wander in a single path. Defaults to 10.
|
||||
- `verticalRange`: A MoLang expression that determines the maximum vertical distance the entity will wander from its starting position. Defaults to 5.
|
||||
- `speedMultiplier`: A MoLang expression that determines the speed multiplier for wandering. Defaults to 0.35 which is a standard walk speed.
|
||||
- `completionRange`: A MoLang expression that determines the distance from the target location that the entity needs to be for the path to be considered complete. Defaults to 1.
|
||||
- `lookTargetYOffset`: A MoLang expression that determines the vertical offset of the look target from the target walk position. Defaults to 1.5. You probably don't need to change this.
|
||||
|
||||
### look_at_entities
|
||||
The 'look_at_entities' type creates a task that will randomly set the entity's look target to a nearby entity.
|
||||
- `condition`: A MoLang expression (with `q.entity` as the entity) that determines if this entire task should exist on the entity or not. This runs at the time of entity creation, not on tick.
|
||||
- `maxDistance`: A MoLang expression that determines the maximum distance for an entity to be from this entity and be looked at.
|
||||
|
||||
### go_to_healing_machine
|
||||
The 'go_to_healing_machine' type creates a task that makes the entity go to a nearby healing machine when they have a static NPC party that could be healed.
|
||||
- `condition`: A MoLang expression (with `q.entity` as the entity) that determines if this entire task should exist on the entity or not. This runs at the time of entity creation, not on tick.
|
||||
- `horizontalSearchRange`: A MoLang expression that determines the maximum distance to search for a healing machine. Defaults to 10.
|
||||
- `verticalSearchRange`: A MoLang expression that determines the maximum vertical distance to search for a healing machine. Defaults to 5.
|
||||
- `speedMultiplier`: A MoLang expression that determines the speed multiplier for walking to the healing machine. Defaults to 0.35 which is a standard walk speed.
|
||||
- `completionRange`: A MoLang expression that determines the distance from the target location that the entity needs to be for the path to be considered complete. Defaults to 1.
|
||||
|
||||
### heal_using_healing_machine
|
||||
The 'heal_using_healing_machine' type creates a task that makes the entity heal its static NPC party using a healing machine if one is right next to the NPC.
|
||||
- `condition`: A MoLang expression (with `q.entity` as the entity) that determines if this entire task should exist on the entity or not. This runs at the time of entity creation, not on tick.
|
||||
- `horizontalUseRange`: A MoLang expression that determines the maximum distance to a healing machine from which the NPC can use it. Defaults to 3.
|
||||
- `verticalUseRange`: A MoLang expression that determines the maximum vertical distance to a healing machine from which the NPC can use it. Defaults to 2.
|
||||
|
||||
### switch_npc_to_battle
|
||||
The 'switch_npc_to_battle' type creates a task that will switch NPC entities to the battle activity when it is in a battle. You'd generally put this
|
||||
on the idle activity so that it automatically transitions activities when a battle starts.
|
||||
|
||||
### switch_npc_from_battle
|
||||
The 'switch_npc_from_battle' type creates a task that will switch NPC entities from the battle activity when it is no longer in a battle. You should
|
||||
put this in the battle activity so that it automatically transitions activities when a battle ends. If the NPC is in multiple battles, this
|
||||
will only activate when no battles remain. It will rely on the brain schedule to decide what activity to switch back to (usually idle).
|
||||
|
||||
### look_at_battling_pokemon
|
||||
The 'look_at_battling_pokemon' type creates a task that will set the entity's look target to switch between the battling Pokémon.
|
||||
- `minDurationTicks`: A MoLang expression that determines the minimum number of ticks that the entity will look at a specific battling Pokémon. Defaults to 40.
|
||||
- `maxDurationTicks`: A MoLang expression that determines the maximum number of ticks that the entity will look at a specific battling Pokémon. Defaults to 80.
|
||||
|
||||
### exit_battle_when_hurt
|
||||
The 'exit_battle_when_hurt' type creates a task that will make the entity exit the battle when it is hurt.
|
||||
- `condition`: A MoLang expression (with `q.entity` as the entity) that determines if this entire task should exist on the entity or not. This runs at the time of entity creation, not on tick.
|
||||
- `includePassiveDamage`: A boolean that determines if passive damage (like poison or cacti) should trigger this task. Defaults to true.
|
||||
|
||||
### switch_to_chatting
|
||||
The 'switch_to_chatting' type creates a task that will switch the entity to the chatting activity when a dialogue has been opened with that NPC.
|
||||
|
||||
### switch_from_chatting
|
||||
The 'switch_from_chatting' type creates a task that will switch the entity from the chatting activity when the dialogue has been closed with that NPC.
|
||||
It will rely on the brain schedule to decide what activity to switch back to (usually idle).
|
||||
|
||||
### look_at_speaker
|
||||
The 'look_at_speaker' type creates a task that will set the entity's look target to the speaker of the dialogue.
|
||||
|
||||
### switch_to_action_effect
|
||||
The 'switch_to_action_effect' type creates a task that will switch the entity to the action effect activity when it is performing an action effect.
|
||||
|
||||
### switch_from_action_effect
|
||||
The 'switch_from_action_effect' type creates a task that will switch the entity from the action effect activity when it is no longer performing an action effect.
|
||||
It will rely on the brain schedule to decide what activity to switch back to (usually idle).
|
||||
|
||||
### switch_to_fight
|
||||
The 'switch_to_fight' type creates a task that will switch the entity to the fight activity when it has an attack target.
|
||||
- `condition`: A MoLang expression (with `q.entity` as the entity) that determines if this entire task should exist on the entity or not. This runs at the time of entity creation, not on tick.
|
||||
- `activity`: The activity to switch to when the NPC is in a fight. Defaults to `minecraft:fight`.
|
||||
|
||||
### switch_from_fight
|
||||
The 'switch_from_fight' type creates a task that will switch the entity from the fight activity when it no longer has an attack target. It will rely on the brain schedule to decide what activity to switch back to (usually idle).
|
||||
|
||||
### get_angry_at_attacker
|
||||
The 'get_angry_at_attacker' type creates a task that will make the entity get angry at the attacker when it is attacked.
|
||||
- `condition`: A MoLang expression (with `q.entity` as the entity) that determines if this entire task should exist on the entity or not. This runs at the time of entity creation, not on tick.
|
||||
|
||||
### stop_being_angry_if_attacker_dead
|
||||
The 'stop_being_angry_if_attacker_dead' type creates a task that will make the entity stop being angry at the attacker when the attacker is dead.
|
||||
|
||||
### attack_angry_at
|
||||
The 'attack_angry_at' type creates a task that will make the entity choose the entity that it is angry at to be its attack target.
|
||||
- `condition`: A MoLang expression (with `q.entity` as the entity) that determines if this entire task should exist on the entity or not. This runs at the time of entity creation, not on tick.
|
||||
|
||||
### move_to_attack_target
|
||||
The 'move_to_attack_target' type creates a task that will make the entity move to its attack target.
|
||||
- `speedMultiplier`: A MoLang expression that determines the speed multiplier for moving to the attack target. Defaults to 0.5 which is an elevated speed to usual.
|
||||
- `closeEnoughDistance`: A MoLang expression that determines the distance from the attack target that the entity needs to be for the path to be considered complete. Defaults to 1.
|
||||
|
||||
### melee_attack
|
||||
The 'melee_attack' type creates a task that will make the entity perform a melee attack on its attack target when it is close enough. The damage dealt is determined by entity properties, which cannot be easily configured at time of writing.
|
||||
- `condition`: A MoLang expression (with `q.entity` as the entity) that determines if this entire task should exist on the entity or not. This runs at the time of entity creation, not on tick.
|
||||
- `range`: A MoLang expression that determines the maximum distance from the attack target that the entity can be to perform the attack. Defaults to 1.5.
|
||||
- `cooldownTicks`: A MoLang expression that determines the number of ticks to wait before performing another attack. Defaults to 30.
|
||||
|
||||
### switch_to_panic_when_hurt
|
||||
The 'switch_to_panic_when_hurt' type creates a task that will switch the entity to the panic activity when it is hurt.
|
||||
- `condition`: A MoLang expression (with `q.entity` as the entity) that determines if this entire task should exist on the entity or not. This runs at the time of entity creation, not on tick.
|
||||
- `includePassiveDamage`: A boolean that determines if passive damage (like poison or cacti) should trigger this task. Defaults to false.
|
||||
|
||||
### switch_to_panic_when_hostiles_nearby
|
||||
The 'switch_to_panic_when_hostiles_nearby' type creates a task that will switch the entity to the panic activity when hostiles are nearby. The definition of nearby depends
|
||||
on the entity type and this piggy-backs off the Minecraft list of aggressive mobs and the right distances.
|
||||
- `condition`: A MoLang expression (with `q.entity` as the entity) that determines if this entire task should exist on the entity or not. This runs at the time of entity creation, not on tick.
|
||||
|
||||
### calm_down
|
||||
The 'calm_down' type creates a task that will make the entity calm down when it is no longer being hurt and is far from hostiles and its attacker.
|
||||
- `condition`: A MoLang expression (with `q.entity` as the entity) that determines if this entire task should exist on the entity or not. This runs at the time of entity creation, not on tick.
|
||||
|
||||
### flee_attacker
|
||||
The 'flee_attacker' type creates a task that will make the entity flee from its attacker when it is hurt.
|
||||
- `condition`: A MoLang expression (with `q.entity` as the entity) that determines if this entire task should exist on the entity or not. This runs at the time of entity creation, not on tick.
|
||||
- `speedMultiplier`: A MoLang expression that determines the speed multiplier for fleeing from the attacker. Defaults to 0.5 which is an elevated speed to usual.
|
||||
- `desiredDistance`: A MoLang expression that determines the distance from the attacker that the entity should try to maintain. Defaults to 9.
|
||||
|
||||
### flee_nearest_hostile
|
||||
The 'flee_nearest_hostile' type creates a task that will make the entity flee from the nearest hostile entity when it is hurt.
|
||||
- `condition`: A MoLang expression (with `q.entity` as the entity) that determines if this entire task should exist on the entity or not. This runs at the time of entity creation, not on tick.
|
||||
- `speedMultiplier`: A MoLang expression that determines the speed multiplier for fleeing from the nearest hostile. Defaults to 0.5 which is an elevated speed to usual.
|
||||
- `desiredDistance`: A MoLang expression that determines the distance from the nearest hostile that the entity should try to maintain. Defaults to 9.
|
||||
|
||||
### run_script
|
||||
The 'run_script' type creates a task that will run a MoLang script every tick on this entity. This has the risk of being very laggy
|
||||
if the script is doing expensive things. Remember to use `q.entity.world.game_time` and `math.mod` to do intermittent actions inside
|
||||
the script to avoid lagging the game.
|
||||
- `script`: A resource location identifying the script to run. This should be in the `molang` datapack folder.
|
||||
|
||||
### look_in_direction
|
||||
The 'look_in_direction' type creates a task that will make the entity look in a specific direction at all times where they aren't being told to look elsewhere.
|
||||
- `yaw`: A MoLang expression that determines the yaw angle (degrees) of the entity. Defaults to `0`.
|
||||
- `pitch`: A MoLang expression that determines the pitch angle (degrees) of the entity. Defaults to `0`.
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.config.task
|
||||
|
||||
import com.bedrockk.molang.runtime.value.DoubleValue
|
||||
import com.cobblemon.mod.common.api.ai.BrainConfigurationContext
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
|
||||
import com.cobblemon.mod.common.api.scripting.CobblemonScripts
|
||||
import com.cobblemon.mod.common.entity.PosableEntity
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.cobblemon.mod.common.util.withQueryValue
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.ai.behavior.BehaviorControl
|
||||
import net.minecraft.world.entity.ai.behavior.declarative.BehaviorBuilder
|
||||
import net.minecraft.world.entity.ai.behavior.declarative.Trigger
|
||||
import net.minecraft.world.entity.ai.memory.MemoryModuleType
|
||||
|
||||
/**
|
||||
* Runs a registered MoLang script (from the molang datapack folder) each tick.
|
||||
*
|
||||
* It's up to the user to not make expensive ass scripts. Remember you can access the game time
|
||||
* from q.entity.level.game_time which can be used with a modulo operator to make something intermittent.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since December 2nd, 2024
|
||||
*/
|
||||
class RunScript : SingleTaskConfig {
|
||||
val script = cobblemonResource("dummy")
|
||||
|
||||
override fun createTask(
|
||||
entity: LivingEntity,
|
||||
brainConfigurationContext: BrainConfigurationContext
|
||||
): BehaviorControl<in LivingEntity>? = BehaviorBuilder.create {
|
||||
it.group(
|
||||
it.registered(MemoryModuleType.LOOK_TARGET) // I think I need to have at least something here?
|
||||
).apply(it) { _ ->
|
||||
Trigger { world, entity, _ ->
|
||||
runtime.withQueryValue("entity", (entity as? PosableEntity)?.struct ?: entity.asMostSpecificMoLangValue())
|
||||
return@Trigger CobblemonScripts.run(script, runtime) == DoubleValue.ONE
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+13
-4
@@ -8,15 +8,24 @@
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.config.task
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.api.ai.BrainConfigurationContext
|
||||
import com.cobblemon.mod.common.entity.npc.ai.SwitchFromActionEffectTask
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.schedule.Activity
|
||||
import net.minecraft.world.entity.ai.behavior.declarative.BehaviorBuilder
|
||||
import net.minecraft.world.entity.ai.behavior.declarative.Trigger
|
||||
|
||||
class SwitchFromActionEffectTaskConfig : SingleTaskConfig {
|
||||
val activity = Activity.IDLE
|
||||
override fun createTask(
|
||||
entity: LivingEntity,
|
||||
brainConfigurationContext: BrainConfigurationContext
|
||||
) = SwitchFromActionEffectTask.create(activity)
|
||||
) = BehaviorBuilder.create {
|
||||
it.group(
|
||||
it.absent(CobblemonMemories.ACTIVE_ACTION_EFFECT)
|
||||
).apply(it) { _ ->
|
||||
Trigger { level, entity, _ ->
|
||||
entity.brain.updateActivityFromSchedule(level.dayTime, level.gameTime)
|
||||
return@Trigger true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-4
@@ -13,10 +13,8 @@ import com.cobblemon.mod.common.api.ai.BrainConfigurationContext
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.ai.behavior.declarative.BehaviorBuilder
|
||||
import net.minecraft.world.entity.ai.behavior.declarative.Trigger
|
||||
import net.minecraft.world.entity.schedule.Activity
|
||||
|
||||
class SwitchFromChattingTaskConfig : SingleTaskConfig {
|
||||
val activity = Activity.IDLE
|
||||
override fun createTask(
|
||||
entity: LivingEntity,
|
||||
brainConfigurationContext: BrainConfigurationContext
|
||||
@@ -24,8 +22,8 @@ class SwitchFromChattingTaskConfig : SingleTaskConfig {
|
||||
it.group(
|
||||
it.absent(CobblemonMemories.DIALOGUES)
|
||||
).apply(it) { _ ->
|
||||
Trigger { world, entity, _ ->
|
||||
entity.brain.setActiveActivityIfPossible(activity)
|
||||
Trigger { level, entity, _ ->
|
||||
entity.brain.updateActivityFromSchedule(level.dayTime, level.gameTime)
|
||||
return@Trigger true
|
||||
}
|
||||
}
|
||||
|
||||
+2
-4
@@ -13,10 +13,8 @@ import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.ai.behavior.declarative.BehaviorBuilder
|
||||
import net.minecraft.world.entity.ai.behavior.declarative.Trigger
|
||||
import net.minecraft.world.entity.ai.memory.MemoryModuleType
|
||||
import net.minecraft.world.entity.schedule.Activity
|
||||
|
||||
class SwitchFromFightTaskConfig : SingleTaskConfig {
|
||||
val activity = Activity.IDLE
|
||||
override fun createTask(
|
||||
entity: LivingEntity,
|
||||
brainConfigurationContext: BrainConfigurationContext
|
||||
@@ -24,8 +22,8 @@ class SwitchFromFightTaskConfig : SingleTaskConfig {
|
||||
it.group(
|
||||
it.absent(MemoryModuleType.ATTACK_TARGET)
|
||||
).apply(it) { _ ->
|
||||
Trigger { world, entity, _ ->
|
||||
entity.brain.setActiveActivityIfPossible(activity)
|
||||
Trigger { level, entity, _ ->
|
||||
entity.brain.updateActivityFromSchedule(level.dayTime, level.gameTime)
|
||||
return@Trigger true
|
||||
}
|
||||
}
|
||||
|
||||
+11
-4
@@ -8,18 +8,25 @@
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.config.task
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.api.ai.BrainConfigurationContext
|
||||
import com.cobblemon.mod.common.entity.npc.ai.SwitchFromBattleTask
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.ai.behavior.BehaviorControl
|
||||
import net.minecraft.world.entity.schedule.Activity
|
||||
import net.minecraft.world.entity.ai.behavior.declarative.BehaviorBuilder
|
||||
import net.minecraft.world.entity.ai.behavior.declarative.Trigger
|
||||
|
||||
class SwitchFromNPCBattleTaskConfig : SingleTaskConfig {
|
||||
val activity: Activity = Activity.IDLE
|
||||
override fun createTask(
|
||||
entity: LivingEntity,
|
||||
brainConfigurationContext: BrainConfigurationContext
|
||||
): BehaviorControl<in LivingEntity>? {
|
||||
return SwitchFromBattleTask.create(activity)
|
||||
return BehaviorBuilder.create {
|
||||
it.group(it.absent(CobblemonMemories.NPC_BATTLING)).apply(it) { _ ->
|
||||
Trigger { level, entity, _ ->
|
||||
entity.brain.updateActivityFromSchedule(level.dayTime, level.gameTime)
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -26,7 +26,7 @@ class SwitchToActionEffectTaskConfig : SingleTaskConfig {
|
||||
it.absent(CobblemonMemories.ACTIVE_ACTION_EFFECT)
|
||||
).apply(it) { _ ->
|
||||
Trigger { world, entity, time ->
|
||||
entity.brain.setActiveActivityIfPossible(CobblemonActivities.NPC_ACTION_EFFECT)
|
||||
entity.brain.setActiveActivityIfPossible(CobblemonActivities.ACTION_EFFECT)
|
||||
return@Trigger true
|
||||
}
|
||||
}
|
||||
|
||||
-8
@@ -8,21 +8,15 @@
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.config.task
|
||||
|
||||
import com.bedrockk.molang.runtime.struct.QueryStruct
|
||||
import com.cobblemon.mod.common.CobblemonActivities
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.api.ai.BrainConfigurationContext
|
||||
import com.cobblemon.mod.common.entity.PosableEntity
|
||||
import com.cobblemon.mod.common.util.asExpressionLike
|
||||
import com.cobblemon.mod.common.util.resolveBoolean
|
||||
import com.cobblemon.mod.common.util.withQueryValue
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.ai.behavior.declarative.BehaviorBuilder
|
||||
import net.minecraft.world.entity.ai.behavior.declarative.Trigger
|
||||
import net.minecraft.world.entity.ai.memory.MemoryModuleType
|
||||
|
||||
class SwitchToChattingTaskConfig : SingleTaskConfig {
|
||||
val condition = "true".asExpressionLike()
|
||||
override fun createTask(
|
||||
entity: LivingEntity,
|
||||
brainConfigurationContext: BrainConfigurationContext
|
||||
@@ -32,8 +26,6 @@ class SwitchToChattingTaskConfig : SingleTaskConfig {
|
||||
it.registered(MemoryModuleType.WALK_TARGET)
|
||||
).apply(it) { _, walkTarget ->
|
||||
Trigger { world, entity, _ ->
|
||||
runtime.withQueryValue("entity", (entity as? PosableEntity)?.struct ?: QueryStruct(hashMapOf()))
|
||||
if (!runtime.resolveBoolean(condition)) return@Trigger false
|
||||
walkTarget.erase()
|
||||
entity.brain.setActiveActivityIfPossible(CobblemonActivities.NPC_CHATTING)
|
||||
return@Trigger true
|
||||
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.config.task
|
||||
|
||||
import com.cobblemon.mod.common.api.ai.BrainConfigurationContext
|
||||
import com.cobblemon.mod.common.util.asExpression
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.PathfinderMob
|
||||
import net.minecraft.world.entity.ai.behavior.declarative.BehaviorBuilder
|
||||
import net.minecraft.world.entity.ai.behavior.declarative.Trigger
|
||||
import net.minecraft.world.entity.ai.memory.MemoryModuleType
|
||||
import net.minecraft.world.entity.schedule.Activity
|
||||
|
||||
class SwitchToPanicWhenHostilesNearbyTaskConfig : SingleTaskConfig {
|
||||
var condition = "true".asExpression()
|
||||
override fun createTask(
|
||||
entity: LivingEntity,
|
||||
brainConfigurationContext: BrainConfigurationContext
|
||||
) = BehaviorBuilder.create {
|
||||
it.group(it.present(MemoryModuleType.NEAREST_HOSTILE))
|
||||
.apply(it) { nearestHostile ->
|
||||
Trigger { world, entity, _ ->
|
||||
(entity as? PathfinderMob)?.navigation?.stop()
|
||||
entity.brain.setActiveActivityIfPossible(Activity.PANIC)
|
||||
return@Trigger true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.config.task
|
||||
|
||||
import com.bedrockk.molang.runtime.struct.QueryStruct
|
||||
import com.cobblemon.mod.common.api.ai.BrainConfigurationContext
|
||||
import com.cobblemon.mod.common.entity.PosableEntity
|
||||
import com.cobblemon.mod.common.util.asExpression
|
||||
import com.cobblemon.mod.common.util.resolveBoolean
|
||||
import com.cobblemon.mod.common.util.withQueryValue
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.PathfinderMob
|
||||
import net.minecraft.world.entity.ai.behavior.BehaviorControl
|
||||
import net.minecraft.world.entity.ai.behavior.declarative.BehaviorBuilder
|
||||
import net.minecraft.world.entity.ai.behavior.declarative.Trigger
|
||||
import net.minecraft.world.entity.ai.memory.MemoryModuleType
|
||||
import net.minecraft.world.entity.schedule.Activity
|
||||
|
||||
class SwitchToPanicWhenHurtTaskConfig : SingleTaskConfig {
|
||||
var condition = "true".asExpression()
|
||||
var includePassiveDamage = false
|
||||
|
||||
override fun createTask(
|
||||
entity: LivingEntity,
|
||||
brainConfigurationContext: BrainConfigurationContext
|
||||
): BehaviorControl<in LivingEntity>? {
|
||||
runtime.withQueryValue("entity", (entity as? PosableEntity)?.struct ?: QueryStruct(hashMapOf()))
|
||||
if (!runtime.resolveBoolean(condition)) return null
|
||||
return if (includePassiveDamage) {
|
||||
BehaviorBuilder.create {
|
||||
it.group(it.present(MemoryModuleType.HURT_BY))
|
||||
.apply(it) { _ ->
|
||||
Trigger { world, entity, _ ->
|
||||
(entity as? PathfinderMob)?.navigation?.stop()
|
||||
entity.brain.setActiveActivityIfPossible(Activity.PANIC)
|
||||
return@Trigger true
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
BehaviorBuilder.create {
|
||||
it.group(it.present(MemoryModuleType.HURT_BY_ENTITY))
|
||||
.apply(it) { _ ->
|
||||
Trigger { world, entity, _ ->
|
||||
(entity as? PathfinderMob)?.navigation?.stop()
|
||||
entity.brain.setActiveActivityIfPossible(Activity.PANIC)
|
||||
return@Trigger true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -52,6 +52,14 @@ fun interface TaskConfig {
|
||||
"look_at_speaker" to LookAtSpeakerTaskConfig::class.java,
|
||||
"switch_to_action_effect" to SwitchToActionEffectTaskConfig::class.java,
|
||||
"switch_from_action_effect" to SwitchFromActionEffectTaskConfig::class.java,
|
||||
"exit_battle_when_hurt" to ExitBattleWhenHurtTaskConfig::class.java,
|
||||
"switch_to_panic_when_hurt" to SwitchToPanicWhenHurtTaskConfig::class.java,
|
||||
"switch_to_panic_when_hostiles_nearby" to SwitchToPanicWhenHostilesNearbyTaskConfig::class.java,
|
||||
"calm_down" to CalmDownTaskConfig::class.java,
|
||||
"flee_attacker" to FleeAttackerTaskConfig::class.java,
|
||||
"flee_nearest_hostile" to FleeNearestHostileTaskConfig::class.java,
|
||||
"run_script" to RunScript::class.java,
|
||||
"look_in_direction" to LookInDirectionTaskConfig::class.java,
|
||||
)
|
||||
|
||||
val runtime = MoLangRuntime().setup()
|
||||
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.battles.interpreter
|
||||
|
||||
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
|
||||
|
||||
/**
|
||||
* An exception thrown when properties from a [BattleMessage] cannot be interpreted for an [InterpreterInstruction].
|
||||
*
|
||||
* @author Segfault Guy
|
||||
* @since November 10th, 2024
|
||||
*/
|
||||
class InvalidInstructionException(battleMessage: BattleMessage) : Exception("Failed to interpret ${battleMessage.rawMessage}")
|
||||
@@ -11,7 +11,6 @@ package com.cobblemon.mod.common.api.battles.model
|
||||
import com.bedrockk.molang.runtime.MoLangRuntime
|
||||
import com.bedrockk.molang.runtime.struct.QueryStruct
|
||||
import com.bedrockk.molang.runtime.value.DoubleValue
|
||||
import com.bedrockk.molang.runtime.value.MoValue
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.Cobblemon.LOGGER
|
||||
import com.cobblemon.mod.common.CobblemonNetwork
|
||||
@@ -22,7 +21,6 @@ import com.cobblemon.mod.common.api.battles.model.actor.EntityBackedBattleActor
|
||||
import com.cobblemon.mod.common.api.battles.model.actor.FleeableBattleActor
|
||||
import com.cobblemon.mod.common.api.events.CobblemonEvents
|
||||
import com.cobblemon.mod.common.api.events.battles.BattleFledEvent
|
||||
import com.cobblemon.mod.common.api.moves.Moves
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
|
||||
import com.cobblemon.mod.common.api.net.NetworkPacket
|
||||
import com.cobblemon.mod.common.api.storage.party.PlayerPartyStore
|
||||
@@ -43,6 +41,7 @@ import com.cobblemon.mod.common.battles.dispatch.WaitDispatch
|
||||
import com.cobblemon.mod.common.battles.interpreter.ContextManager
|
||||
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
|
||||
import com.cobblemon.mod.common.battles.runner.ShowdownService
|
||||
import com.cobblemon.mod.common.entity.PlatformType
|
||||
import com.cobblemon.mod.common.entity.npc.NPCBattleActor
|
||||
import com.cobblemon.mod.common.entity.npc.NPCEntity
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
@@ -51,17 +50,16 @@ import com.cobblemon.mod.common.net.messages.client.battle.BattleMessagePacket
|
||||
import com.cobblemon.mod.common.pokemon.evolution.progress.DefeatEvolutionProgress
|
||||
import com.cobblemon.mod.common.pokemon.evolution.progress.LastBattleCriticalHitsEvolutionProgress
|
||||
import com.cobblemon.mod.common.pokemon.evolution.requirements.DefeatRequirement
|
||||
import com.cobblemon.mod.common.pokemon.helditem.CobblemonHeldItemManager
|
||||
import com.cobblemon.mod.common.util.battleLang
|
||||
import com.cobblemon.mod.common.util.getPlayer
|
||||
import net.minecraft.network.chat.Component
|
||||
import com.cobblemon.mod.common.util.giveOrDropItemStack
|
||||
import com.cobblemon.mod.common.util.itemRegistry
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import java.io.File
|
||||
import java.util.UUID
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import java.util.concurrent.ConcurrentLinkedDeque
|
||||
import net.minecraft.network.chat.Component
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import net.minecraft.world.item.ItemStack
|
||||
|
||||
@@ -96,7 +94,6 @@ open class PokemonBattle(
|
||||
.filterIsInstance<LastBattleCriticalHitsEvolutionProgress>()
|
||||
.forEach { it.reset() }
|
||||
}
|
||||
actor.setupStruct()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -271,22 +268,31 @@ open class PokemonBattle(
|
||||
else -> continue
|
||||
}
|
||||
val experience = Cobblemon.experienceCalculator.calculate(opponentPokemon, faintedPokemon, multiplier)
|
||||
if (experience > 0) {
|
||||
if (experience > 0 && actor.pokemonList.all { it.health <= 0 }) {
|
||||
opponent.awardExperience(opponentPokemon, experience)
|
||||
}
|
||||
Cobblemon.evYieldCalculator.calculate(opponentPokemon, faintedPokemon).forEach { (stat, amount) ->
|
||||
pokemon.evs.add(stat, amount)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
if(actor.itemsUsed.isNotEmpty() && actor.getPlayerUUIDs().count() > 0) {
|
||||
if (actor.itemsUsed.isNotEmpty() && actor.getPlayerUUIDs().count() > 0) {
|
||||
val player = actor.getPlayerUUIDs().first().getPlayer()
|
||||
player?.level()?.itemRegistry.let { registry ->
|
||||
actor.itemsUsed.mapNotNull { registry?.get(ResourceLocation.tryBySeparator(it.itemName.substringAfter('.'), '.')) }
|
||||
.forEach { player?.giveOrDropItemStack(ItemStack(it))}
|
||||
}
|
||||
}
|
||||
|
||||
// Heal NPC's team if enabled
|
||||
if (actor is NPCBattleActor) {
|
||||
if (actor.npc.npc.autoHealParty) {
|
||||
actor.npc.party?.heal()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
// Heal Mon if wild
|
||||
actors.filter { it.type == ActorType.WILD }
|
||||
@@ -297,6 +303,11 @@ open class PokemonBattle(
|
||||
actors.forEach { actor ->
|
||||
actor.pokemonList.forEach { battlePokemon ->
|
||||
battlePokemon.entity?.let { entity -> battlePokemon.postBattleEntityOperation(entity) }
|
||||
if (battlePokemon.effectedPokemon.entity != null
|
||||
&& battlePokemon.effectedPokemon.entity?.beamMode == 0
|
||||
&& battlePokemon.effectedPokemon.entity?.platform != PlatformType.NONE) {
|
||||
battlePokemon.effectedPokemon.tryRecallWithAnimation()
|
||||
}
|
||||
}
|
||||
}
|
||||
sendUpdate(BattleEndPacket())
|
||||
|
||||
+3
-5
@@ -11,6 +11,7 @@ package com.cobblemon.mod.common.api.battles.model.actor
|
||||
import com.bedrockk.molang.runtime.struct.QueryStruct
|
||||
import com.bedrockk.molang.runtime.value.StringValue
|
||||
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
|
||||
import com.cobblemon.mod.common.api.net.NetworkPacket
|
||||
import com.cobblemon.mod.common.battles.ActiveBattlePokemon
|
||||
import com.cobblemon.mod.common.battles.ForcePassActionResponse
|
||||
@@ -36,10 +37,11 @@ abstract class BattleActor(
|
||||
pokemonList.forEach { it.actor = this }
|
||||
}
|
||||
|
||||
val struct = QueryStruct(hashMapOf())
|
||||
val struct = asMoLangValue()
|
||||
|
||||
lateinit var showdownId: String
|
||||
lateinit var battle: PokemonBattle
|
||||
fun isInitialized() = this::battle.isInitialized && this::showdownId.isInitialized
|
||||
|
||||
val activePokemon = mutableListOf<ActiveBattlePokemon>()
|
||||
var canDynamax = false
|
||||
@@ -138,10 +140,6 @@ abstract class BattleActor(
|
||||
battle.writeShowdownAction(">$showdownId ${showdownMessages.joinToString()}")
|
||||
}
|
||||
|
||||
open fun setupStruct() {
|
||||
struct.addFunction("uuid") { StringValue(uuid.toString()) }
|
||||
}
|
||||
|
||||
abstract fun getName(): MutableComponent
|
||||
|
||||
/**
|
||||
|
||||
@@ -11,6 +11,7 @@ package com.cobblemon.mod.common.api.data
|
||||
import com.cobblemon.mod.common.api.net.Decodable
|
||||
import com.cobblemon.mod.common.api.net.Encodable
|
||||
|
||||
// TODO this is implemented in multiple places but unused. Can we scratch this?
|
||||
/**
|
||||
* Represents an object that will possibly be synchronized to the client during datapack synchronization.
|
||||
*
|
||||
|
||||
@@ -9,8 +9,11 @@
|
||||
package com.cobblemon.mod.common.api.data
|
||||
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.util.endsWith
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import java.io.File
|
||||
import java.util.concurrent.ExecutionException
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.server.packs.resources.ResourceManager
|
||||
|
||||
@@ -42,7 +45,24 @@ interface JsonDataRegistry<T> : DataRegistry {
|
||||
val resourcePath: String
|
||||
|
||||
override fun reload(manager: ResourceManager) {
|
||||
this.reload(Cobblemon.implementation.reloadJsonRegistry(this, manager))
|
||||
val data = hashMapOf<ResourceLocation, T>()
|
||||
manager.listResources(resourcePath) { path -> path.endsWith(JSON_EXTENSION) }.forEach { (identifier, resource) ->
|
||||
if (identifier.namespace == "pixelmon") {
|
||||
return@forEach
|
||||
}
|
||||
|
||||
resource.open().use { stream ->
|
||||
stream.bufferedReader().use { reader ->
|
||||
val resolvedIdentifier = ResourceLocation.fromNamespaceAndPath(identifier.namespace, File(identifier.path).nameWithoutExtension)
|
||||
try {
|
||||
data[resolvedIdentifier] = gson.fromJson(reader, typeToken.type)
|
||||
} catch (exception: Exception) {
|
||||
throw ExecutionException("Error loading JSON for data: $identifier", exception)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
reload(data)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -21,13 +21,13 @@ import com.cobblemon.mod.common.util.adapters.DialogueTextAdapter
|
||||
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.TextAdapter
|
||||
import com.cobblemon.mod.common.util.adapters.TranslatedTextAdapter
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import net.minecraft.network.chat.MutableComponent
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import net.minecraft.server.packs.PackType
|
||||
|
||||
/**
|
||||
@@ -56,7 +56,7 @@ object Dialogues : JsonDataRegistry<Dialogue> {
|
||||
.registerTypeAdapter(DialogueText::class.java, DialogueTextAdapter)
|
||||
.registerTypeAdapter(Expression::class.java, ExpressionAdapter)
|
||||
.registerTypeAdapter(ExpressionLike::class.java, ExpressionLikeAdapter)
|
||||
.registerTypeAdapter(MutableComponent::class.java, TextAdapter)
|
||||
.registerTypeAdapter(MutableComponent::class.java, TranslatedTextAdapter)
|
||||
.registerTypeAdapter(ResourceLocation::class.java, IdentifierAdapter)
|
||||
.also { gsonObservable.emit(it) }
|
||||
.create()
|
||||
|
||||
@@ -11,11 +11,12 @@ package com.cobblemon.mod.common.api.drop
|
||||
import com.cobblemon.mod.common.api.events.CobblemonEvents.LOOT_DROPPED
|
||||
import com.cobblemon.mod.common.api.events.drops.LootDroppedEvent
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import kotlin.random.Random
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
import net.minecraft.server.level.ServerLevel
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.phys.Vec3
|
||||
import kotlin.random.Random
|
||||
|
||||
/**
|
||||
* A table of drops that can produce a list of [DropEntry]. You can produce a drop list from [getDrops] to
|
||||
@@ -96,4 +97,14 @@ class DropTable {
|
||||
ifSucceeded = { it.drops.forEach { it.drop(entity, world, pos, player) } }
|
||||
)
|
||||
}
|
||||
|
||||
fun encode(buffer: RegistryFriendlyByteBuf) {
|
||||
buffer.writeInt(this.entries.size)
|
||||
this.entries.filterIsInstance<ItemDropEntry>().forEach { it.encode(buffer) }
|
||||
}
|
||||
|
||||
fun decode(buffer: RegistryFriendlyByteBuf) {
|
||||
val entries = buffer.readInt()
|
||||
repeat(entries) { this.entries.add(ItemDropEntry().decode(buffer)) }
|
||||
}
|
||||
}
|
||||
@@ -17,9 +17,10 @@ import com.cobblemon.mod.common.util.toBlockPos
|
||||
import net.minecraft.core.component.DataComponentMap
|
||||
import net.minecraft.core.component.DataComponentPatch
|
||||
import net.minecraft.core.registries.Registries
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.server.level.ServerLevel
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.item.ItemEntity
|
||||
import net.minecraft.world.item.ItemStack
|
||||
@@ -33,12 +34,12 @@ import net.minecraft.world.phys.Vec3
|
||||
* @since July 24th, 2022
|
||||
*/
|
||||
open class ItemDropEntry : DropEntry {
|
||||
override val percentage = 100F
|
||||
override val quantity = 1
|
||||
open val quantityRange: IntRange? = null
|
||||
override val maxSelectableTimes = 1
|
||||
override var percentage = 100F
|
||||
override var quantity = 1
|
||||
open var quantityRange: IntRange? = null
|
||||
override var maxSelectableTimes = 1
|
||||
open val dropMethod: ItemDropMethod? = null
|
||||
open val item = ResourceLocation.parse("minecraft:fish")
|
||||
open var item = ResourceLocation.parse("minecraft:fish")
|
||||
open val components: DataComponentMap? = null
|
||||
|
||||
override fun drop(entity: LivingEntity?, world: ServerLevel, pos: Vec3, player: ServerPlayer?) {
|
||||
@@ -76,4 +77,19 @@ open class ItemDropEntry : DropEntry {
|
||||
world.addFreshEntity(ItemEntity(world, pos.x, pos.y, pos.z, stack))
|
||||
}
|
||||
}
|
||||
|
||||
fun encode(buffer: RegistryFriendlyByteBuf) {
|
||||
buffer.writeFloat(this.percentage)
|
||||
buffer.writeVarInt(this.quantity)
|
||||
buffer.writeResourceLocation(this.item)
|
||||
buffer.writeNullable(this.quantityRange) { _, it -> buffer.writeVarInt(it.first); buffer.writeVarInt(it.last) }
|
||||
}
|
||||
|
||||
fun decode(buffer: RegistryFriendlyByteBuf): ItemDropEntry {
|
||||
this.percentage = buffer.readFloat()
|
||||
this.quantity = buffer.readVarInt()
|
||||
this.item = buffer.readResourceLocation()
|
||||
this.quantityRange = buffer.readNullable { buffer.readVarInt()..buffer.readVarInt() }
|
||||
return this
|
||||
}
|
||||
}
|
||||
@@ -8,11 +8,13 @@
|
||||
|
||||
package com.cobblemon.mod.common.api.entity.pokemon
|
||||
|
||||
import com.cobblemon.mod.common.api.pokeball.PokeBalls
|
||||
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
|
||||
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.entity.pokemon.effects.IllusionEffect
|
||||
import com.cobblemon.mod.common.entity.pokemon.effects.TransformEffect
|
||||
import com.cobblemon.mod.common.pokeball.PokeBall
|
||||
import com.cobblemon.mod.common.pokemon.FormData
|
||||
import com.cobblemon.mod.common.pokemon.Species
|
||||
import com.cobblemon.mod.common.util.DataKeys
|
||||
@@ -95,4 +97,7 @@ interface MocKEffect : PhysicalEffect {
|
||||
get() = this.mock.form?.let {
|
||||
formID -> this.exposedSpecies?.forms?.firstOrNull { it.formOnlyShowdownId().equals(formID, true) } }
|
||||
?: this.exposedSpecies?.standardForm
|
||||
|
||||
val exposedBall: PokeBall?
|
||||
get() = this.mock.pokeball?.let { PokeBalls.getPokeBall(it.asIdentifierDefaultingNamespace()) }
|
||||
}
|
||||
|
||||
@@ -54,6 +54,10 @@ import net.minecraft.server.level.ServerPlayer
|
||||
@Suppress("unused")
|
||||
object CobblemonEvents {
|
||||
|
||||
@JvmField
|
||||
val POKEMON_PROPERTY_INITIALISED = SimpleObservable<Unit>()
|
||||
@JvmField
|
||||
val COBBLEMON_INITIALISED = SimpleObservable<Unit>()
|
||||
@JvmField
|
||||
val DATA_SYNCHRONIZED = SimpleObservable<ServerPlayer>()
|
||||
@JvmField
|
||||
@@ -96,7 +100,7 @@ object CobblemonEvents {
|
||||
|
||||
// instructions
|
||||
@JvmField
|
||||
val MEGA_EVOLUTON = EventObservable<MegaEvolutionEvent>()
|
||||
val MEGA_EVOLUTION = EventObservable<MegaEvolutionEvent>()
|
||||
@JvmField
|
||||
val TERASTALLIZATION = EventObservable<TerastallizationEvent>()
|
||||
@JvmField
|
||||
@@ -129,6 +133,8 @@ object CobblemonEvents {
|
||||
val POKEMON_ENTITY_SAVE_TO_WORLD = CancelableObservable<PokemonEntitySaveToWorldEvent>()
|
||||
@JvmField
|
||||
val ENTITY_SPAWN = CancelableObservable<SpawnEvent<*>>()
|
||||
@JvmField
|
||||
val SHINY_CHANCE_CALCULATION = EventObservable<ShinyChanceCalculationEvent>()
|
||||
|
||||
@JvmField
|
||||
val POKEMON_ENTITY_SPAWN = ENTITY_SPAWN
|
||||
|
||||
+4
-4
@@ -19,8 +19,8 @@ import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
|
||||
* @author Segfault Guy
|
||||
* @since April 6th, 2023
|
||||
*/
|
||||
data class BattleFaintedEvent (
|
||||
override val battle: PokemonBattle,
|
||||
val killed: BattlePokemon,
|
||||
val context: BattleContext
|
||||
data class BattleFaintedEvent(
|
||||
override val battle: PokemonBattle,
|
||||
val killed: BattlePokemon,
|
||||
val context: BattleContext
|
||||
) : BattleEvent
|
||||
|
||||
@@ -18,8 +18,6 @@ import com.cobblemon.mod.common.battles.actor.PlayerBattleActor
|
||||
* @since March 25th 2023
|
||||
*/
|
||||
data class BattleFledEvent (
|
||||
|
||||
override val battle: PokemonBattle,
|
||||
val player: PlayerBattleActor
|
||||
|
||||
) : BattleEvent
|
||||
+23
-1
@@ -8,8 +8,14 @@
|
||||
|
||||
package com.cobblemon.mod.common.api.events.battles
|
||||
|
||||
import com.bedrockk.molang.runtime.value.DoubleValue
|
||||
import com.bedrockk.molang.runtime.value.MoValue
|
||||
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
|
||||
import com.cobblemon.mod.common.api.battles.model.actor.ActorType
|
||||
import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
|
||||
import com.cobblemon.mod.common.battles.actor.PlayerBattleActor
|
||||
import com.cobblemon.mod.common.entity.npc.NPCBattleActor
|
||||
import com.cobblemon.mod.common.util.asArrayValue
|
||||
|
||||
/**
|
||||
* Event fired when a battle is won by some number of [BattleActor]s.
|
||||
@@ -22,4 +28,20 @@ data class BattleVictoryEvent (
|
||||
val winners : List<BattleActor>,
|
||||
val losers : List<BattleActor>,
|
||||
val wasWildCapture : Boolean
|
||||
) : BattleEvent
|
||||
) : BattleEvent {
|
||||
val context = mutableMapOf<String, MoValue>(
|
||||
"battle" to battle.struct,
|
||||
"winners" to winners.asArrayValue { it.struct },
|
||||
"losers" to losers.asArrayValue { it.struct },
|
||||
"was_wild_capture" to DoubleValue(wasWildCapture),
|
||||
"player_winners" to winners.filter { it.type == ActorType.PLAYER }.asArrayValue { it.struct },
|
||||
"player_losers" to losers.filter { it.type == ActorType.PLAYER }.asArrayValue { it.struct },
|
||||
"npc_winners" to winners.filter { it.type == ActorType.NPC }.asArrayValue { it.struct },
|
||||
"npc_losers" to losers.filter { it.type == ActorType.NPC }.asArrayValue { it.struct },
|
||||
"wild_pokemon_winners" to winners.filter { it.type == ActorType.WILD }.asArrayValue { it.struct },
|
||||
"wild_pokemon_losers" to losers.filter { it.type == ActorType.WILD }.asArrayValue { it.struct },
|
||||
"players" to battle.actors.filter { it.type == ActorType.PLAYER }.asArrayValue { it.struct },
|
||||
"npcs" to battle.actors.filter { it.type == ActorType.NPC }.asArrayValue { it.struct },
|
||||
"wild_pokemon" to battle.actors.filter { it.type == ActorType.WILD }.asArrayValue { it.struct }
|
||||
)
|
||||
}
|
||||
+12
-1
@@ -8,12 +8,23 @@
|
||||
|
||||
package com.cobblemon.mod.common.api.events.pokemon
|
||||
|
||||
import com.bedrockk.molang.runtime.value.MoValue
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
|
||||
import com.cobblemon.mod.common.entity.pokeball.EmptyPokeBallEntity
|
||||
import com.cobblemon.mod.common.pokemon.Pokemon
|
||||
import com.cobblemon.mod.common.util.itemRegistry
|
||||
import net.minecraft.core.registries.Registries
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
|
||||
data class PokemonCapturedEvent (
|
||||
val pokemon: Pokemon,
|
||||
val player: ServerPlayer,
|
||||
val pokeBallEntity: EmptyPokeBallEntity
|
||||
)
|
||||
) {
|
||||
val context = mutableMapOf<String, MoValue>(
|
||||
"pokemon" to pokemon.struct,
|
||||
"player" to player.asMoLangValue(),
|
||||
"poke_ball" to pokeBallEntity.struct,
|
||||
"item" to player.level().itemRegistry.wrapAsHolder(pokeBallEntity.pokeBall.item).asMoLangValue(Registries.ITEM)
|
||||
)
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.events.pokemon
|
||||
|
||||
import com.cobblemon.mod.common.pokemon.Pokemon
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
|
||||
/**
|
||||
* An event to modify the shiny chance of a Pokémon.
|
||||
*/
|
||||
class ShinyChanceCalculationEvent(
|
||||
val baseChance: Float,
|
||||
val pokemon: Pokemon
|
||||
) {
|
||||
val chance: Float = baseChance
|
||||
var isShiny: Boolean = false
|
||||
private val modifiers = mutableListOf<Float>()
|
||||
private val modificationFunctions = mutableListOf<(Float, ServerPlayer?, Pokemon) -> Float>()
|
||||
|
||||
/**
|
||||
* Adds a modifier to the shiny chance.
|
||||
*/
|
||||
fun addModifier(modifier: Float) {
|
||||
modifiers.add(modifier)
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a function to modify the shiny chance.
|
||||
*/
|
||||
fun addModificationFunction(function: (Float, ServerPlayer?, Pokemon) -> Float) {
|
||||
modificationFunctions.add(function)
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the shiny chance of a Pokémon.
|
||||
*/
|
||||
fun calculate(player: ServerPlayer?): Float {
|
||||
var result = baseChance
|
||||
for (modifier in modifiers) {
|
||||
result += modifier
|
||||
}
|
||||
for (function in modificationFunctions) {
|
||||
result = function(result, player, pokemon)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
fun isShiny(player: ServerPlayer?): Boolean {
|
||||
return calculate(player) >= chance || isShiny
|
||||
}
|
||||
}
|
||||
@@ -13,11 +13,12 @@ import com.cobblemon.mod.common.api.events.fishing.BaitEffectFunctionRegistryEve
|
||||
import com.cobblemon.mod.common.api.spawning.fishing.FishingSpawnCause
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.mojang.serialization.Codec
|
||||
import com.mojang.serialization.codecs.RecordCodecBuilder
|
||||
import java.util.Optional
|
||||
import net.minecraft.core.Registry
|
||||
import net.minecraft.nbt.CompoundTag
|
||||
import net.minecraft.nbt.ListTag
|
||||
import net.minecraft.network.codec.ByteBufCodecs
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.world.entity.Entity
|
||||
import net.minecraft.world.item.Item
|
||||
import net.minecraft.world.item.ItemStack
|
||||
|
||||
@@ -33,46 +34,30 @@ data class FishingBait(
|
||||
val chance: Double = 0.0,
|
||||
val value: Double = 0.0
|
||||
) {
|
||||
fun toNbt(): CompoundTag {
|
||||
val nbt = CompoundTag()
|
||||
nbt.putString("Type", type.toString())
|
||||
subcategory?.let { nbt.putString("Subcategory", it.toString()) }
|
||||
nbt.putDouble("Chance", chance)
|
||||
nbt.putDouble("Value", value)
|
||||
return nbt
|
||||
}
|
||||
constructor(type: ResourceLocation, subcategory: Optional<ResourceLocation>, chance: Double, value: Double) : this(type, subcategory.orElse(null), chance, value)
|
||||
|
||||
companion object {
|
||||
fun fromNbt(nbt: CompoundTag): Effect {
|
||||
val type = ResourceLocation.parse(nbt.getString("Type"))
|
||||
val subcategory = if (nbt.contains("Subcategory")) ResourceLocation.parse(nbt.getString("Subcategory")) else null
|
||||
val chance = nbt.getDouble("Chance")
|
||||
val value = nbt.getDouble("Value")
|
||||
return Effect(type, subcategory, chance, value)
|
||||
val CODEC = RecordCodecBuilder.create<Effect> { instance ->
|
||||
instance.group(
|
||||
ResourceLocation.CODEC.fieldOf("type").forGetter { it.type },
|
||||
ResourceLocation.CODEC.optionalFieldOf("subcategory").forGetter { Optional.ofNullable(it.subcategory) },
|
||||
Codec.DOUBLE.fieldOf("chance").forGetter { it.chance },
|
||||
Codec.DOUBLE.fieldOf("value").forGetter { it.value }
|
||||
).apply(instance, ::Effect)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun toNbt(): CompoundTag {
|
||||
val nbt = CompoundTag()
|
||||
nbt.putString("Item", item.toString())
|
||||
val effectsList = ListTag()
|
||||
effects.forEach { effectsList.add(it.toNbt()) }
|
||||
nbt.put("Effects", effectsList)
|
||||
return nbt
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun fromNbt(nbt: CompoundTag): FishingBait {
|
||||
val item = ResourceLocation.parse(nbt.getString("Item"))
|
||||
val effectsList = nbt.getList("Effects", 10) // 10 is the type for NbtCompound
|
||||
val effects = mutableListOf<Effect>()
|
||||
for (i in 0 until effectsList.size) {
|
||||
effects.add(Effect.fromNbt(effectsList.getCompound(i)))
|
||||
}
|
||||
return FishingBait(item, effects)
|
||||
val CODEC = RecordCodecBuilder.create<FishingBait> { instance ->
|
||||
instance.group(
|
||||
ResourceLocation.CODEC.fieldOf("item").forGetter { it.item },
|
||||
Effect.CODEC.listOf().fieldOf("effects").forGetter {it.effects}
|
||||
).apply(instance, ::FishingBait)
|
||||
}
|
||||
|
||||
val STREAM_CODEC = ByteBufCodecs.fromCodec(CODEC)
|
||||
|
||||
val BLANK_BAIT = FishingBait(
|
||||
cobblemonResource("blank"),
|
||||
emptyList()
|
||||
@@ -87,7 +72,8 @@ data class FishingBait(
|
||||
val BITE_TIME = cobblemonResource("bite_time")
|
||||
val GENDER_CHANCE = cobblemonResource("gender_chance")
|
||||
val LEVEL_RAISE = cobblemonResource("level_raise")
|
||||
val TERA = cobblemonResource("tera")
|
||||
val TYPING = cobblemonResource("typing")
|
||||
val EGG_GROUP = cobblemonResource("egg_group")
|
||||
val SHINY_REROLL = cobblemonResource("shiny_reroll")
|
||||
val HIDDEN_ABILITY_CHANCE = cobblemonResource("ha_chance")
|
||||
val POKEMON_CHANCE = cobblemonResource("pokemon_chance")
|
||||
@@ -108,7 +94,6 @@ data class FishingBait(
|
||||
EFFECT_FUNCTIONS[SHINY_REROLL] = { entity, effect -> FishingSpawnCause.shinyReroll(entity, effect) }
|
||||
EFFECT_FUNCTIONS[GENDER_CHANCE] = { entity, effect -> FishingSpawnCause.alterGenderAttempt(entity, effect) }
|
||||
EFFECT_FUNCTIONS[LEVEL_RAISE] = { entity, effect -> FishingSpawnCause.alterLevelAttempt(entity, effect) }
|
||||
EFFECT_FUNCTIONS[TERA] = { entity, effect -> FishingSpawnCause.alterTeraAttempt(entity, effect) }
|
||||
EFFECT_FUNCTIONS[HIDDEN_ABILITY_CHANCE] = { entity, _ -> FishingSpawnCause.alterHAAttempt(entity) }
|
||||
EFFECT_FUNCTIONS[FRIENDSHIP] = { entity, effect -> FishingSpawnCause.alterFriendshipAttempt(entity, effect) }
|
||||
CobblemonEvents.BAIT_EFFECT_REGISTRATION.post(BaitEffectFunctionRegistryEvent()) { event ->
|
||||
|
||||
@@ -24,7 +24,7 @@ abstract class ParentWidget(
|
||||
component: Component
|
||||
): Renderable, AbstractWidget(pX, pY, pWidth, pHeight, component) {
|
||||
|
||||
private val children: MutableList<GuiEventListener> = mutableListOf()
|
||||
val children: MutableList<GuiEventListener> = mutableListOf()
|
||||
|
||||
/**
|
||||
* Adds Widget to the children list
|
||||
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.interaction
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonNetwork
|
||||
import com.cobblemon.mod.common.CobblemonNetwork.sendPacket
|
||||
import com.cobblemon.mod.common.api.net.NetworkPacket
|
||||
import com.cobblemon.mod.common.api.text.red
|
||||
import com.cobblemon.mod.common.api.text.yellow
|
||||
import com.cobblemon.mod.common.battles.TeamManager
|
||||
import com.cobblemon.mod.common.util.lang
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import java.util.UUID
|
||||
|
||||
/**
|
||||
* Represents an interaction request between players.
|
||||
*
|
||||
* @author Segfault Guy
|
||||
* @since September 3rd, 2024
|
||||
*/
|
||||
interface PlayerActionRequest {
|
||||
/** The unique ID of this request. */
|
||||
val requestID: UUID
|
||||
|
||||
/** The unique ID of the party that's initiating this request. */
|
||||
val senderID: UUID
|
||||
|
||||
/** The amount of seconds this request is valid for. */
|
||||
val expiryTime: Int
|
||||
}
|
||||
|
||||
/**
|
||||
* An outbound [PlayerActionRequest].
|
||||
*
|
||||
* @author Segfault Guy
|
||||
* @since September 29th, 2024
|
||||
*/
|
||||
interface ServerPlayerActionRequest : PlayerActionRequest {
|
||||
|
||||
/** The subkey identifying lang entries associated with this request. */
|
||||
val key: String
|
||||
|
||||
/** The player initiating this interaction request. */
|
||||
val sender: ServerPlayer
|
||||
|
||||
/** The player that's the target of this interaction request. */
|
||||
val receiver: ServerPlayer
|
||||
|
||||
/** The unique ID of the party that's the target of this request. */
|
||||
val receiverID: UUID get() = receiver.uuid
|
||||
|
||||
override val senderID: UUID get() = sender.uuid
|
||||
|
||||
/** Sends [packet] to sending party of this request. */
|
||||
fun sendToSender(packet: NetworkPacket<*>) = sender.sendPacket(packet)
|
||||
|
||||
/** Sends [packet] to receiving party of this request. */
|
||||
fun sendToReceiver(packet: NetworkPacket<*>) = receiver.sendPacket(packet)
|
||||
|
||||
/** Notifies the sending party of this request about [langKey]. */
|
||||
fun notifySender(error: Boolean, langKey: String, vararg params: Any) = notify(sender, error, "$key.$langKey", *params)
|
||||
|
||||
/** Notifies the receiving party of this request about [langKey]. */
|
||||
fun notifyReceiver(error: Boolean, langKey: String, vararg params: Any) = notify(receiver, error, "$key.$langKey", *params)
|
||||
|
||||
/** System message to inform individual [player] about [langKey]. */
|
||||
fun notify(player: ServerPlayer, error: Boolean, langKey: String, vararg params: Any) {
|
||||
val lang = lang(langKey, *params).apply { if (error) red() else yellow() }
|
||||
player.sendSystemMessage(lang, false)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An outbound [PlayerActionRequest] made on behalf of a team.
|
||||
*
|
||||
* @author Segfault Guy
|
||||
* @since October 29th, 2024
|
||||
*/
|
||||
interface ServerTeamActionRequest : ServerPlayerActionRequest {
|
||||
|
||||
/** The team initiating this request. */
|
||||
val senderTeam: TeamManager.MultiBattleTeam
|
||||
|
||||
/** The team that's the target of this request. */
|
||||
val receiverTeam: TeamManager.MultiBattleTeam
|
||||
|
||||
override val senderID: UUID get() = senderTeam.teamID
|
||||
|
||||
override val receiverID: UUID get() = receiverTeam.teamID
|
||||
|
||||
override fun sendToSender(packet: NetworkPacket<*>) = CobblemonNetwork.sendPacketToPlayers(senderTeam.teamPlayers, packet)
|
||||
|
||||
override fun sendToReceiver(packet: NetworkPacket<*>) = CobblemonNetwork.sendPacketToPlayers(receiverTeam.teamPlayers, packet)
|
||||
|
||||
override fun notifySender(error: Boolean, langKey: String, vararg params: Any) =
|
||||
senderTeam.teamPlayers.forEach { this.notify(it, error, "$key.$langKey", *params) }
|
||||
|
||||
override fun notifyReceiver(error: Boolean, langKey: String, vararg params: Any) =
|
||||
receiverTeam.teamPlayers.forEach { this.notify(it, error, "$key.$langKey", *params) }
|
||||
}
|
||||
@@ -0,0 +1,230 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.interaction
|
||||
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.api.net.NetworkPacket
|
||||
import com.cobblemon.mod.common.api.scheduling.afterOnServer
|
||||
import com.cobblemon.mod.common.api.text.aqua
|
||||
import com.cobblemon.mod.common.api.text.red
|
||||
import com.cobblemon.mod.common.battles.TeamManager
|
||||
import com.cobblemon.mod.common.util.*
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Defines a manager for organizing active interaction requests between parties. Responsible for dispatching requests to clients,
|
||||
* notifying senders and receivers of requests, and handling their acceptance, expiration, or cancellation.
|
||||
*
|
||||
* Implementations can manage either PLAYER-to-PLAYER requests or TEAM-to-TEAM requests.
|
||||
*
|
||||
* @param T The type of interaction request this manager tracks.
|
||||
*
|
||||
* @author Segfault Guy
|
||||
* @since October 26th, 2024
|
||||
*/
|
||||
abstract class RequestManager<T : ServerPlayerActionRequest> {
|
||||
|
||||
/**
|
||||
* Tracking of the active [ServerPlayerActionRequest]s by their sender.
|
||||
*
|
||||
* NOTE: only 1 request can be sent by a sender at a time.
|
||||
*/
|
||||
private val outboundRequests = mutableMapOf<UUID, T>()
|
||||
|
||||
/**
|
||||
* Tracking of the active [ServerPlayerActionRequest]s by their recipient.
|
||||
*
|
||||
* NOTE: a recipient can receive multiple requests from different senders at the same time.
|
||||
*/
|
||||
private val inboundRequests = mutableMapOf<UUID, MutableList<T>>()
|
||||
|
||||
// we use the term 'sender' because BattleChallenges can be sent TEAM-to-TEAM instead of PLAYER-to-PLAYER for multi battles
|
||||
|
||||
/** Queries the latest pending request that was sent by [senderID]. */
|
||||
fun getOutboundRequest(senderID: UUID) = outboundRequests.get(senderID)
|
||||
|
||||
/** Queries the pending request of [requestID] that was sent by [senderID]. */
|
||||
fun getOutboundRequest(senderID: UUID, requestID: UUID) = this.getOutboundRequest(senderID)?.takeIf { it.requestID == requestID }
|
||||
|
||||
/** Queries the pending request that was sent by [senderID] and received by [receiverID]. */
|
||||
fun getOutboundRequestByRecipient(senderID: UUID, receiverID: UUID) = this.getOutboundRequest(senderID)?.takeIf { it.receiverID == receiverID }
|
||||
|
||||
/** Queries the pending request of [requestID] that was sent by [sender] or their team. */
|
||||
fun getOutboundRequest(sender: ServerPlayer, requestID: UUID) = TeamManager.getTeam(sender)?.let { this.getOutboundRequest(it.teamID, requestID) }
|
||||
?: this.getOutboundRequest(sender.uuid, requestID)
|
||||
|
||||
/** Queries all pending requests that were received by [receiverID]. */
|
||||
fun getInboundRequests(receiverID: UUID) = inboundRequests.get(receiverID)
|
||||
|
||||
/** Queries the pending request of [requestID] that was received by [receiverID]. */
|
||||
fun getInboundRequest(receiverID: UUID, requestID: UUID) = this.getInboundRequests(receiverID)?.find { it.requestID == requestID }
|
||||
|
||||
/** Queries the pending request that was sent by [senderID] and received by [receiverID]. */
|
||||
fun getInboundRequestBySender(receiverID: UUID, senderID: UUID) = this.getInboundRequests(receiverID)?.find { it.senderID == senderID }
|
||||
|
||||
/** Queries the pending request of [requestID] that was received by [receiver] or their team. */
|
||||
fun getInboundRequest(receiver: ServerPlayer, requestID: UUID) = TeamManager.getTeam(receiver)?.let { this.getInboundRequest(it.teamID, requestID) }
|
||||
?: this.getInboundRequest(receiver.uuid, requestID)
|
||||
|
||||
/** Determines whether [player] can receive a [T]. */
|
||||
open fun isBusy(player: ServerPlayer): Boolean = player.isInBattle() || player.isTrading()
|
||||
|
||||
/** Determines if the [target] is a valid interaction request target. */
|
||||
abstract fun isValidInteraction(player: ServerPlayer, target: ServerPlayer): Boolean
|
||||
|
||||
/** Determines whether [request] response is valid and can be accepted. If invalid, notifies the sender and receiver of the request why. */
|
||||
protected abstract fun canAccept(request: T): Boolean
|
||||
|
||||
/** Creates a packet for notifying a recipient about a received request. */
|
||||
protected abstract fun notificationPacket(request: T): NetworkPacket<*>
|
||||
|
||||
/** Creates a packet for canceling received requests. */
|
||||
protected abstract fun expirationPacket(request: T): NetworkPacket<*>
|
||||
|
||||
protected fun addRequest(request: T) {
|
||||
outboundRequests.put(request.senderID, request)
|
||||
inboundRequests.computeIfAbsent(request.receiverID) { mutableListOf() }.add(request)
|
||||
request.sendToReceiver(this.notificationPacket(request))
|
||||
}
|
||||
|
||||
protected fun removeRequest(request: T): Boolean {
|
||||
val inbound = inboundRequests.get(request.receiverID)
|
||||
val removeOutbound = outboundRequests.remove(request.senderID, request)
|
||||
val removeInbound = inbound?.remove(request) == true
|
||||
if (removeInbound && inbound?.size == 0) inboundRequests.remove(request.receiverID, inbound)
|
||||
if (removeInbound != removeOutbound)
|
||||
Cobblemon.LOGGER.error("RequestManager: PlayerActionRequest desync") // USE ADDREQUEST AND REMOVEREQUEST
|
||||
|
||||
val isRemoved = removeInbound && removeOutbound
|
||||
if (isRemoved) request.sendToReceiver(this.expirationPacket(request))
|
||||
return isRemoved
|
||||
}
|
||||
|
||||
/** Notifies and removes an outbound request [T] sent by [ServerPlayerActionRequest.sender] to [ServerPlayerActionRequest.receiver]. */
|
||||
open fun cancelRequest(request: T, expired: Boolean = false) {
|
||||
if (!this.removeRequest(request)) return
|
||||
// canceled by expiration
|
||||
if (expired) {
|
||||
request.notifySender(true, "expired.sender", request.receiver.name.copy().aqua())
|
||||
request.notifyReceiver(true, "expired.receiver", request.sender.name.copy().aqua())
|
||||
}
|
||||
// canceled by sender
|
||||
else {
|
||||
request.notifySender(true, "canceled.sender", request.receiver.name.copy().aqua())
|
||||
request.notifyReceiver(true, "canceled.receiver", request.sender.name.copy().aqua())
|
||||
}
|
||||
// receivers DECLINE, not cancel
|
||||
}
|
||||
|
||||
/** Hook into [sendRequest] immediately after the [request] is sent and added to the manager. Default behavior is to notify parties. */
|
||||
protected open fun onSend(request: T) {
|
||||
request.notifySender(false, "sent", request.receiver.name.copy().aqua())
|
||||
request.notifyReceiver(false, "received", request.sender.name.copy().aqua())
|
||||
}
|
||||
|
||||
/** Sends an outbound request [T] from [ServerPlayerActionRequest.sender] to [ServerPlayerActionRequest.receiver]. */
|
||||
fun sendRequest(request: T): Boolean {
|
||||
val existingRequest = this.getOutboundRequest(request.senderID)
|
||||
val pendingRequest = this.getInboundRequestBySender(request.senderID, request.receiverID)
|
||||
|
||||
// old request
|
||||
if (existingRequest != null && existingRequest.receiverID != request.receiverID)
|
||||
this.cancelRequest(existingRequest)
|
||||
// if sender already has a request awaiting a response from the receiver they're trying to send to.
|
||||
if (existingRequest != null && existingRequest.receiverID == request.receiverID)
|
||||
request.notify(request.sender, true, "${request.key}.error.duplicate", request.receiver.name.copy().aqua())
|
||||
// if sender already has a pending request from the receiver they're trying to send to.
|
||||
else if (pendingRequest != null)
|
||||
request.notify(request.sender, true, "${request.key}.error.pending", request.receiver.name.copy().aqua())
|
||||
// verify sending player can interact with target player
|
||||
else if (!this.isValidInteraction(request.sender, request.receiver))
|
||||
request.notify(request.sender, true, "ui.interact.failed")
|
||||
else if (this.isBusy(request.receiver)) // TODO call to isBusy when setting up the interaction packet
|
||||
request.notify(request.sender, true, "ui.interact.unavailable")
|
||||
// new request
|
||||
else {
|
||||
this.addRequest(request)
|
||||
afterOnServer(seconds = request.expiryTime.toFloat()) { this.cancelRequest(request, true) }
|
||||
this.onSend(request)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/** Hook into [acceptRequest] immediately after the [request] is accepted and removed from the manager. Default behavior is to notify parties. */
|
||||
protected open fun onAccept(request: T) {
|
||||
request.notifySender(false, "accept.sender", request.receiver.name.copy().aqua())
|
||||
request.notifyReceiver(false, "accept.receiver", request.sender.name.copy().aqua())
|
||||
}
|
||||
|
||||
/** Accepts a pending inbound [requestID] for [player]. */
|
||||
fun acceptRequest(player: ServerPlayer, requestID: UUID, target: ServerPlayer? = null): Boolean {
|
||||
var accepted = false
|
||||
val request = this.getInboundRequest(player, requestID)
|
||||
|
||||
// verify request being responded to still valid
|
||||
if (request == null)
|
||||
player.sendSystemMessage(lang("ui.interact.request_already_expired").red(), false)
|
||||
// verify accepting player can respond to sending player
|
||||
else if (!this.isValidInteraction(player, target ?: request.sender)) // with teams anyone can accept
|
||||
request.notify(player, true, "ui.interact.failed")
|
||||
// if sending player is occupied, can't accept response
|
||||
else if (this.isBusy(request.sender)) // TODO enhancement: allow retries - modify client so we don't remove request when sending acceptance
|
||||
request.notify(player, true, "ui.interact.unavailable")
|
||||
// if no condition is blocking acceptance, accept
|
||||
else if (this.canAccept(request))
|
||||
accepted = true
|
||||
|
||||
request?.let {
|
||||
this.removeRequest(it)
|
||||
if (accepted) this.onAccept(request) // order matters
|
||||
}
|
||||
return accepted
|
||||
}
|
||||
|
||||
/** Hook into [declineRequest] immediately after the [request] is declined and removed from the manager. Default behavior is to notify parties. */
|
||||
protected open fun onDecline(request: T) {
|
||||
request.notifySender(true, "decline.sender", request.receiver.name.copy().aqua())
|
||||
request.notifyReceiver(false, "decline.receiver", request.sender.name.copy().aqua())
|
||||
}
|
||||
|
||||
/** Declines a pending [request]. */
|
||||
fun declineRequest(request: T) {
|
||||
this.removeRequest(request)
|
||||
this.onDecline(request)
|
||||
}
|
||||
|
||||
/** Declines an inbound request [requestID] for [receiver]. */
|
||||
fun declineRequest(receiver: ServerPlayer, requestID: UUID) {
|
||||
val request = this.getInboundRequest(receiver, requestID) ?: return
|
||||
this.declineRequest(request)
|
||||
}
|
||||
|
||||
/** Cancels pending outbound requests sent from, and pending inbound request sent to, [player]. */
|
||||
protected open fun onLogoff(player: ServerPlayer) {
|
||||
// ONLY regarding the player. see TeamManager for how team requests are canceled on team disband.
|
||||
outboundRequests.get(player.uuid)?.let { request ->
|
||||
this.cancelRequest(request)
|
||||
}
|
||||
inboundRequests.get(player.uuid)?.toMutableList()?.let { requests ->
|
||||
requests.forEach { request ->
|
||||
this.declineRequest(player, request.requestID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val managers = mutableListOf<RequestManager<*>>()
|
||||
|
||||
fun register(manager: RequestManager<*>) = managers.add(manager)
|
||||
|
||||
fun onLogoff(player: ServerPlayer) = managers.forEach { it.onLogoff(player) }
|
||||
}
|
||||
}
|
||||
@@ -22,27 +22,43 @@ import com.cobblemon.mod.common.CobblemonActivities
|
||||
import com.cobblemon.mod.common.CobblemonBlockEntities
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
|
||||
import com.cobblemon.mod.common.api.battles.model.actor.ActorType
|
||||
import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
|
||||
import com.cobblemon.mod.common.api.dialogue.PlayerDialogueFaceProvider
|
||||
import com.cobblemon.mod.common.api.dialogue.ReferenceDialogueFaceProvider
|
||||
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
|
||||
import com.cobblemon.mod.common.api.moves.animations.ActionEffects
|
||||
import com.cobblemon.mod.common.api.moves.animations.NPCProvider
|
||||
import com.cobblemon.mod.common.api.pokemon.stats.Stats
|
||||
import com.cobblemon.mod.common.api.scripting.CobblemonScripts
|
||||
import com.cobblemon.mod.common.api.spawning.context.SpawningContext
|
||||
import com.cobblemon.mod.common.api.storage.PokemonStore
|
||||
import com.cobblemon.mod.common.api.storage.party.PartyStore
|
||||
import com.cobblemon.mod.common.api.storage.party.PlayerPartyStore
|
||||
import com.cobblemon.mod.common.api.storage.pc.PCPosition
|
||||
import com.cobblemon.mod.common.api.storage.pc.PCStore
|
||||
import com.cobblemon.mod.common.api.text.text
|
||||
import com.cobblemon.mod.common.battles.BattleRegistry
|
||||
import com.cobblemon.mod.common.battles.actor.PlayerBattleActor
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.wavefunction.WaveFunctions
|
||||
import com.cobblemon.mod.common.entity.PosableEntity
|
||||
import com.cobblemon.mod.common.entity.npc.NPCBattleActor
|
||||
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.entity.pokemon.ai.PokemonMoveControl
|
||||
import com.cobblemon.mod.common.net.messages.client.animation.PlayPosableAnimationPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.effect.RunPosableMoLangPacket
|
||||
import com.cobblemon.mod.common.pokemon.Pokemon
|
||||
import com.cobblemon.mod.common.util.*
|
||||
import com.mojang.datafixers.util.Either
|
||||
import java.util.UUID
|
||||
import kotlin.math.sqrt
|
||||
import net.minecraft.commands.arguments.EntityAnchorArgument
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.core.Holder
|
||||
import net.minecraft.core.Registry
|
||||
import net.minecraft.core.registries.BuiltInRegistries
|
||||
import net.minecraft.core.registries.Registries
|
||||
import net.minecraft.nbt.CompoundTag
|
||||
import net.minecraft.nbt.DoubleTag
|
||||
@@ -51,15 +67,23 @@ import net.minecraft.nbt.StringTag
|
||||
import net.minecraft.nbt.Tag
|
||||
import net.minecraft.resources.ResourceKey
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.server.MinecraftServer
|
||||
import net.minecraft.server.level.ServerLevel
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import net.minecraft.tags.TagKey
|
||||
import net.minecraft.world.damagesource.DamageSource
|
||||
import net.minecraft.world.damagesource.DamageTypes
|
||||
import net.minecraft.world.entity.Entity
|
||||
import net.minecraft.world.entity.EntityType
|
||||
import net.minecraft.world.entity.LightningBolt
|
||||
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.memory.MemoryModuleType
|
||||
import net.minecraft.world.entity.ai.memory.MemoryStatus
|
||||
import net.minecraft.world.entity.ai.memory.WalkTarget
|
||||
import net.minecraft.world.entity.player.Player
|
||||
import net.minecraft.world.level.Level
|
||||
import net.minecraft.world.level.Level.ExplosionInteraction
|
||||
import net.minecraft.world.level.biome.Biome
|
||||
import net.minecraft.world.level.block.Block
|
||||
import net.minecraft.world.level.dimension.DimensionType
|
||||
@@ -74,6 +98,25 @@ import net.minecraft.world.phys.Vec3
|
||||
*/
|
||||
object MoLangFunctions {
|
||||
val generalFunctions = hashMapOf<String, java.util.function.Function<MoParams, Any>>(
|
||||
"print" to java.util.function.Function { params ->
|
||||
val message = params.get<MoValue>(0).asString()
|
||||
Cobblemon.LOGGER.info(message)
|
||||
},
|
||||
"replace" to java.util.function.Function { params ->
|
||||
val text = params.getString(0)
|
||||
val search = params.getString(1)
|
||||
val replace = params.getString(2)
|
||||
return@Function StringValue(text.replace(search, replace))
|
||||
},
|
||||
"is_blank" to java.util.function.Function { params ->
|
||||
val arg = params.get<MoValue>(0)
|
||||
return@Function DoubleValue((arg is StringValue && (arg.value.isBlank() || arg.value.toDoubleOrNull() == 0.0)) || (arg is DoubleValue && arg.value == 0.0))
|
||||
},
|
||||
"run_command" to java.util.function.Function { params ->
|
||||
val command = params.getString(0)
|
||||
val server = server() ?: return@Function DoubleValue.ZERO
|
||||
server.commands.performPrefixedCommand(server.createCommandSourceStack(), command)
|
||||
},
|
||||
"is_int" to java.util.function.Function { params -> DoubleValue(params.get<MoValue>(0).asString().isInt()) },
|
||||
"is_number" to java.util.function.Function { params -> DoubleValue(params.get<MoValue>(0).asString().toDoubleOrNull() != null) },
|
||||
"to_number" to java.util.function.Function { params -> DoubleValue(params.get<MoValue>(0).asString().toDoubleOrNull() ?: 0.0) },
|
||||
@@ -108,36 +151,107 @@ object MoLangFunctions {
|
||||
runtime.environment.variable = params.environment.variable
|
||||
runtime.environment.context = params.environment.context
|
||||
val script = params.getString(0).asIdentifierDefaultingNamespace()
|
||||
CobblemonScripts.run(script, runtime) ?: DoubleValue(0)
|
||||
CobblemonScripts.run(script, runtime) ?: DoubleValue.ZERO
|
||||
},
|
||||
"run_command" to java.util.function.Function { params ->
|
||||
val command = params.getString(0)
|
||||
server()?.let {
|
||||
return@Function DoubleValue(it.commands.dispatcher.execute(command, it.createCommandSourceStack()))
|
||||
} ?: return@Function DoubleValue.ZERO
|
||||
"run_molang" to java.util.function.Function { params ->
|
||||
val runtime = MoLangRuntime()
|
||||
runtime.environment.query = params.environment.query
|
||||
runtime.environment.variable = params.environment.variable
|
||||
runtime.environment.context = params.environment.context
|
||||
val expression = params.getString(0).asExpressionLike()
|
||||
runtime.resolve(expression)
|
||||
},
|
||||
)
|
||||
val biomeFunctions = mutableListOf<(Holder<Biome>) -> HashMap<String, java.util.function.Function<MoParams, Any>>>()
|
||||
val worldFunctions = mutableListOf<(Holder<Level>) -> HashMap<String, java.util.function.Function<MoParams, Any>>>(
|
||||
{ worldHolder ->
|
||||
val world = worldHolder.value()
|
||||
val map = hashMapOf<String, java.util.function.Function<MoParams, Any>>()
|
||||
map.put("game_time") { _ -> DoubleValue(world.gameTime.toDouble()) }
|
||||
map.put("time_of_day") {
|
||||
val time = world.dayTime % 24000
|
||||
return@put DoubleValue(time.toDouble())
|
||||
}
|
||||
map.put("server") { _ -> server()?.asMoLangValue() ?: DoubleValue.ZERO }
|
||||
map.put("is_raining_at") { params ->
|
||||
val x = params.getInt(0)
|
||||
val y = params.getInt(1)
|
||||
val z = params.getInt(2)
|
||||
return@put DoubleValue(world.isRainingAt(BlockPos(x, y, z)))
|
||||
}
|
||||
map.put("is_chunk_loaded_at") { params ->
|
||||
val x = params.getInt(0)
|
||||
val y = params.getInt(1)
|
||||
val z = params.getInt(2)
|
||||
return@put DoubleValue(world.isLoaded(BlockPos(x, y, z)))
|
||||
}
|
||||
map.put("is_thundering") { _ -> DoubleValue(world.isThundering) }
|
||||
map.put("is_raining") { _ -> DoubleValue(world.isRaining) }
|
||||
map.put("set_block") { params ->
|
||||
val x = params.getInt(0)
|
||||
val y = params.getInt(1)
|
||||
val z = params.getInt(2)
|
||||
val block = world.blockRegistry.get(params.getString(3).asIdentifierDefaultingNamespace())
|
||||
?: run {
|
||||
Cobblemon.LOGGER.error("Unknown block: ${params.getString(3)}")
|
||||
return@put DoubleValue.ZERO
|
||||
}
|
||||
world.setBlock(BlockPos(x, y, z), block.defaultBlockState(), Block.UPDATE_ALL)
|
||||
}
|
||||
map.put("get_block") { params ->
|
||||
val x = params.getInt(0)
|
||||
val y = params.getInt(1)
|
||||
val z = params.getInt(2)
|
||||
val block = world.getBlockState(BlockPos(x, y, z)).block
|
||||
return@put world.blockRegistry.wrapAsHolder(block).asMoLangValue(Registries.BLOCK)
|
||||
}
|
||||
map.put("spawn_explosion") { params ->
|
||||
val x = params.getDouble(0)
|
||||
val y = params.getDouble(1)
|
||||
val z = params.getDouble(2)
|
||||
val range = params.getDouble(3).toFloat()
|
||||
world.explode(null, x, y, z, range, ExplosionInteraction.valueOf(params.getStringOrNull(4)?.uppercase() ?: ExplosionInteraction.TNT.name))
|
||||
}
|
||||
map.put("spawn_lightning") { params ->
|
||||
val x = params.getDouble(0)
|
||||
val y = params.getDouble(1)
|
||||
val z = params.getDouble(2)
|
||||
val lightning = LightningBolt(EntityType.LIGHTNING_BOLT, world)
|
||||
lightning.setPos(x, y, z)
|
||||
world.addFreshEntity(lightning)
|
||||
return@put DoubleValue.ONE
|
||||
}
|
||||
map.put("get_entities_around") { params ->
|
||||
val x = params.getDouble(0)
|
||||
val y = params.getDouble(1)
|
||||
val z = params.getDouble(2)
|
||||
val range = params.getDouble(3) * 2
|
||||
val entities = world.getEntities(null, AABB.ofSize(Vec3(x, y, z), range, range, range))
|
||||
return@put entities
|
||||
.filterIsInstance<LivingEntity>()
|
||||
.map { it.asMostSpecificMoLangValue() }
|
||||
.asArrayValue()
|
||||
}
|
||||
|
||||
return@mutableListOf map
|
||||
}
|
||||
)
|
||||
val biomeFunctions = hashMapOf<String, java.util.function.Function<MoParams, Any>>()
|
||||
val worldFunctions = hashMapOf<String, java.util.function.Function<MoParams, Any>>()
|
||||
val dimensionTypeFunctions = hashMapOf<String, java.util.function.Function<MoParams, Any>>()
|
||||
val blockFunctions = hashMapOf<String, java.util.function.Function<MoParams, Any>>()
|
||||
val dimensionTypeFunctions = mutableListOf<(Holder<DimensionType>) -> HashMap<String, java.util.function.Function<MoParams, Any>>>()
|
||||
val blockFunctions = mutableListOf<(Holder<Block>) -> HashMap<String, java.util.function.Function<MoParams, Any>>>()
|
||||
val playerFunctions = mutableListOf<(Player) -> HashMap<String, java.util.function.Function<MoParams, Any>>>(
|
||||
{ player ->
|
||||
val map = hashMapOf<String, java.util.function.Function<MoParams, Any>>()
|
||||
map.put("username") { _ -> StringValue(player.gameProfile.name) }
|
||||
map.put("uuid") { _ -> StringValue(player.gameProfile.id.toString()) }
|
||||
map.put("data") { _ -> return@put if (player is ServerPlayer) Cobblemon.molangData.load(player.uuid) else DoubleValue(0) }
|
||||
map.put("save_data") { _ -> if (player is ServerPlayer) Cobblemon.molangData.save(player.uuid) else DoubleValue(0) }
|
||||
map.put("main_held_item") { _ -> player.level().itemRegistry.wrapAsHolder(player.mainHandItem.item).asMoLangValue(Registries.ITEM) }
|
||||
map.put("off_held_item") { _ -> player.level().itemRegistry.wrapAsHolder(player.offhandItem.item).asMoLangValue(Registries.ITEM) }
|
||||
map.put("face") { params -> ObjectValue(PlayerDialogueFaceProvider(player.uuid, params.getBooleanOrNull(0) != false)) }
|
||||
map.put("swing_hand") { _ -> player.swing(player.usedItemHand) }
|
||||
map.put("food_level") { _ -> DoubleValue(player.foodData.foodLevel) }
|
||||
map.put("saturation_level") { _ -> DoubleValue(player.foodData.saturationLevel) }
|
||||
// map.put("has_permission") { params -> DoubleValue(Cobblemon.permissionValidator.hasPermission(player, Permission)) }
|
||||
map.put("tell") { params ->
|
||||
val message = params.getString(0).text()
|
||||
val overlay = params.getBooleanOrNull(1) ?: false
|
||||
val overlay = params.getBooleanOrNull(1) == true
|
||||
player.displayClientMessage(message, overlay)
|
||||
}
|
||||
map.put("teleport") { params ->
|
||||
@@ -156,7 +270,12 @@ object MoLangFunctions {
|
||||
environment.query = player.asMoLangValue()
|
||||
environment
|
||||
}
|
||||
map.put("is_player") { DoubleValue.ONE }
|
||||
if (player is ServerPlayer) {
|
||||
map.put("run_command") { params ->
|
||||
val command = params.getString(0)
|
||||
player.server.commands.performPrefixedCommand(player.createCommandSourceStack(), command)
|
||||
}
|
||||
map.put("is_party_at_full_health") { _ ->
|
||||
DoubleValue(player.party().none(Pokemon::canBeHealed)) }
|
||||
map.put("can_heal_at_healer") { params ->
|
||||
@@ -177,11 +296,53 @@ object MoLangFunctions {
|
||||
return@put DoubleValue.ZERO
|
||||
}
|
||||
}
|
||||
map.put("party") { player.party().struct }
|
||||
map.put("pc") { player.pc().struct }
|
||||
map.put("has_permission") { params -> DoubleValue(Cobblemon.permissionValidator.hasPermission(player, params.getString(0), params.getIntOrNull(1) ?: 4)) }
|
||||
map.put("data") { _ -> Cobblemon.molangData.load(player.uuid) }
|
||||
map.put("save_data") { _ -> Cobblemon.molangData.save(player.uuid) }
|
||||
map.put("in_battle") { DoubleValue(player.isInBattle()) }
|
||||
map.put("battle") { player.getBattleState()?.first?.struct ?: DoubleValue.ZERO }
|
||||
map.put("get_npc_data") { params ->
|
||||
val npcId = (params.get<MoValue>(0) as? ObjectValue<NPCEntity>)?.obj?.stringUUID ?: params.getString(0)
|
||||
val data = Cobblemon.molangData.load(player.uuid)
|
||||
if (data.map.containsKey(npcId)) {
|
||||
return@put data.map[npcId]!!
|
||||
} else {
|
||||
val vars = VariableStruct()
|
||||
data.map[npcId] = vars
|
||||
return@put vars
|
||||
}
|
||||
}
|
||||
map.put("get_npc_variable") { params ->
|
||||
val npcId = (params.get<MoValue>(0) as? ObjectValue<NPCEntity>)?.obj?.stringUUID ?: params.getString(0)
|
||||
val variable = params.getString(1)
|
||||
val data = Cobblemon.molangData.load(player.uuid)
|
||||
if (data.map.containsKey(npcId)) {
|
||||
return@put (data.map[npcId] as VariableStruct).map[variable] ?: DoubleValue.ZERO
|
||||
}
|
||||
}
|
||||
map.put("set_npc_variable") { params ->
|
||||
val npcId = (params.get<MoValue>(0) as? ObjectValue<NPCEntity>)?.obj?.stringUUID ?: params.getString(0)
|
||||
val variable = params.getString(1)
|
||||
val value = params.get<MoValue>(2)
|
||||
val saveAfterwards = params.getBooleanOrNull(3) != false
|
||||
val data = Cobblemon.molangData.load(player.uuid)
|
||||
if (data.map.containsKey(npcId)) {
|
||||
(data.map[npcId] as VariableStruct).map[variable] = value
|
||||
if (saveAfterwards) {
|
||||
Cobblemon.molangData.save(player.uuid)
|
||||
}
|
||||
return@put DoubleValue.ONE
|
||||
} else {
|
||||
return@put DoubleValue.ZERO
|
||||
}
|
||||
}
|
||||
}
|
||||
map
|
||||
}
|
||||
)
|
||||
val entityFunctions = mutableListOf<(LivingEntity) -> HashMap<String, java.util.function.Function<MoParams, Any>>>(
|
||||
val entityFunctions: MutableList<(LivingEntity) -> HashMap<String, java.util.function.Function<MoParams, Any>>> = mutableListOf<(LivingEntity) -> HashMap<String, java.util.function.Function<MoParams, Any>>>(
|
||||
{ entity ->
|
||||
val map = hashMapOf<String, java.util.function.Function<MoParams, Any>>()
|
||||
map.put("uuid") { _ -> StringValue(entity.uuid.toString()) }
|
||||
@@ -190,6 +351,28 @@ object MoLangFunctions {
|
||||
val source = DamageSource(entity.level().registryAccess().registryOrThrow(Registries.DAMAGE_TYPE).getHolder(DamageTypes.GENERIC).get())
|
||||
entity.hurt(source, amount.toFloat())
|
||||
}
|
||||
map.put("walk_to") { params ->
|
||||
val x = params.getDouble(0)
|
||||
val y = params.getDouble(1)
|
||||
val z = params.getDouble(2)
|
||||
val speedMultiplier = params.getDoubleOrNull(3) ?: 0.35
|
||||
if (entity is PathfinderMob) {
|
||||
if (entity.brain.checkMemory(MemoryModuleType.WALK_TARGET, MemoryStatus.REGISTERED)) {
|
||||
entity.brain.setMemory(MemoryModuleType.WALK_TARGET, WalkTarget(Vec3(x, y, z), speedMultiplier.toFloat(), 1))
|
||||
entity.brain.setMemory(MemoryModuleType.LOOK_TARGET, BlockPosTracker(Vec3(x, y + entity.eyeHeight, z)))
|
||||
} else {
|
||||
entity.navigation.moveTo(x, y, z, speedMultiplier)
|
||||
entity.lookControl.setLookAt(Vec3(x, y + entity.eyeHeight, z))
|
||||
}
|
||||
}
|
||||
}
|
||||
map.put("has_walk_target") { _ ->
|
||||
if (entity is PathfinderMob) {
|
||||
DoubleValue(entity.brain.getMemory(MemoryModuleType.WALK_TARGET).isPresent || entity.isPathFinding)
|
||||
} else {
|
||||
DoubleValue.ZERO
|
||||
}
|
||||
}
|
||||
map.put("is_sneaking") { _ -> DoubleValue(entity.isShiftKeyDown) }
|
||||
map.put("is_sprinting") { _ -> DoubleValue(entity.isSprinting) }
|
||||
map.put("is_flying") { _ -> DoubleValue(entity.isFallFlying) }
|
||||
@@ -201,8 +384,14 @@ object MoLangFunctions {
|
||||
map.put("is_on_fire") { _ -> DoubleValue(entity.isOnFire) }
|
||||
map.put("is_invisible") { _ -> DoubleValue(entity.isInvisible) }
|
||||
map.put("is_sleeping") { _ -> DoubleValue(entity.isSleeping) }
|
||||
map.put("is_riding") { _ -> DoubleValue(entity.isPassenger()) }
|
||||
map.put("is_riding") { _ -> DoubleValue(entity.isPassenger) }
|
||||
map.put("health") { _ -> DoubleValue(entity.health) }
|
||||
map.put("distance_to_pos") { params ->
|
||||
val x = params.getDouble(0)
|
||||
val y = params.getDouble(1)
|
||||
val z = params.getDouble(2)
|
||||
return@put DoubleValue(sqrt(entity.distanceToSqr(Vec3(x, y, z))))
|
||||
}
|
||||
map.put("max_health") { _ -> DoubleValue(entity.maxHealth) }
|
||||
map.put("name") { _ -> StringValue(entity.effectiveName().string) }
|
||||
map.put("yaw") { _ -> DoubleValue(entity.yRot.toDouble()) }
|
||||
@@ -218,7 +407,7 @@ object MoLangFunctions {
|
||||
map.put("entity_size") { DoubleValue(entity.boundingBox.run { if (xsize > ysize) xsize else ysize }) }
|
||||
map.put("entity_width") { DoubleValue(entity.boundingBox.xsize) }
|
||||
map.put("entity_height") { DoubleValue(entity.boundingBox.ysize) }
|
||||
|
||||
map.put("id_modulo") { params -> DoubleValue(entity.uuid.hashCode() % params.getDouble(0)) }
|
||||
map.put("horizontal_velocity") { _ -> DoubleValue(entity.deltaMovement.horizontalDistance()) }
|
||||
map.put("vertical_velocity") { DoubleValue(entity.deltaMovement.y) }
|
||||
map.put("is_on_ground") { _ -> DoubleValue(entity.onGround()) }
|
||||
@@ -244,6 +433,21 @@ object MoLangFunctions {
|
||||
return@put DoubleValue.ZERO
|
||||
}
|
||||
}
|
||||
map.put("get_nearby_entities") { params ->
|
||||
val distance = params.getDouble(0)
|
||||
val entities = entity.level().getEntities(entity, AABB.ofSize(entity.position(), distance, distance, distance))
|
||||
return@put entities
|
||||
.filterIsInstance<LivingEntity>()
|
||||
.map { it.asMostSpecificMoLangValue() }
|
||||
.asArrayValue()
|
||||
}
|
||||
if (entity is PosableEntity) {
|
||||
map.put("play_animation") { params ->
|
||||
val animation = params.getString(0)
|
||||
val packet = PlayPosableAnimationPacket(entity.id, setOf(animation), emptyList())
|
||||
packet.sendToPlayersAround(entity.x, entity.y, entity.z, 64.0, entity.level().dimension())
|
||||
}
|
||||
}
|
||||
map
|
||||
}
|
||||
)
|
||||
@@ -252,8 +456,21 @@ object MoLangFunctions {
|
||||
val map = hashMapOf<String, java.util.function.Function<MoParams, Any>>()
|
||||
map.put("class") { StringValue(npc.npc.id.toString()) }
|
||||
map.put("name") { StringValue(npc.name.string) }
|
||||
map.put("level") { DoubleValue(npc.level) }
|
||||
map.put("has_aspect") { params -> DoubleValue(npc.aspects.contains(params.getString(0))) }
|
||||
map.put("face") { params -> ObjectValue(ReferenceDialogueFaceProvider(npc.id, params.getBooleanOrNull(0) != false)) }
|
||||
map.put("in_battle") { DoubleValue(npc.isInBattle()) }
|
||||
map.put("battles") { ArrayStruct(npc.battleIds.mapNotNull { BattleRegistry.getBattle(it)?.struct }.mapIndexed { index, value -> "$index" to value }.toMap()) }
|
||||
map.put("stop_battles") { _ -> npc.battleIds.forEach { BattleRegistry.getBattle(it)?.stop() } }
|
||||
map.put("is_doing_activity") { params ->
|
||||
val identifiers = params.params.map { it.asString().asIdentifierDefaultingNamespace(namespace = "minecraft") }
|
||||
val activities = identifiers.mapNotNull { identifier -> BuiltInRegistries.ACTIVITY.get(identifier) }
|
||||
if (activities.isNotEmpty()) {
|
||||
return@put DoubleValue(activities.any { activity -> npc.brain.isActive(activity) })
|
||||
} else {
|
||||
return@put DoubleValue.ZERO
|
||||
}
|
||||
}
|
||||
map.put("run_script_on_client") { params ->
|
||||
val world = npc.level()
|
||||
if (world is ServerLevel) {
|
||||
@@ -282,22 +499,22 @@ object MoLangFunctions {
|
||||
)
|
||||
npc.actionEffect = context
|
||||
npc.brain.setMemory(CobblemonMemories.ACTIVE_ACTION_EFFECT, context)
|
||||
npc.brain.setActiveActivityIfPossible(CobblemonActivities.NPC_ACTION_EFFECT)
|
||||
npc.brain.setActiveActivityIfPossible(CobblemonActivities.ACTION_EFFECT)
|
||||
actionEffect.run(context).thenRun {
|
||||
val npcActionEffect = npc.brain.getMemory(CobblemonMemories.ACTIVE_ACTION_EFFECT).orElse(null)
|
||||
if (npcActionEffect == context && npc.brain.isActive(CobblemonActivities.NPC_ACTION_EFFECT)) {
|
||||
if (npcActionEffect == context && npc.brain.isActive(CobblemonActivities.ACTION_EFFECT)) {
|
||||
npc.brain.eraseMemory(CobblemonMemories.ACTIVE_ACTION_EFFECT)
|
||||
npc.actionEffect = null
|
||||
}
|
||||
}
|
||||
|
||||
return@put DoubleValue(1)
|
||||
return@put DoubleValue.ONE
|
||||
}
|
||||
return@put DoubleValue(0)
|
||||
return@put DoubleValue.ZERO
|
||||
}
|
||||
map.put("put_pokemon_in_healer") { params ->
|
||||
val healer = npc.level().getBlockEntity(params.get<ArrayStruct>(0).asBlockPos(), CobblemonBlockEntities.HEALING_MACHINE).orElse(null) ?: return@put DoubleValue.ZERO
|
||||
val party = npc.staticParty ?: return@put DoubleValue.ZERO
|
||||
val party = npc.party ?: return@put DoubleValue.ZERO
|
||||
if (healer.canHeal(party)) {
|
||||
healer.activate(npc.uuid, party)
|
||||
return@put DoubleValue.ONE
|
||||
@@ -310,6 +527,10 @@ object MoLangFunctions {
|
||||
npc.lookAt(EntityAnchorArgument.Anchor.EYES, pos)
|
||||
}
|
||||
map.put("environment") { _ -> npc.runtime.environment }
|
||||
map.put("party") { npc.party?.struct ?: DoubleValue.ZERO }
|
||||
map.put("has_party") { DoubleValue(npc.party != null) }
|
||||
map.put("is_npc") { DoubleValue.ONE }
|
||||
map.put("can_battle") { DoubleValue(npc.party?.any { it.currentHealth > 0 } == true || npc.npc.party?.isStatic == false) }
|
||||
map
|
||||
}
|
||||
)
|
||||
@@ -328,17 +549,54 @@ object MoLangFunctions {
|
||||
val actor = battle.actors.find { it.uuid == uuid } ?: return@put DoubleValue.ZERO
|
||||
return@put actor.struct
|
||||
}
|
||||
map.put("stop") { _ -> battle.stop() }
|
||||
map.put("actors") { battle.actors.toList().asArrayValue { it.struct } }
|
||||
map
|
||||
}
|
||||
)
|
||||
|
||||
val battleActorFunctions = mutableListOf<(BattleActor) -> HashMap<String, java.util.function.Function<MoParams, Any>>>(
|
||||
{ battleActor ->
|
||||
val map = hashMapOf<String, java.util.function.Function<MoParams, Any>>()
|
||||
map.put("is_npc") { DoubleValue(battleActor.type == ActorType.NPC) }
|
||||
map.put("is_player") { DoubleValue(battleActor.type == ActorType.PLAYER) }
|
||||
map.put("is_wild") { DoubleValue(battleActor.type == ActorType.WILD) }
|
||||
if (battleActor is NPCBattleActor) {
|
||||
map.put("npc") { battleActor.entity.struct }
|
||||
} else if (battleActor is PlayerBattleActor) {
|
||||
map.put("player") { battleActor.entity?.asMoLangValue() ?: DoubleValue.ZERO }
|
||||
}
|
||||
return@mutableListOf map
|
||||
}
|
||||
)
|
||||
|
||||
val pokemonFunctions = mutableListOf<(Pokemon) -> HashMap<String, java.util.function.Function<MoParams, Any>>>(
|
||||
{ pokemon ->
|
||||
val map = hashMapOf<String, java.util.function.Function<MoParams, Any>>()
|
||||
map.put("is_wild") { DoubleValue(pokemon.isWild()) }
|
||||
map.put("id") { StringValue(pokemon.uuid.toString()) }
|
||||
map.put("level") { DoubleValue(pokemon.level.toDouble()) }
|
||||
map.put("max_hp") { DoubleValue(pokemon.maxHealth.toDouble()) }
|
||||
map.put("current_hp") { DoubleValue(pokemon.currentHealth.toDouble()) }
|
||||
map.put("friendship") { DoubleValue(pokemon.friendship.toDouble()) }
|
||||
map.put("evs") {
|
||||
val struct = QueryStruct(hashMapOf())
|
||||
for (stat in Stats.PERMANENT) {
|
||||
struct.addFunction(stat.showdownId) { DoubleValue(pokemon.evs.getOrDefault(stat).toDouble()) }
|
||||
}
|
||||
struct
|
||||
}
|
||||
map.put("ivs") {
|
||||
val struct = QueryStruct(hashMapOf())
|
||||
for (stat in Stats.PERMANENT) {
|
||||
struct.addFunction(stat.showdownId) { DoubleValue(pokemon.ivs.getOrDefault(stat).toDouble()) }
|
||||
}
|
||||
struct
|
||||
}
|
||||
map.put("is_wild") { DoubleValue(pokemon.entity?.let { it.ownerUUID == null } == true) }
|
||||
map.put("is_shiny") { DoubleValue(pokemon.shiny) }
|
||||
map.put("form") { StringValue(pokemon.form.name) }
|
||||
map.put("weight") { DoubleValue(pokemon.species.weight.toDouble()) }
|
||||
map.put("matches") { params -> DoubleValue(params.getString(0).toProperties().matches(pokemon)) }
|
||||
map
|
||||
}
|
||||
)
|
||||
@@ -350,6 +608,83 @@ object MoLangFunctions {
|
||||
map.put("is_moving") { DoubleValue((pokemonEntity.moveControl as? PokemonMoveControl)?.hasWanted() == true) }
|
||||
map.put("is_flying") { DoubleValue(pokemonEntity.getBehaviourFlag(PokemonBehaviourFlag.FLYING)) }
|
||||
map.put("has_aspect") { DoubleValue(it.getString(0) in pokemonEntity.aspects) }
|
||||
map.put("is_pokemon") { DoubleValue.ONE }
|
||||
map
|
||||
}
|
||||
)
|
||||
|
||||
val pokemonStoreFunctions = mutableListOf<(PokemonStore<*>) -> HashMap<String, java.util.function.Function<MoParams, Any>>>(
|
||||
{ store ->
|
||||
val map = hashMapOf<String, java.util.function.Function<MoParams, Any>>()
|
||||
map.put("add") { params ->
|
||||
val pokemon = params.get<ObjectValue<Pokemon>>(0)
|
||||
return@put DoubleValue(store.add(pokemon.obj))
|
||||
}
|
||||
map.put("add_by_properties") { params ->
|
||||
val props = params.getString(0).toProperties()
|
||||
val player = (store as? PlayerPartyStore)?.playerUUID?.getPlayer() // Only really know for sure when it's a player party store
|
||||
val pokemon = props.create(player = player)
|
||||
return@put DoubleValue(store.add(pokemon))
|
||||
}
|
||||
map.put("find_by_properties") { params ->
|
||||
val props = params.getString(0).toProperties()
|
||||
val pokemon = store.find { props.matches(it) }
|
||||
return@put pokemon?.asStruct() ?: DoubleValue.ZERO
|
||||
}
|
||||
map.put("find_all_by_properties") { params ->
|
||||
val props = params.getString(0).toProperties()
|
||||
val pokemon = store.filter { props.matches(it) }
|
||||
return@put ArrayStruct(pokemon.mapIndexed { index, value -> "$index" to value.asStruct() }.toMap())
|
||||
}
|
||||
map.put("find_by_id") { params ->
|
||||
val id = params.getString(0).asUUID
|
||||
val pokemon = store.find { it.uuid == id }
|
||||
return@put pokemon?.asStruct() ?: DoubleValue.ZERO
|
||||
}
|
||||
map.put("remove_by_id") { params ->
|
||||
val id = params.getString(0).asUUID
|
||||
val pokemon = store.find { it.uuid == id } ?: return@put DoubleValue.ZERO
|
||||
return@put DoubleValue(store.remove(pokemon))
|
||||
}
|
||||
map.put("average_level") { _ ->
|
||||
var numberOfPokemon = 0
|
||||
var totalLevel = 0
|
||||
for (pokemon in store) {
|
||||
totalLevel += pokemon.level
|
||||
numberOfPokemon++
|
||||
}
|
||||
if (numberOfPokemon == 0) {
|
||||
return@put DoubleValue.ZERO
|
||||
}
|
||||
return@put DoubleValue(totalLevel.toDouble() / numberOfPokemon)
|
||||
}
|
||||
map.put("count") { _ -> DoubleValue(store.count()) }
|
||||
map.put("count_by_properties") { params ->
|
||||
val props = params.getString(0).toProperties()
|
||||
return@put DoubleValue(store.count { props.matches(it) })
|
||||
}
|
||||
map.put("highest_level") {
|
||||
val highest = store.maxOfOrNull { it.level } ?: 0
|
||||
return@put DoubleValue(highest)
|
||||
}
|
||||
map.put("lowest_level") {
|
||||
val lowest = store.minOfOrNull { it.level } ?: 0
|
||||
return@put DoubleValue(lowest)
|
||||
}
|
||||
map.put("heal") {
|
||||
for (pokemon in store) {
|
||||
pokemon.heal()
|
||||
}
|
||||
return@put DoubleValue.ONE
|
||||
}
|
||||
map.put("healing_remainder_percent") { _ ->
|
||||
var totalPercent = 0.0f
|
||||
for (pokemon in store) {
|
||||
totalPercent += (1.0f - (pokemon.currentHealth.toFloat() / pokemon.maxHealth))
|
||||
}
|
||||
DoubleValue(totalPercent)
|
||||
}
|
||||
map.put("has_usable_pokemon") { _ -> DoubleValue(store.any { !it.isFainted() }) }
|
||||
map
|
||||
}
|
||||
)
|
||||
@@ -360,19 +695,120 @@ object MoLangFunctions {
|
||||
map.put("get_pokemon") { params ->
|
||||
val index = params.getInt(0)
|
||||
val pokemon = party.get(index) ?: return@put DoubleValue.ZERO
|
||||
val struct = VariableStruct()
|
||||
pokemon.writeVariables(struct)
|
||||
return@put struct
|
||||
return@put pokemon.struct
|
||||
}
|
||||
map.put("healing_remainder_percent") { _ -> DoubleValue(party.getHealingRemainderPercent()) }
|
||||
|
||||
return@mutableListOf map
|
||||
})
|
||||
|
||||
fun Holder<Biome>.asBiomeMoLangValue() = asMoLangValue(Registries.BIOME).addFunctions(biomeFunctions)
|
||||
fun Holder<Level>.asWorldMoLangValue() = asMoLangValue(Registries.DIMENSION).addFunctions(worldFunctions)
|
||||
fun Holder<Block>.asBlockMoLangValue() = asMoLangValue(Registries.BLOCK).addFunctions(blockFunctions)
|
||||
fun Holder<DimensionType>.asDimensionTypeMoLangValue() = asMoLangValue(Registries.DIMENSION_TYPE).addFunctions(dimensionTypeFunctions)
|
||||
val pcFunctions = mutableListOf<(PCStore) -> HashMap<String, java.util.function.Function<MoParams, Any>>>(
|
||||
{ pc ->
|
||||
val map = hashMapOf<String, java.util.function.Function<MoParams, Any>>()
|
||||
map.put("get_pokemon") { params ->
|
||||
val box = params.getInt(0)
|
||||
val slot = params.getInt(1)
|
||||
val pokemon = pc[PCPosition(box, slot)] ?: return@put DoubleValue.ZERO
|
||||
return@put pokemon.struct
|
||||
}
|
||||
map.put("resize") { params ->
|
||||
val newSize = params.getInt(0)
|
||||
val lockNewSize = params.getBooleanOrNull(1) == true
|
||||
pc.resize(newSize, lockNewSize)
|
||||
return@put DoubleValue.ONE
|
||||
}
|
||||
map.put("get_box_count") { _ -> DoubleValue(pc.boxes.size.toDouble()) }
|
||||
return@mutableListOf map
|
||||
}
|
||||
)
|
||||
|
||||
val spawningContextFunctions = mutableListOf<(SpawningContext) -> HashMap<String, java.util.function.Function<MoParams, Any>>>(
|
||||
{ spawningContext ->
|
||||
val map = hashMapOf<String, java.util.function.Function<MoParams, Any>>()
|
||||
val worldValue = spawningContext.world.registryAccess().registryOrThrow(Registries.DIMENSION).wrapAsHolder(spawningContext.world).asWorldMoLangValue()
|
||||
val biomeValue = spawningContext.biomeRegistry.wrapAsHolder(spawningContext.biome).asBiomeMoLangValue()
|
||||
map.put("biome") { _ -> biomeValue }
|
||||
map.put("world") { _ -> worldValue }
|
||||
map.put("light") { _ -> DoubleValue(spawningContext.light.toDouble()) }
|
||||
map.put("x") { _ -> DoubleValue(spawningContext.position.x.toDouble()) }
|
||||
map.put("y") { _ -> DoubleValue(spawningContext.position.y.toDouble()) }
|
||||
map.put("z") { _ -> DoubleValue(spawningContext.position.z.toDouble()) }
|
||||
map.put("moon_phase") { _ -> DoubleValue(spawningContext.moonPhase.toDouble()) }
|
||||
map.put("can_see_sky") { _ -> DoubleValue(spawningContext.canSeeSky) }
|
||||
map.put("sky_light") { _ -> DoubleValue(spawningContext.skyLight.toDouble()) }
|
||||
map.put("bucket") { _ -> StringValue(spawningContext.cause.bucket.name) }
|
||||
map.put("player") { _ ->
|
||||
val causeEntity = spawningContext.cause.entity ?: return@put DoubleValue.ZERO
|
||||
if (causeEntity is ServerPlayer) {
|
||||
return@put causeEntity.asMoLangValue()
|
||||
} else {
|
||||
return@put DoubleValue.ZERO
|
||||
}
|
||||
}
|
||||
map
|
||||
}
|
||||
)
|
||||
|
||||
val serverFunctions: MutableList<(MinecraftServer) -> HashMap<String, java.util.function.Function<MoParams, Any>>> = mutableListOf(
|
||||
{ server ->
|
||||
val map = hashMapOf<String, java.util.function.Function<MoParams, Any>>()
|
||||
|
||||
map.put("get_world") { params ->
|
||||
val world = server.getLevel(
|
||||
ResourceKey.create(
|
||||
Registries.DIMENSION,
|
||||
params.getString(0).asIdentifierDefaultingNamespace(namespace = "minecraft")
|
||||
)
|
||||
) ?: return@put DoubleValue.ZERO
|
||||
|
||||
return@put world
|
||||
.registryAccess()
|
||||
.registryOrThrow(Registries.DIMENSION)
|
||||
.wrapAsHolder(world)
|
||||
.asWorldMoLangValue()
|
||||
}
|
||||
|
||||
map.put("broadcast") { params ->
|
||||
val message = params.getString(0)
|
||||
server.playerList.broadcastSystemMessage(message.text(), params.getBooleanOrNull(1) == true)
|
||||
return@put DoubleValue.ONE
|
||||
}
|
||||
|
||||
map.put("get_player_by_uuid") { params ->
|
||||
val uuid = UUID.fromString(params.getString(0))
|
||||
val player = server.playerList.getPlayer(uuid) ?: return@put DoubleValue.ZERO
|
||||
return@put player.asMoLangValue()
|
||||
}
|
||||
|
||||
map.put("get_player_by_username") { params ->
|
||||
val username = params.getString(0)
|
||||
val player = server.playerList.getPlayerByName(username) ?: return@put DoubleValue.ZERO
|
||||
return@put player.asMoLangValue()
|
||||
}
|
||||
|
||||
map.put("data") { params ->
|
||||
val data = Cobblemon.molangData.load(UUID(0L, 0L))
|
||||
return@put data
|
||||
}
|
||||
|
||||
map.put("save_data") {
|
||||
Cobblemon.molangData.save(UUID(0L, 0L))
|
||||
return@put DoubleValue.ONE
|
||||
}
|
||||
|
||||
// Maybe later...
|
||||
// map.put("stop") { params ->
|
||||
// Cobblemon.LOGGER.warn("Server is being stopped from a MoLang script.")
|
||||
// server.stopServer()
|
||||
// }
|
||||
|
||||
return@mutableListOf map
|
||||
}
|
||||
)
|
||||
|
||||
fun Holder<Biome>.asBiomeMoLangValue() = asMoLangValue(Registries.BIOME).addFunctions(biomeFunctions.flatMap { it(this).entries.map { it.key to it.value } }.toMap())
|
||||
fun Holder<Level>.asWorldMoLangValue() = asMoLangValue(Registries.DIMENSION).addFunctions(worldFunctions.flatMap { it(this).entries.map { it.key to it.value } }.toMap())
|
||||
fun Holder<Block>.asBlockMoLangValue() = asMoLangValue(Registries.BLOCK).addFunctions(blockFunctions.flatMap { it(this).entries.map { it.key to it.value } }.toMap())
|
||||
fun MinecraftServer.asMoLangValue() = ObjectValue(this).addStandardFunctions().addFunctions(serverFunctions.flatMap { it(this).entries.map { it.key to it.value } }.toMap())
|
||||
fun Holder<DimensionType>.asDimensionTypeMoLangValue() = asMoLangValue(Registries.DIMENSION_TYPE).addFunctions(dimensionTypeFunctions.flatMap { it(this).entries.map { it.key to it.value } }.toMap())
|
||||
fun Player.asMoLangValue(): ObjectValue<Player> {
|
||||
val value = ObjectValue(
|
||||
obj = this,
|
||||
@@ -383,6 +819,33 @@ object MoLangFunctions {
|
||||
return value
|
||||
}
|
||||
|
||||
// We need to migrate the writeVariables thing to be all about query structs, variable doesn't make sense and I don't want to break fringe 1.6 compatibility issues close to release
|
||||
fun Pokemon.asStruct(): ObjectValue<Pokemon> {
|
||||
val queryStruct = ObjectValue(this)
|
||||
queryStruct.addFunctions(pokemonFunctions.flatMap { it(this).entries.map { it.key to it.value } }.toMap())
|
||||
return queryStruct
|
||||
}
|
||||
|
||||
fun PartyStore.asMoLangValue(): ObjectValue<PartyStore> {
|
||||
val value = ObjectValue(
|
||||
obj = this,
|
||||
stringify = { it.toString() }
|
||||
)
|
||||
value.addFunctions(pokemonStoreFunctions.flatMap { it(this).entries.map { it.key to it.value } }.toMap())
|
||||
value.addFunctions(partyFunctions.flatMap { it(this).entries.map { it.key to it.value } }.toMap())
|
||||
return value
|
||||
}
|
||||
|
||||
fun PCStore.asMoLangValue(): ObjectValue<PCStore> {
|
||||
val value = ObjectValue(
|
||||
obj = this,
|
||||
stringify = { it.toString() }
|
||||
)
|
||||
value.addFunctions(pokemonStoreFunctions.flatMap { it(this).entries.map { it.key to it.value } }.toMap())
|
||||
value.addFunctions(pcFunctions.flatMap { it(this).entries.map { it.key to it.value } }.toMap())
|
||||
return value
|
||||
}
|
||||
|
||||
fun NPCEntity.asMoLangValue(): ObjectValue<NPCEntity> {
|
||||
val value = ObjectValue(
|
||||
obj = this,
|
||||
@@ -393,6 +856,17 @@ object MoLangFunctions {
|
||||
return value
|
||||
}
|
||||
|
||||
fun PokemonEntity.asMoLangValue(): ObjectValue<PokemonEntity> {
|
||||
val value = ObjectValue(
|
||||
obj = this,
|
||||
stringify = { it.pokemon.uuid.toString() }
|
||||
)
|
||||
value.addFunctions(entityFunctions.flatMap { it(this).entries.map { it.key to it.value } }.toMap())
|
||||
value.addFunctions(pokemonFunctions.flatMap { it(this.pokemon).entries.map { it.key to it.value } }.toMap()) // Convenience
|
||||
value.addFunctions(pokemonEntityFunctions.flatMap { it(this).entries.map { it.key to it.value } }.toMap())
|
||||
return value
|
||||
}
|
||||
|
||||
fun PokemonBattle.asMoLangValue(): ObjectValue<PokemonBattle> {
|
||||
val value = ObjectValue(
|
||||
obj = this,
|
||||
@@ -402,6 +876,36 @@ object MoLangFunctions {
|
||||
return value
|
||||
}
|
||||
|
||||
fun BattleActor.asMoLangValue(): ObjectValue<BattleActor> {
|
||||
val value = ObjectValue(
|
||||
obj = this,
|
||||
stringify = { it.toString() }
|
||||
)
|
||||
value.addFunctions(battleActorFunctions.flatMap { it(this).entries.map { it.key to it.value } }.toMap())
|
||||
return value
|
||||
}
|
||||
|
||||
fun SpawningContext.asMoLangValue(): ObjectValue<SpawningContext> {
|
||||
val value = ObjectValue(
|
||||
obj = this,
|
||||
stringify = { it.toString() }
|
||||
)
|
||||
value.addFunctions(spawningContextFunctions.flatMap { it(this).entries.map { it.key to it.value } }.toMap())
|
||||
return value
|
||||
}
|
||||
|
||||
/**
|
||||
* Different functions exist depending on
|
||||
*/
|
||||
fun LivingEntity.asMostSpecificMoLangValue(): ObjectValue<out LivingEntity> {
|
||||
return when (this) {
|
||||
is Player -> asMoLangValue()
|
||||
is PokemonEntity -> struct
|
||||
is NPCEntity -> struct
|
||||
else -> ObjectValue(this).also { it.addStandardFunctions().addFunctions(entityFunctions.flatMap { it(this).entries.map { it.key to it.value } }.toMap()) }
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> Holder<T>.asMoLangValue(key: ResourceKey<Registry<T>>): ObjectValue<Holder<T>> {
|
||||
val value = ObjectValue(
|
||||
obj = this,
|
||||
|
||||
+5
-2
@@ -11,12 +11,14 @@ package com.cobblemon.mod.common.api.moves.animations.keyframes
|
||||
import com.cobblemon.mod.common.api.molang.ExpressionLike
|
||||
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
|
||||
import com.cobblemon.mod.common.api.moves.animations.EntityProvider
|
||||
import com.cobblemon.mod.common.api.moves.animations.TargetsProvider
|
||||
import com.cobblemon.mod.common.api.moves.animations.UsersProvider
|
||||
import com.cobblemon.mod.common.api.scheduling.delayedFuture
|
||||
import com.cobblemon.mod.common.entity.PosableEntity
|
||||
import com.cobblemon.mod.common.net.messages.client.effect.SpawnSnowstormEntityParticlePacket
|
||||
import com.cobblemon.mod.common.util.asExpressionLike
|
||||
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
|
||||
import net.minecraft.commands.arguments.EntityArgument.getPlayers
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import net.minecraft.server.level.ServerLevel
|
||||
|
||||
@@ -29,7 +31,8 @@ import net.minecraft.server.level.ServerLevel
|
||||
class EntityParticlesActionEffectKeyframe : ConditionalActionEffectKeyframe(), EntityConditionalActionEffectKeyframe {
|
||||
override val entityCondition = "q.entity.is_user".asExpressionLike()
|
||||
var effect: String? = null
|
||||
var locator: String = "root"
|
||||
var locators: List<String> = listOf("target")
|
||||
|
||||
val delay: ExpressionLike = "0".asExpressionLike()
|
||||
val visibilityRange = 200
|
||||
|
||||
@@ -45,7 +48,7 @@ class EntityParticlesActionEffectKeyframe : ConditionalActionEffectKeyframe(), E
|
||||
}?.asIdentifierDefaultingNamespace() ?: return skip()
|
||||
|
||||
entities.filter { it is PosableEntity }.forEach { entity ->
|
||||
val packet = SpawnSnowstormEntityParticlePacket(effectIdentifier, entity.id, listOf(locator))
|
||||
val packet = SpawnSnowstormEntityParticlePacket(effectIdentifier, entity.id, (locators).toList())
|
||||
val players = (entity.level() as ServerLevel).getPlayers { it.distanceTo(entity) <= visibilityRange }
|
||||
packet.sendToPlayers(players)
|
||||
}
|
||||
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.net.serializers
|
||||
|
||||
import com.cobblemon.mod.common.entity.PlatformType
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
import net.minecraft.network.codec.StreamCodec
|
||||
import net.minecraft.network.syncher.EntityDataSerializer
|
||||
|
||||
object PlatformTypeDataSerializer : EntityDataSerializer<PlatformType> {
|
||||
// TODO: This ID only looks it's used by NeoForge and that hasn't been tested yet
|
||||
val ID = cobblemonResource("platform_type")
|
||||
fun read(buf: RegistryFriendlyByteBuf) = PlatformType.entries[buf.readInt()]
|
||||
override fun copy(value: PlatformType) = value
|
||||
fun write(buf: RegistryFriendlyByteBuf, value: PlatformType) {
|
||||
buf.writeInt(value.ordinal)
|
||||
}
|
||||
|
||||
override fun codec() = StreamCodec.of(::write, ::read)
|
||||
}
|
||||
@@ -47,8 +47,13 @@ class NPCClass {
|
||||
var variables = mutableMapOf<String, MoValue>() // Questionable whether this should be here.
|
||||
var party: NPCPartyProvider? = null
|
||||
var skill: Int = 0
|
||||
var autoHealParty: Boolean = true
|
||||
var battleTheme: ResourceLocation? = null
|
||||
var ai: MutableList<BrainConfig> = mutableListOf()
|
||||
var isMovable: Boolean = true
|
||||
var isInvulnerable = false
|
||||
var isLeashable = true
|
||||
var allowProjectileHits = true
|
||||
|
||||
// If you're adding stuff here, add it to NPCPreset and NPCClassAdapter too
|
||||
|
||||
@@ -77,11 +82,16 @@ class NPCClass {
|
||||
buffer.writeString(v.defaultValue)
|
||||
}
|
||||
buffer.writeInt(skill)
|
||||
buffer.writeBoolean(autoHealParty)
|
||||
buffer.writeMapK(size = IntSize.U_BYTE, map = variables) { (key, value) ->
|
||||
buffer.writeString(key)
|
||||
buffer.writeString(value.asString())
|
||||
}
|
||||
buffer.writeNullable(battleTheme) { _, v -> buffer.writeIdentifier(v) }
|
||||
buffer.writeBoolean(isMovable)
|
||||
buffer.writeBoolean(isInvulnerable)
|
||||
buffer.writeBoolean(isLeashable)
|
||||
buffer.writeBoolean(allowProjectileHits)
|
||||
}
|
||||
|
||||
fun decode(buffer: RegistryFriendlyByteBuf) {
|
||||
@@ -116,6 +126,7 @@ class NPCClass {
|
||||
NPCConfigVariable(variableName, displayName, description, type, defaultValue)
|
||||
}.toMutableList()
|
||||
skill = buffer.readInt()
|
||||
autoHealParty = buffer.readBoolean()
|
||||
buffer.readMapK(size = IntSize.U_BYTE, map = variables) {
|
||||
val key = buffer.readString()
|
||||
val value = buffer.readString()
|
||||
@@ -126,5 +137,9 @@ class NPCClass {
|
||||
}
|
||||
}
|
||||
battleTheme = buffer.readNullable { buffer.readIdentifier() }
|
||||
isMovable = buffer.readBoolean()
|
||||
isInvulnerable = buffer.readBoolean()
|
||||
isLeashable = buffer.readBoolean()
|
||||
allowProjectileHits = buffer.readBoolean()
|
||||
}
|
||||
}
|
||||
@@ -70,7 +70,7 @@ object NPCClasses : JsonDataRegistry<NPCClass> {
|
||||
.registerTypeAdapter(MoValue::class.java, MoValueAdapter)
|
||||
.registerTypeAdapter(NPCClass::class.java, NPCClassAdapter)
|
||||
.registerTypeAdapter(Activity::class.java, ActivityAdapter)
|
||||
.registerTypeAdapter(Component::class.java, TextAdapter)
|
||||
.registerTypeAdapter(Component::class.java, TranslatedTextAdapter)
|
||||
.registerTypeAdapter(WeightedAspect::class.java, WeightedAspectAdapter)
|
||||
.registerTypeAdapter(BrainConfig::class.java, BrainConfigAdapter)
|
||||
.registerTypeAdapter(TaskConfig::class.java, TaskConfigAdapter)
|
||||
|
||||
@@ -8,14 +8,12 @@
|
||||
|
||||
package com.cobblemon.mod.common.api.npc
|
||||
|
||||
import com.cobblemon.mod.common.api.npc.partyproviders.DynamicPartyProvider
|
||||
import com.cobblemon.mod.common.api.npc.partyproviders.NPCParty
|
||||
import com.cobblemon.mod.common.api.npc.partyproviders.PoolPartyProvider
|
||||
import com.cobblemon.mod.common.api.npc.partyproviders.ScriptPartyProvider
|
||||
import com.cobblemon.mod.common.api.npc.partyproviders.SimplePartyProvider
|
||||
import com.cobblemon.mod.common.api.storage.party.PartyStore
|
||||
import com.cobblemon.mod.common.api.storage.party.NPCPartyStore
|
||||
import com.cobblemon.mod.common.entity.npc.NPCEntity
|
||||
import com.google.gson.JsonElement
|
||||
import net.minecraft.nbt.CompoundTag
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
|
||||
/**
|
||||
@@ -29,11 +27,14 @@ interface NPCPartyProvider {
|
||||
companion object {
|
||||
val types = mutableMapOf<String, (String) -> NPCPartyProvider>(
|
||||
SimplePartyProvider.TYPE to { SimplePartyProvider() },
|
||||
DynamicPartyProvider.TYPE to { DynamicPartyProvider() }
|
||||
PoolPartyProvider.TYPE to { PoolPartyProvider() },
|
||||
ScriptPartyProvider.TYPE to { ScriptPartyProvider() }
|
||||
)
|
||||
}
|
||||
|
||||
val type: String
|
||||
fun provide(npc: NPCEntity, level: Int): NPCParty
|
||||
val isStatic: Boolean
|
||||
fun provide(npc: NPCEntity, level: Int, players: List<ServerPlayer> = emptyList()): NPCPartyStore
|
||||
// Why did I opt for manual JSON loading??? I must have had a reason but I can't remember. Maybe for S2C? Use a codec doofus
|
||||
fun loadFromJSON(json: JsonElement)
|
||||
}
|
||||
@@ -29,7 +29,12 @@ class NPCPreset {
|
||||
var hitbox: EntityDimensions? = null
|
||||
var ai: List<BrainConfig>? = null
|
||||
var skill: Int? = null
|
||||
var autoHealParty: Boolean? = null
|
||||
var battleTheme: ResourceLocation? = null
|
||||
var isMovable: Boolean? = null
|
||||
var isInvulnerable: Boolean? = null
|
||||
var isLeashable: Boolean? = null
|
||||
var allowProjectileHits: Boolean? = null
|
||||
|
||||
fun applyTo(npcClass: NPCClass) {
|
||||
resourceIdentifier?.let { npcClass.resourceIdentifier = it }
|
||||
@@ -47,7 +52,12 @@ class NPCPreset {
|
||||
names?.let { npcClass.names.addAll(it) }
|
||||
hitbox?.let { npcClass.hitbox = it }
|
||||
skill?.let { npcClass.skill = it }
|
||||
autoHealParty?.let { npcClass.autoHealParty = it }
|
||||
battleTheme?.let { npcClass.battleTheme = it }
|
||||
ai?.let { npcClass.ai.addAll(it) }
|
||||
isMovable?.let { npcClass.isMovable = it }
|
||||
isInvulnerable?.let { npcClass.isInvulnerable = it }
|
||||
isLeashable?.let { npcClass.isLeashable = it }
|
||||
allowProjectileHits?.let { npcClass.allowProjectileHits = it }
|
||||
}
|
||||
}
|
||||
@@ -67,7 +67,7 @@ object NPCPresets : JsonDataRegistry<NPCPreset> {
|
||||
.registerTypeAdapter(NPCVariationProvider::class.java, NPCVariationProviderAdapter)
|
||||
.registerTypeAdapter(Activity::class.java, ActivityAdapter)
|
||||
.registerTypeAdapter(MoValue::class.java, MoValueAdapter)
|
||||
.registerTypeAdapter(Component::class.java, TextAdapter)
|
||||
.registerTypeAdapter(Component::class.java, TranslatedTextAdapter)
|
||||
.registerTypeAdapter(BrainConfig::class.java, BrainConfigAdapter)
|
||||
.registerTypeAdapter(TaskConfig::class.java, TaskConfigAdapter)
|
||||
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Biome::class.java).type, BiomeLikeConditionAdapter)
|
||||
|
||||
@@ -57,14 +57,15 @@ any number of presets and the properties of those presets will be merged into th
|
||||
"variableName": "challenge_distance",
|
||||
"displayName": "cobblemon.npc.challenge_distance",
|
||||
"description": "cobblemon.npc.challenge_distance.description",
|
||||
"type": "number",
|
||||
"default": "5"
|
||||
"type": "NUMBER",
|
||||
"defaultValue": "5"
|
||||
}
|
||||
],
|
||||
"names": [
|
||||
"Vera",
|
||||
"Hiro",
|
||||
"John Cobblemon"
|
||||
"Red",
|
||||
"Green",
|
||||
"Blue",
|
||||
"Yellow"
|
||||
],
|
||||
"interaction": {
|
||||
"type": "dialogue",
|
||||
@@ -75,6 +76,7 @@ any number of presets and the properties of those presets will be merged into th
|
||||
},
|
||||
"canDespawn": false,
|
||||
"skill": 5,
|
||||
"autoHealParty": true,
|
||||
"party": {
|
||||
"type": "simple",
|
||||
"pokemon": [
|
||||
@@ -176,19 +178,19 @@ An example situation of using this is if there is an NPC class preset that defin
|
||||
challenging a player when they come within a certain range. Inside that class preset, the AI script may
|
||||
need to know at what distance the player will be engaged. This can be defined in the NPC class preset as
|
||||
a config variable, and therefore any NPC class that relies on this preset will have that config variable
|
||||
and present it to users when they setup an NPC of that class.
|
||||
and present it to users when they set up an NPC of that class.
|
||||
|
||||
"config": [
|
||||
{
|
||||
"variableName": "challenge_distance",
|
||||
"displayName": "cobblemon.npc.challenge_distance",
|
||||
"description": "cobblemon.npc.challenge_distance.description",
|
||||
"type": "number",
|
||||
"default": "5"
|
||||
"displayName": "cobblemon.npc.challenge_distance.name",
|
||||
"description": "cobblemon.npc.challenge_distance.desc",
|
||||
"type": "NUMBER",
|
||||
"defaultValue": "5"
|
||||
}
|
||||
]
|
||||
|
||||
The "type" of variable is one of: `string`, `number`, or `boolean`. The type is used to preset a more
|
||||
The "type" of variable is one of: `STRING`, `NUMBER`, or `BOOLEAN`. The type is used to preset a more
|
||||
appropriate input field for the user when they are setting up the NPC.
|
||||
|
||||
### names
|
||||
@@ -248,22 +250,24 @@ spawning system, it will use a seed level that is based on the player's party's
|
||||
spawned using the NPC command, the seed level can be defined in the command and is otherwise set to 1.
|
||||
|
||||
A party provider produces either 'static' or 'dynamic' parties. A static party is one that is generated
|
||||
when the NPC is initialized and will not change. A dynamic party is one that may be configured using the
|
||||
when the NPC is initialized then does not change. A dynamic party is one that may be configured using the
|
||||
seed level but can be different any time the NPC is challenged.
|
||||
|
||||
A dynamic party prevents the NPC from doing several actions, such as using healing machines, having
|
||||
persistent health of their party that changes between battles, and keeping their Pokémon out of their
|
||||
balls. This is because the NPC's party is not stored in the world.
|
||||
balls. This is because a NPC's dynamic party is not stored in the world and is generated every time
|
||||
they are challenged.
|
||||
|
||||
#### simple
|
||||
The "simple" party type is a way to define a party of Pokémon in a simple list format. Each string in the
|
||||
list should be a set of Pokémon properties, as used in commands and spawn files.
|
||||
|
||||
This party provider type is considered 'static'. Any level that is not mentioned in the Pokémon properties
|
||||
will be set to the seed level.
|
||||
This party provider type can be static or dynamic depending on the value of `isStatic`. Any level that
|
||||
is not mentioned in the Pokémon properties will be set to the seed level.
|
||||
|
||||
"party": {
|
||||
"type": "simple",
|
||||
"isStatic": true,
|
||||
"pokemon": [
|
||||
"spiritomb moves=splash,firepunch,flamethrower,swift",
|
||||
"porygonz speed_ev=252",
|
||||
@@ -274,5 +278,123 @@ will be set to the seed level.
|
||||
]
|
||||
}
|
||||
|
||||
#### dynamic
|
||||
To be implemented
|
||||
#### pool
|
||||
The "pool" party type is a way to define a party of Pokémon using a more complicated format that involves some
|
||||
randomization and weighting to different Pokémon options.
|
||||
|
||||
A pool party provider can be static or dynamic depending on the value of `isStatic`. It specifies the minimum
|
||||
and maximum number of Pokémon that can be in the party using `minPokemon` and `maxPokemon`. The `pool` property dictates all of the possible Pokémon, with
|
||||
each being an object with several properties.
|
||||
|
||||
- The `pokemon` property is the Pokémon properties as used in commands and spawn files that will generate the Pokémon.
|
||||
- The `weight` property is a decimal number that represents how likely that Pokémon is to be chosen. If the weight is -1, it will be guaranteed to be selected. Defaults to 1. The larger the value, the more likely it will be chosen.
|
||||
- The `selectableTimes` property is the maximum number of times that the entry can be selected in the party. Defaults to 1.
|
||||
- The `npcLevels` property is a range of seed levels that the Pokémon can be generated at. For example, if the NPC is spawned with seed level 10, and the range is 5-9, the Pokémon will not be selected. The actual level of the Pokémon will be the seed level. Defaults to "1-100".
|
||||
- The `levelVariation` property is the amount of variation the Pokémon's level can have from the NPC seed level. For example, a value of 2 will mean that for an NPC generated with seed level 10, the Pokémon will be generated somewhere between level 8 and 12 inclusive. Defaults to zero.
|
||||
|
||||
For `minPokemon`, `maxPokemon`, `weight`, `selectableTimes`, and `levelVariation`, the values can be a simple number or be full MoLang expressions. At the time
|
||||
of seeding an NPC, the cosmetic variations have already been applied. Therefore, it's possible to make these properties
|
||||
depend on visual aspects of the NPC. For example, if the NPC has a variation for "dirt", you could set a
|
||||
certain Pokémon to be `"weight": "q.npc.has_aspect('dirty') ? -1 : 0"` which would make it a guaranteed party Pokémon for "dirty" NPCs
|
||||
and impossible for all other variations of the NPC.
|
||||
|
||||
The MoLang environment used for the pool party provider contains q.level for the seed level, q.npc for the NPC, as well as q.players for
|
||||
an array of all players that are challenging the NPC. The players list is only present for dynamic parties. If it is specifically
|
||||
a single battle challenge and this is a dynamic party, there will also be a `q.player`, for convenience.
|
||||
|
||||
"party": {
|
||||
"type": "pool",
|
||||
"minPokemon": "3",
|
||||
"maxPokemon": "6",
|
||||
"isStatic": true,
|
||||
"pool": [
|
||||
{
|
||||
"pokemon": "weedle",
|
||||
"weight": "10",
|
||||
"selectableTimes": "2",
|
||||
"levelVariation": "2"
|
||||
"npcLevels": "1-8"
|
||||
},
|
||||
{
|
||||
"pokemon": "caterpie",
|
||||
"weight": "5",
|
||||
"npcLevels": "1-8",
|
||||
"selectableTimes": "2"
|
||||
},
|
||||
{
|
||||
"pokemon": "kakuna",
|
||||
"weight": "7",
|
||||
"selectableTimes": "2",
|
||||
"npcLevels": "8-10"
|
||||
},
|
||||
{
|
||||
"pokemon": "metapod",
|
||||
"weight": "7",
|
||||
"npcLevels": "8-10",
|
||||
"selectableTimes": "2"
|
||||
},
|
||||
{
|
||||
"pokemon": "beedrill",
|
||||
"weight": "5",
|
||||
"npcLevels": "10-15",
|
||||
"selectableTimes": "1"
|
||||
},
|
||||
{
|
||||
"pokemon": "butterfree",
|
||||
"weight": "5",
|
||||
"npcLevels": "10-15",
|
||||
"selectableTimes": "1"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
#### script
|
||||
The "script" party type is a way to define a party of Pokémon using a MoLang script. The script can modify a q.party struct,
|
||||
primarily using function calls such as `q.party.add_by_properties('pikachu shiny)`.
|
||||
|
||||
There is an `isStatic` property to control whether the party is static or dynamic. There is a `script` property which is a
|
||||
resource location pointing to the script that should be located in the `molang` datapack folder.
|
||||
|
||||
The MoLang environment used for the script party provider contains q.level for the seed level, q.npc for the NPC, as well as q.players for
|
||||
an array of all players that are challenging the NPC. The players list is only present for dynamic parties. If it is specifically
|
||||
a single battle challenge and this is a dynamic party, there will also be a `q.player`, for convenience.
|
||||
|
||||
"party": {
|
||||
"type": "script",
|
||||
"isStatic": true,
|
||||
"script": "cobblemon:npc_party"
|
||||
}
|
||||
|
||||
An example script might be:
|
||||
|
||||
t.use_butterfree = math.random_integer(1, 2) == 1;
|
||||
t.use_butterfree ? {
|
||||
q.party.add_by_properties('butterfree level=' + (q.level + 3));
|
||||
} : {
|
||||
q.party.add_by_properties('beedrill level=' + (q.level + 3));
|
||||
};
|
||||
q.party.add_by_properties('pidgey');
|
||||
|
||||
### autoHealParty
|
||||
Whether or not the NPC's party will be healed between battles. If the NPC has a dynamic party, this option will
|
||||
do nothing. If the NPC has a static party and this option is false, their Pokémon will not be healed and the
|
||||
NPC will have to use healing machines to heal them if they have that enabled in their AI.
|
||||
|
||||
### isMovable
|
||||
Controls whether the NPC can be moved by players or other entities. If disabled, the NPC will remain
|
||||
stationary.
|
||||
|
||||
### isInvulnerable
|
||||
Controls whether the NPC is immune to all damage sources.
|
||||
|
||||
### isLeashable
|
||||
Controls whether players can attach a lead to a NPC. If disabled, players will be unable to leash
|
||||
the NPC and move them using leads.
|
||||
|
||||
### allowProjectileHits
|
||||
Controls whether the NPC can be hit by projectiles. Disabling this option prevents unwanted
|
||||
interactions, such as players moving the NPC with fishing rods or other projectiles.
|
||||
|
||||
### ai
|
||||
The AI property is an array of brain configurations. This is used to configure the behaviours of the NPC. See
|
||||
the documentation for brain configurations at [AI Configuration](../ai/config/README.md).
|
||||
+2
-2
@@ -8,15 +8,15 @@
|
||||
|
||||
package com.cobblemon.mod.common.api.npc.configuration
|
||||
|
||||
import com.cobblemon.mod.common.api.npc.NPCPartyProvider
|
||||
import com.cobblemon.mod.common.util.DataKeys
|
||||
import net.minecraft.nbt.CompoundTag
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
|
||||
class NPCBattleConfiguration {
|
||||
var canChallenge = false
|
||||
var party: NPCPartyProvider? = null
|
||||
@Deprecated("This is not likely to get used")
|
||||
var simultaneousBattles = false
|
||||
@Deprecated("This is not likely to get used")
|
||||
var healAfterwards = true
|
||||
|
||||
fun encode(buffer: RegistryFriendlyByteBuf) {
|
||||
|
||||
+36
-8
@@ -13,6 +13,11 @@ import com.bedrockk.molang.runtime.value.MoValue
|
||||
import com.bedrockk.molang.runtime.value.StringValue
|
||||
import com.cobblemon.mod.common.entity.npc.NPCEntity
|
||||
import com.cobblemon.mod.common.util.asTranslated
|
||||
import com.cobblemon.mod.common.util.readString
|
||||
import com.cobblemon.mod.common.util.readText
|
||||
import com.cobblemon.mod.common.util.writeString
|
||||
import com.cobblemon.mod.common.util.writeText
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
import net.minecraft.network.chat.Component
|
||||
|
||||
/**
|
||||
@@ -30,10 +35,38 @@ class NPCConfigVariable(
|
||||
val type: NPCVariableType = NPCVariableType.NUMBER,
|
||||
val defaultValue: String = "0",
|
||||
) {
|
||||
companion object {
|
||||
fun decode(buffer: RegistryFriendlyByteBuf): NPCConfigVariable {
|
||||
return NPCConfigVariable(
|
||||
buffer.readString(),
|
||||
buffer.readText(),
|
||||
buffer.readText(),
|
||||
buffer.readEnum(NPCVariableType::class.java),
|
||||
buffer.readString()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
enum class NPCVariableType {
|
||||
NUMBER,
|
||||
STRING,
|
||||
BOOLEAN
|
||||
TEXT,
|
||||
BOOLEAN;
|
||||
|
||||
fun toMoValue(value: String): MoValue {
|
||||
return when (this) {
|
||||
TEXT -> StringValue(value)
|
||||
BOOLEAN -> DoubleValue(value.let { it == "1" || it.toBooleanStrictOrNull() == true })
|
||||
else -> DoubleValue(value.toDouble())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun encode(buffer: RegistryFriendlyByteBuf) {
|
||||
buffer.writeString(variableName)
|
||||
buffer.writeText(displayName)
|
||||
buffer.writeText(description)
|
||||
buffer.writeEnum(type)
|
||||
buffer.writeString(defaultValue)
|
||||
}
|
||||
|
||||
fun apply(npc: NPCEntity, value: MoValue) {
|
||||
@@ -41,11 +74,6 @@ class NPCConfigVariable(
|
||||
}
|
||||
|
||||
fun applyDefault(npc: NPCEntity) {
|
||||
val value = when (type) {
|
||||
NPCVariableType.STRING -> StringValue(defaultValue)
|
||||
NPCVariableType.BOOLEAN -> DoubleValue(defaultValue.toBoolean())
|
||||
else -> DoubleValue(defaultValue.toDouble())
|
||||
}
|
||||
npc.config.set(variableName.split(".").iterator(), value)
|
||||
npc.config.setDirectly(variableName, type.toMoValue(defaultValue))
|
||||
}
|
||||
}
|
||||
-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.npc.partyproviders
|
||||
|
||||
import com.cobblemon.mod.common.api.npc.NPCPartyProvider
|
||||
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
|
||||
import com.cobblemon.mod.common.entity.npc.NPCEntity
|
||||
import com.google.gson.JsonElement
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
|
||||
/**
|
||||
* A provider of a party for battling the NPC. It generates a [StaticNPCParty] but using a more complex process
|
||||
* that takes an input level and makes a randomized selection of Pokémon and movesets.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since July 12th, 2024
|
||||
*/
|
||||
class DynamicPartyProvider : NPCPartyProvider {
|
||||
companion object {
|
||||
val TYPE = "dynamic"
|
||||
}
|
||||
|
||||
class DynamicPool {
|
||||
val id = ResourceLocation.parse("cobblemon:pool")
|
||||
val pokemon: MutableList<DynamicPokemon> = mutableListOf()
|
||||
}
|
||||
|
||||
|
||||
class DynamicPokemon(
|
||||
val pokemon: PokemonProperties,
|
||||
val possibleLevels: IntRange,
|
||||
val selectableTimes: IntRange,
|
||||
val guaranteed: Boolean = false
|
||||
)
|
||||
|
||||
override val type = "dynamic"
|
||||
|
||||
override fun loadFromJSON(json: JsonElement) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun provide(npc: NPCEntity, level: Int): NPCParty {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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.npc.partyproviders
|
||||
|
||||
import com.cobblemon.mod.common.api.storage.party.NPCPartyStore
|
||||
import com.cobblemon.mod.common.api.storage.party.PartyStore
|
||||
import com.cobblemon.mod.common.entity.npc.NPCEntity
|
||||
import com.cobblemon.mod.common.net.IntSize
|
||||
import com.cobblemon.mod.common.pokemon.Pokemon
|
||||
import com.cobblemon.mod.common.util.DataKeys
|
||||
import com.cobblemon.mod.common.util.readSizedInt
|
||||
import com.cobblemon.mod.common.util.writeSizedInt
|
||||
import net.minecraft.core.RegistryAccess
|
||||
import net.minecraft.nbt.CompoundTag
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
|
||||
/**
|
||||
* Represents a party of Pokemon that an NPC can have. This isn't directly a party of Pokémon so that
|
||||
* a dynamically generated party can exist where a party is generated at the time of a challenge.
|
||||
*
|
||||
* This interface is sealed. Implementations should be either [StaticNPCParty] or [DynamicNPCParty].
|
||||
* If you want a custom type then you should implement [DynamicNPCParty] and register it in the
|
||||
* [DynamicNPCParty.types] map.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since July 12th, 2024
|
||||
*/
|
||||
sealed interface NPCParty {
|
||||
fun getParty(player: ServerPlayer, npc: NPCEntity): PartyStore?
|
||||
fun encode(buffer: RegistryFriendlyByteBuf)
|
||||
fun decode(buffer: RegistryFriendlyByteBuf)
|
||||
fun saveToNBT(nbt: CompoundTag, registryAccess: RegistryAccess)
|
||||
fun loadFromNBT(nbt: CompoundTag, registryAccess: RegistryAccess)
|
||||
}
|
||||
|
||||
/**
|
||||
* A simple wrapping around a [NPCPartyStore]. This is a straightforward party that is always the same.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since July 12th, 2024
|
||||
*/
|
||||
class StaticNPCParty(val party: NPCPartyStore) : NPCParty {
|
||||
override fun getParty(player: ServerPlayer, npc: NPCEntity) = party
|
||||
|
||||
override fun encode(buffer: RegistryFriendlyByteBuf) {
|
||||
buffer.writeSizedInt(IntSize.U_BYTE, party.size())
|
||||
for (pokemon in party) {
|
||||
Pokemon.S2C_CODEC.encode(buffer, pokemon)
|
||||
}
|
||||
}
|
||||
|
||||
override fun decode(buffer: RegistryFriendlyByteBuf) {
|
||||
repeat(times = buffer.readSizedInt(IntSize.U_BYTE)) {
|
||||
party.add(Pokemon.S2C_CODEC.decode(buffer))
|
||||
}
|
||||
}
|
||||
|
||||
override fun saveToNBT(nbt: CompoundTag, registryAccess: RegistryAccess) {
|
||||
for ((index, pokemon) in party.withIndex()) {
|
||||
nbt.put(DataKeys.NPC_PARTY_POKEMON + index, pokemon.saveToNBT(registryAccess))
|
||||
}
|
||||
}
|
||||
|
||||
override fun loadFromNBT(nbt: CompoundTag, registryAccess: RegistryAccess) {
|
||||
var index = 0
|
||||
while (nbt.contains(DataKeys.NPC_PARTY_POKEMON + index)) {
|
||||
this.party.add(Pokemon().loadFromNBT(registryAccess, nbt.getCompound(DataKeys.NPC_PARTY_POKEMON + index)))
|
||||
index++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A dynamic party that is generated at the time of a challenge.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since July 12th, 2024
|
||||
*/
|
||||
interface DynamicNPCParty : NPCParty {
|
||||
companion object {
|
||||
val types = mutableMapOf<String, Class<out NPCParty>>()
|
||||
init {}
|
||||
}
|
||||
}
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.npc.partyproviders
|
||||
|
||||
import com.bedrockk.molang.Expression
|
||||
import com.bedrockk.molang.runtime.MoLangRuntime
|
||||
import com.bedrockk.molang.runtime.value.DoubleValue
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.setup
|
||||
import com.cobblemon.mod.common.api.npc.NPCPartyProvider
|
||||
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
|
||||
import com.cobblemon.mod.common.api.storage.party.NPCPartyStore
|
||||
import com.cobblemon.mod.common.entity.npc.NPCEntity
|
||||
import com.cobblemon.mod.common.util.asArrayValue
|
||||
import com.cobblemon.mod.common.util.asExpression
|
||||
import com.cobblemon.mod.common.util.resolveFloat
|
||||
import com.cobblemon.mod.common.util.resolveInt
|
||||
import com.cobblemon.mod.common.util.weightedSelection
|
||||
import com.cobblemon.mod.common.util.withQueryValue
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.JsonObject
|
||||
import com.google.gson.JsonPrimitive
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
|
||||
/**
|
||||
* A provider of a party for battling the NPC. It generates a party using a more complex process
|
||||
* that takes an input level and makes a randomized selection of Pokémon and movesets from a pool.
|
||||
*
|
||||
* The party can be static or dynamic based on [isStatic]. If it is dynamic, the pool will be used
|
||||
* to generate a party whenever the NPC is challenged and AI that depends on a reliable party will
|
||||
* not run.
|
||||
*
|
||||
* Not an actual pool party. That's for v2.0.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since July 12th, 2024
|
||||
*/
|
||||
class PoolPartyProvider : NPCPartyProvider {
|
||||
companion object {
|
||||
const val TYPE = "pool"
|
||||
}
|
||||
|
||||
@Transient
|
||||
override val type = TYPE
|
||||
|
||||
override var isStatic: Boolean = false
|
||||
var minPokemon: Expression = "1".asExpression()
|
||||
var maxPokemon: Expression = "6".asExpression()
|
||||
var pool = mutableListOf<DynamicPokemon>()
|
||||
|
||||
override fun loadFromJSON(json: JsonElement) {
|
||||
json as JsonObject
|
||||
this.minPokemon = json.getAsJsonPrimitive("minPokemon").asString?.asExpression() ?: "1".asExpression()
|
||||
this.maxPokemon = json.getAsJsonPrimitive("maxPokemon").asString?.asExpression() ?: "6".asExpression()
|
||||
this.pool = json.getAsJsonArray("pool").map {
|
||||
if (it is JsonPrimitive) {
|
||||
return@map DynamicPokemon(
|
||||
PokemonProperties.parse(it.asString),
|
||||
"0".asExpression(),
|
||||
1..100,
|
||||
"1".asExpression(),
|
||||
"1".asExpression()
|
||||
)
|
||||
}
|
||||
|
||||
it as JsonObject
|
||||
DynamicPokemon(
|
||||
PokemonProperties.parse(it.getAsJsonPrimitive("pokemon").asString),
|
||||
it.getAsJsonPrimitive("levelVariation")?.asString?.asExpression() ?: "0".asExpression(),
|
||||
it.getAsJsonPrimitive("npcLevels")?.asString?.split("-")?.let { it[0].toInt()..it[1].toInt() } ?: 1..100,
|
||||
it.getAsJsonPrimitive("selectableTimes")?.asString?.asExpression() ?: "1".asExpression(),
|
||||
it.getAsJsonPrimitive("weight")?.asString?.asExpression() ?: "1".asExpression()
|
||||
)
|
||||
}.toMutableList()
|
||||
isStatic = json.getAsJsonPrimitive("isStatic").asBoolean
|
||||
}
|
||||
|
||||
class DynamicPokemon(
|
||||
val pokemon: PokemonProperties,
|
||||
val levelVariation: Expression,
|
||||
val npcLevels: IntRange,
|
||||
val selectableTimes: Expression,
|
||||
val weight: Expression = "1".asExpression()
|
||||
) {
|
||||
fun getWeight(runtime: MoLangRuntime): Float {
|
||||
return runtime.resolveFloat(weight)
|
||||
}
|
||||
|
||||
fun hasWeight(runtime: MoLangRuntime): Boolean {
|
||||
return runtime.resolveFloat(weight) != 0F
|
||||
}
|
||||
}
|
||||
|
||||
fun formulateParty(npc: NPCEntity, level: Int, players: List<ServerPlayer>, party: NPCPartyStore) {
|
||||
val runtime = MoLangRuntime().setup().withQueryValue("npc", npc.struct)
|
||||
runtime.withQueryValue("level", DoubleValue(level))
|
||||
runtime.withQueryValue("players", players.asArrayValue { it.asMoLangValue() })
|
||||
if (players.size == 1) {
|
||||
// This is for the convenience, most cases will be pvn one player
|
||||
runtime.withQueryValue("player", players.first().asMoLangValue())
|
||||
}
|
||||
val minPokemon = runtime.resolveInt(this.minPokemon)
|
||||
val maxPokemon = runtime.resolveInt(this.maxPokemon)
|
||||
var desiredPokemonCount = (minPokemon..maxPokemon).random()
|
||||
|
||||
val workingPool = pool.filter { level in it.npcLevels && runtime.resolveInt(it.selectableTimes) > 0 }.toMutableList()
|
||||
val useCounts = mutableMapOf<DynamicPokemon, Int>()
|
||||
|
||||
while (desiredPokemonCount > 0 && workingPool.any { it.hasWeight(runtime) }) {
|
||||
val selected = workingPool.filter { it.getWeight(runtime) == -1F }.randomOrNull()
|
||||
?: workingPool.weightedSelection { it.getWeight(runtime) }
|
||||
?: break
|
||||
useCounts[selected] = useCounts.getOrDefault(selected, 0) + 1
|
||||
val allowedSelections = runtime.resolveInt(selected.selectableTimes)
|
||||
if (useCounts[selected]!! >= allowedSelections) {
|
||||
workingPool.remove(selected)
|
||||
}
|
||||
desiredPokemonCount--
|
||||
|
||||
// If the Pokémon's props specifies a level then use that, otherwise choose a random level within the range
|
||||
val levelVariation = (0..runtime.resolveInt(selected.levelVariation)).random()
|
||||
val randomLevel = level + levelVariation
|
||||
val instance = selected.pokemon.copy().also { it.level = it.level ?: randomLevel }.create()
|
||||
party.add(instance)
|
||||
}
|
||||
}
|
||||
|
||||
override fun provide(npc: NPCEntity, level: Int, players: List<ServerPlayer>): NPCPartyStore {
|
||||
val party = NPCPartyStore(npc)
|
||||
formulateParty(npc, level, players, party)
|
||||
return party
|
||||
}
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.npc.partyproviders
|
||||
|
||||
import com.bedrockk.molang.runtime.MoLangRuntime
|
||||
import com.bedrockk.molang.runtime.value.DoubleValue
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.setup
|
||||
import com.cobblemon.mod.common.api.npc.NPCPartyProvider
|
||||
import com.cobblemon.mod.common.api.scripting.CobblemonScripts
|
||||
import com.cobblemon.mod.common.api.storage.party.NPCPartyStore
|
||||
import com.cobblemon.mod.common.entity.npc.NPCEntity
|
||||
import com.cobblemon.mod.common.util.asArrayValue
|
||||
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.cobblemon.mod.common.util.toProperties
|
||||
import com.cobblemon.mod.common.util.withQueryValue
|
||||
import com.google.gson.JsonElement
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
|
||||
class ScriptPartyProvider : NPCPartyProvider {
|
||||
companion object {
|
||||
const val TYPE = "script"
|
||||
}
|
||||
|
||||
override val type = TYPE
|
||||
override var isStatic: Boolean = true
|
||||
var script: ResourceLocation = cobblemonResource("dummy")
|
||||
|
||||
override fun loadFromJSON(json: JsonElement) {
|
||||
isStatic = json.asJsonObject.get("isStatic")?.asBoolean != false
|
||||
script = json.asJsonObject.get("script").asString.asIdentifierDefaultingNamespace()
|
||||
}
|
||||
|
||||
override fun provide(npc: NPCEntity, level: Int, players: List<ServerPlayer>): NPCPartyStore {
|
||||
val runtime = MoLangRuntime().setup().withQueryValue("npc", npc.struct)
|
||||
runtime.withQueryValue("level", DoubleValue(level))
|
||||
runtime.withQueryValue("players", players.asArrayValue { it.asMoLangValue() })
|
||||
if (players.size == 1) {
|
||||
// This is for the convenience, most cases will be pvn one player
|
||||
runtime.withQueryValue("player", players.first().asMoLangValue())
|
||||
}
|
||||
val npcParty = NPCPartyStore(npc)
|
||||
runtime.withQueryValue("party", npcParty.struct)
|
||||
|
||||
if (!CobblemonScripts.scripts.containsKey(script)) {
|
||||
Cobblemon.LOGGER.error("Script $script not found in script registry.")
|
||||
npcParty.add("magikarp".toProperties().create())
|
||||
return npcParty
|
||||
}
|
||||
|
||||
CobblemonScripts.run(identifier = script, runtime = runtime)
|
||||
|
||||
return npcParty
|
||||
}
|
||||
}
|
||||
+9
-3
@@ -14,9 +14,10 @@ import com.cobblemon.mod.common.api.storage.party.NPCPartyStore
|
||||
import com.cobblemon.mod.common.entity.npc.NPCEntity
|
||||
import com.cobblemon.mod.common.util.toProperties
|
||||
import com.google.gson.JsonElement
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
|
||||
/**
|
||||
* A basic party provider that just produces a [StaticNPCParty] based on a list of [PokemonProperties].
|
||||
* A basic party provider that just produces a [NPCPartyStore] based on a simple list of [PokemonProperties].
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since August 19th, 2023
|
||||
@@ -28,10 +29,15 @@ class SimplePartyProvider : NPCPartyProvider {
|
||||
|
||||
@Transient
|
||||
override val type = TYPE
|
||||
override var isStatic = true
|
||||
|
||||
val pokemon = mutableListOf<PokemonProperties>()
|
||||
|
||||
override fun loadFromJSON(json: JsonElement) {
|
||||
val isStatic = json.asJsonObject.get("isStatic")?.asBoolean
|
||||
if (isStatic != null) {
|
||||
this.isStatic = isStatic
|
||||
}
|
||||
val pokemonList = json.asJsonObject.getAsJsonArray("pokemon")
|
||||
pokemonList.forEach {
|
||||
val pokemon = if (it.isJsonPrimitive) {
|
||||
@@ -47,10 +53,10 @@ class SimplePartyProvider : NPCPartyProvider {
|
||||
}
|
||||
}
|
||||
|
||||
override fun provide(npc: NPCEntity, level: Int): NPCParty {
|
||||
override fun provide(npc: NPCEntity, level: Int, players: List<ServerPlayer>): NPCPartyStore {
|
||||
val pokemon = pokemon.map { it.copy().also { it.level = it.level ?: level }.create() }
|
||||
val party = NPCPartyStore(npc)
|
||||
pokemon.forEach(party::add)
|
||||
return StaticNPCParty(party)
|
||||
return party
|
||||
}
|
||||
}
|
||||
@@ -76,6 +76,7 @@ object CobblemonPermissions {
|
||||
val POKEDEX = this.create("${COMMAND_PREFIX}pokedex", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
|
||||
|
||||
val NPC_EDIT = this.create("${COMMAND_PREFIX}npcedit", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
|
||||
val NPC_DELETE = this.create("${COMMAND_PREFIX}npcdelete", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
|
||||
val FREEZE_POKEMON = this.create("${COMMAND_PREFIX}freezepokemon", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
|
||||
val APPLY_PLAYER_TEXTURE = this.create("${COMMAND_PREFIX}applyplayertexture", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
|
||||
|
||||
|
||||
@@ -22,6 +22,11 @@ enum class PermissionLevel(val numericalValue: Int) {
|
||||
SPAWN_PROTECTION_BYPASS(1),
|
||||
CHEAT_COMMANDS_AND_COMMAND_BLOCKS(2),
|
||||
MULTIPLAYER_MANAGEMENT(3),
|
||||
ALL_COMMANDS(4)
|
||||
ALL_COMMANDS(4);
|
||||
|
||||
companion object {
|
||||
fun byNumericValue(value: Int): PermissionLevel {
|
||||
return entries.first { it.numericalValue == value }
|
||||
}
|
||||
}
|
||||
}
|
||||
+16
-2
@@ -24,7 +24,6 @@ interface PermissionValidator {
|
||||
|
||||
/**
|
||||
* Invoked when the validator replaces the existing one in [Cobblemon.permissionValidator].
|
||||
*
|
||||
*/
|
||||
fun initialize()
|
||||
|
||||
@@ -36,6 +35,14 @@ interface PermissionValidator {
|
||||
* @return If the [player] has the [permission].
|
||||
*/
|
||||
fun hasPermission(player: ServerPlayer, permission: Permission): Boolean
|
||||
/**
|
||||
* Validates a permission for [ServerPlayer] based only on a permission string and a permission level.
|
||||
*
|
||||
* @param player The target [ServerPlayer].
|
||||
* @param permission The permission string being queried such as cobblemon.command.giveallpokemon.
|
||||
* @param level The permission level being queried. 4 is generally used for cheats.
|
||||
*/
|
||||
fun hasPermission(player: ServerPlayer, permission: String, level: Int): Boolean = hasPermission(player, CobblemonPermission(permission, PermissionLevel.byNumericValue(level)))
|
||||
|
||||
/**
|
||||
* Validates a permission for [CommandSource].
|
||||
@@ -45,5 +52,12 @@ interface PermissionValidator {
|
||||
* @return If the [source] has the [permission].
|
||||
*/
|
||||
fun hasPermission(source: CommandSourceStack, permission: Permission): Boolean
|
||||
|
||||
/**
|
||||
* Validates a permission for [CommandSourceStack] based only on a permission string and a permission level.
|
||||
*
|
||||
* @param source The target [CommandSourceStack].
|
||||
* @param permission The permission string being queried such as cobblemon.command.giveallpokemon.
|
||||
* @param level The permission level being queried. 4 is generally used for cheats.
|
||||
*/
|
||||
fun hasPermission(source: CommandSourceStack, permission: String, level: Int): Boolean = hasPermission(source, CobblemonPermission(permission, PermissionLevel.byNumericValue(level)))
|
||||
}
|
||||
+1
-1
@@ -133,7 +133,7 @@ object CatchRateModifiers {
|
||||
*/
|
||||
val LURE = WorldStateModifier { _, entity ->
|
||||
if (FishingSpawnCause.FISHED_ASPECT in entity.aspects)
|
||||
4f
|
||||
4F
|
||||
else
|
||||
1F
|
||||
}
|
||||
|
||||
+1
-2
@@ -42,7 +42,6 @@ abstract class AbstractPokedexManager {
|
||||
return speciesRecords.getOrPut(speciesId) {
|
||||
val record = SpeciesDexRecord()
|
||||
record.initialize(this, speciesId)
|
||||
onSpeciesRecordUpdated(record)
|
||||
record
|
||||
}
|
||||
}
|
||||
@@ -95,7 +94,7 @@ abstract class AbstractPokedexManager {
|
||||
if (formRecord == null || formRecord.knowledge == PokedexEntryProgress.NONE) {
|
||||
return PokedexLearnedInformation.FORM
|
||||
}
|
||||
if (pokedexEntityData.aspects.none(speciesRecord::hasAspect) || pokedexEntityData.gender !in formRecord.getGenders() || !formRecord.hasSeenShinyState(pokedexEntityData.shiny)) {
|
||||
if (pokedexEntityData.aspects.any{ !speciesRecord.hasAspect(it) } || pokedexEntityData.gender !in formRecord.getGenders() || !formRecord.hasSeenShinyState(pokedexEntityData.shiny)) {
|
||||
return PokedexLearnedInformation.VARIATION
|
||||
}
|
||||
return PokedexLearnedInformation.NONE
|
||||
|
||||
@@ -61,17 +61,27 @@ class FormDexRecord {
|
||||
@Transient
|
||||
lateinit var speciesDexRecord: SpeciesDexRecord
|
||||
|
||||
@Transient
|
||||
lateinit var formName: String
|
||||
|
||||
@Transient
|
||||
lateinit var struct: QueryStruct
|
||||
|
||||
fun initialize(speciesDexRecord: SpeciesDexRecord) {
|
||||
fun initialize(speciesDexRecord: SpeciesDexRecord, formName: String) {
|
||||
this.speciesDexRecord = speciesDexRecord
|
||||
this.formName = formName
|
||||
struct = QueryStruct(hashMapOf())
|
||||
.addFunction("data") { data }
|
||||
.addFunction("knowledge") { StringValue(knowledge.name) }
|
||||
.addFunction("has_seen_gender") { params -> genders.contains(Gender.valueOf(params.getString(0).uppercase())) }
|
||||
}
|
||||
|
||||
fun clone() = FormDexRecord().also {
|
||||
it.genders.addAll(genders)
|
||||
it.seenShinyStates.addAll(seenShinyStates)
|
||||
it.knowledge = knowledge
|
||||
}
|
||||
|
||||
fun encountered(pokemon: Pokemon) {
|
||||
if (wouldBeDifferent(pokemon, PokedexEntryProgress.ENCOUNTERED)) {
|
||||
addInformation(pokemon, PokedexEntryProgress.ENCOUNTERED)
|
||||
|
||||
@@ -57,7 +57,7 @@ class PokedexManager(
|
||||
uuid.getPlayer()?.sendPacket(
|
||||
SetClientPlayerDataPacket(
|
||||
type = PlayerInstancedDataStoreTypes.POKEDEX,
|
||||
playerData = ClientPokedexManager(mutableMapOf(speciesDexRecord.id to speciesDexRecord)),
|
||||
playerData = ClientPokedexManager(mutableMapOf(speciesDexRecord.id to speciesDexRecord.clone())),
|
||||
isIncremental = true
|
||||
)
|
||||
)
|
||||
@@ -75,5 +75,9 @@ class PokedexManager(
|
||||
}
|
||||
}
|
||||
|
||||
override fun toClientData() = ClientPokedexManager(speciesRecords)
|
||||
override fun toClientData(): ClientPokedexManager {
|
||||
val copied = mutableMapOf<ResourceLocation, SpeciesDexRecord>()
|
||||
speciesRecords.forEach { (key, value) -> copied[key] = value.clone() }
|
||||
return ClientPokedexManager(copied)
|
||||
}
|
||||
}
|
||||
@@ -51,6 +51,10 @@ class SpeciesDexRecord {
|
||||
private val aspects: MutableSet<String> = mutableSetOf()
|
||||
private val formRecords: MutableMap<String, FormDexRecord> = mutableMapOf()
|
||||
|
||||
fun describe(): String {
|
||||
return "SpeciesDexRecord(aspects=$aspects, formRecords=$formRecords)"
|
||||
}
|
||||
|
||||
@Transient
|
||||
val struct = QueryStruct(hashMapOf()).addStandardFunctions()
|
||||
.addFunction("get_form_record") { params ->
|
||||
@@ -79,7 +83,7 @@ class SpeciesDexRecord {
|
||||
fun initialize(pokedexManager: AbstractPokedexManager, id: ResourceLocation) {
|
||||
this.id = id
|
||||
this.pokedexManager = pokedexManager
|
||||
this.formRecords.forEach { it.value.initialize(this) }
|
||||
this.formRecords.forEach { it.value.initialize(this, it.key) }
|
||||
}
|
||||
|
||||
fun onFormRecordUpdated(formDexRecord: FormDexRecord) {
|
||||
@@ -106,8 +110,7 @@ class SpeciesDexRecord {
|
||||
fun getOrCreateFormRecord(formName: String): FormDexRecord {
|
||||
return formRecords.getOrPut(formName.lowercase()) {
|
||||
val record = FormDexRecord()
|
||||
record.initialize(this)
|
||||
onFormRecordUpdated(record)
|
||||
record.initialize(this, formName)
|
||||
// Some more stuff eventually
|
||||
record
|
||||
}
|
||||
@@ -121,6 +124,11 @@ class SpeciesDexRecord {
|
||||
formRecords.remove(formName)
|
||||
}
|
||||
|
||||
fun clone() = SpeciesDexRecord().also {
|
||||
it.aspects.addAll(aspects)
|
||||
it.formRecords.putAll(formRecords.mapValues { it.value.clone() })
|
||||
}
|
||||
|
||||
fun getAspects(): Set<String> = this.aspects
|
||||
fun hasAspect(aspect: String) = aspect in aspects
|
||||
fun getKnowledge() = formRecords.values.maxOfOrNull { it.knowledge } ?: PokedexEntryProgress.NONE
|
||||
|
||||
+6
-4
@@ -6,8 +6,10 @@
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.client.trade
|
||||
package com.cobblemon.mod.common.api.pokedex.filter
|
||||
|
||||
import java.util.UUID
|
||||
|
||||
class ClientTradeOffer(val tradeOfferId: UUID, val traderId: UUID)
|
||||
enum class SearchByType {
|
||||
SPECIES,
|
||||
ABILITIES,
|
||||
DROPS;
|
||||
}
|
||||
@@ -8,10 +8,16 @@
|
||||
|
||||
package com.cobblemon.mod.common.api.pokedex.filter
|
||||
|
||||
import com.cobblemon.mod.common.api.drop.ItemDropEntry
|
||||
import com.cobblemon.mod.common.api.pokedex.AbstractPokedexManager
|
||||
import com.cobblemon.mod.common.api.pokedex.PokedexEntryProgress
|
||||
import com.cobblemon.mod.common.api.pokedex.entry.PokedexEntry
|
||||
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
|
||||
import com.cobblemon.mod.common.pokemon.abilities.HiddenAbility
|
||||
import com.cobblemon.mod.common.util.asTranslated
|
||||
import com.cobblemon.mod.common.util.itemRegistry
|
||||
import net.minecraft.client.Minecraft
|
||||
import net.minecraft.world.item.ItemStack
|
||||
|
||||
/**
|
||||
* A Pokedex [EntryFilter] that filters out entries that do not contain the current search.
|
||||
@@ -20,12 +26,45 @@ import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
|
||||
* @since September 4th, 2024
|
||||
* @param searchString The string to use when checking.
|
||||
*/
|
||||
class SearchFilter(val pokedexManager: AbstractPokedexManager, val searchString: String) : EntryFilter() {
|
||||
class SearchFilter(val pokedexManager: AbstractPokedexManager, val searchString: String, val searchByType: SearchByType = SearchByType.SPECIES) : EntryFilter() {
|
||||
|
||||
override fun test(entry: PokedexEntry): Boolean {
|
||||
if (searchString == "") return true
|
||||
|
||||
val species = PokemonSpecies.getByIdentifier(entry.speciesId) ?: return false
|
||||
if (pokedexManager.getHighestKnowledgeFor(entry) == PokedexEntryProgress.NONE) return false
|
||||
return species.translatedName.string.contains(searchString.trim(), true)
|
||||
|
||||
when(searchByType) {
|
||||
SearchByType.ABILITIES -> {
|
||||
if (pokedexManager.getHighestKnowledgeFor(entry) !== PokedexEntryProgress.CAUGHT) return false
|
||||
val abilityList = mutableListOf<String>()
|
||||
val formsList = if (species.forms.isEmpty()) mutableListOf(species.standardForm) else species.forms
|
||||
formsList.forEach {
|
||||
it.abilities.sortedBy { it is HiddenAbility }.map { ability -> ability.template }.forEach {
|
||||
abilityList.add(it.displayName.asTranslated().string.lowercase())
|
||||
}
|
||||
}
|
||||
return abilityList.any { it.contains(searchString.trim().lowercase()) }
|
||||
}
|
||||
SearchByType.DROPS -> {
|
||||
if (pokedexManager.getHighestKnowledgeFor(entry) !== PokedexEntryProgress.CAUGHT) return false
|
||||
val dropsList = mutableListOf<String>()
|
||||
val formsList = if (species.forms.isEmpty()) mutableListOf(species.standardForm) else species.forms
|
||||
formsList.forEach {
|
||||
it.drops.entries.forEach {
|
||||
if (it is ItemDropEntry) {
|
||||
val itemStack = Minecraft.getInstance().player?.level()?.itemRegistry?.get(it.item)?.defaultInstance ?: ItemStack.EMPTY
|
||||
if (!itemStack.isEmpty) dropsList.add(itemStack.displayName.string.lowercase())
|
||||
}
|
||||
}
|
||||
}
|
||||
return dropsList.any { it.contains(searchString.trim().lowercase()) }
|
||||
}
|
||||
// Search by species name
|
||||
else -> {
|
||||
return species.translatedName.string.contains(searchString.trim(), true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -14,6 +14,8 @@ import com.bedrockk.molang.runtime.value.StringValue
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.api.abilities.Abilities
|
||||
import com.cobblemon.mod.common.api.abilities.Ability
|
||||
import com.cobblemon.mod.common.api.events.CobblemonEvents
|
||||
import com.cobblemon.mod.common.api.events.pokemon.ShinyChanceCalculationEvent
|
||||
import com.cobblemon.mod.common.api.moves.Moves
|
||||
import com.cobblemon.mod.common.api.pokeball.PokeBalls
|
||||
import com.cobblemon.mod.common.api.pokemon.aspect.AspectProvider
|
||||
@@ -34,7 +36,7 @@ import com.cobblemon.mod.common.pokemon.RenderablePokemon
|
||||
import com.cobblemon.mod.common.pokemon.status.PersistentStatus
|
||||
import com.cobblemon.mod.common.util.DataKeys
|
||||
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
|
||||
import com.cobblemon.mod.common.util.isInt
|
||||
import com.cobblemon.mod.common.util.isDouble
|
||||
import com.cobblemon.mod.common.util.isUuid
|
||||
import com.cobblemon.mod.common.util.server
|
||||
import com.cobblemon.mod.common.util.simplify
|
||||
@@ -56,6 +58,7 @@ import net.minecraft.nbt.Tag
|
||||
import net.minecraft.network.chat.Component
|
||||
import net.minecraft.network.chat.MutableComponent
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import net.minecraft.world.item.ItemStack
|
||||
import net.minecraft.world.level.Level
|
||||
|
||||
@@ -183,10 +186,10 @@ open class PokemonProperties {
|
||||
private fun parseIntProperty(keyPairs: MutableList<Pair<String, String?>>, labels: Iterable<String>): Int? {
|
||||
val matchingKeyPair = getMatchedKeyPair(keyPairs, labels) ?: return null
|
||||
val value = matchingKeyPair.second
|
||||
return if (value == null || !value.isInt()) {
|
||||
return if (value == null || !value.isDouble()) {
|
||||
null
|
||||
} else {
|
||||
value.toInt()
|
||||
value.toDouble().toInt()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -573,18 +576,24 @@ open class PokemonProperties {
|
||||
return true
|
||||
}
|
||||
|
||||
fun create(): Pokemon {
|
||||
@JvmOverloads
|
||||
fun create(player: ServerPlayer? = null): Pokemon {
|
||||
val pokemon = Pokemon()
|
||||
apply(pokemon)
|
||||
pokemon.initialize()
|
||||
roll(pokemon)
|
||||
roll(pokemon, player)
|
||||
return pokemon
|
||||
}
|
||||
|
||||
// TEST YOUR LUCK!
|
||||
fun roll(pokemon: Pokemon) {
|
||||
@JvmOverloads
|
||||
fun roll(pokemon: Pokemon, player: ServerPlayer? = null) {
|
||||
val baseTypes = pokemon.form.types.toList()
|
||||
if (this.shiny == null) pokemon.shiny = Cobblemon.config.shinyRate.checkRate()
|
||||
var shinyRate = Cobblemon.config.shinyRate
|
||||
CobblemonEvents.SHINY_CHANCE_CALCULATION.post(ShinyChanceCalculationEvent(shinyRate, pokemon)) { event ->
|
||||
shinyRate = event.calculate(player)
|
||||
}
|
||||
if (this.shiny == null) pokemon.shiny = shinyRate.checkRate()
|
||||
if (this.teraType == null) pokemon.teraType =
|
||||
if (Cobblemon.config.teraTypeRate.checkRate()) {
|
||||
var picked = TeraTypes.random(true)
|
||||
@@ -596,8 +605,9 @@ open class PokemonProperties {
|
||||
else TeraTypes.forElementalType(baseTypes.random())
|
||||
}
|
||||
|
||||
fun createEntity(world: Level): PokemonEntity {
|
||||
return PokemonEntity(world, create()).also { applyCustomProperties(it) }
|
||||
@JvmOverloads
|
||||
fun createEntity(world: Level, player: ServerPlayer? = null): PokemonEntity {
|
||||
return PokemonEntity(world, create(player)).also { applyCustomProperties(it) }
|
||||
}
|
||||
|
||||
// TODO Codecs at some point
|
||||
@@ -646,8 +656,8 @@ open class PokemonProperties {
|
||||
nature = if (tag.contains(DataKeys.POKEMON_NATURE)) tag.getString(DataKeys.POKEMON_NATURE) else null
|
||||
ability = if (tag.contains(DataKeys.POKEMON_ABILITY)) tag.getString(DataKeys.POKEMON_ABILITY) else null
|
||||
status = if (tag.contains(DataKeys.POKEMON_STATUS_NAME)) tag.getString(DataKeys.POKEMON_STATUS_NAME) else null
|
||||
ivs = if (tag.contains(DataKeys.POKEMON_IVS)) ivs?.loadFromNBT(tag.getCompound(DataKeys.POKEMON_IVS)) as IVs? else null
|
||||
evs = if (tag.contains(DataKeys.POKEMON_EVS)) evs?.loadFromNBT(tag.getCompound(DataKeys.POKEMON_EVS)) as EVs? else null
|
||||
ivs = if (tag.contains(DataKeys.POKEMON_IVS)) IVs().loadFromNBT(tag.getCompound(DataKeys.POKEMON_IVS)) as IVs else null
|
||||
evs = if (tag.contains(DataKeys.POKEMON_EVS)) EVs().loadFromNBT(tag.getCompound(DataKeys.POKEMON_EVS)) as EVs else null
|
||||
type = if (tag.contains(DataKeys.ELEMENTAL_TYPE)) tag.getString(DataKeys.ELEMENTAL_TYPE) else null
|
||||
teraType = if (tag.contains(DataKeys.POKEMON_TERA_TYPE)) tag.getString(DataKeys.POKEMON_TERA_TYPE) else null
|
||||
dmaxLevel = if (tag.contains(DataKeys.POKEMON_DMAX_LEVEL)) tag.getInt(DataKeys.POKEMON_DMAX_LEVEL) else null
|
||||
@@ -711,8 +721,8 @@ open class PokemonProperties {
|
||||
nature = json.get(DataKeys.POKEMON_NATURE)?.asString
|
||||
ability = json.get(DataKeys.POKEMON_ABILITY)?.asString
|
||||
status = json.get(DataKeys.POKEMON_STATUS_NAME)?.asString
|
||||
ivs?.loadFromJSON(json.getAsJsonObject(DataKeys.POKEMON_IVS))
|
||||
evs?.loadFromJSON(json.getAsJsonObject(DataKeys.POKEMON_EVS))
|
||||
ivs = json.getAsJsonObject(DataKeys.POKEMON_IVS)?.let { IVs().loadFromJSON(it) } as IVs
|
||||
evs = json.getAsJsonObject(DataKeys.POKEMON_EVS)?.let { EVs().loadFromJSON(it) } as EVs
|
||||
type = json.get(DataKeys.ELEMENTAL_TYPE)?.asString
|
||||
teraType = json.get(DataKeys.POKEMON_TERA_TYPE)?.asString
|
||||
dmaxLevel = json.get(DataKeys.POKEMON_DMAX_LEVEL)?.asInt
|
||||
@@ -779,11 +789,11 @@ open class PokemonProperties {
|
||||
}
|
||||
|
||||
fun copy(): PokemonProperties {
|
||||
return PokemonProperties().loadFromJSON(saveToJSON())
|
||||
return PokemonProperties().loadFromJSON(this.saveToJSON())
|
||||
}
|
||||
|
||||
// If the config value is at least 1, then do 1/x and use that as the property chance
|
||||
private fun Float.checkRate(): Boolean = this >= 1 && (Random.Default.nextFloat() < 1 / this)
|
||||
private fun Float.checkRate(): Boolean = if (this >= 1) (Random.Default.nextFloat() < 1 / this) else Random.Default.nextFloat() < this
|
||||
|
||||
/**
|
||||
* Attempts to find an ability by ID and resolve if it should be forced or if it's legal for the given form.
|
||||
|
||||
+1
-1
@@ -53,7 +53,7 @@ fun interface PokemonPropertyExtractor {
|
||||
val EVS = add { pokemon, properties -> properties.evs = pokemon.evs }
|
||||
|
||||
@JvmField
|
||||
val ILLUSION = mutableListOf(SPECIES, FORM, ASPECTS, GENDER, NICKNAME, SHINY)
|
||||
val ILLUSION = mutableListOf(SPECIES, FORM, ASPECTS, GENDER, NICKNAME, SHINY, POKEBALL)
|
||||
@JvmField
|
||||
val TRANSFORM = mutableListOf(SPECIES, FORM, ASPECTS, GENDER)
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ package com.cobblemon.mod.common.api.pokemon.egg
|
||||
*
|
||||
* @property showdownID used for data synchronization with Showdown data format.
|
||||
*/
|
||||
enum class EggGroup(internal val showdownID: String) {
|
||||
enum class EggGroup(val showdownID: String) {
|
||||
|
||||
MONSTER("Monster"),
|
||||
WATER_1("Water 1"),
|
||||
@@ -31,6 +31,16 @@ enum class EggGroup(internal val showdownID: String) {
|
||||
WATER_2("Water 2"),
|
||||
DITTO("Ditto"),
|
||||
DRAGON("Dragon"),
|
||||
UNDISCOVERED("Undiscovered")
|
||||
UNDISCOVERED("Undiscovered");
|
||||
|
||||
companion object {
|
||||
private val identifierMap: Map<String, EggGroup> = entries.associateBy {
|
||||
it.showdownID.lowercase().replace(" ", "_").replace("-", "_")
|
||||
}
|
||||
|
||||
fun fromIdentifier(identifier: String): EggGroup? {
|
||||
val key = identifier.lowercase().replace(" ", "_").replace("-", "_")
|
||||
return identifierMap[key]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -156,12 +156,11 @@ interface Evolution : EvolutionLike {
|
||||
}
|
||||
|
||||
val preEvoName = pokemon.getDisplayName()
|
||||
val evolveMessage = lang("ui.evolve.into", preEvoName, pokemon.species.translatedName)
|
||||
val pokemonEntity = pokemon.entity
|
||||
if (pokemonEntity == null || !useEvolutionEffect) {
|
||||
pokemon.getOwnerPlayer()?.playNotifySound(CobblemonSounds.EVOLUTION_UI, SoundSource.PLAYERS, 1F, 1F)
|
||||
evolutionMethod(pokemon)
|
||||
pokemon.getOwnerPlayer()?.sendSystemMessage(evolveMessage)
|
||||
pokemon.getOwnerPlayer()?.sendSystemMessage(lang("ui.evolve.into", preEvoName, pokemon.species.translatedName))
|
||||
} else {
|
||||
pokemonEntity.busyLocks.add("evolving")
|
||||
pokemonEntity.navigation.stop()
|
||||
@@ -174,7 +173,7 @@ interface Evolution : EvolutionLike {
|
||||
pokemonEntity.after( seconds = 12F ) {
|
||||
cryAnimation(pokemonEntity)
|
||||
pokemonEntity.busyLocks.remove("evolving")
|
||||
pokemon.getOwnerPlayer()?.sendSystemMessage(evolveMessage)
|
||||
pokemon.getOwnerPlayer()?.sendSystemMessage(lang("ui.evolve.into", preEvoName, pokemon.species.translatedName))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,13 +50,13 @@ enum class Stats(override val identifier: ResourceLocation, override val display
|
||||
val BATTLE_ONLY: Set<Stat> = EnumSet.of(EVASION, ACCURACY)
|
||||
|
||||
/** Gets the [Stat] from the respective Showdown id. */
|
||||
fun getStat(statKey: String) = when(statKey) {
|
||||
fun getStat(statKey: String) = when(statKey.lowercase()) {
|
||||
"hp" -> HP
|
||||
"atk", "Attack" -> ATTACK // Hyper Cutter states the full stat name "Attack"
|
||||
"def", "Defense" -> DEFENCE // Big Pecks states the full stat name "Defense"
|
||||
"atk", "attack" -> ATTACK // Hyper Cutter states the full stat name "Attack"
|
||||
"def", "defense" -> DEFENCE // Big Pecks states the full stat name "Defense"
|
||||
"spa" -> SPECIAL_ATTACK
|
||||
"spd" -> SPECIAL_DEFENCE
|
||||
"spe" -> SPEED
|
||||
"spe", "speed" -> SPEED
|
||||
"evasion" -> EVASION
|
||||
else -> ACCURACY
|
||||
}
|
||||
|
||||
+8
-2
@@ -10,6 +10,8 @@ package com.cobblemon.mod.common.api.scheduling
|
||||
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import com.cobblemon.mod.common.util.runOnServer
|
||||
import net.minecraft.world.TickRateManager
|
||||
import net.minecraft.world.level.Level
|
||||
|
||||
@Deprecated("Use afterOnServer or afterOnClient; ambiguous side is not good for your health")
|
||||
@JvmOverloads
|
||||
@@ -33,9 +35,13 @@ fun delayedFuture(ticks: Int = 0, seconds: Float = 0F, serverThread: Boolean = f
|
||||
* being completed after the delay does things like entity removal or other thread-unsafe actions.
|
||||
*/
|
||||
@JvmOverloads
|
||||
fun afterOnServer(ticks: Int = 0, seconds: Float = 0F, action: () -> Unit) = ServerTaskTracker.after(seconds + ticks / 20F, action)
|
||||
fun afterOnServer(seconds: Float, action: () -> Unit) = ServerTaskTracker.after(seconds, action)
|
||||
@JvmOverloads
|
||||
fun afterOnClient(ticks: Int = 0, seconds: Float, action: () -> Unit) = ClientTaskTracker.after(seconds + ticks / 20F, action)
|
||||
fun afterOnServer(ticks: Int, level: Level, action: () -> Unit) = ServerTaskTracker.after(ticks / level.tickRateManager().tickrate(), action)
|
||||
@JvmOverloads
|
||||
fun afterOnClient(seconds: Float, action: () -> Unit) = ClientTaskTracker.after(seconds, action)
|
||||
@JvmOverloads
|
||||
fun afterOnClient(ticks: Int, level: Level, action: () -> Unit) = ClientTaskTracker.after(ticks / level.tickRateManager().tickrate(), action)
|
||||
|
||||
@Deprecated("Use lerpOnServer or lerpOnClient, side-ambiguity causes problems now")
|
||||
fun lerp(seconds: Float = 0F, serverThread: Boolean = false, action: (Float) -> Unit) = (if (serverThread) ServerTaskTracker else ClientTaskTracker).lerp(seconds, action = action)
|
||||
|
||||
@@ -18,6 +18,7 @@ import com.cobblemon.mod.common.api.reactive.SimpleObservable
|
||||
import com.cobblemon.mod.common.net.messages.client.data.ScriptRegistrySyncPacket
|
||||
import com.cobblemon.mod.common.util.asExpressionLike
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.cobblemon.mod.common.util.contextOrEmpty
|
||||
import com.cobblemon.mod.common.util.endsWith
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
@@ -64,6 +65,6 @@ object CobblemonScripts : DataRegistry {
|
||||
}
|
||||
|
||||
fun run(identifier: ResourceLocation, runtime: MoLangRuntime): MoValue? {
|
||||
return scripts[identifier]?.resolve(runtime)
|
||||
return scripts[identifier]?.resolve(runtime, runtime.contextOrEmpty)
|
||||
}
|
||||
}
|
||||
@@ -8,9 +8,12 @@
|
||||
|
||||
package com.cobblemon.mod.common.api.spawning
|
||||
|
||||
import com.bedrockk.molang.Expression
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.api.conditional.RegistryLikeCondition
|
||||
import com.cobblemon.mod.common.api.data.JsonDataRegistry
|
||||
import com.cobblemon.mod.common.api.molang.ExpressionLike
|
||||
import com.cobblemon.mod.common.api.npc.NPCClass
|
||||
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
|
||||
import com.cobblemon.mod.common.api.reactive.SimpleObservable
|
||||
import com.cobblemon.mod.common.api.spawning.condition.SpawningCondition
|
||||
@@ -69,6 +72,9 @@ object SpawnDetailPresets : JsonDataRegistry<SpawnDetailPreset> {
|
||||
.registerTypeAdapter(MoonPhaseRange::class.java, IntRangesAdapter(MoonPhaseRange.moonPhaseRanges) { MoonPhaseRange(*it) })
|
||||
.registerTypeAdapter(PokemonProperties::class.java, pokemonPropertiesShortAdapter)
|
||||
.registerTypeAdapter(PossibleHeldItem::class.java, PossibleHeldItemAdapter)
|
||||
.registerTypeAdapter(NPCClass::class.java, NPCClassReferenceAdapter)
|
||||
.registerTypeAdapter(Expression::class.java, ExpressionAdapter)
|
||||
.registerTypeAdapter(ExpressionLike::class.java, ExpressionLikeAdapter)
|
||||
.create()
|
||||
|
||||
val presetTypes = mutableMapOf<String, Class<out SpawnDetailPreset>>()
|
||||
|
||||
@@ -8,9 +8,12 @@
|
||||
|
||||
package com.cobblemon.mod.common.api.spawning
|
||||
|
||||
import com.bedrockk.molang.Expression
|
||||
import com.cobblemon.mod.common.api.conditional.RegistryLikeCondition
|
||||
import com.cobblemon.mod.common.api.drop.DropEntry
|
||||
import com.cobblemon.mod.common.api.drop.ItemDropMethod
|
||||
import com.cobblemon.mod.common.api.molang.ExpressionLike
|
||||
import com.cobblemon.mod.common.api.npc.NPCClass
|
||||
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
|
||||
import com.cobblemon.mod.common.api.spawning.condition.SpawningCondition
|
||||
import com.cobblemon.mod.common.api.spawning.context.RegisteredSpawningContext
|
||||
@@ -68,6 +71,9 @@ object SpawnLoader {
|
||||
.registerTypeAdapter(CompoundTag::class.java, NbtCompoundAdapter)
|
||||
.registerTypeAdapter(IntRange::class.java, IntRangeAdapter)
|
||||
.registerTypeAdapter(PossibleHeldItem::class.java, PossibleHeldItemAdapter)
|
||||
.registerTypeAdapter(NPCClass::class.java, NPCClassReferenceAdapter)
|
||||
.registerTypeAdapter(Expression::class.java, ExpressionAdapter)
|
||||
.registerTypeAdapter(ExpressionLike::class.java, ExpressionLikeAdapter)
|
||||
.create()
|
||||
|
||||
var deserializingConditionClass: Class<out SpawningCondition<*>>? = null
|
||||
|
||||
+11
-14
@@ -8,9 +8,10 @@
|
||||
|
||||
package com.cobblemon.mod.common.api.spawning.context
|
||||
|
||||
import com.bedrockk.molang.runtime.struct.VariableStruct
|
||||
import com.bedrockk.molang.runtime.value.DoubleValue
|
||||
import com.cobblemon.mod.common.api.molang.ObjectValue
|
||||
import com.bedrockk.molang.runtime.MoLangRuntime
|
||||
import com.bedrockk.molang.runtime.struct.QueryStruct
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.setup
|
||||
import com.cobblemon.mod.common.api.spawning.SpawnCause
|
||||
import com.cobblemon.mod.common.api.spawning.condition.BasicSpawningCondition
|
||||
import com.cobblemon.mod.common.api.spawning.detail.SpawnDetail
|
||||
@@ -55,6 +56,10 @@ abstract class SpawningContext {
|
||||
}
|
||||
}
|
||||
|
||||
@Transient
|
||||
val runtime = MoLangRuntime().setup()
|
||||
.also { runtime -> runtime.environment.query.addFunction("context") { getOrSetupStruct() } }
|
||||
|
||||
/** What caused the spawn context, as a [SpawnCause]. */
|
||||
abstract val cause: SpawnCause
|
||||
val spawner: Spawner
|
||||
@@ -87,7 +92,7 @@ abstract class SpawningContext {
|
||||
val biomeName: ResourceLocation
|
||||
get() = this.biomeRegistry.getKey(biome)!!
|
||||
|
||||
private val struct = VariableStruct()
|
||||
private var struct = QueryStruct(hashMapOf())
|
||||
private var structCompiled = false
|
||||
|
||||
class StructureChunkCache {
|
||||
@@ -168,19 +173,11 @@ abstract class SpawningContext {
|
||||
return weight
|
||||
}
|
||||
|
||||
fun getOrSetupStruct(): VariableStruct {
|
||||
fun getOrSetupStruct(): QueryStruct {
|
||||
if (structCompiled) {
|
||||
return struct
|
||||
}
|
||||
|
||||
struct.setDirectly("light", DoubleValue(light.toDouble()))
|
||||
struct.setDirectly("x", DoubleValue(position.x.toDouble()))
|
||||
struct.setDirectly("y", DoubleValue(position.y.toDouble()))
|
||||
struct.setDirectly("z", DoubleValue(position.z.toDouble()))
|
||||
struct.setDirectly("moon_phase", DoubleValue(moonPhase.toDouble()))
|
||||
struct.setDirectly("world", ObjectValue(world.registryAccess().registryOrThrow(Registries.DIMENSION).wrapAsHolder(world)))
|
||||
struct.setDirectly("biome", ObjectValue(biomeRegistry.wrapAsHolder(biome)))
|
||||
|
||||
struct = this.asMoLangValue()
|
||||
structCompiled = true
|
||||
return struct
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user