Compare commits

..
Author SHA1 Message Date
Guilherme Pereira 1986cb480b add moves tooltips 2024-12-15 22:47:47 -03:00
Polymeta c8b0f36049 Merge branch 'bugfixes-apion' into 'main'
Fixes #1182

Closes #1182

See merge request cable-mc/cobblemon!1373
2024-12-15 18:25:02 +00:00
ApionandPolymeta be2a0be806 Fixes #1182 2024-12-15 18:25:02 +00:00
Paul ba4d42ccf6 Try tweaking spawn direction handling 2024-12-15 23:04:08 +10:00
Paul 5545d3498f Include 2 more variables in clamping of rotations 2024-12-15 20:53:50 +10:00
Hiroku 641bd7ebaa Merge branch 'advancing' into 'main'
1.6 Advancement Reworks

See merge request cable-mc/cobblemon!1374
2024-12-15 09:16:28 +00:00
dhi_holoandHiroku 234c4fa0d4 1.6 Advancement Reworks 2024-12-15 09:16:28 +00:00
Hiroku 3dfda71ed5 Merge branch 'AbilityPatchCapsuleRefactor' into 'main'
AbilityPatchCapsuleRefactor

See merge request cable-mc/cobblemon!1377
2024-12-15 09:08:05 +00:00
Hiroku 65ee6de93f Merge branch 'chest-boat-interact' into 'main'
moved changes to chest boat (i'm dumb)

Closes #1214

See merge request cable-mc/cobblemon!1379
2024-12-15 09:07:48 +00:00
Paul be88b727bc Added useFixedRandom to pool party provider, added randomizePartyOrder to NPC classes and presets for randomly choosing lead battle pokemon 2024-12-15 18:04:04 +10:00
luke a69fc462ee moved changes to chest boat (i'm dumb) 2024-12-15 00:38:08 -07:00
Hiroku 7d5c30d27c Merge branch 'shift-click-interact' into 'main'
Changed distance to use player attribute

Closes #1232

See merge request cable-mc/cobblemon!1378
2024-12-15 07:32:39 +00:00
Hiroku f7c9c9d4f4 Update AbilityTypeChanger.kt 2024-12-15 07:30:12 +00:00
luke 41bc14168a Changed distance to use player attribute 2024-12-15 00:24:38 -07:00
Paul 9540c38a72 Updated readme example for NPC 2024-12-15 16:58:31 +10:00
JazzMcNade 7a96ed7cd1 Refactored queryPossible() as to not to require explicit Priority values 2024-12-15 00:45:51 -06:00
Hiroku 677adc8d2f Merge branch 'starter-selection-cry' into 'main'
Play cry when Pokémon is clicked on the starter selection screen

See merge request cable-mc/cobblemon!1362
2024-12-15 06:25:29 +00:00
lionandHiroku a99b1dfc6c Play cry when Pokémon is clicked on the starter selection screen 2024-12-15 06:25:29 +00:00
Hiroku 4a718ad0f6 Merge branch 'dex-update-event' into 'main'
Dex Data Updated Event

See merge request cable-mc/cobblemon!1353
2024-12-15 05:27:08 +00:00
Paul ddd417d458 Added player to PokedexDataChangedEvent's flow context 2024-12-15 15:19:43 +10:00
Paul 10a12a897e Merge remote-tracking branch 'origin/main' into dex-update-event 2024-12-15 15:15:48 +10:00
Paul 9fce99ed73 Improved dex event, added dex update flow, expanded dex molang and added q.player.pokedex 2024-12-15 15:15:13 +10:00
Hiroku c35f60dd5d Merge branch 'AbilitiesFixesRedux' into 'main'
AbilitiesFixesRedux

Closes #1245

See merge request cable-mc/cobblemon!1376
2024-12-15 03:11:07 +00:00
JazzMcNade 54e5a5e819 Fixed single abilities mons always evolving into the hidden ability. Fixed ability capsule / patch not being able to revert abilities. 2024-12-14 20:35:23 -06:00
Test 79ae35ea9e Merge branch 'n-p-c' into 'main'
fix npc scaling + eye height, update edit interface

See merge request cable-mc/cobblemon!1375
2024-12-15 02:17:45 +00:00
Test 58f85e6d0d fix npc scaling + eye height, update edit interface 2024-12-15 02:17:45 +00:00
Polymeta 71bfdf41f4 Merge branch 'the-else-strikes-back' into 'main'
Fix Feebas spawning

Closes #1187

See merge request cable-mc/cobblemon!1365
2024-12-15 01:13:26 +00:00
JordanandPolymeta 9976965673 Fix Feebas spawning 2024-12-15 01:13:26 +00:00
Hiroku 098df1a5a6 Merge branch 'dex-travelling-dimensions' into 'main'
Fix Pokédex screen getting stuck when changing dimensions or opening the battle GUI

See merge request cable-mc/cobblemon!1370
2024-12-15 01:09:47 +00:00
Guilherme PereiraandHiroku f49f34efa3 Fix Pokédex screen getting stuck when changing dimensions or opening the battle GUI 2024-12-15 01:09:47 +00:00
Hiroku eac19929c4 Merge branch 'fix-dex-animations-not-playing' into 'main'
Fix animations not playing on Pokédex

See merge request cable-mc/cobblemon!1355
2024-12-15 00:43:09 +00:00
Guilherme PereiraandHiroku 59cced5b2c Fix animations not playing on Pokédex 2024-12-15 00:43:08 +00:00
Apion f07707221f Merge branch 'implement_pokemon' into 'main'
Pokémon fixes and tweaks

Closes #1230 and #1137

See merge request cable-mc/cobblemon!1371
2024-12-14 23:57:34 +00:00
PolymetaandApion 89b89592ab Pokémon fixes and tweaks 2024-12-14 23:57:33 +00:00
Hiroku cdeb977769 Update DexInformationChangedEvent.kt 2024-12-14 23:55:43 +00:00
Paul a3987b433c Merge remote-tracking branch 'origin/main' 2024-12-14 22:19:51 +10:00
Paul 99de91bd7e Added optional level property to pool party providers for using molang to force a level value, closes #1244 2024-12-14 22:19:39 +10:00
Paul 4b292b1241 Less zealous on the warning logging 2024-12-14 22:16:07 +10:00
Hiroku 1e6e4d8c5f Merge branch 'moe-eats-bugs' into 'main'
Added ignore checks to prevent passive healing/revival when config values are 0

Closes #875

See merge request cable-mc/cobblemon!1372
2024-12-14 11:41:35 +00:00
MoeBoy76 9a61c3471b Added ignore checks to prevent passive healing/revival when config values are 0 2024-12-14 22:12:08 +11:00
Paul 77f7439853 Fixed brewing on neoforged 2024-12-14 14:51:01 +10:00
Paul e9718b7e8b Merge remote-tracking branch 'origin/main' 2024-12-14 14:29:28 +10:00
Hiroku 4cadd4e20f Merge branch 'fix/watchdog' into 'main'
Watchdog: Possible fix for watchdog crashing

See merge request cable-mc/cobblemon!1368
2024-12-14 04:26:24 +00:00
Hiroku 17a3d86324 Update PokemonEntity.kt 2024-12-14 04:25:19 +00:00
Hiroku 48653073f5 if ( 2024-12-14 04:21:49 +00:00
Nick 82c7c03e28 Initial tests for watchdog fix 2024-12-13 20:18:13 -08:00
Nick 2d7f3257d1 Merge branch 'hiro-fix' into 'main'
I fix bugs too

Closes #1212, #1222, and #1228

See merge request cable-mc/cobblemon!1367
2024-12-14 04:16:35 +00:00
HirokuandNick bc7b4f94c2 I fix bugs too 2024-12-14 04:16:35 +00:00
Paul 7a9e9d7ced Fixed battles being startable while pokemon are busy, closes #1212 2024-12-14 13:52:19 +10:00
Paul 0f85ece16f Fixed cry sound effects playing twice for specific cases, closes #1222 2024-12-14 13:36:53 +10:00
Paul c4656e1a4e Fixed cubone alola bias not being visible, closes #1228 2024-12-14 12:57:17 +10:00
Paul 0c48a318c5 Added /runmolangscript, updated molang version 2024-12-14 12:22:25 +10:00
Paul dab89a9f8c Merge remote-tracking branch 'origin/main' 2024-12-14 11:05:55 +10:00
Paul 53fd2dba2d Fixed flows double-registering then triple-registering then quadruple-registering then- 2024-12-14 11:05:48 +10:00
Polymeta a5b15c6127 Merge branch 'more-items-tooltips' into 'main'
Add tooltips to many items

See merge request cable-mc/cobblemon!1346
2024-12-14 00:48:11 +00:00
Guilherme PereiraandPolymeta 174fb59bbb Add tooltips to many items 2024-12-14 00:48:11 +00:00
Paul 2f8bd44938 Added q.set_query for assigning new query function values, closes #1240 2024-12-14 10:36:53 +10:00
Paul 9d58c7126e Fixed set_npc_variable not initializing NPC data properly 2024-12-14 10:12:50 +10:00
Hiroku 2afd722125 Incl default return for get_npc_variable when no data exists for the NPC yet 2024-12-14 00:03:25 +00:00
Polymeta c9cfe489d6 Merge branch 'EV-Berry-Lang-fixes' into 'main'
Fixed lang files for EV lowering berries to actually be correct

See merge request cable-mc/cobblemon!1364
2024-12-13 11:23:29 +00:00
Plastered_Crab 4ee029fa54 Fixed lang files for EV lowering berries to actually be correct 2024-12-12 09:41:17 -08: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
amo d1a8171079 resolve threads 2024-12-09 23:57:38 +00:00
amo d4b673ea38 Merge branch 'main' into dex-update-event 2024-12-09 23:54:08 +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
amo de21b7c402 forgot me loighter 2024-12-07 04:44:27 +00:00
amo 10fe3c4362 🍋‍🟩 Chore: Add Dex data update event 2024-12-07 04:40:50 +00: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
729 changed files with 16058 additions and 7274 deletions
+45 -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,10 @@
- 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 Hisuian Sneasel using Johtonian Sneasel's cry
- Fixed Trevenant t-posing after 8 seconds of sleep.
- Fixed Bewear t-posing for a bit if a wild one fainted.
- 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 +426,11 @@
- 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.
- Fixed passive healing and wake from faint not disabling when their config values are set to 0.
### Developer
- `SpawnCause` is now an implementation of `SpawningInfluence`.
@@ -433,11 +462,13 @@
- Added HealingSource, an interface applied to all sources of healing from player actions, for easier tracking of healing sources.
- Move Change
- Shiny Chance Calculation Event (with player context)
- Dex Information Changed; this event is fired when the Pokédex is updated with new information.
- Rebuilt large swaths of the model animation code to simplify it.
- Renamed a bunch of things from %Poseable% to %Posable% because spelling.
- Renamed StatelessAnimation to PoseAnimation.
- Renamed StatefulAnimation to ActiveAnimation.
- Documented the animation system.
- Allowed for SpawnSnowstormEntityParticleHandler to handle non-posable entities (due to being non-posable, locators are useless, to set the offset use the settings in the particle itself)
### Data Pack & Resource Pack Creators
- Added support for "shedders" similar to Shedinja's evolution logic.
@@ -454,11 +485,16 @@
- Sound events for all blocks now start with "block."
- Gimmighoul chest and item interaction sounds have been moved to where its cry is.
- Unused sound files and sound events have been removed.
- Poké Ball sounds are now in their animation files, making them more flexible to edit.
- Added MoLang compatibility in the isVisible property for transformed parts.
- Added q.has_aspect('some_aspect') function to animations, posers, and entity particle effects.
- Added support for conditional pose animations.
- Added a new universal locator called "top".
- Added `eggs_collected` and `eggs_hatched` Advancement triggers.
- Added missing `minYaw` configuration to the `q.look()` function for JSON posers.
- Some pose condition names have been changed:
- `isTouchingWaterOrRain` has been changed to `isInWaterOrRain`
- `isSubermegedInWater` has been changed to `isUnderWater`
### Localization
- Updated translations for:
@@ -1693,6 +1729,7 @@ Thank you so much to all of our community translators that bring the mod to the
- Lowered spawn rate of Tauros.
- Sableye now spawns near gem ores as well as amethyst.
- Added evolution stones and items to item tags
- Pokémon now play their cry animations when clicked on the starter selection screen.
### Fixes
- Fixed catch rates being entirely too difficult.
@@ -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,9 +52,11 @@ object CobblemonCommands {
BedrockParticleCommand.register(dispatcher)
OpenDialogueCommand.register(dispatcher)
NPCEditCommand.register(dispatcher)
NPCDeleteCommand.register(dispatcher)
AbandonMultiTeam.register(dispatcher)
FreezePokemonCommand.register(dispatcher)
ApplyPlayerTextureCommand.register(dispatcher)
RunMolangScriptCommand.register(dispatcher)
// Possibly lock down registration if and only if under dev environment or running in an environment
// with a certain system environment variable set
@@ -0,0 +1,80 @@
/*
* 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) {
clientFlows.clear()
flows.clear()
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
@@ -54,6 +54,9 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
override val registry: Registry<Item> = BuiltInRegistries.ITEM
override val resourceKey: ResourceKey<Registry<Item>> = Registries.ITEM
@JvmField
val NPC_EDITOR = create("npc_editor", CobblemonItem(Item.Properties().stacksTo(1)))
@JvmField
val pokeBalls = mutableListOf<PokeBallItem>()
@JvmField
@@ -155,19 +158,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 +1245,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()
@@ -55,6 +55,8 @@ object CobblemonCriteria : PlatformRegistry<Registry<CriterionTrigger<*>>, Resou
val CAST_POKE_ROD = this.create("cast_poke_rod", SimpleCriterionTrigger(CastPokeRodCriterionCondition.CODEC))
val REEL_IN_POKEMON = this.create("reel_in_pokemon", SimpleCriterionTrigger(ReelInPokemonCriterionCondition.CODEC))
// Advancement criteria for [grow_tumblestone.json]
val PLANT_TUMBLESTONE = this.create("plant_tumblestone", SimpleCriterionTrigger(PlantTumblestoneCriterion.CODEC))
@@ -8,26 +8,30 @@
package com.cobblemon.mod.common.advancement.criterion
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import com.mojang.serialization.Codec
import com.mojang.serialization.codecs.PrimitiveCodec
import com.mojang.serialization.codecs.RecordCodecBuilder
import java.util.Optional
import net.minecraft.advancements.critereon.ContextAwarePredicate
import net.minecraft.advancements.critereon.EntityPredicate
import net.minecraft.server.level.ServerPlayer
import net.minecraft.resources.ResourceLocation
class CastPokeRodContext(val baitId: ResourceLocation)
class CastPokeRodCriterionCondition(
playerCtx: Optional<ContextAwarePredicate>,
val hasBait: Boolean
): SimpleCriterionCondition<Boolean>(playerCtx) {
playerCtx: Optional<ContextAwarePredicate>,
val baitId: String
): SimpleCriterionCondition<CastPokeRodContext>(playerCtx) {
companion object {
val CODEC: Codec<CastPokeRodCriterionCondition> = RecordCodecBuilder.create { it.group(
EntityPredicate.ADVANCEMENT_CODEC.optionalFieldOf("player").forGetter(CastPokeRodCriterionCondition::playerCtx),
PrimitiveCodec.BOOL.fieldOf("hasBait").forGetter(CastPokeRodCriterionCondition::hasBait)
).apply(it, ::CastPokeRodCriterionCondition) }
EntityPredicate.ADVANCEMENT_CODEC.optionalFieldOf("player").forGetter(CastPokeRodCriterionCondition::playerCtx),
Codec.STRING.optionalFieldOf("baitId", "empty_bait").forGetter(CastPokeRodCriterionCondition::baitId)
).apply(it, { playerCtx, baitId -> CastPokeRodCriterionCondition(playerCtx, baitId.ifEmpty { "empty_bait" }) }) }
}
override fun matches(player: ServerPlayer, context: Boolean): Boolean {
return hasBait == context
override fun matches(player: ServerPlayer, context: CastPokeRodContext): Boolean {
return (context.baitId == this.baitId.asIdentifierDefaultingNamespace() || this.baitId.equals("empty_bait"))
}
}
}
@@ -0,0 +1,39 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.advancement.criterion
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import com.mojang.serialization.Codec
import com.mojang.serialization.codecs.RecordCodecBuilder
import java.util.Optional
import net.minecraft.advancements.critereon.ContextAwarePredicate
import net.minecraft.advancements.critereon.EntityPredicate
import net.minecraft.server.level.ServerPlayer
import net.minecraft.resources.ResourceLocation
class ReelInPokemonContext(val pokemonId : ResourceLocation, val baitId: ResourceLocation)
class ReelInPokemonCriterionCondition(
playerCtx: Optional<ContextAwarePredicate>,
val pokemonId: String,
val baitId: String
) : SimpleCriterionCondition<ReelInPokemonContext>(playerCtx) {
companion object {
val CODEC: Codec<ReelInPokemonCriterionCondition> = RecordCodecBuilder.create { it.group(
EntityPredicate.ADVANCEMENT_CODEC.optionalFieldOf("player").forGetter { it.playerCtx },
Codec.STRING.optionalFieldOf("pokemonId", "any").forGetter { it.pokemonId },
Codec.STRING.optionalFieldOf("baitId", "empty_bait").forGetter { it.baitId }
).apply(it, { playerCtx, pokemonId, baitId -> ReelInPokemonCriterionCondition(playerCtx, pokemonId, baitId.ifEmpty { "empty_bait" }) }) }
}
override fun matches(player: ServerPlayer, context: ReelInPokemonContext): Boolean {
return (context.pokemonId == this.pokemonId.asIdentifierDefaultingNamespace() || this.pokemonId == "any") && (context.baitId == this.baitId.asIdentifierDefaultingNamespace() || this.baitId == "empty_bait")
}
}
@@ -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
@@ -192,6 +192,8 @@ object CobblemonEvents {
val POKEMON_GAINED = EventObservable<PokemonGainedEvent>()
@JvmField
val POKEMON_SEEN = EventObservable<PokemonSeenEvent>()
@JvmField
val POKEDEX_DATA_CHANGED = EventObservable<PokedexDataChangedEvent>()
// Fishing
@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 }
)
}
@@ -0,0 +1,43 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.events.pokemon
import com.bedrockk.molang.runtime.value.DoubleValue
import com.bedrockk.molang.runtime.value.MoValue
import com.bedrockk.molang.runtime.value.StringValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.api.pokedex.FormDexRecord
import com.cobblemon.mod.common.api.pokedex.PokedexEntryProgress
import com.cobblemon.mod.common.api.pokedex.AbstractPokedexManager
import com.cobblemon.mod.common.pokedex.scanner.PokedexEntityData
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.getPlayer
import com.mojang.datafixers.util.Either
import java.util.UUID
/**
* Event that fires when a Pokémon's information is gained or updated in the Pokédex.
*/
data class PokedexDataChangedEvent(
val dataSource: Either<PokedexEntityData, Pokemon>,
val knowledge: PokedexEntryProgress,
val playerUUID: UUID,
val record: FormDexRecord
) {
val pokedexManager: AbstractPokedexManager
get() = record.speciesDexRecord.pokedexManager
val context = mutableMapOf<String, MoValue>(
"player" to (playerUUID.getPlayer()?.asMoLangValue() ?: DoubleValue.ZERO),
"pokemon" to dataSource.map({ DoubleValue.ZERO }, { it.struct }),
"data" to dataSource.map({ it.struct }, { DoubleValue.ZERO }),
"knowledge" to StringValue(knowledge.name.lowercase()),
"pokedex" to pokedexManager.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)
)
}
@@ -14,5 +14,4 @@ import java.util.UUID
data class PokemonGainedEvent(
val playerId: UUID,
val pokemon: Pokemon
) {
}
)
@@ -9,11 +9,9 @@
package com.cobblemon.mod.common.api.events.pokemon
import com.cobblemon.mod.common.pokemon.Pokemon
import net.minecraft.server.level.ServerPlayer
import java.util.UUID
data class PokemonSeenEvent(
val playerId: UUID,
val pokemon: Pokemon
) {
}
)
@@ -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()
@@ -8,6 +8,7 @@
package com.cobblemon.mod.common.api.gui
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import net.minecraft.client.gui.components.AbstractWidget
import net.minecraft.client.gui.components.Renderable
import net.minecraft.client.gui.components.events.GuiEventListener
@@ -22,9 +23,9 @@ abstract class ParentWidget(
pX: Int, pY: Int,
pWidth: Int, pHeight: Int,
component: Component
): Renderable, AbstractWidget(pX, pY, pWidth, pHeight, component) {
): CobblemonRenderable, AbstractWidget(pX, pY, pWidth, pHeight, component) {
private val children: MutableList<GuiEventListener> = mutableListOf()
val children: MutableList<GuiEventListener> = mutableListOf()
/**
* Adds Widget to the children list
@@ -8,6 +8,7 @@
package com.cobblemon.mod.common.api.item.ability
import com.cobblemon.mod.common.api.Priority
import com.cobblemon.mod.common.api.abilities.*
import com.cobblemon.mod.common.item.interactive.ability.AbilityTypeChanger
import com.cobblemon.mod.common.pokemon.Pokemon
@@ -33,7 +34,7 @@ interface AbilityChanger<T : PotentialAbility> {
* @param pokemon The [Pokemon] being queried.
* @return A set containing all their legal abilities associated with [type], can be empty.
*/
fun queryPossible(pokemon: Pokemon): Set<AbilityTemplate>
fun queryPossible(pokemon: Pokemon): Set<Pair<AbilityTemplate, Priority>>
/**
* Attempts to update the ability of the given [pokemon].
@@ -22,27 +22,51 @@ 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.pokedex.AbstractPokedexManager
import com.cobblemon.mod.common.api.pokedex.CaughtCount
import com.cobblemon.mod.common.api.pokedex.CaughtPercent
import com.cobblemon.mod.common.api.pokedex.PokedexEntryProgress
import com.cobblemon.mod.common.api.pokedex.PokedexManager
import com.cobblemon.mod.common.api.pokedex.SeenCount
import com.cobblemon.mod.common.api.pokedex.SeenPercent
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.pokemon.Species
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 +75,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 +106,31 @@ 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)
},
"set_query" to java.util.function.Function { params ->
val variable = params.getString(0)
val value = params.get<MoValue>(1)
params.environment.query.addFunction(variable) { value }
return@Function value
},
"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 +165,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 +284,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 +310,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
} else {
return@put 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)
val npcData = data.map.getOrPut(npcId) { VariableStruct() } as VariableStruct
npcData.map[variable] = value
if (saveAfterwards) {
Cobblemon.molangData.save(player.uuid)
}
return@put DoubleValue.ONE
}
map.put("pokedex") { player.pokedex().struct }
}
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 +365,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 +398,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 +421,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 +447,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 +474,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 +513,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 +541,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 +563,55 @@ 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("species") { pokemon.species.struct }
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 +623,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 +710,192 @@ 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
}
)
val pokedexFunctions: MutableList<(AbstractPokedexManager) -> HashMap<String, java.util.function.Function<MoParams, Any>>> = mutableListOf(
{ pokedex ->
val map = hashMapOf<String, java.util.function.Function<MoParams, Any>>()
map.put("get_species_record") { params ->
val speciesId = params.getString(0).asIdentifierDefaultingNamespace()
pokedex.speciesRecords[speciesId]?.struct ?: QueryStruct(hashMapOf())
}
map.put("has_seen") { params ->
val speciesId = params.getString(0).asIdentifierDefaultingNamespace()
val formName = params.getStringOrNull(1)
if (formName == null) {
return@put DoubleValue(pokedex.getHighestKnowledgeForSpecies(speciesId).ordinal >= PokedexEntryProgress.ENCOUNTERED.ordinal)
} else {
return@put DoubleValue((pokedex.getSpeciesRecord(speciesId)?.getFormRecord(formName)?.knowledge?.ordinal ?: 0) >= PokedexEntryProgress.ENCOUNTERED.ordinal)
}
}
map.put("has_caught") { params ->
val speciesId = params.getString(0).asIdentifierDefaultingNamespace()
val formName = params.getStringOrNull(1)
if (formName == null) {
return@put DoubleValue(pokedex.getHighestKnowledgeForSpecies(speciesId) == PokedexEntryProgress.CAUGHT)
} else {
return@put DoubleValue(pokedex.getSpeciesRecord(speciesId)?.getFormRecord(formName)?.knowledge == PokedexEntryProgress.CAUGHT)
}
}
map.put("caught_count") { DoubleValue(pokedex.getGlobalCalculatedValue(CaughtCount)) }
map.put("seen_count") { DoubleValue(pokedex.getGlobalCalculatedValue(SeenCount)) }
map.put("caught_percent") { DoubleValue(pokedex.getGlobalCalculatedValue(CaughtPercent)) }
map.put("seen_percent") { DoubleValue(pokedex.getGlobalCalculatedValue(SeenPercent)) }
if (pokedex is PokedexManager) {
map.put("player_id") { StringValue(pokedex.uuid.toString()) }
map.put("see") { params ->
val pokemon = params.get<ObjectValue<Pokemon>>(0).obj
pokedex.encounter(pokemon)
return@put DoubleValue.ONE
}
map.put("catch") { params ->
val pokemon = params.get<ObjectValue<Pokemon>>(0).obj
pokedex.catch(pokemon)
return@put DoubleValue.ONE
}
}
map
}
)
val speciesFunctions: MutableList<(Species) -> HashMap<String, java.util.function.Function<MoParams, Any>>> = mutableListOf(
{ species ->
val map = hashMapOf<String, java.util.function.Function<MoParams, Any>>()
map.put("identifier") { StringValue(species.resourceIdentifier.toString()) }
map.put("name") { StringValue(species.name) }
map.put("primary_type") { StringValue(species.primaryType.name) }
map.put("secondary_type") { StringValue(species.secondaryType?.name ?: "null") }
map.put("experience_group") { StringValue(species.experienceGroup.name) }
map.put("height") { DoubleValue(species.height) }
map.put("weight") { DoubleValue(species.weight) }
map.put("base_scale") { DoubleValue(species.baseScale) }
map.put("hitbox_width") { DoubleValue(species.hitbox.width) }
map.put("hitbox_height") { DoubleValue(species.hitbox.height) }
map.put("hitbox_fixed") { DoubleValue(species.hitbox.fixed) }
map.put("catch_rate") { DoubleValue(species.catchRate) }
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 +906,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 +943,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 +963,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,
@@ -433,6 +1022,22 @@ object MoLangFunctions {
return this
}
fun QueryStruct.addPokedexFunctions(pokedexManager: AbstractPokedexManager): QueryStruct {
val addedFunctions = pokedexFunctions
.flatMap { it.invoke(pokedexManager).entries }
.associate { it.key to it.value }
functions.putAll(addedFunctions)
return this
}
fun QueryStruct.addSpeciesFunctions(species: Species): QueryStruct {
val addedFunctions = speciesFunctions
.flatMap { it.invoke(species).entries }
.associate { it.key to it.value }
functions.putAll(addedFunctions)
return this
}
fun QueryStruct.addPokemonFunctions(pokemon: Pokemon): QueryStruct {
val addedFunctions = pokemonFunctions
.flatMap { it.invoke(pokemon).entries }
@@ -0,0 +1,23 @@
/*
* 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.moves
enum class MoveEffectiveness(val showdownId: Int, val multiplier: Double) {
NORMAL(0, 1.0),
WEAKNESS(1, 2.0),
RESISTANCE(2, 0.5),
IMMUNITY(3, 0.0);
companion object {
fun of(showdownId: Int): MoveEffectiveness {
return entries.find { it.showdownId == showdownId }
?: throw IllegalArgumentException("Unknown MoveEffectiveness: $showdownId")
}
}
}
@@ -0,0 +1,48 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.moves
import com.cobblemon.mod.common.api.types.ElementalType
import com.cobblemon.mod.common.api.types.ElementalTypes
import com.cobblemon.mod.common.battles.runner.ShowdownService
object TypeChart {
private val movesEffectiveness = mutableMapOf<ElementalType, MutableMap<ElementalType, MoveEffectiveness>>()
fun load() {
movesEffectiveness.clear()
val typeChartJson = ShowdownService.service.getTypeChart()
typeChartJson.keySet().forEach { type ->
val defenderType = ElementalTypes.get(type) ?: return@forEach
val moveTypes = typeChartJson.get(type).asJsonObject.get("damageTaken").asJsonObject
moveTypes.keySet().forEach innerLoop@ {
val moveType = ElementalTypes.get(it) ?: return@innerLoop
val moveEffectiveness = moveTypes.get(it).asInt
val defenderMap = movesEffectiveness.getOrPut(moveType) { mutableMapOf() }
defenderMap[defenderType] = MoveEffectiveness.of(moveEffectiveness)
}
}
}
fun getMoveEffectiveness(moveType: ElementalType, defenderTypes: List<ElementalType?>): Double {
var multiplier = 1.0
defenderTypes.filterNotNull().forEach {
multiplier *= getMoveEffectiveness(moveType, it).multiplier
}
return multiplier
}
private fun getMoveEffectiveness(moveType: ElementalType, defenderType: ElementalType): MoveEffectiveness {
return movesEffectiveness[moveType]?.get(defenderType) ?: throw IllegalArgumentException("Unknown effectiveness type: $moveType $defenderType")
}
}
@@ -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)
}
@@ -38,7 +38,8 @@ class NPCClass {
var resourceIdentifier: ResourceLocation = cobblemonResource("dummy")
var names: MutableList<Component> = mutableListOf()
var aspects: MutableSet<String> = mutableSetOf() // These only make sense when applied via presets
var hitbox = EntityDimensions.scalable(0.6F, 1.8F)
var hitbox = EntityDimensions.scalable(0.6F, 1.8F).withEyeHeight(1.62F)
var modelScale: Float = 0.94F
var battleConfiguration = NPCBattleConfiguration()
var interaction: NPCInteractConfiguration? = null
var canDespawn = true
@@ -47,9 +48,14 @@ 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 randomizePartyOrder: Boolean = false
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
@@ -79,11 +85,16 @@ class NPCClass {
}
buffer.writeInt(skill)
buffer.writeBoolean(autoHealParty)
buffer.writeBoolean(randomizePartyOrder)
buffer.writeMapK(size = IntSize.U_BYTE, map = variables) { (key, value) ->
buffer.writeString(key)
buffer.writeString(value.asString())
}
buffer.writeNullable(battleTheme) { _, v -> buffer.writeIdentifier(v) }
buffer.writeBoolean(isMovable)
buffer.writeBoolean(isInvulnerable)
buffer.writeBoolean(isLeashable)
buffer.writeBoolean(allowProjectileHits)
}
fun decode(buffer: RegistryFriendlyByteBuf) {
@@ -119,6 +130,7 @@ class NPCClass {
}.toMutableList()
skill = buffer.readInt()
autoHealParty = buffer.readBoolean()
randomizePartyOrder = buffer.readBoolean()
buffer.readMapK(size = IntSize.U_BYTE, map = variables) {
val key = buffer.readString()
val value = buffer.readString()
@@ -129,5 +141,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)
}
@@ -30,7 +30,12 @@ class NPCPreset {
var ai: List<BrainConfig>? = null
var skill: Int? = null
var autoHealParty: Boolean? = null
var randomizePartyOrder: 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 }
@@ -49,7 +54,12 @@ class NPCPreset {
hitbox?.let { npcClass.hitbox = it }
skill?.let { npcClass.skill = it }
autoHealParty?.let { npcClass.autoHealParty = it }
randomizePartyOrder?.let { npcClass.randomizePartyOrder = 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,8 @@ any number of presets and the properties of those presets will be merged into th
},
"canDespawn": false,
"skill": 5,
"autoHealParty": true,
"randomizePartyOrder": true,
"party": {
"type": "simple",
"pokemon": [
@@ -176,19 +179,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 +251,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 +279,136 @@ 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`. If `useFixedRandom` is true, the Pokémon it selects
from the pool will be constant for a single spawned NPC entity. This is slightly different to a static party because
the level of the Pokémon can still be modified dynamically using MoLang values inside each entry's `level` field.
The pool specifies the minimum and maximum number of Pokémon that can be in the party using `minPokemon` and `maxPokemon`.
The `pool` property dictates all 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.
- The `level` property can be used instead of `levelVariation` to force a specific level. This is a MoLang expression.
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": false,
"useFixedRandom": 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",
"level": "q.player.party.highest_level"
}
]
}
#### 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.
### randomizePartyOrder
Whether the NPC's party order will be randomized between battles. When true, this NPC's party (regardless of
whether it was dynamic or static) will choose its initial send-out Pokémon randomly instead of always choosing the first
Pokémon in the party.
### 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,8 @@ import com.cobblemon.mod.common.util.withQueryValue
import com.google.gson.JsonElement
import com.google.gson.JsonObject
import com.google.gson.JsonPrimitive
import kotlin.random.Random
import net.minecraft.server.level.ServerPlayer
/**
* A provider of a party for battling the NPC. It generates a party using a more complex process
@@ -46,6 +51,7 @@ class PoolPartyProvider : NPCPartyProvider {
override val type = TYPE
override var isStatic: Boolean = false
var useFixedRandom: Boolean = false
var minPokemon: Expression = "1".asExpression()
var maxPokemon: Expression = "6".asExpression()
var pool = mutableListOf<DynamicPokemon>()
@@ -58,8 +64,9 @@ class PoolPartyProvider : NPCPartyProvider {
if (it is JsonPrimitive) {
return@map DynamicPokemon(
PokemonProperties.parse(it.asString),
0..0,
0..100,
"0".asExpression(),
null,
1..100,
"1".asExpression(),
"1".asExpression()
)
@@ -67,19 +74,22 @@ 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("selectableTimes")?.asString?.asExpression() ?: "1".asExpression(),
it.getAsJsonPrimitive("weight")?.asString?.asExpression() ?: "1".asExpression()
PokemonProperties.parse(it.get("pokemon").asString),
it.get("levelVariation")?.asString?.asExpression() ?: "0".asExpression(),
it.get("level")?.asString?.asExpression(),
it.get("npcLevels")?.asString?.split("-")?.let { it[0].toInt()..it[1].toInt() } ?: 1..100,
it.get("selectableTimes")?.asString?.asExpression() ?: "1".asExpression(),
it.get("weight")?.asString?.asExpression() ?: "1".asExpression()
)
}.toMutableList()
isStatic = json.getAsJsonPrimitive("isStatic").asBoolean
isStatic = json.get("isStatic").asBoolean
json.get("useFixedRandom")?.asBoolean?.let { useFixedRandom = it }
}
class DynamicPokemon(
val pokemon: PokemonProperties,
val levelVariation: IntRange,
val levelVariation: Expression,
val level: Expression? = null,
val npcLevels: IntRange,
val selectableTimes: Expression,
val weight: Expression = "1".asExpression()
@@ -93,18 +103,25 @@ 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)
val random = if (useFixedRandom) Random(npc.uuid.hashCode()) else Random.Default
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()
var desiredPokemonCount = (minPokemon..maxPokemon).random(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()) {
val selected = workingPool.filter { it.getWeight(runtime) == -1F }.randomOrNull()
?: workingPool.weightedSelection { it.getWeight(runtime) }
while (desiredPokemonCount > 0 && workingPool.any { it.hasWeight(runtime) }) {
val selected = workingPool.filter { it.getWeight(runtime) == -1F }.randomOrNull(random)
?: workingPool.weightedSelection(random = random) { it.getWeight(runtime) }
?: break
useCounts[selected] = useCounts.getOrDefault(selected, 0) + 1
val allowedSelections = runtime.resolveInt(selected.selectableTimes)
@@ -114,15 +131,17 @@ 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 instance = selected.pokemon.copy().also { it.level = it.level ?: randomLevel }.create()
val levelVariation = (0..runtime.resolveInt(selected.levelVariation)).random(random)
val randomLevel = level + levelVariation
val dictatedLevel = selected.level?.let { runtime.resolveInt(it) }
val instance = selected.pokemon.copy().also { it.level = it.level ?: dictatedLevel ?: 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)
@@ -32,5 +32,5 @@ class WeightedNPCVariationProvider : NPCVariationProvider {
override val aspects: Set<String>
get() = options.flatMap(WeightedAspect::aspects).toSet()
override fun provideAspects(npcEntity: NPCEntity) = options.weightedSelection(WeightedAspect::weight)?.aspects ?: emptySet()
override fun provideAspects(npcEntity: NPCEntity) = options.weightedSelection { it.weight }?.aspects ?: emptySet()
}
@@ -76,11 +76,14 @@ 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)
val ABANDON_MULTITEAM = this.create("${COMMAND_PREFIX}abandonmultiteam", PermissionLevel.NONE)
val RUN_MOLANG_SCRIPT = this.create("${COMMAND_PREFIX}runmolangscript", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
fun all(): Iterable<Permission> = this.permissions
private fun create(node: String, level: PermissionLevel): Permission {
@@ -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
}
@@ -8,13 +8,13 @@
package com.cobblemon.mod.common.api.pokedex
import com.bedrockk.molang.runtime.struct.QueryStruct
import com.cobblemon.mod.common.api.molang.MoLangFunctions.addPokedexFunctions
import com.cobblemon.mod.common.api.molang.MoLangFunctions.addStandardFunctions
import com.cobblemon.mod.common.api.molang.ObjectValue
import com.cobblemon.mod.common.api.pokedex.entry.PokedexEntry
import com.cobblemon.mod.common.api.pokedex.entry.PokedexForm
import com.cobblemon.mod.common.pokedex.scanner.PokedexEntityData
import com.cobblemon.mod.common.pokemon.Gender
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import net.minecraft.resources.ResourceLocation
abstract class AbstractPokedexManager {
@@ -23,11 +23,9 @@ abstract class AbstractPokedexManager {
private val globalCalculatedValues = mutableMapOf<GlobalPokedexValueCalculator<*>, Any>()
@Transient
val struct = QueryStruct(hashMapOf()).addStandardFunctions()
.addFunction("get_species_record") { params ->
val speciesId = params.getString(0).asIdentifierDefaultingNamespace()
speciesRecords[speciesId]?.struct ?: QueryStruct(hashMapOf())
}
val struct = ObjectValue<AbstractPokedexManager>(this)
.addStandardFunctions()
.addPokedexFunctions(this)
fun deleteSpeciesRecord(speciesId: ResourceLocation) {
speciesRecords.remove(speciesId)
@@ -42,7 +40,6 @@ abstract class AbstractPokedexManager {
return speciesRecords.getOrPut(speciesId) {
val record = SpeciesDexRecord()
record.initialize(this, speciesId)
onSpeciesRecordUpdated(record)
record
}
}
@@ -95,7 +92,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
@@ -11,6 +11,8 @@ package com.cobblemon.mod.common.api.pokedex
import com.bedrockk.molang.runtime.struct.QueryStruct
import com.bedrockk.molang.runtime.struct.VariableStruct
import com.bedrockk.molang.runtime.value.StringValue
import com.cobblemon.mod.common.api.events.CobblemonEvents
import com.cobblemon.mod.common.api.events.pokemon.PokedexDataChangedEvent
import com.cobblemon.mod.common.pokedex.scanner.PokedexEntityData
import com.cobblemon.mod.common.pokemon.Gender
import com.cobblemon.mod.common.pokemon.Pokemon
@@ -19,6 +21,7 @@ import com.cobblemon.mod.common.util.readString
import com.cobblemon.mod.common.util.writeEnumConstant
import com.cobblemon.mod.common.util.writeString
import com.google.common.collect.Sets
import com.mojang.datafixers.util.Either
import com.mojang.serialization.Codec
import com.mojang.serialization.codecs.ListCodec
import com.mojang.serialization.codecs.RecordCodecBuilder
@@ -76,6 +79,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)
@@ -117,6 +126,10 @@ class FormDexRecord {
}
speciesDexRecord.addInformation(pokemon, knowledge)
speciesDexRecord.onFormRecordUpdated(this)
(speciesDexRecord.pokedexManager as? PokedexManager)?.let { pokedexManager ->
CobblemonEvents.POKEDEX_DATA_CHANGED.post(PokedexDataChangedEvent(Either.right(pokemon), knowledge, pokedexManager.uuid, this))
}
}
private fun addInformation(pokedexEntityData: PokedexEntityData, knowledge: PokedexEntryProgress) {
@@ -127,6 +140,10 @@ class FormDexRecord {
}
speciesDexRecord.addInformation(pokedexEntityData, knowledge)
speciesDexRecord.onFormRecordUpdated(this)
(speciesDexRecord.pokedexManager as? PokedexManager)?.let { pokedexManager ->
CobblemonEvents.POKEDEX_DATA_CHANGED.post(PokedexDataChangedEvent(Either.left(pokedexEntityData), knowledge, pokedexManager.uuid, this))
}
}
/** Returns whether the given [Pokemon] and [knowledge] would add new information to the Pokédex.*/
@@ -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
@@ -19,6 +19,7 @@ import com.cobblemon.mod.common.api.drop.ItemDropMethod
import com.cobblemon.mod.common.api.entity.EntityDimensionsAdapter
import com.cobblemon.mod.common.api.molang.ExpressionLike
import com.cobblemon.mod.common.api.moves.MoveTemplate
import com.cobblemon.mod.common.api.moves.TypeChart
import com.cobblemon.mod.common.api.moves.adapters.MoveTemplateAdapter
import com.cobblemon.mod.common.api.pokemon.effect.ShoulderEffect
import com.cobblemon.mod.common.api.pokemon.effect.adapter.ShoulderEffectAdapter
@@ -125,6 +126,7 @@ object PokemonSpecies : JsonDataRegistry<Species> {
it.indicateSpeciesInitialized()
// Reload this with the mod
CobblemonHeldItemManager.load()
TypeChart.load()
Cobblemon.LOGGER.info("Loaded {} Pokémon species", this.speciesByIdentifier.size)
this.observable.emit(this)
}
@@ -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
@@ -12,6 +12,8 @@ import com.cobblemon.mod.common.api.conditional.RegistryLikeCondition
import com.cobblemon.mod.common.api.spawning.context.FishingSpawningContext
import com.cobblemon.mod.common.util.itemRegistry
import net.minecraft.world.item.Item
import net.minecraft.world.item.enchantment.EnchantmentHelper
import net.minecraft.world.item.enchantment.Enchantments
import net.minecraft.world.level.block.Block
/**
@@ -33,9 +35,43 @@ class FishingSpawningCondition: SpawningCondition<FishingSpawningContext>() {
return false
} else if (neededNearbyBlocks != null && neededNearbyBlocks!!.none { cond -> ctx.nearbyBlockTypes.any { cond.fits(it, ctx.blockRegistry) } }) {
return false
} else {
return true
}
if (minLureLevel != null) { // check for the lureLevel of the rod
val pokerodStack = ctx.rodStack
val lureLevel = EnchantmentHelper.getItemEnchantmentLevel(
ctx.enchantmentRegistry.getHolder(Enchantments.LURE).get(),
pokerodStack
)
if (lureLevel < minLureLevel!!) {
return false
} else if (maxLureLevel != null && lureLevel > maxLureLevel!!) {
return false
}
}
if (bait != null) { // check for the bait on the bobber
val pokerodBait = ctx.rodBait?.item
if (pokerodBait != bait) {
return false
}
}
if (rodType != null) { // check for the type of pokerod being used
val pokerodItem = ctx.rodItem
if (pokerodItem?.pokeRodId != rodType) {
return false
}
}
/*if (ctx is FishingSpawningContext && (ctx as FishingSpawningContext).rodItem != null) { // check if the bait attracts certain EV yields
val pokerodItem = (ctx as FishingSpawningContext).rodItem
// todo check if the EV yield of the berry matches the bait EV attract maybe?
if (// todo if bait EV yield != EV yield of pokemon consideration //Registries.ITEM.getId(pokerodItem?.bait?.item).path == )
return false
}*/
return true
}
companion object {
@@ -116,24 +116,6 @@ abstract class SpawningCondition<T : SpawningContext> {
}
) {
return false
} else if (minLureLevel != null && ctx is FishingSpawningContext) { // check for the lureLevel of the rod
val pokerodStack = (ctx as FishingSpawningContext).rodStack
val lureLevel = EnchantmentHelper.getItemEnchantmentLevel(ctx.enchantmentRegistry.getHolder(Enchantments.LURE).get(), pokerodStack)
if (lureLevel < minLureLevel!!) {
return false
} else if (maxLureLevel != null && lureLevel > maxLureLevel!!) {
return false
}
} else if (bait != null && ctx is FishingSpawningContext) { // check for the bait on the bobber
val pokerodBait = (ctx as FishingSpawningContext).rodBait?.item
if (pokerodBait != bait) {
return false
}
} else if (rodType != null && ctx is FishingSpawningContext) { // check for the type of pokerod being used
val pokerodItem = (ctx as FishingSpawningContext).rodItem
if (pokerodItem?.pokeRodId != rodType) {
return false
}
} else if (isSlimeChunk != null && isSlimeChunk != false) {
val isSlimeChunk = WorldgenRandom.seedSlimeChunk(ctx.position.x shr 4, ctx.position.z shr 4, ctx.world.seed, 987234911L).nextInt(10) == 0
@@ -152,15 +134,6 @@ abstract class SpawningCondition<T : SpawningContext> {
return random.nextInt(10) == 0*/
}
/*else if (ctx is FishingSpawningContext && (ctx as FishingSpawningContext).rodItem != null) { // check if the bait attracts certain EV yields
val pokerodItem = (ctx as FishingSpawningContext).rodItem
// todo check if the EV yield of the berry matches the bait EV attract maybe?
if (// todo if bait EV yield != EV yield of pokemon consideration //Registries.ITEM.getId(pokerodItem?.bait?.item).path == )
return false
}*/
return true
}
@@ -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)
}
@@ -16,7 +16,6 @@ import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.pokemon.feature.SeasonFeatureHandler
import com.cobblemon.mod.common.util.weightedSelection
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.entity.player.Player
/**
* A [SpawnAction] that will spawn a single [PokemonEntity].
@@ -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] }
@@ -266,7 +269,7 @@ open class PartyStore(override val uuid: UUID) : PokemonStore<PartyPosition>() {
BattlePokemon.playerOwned(it)
}.also { if (healPokemon) it.effectedPokemon.heal() }
}.toMutableList()
if(leadingPokemon != null) {
if (leadingPokemon != null) {
Collections.rotate(result, result.size - this.indexOfFirst { it.uuid == leadingPokemon })
}
return result
@@ -102,20 +102,32 @@ open class PlayerPartyStore(
for (pokemon in this) {
// Awake from fainted
if (pokemon.isFainted()) {
pokemon.faintedTimer -= 1
if (pokemon.faintedTimer <= -1) {
val php = ceil(pokemon.maxHealth * Cobblemon.config.faintAwakenHealthPercent)
pokemon.currentHealth = php.toInt()
player.sendSystemMessage(Component.translatable("cobblemon.party.faintRecover", pokemon.getDisplayName()))
//Skip awaken logic check if config value is 0
if (Cobblemon.config.faintAwakenHealthPercent > 0) {
pokemon.faintedTimer -= 1
if (pokemon.faintedTimer <= -1) {
val php = ceil(pokemon.maxHealth * Cobblemon.config.faintAwakenHealthPercent)
pokemon.currentHealth = php.toInt()
player.sendSystemMessage(
Component.translatable(
"cobblemon.party.faintRecover",
pokemon.getDisplayName()
)
)
}
}
}
// Passive healing while less than full health
else if (pokemon.currentHealth < pokemon.maxHealth) {
pokemon.healTimer--
if (pokemon.healTimer <= -1) {
pokemon.healTimer = Cobblemon.config.healTimer
val healAmount = 1.0.coerceAtLeast(pokemon.maxHealth.toDouble() * Cobblemon.config.healPercent)
pokemon.currentHealth = pokemon.currentHealth + round(healAmount).toInt()
//Skip passive healing logic check if config value is 0
if (Cobblemon.config.healPercent > 0) {
pokemon.healTimer--
if (pokemon.healTimer <= -1) {
pokemon.healTimer = Cobblemon.config.healTimer
val healAmount =
1.0.coerceAtLeast(pokemon.maxHealth.toDouble() * Cobblemon.config.healPercent)
pokemon.currentHealth = pokemon.currentHealth + round(healAmount).toInt()
}
}
}
@@ -204,4 +216,4 @@ open class PlayerPartyStore(
super.set(position, pokemon)
pokemon.getOwnerPlayer()?.let { CobblemonCriteria.PARTY_CHECK.trigger(it, this) }
}
}
}
@@ -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)
@@ -100,11 +100,18 @@ object BattleBuilder {
)
}
if (actor.pokemonList.any { it.entity?.isBusy == true }) {
errors.participantErrors[actor] += BattleStartError.targetIsBusy(player.displayName ?: player.name)
}
if (BattleRegistry.getBattleByParticipatingPlayer(player) != null) {
errors.participantErrors[actor] += BattleStartError.alreadyInBattle(player)
}
}
player1Actor.battleTheme = player2.getBattleTheme()
player2Actor.battleTheme = player1.getBattleTheme()
return if (errors.isEmpty) {
var result: BattleStartResult = errors
BattleRegistry.startBattle(
@@ -112,8 +119,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)
}
@@ -177,6 +182,10 @@ object BattleBuilder {
)
}
if (actor.pokemonList.any { it.entity?.isBusy == true }) {
errors.participantErrors[actor] += BattleStartError.targetIsBusy(player.displayName ?: player.name)
}
if (BattleRegistry.getBattleByParticipatingPlayer(player) != null) {
errors.participantErrors[actor] += BattleStartError.alreadyInBattle(player)
}
@@ -197,19 +206,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
@@ -248,7 +256,7 @@ object BattleBuilder {
val wildActor = PokemonBattleActor(pokemonEntity.pokemon.uuid, BattlePokemon(pokemonEntity.pokemon), fleeDistance)
val errors = ErroredBattleStart()
if(playerTeam.isNotEmpty() && playerTeam[0].health <= 0){
if (playerTeam.isNotEmpty() && playerTeam[0].health <= 0){
errors.participantErrors[playerActor] += BattleStartError.insufficientPokemon(
actorEntity = player,
requiredCount = battleFormat.battleType.slotsPerActor,
@@ -264,6 +272,10 @@ object BattleBuilder {
)
}
if (playerActor.pokemonList.any { it.entity?.isBusy == true }) {
errors.participantErrors[playerActor] += BattleStartError.targetIsBusy(player.displayName ?: player.name)
}
if (BattleRegistry.getBattleByParticipatingPlayer(player) != null) {
errors.participantErrors[playerActor] += BattleStartError.alreadyInBattle(playerActor)
}
@@ -272,6 +284,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 +296,6 @@ object BattleBuilder {
if (!cloneParties) {
pokemonEntity.battleId = it.battleId
}
playerActor.battleTheme = pokemonEntity.getBattleTheme()
result = SuccessfulBattleStart(it)
}
result
@@ -314,7 +327,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 +360,12 @@ object BattleBuilder {
)
}
if (playerActor.pokemonList.any { it.entity?.isBusy == true }) {
errors.participantErrors[playerActor] += BattleStartError.targetIsBusy(player.displayName ?: player.name)
}
playerActor.battleTheme = npcEntity.getBattleTheme()
return if (errors.isEmpty) {
var result: BattleStartResult = errors
BattleRegistry.startBattle(
@@ -354,7 +373,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)
}
@@ -392,7 +410,7 @@ interface BattleStartError {
fun alreadyInBattle(pokemonEntity: PokemonEntity) = AlreadyInBattleError(pokemonEntity.uuid, pokemonEntity.effectiveName())
fun alreadyInBattle(actor: BattleActor) = AlreadyInBattleError(actor.uuid, actor.getName())
fun noParty(npcEntity: NPCEntity) = NoPartyError(npcEntity)
fun targetIsBusy(targetName: MutableComponent) = BusyError(targetName)
fun targetIsBusy(targetName: Component) = BusyError(targetName)
fun insufficientPokemon(
actorEntity: Entity,
requiredCount: Int,
@@ -472,7 +490,7 @@ class AlreadyInBattleError(
}
}
class BusyError(
val targetName: MutableComponent
val targetName: Component
): BattleStartError {
override fun getMessageFor(entity: Entity) = battleLang("errors.busy", targetName)
}
@@ -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
}
}

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