Compare commits

...
230 Commits
Author SHA1 Message Date
JPAK f70d7ce336 Change how lure effects Pokerod so high lure levels no longer get stuck. 2024-12-11 23:57:12 -06:00
Polymeta 5b0a221ade Merge branch 'RaftFixes' into 'main'
Fixed reading speciesdata instead of formdata. Fixed rafts deploying on...

See merge request cable-mc/cobblemon!1359
2024-12-11 08:14:41 +00:00
JazzMcNadeandPolymeta af23b82ce5 Fixed reading speciesdata instead of formdata. Fixed rafts deploying on... 2024-12-11 08:14:41 +00:00
Hiroku 36e78a5af2 Merge branch 'fix-dex-quiet-cries' into 'main'
Fix quiet cries in the Pokédex

See merge request cable-mc/cobblemon!1354
2024-12-07 22:38:12 +00:00
Guilherme PereiraandHiroku 55621b01b7 Fix quiet cries in the Pokédex 2024-12-07 22:38:12 +00:00
Paul b828fe2b3a Merge remote-tracking branch 'origin/main' 2024-12-07 20:59:45 +10:00
Paul b7b10151e1 Added get_npc_variable and set_npc_variable to player functions for convenience, updated molang for flexibility with query struct nested assignments 2024-12-07 20:59:33 +10:00
Test cac6ed843a Merge branch 'sterr/bugfixes' into 'main'
Adjusted portrait space detection in the Pokedex to prevent overlap

Closes #1055

See merge request cable-mc/cobblemon!1348
2024-12-06 18:32:20 +00:00
SterrezoandTest 8f5f40c360 Adjusted portrait space detection in the Pokedex to prevent overlap 2024-12-06 18:32:19 +00:00
Hiroku 0e586b4f38 Merge branch 'fix/ivs' into 'main'
Rework copy on properties, just clone fields rather than serialize + deserialize JSON

See merge request cable-mc/cobblemon!1344
2024-12-06 12:38:01 +00:00
Paul 62dd487448 Removed platform independent JSON datapack loading because Fabric has fixed the bug, closes #1184 2024-12-06 20:06:50 +10:00
Nick 04ecd9189c It would seem a particular build step was forgotten 2024-12-05 21:37:34 -08:00
Nick 55be22e942 Revert previous change, update stats differently instead 2024-12-05 21:29:13 -08:00
Hiroku f2e1b3dab4 Merge branch 'pokeedit-hidden-ability-false' into 'main'
Revert Pokémon ability to normal when hiddenAbility is false

See merge request cable-mc/cobblemon!1351
2024-12-06 00:51:21 +00:00
Paul 66f98ebf12 Added v.npc for on_player_wins and on_player_loses script hooks, v.npc to on_defeat and on_victory hooks 2024-12-05 20:43:04 +10:00
Paul c1842e42b0 Fixed run_molang script hooks not running multi-expressions, closes #1224 2024-12-05 19:14:49 +10:00
Hiroku e6c2f241d4 Merge branch 'MoreDynamicRafts' into 'main'
MoreDynamicRafts

See merge request cable-mc/cobblemon!1347
2024-12-05 08:39:13 +00:00
JazzMcNade 0f9c282e1f Merge branch 'main' into MoreDynamicRafts 2024-12-04 21:23:45 -06:00
JazzMcNade 7bd5d77b60 Fixed naming conventions, removed some unnecessary variables. 2024-12-04 21:22:35 -06:00
Guilherme Pereira f7b4d4e65a add changelog of when hiddenability=false on pokespawn and pokeedit commands 2024-12-04 22:23:28 -03:00
Guilherme Pereira e78f403198 set pokemon ability to normal when property is false 2024-12-04 21:43:07 -03:00
Test 4ca91cc8d8 Merge branch 'pokeedit-nickname-completion' into 'main'
Add nickname completion suggestion for pokeedit and pokespawn commands

See merge request cable-mc/cobblemon!1350
2024-12-05 00:20:08 +00:00
Test 1518181f6b Merge branch 'sound' into 'main'
Move sounds + Pokedex volume adjustments + missing sound fixes

See merge request cable-mc/cobblemon!1341
2024-12-05 00:14:06 +00:00
Hiroku b915237c8d Merge branch 'more-npc-dakka' into 'main'
Even more npc dakka

See merge request cable-mc/cobblemon!1330
2024-12-04 21:52:38 +00:00
Guilherme Pereira bb41a72067 add nickname completion suggestion for properties argument type 2024-12-04 16:29:36 -03:00
Paul fbb8492c75 Added ScriptPartyProvider 2024-12-04 22:36:17 +10:00
Paul c07aef04b1 Merge remote-tracking branch 'origin/main' into more-npc-dakka 2024-12-04 22:05:11 +10:00
Paul 6ff4befb04 Possible fix for tick watchdog hang 2024-12-04 22:00:56 +10:00
Paul a858d4f532 Use emptyList default rather than null for party provider player list, update doco, corrected LookInDirectionTask's extra start conditions 2024-12-04 21:13:38 +10:00
Hiroku 28fc273b26 Merge branch 'gimmi_tower_typo_fix' into 'main'
Fixing something i found in the processor lists

See merge request cable-mc/cobblemon!1345
2024-12-04 10:47:14 +00:00
JazzMcNade fc89757a09 Tab spacing 2024-12-04 01:16:00 -06:00
JazzMcNade 243c9abfd1 Rafts can now can (un)deploy upon touching water or a solid surface. 2024-12-04 01:11:02 -06:00
Slippy b871645e9e Fixing something i found in the processor lists 2024-12-03 11:23:40 -06:00
Hiroku cec0ee04e0 Merge branch 'ability-patch-fix' into 'main'
Ability Patch fix

See merge request cable-mc/cobblemon!1342
2024-12-03 11:41:07 +00:00
Conan 069bc91aa1 Added fix to changelog 2024-12-03 12:39:54 +01:00
Hiroku 692a1bd6ba Merge branch 'stat-sanity' into 'main'
Make Stats#getStat use actual sane checking logic

See merge request cable-mc/cobblemon!1343
2024-12-03 11:22:35 +00:00
Paul c79916d8a0 More handy NPC presets, add player context to dynamic party molang environments, separated applied aspects from variation aspects for API 2024-12-03 21:20:31 +10:00
Nick d503d4a1eb Rework copy on properties, just clone fields rather than serialize + deserialize JSON 2024-12-02 19:10:30 -08:00
amo 3d877496b3 🍋‍🟩 Chore: why was it ever any other way 2024-12-03 02:18:33 +00:00
Conan 1e4964d80c Fixed an issue where Pokémon with their hidden ability couldn't change back to their original abilities 2024-12-02 13:41:01 +01:00
Paul a0f75dd090 Added is_chunk_loaded_at molang to level, added deeper support to json -> component deserialization 2024-12-02 09:46:52 +10:00
Sterrezo 5eceaa849c Quick fix to Mud Sport particles 2024-12-01 16:12:53 +01:00
Sterrezo 0e0233993a Merge branch 'main' into sound 2024-12-01 15:34:51 +01:00
Sterrezo 56ed5a6412 Added a sound to Kinesis 2024-12-01 15:30:47 +01:00
Hiroku 92ecaa66fa Merge branch 'power-items-tooltips' into 'main'
Fixes power items tooltips

See merge request cable-mc/cobblemon!1340
2024-12-01 09:46:08 +00:00
Guilherme Pereira 4d3e6400ab fixes power items tooltips 2024-12-01 05:30:46 -03:00
Paul f2e5ecef66 Conflicts begone 2024-12-01 17:00:03 +10:00
Paul ad9e85923a Merge remote-tracking branch 'origin/main' into more-npc-dakka
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonNetwork.kt
2024-12-01 16:57:06 +10:00
Paul 60d0c490dd Critical typo correction 2024-12-01 12:23:23 +10:00
Paul bf461297b2 Corrected multiple little inaccuracies around molang and flows 2024-12-01 12:09:11 +10:00
Hiroku 3ec89cd84e Merge branch 'beast-rod-line-fix' into 'main'
Fix fishing line color for beast rod

See merge request cable-mc/cobblemon!1339
2024-12-01 01:50:25 +00:00
Hiroku 6452521434 Merge branch 'held-items-tooltips' into 'main'
Held items, evolutions items and candies tooltips

See merge request cable-mc/cobblemon!1318
2024-12-01 01:39:30 +00:00
Guilherme PereiraandHiroku 5cbf961ac2 Held items, evolutions items and candies tooltips 2024-12-01 01:39:30 +00:00
Hiroku 6b473cffa3 Merge branch 'dex' into 'main'
refactor pokedex item model registration, update Pokedex rendering

See merge request cable-mc/cobblemon!1323
2024-12-01 01:38:41 +00:00
Nick f353548f9e Merge branch 'TheRaftShow' into 'main'
Battle Rafts

See merge request cable-mc/cobblemon!1338
2024-12-01 01:02:38 +00:00
JazzMcNadeandNick bba995f3d2 Battle Rafts 2024-12-01 01:02:38 +00:00
Sterrezo 5965d5a81b Psychiiiiiiic 2024-12-01 01:58:23 +01:00
Sterrezo 4e02ca310d Added sounds for Ember, fixed Ember missing an impact and corrected Psychic's timing again 2024-12-01 00:44:35 +01:00
Sterrezo 3260271d3c Adjusted Psychic 2024-11-30 23:58:20 +01:00
Sterrezo a77863adab Implemented sounds for new move particles 2024-11-30 18:07:30 +01:00
Sterrezo 455cfeb894 Missing sound fixes for particles 2024-11-30 17:29:42 +01:00
Plastered_Crab 46266c08ac Fix fishing line color for beast rod 2024-11-29 21:40:47 -08:00
Nick 19aa45e0af Merge branch 'bugfixes' into 'main'
Fishing bugfixes

See merge request cable-mc/cobblemon!1337
2024-11-30 03:26:06 +00:00
Nick 0fe8ebd008 Merge branch 'Offhand-bait-fixes' into 'main'
Fix offhand bait being applied

See merge request cable-mc/cobblemon!1336
2024-11-30 03:19:34 +00:00
Sterrezo 3e393546a2 Fishing Pokemon now uses up durability 2024-11-30 01:45:13 +01:00
Plastered_Crab e5995a5491 Fix offhand bait being applied
It was being applied still even when that bait was already on the bobber.
2024-11-29 16:31:25 -08:00
Sterrezo 7de083bf4f Fixed Fishermen selling 3 Pokerod smithing templates and capped max trades 2024-11-29 23:34:39 +01:00
Test 5f7451c284 rm unused files 2024-11-29 19:13:17 +00:00
Sterrezo fd3c614c2c Pokedex sound balancing
why did mojang hardcode this
2024-11-29 14:54:29 +01:00
Test 454c2c60ab merge from main 2024-11-28 17:35:37 -05:00
Test c581673ef1 Merge branch 'main' of https://gitlab.com/cable-mc/cobblemon into dex
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/client/gui/pokedex/PokedexGUI.kt
2024-11-28 17:19:53 -05:00
Test 09e29137fc check for view counter block in packet handler 2024-11-28 17:04:48 -05:00
Test fd1e5ef421 split lectern block entity into reuseable class 2024-11-28 16:38:39 -05:00
Test 8de05006f4 update dex rendering for player and blockEntities 2024-11-28 11:15:15 -05:00
Hiroku 7188978a66 Merge branch 'sound' into 'main'
Sounds for candy items + missing sound fixes + cleanup

See merge request cable-mc/cobblemon!1334
2024-11-28 11:08:28 +00:00
Hiroku f60fd26cbf Merge branch 'Lure_ball_tooltip_fix' into 'main'
Add correct Lure Ball tooltip now that fishing works :D

See merge request cable-mc/cobblemon!1335
2024-11-28 04:39:32 +00:00
Hiroku 3ba59d2165 Merge branch 'pokerod-durability-buff' into 'main'
Increase durability of Pokerods to be 256 instead of 64

See merge request cable-mc/cobblemon!1333
2024-11-28 04:24:44 +00:00
Plastered_Crab cd80598857 Add correct Lure Ball tooltip now that fishing works :D 2024-11-27 17:55:04 -08:00
Sterrezo 0978febaba Update CHANGELOG.md 2024-11-28 00:25:25 +01:00
Sterrezo bb67629fa5 Fixed Quick Attack and Paralysis missing sounds 2024-11-28 00:16:24 +01:00
Sterrezo d21039953f Update quickattack.json 2024-11-27 23:25:13 +01:00
Sterrezo 682f931f0f sounds.json fixes and duplicate asset removal 2024-11-27 23:16:00 +01:00
Plastered_Crab 3796997a12 Increase durability of Pokerods to be 256 instead of 64 2024-11-27 12:55:01 -08:00
Sterrezo b4cf49e1a8 Added a sound for using candy items
Closes a closed issue that requested sounds for candy items
2024-11-27 19:05:42 +01:00
Hiroku 2820989586 Merge branch 'ihatebugtypes' into 'main'
Ihatebugtypes

Closes #1174, #1180, and #1183

See merge request cable-mc/cobblemon!1313
2024-11-27 11:44:53 +00:00
Hiroku d37f6bc5a4 Merge branch 'pumpkins-can-move' into 'main'
re-added Pumpkaboo's moves

Closes #1202

See merge request cable-mc/cobblemon!1332
2024-11-27 11:44:23 +00:00
Hiroku 90d7fc3c2f Merge branch 'sterr/subtitles' into 'main'
Generic subtitles + bugfix

Closes #1200

See merge request cable-mc/cobblemon!1331
2024-11-27 11:44:03 +00:00
MoeBoy76 e2779c55f2 re-added Pumpkaboo's moves 2024-11-27 11:46:32 +11:00
Segfault Guy 349b4c28cc Merge remote-tracking branch 'origin/main' into ihatebugtypes 2024-11-26 13:49:07 -05:00
Sterrezo 7f2540a48f Added generic subtitles to block sounds 2024-11-26 18:02:22 +01:00
Sterrezo 367db9a368 Corrected subtitles for successful capture
Closes #1200
2024-11-26 17:53:16 +01:00
Paul 4f3eb7e4ba Event flows merged in and used for a few battle-related NPC presets, heaps more molang hooks 2024-11-26 21:57:55 +10:00
Hiroku c95f4cb9ad Merge branch 'implement_pokemon' into 'main'
Several fixes to some pokemon and redo of Magikarp jump forms to fit standards

Closes #1137

See merge request cable-mc/cobblemon!1329
2024-11-26 11:30:17 +00:00
Frank The FarmerandHiroku c0ccefd9b4 Several fixes to some pokemon and redo of Magikarp jump forms to fit standards 2024-11-26 11:30:17 +00:00
Nick f45a1681fa Merge branch 'pokerod-tooltip-changes' into 'main'
Add new tooltips for pokerods to show players how to add bait

See merge request cable-mc/cobblemon!1328
2024-11-25 21:09:40 +00:00
Plastered_Crab 12c820313a Add lang files for adding and removing bait tooltips 2024-11-25 13:01:28 -08:00
Nick e6b287434b Merge branch 'off-hand-fishing-bait-changes' into 'main'
Changes to how offhand bait is applied to rod

See merge request cable-mc/cobblemon!1327
2024-11-25 20:13:38 +00:00
Nick d39ee60158 Merge branch 'lucky-sea-rate-changes' into 'main'
Alter Luck of the Sea spawn bucket rates

See merge request cable-mc/cobblemon!1326
2024-11-25 20:12:05 +00:00
Plastered_Crab d405ede256 Changes to how offhand bait is applied to rod
- only 1 bait from off hand is applied
- if bait is already on the rod when applying from offhand, bait is dropped from rod before applying bait from offhand
2024-11-25 11:29:19 -08:00
Nick bbaf4332c6 Merge branch 'jumping-ancient-balls' into 'main'
Ancient Pokeball particles and animations.

See merge request cable-mc/cobblemon!1324
2024-11-25 19:20:20 +00:00
TyzillionandNick 954e690a20 Ancient Pokeball particles and animations. 2024-11-25 19:20:20 +00:00
Nick 0dda070c4c Merge branch 'sound' into 'main'
Poke Ball sound overhaul

See merge request cable-mc/cobblemon!1319
2024-11-25 19:17:41 +00:00
Plastered_Crab e855e0f1ae Alter Luck of the Sea bucket rates 2024-11-25 10:13:36 -08:00
Hiroku 0515b3204b Merge branch 'improve-dex-search' into 'main'
Add functionality to search by species, ability, or drops in dex

Closes #1129

See merge request cable-mc/cobblemon!1320
2024-11-25 12:00:28 +00:00
TestandHiroku 049c0c308a Add functionality to search by species, ability, or drops in dex 2024-11-25 12:00:27 +00:00
Hiroku e424bd76b5 Merge branch 'sterr/npc-sound' into 'main'
Click sound in NPC dialogue now plays at the correct pitch

See merge request cable-mc/cobblemon!1325
2024-11-25 03:54:50 +00:00
Sterrezo 0443aa5109 Click sound now plays at the correct pitch. That float is in fact not the volume. 2024-11-25 04:45:44 +01:00
Paul f9a3d2dc0e Merge remote-tracking branch 'origin/event-flows' into more-npc-dakka
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/Cobblemon.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonNetwork.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/events/pokemon/PokemonCapturedEvent.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/molang/MoLangFunctions.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/data/CobblemonDataProvider.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/entity/pokemon/PokemonEntity.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/pokemon/Pokemon.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/util/MoLangExtensions.kt
2024-11-24 21:07:50 +10:00
Paul f817c18f4f Added NPC variable editing in NPC config menu 2024-11-24 21:02:38 +10:00
Test bd801b1517 refactor pokedex item model registration 2024-11-23 23:08:47 -05:00
Hiroku 0f17d30ece Merge branch 'Extreme-Tower-Makeover' into 'main'
Giving the Frozen Gimmi tower a new look & making it so Breezes sometimes...

See merge request cable-mc/cobblemon!1322
2024-11-23 10:39:20 +00:00
Hiroku f54aed0953 Merge branch 'particle-packet-expansion' into 'main'
[1.6] Expand SpawnSnowstormEntityPacket capabilties to support non-posable entities at a basic level

See merge request cable-mc/cobblemon!1309
2024-11-23 10:37:18 +00:00
Slippy 2b5a16d30f Giving the Frozen Gimmi tower a new look & making it so Breezes sometimes spawn on top instead of Evokers (Don't fall) 2024-11-23 02:11:03 -06:00
Slippy f1ed9b010e Merge branch 'main' of https://gitlab.com/cable-mc/cobblemon into 1.6-Bug-Testing 2024-11-23 01:58:17 -06:00
Paul e8dcd994be Fixed fish baits. Fished fix baits? Bished fix fates 2024-11-23 16:27:33 +10:00
Hiroku da371fd940 Merge branch 'fishingrod-crash-fix' into 'main'
Fix drowned crashing game when holding fishing rod

See merge request cable-mc/cobblemon!1321
2024-11-23 05:56:47 +00:00
mia 0cdeab0800 check livingEntity instanceof Player before casting 2024-11-22 20:14:29 -05:00
Sterrezo eca3c582aa Added sounds for Poke Balls landing on the ground during capture, updated sounds with new iterations 2024-11-22 14:32:54 +01:00
Sterrezo 8dd09a2e14 Merge branch 'main' into sound 2024-11-22 13:16:41 +01:00
Hiroku a96880b8ed Merge branch 'more-npc-dakka' into 'main'
Added panic AI for NPCs and much more molang hooking

See merge request cable-mc/cobblemon!1303
2024-11-22 10:15:33 +00:00
Hiroku 40de8b3ab9 Merge branch 'dex-fix' into 'main'
Fix dex search crash + add text cursor functionality

Closes #1185

See merge request cable-mc/cobblemon!1317
2024-11-22 08:59:37 +00:00
TestandHiroku 6eb814a7f0 Fix dex search crash + add text cursor functionality 2024-11-22 08:59:37 +00:00
Paul 2f2fd3b17e Fixed a couple of molang functions, added level support and fixes to NPC spawn files 2024-11-22 17:21:20 +10:00
Hiroku 4234f9be67 Merge branch 'fishing_particles' into 'main'
Fishing Particles

See merge request cable-mc/cobblemon!1316
2024-11-22 06:46:43 +00:00
Tyzillion f5766cdad4 Milliseconds? More like LITERAL TICKS. Lime disease... 2024-11-22 01:29:17 -05:00
Tyzillion 8c11ef9c00 System time? More like fishing time. Amiright? 2024-11-22 01:15:41 -05:00
Tyzillion 78ac8542f6 Added new particles to Poke fishing. 2024-11-22 00:51:55 -05:00
Segfault Guy 8c1d03359a Fix deserialization of IVs and EVs for PokemonProperties. 2024-11-21 22:19:01 -05:00
amo bb574e58d8 Merge branch 'main' into particle-packet-expansion 2024-11-22 00:51:23 +00:00
amo 5c3c7b4195 🍋‍🟩 Chore: Oops! v2 2024-11-22 00:50:37 +00:00
Hiroku ddec062e87 Merge branch 'experience-fix' into 'main'
Stops rewarding Pokemon EXP upon forfeiting NPC battles.

See merge request cable-mc/cobblemon!1278
2024-11-22 00:07:51 +00:00
Conan 3322f3ffb6 Changelog addition 2024-11-22 01:05:00 +01:00
Hiroku ab1ec11c26 Merge branch 'sterr/bugfixes' into 'main'
Sound and music bugfixes

Closes #1161 and #1172

See merge request cable-mc/cobblemon!1299
2024-11-21 23:49:19 +00:00
Hiroku 36aaa97029 Merge branch 'small-bugfixes' into 'main'
Small bugfixes

Closes #1099 and #1156

See merge request cable-mc/cobblemon!1284
2024-11-21 23:48:39 +00:00
Hiroku 6b3443fbd2 Merge branch 'berries-tooltips' into 'main'
Add tooltips for Pomeg, Tamato, Grepa, Kelpsy, Hondew and Qualot berries

See merge request cable-mc/cobblemon!1308
2024-11-21 23:46:56 +00:00
Hiroku 268a1ab311 Merge branch 'feed-display-case-from-underneath' into 'main'
Add support for feeding display case items from underneath

Closes #1177

See merge request cable-mc/cobblemon!1307
2024-11-21 23:42:45 +00:00
Paul 40957b6eac Added defaults to new PermissionValidator functions 2024-11-22 09:37:12 +10:00
Sterrezo d67be68b4b Changelog, updated break out animation (thanks Torch!), new break out sound for ancient Pokeballs 2024-11-22 00:29:52 +01:00
Hiroku f6f34a2b10 Merge branch 'npc-no-pain-no-gain' into 'main'
Added NPC properties: isInvulnerable, isLeashable, and allowProjectileHits. (no pain no gain)

Closes #1179

See merge request cable-mc/cobblemon!1306
2024-11-21 23:18:28 +00:00
Hiroku b2f71d305f Update SpawnSnowstormEntityParticleHandler.kt 2024-11-21 23:12:05 +00:00
Hiroku 51a8c31997 Merge branch 'npc-kill-command' into 'main'
Added a command to delete NPCs

See merge request cable-mc/cobblemon!1315
2024-11-21 23:10:40 +00:00
Conan 67ae5ff155 Added a command to delete NPCs 2024-11-22 00:06:07 +01:00
Hiroku 9e0cf03d8e Merge branch 'fix-mega-event-spelling-error' into 'main'
Fix spelling error in mega evolution event

See merge request cable-mc/cobblemon!1314
2024-11-21 22:13:25 +00:00
Conan 6a076d36f9 "isInvincible" != "isInvulnerable" 2024-11-21 20:28:54 +01:00
Sterrezo 4314a0b506 Major rework of Pokeball sound implementation and new sounds 2024-11-21 20:18:28 +01:00
amo 77490b5084 🍋‍🟩 Chore: Oops! 2024-11-21 14:15:44 +00:00
Segfault Guy 109eecd4fe Merge remote-tracking branch 'origin/main' into ihatebugtypes 2024-11-20 23:00:34 -05:00
Segfault Guy d6353aa2c7 Remedy for remedy. 2024-11-20 22:44:04 -05:00
Test fd0e741a25 update ui, fix renders in dex from clipping into background 2024-11-20 22:39:50 -05:00
Segfault Guy b0e0a06e9a Fix battle music race condition on forceBattle. 2024-11-20 21:00:16 -05:00
Conan 1c569ac5cf Added property descriptions 2024-11-20 17:01:14 +01:00
Guilherme Pereira 5ff5de8d14 re-add tooltip 'Cannot be eaten by holder' on another line for pomeg, tamato, grepa, kelpsy, hondew and qualot berries 2024-11-20 07:58:41 -03:00
Hiroku 881e1dc1a6 Merge branch 'hatenna-line-files-wrong-dex-numbers' into 'main'
Rename Hatenna line folders to match correct pokedex numbers

Closes #1173

See merge request cable-mc/cobblemon!1310
2024-11-20 09:20:37 +00:00
Hiroku 09b9fade87 Merge branch 'feature/fix-gui-mixin' into 'main'
fix gui mixin to prevent accidental 3rd person HUD disappearances

See merge request cable-mc/cobblemon!1312
2024-11-20 09:18:25 +00:00
Segfault Guy acd3b7cade Fix activation messages on ability suppressing moves. 2024-11-19 22:28:48 -05:00
Polymeta 482a0015bc fix gui mixin to prevent accidental 3rd person HUD disappearances
Signed-off-by: Polymeta <40173118+Polymeta@users.noreply.github.com>
2024-11-20 02:13:40 +00:00
Guilherme Pereira b881c96630 add changelog for feeding display case items from underneath fix 2024-11-19 20:09:25 -03:00
Hiroku 79cd1f5224 Merge branch 'init-events' into 'main'
emit(Unit), not just emit(), otherwise theres no data to post, duh

See merge request cable-mc/cobblemon!1311
2024-11-19 21:18:01 +00:00
Xander Forrest f369877c10 emit(Unit), not just emit(), otherwise theres no data to post, duh 2024-11-19 18:52:03 +00:00
Guilherme Pereira 1f1ff0e4fd rename hatenna, hattrem and hatterene folders to match correct pokedex numbers 2024-11-19 13:55:28 -03:00
amo 0a34e51e07 🍋‍🟩 Chore: Changelog 2024-11-19 15:51:45 +00:00
amo 741530eb43 🍋‍🟩 Feature: Expand SpawnSnowstormEntityParticleHandler to support non-bedrock entities at a very basic level 2024-11-19 15:44:02 +00:00
Guilherme Pereira 6c9c5a93f1 add tooltips for pomeg, tamato, grepa, kelpsy, hondew and qualot berries 2024-11-19 11:43:35 -03:00
Guilherme Pereira ddbf6d7345 add support for feeding display case items from underneath 2024-11-19 09:52:38 -03:00
Hiroku 0da09c767c Merge branch 'evolution_tweaks' into 'main'
Edited the obscuring evolution particles to better obscure pokemon based on hitbox volume and scale.

See merge request cable-mc/cobblemon!1305
2024-11-19 09:36:18 +00:00
Conan fb39ff8657 Added NPC properties: isInvulnerable, isLeashable, and allowProjectileHits. 2024-11-19 08:41:10 +01:00
Conan dd516c153e Added NPC properties: isInvulnerable, isLeashable, and allowProjectileHits. 2024-11-19 08:39:14 +01:00
Tyzillion 739d31a9ca I'M LOSING MY MIND... Anyways, more tweaks to the obscure particle. 2024-11-18 23:45:22 -05:00
Tyzillion a5675aedcb Tweaked the formula further to better account for middle stage evos. 2024-11-18 22:46:00 -05:00
Tyzillion 196bf42fb6 Tweaked obscuring molang formula to better obscure Pokemon based on hitbox volume and scale. 2024-11-18 22:02:34 -05:00
Test 1d01a2bf1f fix species name not being translated when scanned 2024-11-18 19:23:28 -05:00
Paul ce6087f368 Added panic AI for NPCs and much more molang hooking 2024-11-18 13:28:50 +10:00
mia f706dcda26 clarify ItemPropertiesMixin inject location 2024-11-17 20:45:59 -05:00
Segfault Guy 8e46a55cbd Merge remote-tracking branch 'origin/main' into ihatebugtypes 2024-11-17 19:12:41 -05:00
Hiroku d3ae4cd3e7 Merge branch '1.6-spawns' into 'main'
Updated spawn files. Edited ocean_monument preset. Edited is_lush, is_ocean,...

See merge request cable-mc/cobblemon!1293
2024-11-17 09:25:02 +00:00
Hiroku b41b496b80 Merge branch 'bugfix/dexo-fixo' into 'main'
Fixed dex errors caused by /giveallpokemon and other random catches & command permission

See merge request cable-mc/cobblemon!1301
2024-11-17 09:17:30 +00:00
Hiroku 9cd3ded705 Merge branch 'summary-updates' into 'main'
Summary updates

See merge request cable-mc/cobblemon!1300
2024-11-17 09:15:50 +00:00
TestandHiroku ca4b50d727 Summary updates 2024-11-17 09:15:50 +00:00
Paul f6bc3f662a PokedexCommand permission tweak 2024-11-17 13:23:07 +10:00
Paul 7ab621e5b9 Fixed dex errors caused by /giveallpokemon and other random catches 2024-11-17 13:22:55 +10:00
plastered_crab 4e63cb0bb3 Remove Tympole, Palpitoad, Seismitoad spawn pool world files 2024-11-16 18:15:07 -08:00
plastered_crab 671c7adb05 Tweak incorrect data for Magikarp and Spiritomb spawns 2024-11-16 18:13:14 -08:00
Sterrezo edbe539537 Merge branch 'main' into sterr/bugfixes 2024-11-17 03:05:56 +01:00
Sterrezo e20728faa0 Made owned shiny ambient sounds actually audible 2024-11-17 03:05:28 +01:00
Sterrezo 5a2f948acb Fixed music not playing when fishing or in npc battles
Doozy. Basically, using forceBattle from PokemonEntity.kt makes InitializeInstruction.kt send a packet with battle music before battleTheme is assigned in PlayerBattleActor.kt.
There's likely a more sophisticated way to fix this, but this works.
Closes #1172 and #1161
2024-11-17 03:04:00 +01:00
Hiroku 8a1faf3270 Merge branch 'apion-bugfix' into 'main'
Fix issue with evo progression not allowing 0.

See merge request cable-mc/cobblemon!1298
2024-11-16 22:55:55 +00:00
Hiroku d0b148a91a Merge branch 'cc-compat' into 'main'
Set cobblemon data version on genned chunks

See merge request cable-mc/cobblemon!1297
2024-11-16 22:52:51 +00:00
apion f6f88f5eef Revert "Added additional checks"
This reverts commit 3f36624ba7.
2024-11-16 16:19:48 -05:00
apion 3f36624ba7 Added additional checks 2024-11-16 15:22:08 -05:00
apion 7e256e8381 Fix issue with evo progression not allowing 0. 2024-11-16 12:24:59 -05:00
apion ad1aa9ad47 Licenses :( 2024-11-16 11:31:11 -05:00
apion b4e403a701 Removed unnecessary inject 2024-11-16 11:27:00 -05:00
apion 08557913d8 Set cobblemon data version on genned chunks 2024-11-16 11:22:42 -05:00
Hiroku 830c409a06 Merge branch 'non-pushable-npcs' into 'main'
Added a flag to prevent NPCs from being pushed.

See merge request cable-mc/cobblemon!1281
2024-11-16 12:59:34 +00:00
Hiroku 5e2ef6b5fb Merge branch 'main' into 'non-pushable-npcs'
# Conflicts:
#   common/src/main/kotlin/com/cobblemon/mod/common/util/DataKeys.kt
2024-11-16 12:59:16 +00:00
Conan a05f4edc82 Added a flag to prevent NPCs from being pushed (part 3). 2024-11-16 12:28:07 +01:00
Paul 97a3dc4d61 Treat NPC skill as a proper optional property on the entity instead of duplicating class data 2024-11-16 14:22:40 +10:00
Hiroku 768789d998 Merge branch 'feature/small-fixes' into 'main'
no more caffeine for shouldered pokemon

Closes #1146 and #1162

See merge request cable-mc/cobblemon!1277
2024-11-16 04:14:48 +00:00
PolymetaandHiroku bfa58e9fb9 no more caffeine for shouldered pokemon 2024-11-16 04:14:48 +00:00
Hiroku 9a514477c5 Merge branch 'trading-display-names' into 'main'
Display correct usernames in the TradeGUI

Closes #1168

See merge request cable-mc/cobblemon!1294
2024-11-16 04:12:16 +00:00
Paul 7623452f4a Fixed spectating not being an option in interact wheel (lol) 2024-11-16 13:47:15 +10:00
Test 72635d0f2f genericize example names 2024-11-15 22:39:11 -05:00
Paul 34e581a470 Merge remote-tracking branch 'origin/main' 2024-11-16 13:09:28 +10:00
Paul 5444d67c21 Added heaps of documentation on brain AI stuff for NPCs 2024-11-16 13:09:09 +10:00
Sterrezo 4c8c0fd063 Shiny Pokemon now play the shiny sendout sound when breaking out of a Pokeball instead of the notification 2024-11-16 03:01:07 +01:00
Sterrezo 73209ab6b5 Shiny notification and owned ambient particles no longer play in spectator 2024-11-16 02:35:09 +01:00
Hiroku 539331bdc7 Merge branch 'bait-tooltip-fixes' into 'main'
Changed fishing bait to use a codec instead of nbt stuff. Fixed issue where...

See merge request cable-mc/cobblemon!1296
2024-11-15 22:37:26 +00:00
apion 0d0598d68c Changed fishing bait to use a codec instead of nbt stuff. Fixed issue where bait tooltips don't work on servers 2024-11-15 06:06:03 -05:00
Conan 08d621ca5e Display correct usernames in the TradeGUI 2024-11-15 00:03:41 +01:00
mia 6dfb1323b7 cleanup if statement logic 2024-11-14 08:49:01 -05:00
mia 973e730b48 license + remove unused imports 2024-11-14 08:42:36 -05:00
mia bbb06677b9 fix: uncast rods (both pokerods and vanilla) appearing as cast when dual-wielding 2024-11-13 09:19:20 -05:00
mia 8a5279741d fix bobber line being drawn incorrectly sometimes (fixes #1099) 2024-11-13 09:18:30 -05:00
Segfault Guy fc8b6fb54b Fix illusion not copying caught ball. 2024-11-12 22:09:36 -05:00
mia 9e22b662a4 remove duplicate sand_attack sounds and re-add to particles (fixes #1156) 2024-11-12 15:23:22 -05:00
Conan d731bde915 Added a flag to prevent NPCs from being pushed (part 2). 2024-11-12 12:48:34 +01:00
Conan 6ecdf25821 Added a flag to prevent NPCs from being pushed. 2024-11-12 03:09:28 +01:00
Conan 77c3f12634 Stops rewarding Pokemon EXP upon forfeiting NPC battles. 2024-11-11 18:52:35 +01:00
Segfault Guy d13d3fb137 licenses? who cares. 2024-11-10 21:43:51 -05:00
Segfault Guy f436b4a738 Merge remote-tracking branch 'origin/main' into ihatebugtypes 2024-11-10 21:43:00 -05:00
Segfault Guy 9e1a1c957b Tweak scheduler functions to work with new TickRateManager changes. 2024-11-10 21:20:59 -05:00
Segfault Guy e04155094b Fix trade parties being mirrored. 2024-11-10 21:03:24 -05:00
Segfault Guy 188115bf15 Boost effects will only play if stat stages change. 2024-11-10 20:55:35 -05:00
Segfault Guy 1d99037005 casting sounds are started by the client on bobber spawn. please dont start them on the server. 2024-11-10 15:47:07 -05:00
Paul a7e6e638fe Added run_command molang function to general query structs and player structs 2024-05-20 09:10:04 +10:00
Paul 9ef3ea2932 Use context for flows because it's cooler, added more functions to pokemon related molang 2024-05-19 19:08:56 +10:00
Paul 38dcee3327 Merge remote-tracking branch 'origin/sega_bass_pro_fishing_2' into event-flows
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonNetwork.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/entity/pokeball/EmptyPokeBallEntity.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/util/MoLangExtensions.kt
2024-05-19 11:27:18 +10:00
Paul 12b92d3f35 Added flow datapackable thing 2024-02-24 11:58:57 +10:00
Paul dacfb4f1ff Merge branch 'npcs' into event-flows
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/entity/pokeball/EmptyPokeBallEntity.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/util/MoLangExtensions.kt
2024-02-24 10:02:26 +10:00
Paul 4086ba73d3 Some structs for things 2024-02-24 09:59:27 +10:00
570 changed files with 11503 additions and 5472 deletions
+35 -8
View File
@@ -1,15 +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.
- Poké Dexes can be placed on lecterns.
- 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.
@@ -27,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.
@@ -35,20 +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 a map to a shipwreck cove and a pokerod smithing template.
- 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
@@ -325,8 +329,24 @@
- 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 erroneously playing when a wild Pokémon breaks out of a Poké ball.
@@ -376,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.
@@ -402,6 +423,10 @@
- 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`.
@@ -438,6 +463,7 @@
- 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.
@@ -454,6 +480,7 @@
- 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.
@@ -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);
}
}
}
@@ -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);
}
}
@@ -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);
}
}
}
@@ -81,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
@@ -286,7 +287,9 @@ object Cobblemon {
CustomPokemonProperty.register(FreezeFrameProperty)
CustomPokemonProperty.register(NoAIProperty)
CobblemonEvents.POKEMON_PROPERTY_INITIALISED.emit()
CobblemonEvents.POKEMON_PROPERTY_INITIALISED.emit(Unit)
FlowHandler.setup()
ifDedicatedServer {
isDedicatedServer = true
@@ -389,7 +392,7 @@ object Cobblemon {
registerEventHandlers()
CobblemonEvents.COBBLEMON_INITIALISED.emit()
CobblemonEvents.COBBLEMON_INITIALISED.emit(Unit)
}
@@ -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_RED = pokedexItem(PokedexTypes.RED)
val POKEDEX_RED = pokedexItem(PokedexType.RED)
@JvmField
val POKEDEX_YELLOW = pokedexItem(PokedexTypes.YELLOW)
val POKEDEX_YELLOW = pokedexItem(PokedexType.YELLOW)
@JvmField
val POKEDEX_GREEN = pokedexItem(PokedexTypes.GREEN)
val POKEDEX_GREEN = pokedexItem(PokedexType.GREEN)
@JvmField
val POKEDEX_BLUE = pokedexItem(PokedexTypes.BLUE)
val POKEDEX_BLUE = pokedexItem(PokedexType.BLUE)
@JvmField
val POKEDEX_PINK = pokedexItem(PokedexTypes.PINK)
val POKEDEX_PINK = pokedexItem(PokedexType.PINK)
@JvmField
val POKEDEX_BLACK = pokedexItem(PokedexTypes.BLACK)
val POKEDEX_BLACK = pokedexItem(PokedexType.BLACK)
@JvmField
val POKEDEX_WHITE = pokedexItem(PokedexTypes.WHITE)
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,10 +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.*
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
@@ -110,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
@@ -125,7 +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.*
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
@@ -139,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
@@ -156,19 +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.serverhandling.trade.AcceptTradeRequestHandler
import com.cobblemon.mod.common.net.serverhandling.trade.CancelTradeHandler
import com.cobblemon.mod.common.net.serverhandling.trade.ChangeTradeAcceptanceHandler
import com.cobblemon.mod.common.net.serverhandling.trade.OfferTradeHandler
import com.cobblemon.mod.common.net.serverhandling.trade.UpdateTradeOfferHandler
import com.cobblemon.mod.common.net.messages.server.battle.*
import com.cobblemon.mod.common.net.serverhandling.battle.*
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
@@ -306,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))
@@ -356,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
}
@@ -414,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))
@@ -423,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()
@@ -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"
]
}
}
]
```
@@ -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()
}
}
@@ -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) } }
}
}
}*/
}
}
@@ -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
)
}
}
@@ -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
)
}
}
@@ -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
)
}
@@ -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(
@@ -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
}
}
}
}
@@ -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
}
}
}
}
@@ -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
}
}
@@ -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
}
}
@@ -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
}
}
}
}
}
@@ -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,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
@@ -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
}
}
}
}
@@ -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()
@@ -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())
@@ -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
/**
@@ -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()
@@ -100,7 +100,7 @@ object CobblemonEvents {
// instructions
@JvmField
val MEGA_EVOLUTON = EventObservable<MegaEvolutionEvent>()
val MEGA_EVOLUTION = EventObservable<MegaEvolutionEvent>()
@JvmField
val TERASTALLIZATION = EventObservable<TerastallizationEvent>()
@JvmField
@@ -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
@@ -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 }
)
}
@@ -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)
)
}
@@ -13,12 +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.nbt.StringTag
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
@@ -34,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()
@@ -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
@@ -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
}
)
@@ -256,6 +460,17 @@ object MoLangFunctions {
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) {
@@ -284,18 +499,18 @@ 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
@@ -312,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
}
)
@@ -330,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("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
}
)
@@ -352,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
}
)
@@ -362,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,
@@ -385,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,
@@ -395,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,
@@ -404,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,
@@ -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,9 +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 = false
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
@@ -84,6 +88,10 @@ class NPCClass {
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) {
@@ -129,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)
@@ -9,10 +9,12 @@
package com.cobblemon.mod.common.api.npc
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.NPCPartyStore
import com.cobblemon.mod.common.entity.npc.NPCEntity
import com.google.gson.JsonElement
import net.minecraft.server.level.ServerPlayer
/**
* A provider of a party for battling the NPC. Completely custom party providers will only display
@@ -25,13 +27,14 @@ interface NPCPartyProvider {
companion object {
val types = mutableMapOf<String, (String) -> NPCPartyProvider>(
SimplePartyProvider.TYPE to { SimplePartyProvider() },
PoolPartyProvider.TYPE to { PoolPartyProvider() }
PoolPartyProvider.TYPE to { PoolPartyProvider() },
ScriptPartyProvider.TYPE to { ScriptPartyProvider() }
)
}
val type: String
val isStatic: Boolean
fun provide(npc: NPCEntity, level: Int): NPCPartyStore
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)
}
@@ -31,6 +31,10 @@ class NPCPreset {
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 }
@@ -51,5 +55,9 @@ class NPCPreset {
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).
@@ -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))
}
}
@@ -10,11 +10,14 @@ 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
@@ -23,6 +26,7 @@ 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
@@ -58,8 +62,8 @@ class PoolPartyProvider : NPCPartyProvider {
if (it is JsonPrimitive) {
return@map DynamicPokemon(
PokemonProperties.parse(it.asString),
0..0,
0..100,
"0".asExpression(),
1..100,
"1".asExpression(),
"1".asExpression()
)
@@ -68,8 +72,8 @@ class PoolPartyProvider : NPCPartyProvider {
it as JsonObject
DynamicPokemon(
PokemonProperties.parse(it.getAsJsonPrimitive("pokemon").asString),
it.getAsJsonPrimitive("levelVariation")?.asString?.split("-")?.let { it[0].toInt()..it[1].toInt() } ?: 0..0,
it.getAsJsonPrimitive("npcLevels")?.asString?.split("-")?.let { it[0].toInt()..it[1].toInt() } ?: 0..100,
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()
)
@@ -79,7 +83,7 @@ class PoolPartyProvider : NPCPartyProvider {
class DynamicPokemon(
val pokemon: PokemonProperties,
val levelVariation: IntRange,
val levelVariation: Expression,
val npcLevels: IntRange,
val selectableTimes: Expression,
val weight: Expression = "1".asExpression()
@@ -93,16 +97,22 @@ class PoolPartyProvider : NPCPartyProvider {
}
}
fun formulateParty(npc: NPCEntity, level: Int, party: NPCPartyStore) {
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 }.toMutableList()
val workingPool = pool.filter { level in it.npcLevels && runtime.resolveInt(it.selectableTimes) > 0 }.toMutableList()
val useCounts = mutableMapOf<DynamicPokemon, Int>()
while (desiredPokemonCount > 0 && workingPool.filter { it.hasWeight(runtime) }.isNotEmpty()) {
while (desiredPokemonCount > 0 && workingPool.any { it.hasWeight(runtime) }) {
val selected = workingPool.filter { it.getWeight(runtime) == -1F }.randomOrNull()
?: workingPool.weightedSelection { it.getWeight(runtime) }
?: break
@@ -114,15 +124,16 @@ class PoolPartyProvider : NPCPartyProvider {
desiredPokemonCount--
// If the Pokémon's props specifies a level then use that, otherwise choose a random level within the range
val randomLevel = (level + selected.levelVariation.random())
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): NPCPartyStore {
override fun provide(npc: NPCEntity, level: Int, players: List<ServerPlayer>): NPCPartyStore {
val party = NPCPartyStore(npc)
formulateParty(npc, level, party)
formulateParty(npc, level, players, party)
return party
}
}
@@ -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
}
}
@@ -14,6 +14,7 @@ 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 [NPCPartyStore] based on a simple list of [PokemonProperties].
@@ -52,7 +53,7 @@ class SimplePartyProvider : NPCPartyProvider {
}
}
override fun provide(npc: NPCEntity, level: Int): NPCPartyStore {
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)
@@ -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 }
}
}
}
@@ -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)))
}
@@ -133,7 +133,7 @@ object CatchRateModifiers {
*/
val LURE = WorldStateModifier { _, entity ->
if (FishingSpawnCause.FISHED_ASPECT in entity.aspects)
4f
4F
else
1F
}
@@ -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
@@ -76,6 +76,12 @@ class FormDexRecord {
.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 ->
@@ -107,7 +111,6 @@ class SpeciesDexRecord {
return formRecords.getOrPut(formName.lowercase()) {
val record = FormDexRecord()
record.initialize(this, formName)
onFormRecordUpdated(record)
// 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
@@ -0,0 +1,15 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of 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.pokedex.filter
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)
}
}
}
}
@@ -36,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
@@ -59,7 +59,6 @@ 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.entity.player.Player
import net.minecraft.world.item.ItemStack
import net.minecraft.world.level.Level
@@ -187,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()
}
}
@@ -657,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
@@ -722,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
@@ -790,7 +789,7 @@ 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
@@ -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
}
@@ -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
@@ -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
}
@@ -8,10 +8,10 @@
package com.cobblemon.mod.common.api.spawning.detail
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.spawning.context.SpawningContext
import com.cobblemon.mod.common.entity.npc.NPCEntity
import com.cobblemon.mod.common.util.party
import net.minecraft.server.level.ServerPlayer
import com.cobblemon.mod.common.util.resolveInt
/**
* A [SpawnAction] for creating [NPCEntity]s.
@@ -19,13 +19,15 @@ import net.minecraft.server.level.ServerPlayer
* @author Hiroku
* @since October 8th, 2023
*/
class NPCSpawnAction(ctx: SpawningContext, override val detail: NPCSpawnDetail) : SpawnAction<EntitySpawnResult>(ctx, detail) {
override fun run(): EntitySpawnResult {
class NPCSpawnAction(ctx: SpawningContext, override val detail: NPCSpawnDetail) : SingleEntitySpawnAction<NPCEntity>(ctx, detail) {
override fun createEntity(): NPCEntity {
val npc = NPCEntity(ctx.world)
npc.npc = detail.npcClass
npc.appliedAspects.addAll(detail.aspects)
val seedLevel = (ctx.cause.entity as? ServerPlayer)?.let { it.party().maxOfOrNull { it.level } } ?: 10
val minLevel = ctx.runtime.resolveInt(detail.minLevel).coerceIn(1, Cobblemon.config.maxPokemonLevel)
val maxLevel = ctx.runtime.resolveInt(detail.maxLevel).coerceIn(1, Cobblemon.config.maxPokemonLevel)
val seedLevel = (minLevel..maxLevel).random()
npc.initialize(seedLevel)
return EntitySpawnResult(listOf(npc))
return npc
}
}
@@ -8,10 +8,11 @@
package com.cobblemon.mod.common.api.spawning.detail
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.api.npc.NPCClass
import com.cobblemon.mod.common.api.npc.NPCConfiguration
import com.cobblemon.mod.common.api.spawning.context.SpawningContext
import com.cobblemon.mod.common.entity.npc.NPCEntity
import com.cobblemon.mod.common.util.asExpression
import com.google.gson.annotations.SerializedName
/**
@@ -30,8 +31,9 @@ class NPCSpawnDetail : SpawnDetail() {
@SerializedName(value = "npcClass", alternate = ["class", "npc"])
val npcClass: NPCClass = blankClass
val configuration: NPCConfiguration? = null
val aspects: Set<String> = emptySet()
val minLevel: Expression = "1".asExpression()
val maxLevel: Expression = "100".asExpression()
override fun doSpawn(ctx: SpawningContext) = NPCSpawnAction(ctx, this)
}
@@ -9,6 +9,7 @@
package com.cobblemon.mod.common.api.storage.party
import com.cobblemon.mod.common.CobblemonNetwork.sendPacket
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.api.reactive.Observable
import com.cobblemon.mod.common.api.reactive.Observable.Companion.stopAfter
import com.cobblemon.mod.common.api.reactive.SimpleObservable
@@ -25,11 +26,11 @@ import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.DataKeys
import com.cobblemon.mod.common.util.server
import com.google.gson.JsonObject
import java.util.Collections
import java.util.UUID
import net.minecraft.core.RegistryAccess
import net.minecraft.nbt.CompoundTag
import net.minecraft.server.level.ServerPlayer
import java.util.Collections
import net.minecraft.core.RegistryAccess
/**
* A [PokemonStore] for a party of Pokémon. This is a simple structure that by default will hold 6 nullable slots of Pokémon.
@@ -47,6 +48,8 @@ open class PartyStore(override val uuid: UUID) : PokemonStore<PartyPosition>() {
/** A list of player UUIDs representing players that are observing this store. This is NOT serialized/deserialized. */
var observerUUIDs = mutableListOf<UUID>()
val struct = asMoLangValue()
override fun iterator() = slots.filterNotNull().iterator()
/** Gets the Pokémon at the specified slot. It will return null if the slot is empty or the given slot is out of bounds. */
fun get(slot: Int) = slot.takeIf { it < slots.size && it >= 0 }?.let { slots[it] }
@@ -9,6 +9,7 @@
package com.cobblemon.mod.common.api.storage.pc
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.api.storage.BottomlessStore
import com.cobblemon.mod.common.api.storage.PokemonStore
@@ -23,11 +24,11 @@ import com.cobblemon.mod.common.util.DataKeys
import com.cobblemon.mod.common.util.getPlayer
import com.cobblemon.mod.common.util.lang
import com.google.gson.JsonObject
import java.util.UUID
import net.minecraft.core.RegistryAccess
import net.minecraft.nbt.CompoundTag
import net.minecraft.network.chat.MutableComponent
import net.minecraft.server.level.ServerPlayer
import java.util.*
import net.minecraft.core.RegistryAccess
/**
* The store used for PCs. It is divided into some number of [PCBox]es, and can
@@ -54,6 +55,9 @@ open class PCStore(
override fun iterator() = boxes.flatMap { it.toList() }.iterator()
override fun getObservingPlayers() = observingUUIDs.mapNotNull { it.getPlayer() }
val struct = asMoLangValue()
fun addObserver(player: ServerPlayer) {
observingUUIDs.add(player.uuid)
sendTo(player)
@@ -105,6 +105,9 @@ object BattleBuilder {
}
}
player1Actor.battleTheme = player2.getBattleTheme()
player2Actor.battleTheme = player1.getBattleTheme()
return if (errors.isEmpty) {
var result: BattleStartResult = errors
BattleRegistry.startBattle(
@@ -112,8 +115,6 @@ object BattleBuilder {
side1 = BattleSide(player1Actor),
side2 = BattleSide(player2Actor)
).ifSuccessful {
player1Actor.battleTheme = player2.getBattleTheme()
player2Actor.battleTheme = player1.getBattleTheme()
it.battlePartyStores.addAll(battlePartyStores)
result = SuccessfulBattleStart(it)
}
@@ -197,19 +198,18 @@ object BattleBuilder {
playerActors.swap(2,3)
}
// TODO: less hard coding
playerActors[0].battleTheme = players[2].getBattleTheme()
playerActors[1].battleTheme = players[2].getBattleTheme()
playerActors[2].battleTheme = players[0].getBattleTheme()
playerActors[3].battleTheme = players[0].getBattleTheme()
return if (errors.isEmpty) {
BattleRegistry.startBattle(
battleFormat = battleFormat,
side1 = BattleSide(playerActors[0], playerActors[1]),
side2 = BattleSide(playerActors[2], playerActors[3])
).ifSuccessful {
// TODO: less hard coding
playerActors[0].battleTheme = players[2].getBattleTheme()
playerActors[1].battleTheme = players[2].getBattleTheme()
playerActors[2].battleTheme = players[0].getBattleTheme()
playerActors[3].battleTheme = players[0].getBattleTheme()
it.battlePartyStores.addAll(battlePartyStores)
}
errors
@@ -272,6 +272,8 @@ object BattleBuilder {
errors.participantErrors[wildActor] += BattleStartError.alreadyInBattle(wildActor)
}
playerActor.battleTheme = pokemonEntity.getBattleTheme()
return if (errors.isEmpty) {
var result: BattleStartResult = errors
BattleRegistry.startBattle(
@@ -282,7 +284,6 @@ object BattleBuilder {
if (!cloneParties) {
pokemonEntity.battleId = it.battleId
}
playerActor.battleTheme = pokemonEntity.getBattleTheme()
result = SuccessfulBattleStart(it)
}
result
@@ -314,7 +315,7 @@ object BattleBuilder {
): BattleStartResult {
val playerTeam = party.toBattleTeam(clone = cloneParties, healPokemon = healFirst, leadingPokemon = leadingPokemon)
val playerActor = PlayerBattleActor(player.uuid, playerTeam)
val npcParty = npcEntity.getPartyForChallenge(player)
val npcParty = npcEntity.getPartyForChallenge(listOf(player))
val errors = ErroredBattleStart()
if (playerActor.pokemonList.size < battleFormat.battleType.slotsPerActor) {
@@ -347,6 +348,8 @@ object BattleBuilder {
)
}
playerActor.battleTheme = npcEntity.getBattleTheme()
return if (errors.isEmpty) {
var result: BattleStartResult = errors
BattleRegistry.startBattle(
@@ -354,7 +357,6 @@ object BattleBuilder {
side1 = BattleSide(playerActor),
side2 = BattleSide(npcActor)
).ifSuccessful { battle ->
playerActor.battleTheme = npcEntity.getBattleTheme()
npcEntity.entityData.update(NPCEntity.BATTLE_IDS) { it + battle.battleId }
result = SuccessfulBattleStart(battle)
}
@@ -290,12 +290,13 @@ object ShowdownInterpreter {
}
fun broadcastOptionalAbility(battle: PokemonBattle, effect: Effect?, pokemon: BattlePokemon) {
if (effect != null && effect.type == Effect.Type.ABILITY)
broadcastAbility(battle, effect, pokemon)
if (effect == null) return
broadcastAbility(battle, effect, pokemon)
}
// Broadcasts a generic lang to notify players of ability activations (effects are broadcasted separately)
fun broadcastAbility(battle: PokemonBattle, effect: Effect, pokemon: BattlePokemon) {
if (effect.type != Effect.Type.ABILITY) return
battle.dispatchWaiting(0.5F) {
val lang = battleLang("ability.generic", pokemon.getName(), effect.typelessData).yellow()
battle.broadcastChatMessage(lang)
@@ -24,11 +24,12 @@ import java.util.UUID
import net.minecraft.network.chat.MutableComponent
import net.minecraft.server.level.ServerPlayer
import net.minecraft.sounds.SoundEvent
import net.minecraft.sounds.SoundEvents
import net.minecraft.world.phys.Vec3
class PlayerBattleActor(
uuid: UUID,
pokemonList: List<BattlePokemon>
pokemonList: List<BattlePokemon>,
) : BattleActor(uuid, pokemonList.toMutableList()), EntityBackedBattleActor<ServerPlayer> {
override val initialPos: Vec3?
@@ -41,8 +42,7 @@ class PlayerBattleActor(
/** The [SoundEvent] to play to the player during a battle. Will start playing as soon as the battle starts. */
var battleTheme: SoundEvent? = null
set(value) {
if (field != value && this.battle.started)
this.sendUpdate(BattleMusicPacket(value))
if (this.isInitialized() && this.battle.started) this.sendUpdate(BattleMusicPacket(value))
field = value
}
@@ -47,6 +47,7 @@ class AbilityInstruction(val instructionSet: InstructionSet, val message: Battle
val lang = when (optionalEffect?.id) {
"trace" -> optionalPokemonName?.let { battleLang("ability.trace", pokemonName, it, effect.typelessData) }
"receiver", "powerofalchemy" -> optionalPokemonName?.let { battleLang("ability.receiver", it, effect.typelessData) } // Receiver and Power of Alchemy share the same text
"worryseed", "entrainment", "simplebeam", "roleplay", "doodle" -> battleLang("ability.replace", pokemonName, effect.typelessData)
else -> when (effect.id) {
"sturdy", "unnerve", "anticipation" -> battleLang("ability.${effect.id}", pokemonName) // Unique message
"airlock", "cloudnine" -> battleLang("ability.airlock") // Cloud Nine shares the same text as Air Lock
@@ -8,6 +8,7 @@
package com.cobblemon.mod.common.battles.interpreter.instructions
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.scheduling.afterOnServer
@@ -30,7 +30,7 @@ class MegaInstruction(val message: BattleMessage): InterpreterInstruction {
battle.dispatchWaiting {
val pokemonName = battlePokemon.getName()
battle.broadcastChatMessage(battleLang("mega", pokemonName).yellow())
CobblemonEvents.MEGA_EVOLUTON.post(MegaEvolutionEvent(battle, battlePokemon))
CobblemonEvents.MEGA_EVOLUTION.post(MegaEvolutionEvent(battle, battlePokemon))
battle.minorBattleActions[battlePokemon.uuid] = message
}
}
@@ -10,17 +10,14 @@ package com.cobblemon.mod.common.battles.interpreter.instructions
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.text.yellow
import com.cobblemon.mod.common.battles.ShowdownInterpreter
import com.cobblemon.mod.common.battles.dispatch.InstructionSet
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.entity.pokemon.effects.IllusionEffect
import com.cobblemon.mod.common.net.messages.client.battle.BattleSwapPokemonPacket
import com.cobblemon.mod.common.util.battleLang
import com.cobblemon.mod.common.util.setPositionSafely
import com.cobblemon.mod.common.util.swap
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.phys.Vec3
import java.util.logging.Level
/**
@@ -32,58 +29,30 @@ import java.util.logging.Level
*/
class SwapInstruction(val message: BattleMessage, val instructionSet: InstructionSet): InterpreterInstruction {
override fun invoke(battle: PokemonBattle) {
// TODO: more error checks
battle.dispatchWaiting {
val battlePokemonA = message.battlePokemon(0, battle) ?: return@dispatchWaiting
val pnxA = message.argumentAt(0)?.substring(0, 3)
var posA: Vec3? = null
val (actor, activePokemonA) = battle.getActorAndActiveSlotFromPNX(pnxA!!)
if (battlePokemonA.entity == null) {
posA = ShowdownInterpreter.getSendoutPosition(battle, activePokemonA, battlePokemonA.actor)
} else {
posA = battlePokemonA.entity?.position()
}
val activeBattlePokemonB = activePokemonA.getAdjacentAllies().firstOrNull()
if(activeBattlePokemonB != null) {
if (activeBattlePokemonB != null) {
val pnxB = activeBattlePokemonB.getPNX()
val (actorB, activePokemonB) = battle.getActorAndActiveSlotFromPNX(pnxB)
// Swap the position of the 2 on the field
var posB: Vec3? = null
if (activePokemonB.battlePokemon?.entity == null) {
// target slot is likely fainted
posB = ShowdownInterpreter.getSendoutPosition(battle, activePokemonB, actorB)
} else {
posB = activePokemonB.battlePokemon?.entity?.position()
}
if (posB != null && battlePokemonA.entity != null) {
battlePokemonA.entity?.setPositionSafely(posB)
} else if(posB != null) {
if (activePokemonA.battlePokemon?.entity != null) {
activePokemonA.battlePokemon?.entity?.setPositionSafely(posB)
} else {
activePokemonB.position.let {
if (it != null) {
activePokemonB.position = Pair<ServerLevel, Vec3>(it.first, posB)
}
}
}
}
if(posA != null) {
if (activePokemonB.battlePokemon?.entity != null) {
activePokemonB.battlePokemon?.entity?.setPositionSafely(posA)
} else {
activePokemonA.position.let {
if (it != null) {
activePokemonB.position = Pair<ServerLevel, Vec3>(it.first, posA)
}
}
}
}
// Swap references of the 2 pokemon
actor.activePokemon.swap((pnxA[2] - 'a'), (pnxB[2] - 'a'))
val posA = ShowdownInterpreter.getSendoutPosition(battle, activePokemonA, battlePokemonA.actor) ?: activePokemonA.position?.second
if (posA != null && battlePokemonA.entity != null) {
battlePokemonA.entity?.setPositionSafely(battlePokemonA.entity!!.getAjustedSendoutPosition(posA))
}
val posB = ShowdownInterpreter.getSendoutPosition(battle, activePokemonB, battlePokemonA.actor) ?: activePokemonB.position?.second
val battlePokemonB = activePokemonB.battlePokemon
if (posB != null && battlePokemonB?.entity != null) {
activePokemonB.battlePokemon?.entity?.setPositionSafely(battlePokemonB.entity!!.getAjustedSendoutPosition(posB))
}
// Notify clients of the swap
battle.sendUpdate(BattleSwapPokemonPacket(pnxA))
@@ -8,7 +8,6 @@
package com.cobblemon.mod.common.battles.pokemon
import com.bedrockk.molang.runtime.struct.VariableStruct
import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
import com.cobblemon.mod.common.api.moves.MoveSet
import com.cobblemon.mod.common.api.pokemon.helditem.HeldItemManager
@@ -107,9 +106,5 @@ open class BattlePokemon(
!isSentOut() && !willBeSwitchedIn && health > 0
}
fun writeVariables(struct: VariableStruct) {
effectedPokemon.writeVariables(struct)
}
fun getIllusion(): BattlePokemon? = this.actor.activePokemon.find { it.battlePokemon == this }?.illusion
}
@@ -8,10 +8,14 @@
package com.cobblemon.mod.common.block
import com.cobblemon.mod.common.CobblemonBlockEntities
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.block.entity.LecternBlockEntity
import com.cobblemon.mod.common.block.entity.ViewerCountBlockEntity
import com.cobblemon.mod.common.item.PokedexItem
import com.cobblemon.mod.common.net.messages.client.ui.PokedexUIPacket
import com.cobblemon.mod.common.util.playSoundServer
import com.cobblemon.mod.common.util.toVec3d
import com.mojang.serialization.MapCodec
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
@@ -29,6 +33,7 @@ import net.minecraft.world.level.Level
import net.minecraft.world.level.LevelReader
import net.minecraft.world.level.block.*
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.entity.BlockEntityType
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.StateDefinition
import net.minecraft.world.level.block.state.properties.DirectionProperty
@@ -52,6 +57,8 @@ class LecternBlock(properties: Properties): BaseEntityBlock(properties) {
override fun codec() = CODEC
override fun <T : BlockEntity> getTicker(world: Level, blockState: BlockState, BlockWithEntityType: BlockEntityType<T>) = createTickerHelper(BlockWithEntityType, CobblemonBlockEntities.LECTERN, ViewerCountBlockEntity.TICKER::tick)
override fun getRenderShape(blockState: BlockState?) = RenderShape.MODEL
override fun getOcclusionShape(blockState: BlockState?, blockGetter: BlockGetter?, blockPos: BlockPos?): VoxelShape = MinecraftLecternBlock.SHAPE_COMMON
@@ -117,12 +124,15 @@ class LecternBlock(properties: Properties): BaseEntityBlock(properties) {
if (!itemStack.isEmpty && itemStack.item is PokedexItem) {
if (!level.isClientSide) {
if (player.isCrouching) takeStoredItem(blockEntity, blockState, level, blockPos, player)
else PokedexUIPacket((blockEntity.getItemStack().item as PokedexItem).type).sendToPlayer(player as ServerPlayer)
else {
blockEntity.incrementViewerCount()
PokedexUIPacket(type = (blockEntity.getItemStack().item as PokedexItem).type, blockPos = blockPos).sendToPlayer(player as ServerPlayer)
level.playSoundServer(position = blockPos.toVec3d(), sound = CobblemonSounds.POKEDEX_OPEN, volume = 0.25F)
}
}
else if (!player.isCrouching) player.playSound(CobblemonSounds.POKEDEX_OPEN)
}
}
return InteractionResult.CONSUME
return InteractionResult.SUCCESS_NO_ITEM_USED
}
override fun isPathfindable(blockState: BlockState?, pathComputationType: PathComputationType?) = false
@@ -173,10 +173,6 @@ class DisplayCaseBlockEntity(pos: BlockPos, state: BlockState) : BlockEntity(Cob
override fun getMaxStackSize() = 1
override fun canPlaceItemThroughFace(slot: Int, stack: ItemStack, dir: Direction?): Boolean {
if(dir == Direction.DOWN) return false
return getStack().isEmpty
}
override fun canPlaceItemThroughFace(slot: Int, stack: ItemStack, dir: Direction?): Boolean = (getStack().isEmpty)
override fun canTakeItemThroughFace(slot: Int, stack: ItemStack, direction: Direction): Boolean = (direction == Direction.DOWN)
}
@@ -13,17 +13,12 @@ import net.minecraft.core.BlockPos
import net.minecraft.core.HolderLookup
import net.minecraft.core.NonNullList
import net.minecraft.nbt.CompoundTag
import net.minecraft.network.protocol.Packet
import net.minecraft.network.protocol.game.ClientGamePacketListener
import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket
import net.minecraft.world.ContainerHelper
import net.minecraft.world.item.ItemStack
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.entity.BlockEntityTicker
import net.minecraft.world.level.block.state.BlockState
class LecternBlockEntity(blockPos: BlockPos, blockState: BlockState) : BlockEntity(CobblemonBlockEntities.LECTERN, blockPos, blockState) {
class LecternBlockEntity(blockPos: BlockPos, blockState: BlockState) : ViewerCountBlockEntity(CobblemonBlockEntities.LECTERN, blockPos, blockState) {
val inventory: NonNullList<ItemStack> = NonNullList.withSize(1, ItemStack.EMPTY)
override fun saveAdditional(compoundTag: CompoundTag, registryLookup: HolderLookup.Provider) {
@@ -36,10 +31,6 @@ class LecternBlockEntity(blockPos: BlockPos, blockState: BlockState) : BlockEnti
ContainerHelper.loadAllItems(compoundTag, inventory, registryLookup)
}
override fun getUpdatePacket(): Packet<ClientGamePacketListener>? {
return ClientboundBlockEntityDataPacket.create(this)
}
override fun getUpdateTag(registryLookup: HolderLookup.Provider): CompoundTag {
return this.saveWithoutMetadata(registryLookup)
}
@@ -51,23 +42,16 @@ class LecternBlockEntity(blockPos: BlockPos, blockState: BlockState) : BlockEnti
fun setItemStack(itemStack: ItemStack) {
if (level != null) {
inventory[0] = itemStack
onItemUpdate(level!!)
updateBlock(level!!)
}
}
fun removeItemStack(): ItemStack {
if (level != null) {
val itemStack = ContainerHelper.removeItem(inventory, 0, 1)
onItemUpdate(level!!)
updateBlock(level!!)
return itemStack
}
return ItemStack.EMPTY
}
private fun onItemUpdate(level: Level) {
val oldState = level.getBlockState(blockPos)
level.sendBlockUpdated(blockPos, oldState, level.getBlockState(blockPos), Block.UPDATE_ALL)
level.updateNeighbourForOutputSignal(blockPos, level.getBlockState(blockPos).block)
setChanged()
}
}
}
@@ -0,0 +1,99 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.block.entity
import com.cobblemon.mod.common.util.DataKeys
import net.minecraft.core.BlockPos
import net.minecraft.core.HolderLookup
import net.minecraft.nbt.CompoundTag
import net.minecraft.network.protocol.Packet
import net.minecraft.network.protocol.game.ClientGamePacketListener
import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket
import net.minecraft.world.entity.player.Player
import net.minecraft.world.level.ClipContext
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.entity.BlockEntityTicker
import net.minecraft.world.level.block.entity.BlockEntityType
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.entity.EntityTypeTest
import net.minecraft.world.phys.AABB
open class ViewerCountBlockEntity(type: BlockEntityType<*>?, blockPos: BlockPos, blockState: BlockState) : BlockEntity(type, blockPos, blockState) {
companion object {
internal val TICKER = BlockEntityTicker<ViewerCountBlockEntity> { world, _, _, blockEntity ->
if (world.isClientSide) return@BlockEntityTicker
blockEntity.checkUsers(blockEntity.getInRangeViewerCount(world, blockEntity.blockPos))
}
}
var viewerCount: Int = 0
private fun checkUsers(inRangeViewers: Int) {
if (inRangeViewers < viewerCount) changeViewerCount(inRangeViewers)
}
override fun saveAdditional(compoundTag: CompoundTag, registryLookup: HolderLookup.Provider) {
super.saveAdditional(compoundTag, registryLookup)
compoundTag.putInt(DataKeys.BLOCK_ENTITY_USER_AMOUNT, viewerCount)
}
override fun loadAdditional(compoundTag: CompoundTag, registryLookup: HolderLookup.Provider) {
super.loadAdditional(compoundTag, registryLookup)
viewerCount = if (compoundTag.contains(DataKeys.BLOCK_ENTITY_USER_AMOUNT)) compoundTag.getInt(DataKeys.BLOCK_ENTITY_USER_AMOUNT) else 0
}
override fun getUpdatePacket(): Packet<ClientGamePacketListener>? {
return ClientboundBlockEntityDataPacket.create(this)
}
override fun getUpdateTag(registryLookup: HolderLookup.Provider): CompoundTag {
return this.saveWithoutMetadata(registryLookup)
}
fun updateBlock(level: Level) {
val oldState = level.getBlockState(blockPos)
level.sendBlockUpdated(blockPos, oldState, level.getBlockState(blockPos), Block.UPDATE_ALL)
level.updateNeighbourForOutputSignal(blockPos, level.getBlockState(blockPos).block)
setChanged()
}
private fun isPlayerLookingAt(world: Level, player: Player, blockPos: BlockPos): Boolean {
val vec3 = player.eyePosition
val vec32 = vec3.add(player.calculateViewVector(player.xRot, player.yRot).scale(player.blockInteractionRange()))
val blockHitResult = world.clip(ClipContext(vec3, vec32, ClipContext.Block.OUTLINE, ClipContext.Fluid.ANY, player))
return blockPos == blockHitResult.blockPos
}
private fun getInRangeViewerCount(world: Level, pos: BlockPos, range: Double = 5.0): Int {
val box = AABB(
pos.x.toDouble() - range,
pos.y.toDouble() - range,
pos.z.toDouble() - range,
(pos.x + 1).toDouble() + range,
(pos.y + 1).toDouble() + range,
(pos.z + 1).toDouble() + range
)
return world.getEntities(EntityTypeTest.forClass(Player::class.java), box) { player: Player? -> isPlayerLookingAt(world, player!!, pos) }.size
}
fun changeViewerCount(amount: Int) {
if (level != null) {
viewerCount = maxOf(0, amount)
updateBlock(level!!)
}
}
fun incrementViewerCount() = changeViewerCount(viewerCount + 1)
fun decrementViewerCount() = changeViewerCount(viewerCount - 1)
}
@@ -9,7 +9,6 @@
package com.cobblemon.mod.common.client
import com.cobblemon.mod.common.BakingOverride
import com.cobblemon.mod.common.client.pokedex.PokedexTypes
import com.cobblemon.mod.common.util.cobblemonModel
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.client.resources.model.ModelResourceLocation
@@ -104,51 +103,9 @@ object CobblemonBakingOverrides {
cobblemonModel("surprise_mulch", "none")
)
// Items
val POKEDEX_BLACK = registerOverride(
cobblemonResource("item/pokedex_black_model"),
cobblemonModel("pokedex_black_model", "inventory")
)
val POKEDEX_BLUE = registerOverride(
cobblemonResource("item/pokedex_blue_model"),
cobblemonModel("pokedex_blue_model", "inventory")
)
val POKEDEX_GREEN = registerOverride(
cobblemonResource("item/pokedex_green_model"),
cobblemonModel("pokedex_green_model", "inventory")
)
val POKEDEX_PINK = registerOverride(
cobblemonResource("item/pokedex_pink_model"),
cobblemonModel("pokedex_pink_model", "inventory")
)
val POKEDEX_RED = registerOverride(
cobblemonResource("item/pokedex_red_model"),
cobblemonModel("pokedex_red_model", "inventory")
)
val POKEDEX_WHITE = registerOverride(
cobblemonResource("item/pokedex_white_model"),
cobblemonModel("pokedex_white_model", "inventory")
)
val POKEDEX_YELLOW = registerOverride(
cobblemonResource("item/pokedex_yellow_model"),
cobblemonModel("pokedex_yellow_model", "inventory")
)
fun getPokedexOverride(type: PokedexTypes): BakingOverride {
return when (type) {
PokedexTypes.BLACK -> POKEDEX_BLACK
PokedexTypes.BLUE -> POKEDEX_BLUE
PokedexTypes.GREEN -> POKEDEX_GREEN
PokedexTypes.PINK -> POKEDEX_PINK
PokedexTypes.WHITE -> POKEDEX_WHITE
PokedexTypes.YELLOW -> POKEDEX_YELLOW
PokedexTypes.RED -> POKEDEX_RED
}
}
fun registerOverride(modelLocation: ResourceLocation, modelIdentifier: ModelResourceLocation): BakingOverride {
val result = BakingOverride(modelLocation, modelIdentifier)
models.add(result)
return result
}
}
}
@@ -156,7 +156,7 @@ object CobblemonClient {
return@subscribe
}
val nearbyShinies = player?.level()?.getEntities(player, AABB.ofSize(player.position(), 16.0, 16.0, 16.0)) { (it is PokemonEntity) && it.pokemon.shiny }
nearbyShinies?.firstOrNull { player.isLookingAt(it) }.let {
nearbyShinies?.firstOrNull { player.isLookingAt(it) && !player.isSpectator }.let {
if(it is PokemonEntity)
it.delegate.spawnShinyParticle(player!!)
}
@@ -379,7 +379,7 @@ class PokemonClientDelegate : PosableState(), PokemonSideDelegate {
val player = Minecraft.getInstance().player ?: return
val isWithinRange = player.position().distanceTo(currentEntity.position()) <= Cobblemon.config.shinyNoticeParticlesDistance
if (currentEntity.pokemon.shiny && currentEntity.ownerUUID == null) {
if (currentEntity.pokemon.shiny && currentEntity.ownerUUID == null && !player.isSpectator ) {
if (isWithinRange && !shined) {
playShinyEffect("cobblemon:wild_shiny_ring")
shined = true
@@ -47,8 +47,8 @@ class BattleSwitchPokemonSelection(
battleGUI,
request,
x = 0,
y = if (Minecraft.getInstance().window.guiScaledHeight > 630) Minecraft.getInstance().window.guiScaledHeight / 2 - 148 / 2
else Minecraft.getInstance().window.guiScaledHeight - 226,
y = if (Minecraft.getInstance().window.guiScaledHeight > 304) (Minecraft.getInstance().window.guiScaledHeight / 2) - (BACKGROUND_HEIGHT / 2)
else Minecraft.getInstance().window.guiScaledHeight - (BACKGROUND_HEIGHT + 78),
width = Minecraft.getInstance().window.guiScaledWidth,
height = Minecraft.getInstance().window.guiScaledHeight,
battleLang("switch_pokemon")
@@ -22,6 +22,7 @@ import com.cobblemon.mod.common.client.battle.ActiveClientBattlePokemon
import com.cobblemon.mod.common.client.battle.SingleActionRequest
import com.cobblemon.mod.common.client.gui.battle.BattleGUI
import com.cobblemon.mod.common.client.gui.battle.BattleOverlay
import com.cobblemon.mod.common.client.gui.battle.subscreen.BattleSwitchPokemonSelection.Companion
import com.cobblemon.mod.common.client.gui.drawProfilePokemon
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.client.render.models.blockbench.FloatingState
@@ -47,8 +48,8 @@ class BattleTargetSelection(
battleGUI = battleGUI,
request = request,
x = 0,
y = if (Minecraft.getInstance().window.guiScaledHeight > 630) Minecraft.getInstance().window.guiScaledHeight / 2 - 148 / 2
else Minecraft.getInstance().window.guiScaledHeight - 226,
y = if (Minecraft.getInstance().window.guiScaledHeight > 304) (Minecraft.getInstance().window.guiScaledHeight / 2) - (BattleSwitchPokemonSelection.BACKGROUND_HEIGHT / 2)
else Minecraft.getInstance().window.guiScaledHeight - (BattleSwitchPokemonSelection.BACKGROUND_HEIGHT + 78),
width = 100,
height = 100,
battleLang("ui.select_move")
@@ -60,7 +60,7 @@ class DialogueOptionWidget(
if (!selectable || dialogueScreen.waitingForServerUpdate) return true
dialogueScreen.sendToServer(InputToDialoguePacket(dialogueScreen.dialogueDTO.dialogueInput.inputId, value))
Minecraft.getInstance().soundManager.play(SimpleSoundInstance.forUI(CobblemonSounds.GUI_CLICK, 0.6F))
Minecraft.getInstance().soundManager.play(SimpleSoundInstance.forUI(CobblemonSounds.GUI_CLICK, 1.0F))
return true
}
}
@@ -8,12 +8,10 @@
package com.cobblemon.mod.common.client.gui.interact.battleRequest
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.gui.ColourLibrary
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.text.bold
import com.cobblemon.mod.common.battles.BattleFormat
import com.cobblemon.mod.common.client.CobblemonClient
import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.battle.ClientBattleChallenge
import com.cobblemon.mod.common.client.battle.ClientTeamRequest
@@ -47,106 +45,106 @@ class BattleConfigureGUI(
var autoLevelNavButtonRight: BattleRequestNavigationButton? = null
val battleRequestMap = mutableMapOf(
Pair(
PlayerInteractOptionsPacket.Options.SINGLE_BATTLE,
BattleTypeTile(
option = PlayerInteractOptionsPacket.Options.SINGLE_BATTLE,
battleFormat = BattleFormat.GEN_9_SINGLES,
tileTexture = cobblemonResource("textures/gui/interact/request/battle_request_single.png"),
overlayTexture = cobblemonResource("textures/gui/interact/request/battle_request_overlay.png"),
title = lang("ui.challenge.challenge_title").bold(),
subTitle = lang("battle.types.singles").bold(),
buttonText = lang("ui.challenge.challenge").bold(),
onRequest = { packet, battleFormat -> sendBattleRequest(battleFormat, packet) },
onResponse = { packet, requestID, accept -> sendBattleResponse(packet, requestID, accept) }
)
),
Pair(
PlayerInteractOptionsPacket.Options.DOUBLE_BATTLE,
BattleTypeTile(
option = PlayerInteractOptionsPacket.Options.DOUBLE_BATTLE,
battleFormat = BattleFormat.GEN_9_DOUBLES,
tileTexture = cobblemonResource("textures/gui/interact/request/battle_request_double.png"),
overlayTexture = cobblemonResource("textures/gui/interact/request/battle_request_overlay.png"),
title = lang("ui.challenge.challenge_title").bold(),
subTitle = lang("battle.types.doubles").bold(),
buttonText = lang("ui.challenge.challenge").bold(),
onRequest = { packet, battleFormat -> sendBattleRequest(battleFormat, packet) },
onResponse = { packet, requestID, accept -> sendBattleResponse(packet, requestID, accept) }
),
),
Pair(
PlayerInteractOptionsPacket.Options.TRIPLE_BATTLE,
BattleTypeTile(
option = PlayerInteractOptionsPacket.Options.TRIPLE_BATTLE,
battleFormat = BattleFormat.GEN_9_TRIPLES,
tileTexture = cobblemonResource("textures/gui/interact/request/battle_request_triple.png"),
overlayTexture = cobblemonResource("textures/gui/interact/request/battle_request_overlay.png"),
title = lang("ui.challenge.challenge_title").bold(),
subTitle = lang("battle.types.triples").bold(),
buttonText = lang("ui.challenge.challenge").bold(),
onRequest = { packet, battleFormat -> sendBattleRequest(battleFormat, packet) },
onResponse = { packet, requestID, accept -> sendBattleResponse(packet, requestID, accept) }
),
),
Pair(
PlayerInteractOptionsPacket.Options.TEAM_REQUEST,
BattleTypeTile(
option = PlayerInteractOptionsPacket.Options.TEAM_REQUEST,
battleFormat = BattleFormat.GEN_9_MULTI,
tileTexture = cobblemonResource("textures/gui/interact/request/battle_request_multi.png"),
overlayTexture = cobblemonResource("textures/gui/interact/request/battle_request_multi_overlay_partner.png"),
title = lang("ui.team.invite_title").bold(),
subTitle = lang("battle.types.multi").bold(),
buttonText = lang("ui.team.invite").bold(),
color = ColourLibrary.SIDE_1_ALLY_BATTLE_COLOUR,
onRequest = { packet, battleFormat -> BattleTeamRequestPacket(packet.numericTargetId).sendToServer() },
onResponse = { packet, requestID, accept -> sendTeamResponse(packet, requestID, accept) }
),
),
Pair(
PlayerInteractOptionsPacket.Options.TEAM_LEAVE,
BattleTypeTile(
option = PlayerInteractOptionsPacket.Options.TEAM_LEAVE,
battleFormat = BattleFormat.GEN_9_MULTI,
tileTexture = cobblemonResource("textures/gui/interact/request/battle_request_multi.png"),
overlayTexture = cobblemonResource("textures/gui/interact/request/battle_request_multi_overlay_partner_leave.png"),
title = lang("ui.team.leave_title").bold(),
subTitle = lang("battle.types.multi").bold(),
buttonText = lang("ui.team.leave").bold(),
color = ColourLibrary.SIDE_1_ALLY_BATTLE_COLOUR,
onRequest = { _, _ -> BattleTeamLeavePacket().sendToServer() },
onResponse = { _, _, _ -> Unit }
),
),
Pair(
PlayerInteractOptionsPacket.Options.MULTI_BATTLE,
BattleTypeTile(
option = PlayerInteractOptionsPacket.Options.MULTI_BATTLE,
battleFormat = BattleFormat.GEN_9_MULTI,
tileTexture = cobblemonResource("textures/gui/interact/request/battle_request_multi.png"),
overlayTexture = cobblemonResource("textures/gui/interact/request/battle_request_multi_overlay_opponent.png"),
title = lang("ui.challenge.challenge_title").bold(),
subTitle = lang("battle.types.multi").bold(),
buttonText = lang("ui.challenge.challenge").bold(),
onRequest = { packet, battleFormat -> sendBattleRequest(battleFormat, packet) },
onResponse = { packet, requestID, accept -> sendBattleResponse(packet, requestID, accept) }
),
),
Pair(
PlayerInteractOptionsPacket.Options.ROYAL_BATTLE,
BattleTypeTile(
option = PlayerInteractOptionsPacket.Options.ROYAL_BATTLE,
battleFormat = BattleFormat.GEN_9_ROYAL,
tileTexture = cobblemonResource("textures/gui/interact/request/battle_request_royal.png"),
overlayTexture = cobblemonResource("textures/gui/interact/request/battle_request_royal_overlay.png"),
title = lang("ui.challenge.challenge_title").bold(),
subTitle = lang("battle.types.freeforall").bold(),
buttonText = lang("ui.challenge.challenge").bold(),
onRequest = { packet, battleFormat -> sendBattleRequest(battleFormat, packet) },
onResponse = { packet, requestID, accept -> sendBattleResponse(packet, requestID, accept) }
),
),
Pair(
PlayerInteractOptionsPacket.Options.SINGLE_BATTLE,
BattleTypeTile(
option = PlayerInteractOptionsPacket.Options.SINGLE_BATTLE,
battleFormat = BattleFormat.GEN_9_SINGLES,
tileTexture = cobblemonResource("textures/gui/interact/request/battle_request_single.png"),
overlayTexture = cobblemonResource("textures/gui/interact/request/battle_request_overlay.png"),
title = lang("ui.challenge.challenge_title").bold(),
subTitle = lang("battle.types.singles").bold(),
buttonText = lang("ui.challenge.challenge").bold(),
onRequest = { packet, battleFormat -> sendBattleRequest(battleFormat, packet) },
onResponse = { packet, requestID, accept -> sendBattleResponse(packet, requestID, accept) }
)
),
Pair(
PlayerInteractOptionsPacket.Options.DOUBLE_BATTLE,
BattleTypeTile(
option = PlayerInteractOptionsPacket.Options.DOUBLE_BATTLE,
battleFormat = BattleFormat.GEN_9_DOUBLES,
tileTexture = cobblemonResource("textures/gui/interact/request/battle_request_double.png"),
overlayTexture = cobblemonResource("textures/gui/interact/request/battle_request_overlay.png"),
title = lang("ui.challenge.challenge_title").bold(),
subTitle = lang("battle.types.doubles").bold(),
buttonText = lang("ui.challenge.challenge").bold(),
onRequest = { packet, battleFormat -> sendBattleRequest(battleFormat, packet) },
onResponse = { packet, requestID, accept -> sendBattleResponse(packet, requestID, accept) }
)
),
Pair(
PlayerInteractOptionsPacket.Options.TRIPLE_BATTLE,
BattleTypeTile(
option = PlayerInteractOptionsPacket.Options.TRIPLE_BATTLE,
battleFormat = BattleFormat.GEN_9_TRIPLES,
tileTexture = cobblemonResource("textures/gui/interact/request/battle_request_triple.png"),
overlayTexture = cobblemonResource("textures/gui/interact/request/battle_request_overlay.png"),
title = lang("ui.challenge.challenge_title").bold(),
subTitle = lang("battle.types.triples").bold(),
buttonText = lang("ui.challenge.challenge").bold(),
onRequest = { packet, battleFormat -> sendBattleRequest(battleFormat, packet) },
onResponse = { packet, requestID, accept -> sendBattleResponse(packet, requestID, accept) }
)
),
Pair(
PlayerInteractOptionsPacket.Options.TEAM_REQUEST,
BattleTypeTile(
option = PlayerInteractOptionsPacket.Options.TEAM_REQUEST,
battleFormat = BattleFormat.GEN_9_MULTI,
tileTexture = cobblemonResource("textures/gui/interact/request/battle_request_multi.png"),
overlayTexture = cobblemonResource("textures/gui/interact/request/battle_request_multi_overlay_partner.png"),
title = lang("ui.team.invite_title").bold(),
subTitle = lang("battle.types.multi").bold(),
buttonText = lang("ui.team.invite").bold(),
color = ColourLibrary.SIDE_1_ALLY_BATTLE_COLOUR,
onRequest = { packet, battleFormat -> BattleTeamRequestPacket(packet.numericTargetId).sendToServer() },
onResponse = { packet, requestID, accept -> sendTeamResponse(packet, requestID, accept) }
)
),
Pair(
PlayerInteractOptionsPacket.Options.TEAM_LEAVE,
BattleTypeTile(
option = PlayerInteractOptionsPacket.Options.TEAM_LEAVE,
battleFormat = BattleFormat.GEN_9_MULTI,
tileTexture = cobblemonResource("textures/gui/interact/request/battle_request_multi.png"),
overlayTexture = cobblemonResource("textures/gui/interact/request/battle_request_multi_overlay_partner_leave.png"),
title = lang("ui.team.leave_title").bold(),
subTitle = lang("battle.types.multi").bold(),
buttonText = lang("ui.team.leave").bold(),
color = ColourLibrary.SIDE_1_ALLY_BATTLE_COLOUR,
onRequest = { _, _ -> BattleTeamLeavePacket().sendToServer() },
onResponse = { _, _, _ -> Unit }
)
),
Pair(
PlayerInteractOptionsPacket.Options.MULTI_BATTLE,
BattleTypeTile(
option = PlayerInteractOptionsPacket.Options.MULTI_BATTLE,
battleFormat = BattleFormat.GEN_9_MULTI,
tileTexture = cobblemonResource("textures/gui/interact/request/battle_request_multi.png"),
overlayTexture = cobblemonResource("textures/gui/interact/request/battle_request_multi_overlay_opponent.png"),
title = lang("ui.challenge.challenge_title").bold(),
subTitle = lang("battle.types.multi").bold(),
buttonText = lang("ui.challenge.challenge").bold(),
onRequest = { packet, battleFormat -> sendBattleRequest(battleFormat, packet) },
onResponse = { packet, requestID, accept -> sendBattleResponse(packet, requestID, accept) }
)
),
Pair(
PlayerInteractOptionsPacket.Options.ROYAL_BATTLE,
BattleTypeTile(
option = PlayerInteractOptionsPacket.Options.ROYAL_BATTLE,
battleFormat = BattleFormat.GEN_9_ROYAL,
tileTexture = cobblemonResource("textures/gui/interact/request/battle_request_royal.png"),
overlayTexture = cobblemonResource("textures/gui/interact/request/battle_request_royal_overlay.png"),
title = lang("ui.challenge.challenge_title").bold(),
subTitle = lang("battle.types.freeforall").bold(),
buttonText = lang("ui.challenge.challenge").bold(),
onRequest = { packet, battleFormat -> sendBattleRequest(battleFormat, packet) },
onResponse = { packet, requestID, accept -> sendBattleResponse(packet, requestID, accept) }
)
)
)
val levelRulesetOption = mutableListOf(-1, 50, 100, 5)
@@ -266,42 +264,43 @@ class BattleConfigureGUI(
// BattleFormat Selection buttons
this.addRenderableWidget(
BattleRequestNavigationButton(
pX = x + 2,
pY = y + 30,
clickHeight = BattleRequestNavigationButton.HEIGHT * 6,
forward = false
) { currentPage = (currentPage - 1) % options.count()
updateRequestButtonText()
}
BattleRequestNavigationButton(
pX = x + 2,
pY = y + 30,
clickHeight = BattleRequestNavigationButton.HEIGHT * 6,
forward = false
) { currentPage = (currentPage - 1) % options.count()
updateRequestButtonText()
}
)
this.addRenderableWidget(
BattleRequestNavigationButton(
pX = x + 106,
pY = y + 30,
clickHeight = BattleRequestNavigationButton.HEIGHT * 6,
forward = true
) { currentPage = (currentPage + 1) % options.count()
updateRequestButtonText()
}
BattleRequestNavigationButton(
pX = x + 106,
pY = y + 30,
clickHeight = BattleRequestNavigationButton.HEIGHT * 6,
forward = true
) { currentPage = (currentPage + 1) % options.count()
updateRequestButtonText()
}
)
if (battleRequestMap[options[currentPage]]?.option != PlayerInteractOptionsPacket.Options.TEAM_REQUEST) {
// Level Selection buttons
autoLevelNavButtonLeft = BattleRequestNavigationButton(
pX = x + 15,
pY = y + 75,
forward = false
pX = x + 15,
pY = y + 76,
backwardIcon = cobblemonResource("textures/gui/interact/request/label_arrow_left.png"),
forward = false
) { levelRulesetOptionIndex -= 1 }
this.addRenderableWidget(autoLevelNavButtonLeft)
autoLevelNavButtonRight = BattleRequestNavigationButton(
pX = x + 93,
pY = y + 75,
forward = true
pX = x + 94,
pY = y + 76,
forwardIcon = cobblemonResource("textures/gui/interact/request/label_arrow_right.png"),
forward = true
) {levelRulesetOptionIndex += 1 }
this.addRenderableWidget(autoLevelNavButtonRight)
}
@@ -16,18 +16,22 @@ import net.minecraft.client.gui.components.Button
import net.minecraft.client.resources.sounds.SimpleSoundInstance
import net.minecraft.client.sounds.SoundManager
import net.minecraft.network.chat.Component
import net.minecraft.resources.ResourceLocation
class BattleRequestNavigationButton(
pX: Int, pY: Int,
private val clickHeight: Float = HEIGHT,
pX: Number, pY: Number,
private val clickHeight: Int = HEIGHT,
private val forward: Boolean,
private val forwardIcon: ResourceLocation = forwardButtonResource,
private val backwardIcon: ResourceLocation = backwardsButtonResource,
onPress: OnPress
): Button(pX, pY, (WIDTH * SCALE).toInt(), (clickHeight * SCALE).toInt(), Component.literal("Navigation"), onPress, DEFAULT_NARRATION) {
): Button(pX.toInt(), pY.toInt(), (WIDTH * SCALE).toInt(), (clickHeight * SCALE).toInt(), Component.literal("Navigation"), onPress, DEFAULT_NARRATION) {
companion object {
const val WIDTH = 9F
const val HEIGHT = 16F
const val HEIGHT = 16
const val WIDTH = 9
const val SCALE = 0.5F
private val forwardButtonResource = cobblemonResource("textures/gui/interact/request/arrow_right.png")
private val backwardsButtonResource = cobblemonResource("textures/gui/interact/request/arrow_left.png")
}
@@ -41,7 +45,7 @@ class BattleRequestNavigationButton(
matrixStack = context.pose(),
x = x / SCALE,
y = (y + (clickHeight - HEIGHT) / 4) / SCALE,
texture = if (forward) forwardButtonResource else backwardsButtonResource,
texture = if (forward) forwardIcon else backwardIcon,
width = WIDTH,
height = HEIGHT,
vOffset = if (hovered) HEIGHT else 0,
@@ -14,12 +14,10 @@ import com.cobblemon.mod.common.api.text.bold
import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.lang
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.components.Button
import net.minecraft.client.resources.sounds.SimpleSoundInstance
import net.minecraft.client.sounds.SoundManager
import net.minecraft.network.chat.Component
import net.minecraft.network.chat.MutableComponent
class BattleRequestButton(

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