Compare commits

..
134 Commits
Author SHA1 Message Date
Test bc0d530484 loisence 2026-01-20 19:19:53 -05:00
Test 1537816132 log of change 2026-01-20 00:56:36 -05:00
Test 1705639950 loot 2026-01-20 00:50:04 -05:00
Test ce32541dd6 Merge branch 'main' of https://gitlab.com/cable-mc/cobblemon into decor-split 2026-01-19 23:45:28 -05:00
Test 3ffea4f4dd split items 2026-01-19 23:44:20 -05:00
Hiroku f942d7c34a Merge branch 'misc-additions-fixes' into 'main'
Couple of misc API additions and bug fixes.

See merge request cable-mc/cobblemon!2125
2026-01-19 02:16:09 +00:00
Deltric 0f1ada4624 Pass in pnx to the event 2026-01-18 18:52:27 -06:00
Deltric b4ee4629bf Couple of misc API additions and bug fixes. 2026-01-18 17:57:28 -06:00
Paul d0b64e2d82 Respect the BYPASSES_INVULNERABILITY damage tag 2026-01-18 22:12:25 +10:00
Paul 1ed2042283 Fixed /calculateseatpositions expecting no underscore when actually we always use an underscore 2026-01-17 22:32:57 +10:00
Hiroku 948ca61ef3 Merge branch 'fix-super-potion-ingredients-tag' into 'main'
Fix super potion ingredients tag

See merge request cable-mc/cobblemon!2121
2026-01-16 08:27:19 +00:00
Hiroku e60aca30ba Merge branch 'patch-2' into 'main'
Fix Syrupy Apple missing from evolution items tag

See merge request cable-mc/cobblemon!2120
2026-01-16 08:26:22 +00:00
Hiroku fb5055c39e Merge branch 'form_crash' into 'main'
Initialize FormData when updating

Closes #1916

See merge request cable-mc/cobblemon!2122
2026-01-16 08:20:23 +00:00
Paul dfb5650121 Merge remote-tracking branch 'origin/main' 2026-01-16 18:17:01 +10:00
Paul 8a0cbf28fe Fixed battles not ending fully when a switch is in progress, closes #1917 2026-01-16 18:16:11 +10:00
aeroeng15 a6b9060d57 Changelog 2026-01-15 20:54:54 -07:00
aeroeng15 8e57995e44 Make It Proper 2026-01-15 20:51:59 -07:00
aeroeng15 1d8257808c Initialize rather than set; seems to fix the issue 2026-01-15 20:42:14 -07:00
Faintful e3582ccfb4 Add changelog 2026-01-14 19:49:11 +01:00
Faintful 4c6822b1c6 Merge remote-tracking branch 'upstream/main' into fix-super-potion-ingredients-tag 2026-01-14 19:46:54 +01:00
Faintful 2e6a4c7341 Change Hondew for Aguav 2026-01-14 19:28:49 +01:00
Micky 8edb5e1013 Fix Syrupy Apple missing from evolution items tag 2026-01-14 18:25:06 +00:00
Polymeta 6fb139cbb9 Merge branch 'pokemon-interactions-fixes' into 'main'
Pokémon Interaction fixes

See merge request cable-mc/cobblemon!2117
2026-01-14 00:03:33 +00:00
lionandPolymeta c81c95142b Pokémon Interaction fixes 2026-01-14 00:03:32 +00:00
Hiroku afde822860 Edit CHANGELOG.md [skip-ci] 2026-01-12 10:50:47 +00:00
Hiroku 474c386535 Merge branch 'main' into 'main'
Fix Fresh Start Mochi EV Reset

See merge request cable-mc/cobblemon!2115
2026-01-12 10:48:40 +00:00
ToothCollector 71b9ae8dee add fix to CHANGELOG.md 2026-01-11 21:31:22 +02:00
ToothCollector b87b47d62a Merge branch 'main' of https://gitlab.com/cable-mc/cobblemon 2026-01-11 21:28:04 +02:00
Hiroku 2bb85718d7 Merge branch 'main' into 'main'
Fix Adorn compatibility

See merge request cable-mc/cobblemon!2112
2026-01-11 12:21:25 +00:00
Tate Frost ee187d7b89 Delete file + update Adorn resource pack metadata
- Delete config file that was committed in error
- Update Adorn resource pack metadata to reflect new wood type
2026-01-10 22:30:27 -06:00
Tate Frost 3532cac278 Update CHANGELOG.md 2026-01-10 22:18:01 -06:00
Hiroku 1447ab25b4 Merge branch 'CombeeWorldGenFix' into 'main'
CombeeWorldGenFix

See merge request cable-mc/cobblemon!2071
2026-01-11 02:55:56 +00:00
Hiroku 9a06ca78c6 Merge branch 'fix-mongo-driver' into 'main'
Fix MongoDB ClassNotFoundException by including drivers at runtime

See merge request cable-mc/cobblemon!2098
2026-01-11 02:47:10 +00:00
Hiroku f6ba70634b Merge branch 'double-battle-gimmicks' into 'main'
Fix double battle gimmicks not being processed

See merge request cable-mc/cobblemon!2094
2026-01-11 02:46:36 +00:00
Hiroku 148fd04a93 Merge branch 'molang-evaluate_expression' into 'main'
molang addition: exception handling for run_molang

See merge request cable-mc/cobblemon!2102
2026-01-11 02:12:07 +00:00
Hiroku 17f60623ee Merge branch 'effect-id-regex-update' into 'main'
Custom messages for datapack moves and abilities

See merge request cable-mc/cobblemon!2106
2026-01-11 02:08:42 +00:00
Hiroku c5ece27c11 Merge branch '1861-galarien_mrmime_mount' into 'main'
Fix Galarian Mr. Mime incorrectly being rideable.

Closes #1861

See merge request cable-mc/cobblemon!2107
2026-01-11 02:02:14 +00:00
Hiroku 296ccd1a76 Merge branch 'main' into 'main'
Fix pasture block incorrect hit-box when facing north and south.

See merge request cable-mc/cobblemon!2109
2026-01-11 02:01:44 +00:00
ToothCollector 82a4f71f24 fix(FreshStartMochiItem.kt): Set all Pokemon's EVs to zero with the appropriate method. 2026-01-10 17:10:15 +02:00
Tate Frost 08b58e2f78 Fix Adorn compatibility
- Fixed Apricorn block model rotation for counters/sinks/cupboards/drawers to match that of most recent version of Adorn
- Added Saccharine wood + Adorn compatibility
2026-01-07 02:04:52 -06:00
Merv 62d2b8db2d Edit CHANGELOG.md 2026-01-06 18:47:45 +00:00
Merv 3869f7ed93 Merge branch cobblemon:main into main 2026-01-05 23:09:08 +00:00
Dragonwhisper92 c9c44872d6 Updated Changelog. 2026-01-05 19:35:05 +00:00
Dragonwhisper92 cae53efedc Updated ActivateInstruction.kt to have an optional message parameter. 2026-01-05 19:14:40 +00:00
Polymeta 5fac24cee8 Merge branch 'Molang-SimpleDateFormat-Fix' into 'main'
Fixed Molang functions:

See merge request cable-mc/cobblemon!2105
2026-01-05 11:22:45 +00:00
Polymeta 1954dfec80 Merge branch '1784-riding_stats_order' into 'main'
Fix riding stat boosts to always render in the same order

Closes #1784

See merge request cable-mc/cobblemon!2108
2026-01-05 11:20:29 +00:00
Landon 2eea2b64c2 Merge branch 'bag-clause' into 'main'
Bag Clause (Preventing Item use in Battles)

See merge request cable-mc/cobblemon!1849
2026-01-05 08:12:36 +00:00
GDWR a127a93be8 Sort boosts when rendering to remove the jumping of stats between items
The sort uses the key. As `RidingStat` it sorts them by the enum value.
2026-01-04 20:27:42 +00:00
GDWR 9b32f85817 Set an empty riding definition for Galarian Mr. Mime to avoid inheriting the default definition 2026-01-04 17:28:02 +00:00
Dragonwhisper92 ec139d2677 Updated Effect.kt to use a custom REGEX entry 2026-01-04 16:46:44 +00:00
Landon Wills 4327be0cf9 Omit bag clause from getting sent to showdown 2026-01-04 02:22:32 -05:00
Landon Wills c25c832b31 Remove import 2026-01-04 01:21:20 -05:00
Landon Wills eabaabc535 Move bag clause check to bag items instead of pokemon entity 2026-01-04 01:19:26 -05:00
Dragonwhisper92 9acf97f5a0 Updated CHANGELOG.md 2026-01-04 00:18:24 +00:00
Dragonwhisper92 24fc212c76 Updated CHANGELOG.md 2026-01-04 00:15:32 +00:00
Hiroku 04f88bdd63 Merge branch 'form-change-move-sanitization-fix' into 'main'
Fix the improper sanitization of moves on mons with compounding properties

Closes #1853 and #1846

See merge request cable-mc/cobblemon!2085
2026-01-04 00:05:26 +00:00
Dragonwhisper92 a4eb58aa57 Fixed molang functions:
- date_local_time
- date_of
- date_is_after
too use the correct SimpleDateFormat of "dd/MM/yyyy"
2026-01-03 23:40:12 +00:00
Merv 6704d4e5ee Maybe fix pasture block incorrect hitbox 2026-01-03 02:05:03 +00:00
Hiroku c88087de29 Merge branch 'main' into 'main'
Small improvements to the Battle AI

See merge request cable-mc/cobblemon!2101
2026-01-02 00:38:26 +00:00
Konoha 86bb802140 adjustment: exception handling to run_molang 2026-01-01 11:44:29 -05:00
GGavino1 6b644b384d Updated CHANGELOG.md to include AI changes 2026-01-01 15:00:15 +00:00
Konoha c370af8914 feature: molang function for safe evaluation of booleans 2025-12-31 22:23:50 -05:00
GGavino1 1cd6d2bfc7 Edit StrongBattleAI.kt 2025-12-31 23:15:52 +00:00
Test fc720916fb wall block properties 2025-12-31 00:24:39 -05:00
Konoha 1edeae2a60 fix: let's actually fix 2025-12-27 09:48:49 -05:00
mkh 409f1056b9 fix: Add MongoDB driver dependency to Fabric/NeoForge build configuration and shade it into the mod jar to resolve ClassNotFoundException. 2025-12-26 17:31:46 +08:00
Landon Wills d8c470db3d Oops, forgot a file 2025-12-23 01:28:50 -05:00
Landon Wills c642c4b84b Update changelog 2025-12-23 01:22:01 -05:00
Landon Wills 0b80f7e547 Make the gimmick buttons work with the back button 2025-12-23 01:18:35 -05:00
Landon Wills d543f45607 Fix double battle gimmicks not being processed, prevent people from trying to use the same gimmick multiple times on the same turn 2025-12-23 00:43:31 -05:00
Test e5699c1cdc plaques, policies 2025-12-22 01:34:10 -05:00
Konoha 2b4fdfd219 changelog: sanitization fix 2025-12-21 15:40:17 -05:00
Hiroku 1a3b655d3f Merge branch 'the-tiniest-of-tiny-changes' into 'main'
Edit pokemon_model.json

See merge request cable-mc/cobblemon!2091
2025-12-20 12:41:36 +00:00
dhi_holo 1f5f278970 Edit pokemon_model.json 2025-12-20 21:25:12 +10:00
Hiroku 0b4eb5239a Merge branch 'in-flight-dismounting-config' into 'main'
Add enable in-flight dismounting config

See merge request cable-mc/cobblemon!2079
2025-12-20 06:01:12 +00:00
lionandHiroku 118e8f6205 Add enable in-flight dismounting config 2025-12-20 06:01:12 +00:00
Hiroku cf126c5906 Merge branch 'molang-is-love-molang-is-life' into 'main'
Fixed the `run_script` Molang function to not fail if the environment's context is null.

See merge request cable-mc/cobblemon!2090
2025-12-20 01:59:22 +00:00
Spicy 645cf61911 Fixed the run_script Molang function to not fail if the environment's context is null. 2025-12-19 19:48:27 -06:00
Hiroku 87d5ede3dd Merge branch 'RestoreCombeeLeavesPathing' into 'main'
RestoreCombeeLeavesPathing

See merge request cable-mc/cobblemon!2046
2025-12-19 07:36:50 +00:00
Hiroku 203e5d89cd Merge branch 'label-consistency' into 'main'
Who are we if not consistent?

See merge request cable-mc/cobblemon!1985
2025-12-19 07:36:21 +00:00
Konoha ce611cc61c cleanup: fix redundancies in form change sanitization 2025-12-18 21:17:48 -05:00
JazzMcNade f6ad7ff865 Fix changelog 2025-12-17 21:08:46 -06:00
JazzMcNade d69627a9c8 Merge branch 'main' into CombeeWorldGenFix 2025-12-17 20:05:09 -06:00
JazzMcNade 54e8763470 Fixed a bug in which Combees could become stuck inside of leaves 2025-12-13 21:17:59 -06:00
JazzMcNade db34687df0 Merge branch 'main' into RestoreCombeeLeavesPathing 2025-12-13 21:11:57 -06:00
Konoha 7f008d85ff fix: form_change move updates no longer sanitize the sets of compounding property mons 2025-12-13 18:37:37 -05:00
Polymeta 06ebf51078 Merge branch 'Wii-Break-It-Wii-Fix-It' into 'main'
Boat Tags and Mint Leaves

Closes #1855

See merge request cable-mc/cobblemon!2082
2025-12-13 22:14:32 +00:00
Polymeta 39c5bcd73c Merge branch 'even-more-seasonings' into 'main'
Adding Missing Effects for Seasonings

See merge request cable-mc/cobblemon!1991
2025-12-13 22:13:24 +00:00
CeejayandPolymeta 8f5f158b60 Adding Missing Effects for Seasonings 2025-12-13 22:13:24 +00:00
dhi_holo 3bc1e35125 Update CHANGELOG.md 2025-12-13 18:42:50 +10:00
dhi_holo 460bedddae Boat Tags
Resolves #1855
2025-12-13 18:12:24 +10:00
dhi_holo 8b64c315f3 Merge branch 'main' into Wii-Break-It-Wii-Fix-It 2025-12-13 17:51:15 +10:00
dhi_holo 7c824a2b72 OVERSIGHTS 2025-12-13 17:50:57 +10:00
Paul f197ac5077 Removed a bunch of duplicates from is_held_item because it looks whack. 2025-12-12 16:24:30 +10:00
Hiroku e0be669482 Merge branch 'patch-1' into 'main'
Fix Soothe Bell not tagged as `cobblemon:held/is_held_item`

See merge request cable-mc/cobblemon!2081
2025-12-12 06:17:17 +00:00
Micky 08ea21fff4 Fix Soothe Bell not tagged as cobblemon:held/is_held_item 2025-12-10 13:27:23 +00:00
JazzMcNade 9dac604a93 Merge branch 'main' into RestoreCombeeLeavesPathing 2025-12-10 00:20:49 -06:00
Polymeta 28c0f8ab7d Merge branch 'item-interactions-sync' into 'main'
Item interactions sync

See merge request cable-mc/cobblemon!2068
2025-12-08 16:08:48 +00:00
lionandPolymeta fcbe0db568 Item interactions sync 2025-12-08 16:08:48 +00:00
Polymeta 83cefe476e Merge branch 'partner-mark' into 'main'
Add Partner Mark

See merge request cable-mc/cobblemon!2034
2025-12-08 15:57:21 +00:00
Guilherme Pereira 690ccbd260 refactor check riding statistics method for server player mixin 2025-12-08 12:06:20 -03:00
Polymeta 63846d53fb Merge branch 'main' into 'main'
Fixed shiny Pokémon not respecting the silent flag on spawn.

See merge request cable-mc/cobblemon!2064
2025-12-08 13:07:25 +00:00
Polymeta 86cb865742 fixed mochi items being consumed twice
Signed-off-by: Polymeta <40173118+Polymeta@users.noreply.github.com>
2025-12-08 13:01:14 +00:00
Polymeta 1a0c9b6a82 Merge branch 'malformed-jsons-no-more' into 'main'
Fixes a crash caused by malformed riding prespectives json file when trying to ride a pokemon

See merge request cable-mc/cobblemon!2077
2025-12-08 12:59:22 +00:00
Polymeta 985eb103fe Merge branch 'sterr/ride-sound-changes' into 'main'
New ride sound stuff

See merge request cable-mc/cobblemon!2065
2025-12-08 11:44:22 +00:00
Polymeta aec76a78da Merge branch 'restore_adorn_texturing' into 'main'
Restore adorn texturing

Closes #1833

See merge request cable-mc/cobblemon!2078
2025-12-08 11:36:41 +00:00
Jacob Hartmann 17f3edf2be Changelog 2025-12-07 20:35:11 -07:00
Jacob Hartmann 9c5bfda20f Updating texture paths 2025-12-07 20:34:09 -07:00
Gertimoshka 6d545fc1ca Fixes a crash caused by malformed riding prespectives json file when trying to ride a pokemon 2025-12-07 11:47:04 +02:00
JazzMcNade 8540a73885 Fix for error occurring while populating bee nests with pokemon on world gen 2025-12-04 01:38:56 -06:00
Khvrram 7e6ca7be5e Merge branch cobblemon:main into main 2025-12-04 01:59:17 +00:00
Neovitalism 25ce784a60 Hopefully fixes changelog
Idk why gitlab didn't detect that when merging main
2025-12-03 10:17:48 -06:00
Neovitalism 5358cccb62 Merge branch 'main' into label-consistency 2025-12-03 10:09:41 -06:00
Sterrezo a575c6bc3d Update CHANGELOG.md 2025-12-01 15:47:34 +01:00
Sterrezo 9c102a7850 Merge branch 'main' into sterr/ride-sound-changes 2025-12-01 15:45:40 +01:00
Sterrezo 22a1eaab19 Volume balancing 2025-12-01 15:35:22 +01:00
Sterrezo 0118ace5fc Implemented stereo flight loops 2025-12-01 15:09:37 +01:00
Sterrezo d4a93abbf7 Added support for submerged ride sounds 2025-12-01 14:27:55 +01:00
Sterrezo d0ee3f3e9d Added an option for ride sounds to not play for the passengers 2025-12-01 13:56:56 +01:00
JazzMcNade 698fa9dff9 Merge branch 'main' into RestoreCombeeLeavesPathing 2025-12-01 00:46:50 -06:00
Khvrram 46ae53f565 Merge branch cobblemon:main into main 2025-12-01 04:25:52 +00:00
Khurram Valiyev 4d123b6e4b Fixed shiny Pokémon not respecting the silent flag on spawn. 2025-11-29 22:50:52 -05:00
Guilherme Pereira 29b560d244 Merge branch 'main' of gitlab.com:cable-mc/cobblemon into partner-mark 2025-11-27 18:21:19 -03:00
JazzMcNade a8949cf1f9 changelog 2025-11-25 21:49:38 -06:00
JazzMcNade 61db6334f9 Restores Combee pathing through leaves 2025-11-25 21:37:57 -06:00
Guilherme Pereira 7cedfb9047 update CHANGELOG.md 2025-11-22 22:02:14 -03:00
Guilherme Pereira a7ab2e37c2 Merge branch 'main' of gitlab.com:cable-mc/cobblemon into partner-mark 2025-11-22 21:57:49 -03:00
Guilherme Pereira 4723f91500 add riding stats 2025-11-22 21:57:25 -03:00
Guilherme Pereira e23aa6b066 add partner mark molang callback 2025-11-22 19:39:21 -03:00
Neovitalism 46d278ca55 key \:) 2025-11-17 03:18:02 -06:00
Neovitalism 339038956d Support for deserialization from any key
...I think.
2025-11-17 03:09:21 -06:00
Neovitalism 116a93d8cc Update CHANGELOG.md 2025-11-16 04:19:32 -06:00
Neovitalism acebc35d3f Who are we if not consistent?
Before, this property was called `PokemonFlagProperty`, had the key `tag`, and checked pokémon's labels. It was also in a package by itself.

Now, it's moved down into the properties package, renamed to LabelProperty, and the key "label" was added, keeping "tag" as a secondary for those that are using it currently.
2025-11-16 04:10:45 -06:00
Konoha aee89f1c92 Preliminary Bag Clause implementation 2025-09-09 18:15:37 -04:00
683 changed files with 15562 additions and 2989 deletions
+64 -4
View File
@@ -2,24 +2,84 @@
## [1.7.2 (MONTH Xth, YEAR)](#1-7-2)
### Additions
- Added riding statistics: `Distance by Pokémon on Land`, `Distance by Pokémon in Air` and `Distance by Pokémon in Liquid`.
- Added the Partner Mark, which party Pokémon have a very small chance to earn as the player walks, provided they have enough friendship.
- Added new effects
- Mental Restoration: Reduces "Time Since Last Rest" statistic, which controls Insomnia, by half a minute per second per effect level
- Negative Effect Cleanse: Removes all negative effects
- Effect Cleanse: Removes all effects
- Added more seasonings for sinister tea
- Mental Herb - Mental Restoration I for 10 seconds
- White Herb - Negative Effect Cleanse
- Milk - Effect Cleanse
- Moomoo Milk - Effect Cleanse
- Added `label` as an alternate key to the `tag` property.
- Added Saccharine wood furniture for Adorn.
- Added block models for existing items: Potion, Super Potion, Hyper Potion, Max Potion, Full Restore, Antidote, Awakening, Burn Heal, Ice Heal, Paralyze Heal, Full Heal, Ether, Elixir, Max Ether, Max Elixir, X Accuracy, X Attack, X Defence, X Special Attack, X Special Defence, X Speed, Dire Hit, Guard Spec, Weakness Policy, Blunder Policy, Cleanse Tag, and Spell Tag.
- Added decorative sign blocks, Plaques.
### Changes
- Ride sounds have been separated into stereo for passengers and mono for other players. Riding will now sound more spacious.
- Added a new config option, `Enable In-Flight Dismounting` (default: off), which lets you dismount while riding a Pokémon in the air.
- Battle AI now uses a smarter threshold for switching, reducing unnecessary switches.
- AI will always use the most damaging move when at low HP and when it is not switching, improving endgame decision-making.
- Added support for optional message variants in battle activate instructions for more context-specific battle text.
- Example: `this.add('-activate', pokemon, 'ability: example', '[msg]message1');` will parse to lang key `cobblemon.battle.activate.example.message1`
### Fixes
- Fixed an issue in the OmniPathNodeMaker/Navigation that prevented Combee from pathing into and out of Saccharine Leaves.
- Fixed orientation not getting reset after crashing while flying and getting back on the Pokémon.
- Fixed Furfrou not being trimmable on NeoForge.
- Fixed shiny Pokémon not respecting the silent flag on spawn.
- Fixed honey from Saccharine Leaves being harvestable at less than the max age.
- Items given from interacting with Saccharine Leaves are placed in the active hotbar slot if possible.
- Fix all Pokémon being saved to chunks and never despawning.
- Fixed PokeSnacks crashing if there was no available spawn.
- Fixed Pokémon spawn influences not being specific to regional forms
- Fixed crashing when viewing another player in spectator mode
- Fixed possible error on world generation when Combee are placed in naturally generated bee nests.
- Fixed Poké Snacks crashing if there was no available spawn.
- Fixed Pokémon spawn influences not being specific to regional forms.
- Fixed crashing when viewing another player in spectator mode.
- Fixed missing apricorn textures for Adorn blocks.
- Fixed crashing when riding due to malformed json file instead of resetting it.
- Fixed mochi items consuming two items at a time.
- Fix item interaction sometimes not working properly when playing on servers.
- Fixed Soothe Bell not being properly tagged as a held item.
- Fixed Mint Leaves not being usable for filling the Resurrection Machine.
- Added Saccharine Boats and Saccharine Boat with Chests to relevant boat tags.
- Fixed the `run_script` Molang function to not fail if the environment's context is null.
- Fixed battle item duplication issue.
- Fixed the movesets of certain Pokémon being incorrectly sanitized upon reload.
- Fixed the ordering of Aprijuice's riding stat boosts to always have the same order.
- Fixed the Molang functions `date_local_time`, `date_of`, `date_is_after` to use the correct date format.
- Fixed the bottom half of pasture blocks having an incorrect hit-box when facing north or south.
- Fixed Galarian Mr. Mime incorrectly being rideable.
- Fixed gimmicks not being usable in double / triple battles.
- Fixed gimmick buttons showing when already used in the same turn in double / triple battles.
- Fixed Apricorn block model rotation for counters/sinks/cupboards/drawers to match that of most recent version of Adorn.
- Fixed Fresh Start Mochi EV reset not syncing health changes properly.
- Fixed requirements such as `biomeCondition` crashing the game when used to define Pokémon interactions.
- Fixed Pokémon interactions not playing sound effects when the volume was not specified.
- Fixed Pokémon interaction cooldowns taking longer than intended when the Pokémon was in the player’s party.
- Fixed Pokémon interactions occasionally crashing the game when spamming an interaction with a 0 cooldown.
- Fixed Pokémon fullness decreasing more than intended when the Pokémon was pastured.
- Fixed crashing when sending a Pokémon out after editing its form.
- Fixed Super Potion recipes incorrectly using Hondew Berry instead of Aguav Berry.
- Fixed `/calculateseatpositions` expecting a locator format that even we don't use. It needs an underscore after "seat".
- Fixed invulnerability-bypassed damage being resisted by invulnerable NPC entities.
- Fixed selected leading Pokémon in battle not being used when levels are raised.
- Fixed killer not being set early enough on a wild wild Pokémon entity from WinInstruction.
### Developer
- Changed the `owner` parameter in the `OwnerQueryRequirement` interface from `ServerPlayer` to `Player`. This method is now also called on the client to verify whether a Pokémon interaction succeeded, so make sure to update your implementations to handle both server and client contexts.
- Added `pnx` to the `BattleFaintedEvent` and `FormeChangeEvent`.
- Added register methods for custom instructions to `ShowdownInterpreter`.
- Changed callback operations in `BattlePokemon` to allow multiple callbacks and is now mutable.
### Molang & Datapacks
- Ride sounds can now be set as exclusive to passengers.
- Ride sounds no longer play when submerged unless specified with a new setting.
- Exception handling has been added to `run_molang`, resolving some crashes caused by malformed MoLang expressions.
### Particles
### Localization
- Updated translations for:
- Updated translations for:
-2
View File
@@ -54,8 +54,6 @@ dependencies {
// Flywheel has no common dep so just pick one and don't use any platform specific code in common
// modCompileOnly(libs.flywheelFabric)
modCompileOnly(files("temp/vivecraft-1.21.1-1.3.4.jar")) // TODO: Switch this to a proper dependency when Vivecraft gets a maven repo
// Showdown
modCompileOnly(libs.graal)
@@ -11,7 +11,9 @@ package com.cobblemon.mod.common.mixin;
import com.cobblemon.mod.common.CobblemonMemories;
import com.cobblemon.mod.common.api.ai.config.task.PathToBeeHiveTaskConfig;
import com.cobblemon.mod.common.api.pokemon.PokemonProperties;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import com.cobblemon.mod.common.world.feature.SaccharineTreeFeature;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.tags.BlockTags;
@@ -42,7 +44,21 @@ public abstract class BeeOccupantMixin {
if (cir.getReturnValue() == null) {
final BeehiveBlockEntity.Occupant occupant = (BeehiveBlockEntity.Occupant) (Object) this;
CompoundTag compoundTag = occupant.entityData().copyTag();
Entity entity = EntityType.loadEntityRecursive(compoundTag, level, (entityx) -> entityx);
String id = compoundTag.getString("id");
Entity entity = null;
if (id.equals(SaccharineTreeFeature.Companion.getPOKEMON_PLACEHOLD_ID())) {
// Special case for Pokemon stuffed into a beenest upon world gen.
// For technical reasons we cannot create them as full entities during world gen.
// So we look for this special id tag and complete the entity when it attempts to exit a hive.
if (compoundTag.contains("pokemonProperties")) {
String properties = compoundTag.getString("pokemonProperties");
PokemonProperties pokemon = PokemonProperties.Companion.parse(properties);
entity = pokemon.createEntity(level);
}
} else {
entity = EntityType.loadEntityRecursive(compoundTag, level, (entityx) -> entityx);
}
if (entity instanceof PokemonEntity pokemonEntity) {
var state = level.getBlockState(pos);
var newPos = Vec3.ZERO;
@@ -11,11 +11,15 @@ package com.cobblemon.mod.common.mixin;
import com.cobblemon.mod.common.Cobblemon;
import com.cobblemon.mod.common.OrientationControllable;
import com.cobblemon.mod.common.PlayerSpawnerAccessor;
import com.cobblemon.mod.common.api.riding.RidingStyle;
import com.cobblemon.mod.common.api.spawning.spawner.PlayerSpawner;
import com.cobblemon.mod.common.api.spawning.spawner.PlayerSpawnerFactory;
import com.cobblemon.mod.common.api.stats.CobblemonStats;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import com.cobblemon.mod.common.world.gamerules.CobblemonGameRules;
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import com.llamalad7.mixinextras.sugar.Local;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
@@ -89,4 +93,32 @@ public abstract class ServerPlayerMixin implements PlayerSpawnerAccessor {
private boolean cobblemon$cancelSavingPokemonMounts(boolean original, @Local(ordinal=0) Entity entity) {
return original && !(entity instanceof PokemonEntity);
}
@WrapOperation(method = "checkRidingStatistics", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/level/ServerPlayer;getVehicle()Lnet/minecraft/world/entity/Entity;"))
public Entity cobblemon$checkRidingStatistics(ServerPlayer player, Operation<Entity> original, @Local int distance) {
var entity = original.call(player);
if (!(entity instanceof PokemonEntity pokemonEntity)) {
return entity;
}
var ridingController = pokemonEntity.getRidingController();
if (ridingController == null) return entity;
var activeContext = ridingController.getContext();
if (activeContext == null) return entity;
var ridingStyle = activeContext.getStyle();
CobblemonStats.CobblemonStat stat = switch (ridingStyle) {
case RidingStyle.LAND -> CobblemonStats.RIDING_LAND;
case RidingStyle.AIR -> CobblemonStats.RIDING_AIR;
case RidingStyle.LIQUID -> CobblemonStats.RIDING_LIQUID;
default -> null;
};
if (stat != null) {
player.awardStat(CobblemonStats.getStat(stat), distance);
}
return entity;
}
}
@@ -77,7 +77,6 @@ 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.*
import com.cobblemon.mod.common.integration.vivecraft.CobblemonVR
import com.cobblemon.mod.common.net.messages.client.settings.ServerSettingsPacket
import com.cobblemon.mod.common.permission.LaxPermissionValidator
import com.cobblemon.mod.common.platform.events.PlatformEvents
@@ -92,7 +91,7 @@ import com.cobblemon.mod.common.pokemon.feature.SlowpokeTailRegrowthSpeciesFeatu
import com.cobblemon.mod.common.pokemon.feature.TagSeasonResolver
import com.cobblemon.mod.common.pokemon.helditem.CobblemonHeldItemManager
import com.cobblemon.mod.common.pokemon.properties.*
import com.cobblemon.mod.common.pokemon.properties.tags.PokemonFlagProperty
import com.cobblemon.mod.common.pokemon.properties.LabelProperty
import com.cobblemon.mod.common.pokemon.stat.CobblemonStatProvider
import com.cobblemon.mod.common.starter.CobblemonStarterHandler
import com.cobblemon.mod.common.util.*
@@ -131,12 +130,10 @@ object Cobblemon {
const val MODID = CobblemonBuildDetails.MOD_ID
const val VERSION = CobblemonBuildDetails.VERSION
const val CONFIG_PATH = "config/$MODID/main.json"
@JvmField
val LOGGER: Logger = LogManager.getLogger()
lateinit var implementation: CobblemonImplementation
@Deprecated("This field is now a config value", ReplaceWith("Cobblemon.config.captureCalculator"))
var captureCalculator: CaptureCalculator
get() = this.config.captureCalculator
@@ -166,49 +163,21 @@ object Cobblemon {
val serverPlayerStructs = mutableMapOf<UUID, ObjectValue<Player>>()
val statistics: CobblemonStats = CobblemonStats
var cobblemonVR: CobblemonVR? = null
@JvmStatic
val builtinPacks = listOf<CobblemonPack>(
CobblemonPack(
id = "adorncompatibility",
name = "Adorn Compatibility",
packType = PackType.CLIENT_RESOURCES,
activationBehaviour = ResourcePackActivationBehaviour.ALWAYS_ENABLED,
neededMods = setOf("adorn")
),
CobblemonPack(
id = "gyaradosjump",
name = "Gyarados Jump Patterns",
packType = PackType.CLIENT_RESOURCES,
activationBehaviour = ResourcePackActivationBehaviour.DEFAULT_ENABLED
),
CobblemonPack(
id = "regionbiasforms",
name = "Region Bias Forms",
packType = PackType.CLIENT_RESOURCES,
activationBehaviour = ResourcePackActivationBehaviour.DEFAULT_ENABLED
),
CobblemonPack(
id = "uniqueshinyforms",
name = "Shinies for Magikarp Jump",
packType = PackType.CLIENT_RESOURCES,
activationBehaviour = ResourcePackActivationBehaviour.NORMAL
),
CobblemonPack(id = "adorncompatibility", name = "Adorn Compatibility", packType = PackType.CLIENT_RESOURCES, activationBehaviour = ResourcePackActivationBehaviour.ALWAYS_ENABLED, neededMods = setOf("adorn")),
CobblemonPack(id = "gyaradosjump", name = "Gyarados Jump Patterns", packType = PackType.CLIENT_RESOURCES, activationBehaviour = ResourcePackActivationBehaviour.DEFAULT_ENABLED),
CobblemonPack(id = "regionbiasforms", name = "Region Bias Forms", packType = PackType.CLIENT_RESOURCES, activationBehaviour = ResourcePackActivationBehaviour.DEFAULT_ENABLED),
CobblemonPack(id = "uniqueshinyforms", name = "Shinies for Magikarp Jump", packType = PackType.CLIENT_RESOURCES, activationBehaviour = ResourcePackActivationBehaviour.NORMAL),
CobblemonPack(
id = "repurposedstructurescobblemon",
name = "Repurposed Structures Cobblemon",
packType = PackType.SERVER_DATA,
activationBehaviour = ResourcePackActivationBehaviour.DEFAULT_ENABLED,
neededMods = setOf("repurposed_structures")
),
CobblemonPack(id = "repurposedstructurescobblemon", name = "Repurposed Structures Cobblemon", packType = PackType.SERVER_DATA, activationBehaviour = ResourcePackActivationBehaviour.DEFAULT_ENABLED, neededMods = setOf("repurposed_structures")),
)
fun preInitialize(implementation: CobblemonImplementation) {
this.implementation = implementation
this.LOGGER.info("Launching Cobblemon ${CobblemonBuildDetails.VERSION}${if (CobblemonBuildDetails.SNAPSHOT) "-SNAPSHOT" else ""} ")
this.LOGGER.info("Launching Cobblemon ${CobblemonBuildDetails.VERSION}${if(CobblemonBuildDetails.SNAPSHOT) "-SNAPSHOT" else ""} ")
if (CobblemonBuildDetails.SNAPSHOT) {
this.LOGGER.info(" - Git Commit: ${smallCommitHash()} (https://gitlab.com/cable-mc/cobblemon/-/commit/${CobblemonBuildDetails.GIT_COMMIT})")
this.LOGGER.info(" - Branch: ${CobblemonBuildDetails.BRANCH}")
@@ -232,6 +201,7 @@ object Cobblemon {
implementation.registerEntityDataSerializers()
implementation.registerCriteria()
implementation.registerEntitySubPredicates()
implementation.registerMobEffects()
DispenserBehaviorRegistry.registerDispenserBehaviors()
DropEntry.register("command", CommandDropEntry::class.java)
@@ -272,10 +242,7 @@ object Cobblemon {
}
PlatformEvents.RIGHT_CLICK_ENTITY.subscribe { event ->
if (event.player.getItemInHand(event.hand).item is NameTagItem && event.entity.type.`is`(
CobblemonEntityTypeTags.CANNOT_HAVE_NAME_TAG
)
) {
if (event.player.getItemInHand(event.hand).item is NameTagItem && event.entity.type.`is`(CobblemonEntityTypeTags.CANNOT_HAVE_NAME_TAG)) {
event.cancel()
}
}
@@ -286,10 +253,7 @@ object Cobblemon {
pokemon.lockedEvolutions
.filterIsInstance<BlockClickEvolution>()
.forEach { evolution ->
evolution.attemptEvolution(
pokemon,
BlockClickEvolution.BlockInteractionContext(block, player.level())
)
evolution.attemptEvolution(pokemon, BlockClickEvolution.BlockInteractionContext(block, player.level()))
}
}
}
@@ -349,14 +313,9 @@ object Cobblemon {
SlowpokeTailRegrowthSpeciesFeatureProvider
)
if (implementation.isModInstalled("vivecraft")) { // VR integration
cobblemonVR = CobblemonVR()
cobblemonVR?.init()
}
CustomPokemonProperty.register(UncatchableProperty)
CustomPokemonProperty.register(BattleCloneProperty)
CustomPokemonProperty.register(PokemonFlagProperty)
CustomPokemonProperty.register(LabelProperty)
CustomPokemonProperty.register(HiddenAbilityPropertyType)
CustomPokemonProperty.register(AspectPropertyType)
CustomPokemonProperty.register(UnaspectPropertyType)
@@ -373,7 +332,7 @@ object Cobblemon {
}
PlatformEvents.SERVER_TICK_POST.subscribe {
ServerTaskTracker.update(1 / 20F)
ServerTaskTracker.update(1/20F)
ServerRealTimeTaskTracker.update()
}
PlatformEvents.SERVER_TICK_PRE.subscribe {
@@ -417,22 +376,10 @@ object Cobblemon {
.applyConnectionString(ConnectionString(config.mongoDBConnectionString))
.build()
mongoClient = MongoClients.create(mongoClientSettings)
val generalMongoFactory = CachedPlayerDataStoreFactory(
PlayerDataMongoBackend(
mongoClient,
config.mongoDBDatabaseName,
"PlayerDataCollection"
)
)
val generalMongoFactory = CachedPlayerDataStoreFactory(PlayerDataMongoBackend(mongoClient, config.mongoDBDatabaseName, "PlayerDataCollection"))
generalMongoFactory.setup(server)
val pokedexMongoFactory = CachedPlayerDataStoreFactory(
DexDataMongoBackend(
mongoClient,
config.mongoDBDatabaseName,
"PokeDexCollection"
)
)
val pokedexMongoFactory = CachedPlayerDataStoreFactory(DexDataMongoBackend(mongoClient, config.mongoDBDatabaseName, "PokeDexCollection"))
pokedexMongoFactory.setup(server)
playerDataManager.setFactory(generalMongoFactory, PlayerInstancedDataStoreTypes.GENERAL)
@@ -554,7 +501,6 @@ object Cobblemon {
field.set(config, field.get(defaultConfig))
}
}
is IntConstraint -> {
var value = field.get(config)
if (value is Int) {
@@ -607,76 +553,20 @@ object Cobblemon {
}
private fun registerArgumentTypes() {
this.implementation.registerCommandArgument(
cobblemonResource("pokemon"),
SpeciesArgumentType::class,
SingletonArgumentInfo.contextFree(SpeciesArgumentType::species)
)
this.implementation.registerCommandArgument(
cobblemonResource("pokemon_properties"),
PokemonPropertiesArgumentType::class,
SingletonArgumentInfo.contextFree(PokemonPropertiesArgumentType::properties)
)
this.implementation.registerCommandArgument(
cobblemonResource("spawn_bucket"),
SpawnBucketArgumentType::class,
SingletonArgumentInfo.contextFree(SpawnBucketArgumentType::spawnBucket)
)
this.implementation.registerCommandArgument(
cobblemonResource("move"),
MoveArgumentType::class,
SingletonArgumentInfo.contextFree(MoveArgumentType::move)
)
this.implementation.registerCommandArgument(
cobblemonResource("party_slot"),
PartySlotArgumentType::class,
SingletonArgumentInfo.contextFree(PartySlotArgumentType::partySlot)
)
this.implementation.registerCommandArgument(
cobblemonResource("pokemon_store"),
PokemonStoreArgumentType::class,
SingletonArgumentInfo.contextFree(PokemonStoreArgumentType::pokemonStore)
)
this.implementation.registerCommandArgument(
cobblemonResource("dialogue"),
DialogueArgumentType::class,
SingletonArgumentInfo.contextFree(DialogueArgumentType::dialogue)
)
this.implementation.registerCommandArgument(
cobblemonResource("form"),
FormArgumentType::class,
SingletonArgumentInfo.contextFree(FormArgumentType::form)
)
this.implementation.registerCommandArgument(
cobblemonResource("dex"),
DexArgumentType::class,
SingletonArgumentInfo.contextFree(DexArgumentType::dex)
)
this.implementation.registerCommandArgument(
cobblemonResource("npc_class"),
NPCClassArgumentType::class,
SingletonArgumentInfo.contextFree(NPCClassArgumentType::npcClass)
)
this.implementation.registerCommandArgument(
cobblemonResource("wallpaper"),
UnlockablePCBoxWallpaperArgumentType::class,
SingletonArgumentInfo.contextFree(UnlockablePCBoxWallpaperArgumentType::wallpaper)
)
this.implementation.registerCommandArgument(
cobblemonResource("mark"),
MarkArgumentType::class,
SingletonArgumentInfo.contextFree(MarkArgumentType::mark)
)
this.implementation.registerCommandArgument(
cobblemonResource("transform_type"),
TransformTypeArgumentType::class,
SingletonArgumentInfo.contextFree(TransformTypeArgumentType::transformType)
)
this.implementation.registerCommandArgument(
cobblemonResource("model_part"),
ModelPartArgumentType::class,
SingletonArgumentInfo.contextFree(ModelPartArgumentType::modelPart)
)
this.implementation.registerCommandArgument(cobblemonResource("pokemon"), SpeciesArgumentType::class, SingletonArgumentInfo.contextFree(SpeciesArgumentType::species))
this.implementation.registerCommandArgument(cobblemonResource("pokemon_properties"), PokemonPropertiesArgumentType::class, SingletonArgumentInfo.contextFree(PokemonPropertiesArgumentType::properties))
this.implementation.registerCommandArgument(cobblemonResource("spawn_bucket"), SpawnBucketArgumentType::class, SingletonArgumentInfo.contextFree(SpawnBucketArgumentType::spawnBucket))
this.implementation.registerCommandArgument(cobblemonResource("move"), MoveArgumentType::class, SingletonArgumentInfo.contextFree(MoveArgumentType::move))
this.implementation.registerCommandArgument(cobblemonResource("party_slot"), PartySlotArgumentType::class, SingletonArgumentInfo.contextFree(PartySlotArgumentType::partySlot))
this.implementation.registerCommandArgument(cobblemonResource("pokemon_store"), PokemonStoreArgumentType::class, SingletonArgumentInfo.contextFree(PokemonStoreArgumentType::pokemonStore))
this.implementation.registerCommandArgument(cobblemonResource("dialogue"), DialogueArgumentType::class, SingletonArgumentInfo.contextFree(DialogueArgumentType::dialogue))
this.implementation.registerCommandArgument(cobblemonResource("form"), FormArgumentType::class, SingletonArgumentInfo.contextFree(FormArgumentType::form))
this.implementation.registerCommandArgument(cobblemonResource("dex"), DexArgumentType::class, SingletonArgumentInfo.contextFree (DexArgumentType::dex))
this.implementation.registerCommandArgument(cobblemonResource("npc_class"), NPCClassArgumentType::class, SingletonArgumentInfo.contextFree(NPCClassArgumentType::npcClass))
this.implementation.registerCommandArgument(cobblemonResource("wallpaper"), UnlockablePCBoxWallpaperArgumentType::class, SingletonArgumentInfo.contextFree(UnlockablePCBoxWallpaperArgumentType::wallpaper))
this.implementation.registerCommandArgument(cobblemonResource("mark"), MarkArgumentType::class, SingletonArgumentInfo.contextFree(MarkArgumentType::mark))
this.implementation.registerCommandArgument(cobblemonResource("transform_type"), TransformTypeArgumentType::class, SingletonArgumentInfo.contextFree(TransformTypeArgumentType::transformType))
this.implementation.registerCommandArgument(cobblemonResource("model_part"), ModelPartArgumentType::class, SingletonArgumentInfo.contextFree(ModelPartArgumentType::modelPart))
}
}
@@ -9,6 +9,7 @@
package com.cobblemon.mod.common
import com.cobblemon.mod.common.api.apricorn.Apricorn
import com.cobblemon.mod.common.api.pokemon.stats.Stats
import com.cobblemon.mod.common.block.*
import com.cobblemon.mod.common.block.campfirepot.CampfireBlock
import com.cobblemon.mod.common.block.LecternBlock
@@ -41,11 +42,11 @@ import net.minecraft.world.level.block.state.BlockBehaviour
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.properties.BlockSetType
import net.minecraft.world.level.block.state.properties.BlockStateProperties
import net.minecraft.world.level.block.state.properties.BlockStateProperties.LIT
import net.minecraft.world.level.block.state.properties.NoteBlockInstrument
import net.minecraft.world.level.block.state.properties.WoodType
import net.minecraft.world.level.material.MapColor
import net.minecraft.world.level.material.PushReaction
import net.minecraft.world.phys.shapes.Shapes
@Suppress("SameParameterValue", "HasPlatformType", "MemberVisibilityCanBePrivate", "unused")
object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<Block>>, Block>() {
@@ -243,9 +244,9 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
val REVIVAL_HERB = this.create("revival_herb", RevivalHerbBlock(BlockBehaviour.Properties.of().pushReaction(PushReaction.DESTROY).mapColor(MapColor.PLANT).ignitedByLava().noCollission().instabreak().sound(CobblemonSounds.REVIVAL_HERB_SOUNDS)))
@JvmField
val POKE_CAKE = this.create("poke_cake", PokeSnackBlock(BlockBehaviour.Properties.of().forceSolidOn().strength(0.5F).sound(SoundType.WOOL).pushReaction(PushReaction.DESTROY).lightLevel { if (it.getValue(LIT)) 3 else 0 }, false))
val POKE_CAKE = this.create("poke_cake", PokeSnackBlock(BlockBehaviour.Properties.of().forceSolidOn().strength(0.5F).sound(SoundType.WOOL).pushReaction(PushReaction.DESTROY).lightLevel { if (it.getValue(BlockStateProperties.LIT)) 3 else 0 }, false))
@JvmField
val POKE_SNACK = this.create("poke_snack", PokeSnackBlock(BlockBehaviour.Properties.of().forceSolidOn().strength(0.5F).sound(SoundType.WOOL).pushReaction(PushReaction.DESTROY).lightLevel { if (it.getValue(LIT)) 3 else 0 }, true))
val POKE_SNACK = this.create("poke_snack", PokeSnackBlock(BlockBehaviour.Properties.of().forceSolidOn().strength(0.5F).sound(SoundType.WOOL).pushReaction(PushReaction.DESTROY).lightLevel { if (it.getValue(BlockStateProperties.LIT)) 3 else 0 }, true))
@JvmField
val TUMBLESTONE_CLUSTER = tumblestoneBlock("tumblestone_cluster", GrowableStoneBlock.STAGE_3, 7, 3, null)
@@ -477,6 +478,99 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
@JvmField
val GIMMIGHOUL_CHEST = create("gimmighoul_chest", GildedChestBlock(BlockBehaviour.Properties.ofFullCopy(Blocks.CHEST).noOcclusion().sound(CobblemonSounds.GILDED_CHEST_SOUNDS), GildedChestBlock.Type.FAKE))
@JvmField
val WHITE_PLAQUE = create("white_plaque", PlaqueBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.METAL).noOcclusion().pushReaction(PushReaction.DESTROY).strength(5.0F)))
@JvmField
val LIGHT_GRAY_PLAQUE = create("light_gray_plaque", PlaqueBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.METAL).noOcclusion().pushReaction(PushReaction.DESTROY).strength(5.0F)))
@JvmField
val GRAY_PLAQUE = create("gray_plaque", PlaqueBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.METAL).noOcclusion().pushReaction(PushReaction.DESTROY).strength(5.0F)))
@JvmField
val BLACK_PLAQUE = create("black_plaque", PlaqueBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.METAL).noOcclusion().pushReaction(PushReaction.DESTROY).strength(5.0F)))
@JvmField
val BROWN_PLAQUE = create("brown_plaque", PlaqueBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.METAL).noOcclusion().pushReaction(PushReaction.DESTROY).strength(5.0F)))
@JvmField
val RED_PLAQUE = create("red_plaque", PlaqueBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.METAL).noOcclusion().pushReaction(PushReaction.DESTROY).strength(5.0F)))
@JvmField
val ORANGE_PLAQUE = create("orange_plaque", PlaqueBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.METAL).noOcclusion().pushReaction(PushReaction.DESTROY).strength(5.0F)))
@JvmField
val YELLOW_PLAQUE = create("yellow_plaque", PlaqueBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.METAL).noOcclusion().pushReaction(PushReaction.DESTROY).strength(5.0F)))
@JvmField
val LIME_PLAQUE = create("lime_plaque", PlaqueBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.METAL).noOcclusion().pushReaction(PushReaction.DESTROY).strength(5.0F)))
@JvmField
val GREEN_PLAQUE = create("green_plaque", PlaqueBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.METAL).noOcclusion().pushReaction(PushReaction.DESTROY).strength(5.0F)))
@JvmField
val CYAN_PLAQUE = create("cyan_plaque", PlaqueBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.METAL).noOcclusion().pushReaction(PushReaction.DESTROY).strength(5.0F)))
@JvmField
val LIGHT_BLUE_PLAQUE = create("light_blue_plaque", PlaqueBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.METAL).noOcclusion().pushReaction(PushReaction.DESTROY).strength(5.0F)))
@JvmField
val BLUE_PLAQUE = create("blue_plaque", PlaqueBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.METAL).noOcclusion().pushReaction(PushReaction.DESTROY).strength(5.0F)))
@JvmField
val PURPLE_PLAQUE = create("purple_plaque", PlaqueBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.METAL).noOcclusion().pushReaction(PushReaction.DESTROY).strength(5.0F)))
@JvmField
val MAGENTA_PLAQUE = create("magenta_plaque", PlaqueBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.METAL).noOcclusion().pushReaction(PushReaction.DESTROY).strength(5.0F)))
@JvmField
val PINK_PLAQUE = create("pink_plaque", PlaqueBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.METAL).noOcclusion().pushReaction(PushReaction.DESTROY).strength(5.0F)))
@JvmField
val CLEANSE_TAG = create("cleanse_tag", WallAttachedStackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.WOOD).mapColor(MapColor.NONE).pushReaction(PushReaction.DESTROY).noOcclusion().noCollission(), WallAttachedStackableItemBlock.WallStackableItemBlockType.TAG))
@JvmField
val SPELL_TAG = create("spell_tag", WallAttachedStackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.WOOD).mapColor(MapColor.NONE).pushReaction(PushReaction.DESTROY).noOcclusion().noCollission(), WallAttachedStackableItemBlock.WallStackableItemBlockType.TAG))
@JvmField
val BLUNDER_POLICY = create("blunder_policy", WallAttachedDirectionalShapeBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion().noCollission(), 14, 12, 1))
@JvmField
val WEAKNESS_POLICY = create("weakness_policy", WallAttachedDirectionalShapeBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion().noCollission(), 14, 12, 1))
@JvmField
val FULL_HEAL = create("full_heal", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
@JvmField
val ANTIDOTE = create("antidote", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
@JvmField
val AWAKENING = create("awakening", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
@JvmField
val BURN_HEAL = create("burn_heal", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
@JvmField
val ICE_HEAL = create("ice_heal", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
@JvmField
val PARALYZE_HEAL = create("paralyze_heal", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
@JvmField
val ETHER = create("ether", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.ETHER))
@JvmField
val MAX_ETHER = create("max_ether", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.ETHER))
@JvmField
val ELIXIR = create("elixir", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.ETHER))
@JvmField
val MAX_ELIXIR = create("max_elixir", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.ETHER))
@JvmField
val POTION = create("potion", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.POTION))
@JvmField
val SUPER_POTION = create("super_potion", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.POTION))
@JvmField
val HYPER_POTION = create("hyper_potion", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.POTION))
@JvmField
val MAX_POTION = create("max_potion", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.MAX_POTION))
@JvmField
val FULL_RESTORE = create("full_restore", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.MAX_POTION))
@JvmField
val DIRE_HIT = create("dire_hit", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.BATTLE_ITEM))
@JvmField
val GUARD_SPEC = create("guard_spec", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.BATTLE_ITEM))
@JvmField
val X_ACCURACY = create("x_${Stats.ACCURACY.identifier.path}", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.BATTLE_ITEM))
@JvmField
val X_ATTACK = create("x_${Stats.ATTACK.identifier.path}", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.BATTLE_ITEM))
@JvmField
val X_DEFENSE = create("x_${Stats.DEFENCE.identifier.path}", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.BATTLE_ITEM))
@JvmField
val X_SP_ATK = create("x_${Stats.SPECIAL_ATTACK.identifier.path}", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.BATTLE_ITEM))
@JvmField
val X_SP_DEF = create("x_${Stats.SPECIAL_DEFENCE.identifier.path}", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.BATTLE_ITEM))
@JvmField
val X_SPEED = create("x_${Stats.SPEED.identifier.path}", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.BATTLE_ITEM))
@JvmField
val MONITOR = create("monitor", MonitorBlock(BlockBehaviour.Properties.of().mapColor(MapColor.METAL).sound(SoundType.METAL).pushReaction(PushReaction.BLOCK).requiresCorrectToolForDrops().strength(5.0F, 6.0F).lightLevel { if (it.getValue(MonitorBlock.SCREEN) != MonitorBlock.MonitorScreen.OFF) 13 else 0 }))
@JvmField
@@ -117,6 +117,11 @@ interface CobblemonImplementation {
fun registerEntitySubPredicates()
/**
* TODO
*/
fun registerMobEffects()
/**
* Add a feature to the current platform implementation.
@@ -507,6 +507,23 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField val SCROLL_OF_DARKNESS = noSettingsItem("scroll_of_darkness")
@JvmField val SCROLL_OF_WATERS = noSettingsItem("scroll_of_waters")
@JvmField val WHITE_PLAQUE = blockItem("white_plaque", CobblemonBlocks.WHITE_PLAQUE)
@JvmField val LIGHT_GRAY_PLAQUE = blockItem("light_gray_plaque", CobblemonBlocks.LIGHT_GRAY_PLAQUE)
@JvmField val GRAY_PLAQUE = blockItem("gray_plaque", CobblemonBlocks.GRAY_PLAQUE)
@JvmField val BLACK_PLAQUE = blockItem("black_plaque", CobblemonBlocks.BLACK_PLAQUE)
@JvmField val BROWN_PLAQUE = blockItem("brown_plaque", CobblemonBlocks.BROWN_PLAQUE)
@JvmField val RED_PLAQUE = blockItem("red_plaque", CobblemonBlocks.RED_PLAQUE)
@JvmField val ORANGE_PLAQUE = blockItem("orange_plaque", CobblemonBlocks.ORANGE_PLAQUE)
@JvmField val YELLOW_PLAQUE = blockItem("yellow_plaque", CobblemonBlocks.YELLOW_PLAQUE)
@JvmField val LIME_PLAQUE = blockItem("lime_plaque", CobblemonBlocks.LIME_PLAQUE)
@JvmField val GREEN_PLAQUE = blockItem("green_plaque", CobblemonBlocks.GREEN_PLAQUE)
@JvmField val CYAN_PLAQUE = blockItem("cyan_plaque", CobblemonBlocks.CYAN_PLAQUE)
@JvmField val LIGHT_BLUE_PLAQUE = blockItem("light_blue_plaque", CobblemonBlocks.LIGHT_BLUE_PLAQUE)
@JvmField val BLUE_PLAQUE = blockItem("blue_plaque", CobblemonBlocks.BLUE_PLAQUE)
@JvmField val PURPLE_PLAQUE = blockItem("purple_plaque", CobblemonBlocks.PURPLE_PLAQUE)
@JvmField val MAGENTA_PLAQUE = blockItem("magenta_plaque", CobblemonBlocks.MAGENTA_PLAQUE)
@JvmField val PINK_PLAQUE = blockItem("pink_plaque", CobblemonBlocks.PINK_PLAQUE)
private val berries = mutableMapOf<ResourceLocation, BerryItem>()
// Plants
@JvmField val CHERI_BERRY = berryItem("cheri", StatusCuringBerryItem(CobblemonBlocks.CHERI_BERRY, Statuses.PARALYSIS))
@@ -699,15 +716,15 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
val MOOMOO_MILK = create("moomoo_milk", MoomooMilk(Item.Properties().craftRemainder(Items.GLASS_BOTTLE).stacksTo((16))))
@JvmField
val POTION = create("potion", PotionItem(PotionType.POTION))
val POTION = create("potion", PotionItem(PotionType.POTION, CobblemonBlocks.POTION))
@JvmField
val SUPER_POTION = create("super_potion", PotionItem(PotionType.SUPER_POTION))
val SUPER_POTION = create("super_potion", PotionItem(PotionType.SUPER_POTION, CobblemonBlocks.SUPER_POTION))
@JvmField
val HYPER_POTION = create("hyper_potion", PotionItem(PotionType.HYPER_POTION))
val HYPER_POTION = create("hyper_potion", PotionItem(PotionType.HYPER_POTION, CobblemonBlocks.HYPER_POTION))
@JvmField
val MAX_POTION = create("max_potion", PotionItem(PotionType.MAX_POTION))
val MAX_POTION = create("max_potion", PotionItem(PotionType.MAX_POTION, CobblemonBlocks.MAX_POTION))
@JvmField
val FULL_RESTORE = create("full_restore", PotionItem(PotionType.FULL_RESTORE))
val FULL_RESTORE = create("full_restore", PotionItem(PotionType.FULL_RESTORE, CobblemonBlocks.FULL_RESTORE))
@JvmField
val HEAL_POWDER = create("heal_powder", HealPowderItem())
@@ -792,28 +809,27 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val SERIOUS_MINT = mintItem("serious_mint", MintItem(Natures.SERIOUS))
@JvmField val X_ACCURACY = create("x_${Stats.ACCURACY.identifier.path}", XStatItem(Stats.ACCURACY))
@JvmField val X_ATTACK = create("x_${Stats.ATTACK.identifier.path}", XStatItem(Stats.ATTACK))
@JvmField val X_DEFENSE = create("x_${Stats.DEFENCE.identifier.path}", XStatItem(Stats.DEFENCE))
@JvmField val X_SP_ATK = create("x_${Stats.SPECIAL_ATTACK.identifier.path}", XStatItem(Stats.SPECIAL_ATTACK))
@JvmField val X_SP_DEF = create("x_${Stats.SPECIAL_DEFENCE.identifier.path}", XStatItem(Stats.SPECIAL_DEFENCE))
@JvmField val X_SPEED = create("x_${Stats.SPEED.identifier.path}", XStatItem(Stats.SPEED))
@JvmField val X_ACCURACY = create("x_${Stats.ACCURACY.identifier.path}", XStatItem(Stats.ACCURACY, block = CobblemonBlocks.X_ACCURACY))
@JvmField val X_ATTACK = create("x_${Stats.ATTACK.identifier.path}", XStatItem(Stats.ATTACK, block = CobblemonBlocks.X_ATTACK))
@JvmField val X_DEFENSE = create("x_${Stats.DEFENCE.identifier.path}", XStatItem(Stats.DEFENCE, block = CobblemonBlocks.X_DEFENSE))
@JvmField val X_SP_ATK = create("x_${Stats.SPECIAL_ATTACK.identifier.path}", XStatItem(Stats.SPECIAL_ATTACK, block = CobblemonBlocks.X_SP_ATK))
@JvmField val X_SP_DEF = create("x_${Stats.SPECIAL_DEFENCE.identifier.path}", XStatItem(Stats.SPECIAL_DEFENCE, block = CobblemonBlocks.X_SP_DEF))
@JvmField val X_SPEED = create("x_${Stats.SPEED.identifier.path}", XStatItem(Stats.SPEED, block = CobblemonBlocks.X_SPEED))
@JvmField val DIRE_HIT = create("dire_hit", DireHitItem())
@JvmField val GUARD_SPEC = create("guard_spec", GuardSpecItem())
@JvmField val DIRE_HIT = create("dire_hit", DireHitItem(CobblemonBlocks.DIRE_HIT))
@JvmField val GUARD_SPEC = create("guard_spec", GuardSpecItem(CobblemonBlocks.GUARD_SPEC))
@JvmField val BURN_HEAL = create("burn_heal", StatusCureItem("item.cobblemon.burn_heal", Statuses.BURN))
@JvmField val PARALYZE_HEAL = create("paralyze_heal", StatusCureItem("item.cobblemon.paralyze_heal", Statuses.PARALYSIS))
@JvmField val ICE_HEAL = create("ice_heal", StatusCureItem("item.cobblemon.ice_heal", Statuses.FROZEN))
@JvmField val ANTIDOTE = create("antidote", StatusCureItem("item.cobblemon.antidote", Statuses.POISON, Statuses.POISON_BADLY))
@JvmField val AWAKENING = create("awakening", StatusCureItem("item.cobblemon.awakening", Statuses.SLEEP))
@JvmField val FULL_HEAL = create("full_heal", StatusCureItem("item.cobblemon.full_heal", block = CobblemonBlocks.FULL_HEAL))
@JvmField val ANTIDOTE = create("antidote", StatusCureItem("item.cobblemon.antidote", Statuses.POISON, Statuses.POISON_BADLY, block = CobblemonBlocks.ANTIDOTE))
@JvmField val AWAKENING = create("awakening", StatusCureItem("item.cobblemon.awakening", Statuses.SLEEP, block = CobblemonBlocks.AWAKENING))
@JvmField val BURN_HEAL = create("burn_heal", StatusCureItem("item.cobblemon.burn_heal", Statuses.BURN, block = CobblemonBlocks.BURN_HEAL))
@JvmField val ICE_HEAL = create("ice_heal", StatusCureItem("item.cobblemon.ice_heal", Statuses.FROZEN, block = CobblemonBlocks.ICE_HEAL))
@JvmField val PARALYZE_HEAL = create("paralyze_heal", StatusCureItem("item.cobblemon.paralyze_heal", Statuses.PARALYSIS, block = CobblemonBlocks.PARALYZE_HEAL))
@JvmField val FULL_HEAL = create("full_heal", StatusCureItem("item.cobblemon.full_heal"))
@JvmField val ETHER = create("ether", EtherItem(max = false))
@JvmField val MAX_ETHER = create("max_ether", EtherItem(max = true))
@JvmField val ELIXIR = create("elixir", ElixirItem(max = false))
@JvmField val MAX_ELIXIR = create("max_elixir", ElixirItem(max = true))
@JvmField val ETHER = create("ether", EtherItem(max = false, CobblemonBlocks.ETHER))
@JvmField val MAX_ETHER = create("max_ether", EtherItem(max = true, CobblemonBlocks.MAX_ETHER))
@JvmField val ELIXIR = create("elixir", ElixirItem(max = false, CobblemonBlocks.ELIXIR))
@JvmField val MAX_ELIXIR = create("max_elixir", ElixirItem(max = true, CobblemonBlocks.MAX_ELIXIR))
@JvmField
val ABILITY_CAPSULE = this.create("ability_capsule", AbilityChangeItem(AbilityChanger.COMMON_ABILITY))
@@ -922,7 +938,7 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val BLACK_SLUDGE = heldItem("black_sludge")
@JvmField
val BLUNDER_POLICY = heldItem("blunder_policy")
val BLUNDER_POLICY = itemNameBlockItem("blunder_policy", CobblemonBlocks.BLUNDER_POLICY)
@JvmField
val CELL_BATTERY = heldItem("cell_battery")
@JvmField
@@ -930,7 +946,7 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val CHOICE_SCARF = heldItem("choice_scarf")
@JvmField
val CLEANSE_TAG = heldItem("cleanse_tag")
val CLEANSE_TAG = itemNameBlockItem("cleanse_tag", CobblemonBlocks.CLEANSE_TAG)
@JvmField
val CLEAR_AMULET = heldItem("clear_amulet")
@JvmField
@@ -1038,7 +1054,7 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val SOFT_SAND = heldItem("soft_sand")
@JvmField
val SPELL_TAG = heldItem("spell_tag")
val SPELL_TAG = itemNameBlockItem("spell_tag", CobblemonBlocks.SPELL_TAG)
@JvmField
val SMOKE_BALL = heldItem("smoke_ball")
@JvmField
@@ -1056,7 +1072,7 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val UTILITY_UMBRELLA = heldItem("utility_umbrella")
@JvmField
val WEAKNESS_POLICY = heldItem("weakness_policy")
val WEAKNESS_POLICY = itemNameBlockItem("weakness_policy", CobblemonBlocks.WEAKNESS_POLICY)
@JvmField
val WIDE_LENS = heldItem("wide_lens")
@JvmField
@@ -1553,6 +1569,7 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
}
}
)
private fun heldItem(name: String, item: Item, remappedName: String? = null) = create(
name = name,
entry = item.also {
@@ -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
import com.cobblemon.mod.common.api.cooking.effects.CleanseAllEffect
import com.cobblemon.mod.common.api.cooking.effects.CleanseNegativeEffect
import com.cobblemon.mod.common.api.cooking.effects.MentalRestorationEffect
import com.cobblemon.mod.common.platform.PlatformRegistry
import net.minecraft.core.Registry
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.core.registries.Registries
import net.minecraft.resources.ResourceKey
import net.minecraft.world.effect.MobEffect
object CobblemonMobEffects : PlatformRegistry<Registry<MobEffect>, ResourceKey<Registry<MobEffect>>, MobEffect>() {
override val registry: Registry<MobEffect> = BuiltInRegistries.MOB_EFFECT
override val resourceKey: ResourceKey<Registry<MobEffect>> = Registries.MOB_EFFECT
@JvmField
val CLEANSE_NEGATIVE = create("cleanse_negative", CleanseNegativeEffect())
@JvmField
val MENTAL_RESTORATION = create("mental_restoration", MentalRestorationEffect())
@JvmField
val CLEANSE_ALL = create("cleanse_all", CleanseAllEffect())
}
File diff suppressed because it is too large Load Diff
@@ -304,7 +304,8 @@ open class PokemonBattle(
.forEach{it.pokemon.heal()}
actors.forEach { actor ->
actor.pokemonList.forEach { battlePokemon ->
battlePokemon.entity?.let { entity -> battlePokemon.postBattleEntityOperation(entity) }
battlePokemon.postBattlePokemonOperations.forEach { it(battlePokemon) }
battlePokemon.entity?.let { entity -> battlePokemon.postBattleEntityOperations.forEach { it(entity) } }
if (battlePokemon.effectedPokemon.entity != null
&& battlePokemon.effectedPokemon.entity?.beamMode == 0
&& battlePokemon.effectedPokemon.entity?.platform != PlatformType.NONE) {
@@ -0,0 +1,22 @@
/*
* 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.cooking.effects
import net.minecraft.world.effect.MobEffect
import net.minecraft.world.effect.MobEffectCategory
import net.minecraft.world.entity.Entity
import net.minecraft.world.entity.LivingEntity
class CleanseAllEffect : MobEffect(MobEffectCategory.BENEFICIAL, 0xFFFFFF) {
override fun isInstantenous() = true
override fun applyInstantenousEffect(source: Entity?, indirect: Entity?, target: LivingEntity, amplifier: Int, proximity: Double) {
target.removeAllEffects()
}
override fun shouldApplyEffectTickThisTick(duration: Int, amplifier: Int) = false
}
@@ -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.cooking.effects
import net.minecraft.world.effect.MobEffect
import net.minecraft.world.effect.MobEffectCategory
import net.minecraft.world.entity.Entity
import net.minecraft.world.entity.LivingEntity
class CleanseNegativeEffect : MobEffect(MobEffectCategory.BENEFICIAL, 0xFFFFFF) {
override fun isInstantenous() = true
override fun applyInstantenousEffect(source: Entity?, indirect: Entity?, target: LivingEntity, amplifier: Int, proximity: Double) {
val snapshot = target.activeEffects.toList()
for (ei in snapshot) {
if (ei.effect.value().category == MobEffectCategory.HARMFUL) {
target.removeEffect(ei.effect)
}
}
}
override fun shouldApplyEffectTickThisTick(duration: Int, amplifier: Int) = 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.api.cooking.effects
import net.minecraft.server.level.ServerPlayer
import net.minecraft.stats.Stats
import net.minecraft.world.effect.MobEffect
import net.minecraft.world.effect.MobEffectCategory
import net.minecraft.world.entity.LivingEntity
class MentalRestorationEffect : MobEffect(MobEffectCategory.BENEFICIAL, 0xAA66FF) {
override fun applyEffectTick(entity: LivingEntity, amplifier: Int): Boolean {
if (entity is ServerPlayer) {
val stats = entity.stats
val statType = Stats.CUSTOM.get(Stats.TIME_SINCE_REST)
val current = stats.getValue(statType)
val reduction = 31 * (amplifier + 1)// reduce by half a minute every second per level (30 ticks removed per tick + 1 to account for the tick passing)
val newValue = Math.max(current - reduction, 0)
stats.setValue(entity, statType, newValue)
}
return true
}
override fun shouldApplyEffectTickThisTick(duration: Int, amplifier: Int): Boolean {
return true
}
}
@@ -9,6 +9,7 @@
package com.cobblemon.mod.common.api.events.battles
import com.bedrockk.molang.runtime.value.MoValue
import com.bedrockk.molang.runtime.value.StringValue
import com.cobblemon.mod.common.api.battles.interpreter.BattleContext
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.battles.model.actor.ActorType
@@ -25,7 +26,8 @@ import com.cobblemon.mod.common.util.asArrayValue
data class BattleFaintedEvent(
override val battle: PokemonBattle,
val killed: BattlePokemon,
val context: BattleContext
val context: BattleContext,
val pnx: String
) : BattleEvent {
val structContext = mutableMapOf<String, MoValue>(
"battle" to battle.struct,
@@ -33,6 +35,7 @@ data class BattleFaintedEvent(
"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 },
"pokemon" to killed.struct,
"context" to context.getStruct()
"context" to context.getStruct(),
"pnx" to StringValue(pnx)
)
}
}
@@ -25,7 +25,8 @@ import com.cobblemon.mod.common.util.asArrayValue
data class FormeChangeEvent(
override val battle: PokemonBattle,
val pokemon: BattlePokemon,
val formeName: String
val formeName: String,
val pnx: String
) : BattleEvent {
val context = mutableMapOf(
"battle" to battle.struct,
@@ -33,6 +34,7 @@ data class FormeChangeEvent(
"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 },
"pokemon" to pokemon.struct,
"forme" to StringValue(formeName)
"forme" to StringValue(formeName),
"pnx" to StringValue(pnx)
)
}
}
@@ -9,13 +9,14 @@
package com.cobblemon.mod.common.api.interaction
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.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.pokemon.requirement.OwnerQueryRequirement
import com.cobblemon.mod.common.api.pokemon.requirement.Requirement
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.net.messages.client.data.PokemonInteractionsSyncPacket
import com.cobblemon.mod.common.pokemon.adapters.CobblemonRequirementAdapter
import com.cobblemon.mod.common.pokemon.evolution.adapters.LegacyItemConditionWrapperAdapter
import com.cobblemon.mod.common.pokemon.requirements.PokemonPropertiesRequirement
@@ -29,6 +30,11 @@ import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
import net.minecraft.server.packs.PackType
import net.minecraft.util.LowerCaseEnumTypeAdapterFactory
import net.minecraft.world.item.Item
import net.minecraft.world.level.biome.Biome
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.levelgen.structure.Structure
import net.minecraft.world.level.material.Fluid
object PokemonInteractions : JsonDataRegistry<PokemonInteractionSet> {
override val id = cobblemonResource("pokemon_interactions")
@@ -43,6 +49,11 @@ object PokemonInteractions : JsonDataRegistry<PokemonInteractionSet> {
.registerTypeAdapter(Requirement::class.java, CobblemonRequirementAdapter)
.registerTypeAdapter(InteractionEffect::class.java, InteractionEffectAdapter)
.registerTypeAdapter(ExpressionLike::class.java, ExpressionLikeAdapter)
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Biome::class.java).type, BiomeLikeConditionAdapter)
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Block::class.java).type, BlockLikeConditionAdapter)
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Item::class.java).type, ItemLikeConditionAdapter)
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Structure::class.java).type, StructureLikeConditionAdapter)
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Fluid::class.java).type, FluidLikeConditionAdapter)
.registerTypeAdapter(IntRange::class.java, IntRangeAdapter)
.registerTypeAdapterFactory(LowerCaseEnumTypeAdapterFactory())
.setPrettyPrinting()
@@ -51,7 +62,12 @@ object PokemonInteractions : JsonDataRegistry<PokemonInteractionSet> {
val speciesInteractions = mutableListOf<PokemonInteractionSet>()
val generalInteractions = mutableListOf<PokemonInteractionSet>()
override fun sync(player: ServerPlayer) {}
override fun sync(player: ServerPlayer) {
PokemonInteractionsSyncPacket(
speciesInteractions,
generalInteractions,
).sendToPlayer(player)
}
override fun reload(data: Map<ResourceLocation, PokemonInteractionSet>) {
speciesInteractions.clear()
@@ -15,11 +15,11 @@ import com.cobblemon.mod.common.api.molang.MoLangFunctions.setup
import com.cobblemon.mod.common.api.pokemon.requirement.Requirement
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.net.messages.client.sound.UnvalidatedPlaySoundS2CPacket
import com.cobblemon.mod.common.util.asExpressionLike
import com.cobblemon.mod.common.util.giveOrDropItemStack
import com.cobblemon.mod.common.util.withPlayerValue
import com.cobblemon.mod.common.util.withQueryValue
import com.cobblemon.mod.common.pokemon.adapters.CobblemonRequirementAdapter
import com.cobblemon.mod.common.util.*
import com.google.gson.annotations.SerializedName
import net.minecraft.core.registries.Registries
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
import net.minecraft.sounds.SoundSource
@@ -28,30 +28,83 @@ import net.minecraft.world.entity.EquipmentSlot
import net.minecraft.world.entity.item.ItemEntity
import net.minecraft.world.item.ItemStack
/**
* A
*/
data class PokemonInteractionSet(
val requirements: List<Requirement> = listOf(),
val interactions: List<PokemonInteraction> = listOf()
)
val interactions: List<PokemonInteraction> = listOf(),
) {
fun encode(buffer: FriendlyByteBuf) {
buffer.writeCollection(requirements) { it, requirement ->
CobblemonRequirementAdapter.encode(requirement, it)
}
buffer.writeCollection(interactions) { it, interaction ->
interaction.encode(it)
}
}
companion object {
fun decode(buffer: FriendlyByteBuf): PokemonInteractionSet {
val requirements = buffer.readList { it ->
CobblemonRequirementAdapter.decode(it)
}
val interactions = buffer.readList { it ->
PokemonInteraction.decode(it)
}
return PokemonInteractionSet(
requirements,
interactions,
)
}
}
}
data class PokemonInteraction(
val grouping: ResourceLocation,
val requirements: List<Requirement> = listOf(),
val effects: List<InteractionEffect> = listOf(),
val cooldown: ExpressionLike = "0".asExpressionLike()
)
) {
fun encode(buffer: FriendlyByteBuf) {
buffer.writeResourceLocation(grouping)
buffer.writeCollection(requirements) { it, requirement ->
CobblemonRequirementAdapter.encode(requirement, it)
}
buffer.writeExpressionLike(cooldown)
}
companion object {
fun decode(buffer: FriendlyByteBuf): PokemonInteraction {
val grouping = buffer.readResourceLocation()
val requirements = buffer.readList { it ->
CobblemonRequirementAdapter.decode(it)
}
// No need to sync these for now
val effects = emptyList<InteractionEffect>()
val cooldown = buffer.readExpressionLike()
return PokemonInteraction(
grouping,
requirements,
effects,
cooldown,
)
}
}
}
interface InteractionEffect {
fun applyEffect(pokemon: PokemonEntity, player: ServerPlayer)
}
class DropItemEffect(val item: ResourceLocation, val amount: IntRange?): InteractionEffect {
class DropItemEffect(val item: ResourceLocation, val amount: IntRange?) : InteractionEffect {
override fun applyEffect(
pokemon: PokemonEntity,
player: ServerPlayer
) {
val item = player.registryAccess().registryOrThrow(Registries.ITEM).get(item) ?: throw IllegalArgumentException("Cannot load item with id: $item")
val item = player.registryAccess().registryOrThrow(Registries.ITEM).get(item) ?: throw IllegalArgumentException(
"Cannot load item with id: $item"
)
val stack = ItemStack(item)
stack.count = amount?.randomOrNull() ?: 1
if (stack.isEmpty)
@@ -68,12 +121,14 @@ class DropItemEffect(val item: ResourceLocation, val amount: IntRange?): Interac
}
}
class GiveItemEffect(val item: ResourceLocation, val amount: IntRange?): InteractionEffect {
class GiveItemEffect(val item: ResourceLocation, val amount: IntRange?) : InteractionEffect {
override fun applyEffect(
pokemon: PokemonEntity,
player: ServerPlayer
) {
val item = player.registryAccess().registryOrThrow(Registries.ITEM).get(item) ?: throw IllegalArgumentException("Cannot load item with id: $item")
val item = player.registryAccess().registryOrThrow(Registries.ITEM).get(item) ?: throw IllegalArgumentException(
"Cannot load item with id: $item"
)
val stack = ItemStack(item)
stack.count = amount?.randomOrNull() ?: 1
if (stack.isEmpty)
@@ -86,25 +141,54 @@ class GiveItemEffect(val item: ResourceLocation, val amount: IntRange?): Interac
}
}
class PlaySoundEffect(val sound: ResourceLocation, val soundSource: SoundSource?, val playAround: Boolean = true, val distance: Double = 64.0, val volume: Float = 1.0F, val pitch: Float = 1.0F): InteractionEffect {
class PlaySoundEffect(
val sound: ResourceLocation,
val soundSource: SoundSource?,
@SerializedName("playAround") private val _playAround: Boolean?,
@SerializedName("distance") private val _distance: Double?,
@SerializedName("volume") private val _volume: Float?,
@SerializedName("pitch") private val _pitch: Float?,
) : InteractionEffect {
override fun applyEffect(
pokemon: PokemonEntity,
player: ServerPlayer
) {
val packet = UnvalidatedPlaySoundS2CPacket(sound, soundSource ?: SoundSource.NEUTRAL, pokemon.x, pokemon.y, pokemon.z, volume, pitch)
val packet = UnvalidatedPlaySoundS2CPacket(
sound,
soundSource ?: SoundSource.NEUTRAL,
pokemon.x,
pokemon.y,
pokemon.z,
volume,
pitch
)
if (playAround) {
packet.sendToPlayersAround( pokemon.x, pokemon.y, pokemon.z, distance, pokemon.level().dimension())
packet.sendToPlayersAround(pokemon.x, pokemon.y, pokemon.z, distance, pokemon.level().dimension())
} else {
packet.sendToPlayer(player)
}
}
val playAround: Boolean
get() = _playAround != false
val distance: Double
get() = _distance ?: 64.0
val volume: Float
get() = _volume ?: 1.0F
val pitch: Float
get() = _pitch ?: 1.0F
companion object {
val ID = "play_sound"
}
}
class ShrinkItemEffect(val amount: Int = 1): InteractionEffect {
class ShrinkItemEffect(val amount: Int = 1) : InteractionEffect {
override fun applyEffect(
pokemon: PokemonEntity,
player: ServerPlayer
@@ -120,7 +204,7 @@ class ShrinkItemEffect(val amount: Int = 1): InteractionEffect {
}
}
class ScriptEffect(val script: ExpressionLike): InteractionEffect {
class ScriptEffect(val script: ExpressionLike) : InteractionEffect {
override fun applyEffect(
pokemon: PokemonEntity,
player: ServerPlayer
@@ -311,7 +311,7 @@ object MoLangFunctions {
runtime.environment.variable = params.environment.variable
val args = params.params.subList(1, params.params.size)
runtime.environment.context = ContextStruct(
params.environment.context.map + args.mapIndexed { index, value -> "arg_${index + 1}" to value }.toMap()
(params.environment.context?.map ?: emptyMap()) + args.mapIndexed { index, value -> "arg_${index + 1}" to value }.toMap()
)
val script = params.getString(0).asIdentifierDefaultingNamespace()
// store the args in the
@@ -324,13 +324,24 @@ object MoLangFunctions {
runtime.environment.context = params.environment.context
val expression = params.getString(0).asExpressionLike()
val delayInSeconds = params.getDoubleOrNull(1)?.toFloat() ?: 0.0f
fun evaluate(): MoValue {
return try {
runtime.resolve(expression)
} catch (ex: Exception) {
Cobblemon.LOGGER.warn("Failed to evaluate MoLang expression${if (delayInSeconds > 0.0f) " (delayed)" else ""}: $expression", ex)
DoubleValue.ZERO
}
}
if (delayInSeconds > 0.0f) {
val tracker = if (Cobblemon.implementation.environment() == Environment.SERVER) ServerTaskTracker else ClientTaskTracker
tracker.after(delayInSeconds) {
runtime.resolve(expression)
evaluate()
}
DoubleValue.ONE
} else {
runtime.resolve(expression)
evaluate()
}
},
"system_time_millis" to java.util.function.Function { _ ->
@@ -340,19 +351,19 @@ object MoLangFunctions {
"date_local_time" to java.util.function.Function { _ ->
val time = System.currentTimeMillis()
val date = java.util.Date(time)
val formatted = java.text.SimpleDateFormat("DD/MM/YYYY").format(date)
val formatted = java.text.SimpleDateFormat("dd/MM/yyyy").format(date)
StringValue(formatted)
},
"date_of" to java.util.function.Function { params ->
val time = params.getDouble(0).toLong()
val date = java.util.Date(time)
val formatted = java.text.SimpleDateFormat("DD/MM/YYYY").format(date)
val formatted = java.text.SimpleDateFormat("dd/MM/yyyy").format(date)
StringValue(formatted)
},
"date_is_after" to java.util.function.Function { params ->
val dateA = params.getString(0)
val dateB = params.getString(1)
val format = java.text.SimpleDateFormat("DD/MM/YYYY")
val format = java.text.SimpleDateFormat("dd/MM/yyyy")
val a = format.parse(dateA)
val b = format.parse(dateB)
DoubleValue(a.after(b))
@@ -779,6 +790,20 @@ object MoLangFunctions {
battleStartResult.ifSuccessful { returnValue = it.struct }
return@put returnValue
}
map.put("get_custom_stat") { params ->
val statName = params.getString(0)
val resourceLocation = ResourceLocation.tryParse(statName) ?: return@put DoubleValue.ZERO
val statResourceLocation = BuiltInRegistries.CUSTOM_STAT.get(resourceLocation) ?: return@put DoubleValue.ZERO
val exists = net.minecraft.stats.Stats.CUSTOM.contains(statResourceLocation)
if (!exists) return@put DoubleValue.ZERO
val stat = net.minecraft.stats.Stats.CUSTOM.get(statResourceLocation)
val value = player.stats.getValue(stat)
DoubleValue(value)
}
}
map
}
@@ -1739,22 +1764,35 @@ object MoLangFunctions {
pokemon.removeCosmeticItem()
}
map.put("add_marks") { params ->
var appliedMark = false
for (param in params.params) {
val identifier = param.asString().asIdentifierDefaultingNamespace()
val mark = Marks.getByIdentifier(identifier)
if (mark != null) pokemon.exchangeMark(mark, true)
if (mark != null) {
pokemon.exchangeMark(mark, true)
appliedMark = true
}
}
DoubleValue(appliedMark)
}
map.put("add_marks_with_chance") { params ->
var appliedMark = false
for (param in params.params) {
val identifier = param.asString().asIdentifierDefaultingNamespace()
val mark = Marks.getByIdentifier(identifier)
mark?.let {
val probability = it.chance.coerceIn(0F, 1F) * 100
val randomValue = Random.nextDouble(0.0, 100.0)
if (randomValue < probability) pokemon.exchangeMark(it, true)
if (randomValue < probability) {
pokemon.exchangeMark(it, true)
appliedMark = true
}
}
}
DoubleValue(appliedMark)
}
map.put("add_potential_marks") { params ->
for (param in params.params) {
@@ -80,7 +80,9 @@ open class ChoiceSpeciesFeatureProvider(
internal constructor(): this(emptyList())
override fun get(pokemon: Pokemon) = pokemon.getFeature<StringSpeciesFeature>(keys.first())
override fun get(pokemon: Pokemon): StringSpeciesFeature? {
return pokemon.features.filterIsInstance<StringSpeciesFeature>().find { it.name in keys }
}
override fun invoke(pokemon: Pokemon): StringSpeciesFeature? {
val existing = get(pokemon)
@@ -101,15 +103,15 @@ open class ChoiceSpeciesFeatureProvider(
}
override fun invoke(nbt: CompoundTag): StringSpeciesFeature? {
return if (nbt.contains(keys.first())) {
StringSpeciesFeature(keys.first(), "").also { it.loadFromNBT(nbt) }
} else null
val key = keys.find { nbt.contains(it) }
if (key == null) return null
return StringSpeciesFeature(key, "").also { it.loadFromNBT(nbt) }
}
override fun invoke(json: JsonObject): StringSpeciesFeature? {
return if (json.has(keys.first())) {
StringSpeciesFeature(keys.first(), "").also { it.loadFromJSON(json) }
} else null
val key = keys.find { json.has(it) }
if (key == null) return null
return StringSpeciesFeature(key, "").also { it.loadFromJSON(json) }
}
override fun fromString(value: String?): StringSpeciesFeature? {
@@ -131,7 +133,7 @@ open class ChoiceSpeciesFeatureProvider(
override fun provide(properties: PokemonProperties): Set<String> {
return if (isAspect) {
val feature = properties.customProperties.filterIsInstance<StringSpeciesFeature>().find { it.name == keys.first() }
val feature = properties.customProperties.filterIsInstance<StringSpeciesFeature>().find { it.name in keys }
if (feature != null) {
setOf(getAspect(feature))
} else {
@@ -132,7 +132,9 @@ class FlagSpeciesFeatureProvider : SynchronizedSpeciesFeatureProvider<FlagSpecie
constructor(vararg keys: String) : this(keys.toList())
override fun get(pokemon: Pokemon) = pokemon.getFeature<FlagSpeciesFeature>(keys.first())
override fun get(pokemon: Pokemon): FlagSpeciesFeature? {
return pokemon.features.filterIsInstance<FlagSpeciesFeature>().find { it.name in keys }
}
override fun invoke(pokemon: Pokemon): FlagSpeciesFeature? {
return get(pokemon)
@@ -144,15 +146,15 @@ class FlagSpeciesFeatureProvider : SynchronizedSpeciesFeatureProvider<FlagSpecie
}
override fun invoke(nbt: CompoundTag): FlagSpeciesFeature? {
return if (nbt.contains(keys.first())) {
FlagSpeciesFeature(keys.first(), false).also { it.loadFromNBT(nbt) }
} else null
val key = keys.find { nbt.contains(it) }
if (key == null) return null
return FlagSpeciesFeature(key, false).also { it.loadFromNBT(nbt) }
}
override fun invoke(json: JsonObject): FlagSpeciesFeature? {
return if (json.has(keys.first())) {
FlagSpeciesFeature(keys.first(), false).also { it.loadFromJSON(json) }
} else null
val key = keys.find { json.has(it) }
if (key == null) return null
return FlagSpeciesFeature(key, false).also { it.loadFromJSON(json) }
}
override fun fromString(value: String?): FlagSpeciesFeature? {
@@ -170,16 +172,26 @@ class FlagSpeciesFeatureProvider : SynchronizedSpeciesFeatureProvider<FlagSpecie
}
override fun provide(pokemon: Pokemon): Set<String> {
return if (isAspect && pokemon.getFeature<FlagSpeciesFeature>(keys.first())?.enabled == true) {
setOf(keys.first())
return if (isAspect) {
val feature = pokemon.features.filterIsInstance<FlagSpeciesFeature>().find { it.name in keys }
if (feature != null && feature.enabled) {
setOf(keys.first())
} else {
emptySet()
}
} else {
emptySet()
}
}
override fun provide(properties: PokemonProperties): Set<String> {
return if (isAspect && properties.customProperties.filterIsInstance<FlagSpeciesFeature>().find { it.name == keys.first() }?.enabled == true) {
setOf(keys.first())
return if (isAspect) {
val feature = properties.customProperties.filterIsInstance<FlagSpeciesFeature>().find { it.name in keys }
if (feature != null && feature.enabled) {
setOf(keys.first())
} else {
emptySet()
}
} else {
emptySet()
}
@@ -142,19 +142,15 @@ class IntSpeciesFeatureProvider : SynchronizedSpeciesFeatureProvider<IntSpeciesF
}
override fun invoke(nbt: CompoundTag): IntSpeciesFeature? {
return if (nbt.contains(keys.first())) {
IntSpeciesFeature(keys.first(), nbt.getInt(keys.first()))
} else {
null
}
val key = keys.find { nbt.contains(it) }
if (key == null) return null
return IntSpeciesFeature(keys.first(), nbt.getInt(key));
}
override fun invoke(json: JsonObject): IntSpeciesFeature? {
return if (json.has(keys.first())) {
IntSpeciesFeature(keys.first(), json.get(keys.first()).asInt)
} else {
null
}
val key = keys.find { json.has(it) }
if (key == null) return null
return IntSpeciesFeature(keys.first(), json.get(key).asInt)
}
override fun get(pokemon: Pokemon) = pokemon.features.filterIsInstance<IntSpeciesFeature>().find { it.name in keys }
@@ -102,6 +102,12 @@ object SpeciesFeatures : JsonDataRegistry<SpeciesFeatureProvider<*>> {
@JvmStatic
fun register(name: String, provider: SpeciesFeatureProvider<*>) = register(name, provider, isCoded = true)
@JvmStatic
fun registerType(key: String, clazz: Class<out SpeciesFeatureProvider<*>>) {
types[key] = clazz
}
private fun registerFromAssets(identifier: ResourceLocation, provider: SpeciesFeatureProvider<*>) = register(identifier.path, provider, isCoded = false)
@JvmStatic
@@ -12,6 +12,7 @@ import com.cobblemon.mod.common.entity.npc.NPCEntity
import com.cobblemon.mod.common.pokemon.Pokemon
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
/**
* A [Requirement] that expects a [LivingEntity] to be attached to the [Pokemon].
@@ -26,7 +27,7 @@ interface OwnerQueryRequirement : Requirement {
override fun check(pokemon: Pokemon): Boolean {
val owner = pokemon.getOwnerEntity() ?: return false
return when (owner) {
is ServerPlayer -> {
is Player -> {
this.checkPlayer(pokemon, owner)
}
is NPCEntity -> {
@@ -37,13 +38,13 @@ interface OwnerQueryRequirement : Requirement {
}
/**
* Checks if the given [Pokemon] & [ServerPlayer] satisfies the requirement.
* Checks if the given [Pokemon] & [Player] satisfies the requirement.
*
* @param pokemon The [Pokemon] being queried.
* @param owner The [ServerPlayer] that owns this [Pokemon].
* @param owner The [Player] that owns this [Pokemon].
* @return If the requirement was satisfied.
*/
fun checkPlayer(pokemon: Pokemon, owner: ServerPlayer): Boolean
fun checkPlayer(pokemon: Pokemon, owner: Player): Boolean
/**
* Checks if the given [Pokemon] & [NPCEntity] satisfies the requirement.
@@ -11,6 +11,7 @@ package com.cobblemon.mod.common.api.reactive
import com.cobblemon.mod.common.api.events.Cancelable
open class EventObservable<T> : SimpleObservable<T>() {
@JvmOverloads
inline fun post(vararg events: T, then: (T) -> Unit = {}) {
// Issuing generic-typed pass-throughs to varargs doesn't work
// properly unless the source is a vararg as well, complicated story.
@@ -12,6 +12,7 @@ package com.cobblemon.mod.common.api.riding.sound
import com.cobblemon.mod.common.api.riding.behaviour.types.composite.CompositeBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.types.composite.CompositeState
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.getIsSubmerged
import com.cobblemon.mod.common.util.resolveDouble
import net.minecraft.client.Minecraft
import net.minecraft.client.resources.sounds.AbstractTickableSoundInstance
@@ -65,6 +66,21 @@ class RideLoopSound(val ride: PokemonEntity, val soundSettings: RideSoundSetting
return
}
// Mute the sound if it's not meant to play for the passenger
if (this.isPassenger && !this.soundSettings.playForPassengers) {
this.volume = 0.0f
return
}
// Mute sounds when submerged unless they support underwater sounds, underwater sounds don't play above water
if (this.ride.getIsSubmerged() && !this.soundSettings.submerged) {
this.volume = 0.0f
return
} else if (!this.ride.getIsSubmerged() && this.soundSettings.submerged) {
this.volume = 0.0f
return
}
// Perform data driven pitch and volume calculation
this.volume = ((soundSettings.volumeExpr.let { ride.runtime.resolveDouble(it) })).toFloat()
this.volume *= this.fade
@@ -27,7 +27,9 @@ data class RideSoundSettings(
val soundLocation: ResourceLocation,
@SerializedName("volumeExpr") private val _volumeExpr: Expression? = "1.0".asExpression(),
@SerializedName("pitchExpr") private val _pitchExpr: Expression? = "1.0".asExpression(),
val playForPassengers: Boolean = true,
val playForNonPassengers: Boolean = false,
val submerged: Boolean = false,
val muffleEnabled: Boolean = false,
@SerializedName("attenuationModel") private val _attenuationModel: RideAttenuationModel? = RideAttenuationModel.NONE
): Encodable {
@@ -46,7 +48,9 @@ data class RideSoundSettings(
buffer.writeResourceLocation(soundLocation)
buffer.writeExpression(volumeExpr)
buffer.writeExpression(pitchExpr)
buffer.writeBoolean(playForPassengers)
buffer.writeBoolean(playForNonPassengers)
buffer.writeBoolean(submerged)
buffer.writeBoolean(muffleEnabled)
buffer.writeEnum(attenuationModel)
}
@@ -57,7 +61,9 @@ data class RideSoundSettings(
val soundLocation = buffer.readResourceLocation()
val volumeExpr = buffer.readExpression()
val pitchExpr = buffer.readExpression()
val playForPassengers = buffer.readBoolean()
val playForNonPassengers = buffer.readBoolean()
val submerged = buffer.readBoolean()
val muffleEnabled = buffer.readBoolean()
val attenuationModel = buffer.readEnum(RideAttenuationModel::class.java)
@@ -65,7 +71,9 @@ data class RideSoundSettings(
soundLocation,
volumeExpr,
pitchExpr,
playForPassengers,
playForNonPassengers,
submerged,
muffleEnabled,
attenuationModel
)
@@ -8,30 +8,58 @@
package com.cobblemon.mod.common.api.stats
import com.cobblemon.mod.common.Cobblemon
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.resources.ResourceLocation
import net.minecraft.stats.StatFormatter
object CobblemonStats {
val stats = mutableMapOf<String, ResourceLocation>()
val stats = mutableMapOf<String, CobblemonStat>()
var CAPTURED: ResourceLocation = ResourceLocation.fromNamespaceAndPath("cobblemon", "captured") //makeCustomStat("captured", StatFormatter.DEFAULT)
val SHINIES_CAPTURED: ResourceLocation = ResourceLocation.fromNamespaceAndPath("cobblemon", "shinies_captured") //makeCustomStat("shinies_captured", StatFormatter.DEFAULT)
val RELEASED: ResourceLocation = ResourceLocation.fromNamespaceAndPath("cobblemon", "released") //makeCustomStat("released", StatFormatter.DEFAULT)
val EVOLVED: ResourceLocation = ResourceLocation.fromNamespaceAndPath("cobblemon", "evolved") //makeCustomStat("evolved", StatFormatter.DEFAULT)
val LEVEL_UP: ResourceLocation = ResourceLocation.fromNamespaceAndPath("cobblemon", "level_up") //makeCustomStat("level_up", StatFormatter.DEFAULT)
val BATTLES_WON: ResourceLocation = ResourceLocation.fromNamespaceAndPath("cobblemon", "battles_won") //makeCustomStat("battles_won", StatFormatter.DEFAULT)
val BATTLES_LOST: ResourceLocation = ResourceLocation.fromNamespaceAndPath("cobblemon", "battles_lost") //makeCustomStat("battles_lost", StatFormatter.DEFAULT)
val BATTLES_FLED: ResourceLocation = ResourceLocation.fromNamespaceAndPath("cobblemon", "battles_fled") //makeCustomStat("battles_fled", StatFormatter.DEFAULT)
val BATTLES_TOTAL: ResourceLocation = ResourceLocation.fromNamespaceAndPath("cobblemon", "battles_total") //makeCustomStat("battles_total", StatFormatter.DEFAULT)
val DEX_ENTRIES: ResourceLocation = ResourceLocation.fromNamespaceAndPath("cobblemon", "dex_entries") //makeCustomStat("dex_entries", StatFormatter.DEFAULT)
val EGGS_COLLECTED: ResourceLocation = ResourceLocation.fromNamespaceAndPath("cobblemon", "eggs_collected") //makeCustomStat("eggs_collected", StatFormatter.DEFAULT)
val EGGS_HATCHED: ResourceLocation = ResourceLocation.fromNamespaceAndPath("cobblemon", "eggs_hatched") //makeCustomStat("eggs_hatched", StatFormatter.DEFAULT)
val TRADED: ResourceLocation = ResourceLocation.fromNamespaceAndPath("cobblemon", "traded") //makeCustomStat("traded", StatFormatter.DEFAULT)
val FOSSILS_REVIVED: ResourceLocation = ResourceLocation.fromNamespaceAndPath("cobblemon", "fossils_revived") //makeCustomStat("fossils_revived", StatFormatter.DEFAULT)
val TIMES_RIDDEN: ResourceLocation = ResourceLocation.fromNamespaceAndPath("cobblemon", "times_ridden")
val ROD_CASTS: ResourceLocation = ResourceLocation.fromNamespaceAndPath("cobblemon", "rod_casts")
val REEL_INS: ResourceLocation = ResourceLocation.fromNamespaceAndPath("cobblemon", "reel_ins")
val POKEMON_INTERACTED_WITH: ResourceLocation = ResourceLocation.fromNamespaceAndPath("cobblemon", "pokemon_interacted_with") //makeCustomStat("pokemon_interacted_with", StatFormatter.DEFAULT)
//TODO block, stats (interact, gimmi), riding (styles?)
@JvmField
var CAPTURED: CobblemonStat = CobblemonStat("captured")
@JvmField
val SHINIES_CAPTURED: CobblemonStat = CobblemonStat("shinies_captured")
@JvmField
val RELEASED: CobblemonStat = CobblemonStat("released")
@JvmField
val EVOLVED: CobblemonStat = CobblemonStat("evolved")
@JvmField
val LEVEL_UP: CobblemonStat = CobblemonStat("level_up")
@JvmField
val BATTLES_WON: CobblemonStat = CobblemonStat("battles_won")
@JvmField
val BATTLES_LOST: CobblemonStat = CobblemonStat("battles_lost")
@JvmField
val BATTLES_FLED: CobblemonStat = CobblemonStat("battles_fled")
@JvmField
val BATTLES_TOTAL: CobblemonStat = CobblemonStat("battles_total")
@JvmField
val DEX_ENTRIES: CobblemonStat = CobblemonStat("dex_entries")
@JvmField
val EGGS_COLLECTED: CobblemonStat = CobblemonStat("eggs_collected")
@JvmField
val EGGS_HATCHED: CobblemonStat = CobblemonStat("eggs_hatched")
@JvmField
val TRADED: CobblemonStat = CobblemonStat("traded")
@JvmField
val FOSSILS_REVIVED: CobblemonStat = CobblemonStat("fossils_revived")
@JvmField
val TIMES_RIDDEN: CobblemonStat = CobblemonStat("times_ridden")
@JvmField
val ROD_CASTS: CobblemonStat = CobblemonStat("rod_casts")
@JvmField
val REEL_INS: CobblemonStat = CobblemonStat("reel_ins")
@JvmField
val POKEMON_INTERACTED_WITH: CobblemonStat = CobblemonStat("pokemon_interacted_with")
@JvmField
val RIDING_LAND: CobblemonStat = CobblemonStat("riding_land", StatFormatter.DISTANCE)
@JvmField
val RIDING_AIR: CobblemonStat = CobblemonStat("riding_air", StatFormatter.DISTANCE)
@JvmField
val RIDING_LIQUID: CobblemonStat = CobblemonStat("riding_liquid", StatFormatter.DISTANCE)
//TODO block, stats (interact, gimmi)
fun registerStats() {
stats["captured"] = CAPTURED
@@ -52,5 +80,22 @@ object CobblemonStats {
stats["rod_casts"] = ROD_CASTS
stats["reel_ins"] = REEL_INS
stats["pokemon_interacted_with"] = POKEMON_INTERACTED_WITH
stats["riding_land"] = RIDING_LAND
stats["riding_air"] = RIDING_AIR
stats["riding_liquid"] = RIDING_LIQUID
}
@JvmStatic
fun getStat(cobblemonStat: CobblemonStat) : ResourceLocation {
val resourceLocation = cobblemonStat.resourceLocation
val stat = BuiltInRegistries.CUSTOM_STAT.get(resourceLocation)
if (stat == null) {
Cobblemon.LOGGER.error("Could not find stat with id {}", resourceLocation)
}
return stat ?: throw NullPointerException("Could not find stat with id $resourceLocation")
}
data class CobblemonStat(val path: String, val formatter: StatFormatter = StatFormatter.DEFAULT) {
val resourceLocation: ResourceLocation = ResourceLocation.fromNamespaceAndPath("cobblemon", path);
}
}
@@ -258,17 +258,22 @@ open class PartyStore(override val uuid: UUID) : PokemonStore<PartyPosition>() {
@JvmOverloads
fun toBattleTeam(clone: Boolean = false, healPokemon: Boolean = false, leadingPokemon: UUID? = null) : List<BattlePokemon> {
var leadingPokemonId = leadingPokemon
val result = this.mapNotNull {
return@mapNotNull if (clone) {
BattlePokemon.safeCopyOf(it)
BattlePokemon.safeCopyOf(it).also { copy ->
if (leadingPokemon != null && it.uuid == leadingPokemonId) {
leadingPokemonId = copy.uuid
}
}
} else {
BattlePokemon.playerOwned(it)
}.also { if (healPokemon) it.effectedPokemon.heal() }
}.toMutableList()
// reposition lead to front of the party
if (leadingPokemon != null && result.first().uuid != leadingPokemon) {
result.find { it.uuid == leadingPokemon }?.let { lead ->
if (leadingPokemonId != null && result.first().uuid != leadingPokemonId) {
result.find { it.uuid == leadingPokemonId }?.let { lead ->
result.remove(lead)
result.add(0, lead)
}
@@ -154,12 +154,12 @@ open class PlayerPartyStore(
// Metabolism for Fullness
if (pokemon.currentFullness > 0) {
pokemon.tickMetabolism()
pokemon.tickMetabolism(20)
}
// Interaction Cooldown
if (pokemon.interactionCooldowns.any()) {
pokemon.tickInteractionCooldown()
pokemon.tickInteractionCooldown(20)
}
pokemon.features.filterIsInstance<TickingSpeciesFeature>().forEach { it.onSecondPassed(player.serverLevel(), pokemon, null) }
@@ -42,6 +42,7 @@ object CobblemonItemTags {
@JvmField val DAWN_STONE_ORES = create("dawn_stone_ores")
@JvmField val POTTERY_SHERDS = create("decorated_pot_sherds")
@JvmField val DEEP_SEAS = create("deep_seas")
@JvmField val DISPLAY_CASE_ITEMS = create("display_case_items")
@JvmField val DUSK_STONE_ORES = create("dusk_stone_ores")
@JvmField val ETHERS = create("ethers")
@JvmField val EVOLUTION_ITEMS = create("evolution_items")
@@ -57,6 +58,7 @@ object CobblemonItemTags {
@JvmField val HANGING_SIGNS = create("hanging_signs")
@JvmField val HERBS = create("herbs")
@JvmField val ICE_STONE_ORES = create("ice_stone_ores")
@JvmField val ITEM_BLOCKS = create("item_blocks")
@JvmField val LEAF_STONE_ORES = create("leaf_stone_ores")
@JvmField val MACHINES = create("machines")
@JvmField val MINT_LEAF = create("mint_leaf")
@@ -65,6 +67,7 @@ object CobblemonItemTags {
@JvmField val MOCHIS = create("mochis")
@JvmField val MOON_STONE_ORES = create("moon_stone_ores")
@JvmField val MUTATED_BERRIES = create("mutated_berries")
@JvmField val PLAQUES = create("plaques")
@JvmField val PLANTS = create("plants")
@JvmField val POKE_BALLS = create("poke_balls")
@JvmField val POKE_RODS = create("poke_rods")
@@ -63,8 +63,8 @@ object BattleBuilder {
): BattleStartResult {
val adjustLevel = battleFormat.adjustLevel
val team1 = partyAccessor(player1).toBattleTeam(clone = cloneParties || adjustLevel > 0, healPokemon = healFirst, leadingPokemonPlayer1).sortedBy { it.health <= 0 }
val team2 = partyAccessor(player2).toBattleTeam(clone = cloneParties || adjustLevel > 0, healPokemon = healFirst, leadingPokemonPlayer2).sortedBy { it.health <= 0 }
val team1 = partyAccessor(player1).toBattleTeam(clone = cloneParties || adjustLevel > 0, healPokemon = healFirst, leadingPokemonPlayer1)
val team2 = partyAccessor(player2).toBattleTeam(clone = cloneParties || adjustLevel > 0, healPokemon = healFirst, leadingPokemonPlayer2)
val battlePartyStores = mutableListOf<PlayerPartyStore>()
@@ -105,12 +105,18 @@ data class BattleFormat(
}
fun toFormatJSON(): String {
/*
Bag clause does not exist in showdown as a rule because showdown simply has no concept of a 'bag'.
If we try sending it, the battle will error. So this simply omits that from ever getting sent.
If we ever find a case where we are doing this for more than just bag clause, we should make a proper abstraction.
*/
val rulesToSendToShowdown = ruleSet.filter { it != BattleRules.BAG_CLAUSE }
return """
{
"mod": "$mod",
"gameType": "${battleType.name}",
"gen": $gen,
"ruleset": [${ruleSet.joinToString { "\"$it\"" }}],
"ruleset": [${rulesToSendToShowdown.joinToString { "\"$it\"" }}],
"effectType": "Format"
}
""".trimIndent().replace("\n", "")
@@ -12,6 +12,7 @@ object BattleRules {
const val OBTAINABLE = "Obtainable"
const val PAST = "+Past"
const val UNOBTAINABLE = "+Unobtainable"
const val BAG_CLAUSE = "Bag Clause"
const val TEAM_PREVIEW = "Team Preview"
const val ENDLESS_BATTLE_CLAUSE = "Endless Battle Clause"
const val CANCEL_MOD = "Cancel Mod"
@@ -289,6 +289,8 @@ class ShowdownMoveset {
var canDynamax = false
var maxMoves: List<InBattleGimmickMove?>? = null
var canTerastallize: String? = null
/** This stores a list of gimmicks that have already been used this turn by a previous Pokemon, but the full turn has not been processed (ie. double battles). */
val pendingGimmickUsedThisTurn = mutableListOf<Gimmick>()
fun saveToBuffer(buffer: RegistryFriendlyByteBuf) {
buffer.writeSizedInt(IntSize.U_BYTE, moves.size)
@@ -300,4 +300,35 @@ object ShowdownInterpreter {
} ?:
MissingContext()
}
}
/**
* Registers a new update instruction parser.
* @param id The ID of the instruction to register.
* @param callback The callback function to create the instruction.
*/
@JvmStatic
fun registerUpdateInstructionParser(id: String, callback: (PokemonBattle, InstructionSet, BattleMessage, Iterator<BattleMessage>) -> InterpreterInstruction) {
updateInstructionParser[id] = callback
}
/**
* Registers a new split instruction parser.
* @param id The ID of the instruction to register.
* @param callback The callback function to create the instruction.
*/
@JvmStatic
fun registerSplitInstructionParser(id: String, callback: (PokemonBattle, BattleActor, InstructionSet, BattleMessage, BattleMessage, Iterator<BattleMessage>) -> InterpreterInstruction) {
splitInstructionParser[id] = callback
}
/**
* Registers a new side instruction parser.
* @param id The ID of the instruction to register.
* @param callback The callback function to create the instruction.
*/
@JvmStatic
fun registerSideInstructionParser(id: String, callback: (PokemonBattle, BattleActor, InstructionSet, BattleMessage) -> InterpreterInstruction) {
sideInstructionParser[id] = callback
}
}
@@ -31,6 +31,7 @@ import kotlin.collections.associateWith
import kotlin.collections.partition
import kotlin.math.absoluteValue
/**
* AI that tries to choose the best move for the given situations. Based off of the Pokemon Trainer Tournament Simulator Github
* https://github.com/cRz-Shadows/Pokemon_Trainer_Tournament_Simulator/blob/main/pokemon-showdown/sim/examples/Simulation-test-1.ts#L330
@@ -271,6 +272,11 @@ class StrongBattleAI(skill: Int) : BattleAI {
}
activeTrackerPokemon.firstTurn = false
// If HP is below 30% and not switching out, always use the most damaging move
if (activeTrackerPokemon.currentHpPercent < 0.3 && !shouldSwitchOut(aiSide, battle, activeBattlePokemon, moveset)) {
findAndUseMostDamagingMove(activeBattlePokemon, activeTrackerPokemon, opponents, availableMoves, moveset, battle, aiSide)?.let { return it }
}
// Decision-making based on move availability and switch-out condition
if (!shouldSwitchOut(aiSide, battle, activeBattlePokemon, moveset)) {
val nRemainingMons = activeTracker.alliedSide.actors.sumOf { actor -> actor.party.filter { it.currentHpPercent > 0 }.size }
@@ -521,6 +527,19 @@ class StrongBattleAI(skill: Int) : BattleAI {
val opponentActiveTracker = activeTracker.opponentSide.activePokemon
val opponentSpeedEstimations = opponentActiveTracker.map { statEstimationActive(it, Stats.SPEED) }
// Only switch if a switch-in is significantly better than the current Pokémon
val currentScore = estimateMatchup(activeBattlePokemon, side, battle)
val bestSwitchScore = availableSwitches.maxOfOrNull { estimateMatchup(activeBattlePokemon, side, battle, it) } ?: currentScore
val improvementThreshold = currentScore.absoluteValue * 0.25 + 2
if (bestSwitchScore <= currentScore + improvementThreshold) {
return false
}
if(bestSwitchScore < 1) {
return false
}
// todo add some way to keep track of the player's boosting to see if it needs to switch out to something that can stop it
// if slower speed stat than the opposing pokemon and HP is less than 20% don't switch out
@@ -548,6 +567,8 @@ class StrongBattleAI(skill: Int) : BattleAI {
if (availableSwitches.isEmpty() || availableSwitches.none { estimateMatchup(activeBattlePokemon, side, battle, it) > 0 })
return false
// ...and a 'good' reason to switch out
if (opponentActiveTracker.any { it.boosts.getOrDefault(Stats.ACCURACY, 0) <= accuracySwitchThreshold } ||
opponentActiveTracker.any { it.boosts.getOrDefault(Stats.DEFENCE, 0) <= -3 } ||
@@ -114,7 +114,14 @@ class ActivateInstruction(val instructionSet: InstructionSet, val message: Battl
}
"maxguard", "protect" -> battleLang("activate.protect", pokemonName)
"shadowforce", "hyperspacefury", "hyperspacehole" -> battleLang("activate.phantomforce", pokemonName)
else -> battleLang("activate.${effect.id}", pokemonName, sourceName, extraEffect)
else -> {
val msgVariant = message.optionalArgument("msg")
if (msgVariant != null) {
battleLang("activate.${effect.id}.$msgVariant", pokemonName, sourceName, extraEffect)
} else {
battleLang("activate.${effect.id}", pokemonName, sourceName, extraEffect)
}
}
}
battle.broadcastChatMessage(lang)
//We check holds here so the chat msg + particle effect happen more concurrently, instead of sequentially
@@ -52,7 +52,7 @@ class FaintInstruction(battle: PokemonBattle, val message: BattleMessage) : Inte
preamble.thenAccept {
faintingPokemon.effectedPokemon.currentHealth = 0
faintingPokemon.sendUpdate()
CobblemonEvents.BATTLE_FAINTED.post(BattleFaintedEvent(battle, pokemon, context))
CobblemonEvents.BATTLE_FAINTED.post(BattleFaintedEvent(battle, pokemon, context, pnx))
battle.getActorAndActiveSlotFromPNX(pnx).second.battlePokemon = null
faintingPokemon.contextManager.add(context)
faintingPokemon.contextManager.clear(BattleContext.Type.STATUS, BattleContext.Type.VOLATILE, BattleContext.Type.BOOST, BattleContext.Type.UNBOOST)
@@ -26,6 +26,7 @@ import com.cobblemon.mod.common.util.battleLang
class FormeChangeInstruction(val message: BattleMessage): InterpreterInstruction {
override fun invoke(battle: PokemonBattle) {
val (pnx, _) = message.pnxAndUuid(0) ?: return
val battlePokemon = message.battlePokemon(0, battle) ?: return
val details = message.argumentAt(1)?.split(',')?.get(0)?.lowercase() ?: return
val speciesName = details.substringBefore('-')
@@ -37,7 +38,7 @@ class FormeChangeInstruction(val message: BattleMessage): InterpreterInstruction
battle.dispatchWaiting {
battle.minorBattleActions[battlePokemon.uuid] = message
CobblemonEvents.FORME_CHANGE.post(FormeChangeEvent(battle, battlePokemon, formName))
CobblemonEvents.FORME_CHANGE.post(FormeChangeEvent(battle, battlePokemon, formName, pnx))
val pokemonName = battlePokemon.getName()
val lang = when(formName) {
"busted", "hero", "complete" -> return@dispatchWaiting
@@ -166,6 +166,11 @@ class SwitchInstruction(val instructionSet: InstructionSet, val battleActor: Bat
// If we can't find the entity for some reason then we're going to skip the recall animation
val sendOutFuture = CompletableFuture<Unit>()
(pokemonEntity?.recallWithAnimation() ?: CompletableFuture.completedFuture(Unit)).thenApply {
if (battle.ended) {
sendOutFuture.complete(Unit)
return@thenApply
}
// Queue actual swap and send-in after the animation has ended
actor.pokemonList.swap(actor.activePokemon.indexOf(activePokemon), actor.pokemonList.indexOf(newPokemon))
activePokemon.battlePokemon = newPokemon
@@ -43,17 +43,17 @@ class WinInstruction(val message: BattleMessage): InterpreterInstruction {
val losersText = losers.map { it.getName() }.reduce { acc, next -> acc + " & " + next }
val wasCaught = battle.showdownMessages.any { "capture" in it }
battle.dispatch {
// If the battle was a PvW battle, we need to set the killer of the wild Pokémon to the player
if (battle.isPvW) {
val nonPlayerActor = battle.actors.first { it.type == ActorType.WILD }
val wildPokemon: BattlePokemon = nonPlayerActor.pokemonList.first()
// If the battle was a PvW battle, we need to set the killer of the wild Pokémon to the player
if (battle.isPvW) {
val nonPlayerActor = battle.actors.first { it.type == ActorType.WILD }
val wildPokemon: BattlePokemon = nonPlayerActor.pokemonList.first()
if (!wasCaught && losers.any { it.uuid == wildPokemon.uuid }) {
wildPokemon.effectedPokemon.entity?.killer = (battle.actors.firstOrNull { it.type == ActorType.PLAYER } as? PlayerBattleActor)?.entity
}
if (!wasCaught && losers.any { it.uuid == wildPokemon.uuid }) {
wildPokemon.effectedPokemon.entity?.killer = (battle.actors.firstOrNull { it.type == ActorType.PLAYER } as? PlayerBattleActor)?.entity
}
}
battle.dispatch {
// broadcast victory / defeat
if (!wasCaught) {
val blackedOut = battle.isPvW && losers.any { it is PlayerBattleActor }
@@ -36,10 +36,18 @@ import java.util.function.Function
open class BattlePokemon(
val originalPokemon: Pokemon,
val effectedPokemon: Pokemon = originalPokemon,
val postBattleEntityOperation: (PokemonEntity) -> Unit = {}
var postBattlePokemonOperations: MutableList<(BattlePokemon) -> Unit> = mutableListOf(),
var postBattleEntityOperations: MutableList<(PokemonEntity) -> Unit> = mutableListOf()
) {
lateinit var actor: BattleActor
constructor(originalPokemon: Pokemon, effectedPokemon: Pokemon, postBattleEntityOperation: (PokemonEntity) -> Unit) : this(
originalPokemon = originalPokemon,
effectedPokemon = effectedPokemon,
postBattlePokemonOperations = mutableListOf<(BattlePokemon) -> Unit>(),
postBattleEntityOperations = mutableListOf(postBattleEntityOperation)
)
companion object {
fun safeCopyOf(pokemon: Pokemon): BattlePokemon {
//TOOD figure out a closer registry access (might have to break some method signatures for this (1.7?)
@@ -19,6 +19,7 @@ import net.minecraft.world.InteractionResult
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.BlockItem
import net.minecraft.world.item.context.BlockPlaceContext
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.Level
import net.minecraft.world.level.LevelAccessor
import net.minecraft.world.level.block.BaseEntityBlock
@@ -110,6 +111,8 @@ class DisplayCaseBlock(settings: Properties) : BaseEntityBlock(settings) {
override fun getRenderShape(state: BlockState) = RenderShape.MODEL
override fun getShadeBrightness(state: BlockState, level: BlockGetter, pos: BlockPos): Float = 0.75F
override fun getAnalogOutputSignal(state: BlockState, world: Level, pos: BlockPos): Int {
val stack = (world.getBlockEntity(pos) as DisplayCaseBlockEntity).getStack()
@@ -29,6 +29,7 @@ import net.minecraft.world.level.block.SoundType
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.StateDefinition
import net.minecraft.world.level.block.state.properties.BlockStateProperties
import net.minecraft.world.level.block.state.properties.BlockStateProperties.WATERLOGGED
import net.minecraft.world.level.block.state.properties.DoubleBlockHalf
import net.minecraft.world.level.block.state.properties.EnumProperty
import net.minecraft.world.level.block.state.properties.IntegerProperty
@@ -43,7 +44,6 @@ class HeartyGrainsBlock(settings: Properties) : CropBlock(settings), SimpleWater
val CODEC = simpleCodec(::HeartyGrainsBlock)
const val MATURE_AGE = 6
const val AGE_AFTER_HARVEST = 3 // Last single block stage
val WATERLOGGED = BlockStateProperties.WATERLOGGED
val AGE: IntegerProperty = IntegerProperty.create("age", 0, MATURE_AGE)
val HALF: EnumProperty<DoubleBlockHalf> = BlockStateProperties.DOUBLE_BLOCK_HALF
@@ -21,6 +21,7 @@ import com.cobblemon.mod.common.util.isInBattle
import com.cobblemon.mod.common.util.playSoundServer
import com.cobblemon.mod.common.util.toVec3d
import com.cobblemon.mod.common.util.voxelShape
import com.cobblemon.mod.common.util.rotateShape
import com.mojang.serialization.MapCodec
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
@@ -71,10 +72,10 @@ class PastureBlock(settings: Properties): BaseEntityBlock(settings), SimpleWater
private val WEST_AABB_TOP = buildCollider(top = true, Direction.WEST)
private val EAST_AABB_TOP = buildCollider(top = true, Direction.EAST)
private val SOUTH_AABB_BOTTOM = buildCollider(top = false, Direction.SOUTH)
private val NORTH_AABB_BOTTOM = buildCollider(top = false, Direction.NORTH)
private val WEST_AABB_BOTTOM = buildCollider(top = false, Direction.WEST)
private val EAST_AABB_BOTTOM = buildCollider(top = false, Direction.EAST)
private val SOUTH_AABB_BOTTOM = rotateShape(Direction.EAST, Direction.SOUTH, EAST_AABB_BOTTOM)
private val NORTH_AABB_BOTTOM = rotateShape(Direction.EAST, Direction.NORTH, EAST_AABB_BOTTOM)
private val WEST_AABB_BOTTOM = rotateShape(Direction.EAST, Direction.WEST, EAST_AABB_BOTTOM)
private fun buildCollider(top: Boolean, direction: Direction): VoxelShape {
if (top) {
@@ -0,0 +1,128 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.block
import com.cobblemon.mod.common.util.rotateShape
import com.mojang.serialization.MapCodec
import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.world.item.context.BlockPlaceContext
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.LevelAccessor
import net.minecraft.world.level.LevelReader
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.Blocks
import net.minecraft.world.level.block.FaceAttachedHorizontalDirectionalBlock
import net.minecraft.world.level.block.SimpleWaterloggedBlock
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.StateDefinition
import net.minecraft.world.level.block.state.properties.AttachFace
import net.minecraft.world.level.block.state.properties.BlockStateProperties.WATERLOGGED
import net.minecraft.world.level.material.FluidState
import net.minecraft.world.level.material.Fluids
import net.minecraft.world.phys.shapes.CollisionContext
import net.minecraft.world.phys.shapes.Shapes
import net.minecraft.world.phys.shapes.VoxelShape
class PlaqueBlock(properties : Properties) : FaceAttachedHorizontalDirectionalBlock(properties), SimpleWaterloggedBlock {
companion object {
val CODEC: MapCodec<PlaqueBlock> = RecordCodecBuilder.mapCodec {
it.group(propertiesCodec()).apply(it, ::PlaqueBlock)
}
val FLOOR_NORTH = Shapes.or(
Shapes.box(0.0625, 0.25, 0.4375, 0.9375, 1.0, 0.5625),
Shapes.box(0.125, 0.0, 0.4375, 0.25, 0.25, 0.5625),
Shapes.box(0.75, 0.0, 0.4375, 0.875, 0.25, 0.5625),
)
val WALL_NORTH = Shapes.box(0.0625, 0.125, 0.9375, 0.9375, 0.875, 1.0)
val CEILING_NORTH = Shapes.box(0.0625, 0.0, 0.4375, 0.9375, 0.75, 0.5625)
}
init {
registerDefaultState(stateDefinition.any()
.setValue(FACING, Direction.NORTH)
.setValue(FACE, AttachFace.WALL)
.setValue(WATERLOGGED, false)
)
}
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
builder.add(FACING, FACE, WATERLOGGED)
}
override fun getFluidState(blockState: BlockState): FluidState? {
return if (blockState.getValue(WATERLOGGED)) Fluids.WATER.getSource(false) else super.getFluidState(blockState);
}
override fun getStateForPlacement(context: BlockPlaceContext): BlockState? {
val fluidState = context.level.getFluidState(context.clickedPos)
for (direction in context.nearestLookingDirections) {
val blockState: BlockState
if (direction.getAxis() === Direction.Axis.Y) {
blockState = (defaultBlockState()
.setValue(FACE, if (direction == Direction.UP) AttachFace.CEILING else AttachFace.FLOOR) as BlockState)
.setValue(FACING, context.horizontalDirection.opposite)
.setValue(WATERLOGGED, fluidState.type === Fluids.WATER
) as BlockState
} else {
blockState = (defaultBlockState()
.setValue(FACE, AttachFace.WALL) as BlockState)
.setValue(FACING, direction.opposite)
.setValue(WATERLOGGED, fluidState.type === Fluids.WATER
) as BlockState
}
if (blockState.canSurvive(context.getLevel(), context.getClickedPos())) {
return blockState
}
}
return null
}
override fun codec(): MapCodec<PlaqueBlock> = CODEC
override fun getShape(state: BlockState, blockGetter: BlockGetter, pos: BlockPos, collisionContext: CollisionContext): VoxelShape {
val direction = state.getValue(FACING)
return when (state.getValue(FACE)) {
AttachFace.FLOOR ->
if (direction.axis === Direction.Axis.X) {
rotateShape(Direction.NORTH, Direction.WEST, FLOOR_NORTH)
} else FLOOR_NORTH
AttachFace.WALL ->
when (direction) {
Direction.NORTH, Direction.UP, Direction.DOWN -> WALL_NORTH
else -> rotateShape(Direction.NORTH, direction, WALL_NORTH)
}
AttachFace.CEILING ->
if (direction.axis === Direction.Axis.X) {
rotateShape(Direction.NORTH, Direction.WEST, CEILING_NORTH)
} else CEILING_NORTH
}
}
override fun updateShape(state: BlockState, direction: Direction, neighborState: BlockState, world: LevelAccessor, pos: BlockPos, neighborPos: BlockPos): BlockState {
if (state.getValue(WATERLOGGED)) {
world.scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(world))
}
return if (getConnectedDirection(state).opposite == direction && !state.canSurvive(world, pos)) Blocks.AIR.defaultBlockState()
else super.updateShape(state, direction, neighborState, world, pos, neighborPos)
}
override fun canSurvive(state: BlockState, level: LevelReader, pos: BlockPos): Boolean {
return if (state.getValue(FACE) == AttachFace.CEILING) {
val direction = getConnectedDirection(state)
canSupportCenter(level, pos.relative(direction.opposite), direction)
} else {
super.canSurvive(state, level, pos)
}
}
}
@@ -0,0 +1,189 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.block
import com.cobblemon.mod.common.util.rotateShape
import com.mojang.serialization.MapCodec
import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.util.StringRepresentable
import net.minecraft.world.item.context.BlockPlaceContext
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.HorizontalDirectionalBlock
import net.minecraft.world.level.block.SimpleWaterloggedBlock
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.StateDefinition
import net.minecraft.world.level.block.state.properties.BlockStateProperties.HORIZONTAL_FACING
import net.minecraft.world.level.block.state.properties.BlockStateProperties.WATERLOGGED
import net.minecraft.world.level.block.state.properties.IntegerProperty
import net.minecraft.world.phys.shapes.CollisionContext
import net.minecraft.world.phys.shapes.Shapes
import net.minecraft.world.phys.shapes.VoxelShape
import kotlin.math.min
class StackableItemBlock(settings: Properties, val type: StackableItemBlockType): HorizontalDirectionalBlock(settings), SimpleWaterloggedBlock {
enum class StackableItemBlockType: StringRepresentable {
BATTLE_ITEM,
ETHER,
MAX_POTION,
POTION,
STATUS_RESTORE;
override fun getSerializedName(): String = this.name.lowercase()
companion object {
val CODEC = StringRepresentable.fromEnum(::values)
}
}
companion object {
val MAX_AMOUNT: Int = 8
val AMOUNT: IntegerProperty = IntegerProperty.create("amount", 1, MAX_AMOUNT)
val CODEC: MapCodec<StackableItemBlock> = RecordCodecBuilder.mapCodec { it.group(
propertiesCodec(),
StackableItemBlockType.CODEC.fieldOf("type").forGetter(StackableItemBlock::type)
).apply(it, ::StackableItemBlock) }
val northShapeMap: Map<StackableItemBlockType, List<VoxelShape>> = mapOf(
StackableItemBlockType.BATTLE_ITEM to listOf(
Shapes.box(0.34375, 0.0, 0.34375, 0.65625, 0.4375, 0.65625),
Shapes.or(
Shapes.box(0.09375, 0.0, 0.34375, 0.40625, 0.4375, 0.65625),
Shapes.box(0.59375, 0.0, 0.40625, 0.90625, 0.4375, 0.71875)
),
Shapes.or(
Shapes.box(0.59375, 0.0, 0.59375, 0.90625, 0.4375, 0.90625),
Shapes.box(0.09375, 0.0, 0.53125, 0.40625, 0.4375, 0.84375),
Shapes.box(0.34375, 0.0, 0.09375, 0.65625, 0.4375, 0.40625)
),
Shapes.or(
Shapes.box(0.09375, 0.0, 0.09375, 0.40625, 0.4375, 0.40625),
Shapes.box(0.59375, 0.0, 0.15625, 0.90625, 0.4375, 0.46875),
Shapes.box(0.65625, 0.0, 0.59375, 0.96875, 0.4375, 0.90625),
Shapes.box(0.15625, 0.0, 0.53125, 0.46875, 0.4375, 0.84375)
)
),
StackableItemBlockType.ETHER to listOf(
Shapes.box(0.375, 0.0, 0.3125, 0.625, 0.5, 0.6875),
Shapes.or(
Shapes.box(0.625, 0.0, 0.375, 0.875, 0.5, 0.75),
Shapes.box(0.125, 0.0, 0.3125, 0.375, 0.5, 0.6875)
),
Shapes.or(
Shapes.box(0.375, 0.0, 0.125, 0.625, 0.5, 0.5),
Shapes.box(0.125, 0.0, 0.5625, 0.375, 0.5, 0.9375),
Shapes.box(0.625, 0.0, 0.625, 0.875, 0.5, 1.0)
),
Shapes.or(
Shapes.box(0.125, 0.0, 0.0625, 0.375, 0.5, 0.4375),
Shapes.box(0.625, 0.0, 0.125, 0.875, 0.5, 0.5),
Shapes.box(0.6875, 0.0, 0.625, 0.9375, 0.5, 1.0),
Shapes.box(0.1875, 0.0, 0.5625, 0.4375, 0.5, 0.9375)
)
),
StackableItemBlockType.MAX_POTION to listOf(
Shapes.box(0.34375, 0.0, 0.3125, 0.65625, 0.5, 0.6875),
Shapes.or(
Shapes.box(0.09375, 0.0, 0.3125, 0.40625, 0.5, 0.6875),
Shapes.box(0.59375, 0.0, 0.375, 0.90625, 0.5, 0.75)
),
Shapes.or(
Shapes.box(0.34375, 0.0, 0.0625, 0.65625, 0.5, 0.4375),
Shapes.box(0.09375, 0.0, 0.5625, 0.40625, 0.5, 0.9375),
Shapes.box(0.59375, 0.0, 0.625, 0.90625, 0.5, 1.0)
),
Shapes.or(
Shapes.box(0.09375, 0.0, 0.0625, 0.40625, 0.5, 0.4375),
Shapes.box(0.15625, 0.0, 0.5625, 0.46875, 0.5, 0.9375),
Shapes.box(0.65625, 0.0, 0.625, 0.96875, 0.5, 1.0),
Shapes.box(0.59375, 0.0, 0.125, 0.90625, 0.5, 0.5)
).singleEncompassing()
),
StackableItemBlockType.POTION to listOf(
Shapes.box(0.375, 0.0, 0.3125, 0.625, 0.5625, 0.75),
Shapes.or(
Shapes.box(0.125, 0.0, 0.3125, 0.375, 0.5625, 0.75),
Shapes.box(0.625, 0.0, 0.375, 0.875, 0.5625, 0.8125)
),
Shapes.or(
Shapes.box(0.375, 0.0, 0.0625, 0.625, 0.5625, 0.5),
Shapes.box(0.625, 0.0, 0.5625, 0.875, 0.5625, 1.0),
Shapes.box(0.125, 0.0, 0.5, 0.375, 0.5625, 0.9375)
),
Shapes.or(
Shapes.box(0.125, 0.0, 0.0, 0.375, 0.5625, 0.4375),
Shapes.box(0.625, 0.0, 0.0625, 0.875, 0.5625, 0.5),
Shapes.box(0.6875, 0.0, 0.5625, 0.9375, 0.5625, 1.0),
Shapes.box(0.1875, 0.0, 0.5, 0.4375, 0.5625, 0.9375)
)
),
StackableItemBlockType.STATUS_RESTORE to listOf(
Shapes.box(0.375, 0.0, 0.3125, 0.625, 0.4375, 0.6875),
Shapes.or(
Shapes.box(0.125, 0.0, 0.3125, 0.375, 0.4375, 0.75),
Shapes.box(0.625, 0.0, 0.375, 0.875, 0.4375, 0.75)
),
Shapes.or(
Shapes.box(0.125, 0.0, 0.5, 0.375, 0.4375, 0.875),
Shapes.box(0.625, 0.0, 0.5625, 0.875, 0.4375, 0.9375),
Shapes.box(0.375, 0.0, 0.0625, 0.625, 0.4375, 0.4375)
),
Shapes.or(
Shapes.box(0.1875, 0.0, 0.5, 0.4375, 0.4375, 0.875),
Shapes.box(0.125, 0.0, 0.0, 0.375, 0.4375, 0.375),
Shapes.box(0.625, 0.0, 0.0625, 0.875, 0.4375, 0.4375),
Shapes.box(0.6875, 0.0, 0.5625, 0.9375, 0.4375, 0.9375)
)
)
)
fun getShape(type: StackableItemBlockType, count: Int): VoxelShape = northShapeMap[type]?.let { list -> list[(count - 1).coerceIn(0, list.lastIndex)] } ?: Shapes.block()
fun getMaxAmount(type: StackableItemBlockType): Int = min(northShapeMap[type]?.size ?: 1 , MAX_AMOUNT)
}
init {
registerDefaultState(stateDefinition.any()
.setValue(HORIZONTAL_FACING, Direction.NORTH)
.setValue(AMOUNT, 1)
.setValue(WATERLOGGED, false))
}
override fun codec(): MapCodec<StackableItemBlock> = CODEC
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
builder.add(HORIZONTAL_FACING, AMOUNT, WATERLOGGED)
}
override fun getCollisionShape(state: BlockState, level: BlockGetter, pos: BlockPos, context: CollisionContext): VoxelShape {
val direction = state.getValue(HORIZONTAL_FACING)
val count = state.getValue(AMOUNT)
return rotateShape(Direction.NORTH, direction, getShape(type, count))
}
override fun getShape(state: BlockState, level: BlockGetter, pos: BlockPos, context: CollisionContext): VoxelShape {
return getCollisionShape(state, level, pos, context).singleEncompassing()
}
override fun getStateForPlacement(context: BlockPlaceContext): BlockState? {
val blockState = context.level.getBlockState(context.clickedPos)
return if (blockState.`is`(this)) blockState.setValue(AMOUNT, min(getMaxAmount(type), (blockState.getValue(AMOUNT) + 1)))
else super.getStateForPlacement(context)?.setValue(HORIZONTAL_FACING, context.horizontalDirection.opposite)
}
override fun canBeReplaced(state: BlockState, useContext: BlockPlaceContext): Boolean {
return if (
!useContext.isSecondaryUseActive
&& useContext.itemInHand.`is`(this.asItem())
&& state.getValue(AMOUNT) <= getMaxAmount(type)
) true else super.canBeReplaced(state, useContext)
}
}
@@ -0,0 +1,81 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.block
import com.mojang.serialization.MapCodec
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.world.item.context.BlockPlaceContext
import net.minecraft.world.level.LevelAccessor
import net.minecraft.world.level.LevelReader
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.Blocks
import net.minecraft.world.level.block.HorizontalDirectionalBlock
import net.minecraft.world.level.block.SimpleWaterloggedBlock
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.StateDefinition
import net.minecraft.world.level.block.state.properties.BlockStateProperties.HORIZONTAL_FACING
import net.minecraft.world.level.block.state.properties.BlockStateProperties.WATERLOGGED
import net.minecraft.world.level.material.FluidState
import net.minecraft.world.level.material.Fluids
open class WallAttachedDirectionalBlock(settings: Properties): HorizontalDirectionalBlock(settings), SimpleWaterloggedBlock {
companion object {
val CODEC = simpleCodec(::WallAttachedDirectionalBlock)
}
init {
registerDefaultState(stateDefinition.any()
.setValue(HORIZONTAL_FACING, Direction.NORTH)
.setValue(WATERLOGGED, false))
}
override fun codec(): MapCodec<out WallAttachedDirectionalBlock> = CODEC
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
builder.add(HORIZONTAL_FACING, WATERLOGGED)
}
override fun getStateForPlacement(context: BlockPlaceContext): BlockState? {
var direction = context.clickedFace
if (direction === null || direction.axis === Direction.Axis.Y) {
direction = context.horizontalDirection.opposite
}
val fluidState = context.level.getFluidState(context.clickedPos)
val blockState = this.defaultBlockState()
.setValue(HORIZONTAL_FACING, direction)
.setValue(WATERLOGGED, fluidState.type === Fluids.WATER
) as BlockState
if (blockState.canSurvive(context.getLevel(), context.getClickedPos())) {
return blockState
}
return null
}
override fun getFluidState(blockState: BlockState): FluidState? = if (blockState.getValue(WATERLOGGED)) Fluids.WATER.getSource(false) else super.getFluidState(blockState)
override fun updateShape(state: BlockState, direction: Direction, neighborState: BlockState, world: LevelAccessor, pos: BlockPos, neighborPos: BlockPos): BlockState {
if (state.getValue(WATERLOGGED)) {
world.scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(world))
}
return if (state.getValue(HORIZONTAL_FACING).opposite == direction && !state.canSurvive(world, pos)) Blocks.AIR.defaultBlockState()
else super.updateShape(state, direction, neighborState, world, pos, neighborPos)
}
override fun canSurvive(state: BlockState, level: LevelReader, pos: BlockPos): Boolean {
return canAttach(level, pos, state.getValue(HORIZONTAL_FACING))
}
fun canAttach(reader: LevelReader, pos: BlockPos, direction: Direction): Boolean {
val blockPos = pos.relative(direction.opposite)
return reader.getBlockState(blockPos).isFaceSturdy(reader, blockPos, direction)
}
}
@@ -0,0 +1,68 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.block
import com.cobblemon.mod.common.util.rotateShape
import com.mojang.serialization.Codec
import com.mojang.serialization.MapCodec
import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.phys.shapes.CollisionContext
import net.minecraft.world.phys.shapes.Shapes
import net.minecraft.world.phys.shapes.VoxelShape
class WallAttachedDirectionalShapeBlock(settings: Properties, val width: Int = 16, val height: Int = 16, val depth: Int = 16): WallAttachedDirectionalBlock(settings) {
companion object {
/**
* Calculates a [VoxelShape] based on a center point of (8, 8, 16).
*
* The resulting shape is centered X horizontally and Y vertically
* expanding inwards/North along the Z-axis from the South face (16).
*
* @param width The width of the shape from 0-16, along the X-axis.
* @param height The height of the shape from 0-16 along the Y-axis.
* @param depth The depth of the shape from 0-16 along the Z-axis, extending inwards from 16.
* @return A VoxelShape.
*/
fun convertToShape(sizeX: Int, sizeY: Int, sizeZ: Int): VoxelShape {
val x = sizeX.coerceIn(0, 16).toDouble()
val y = sizeY.coerceIn(0, 16).toDouble()
val z = sizeZ.coerceIn(0, 16).toDouble()
val minX = (8.0 - (x / 2.0)) / 16.0
val maxX = (8.0 + (x / 2.0)) / 16.0
val minY = (8.0 - (y / 2.0)) / 16.0
val maxY = (8.0 + (y / 2.0)) / 16.0
val minZ = (16.0 - z) / 16.0
return Shapes.box(minX, minY, minZ, maxX, maxY, 1.0)
}
}
override fun codec(): MapCodec<WallAttachedDirectionalShapeBlock> = RecordCodecBuilder.mapCodec { instance ->
instance.group(
propertiesCodec(),
Codec.INT.fieldOf("width").forGetter { it.width },
Codec.INT.fieldOf("height").forGetter { it.height },
Codec.INT.fieldOf("depth").forGetter { it.depth }
).apply(instance, { properties, width, height, depth ->
WallAttachedDirectionalShapeBlock(properties, width, height, depth)
})
}
override fun getShape(state: BlockState, level: BlockGetter, pos: BlockPos, context: CollisionContext): VoxelShape {
val direction = state.getValue(FACING)
return rotateShape(Direction.NORTH, direction, convertToShape(width, height, depth))
}
}
@@ -0,0 +1,98 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.block
import com.cobblemon.mod.common.block.StackableItemBlock.Companion.AMOUNT
import com.cobblemon.mod.common.block.StackableItemBlock.Companion.MAX_AMOUNT
import com.cobblemon.mod.common.util.rotateShape
import com.mojang.serialization.MapCodec
import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.util.StringRepresentable
import net.minecraft.world.item.context.BlockPlaceContext
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.StateDefinition
import net.minecraft.world.level.block.state.properties.BlockStateProperties.HORIZONTAL_FACING
import net.minecraft.world.level.block.state.properties.BlockStateProperties.WATERLOGGED
import net.minecraft.world.phys.shapes.CollisionContext
import net.minecraft.world.phys.shapes.Shapes
import net.minecraft.world.phys.shapes.VoxelShape
import kotlin.math.min
class WallAttachedStackableItemBlock(settings: Properties, val type: WallStackableItemBlockType): WallAttachedDirectionalBlock(settings) {
enum class WallStackableItemBlockType: StringRepresentable {
TAG;
override fun getSerializedName(): String = this.name.lowercase()
companion object {
val CODEC = StringRepresentable.fromEnum(::values)
}
}
companion object {
val CODEC: MapCodec<WallAttachedStackableItemBlock> = RecordCodecBuilder.mapCodec { it.group(
propertiesCodec(),
WallStackableItemBlockType.CODEC.fieldOf("type").forGetter(WallAttachedStackableItemBlock::type)
).apply(it, ::WallAttachedStackableItemBlock) }
val northShapeMap: Map<WallStackableItemBlockType, List<VoxelShape>> = mapOf(
WallStackableItemBlockType.TAG to listOf(
Shapes.box(0.375, 0.25, 0.9375, 0.625, 0.6875, 1.0),
Shapes.box(0.375, 0.0625, 0.9375, 1.0, 0.6875, 1.0),
Shapes.box(0.0, 0.0625, 0.9375, 1.0, 0.9375, 1.0),
Shapes.box(0.0, 0.0625, 0.9375, 1.0, 1.0, 1.0),
Shapes.box(0.0, 0.0, 0.9375, 1.0, 1.0, 1.0),
Shapes.box(0.0, 0.0, 0.9375, 1.0, 1.0, 1.0),
Shapes.box(0.0, 0.0, 0.9375, 1.0, 1.0, 1.0),
Shapes.box(0.0, 0.0, 0.9375, 1.0, 1.0, 1.0)
)
)
fun getShape(type: WallStackableItemBlockType, count: Int): VoxelShape = northShapeMap[type]?.let { list -> list[(count - 1).coerceIn(0, list.lastIndex)] } ?: Shapes.block()
fun getMaxAmount(type: WallStackableItemBlockType): Int = min(northShapeMap[type]?.size ?: 1 , MAX_AMOUNT)
}
init {
registerDefaultState(stateDefinition.any()
.setValue(HORIZONTAL_FACING, Direction.NORTH)
.setValue(AMOUNT, 1)
.setValue(WATERLOGGED, false))
}
override fun codec(): MapCodec<WallAttachedStackableItemBlock> = CODEC
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
super.createBlockStateDefinition(builder)
builder.add(AMOUNT)
}
override fun getShape(state: BlockState, level: BlockGetter, pos: BlockPos, context: CollisionContext): VoxelShape {
val direction = state.getValue(HORIZONTAL_FACING)
val count = state.getValue(AMOUNT)
return rotateShape(Direction.NORTH, direction, getShape(type, count))
}
override fun getStateForPlacement(context: BlockPlaceContext): BlockState? {
val blockState = context.level.getBlockState(context.clickedPos)
return if (blockState.`is`(this)) blockState.setValue(AMOUNT, min(getMaxAmount(type), (blockState.getValue(AMOUNT) + 1)))
else super.getStateForPlacement(context)
}
override fun canBeReplaced(state: BlockState, useContext: BlockPlaceContext): Boolean {
return if (
!useContext.isSecondaryUseActive
&& useContext.itemInHand.`is`(this.asItem())
&& state.getValue(AMOUNT) <= getMaxAmount(type)
) true else super.canBeReplaced(state, useContext)
}
}
@@ -17,7 +17,6 @@ import com.cobblemon.mod.common.api.pasture.PastureLinkManager
import com.cobblemon.mod.common.api.scheduling.afterOnServer
import com.cobblemon.mod.common.api.text.italicise
import com.cobblemon.mod.common.api.text.red
import com.cobblemon.mod.common.api.text.textClickHandlers
import com.cobblemon.mod.common.block.PastureBlock
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.net.messages.client.effect.SpawnSnowstormEntityParticlePacket
@@ -110,10 +109,10 @@ class PokemonPastureBlockEntity(pos: BlockPos, state: BlockState) :
for (tetheredPokemon in tetheredPokemonList) {
val pokemon = tetheredPokemon.getPokemon()!!
if (pokemon.currentFullness > 0) {
pokemon.tickMetabolism()
pokemon.tickMetabolism(1)
}
if (pokemon.interactionCooldowns.any()) {
pokemon.tickInteractionCooldown()
pokemon.tickInteractionCooldown(1)
}
}
blockEntity.togglePastureOn(blockEntity.getInRangeViewerCount(world, blockEntity.blockPos) > 0)
@@ -8,9 +8,7 @@
package com.cobblemon.mod.common.client
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.Cobblemon.LOGGER
import com.cobblemon.mod.common.Cobblemon.cobblemonVR
import com.cobblemon.mod.common.CobblemonBlockEntities
import com.cobblemon.mod.common.CobblemonBlocks
import com.cobblemon.mod.common.CobblemonClientImplementation
@@ -29,7 +27,6 @@ import com.cobblemon.mod.common.client.gui.PartyOverlayDataControl
import com.cobblemon.mod.common.client.gui.RideControlsOverlay
import com.cobblemon.mod.common.client.gui.battle.BattleOverlay
import com.cobblemon.mod.common.client.gui.cookingpot.CookingPotScreen
import com.cobblemon.mod.common.client.integration.vivecraft.CobblemonVRClient
import com.cobblemon.mod.common.client.particle.BedrockParticleOptionsRepository
import com.cobblemon.mod.common.client.render.ClientPlayerIcon
import com.cobblemon.mod.common.client.render.DeferredRenderer
@@ -75,7 +72,6 @@ import com.cobblemon.mod.common.client.tooltips.TooltipManager
import com.cobblemon.mod.common.client.trade.ClientTrade
import com.cobblemon.mod.common.entity.boat.CobblemonBoatType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.integration.vivecraft.CobblemonVR
import com.cobblemon.mod.common.platform.events.PlatformEvents
import com.cobblemon.mod.common.pokedex.scanner.PokedexUsageContext
import com.cobblemon.mod.common.util.isLookingAt
@@ -156,10 +152,6 @@ object CobblemonClient {
this.registerTooltipManagers()
this.registerMenuScreens()
if (Cobblemon.implementation.isModInstalled("vivecraft")) { // VR integration
CobblemonVRClient.init()
}
LOGGER.info("Registering custom BuiltinItemRenderers")
CobblemonBuiltinItemRendererRegistry.register(CobblemonItems.POKEMON_MODEL, PokemonItemRenderer())
@@ -195,9 +187,7 @@ object CobblemonClient {
nearbyPokemon?.forEach { entity ->
if (entity is PokemonEntity && !entity.isSilent && !entity.passengers.contains(player)) {
if (player.isLookingAt(entity) && !player.isSpectator && entity.pokemon.shiny) entity.delegate.spawnShinyParticle(
player
)
if (player.isLookingAt(entity) && !player.isSpectator && entity.pokemon.shiny) entity.delegate.spawnShinyParticle(player)
entity.delegate.spawnAspectParticle()
}
}
@@ -329,7 +319,48 @@ object CobblemonClient {
CobblemonBlocks.PINK_CAMPFIRE_POT,
CobblemonBlocks.RED_CAMPFIRE_POT,
CobblemonBlocks.WHITE_CAMPFIRE_POT,
CobblemonBlocks.YELLOW_CAMPFIRE_POT
CobblemonBlocks.YELLOW_CAMPFIRE_POT,
CobblemonBlocks.WHITE_PLAQUE,
CobblemonBlocks.LIGHT_GRAY_PLAQUE,
CobblemonBlocks.GRAY_PLAQUE,
CobblemonBlocks.BLACK_PLAQUE,
CobblemonBlocks.BROWN_PLAQUE,
CobblemonBlocks.RED_PLAQUE,
CobblemonBlocks.ORANGE_PLAQUE,
CobblemonBlocks.YELLOW_PLAQUE,
CobblemonBlocks.LIME_PLAQUE,
CobblemonBlocks.GREEN_PLAQUE,
CobblemonBlocks.CYAN_PLAQUE,
CobblemonBlocks.LIGHT_BLUE_PLAQUE,
CobblemonBlocks.BLUE_PLAQUE,
CobblemonBlocks.PURPLE_PLAQUE,
CobblemonBlocks.MAGENTA_PLAQUE,
CobblemonBlocks.PINK_PLAQUE,
CobblemonBlocks.BLUNDER_POLICY,
CobblemonBlocks.WEAKNESS_POLICY,
CobblemonBlocks.POTION,
CobblemonBlocks.SUPER_POTION,
CobblemonBlocks.HYPER_POTION,
CobblemonBlocks.MAX_POTION,
CobblemonBlocks.FULL_RESTORE,
CobblemonBlocks.ANTIDOTE,
CobblemonBlocks.AWAKENING,
CobblemonBlocks.BURN_HEAL,
CobblemonBlocks.ICE_HEAL,
CobblemonBlocks.PARALYZE_HEAL,
CobblemonBlocks.FULL_HEAL,
CobblemonBlocks.ETHER,
CobblemonBlocks.MAX_ETHER,
CobblemonBlocks.ELIXIR,
CobblemonBlocks.MAX_ELIXIR,
CobblemonBlocks.DIRE_HIT,
CobblemonBlocks.GUARD_SPEC,
CobblemonBlocks.X_ACCURACY,
CobblemonBlocks.X_ATTACK,
CobblemonBlocks.X_DEFENSE,
CobblemonBlocks.X_SP_ATK,
CobblemonBlocks.X_SP_DEF,
CobblemonBlocks.X_SPEED
)
this.createBoatModelLayers()
@@ -379,10 +410,7 @@ object CobblemonClient {
this.implementation.registerBlockEntityRenderer(CobblemonBlockEntities.DISPLAY_CASE, ::DisplayCaseRenderer)
this.implementation.registerBlockEntityRenderer(CobblemonBlockEntities.LECTERN, ::LecternBlockEntityRenderer)
this.implementation.registerBlockEntityRenderer(CobblemonBlockEntities.CAMPFIRE, ::CampfireBlockEntityRenderer)
this.implementation.registerBlockEntityRenderer(
CobblemonBlockEntities.POKE_SNACK,
::PokeSnackBlockEntityRenderer
)
this.implementation.registerBlockEntityRenderer(CobblemonBlockEntities.POKE_SNACK, ::PokeSnackBlockEntityRenderer)
}
private fun registerEntityRenderers() {
@@ -411,11 +439,7 @@ object CobblemonClient {
private fun registerItemColors() {
implementation.registerItemColors(AprijuiceItemColorProvider, *CobblemonItems.aprijuices.toTypedArray())
implementation.registerItemColors(
PokeSnackItemColorProvider,
CobblemonItems.POKE_SNACK,
CobblemonItems.POKE_CAKE
)
implementation.registerItemColors(PokeSnackItemColorProvider, CobblemonItems.POKE_SNACK, CobblemonItems.POKE_CAKE)
implementation.registerItemColors(PokeBaitItemColorProvider, CobblemonItems.POKE_BAIT)
implementation.registerItemColors(PonigiriItemColorProvider, CobblemonItems.PONIGIRI)
implementation.registerItemColors(SinisterTeaItemColorProvider, CobblemonItems.SINISTER_TEA)
@@ -11,10 +11,11 @@ package com.cobblemon.mod.common.client.battle
import com.cobblemon.mod.common.CobblemonNetwork
import com.cobblemon.mod.common.api.battles.model.actor.ActorType
import com.cobblemon.mod.common.api.pokedex.PokedexEntryProgress
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.battles.BattleFormat
import com.cobblemon.mod.common.battles.ForcePassActionResponse
import com.cobblemon.mod.common.battles.MoveActionResponse
import com.cobblemon.mod.common.battles.PassActionResponse
import com.cobblemon.mod.common.battles.ShowdownMoveset
import com.cobblemon.mod.common.client.CobblemonClient
import com.cobblemon.mod.common.net.messages.server.battle.BattleSelectActionsPacket
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
@@ -61,8 +62,15 @@ class ClientBattle(
if (index != -1) {
while(index < pendingActionRequests.size) {
val request = pendingActionRequests[index]
if (request.response != PassActionResponse && request.response !is ForcePassActionResponse)
if (request.response != PassActionResponse && request.response !is ForcePassActionResponse) {
val response = request.response
// If we're cancelling a turn that used a gimmick, we re-enable the ability for that gimmick to be used.
if (response is MoveActionResponse && response.gimmickID != null) {
val gimmick = ShowdownMoveset.Gimmick.entries.find { it.id == response.gimmickID }
pendingActionRequests.forEach { it.moveSet?.pendingGimmickUsedThisTurn?.remove(gimmick) }
}
request.response = null
}
index++
}
}
@@ -68,7 +68,7 @@ class BattleMoveSelection(
var moveTiles = baseTiles
val backButton = BattleBackButton(x - 11F, Minecraft.getInstance().window.guiScaledHeight - 22F)
val gimmickButtons = moveSet.getGimmicks().mapIndexed { index, gimmick ->
val gimmickButtons = moveSet.getGimmicks().filter { it !in moveSet.pendingGimmickUsedThisTurn }.mapIndexed { index, gimmick ->
val initOff = BattleBackButton.WIDTH * 0.65F
val xOff = initOff + BattleGimmickButton.SPACING * index
BattleGimmickButton.create(gimmick, this, backButton.x + xOff, backButton.y)
@@ -17,6 +17,7 @@ import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.battles.InBattleGimmickMove
import com.cobblemon.mod.common.battles.InBattleMove
import com.cobblemon.mod.common.battles.MoveActionResponse
import com.cobblemon.mod.common.battles.ShowdownMoveset
import com.cobblemon.mod.common.client.CobblemonClient
import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.battle.ActiveClientBattlePokemon
@@ -127,7 +128,7 @@ class BattleTargetSelection(
private val isMultiTarget = multiTargetList?.firstOrNull { it.getPNX() == target.getPNX() } != null
private val isCurrentPokemon = request.activePokemon.battlePokemon?.uuid == target.battlePokemon!!.uuid
open val response: MoveActionResponse get() = MoveActionResponse(targetSelection.move.id, responseTarget)
open val response: MoveActionResponse get() = MoveActionResponse(targetSelection.move.id, responseTarget, gimmickID)
val state = FloatingState()
open val selectable: Boolean get() = isMultiTarget || (responseTarget != null)
@@ -311,6 +312,13 @@ class BattleTargetSelection(
fun onClick() {
if (!selectable) return
targetSelection.playDownSound(Minecraft.getInstance().soundManager)
// If a gimmick is used, remove the ability to use it on any pending action requests ahead of it
if (response.gimmickID != null) {
val gimmick = ShowdownMoveset.Gimmick.entries.first { it.id == response.gimmickID }
CobblemonClient.battle?.pendingActionRequests?.forEach {
it.moveSet?.pendingGimmickUsedThisTurn?.add(gimmick)
}
}
targetSelection.battleGUI.selectAction(targetSelection.request, response)
}
}
@@ -1,26 +0,0 @@
package com.cobblemon.mod.common.client.integration.vivecraft
import com.cobblemon.mod.common.client.integration.vivecraft.party.PartyOnArm
import com.cobblemon.mod.common.client.integration.vivecraft.proximity.ProximityManager
import com.cobblemon.mod.common.client.integration.vivecraft.proximity.ProximityRegistration
import com.cobblemon.mod.common.client.integration.vivecraft.listener.proximity.PokeRodBaitListener
import org.vivecraft.api.data.VRBodyPart
class CobblemonVRClient {
companion object {
fun init() {
PartyOnArm.init()
ProximityManager.init()
ProximityManager.register(
ProximityRegistration(
bodyA = VRBodyPart.MAIN_HAND,
bodyB = VRBodyPart.OFF_HAND,
distanceThreshold = 0.25f,
angleThreshold = 120f,
listener = PokeRodBaitListener("Main-OffHand")
)
)
}
}
}
@@ -1,40 +0,0 @@
package com.cobblemon.mod.common.client.integration.vivecraft.listener.proximity
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonNetwork
import com.cobblemon.mod.common.api.tags.CobblemonItemTags
import com.cobblemon.mod.common.client.integration.vivecraft.proximity.ProximityListener
import com.cobblemon.mod.common.client.integration.vivecraft.proximity.ProximityRegistration
import com.cobblemon.mod.common.net.messages.server.pokerod.ApplyBaitPacket
import net.minecraft.client.Minecraft
import net.minecraft.world.InteractionHand
import net.minecraft.world.item.ItemStack
import org.vivecraft.api.data.VRBodyPart
import org.vivecraft.api.data.VRPose
class PokeRodBaitListener(private val name: String) : ProximityListener {
//TODO: Also add that if 1 is Rod the other needs to be seasoning
override fun isActive(registration: ProximityRegistration): Boolean {
val player = Minecraft.getInstance().player ?: return false
val handA: InteractionHand = VRBodyPart.toInteractionHand(registration.bodyA) ?: return false
val itemStackA: ItemStack = player.getItemInHand(handA)
val handB: InteractionHand = VRBodyPart.toInteractionHand(registration.bodyB) ?: return false
val itemStackB: ItemStack = player.getItemInHand(handB)
return itemStackA.`is`(CobblemonItemTags.POKE_RODS) || itemStackB.`is`(CobblemonItemTags.POKE_RODS)
}
override fun onEnter(bodyA: VRBodyPart, bodyB: VRBodyPart, pose: VRPose) {
Cobblemon.LOGGER.info("[$name] Enter: $bodyA <-> $bodyB")
}
override fun onExit(bodyA: VRBodyPart, bodyB: VRBodyPart, pose: VRPose) {
Cobblemon.LOGGER.info("[$name] Exit: $bodyA <-> $bodyB")
}
override fun onTick(bodyA: VRBodyPart, bodyB: VRBodyPart, pose: VRPose) {
Cobblemon.LOGGER.info("[$name] Proximity: $bodyA <-> $bodyB")
CobblemonNetwork.sendToServer(ApplyBaitPacket())
}
}
@@ -1,237 +0,0 @@
package com.cobblemon.mod.common.client.integration.vivecraft.listener.swing
import com.cobblemon.mod.common.Cobblemon
import net.minecraft.client.Minecraft
import net.minecraft.world.InteractionHand
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.ItemStack
import net.minecraft.world.phys.Vec3
import org.vivecraft.api.client.VRClientAPI
import org.vivecraft.api.client.tracker.swing.SwingContext
import org.vivecraft.api.client.tracker.swing.SwingListener
import org.vivecraft.api.data.VRBodyPart
import kotlin.math.abs
/**
* Listener that classifies directional swings (up, down, forward, backward, left, right)
* using Vivecraft VR pose history and invokes handler methods for each direction.
*
* @param forwardDotThreshold Dot product threshold used to classify forward/back/left/right directions.
* @param historyTicks Number of ticks used to compute velocity from historical poses.
*/
abstract class DirectionalSwingListener(
private val forwardDotThreshold: Double = 0.3,
private val historyTicks: Int = 10
) : SwingListener {
protected val minecraft: Minecraft = Minecraft.getInstance()
protected val clientData: VRClientAPI = VRClientAPI.instance()
/**
* Compute the net movement vector for the specified body part over the given number of ticks.
* Returns null if historical pose data is unavailable, insufficient, or movement is negligible.
*
* @param bodyPart The VR body part to compute movement for.
* @param ticks The number of ticks of history to consider.
* @return The movement vector or null if unavailable/insignificant.
*/
private fun changeForVelocity(bodyPart: VRBodyPart, ticks: Int = historyTicks): Vec3? {
val history = clientData.historicalVRPoses ?: return null
if (history.ticksOfHistory() < ticks) return null
val delta = history.netMovement(bodyPart, ticks)
delta?.lengthSqr()?.let { if (it < 1e-6) return null }
return delta
}
/**
* Called when an upward swing is detected.
*
* @param useHand The hand used for the swing.
* @param handVel The velocity vector of the hand during the swing.
* @param player The player performing the swing.
* @return True if the upward action was handled, false otherwise.
*/
protected open fun onUpwardSwing(
useHand: InteractionHand,
player: Player
): Boolean {
Cobblemon.LOGGER.info(
"Ignoring swing: upward movement. player={}, hand={}",
player, useHand
)
return true
}
/**
* Called when a downward swing is detected.
*
* @param useHand The hand used for the swing.
* @param handVel The velocity vector of the hand during the swing.
* @param player The player performing the swing.
* @return True if the downward action was handled, false otherwise.
*/
protected open fun onDownwardSwing(
useHand: InteractionHand,
handVel: Vec3,
player: Player
): Boolean {
Cobblemon.LOGGER.info(
"Ignoring swing: downward movement. handVel={}, player={}, hand={}",
handVel, player, useHand
)
return true
}
/**
* Called when a forward swing is detected.
*
* @param useHand The hand used for the swing.
* @param handVel The velocity vector of the hand during the swing.
* @param player The player performing the swing.
* @return True if the forward action was handled, false otherwise.
*/
protected open fun onForwardSwing(
useHand: InteractionHand,
handVel: Vec3,
player: Player
): Boolean {
return true
}
/**
* Called when a backward swing is detected.
*
* @param useHand The hand used for the swing.
* @param handVel The velocity vector of the hand during the swing.
* @param player The player performing the swing.
* @return True if the backward action was handled, false otherwise.
*/
protected open fun onBackwardSwing(
useHand: InteractionHand,
handVel: Vec3,
player: Player
): Boolean {
Cobblemon.LOGGER.info(
"Ignoring swing: backward movement. handVel={}, player={}, hand={}",
handVel, player, useHand
)
return true
}
/**
* Called when a leftward swing is detected.
*
* @param useHand The hand used for the swing.
* @param handVel The velocity vector of the hand during the swing.
* @param player The player performing the swing.
* @return True if the leftward action was handled, false otherwise.
*/
protected open fun onLeftSwing(
useHand: InteractionHand,
handVel: Vec3,
player: Player
): Boolean {
Cobblemon.LOGGER.info(
"Ignoring swing: leftward movement. handVel={}, player={}, hand={}",
handVel, player, useHand
)
return true
}
/**
* Called when a rightward swing is detected.
*
* @param useHand The hand used for the swing.
* @param handVel The velocity vector of the hand during the swing.
* @param player The player performing the swing.
* @return True if the rightward action was handled, false otherwise.
*/
protected open fun onRightSwing(
useHand: InteractionHand,
handVel: Vec3,
player: Player
): Boolean {
Cobblemon.LOGGER.info(
"Ignoring swing: rightward movement. handVel={}, player={}, hand={}",
handVel, player, useHand
)
return true
}
/**
* Checks if the given item stack is valid for processing by this swing listener.
* Subclasses must implement this to specify which items they handle.
* @param itemStack The item stack to validate.
* @return True if the item stack is valid, false otherwise.
*/
protected abstract fun isValidItem(itemStack: ItemStack): Boolean
/**
* Called when a swing ends. Validates the held item, computes hand velocity from historical poses,
* handles vertical swings first (upward/downward) and then classifies horizontal direction:
* forward/backward by dot with player look direction; left/right by projecting onto a right vector
* computed from world up and forward. Ambiguous swings are ignored.
*
* @param context Vivecraft swing context containing body part information.
*/
override fun onSwingEnd(context: SwingContext?) {
val player = minecraft.player ?: return
val ctx = context ?: return
val useHand: InteractionHand = VRBodyPart.toInteractionHand(ctx.bodyPart) ?: return
val itemStack: ItemStack = player.getItemInHand(useHand)
if (!isValidItem(itemStack)) return
val handVel = changeForVelocity(ctx.bodyPart) ?: run {
Cobblemon.LOGGER.info(
"Ignoring swing: insufficient historical pose data for velocity. bodyPart={}, player={}",
ctx.bodyPart, player
)
return
}
val vertical = handVel.y
if (vertical > 0.0) {
if (onUpwardSwing(useHand, player)) return
} else if (vertical < 0.0) {
if (onDownwardSwing(useHand, handVel, player)) return
}
val forward = player.lookAngle.normalize()
val handDir = handVel.normalize()
val forwardDot = handDir.dot(forward)
val worldUp = Vec3(0.0, 1.0, 0.0)
val right = forward.cross(worldUp).let {
if (it.lengthSqr() < 1e-6) Vec3(1.0, 0.0, 0.0) else it.normalize()
}
val rightDot = handDir.dot(right)
if (forwardDot > forwardDotThreshold) {
if (onForwardSwing(useHand, handVel, player)) return
return
}
if (forwardDot < -forwardDotThreshold) {
if (onBackwardSwing(useHand, handVel, player)) return
return
}
if (abs(rightDot) > forwardDotThreshold) {
if (rightDot > 0.0) {
if (onRightSwing(useHand, handVel, player)) return
} else {
if (onLeftSwing(useHand, handVel, player)) return
}
return
}
Cobblemon.LOGGER.info(
"Ignoring swing: ambiguous direction. forwardDot={}, rightDot={}, handVel={}, player={}",
forwardDot, rightDot, handVel, player
)
}
}
@@ -1,29 +0,0 @@
package com.cobblemon.mod.common.client.integration.vivecraft.listener.swing
import com.cobblemon.mod.common.CobblemonNetwork
import com.cobblemon.mod.common.api.tags.CobblemonItemTags
import com.cobblemon.mod.common.net.messages.server.pokeball.SendOutPokeBallPacket
import net.minecraft.world.InteractionHand
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.ItemStack
import net.minecraft.world.phys.Vec3
class PokeBallSwingListener : DirectionalSwingListener() {
override fun isValidItem(itemStack: ItemStack): Boolean =
itemStack.`is`(CobblemonItemTags.POKE_BALLS)
override fun onForwardSwing(
useHand: InteractionHand,
handVel: Vec3,
player: Player
): Boolean {
CobblemonNetwork.sendToServer(SendOutPokeBallPacket())
return true
}
override fun onDownwardSwing(useHand: InteractionHand, handVel: Vec3, player: Player): Boolean {
CobblemonNetwork.sendToServer(SendOutPokeBallPacket())
return super.onDownwardSwing(useHand, handVel, player)
}
}
@@ -1,31 +0,0 @@
package com.cobblemon.mod.common.client.integration.vivecraft.listener.swing
import com.cobblemon.mod.common.CobblemonNetwork
import com.cobblemon.mod.common.api.tags.CobblemonItemTags
import com.cobblemon.mod.common.net.messages.server.pokerod.CastPokeRodPacket
import com.cobblemon.mod.common.net.messages.server.pokerod.ReelPokeRodPacket
import net.minecraft.world.InteractionHand
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.ItemStack
import net.minecraft.world.phys.Vec3
class PokeRodSwingListener : DirectionalSwingListener() {
override fun isValidItem(itemStack: ItemStack): Boolean =
itemStack.`is`(CobblemonItemTags.POKE_RODS)
override fun onUpwardSwing(useHand: InteractionHand, player: Player): Boolean {
CobblemonNetwork.sendToServer(ReelPokeRodPacket(useHand == InteractionHand.MAIN_HAND))
return super.onUpwardSwing(useHand, player)
}
override fun onForwardSwing(useHand: InteractionHand, handVel: Vec3, player: Player): Boolean {
CobblemonNetwork.sendToServer(CastPokeRodPacket(useHand == InteractionHand.MAIN_HAND))
return super.onForwardSwing(useHand, handVel, player)
}
override fun onDownwardSwing(useHand: InteractionHand, handVel: Vec3, player: Player): Boolean {
CobblemonNetwork.sendToServer(CastPokeRodPacket(useHand == InteractionHand.MAIN_HAND))
return super.onDownwardSwing(useHand, handVel, player)
}
}
@@ -1,188 +0,0 @@
package com.cobblemon.mod.common.client.integration.vivecraft.party
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.client.CobblemonClient
import com.cobblemon.mod.common.entity.pokeball.EmptyPokeBallEntity
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.platform.events.ClientTickEvent
import com.cobblemon.mod.common.platform.events.PlatformEvents
import com.cobblemon.mod.common.platform.events.RenderEvent
import com.cobblemon.mod.common.pokemon.Pokemon
import com.mojang.math.Axis
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.player.LocalPlayer
import net.minecraft.client.renderer.LightTexture
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.InteractionHand
import net.minecraft.world.entity.Entity
import net.minecraft.world.phys.Vec3
import org.joml.Vector3f
import org.vivecraft.api.VRAPI
import org.vivecraft.api.client.InteractModule
import org.vivecraft.api.client.VRClientAPI
import org.vivecraft.api.data.VRBodyPartData
import java.util.function.Consumer
class PartyOnArm : InteractModule {
val fakePartyEntities: Array<Entity?> = arrayOfNulls(6)
var selectedIndex = -1
private fun onTick(tickEvent: ClientTickEvent.Post) {
val client = tickEvent.client
client.player ?: return
val playerData = VRClientAPI.instance().postTickWorldPose ?: return
val offHandData = playerData.offHand
// Keep up-to-date fakePartyEntities, which is done so we always have a Pokemon or PokeBall entity available to
// render and keep it ticking so we have proper animations.
val party = CobblemonClient.storage.party
// Handle the selected index, placing either a Pokemon or null
if (selectedIndex > -1) {
val actual = party.get(selectedIndex)
if (actual == null) {
fakePartyEntities[selectedIndex] = null
} else if (fakePartyEntities[selectedIndex] is PokemonEntity) {
fakePartyEntities[selectedIndex]!!.tick()
} else {
fakePartyEntities[selectedIndex] = PokemonEntity(client.level!!, actual)
}
}
// Handle the other indices, placing a pokeball or null
for (i in 0..5) {
if (i != selectedIndex) {
val actual = party.get(i)
if (actual == null) {
fakePartyEntities[i] = null
} else if (fakePartyEntities[i] !is EmptyPokeBallEntity) {
val pokeBall = EmptyPokeBallEntity(actual.caughtBall, client.level!!)
pokeBall.captureState = EmptyPokeBallEntity.CaptureState.FALL
fakePartyEntities[i] = pokeBall
} else {
fakePartyEntities[i]!!.tick()
}
}
}
// Check primary hand for overlap with any of the Pokemon
val centerPos = getCenterPos(offHandData)
selectedIndex = -1
for (i in 0..5) {
if (party.get(i) != null) {
val pokemonPos = getPokemonPos(centerPos, playerData.offHand, i)
if (playerData.mainHand.pos.distanceToSqr(pokemonPos) < DIST_SELECT_SQR) {
selectedIndex = i
break
}
}
}
}
private fun onRender(renderEvent: RenderEvent) {
if (renderEvent.stage != RenderEvent.Stage.AFTER_ENTITIES) return
val mc = Minecraft.getInstance()
val bufferSource = mc.renderBuffers().bufferSource()
val guiGraphics = GuiGraphics(mc, mc.renderBuffers().bufferSource())
val cameraPos = renderEvent.camera.getPosition()
val poseStack = guiGraphics.pose()
val partialTick = renderEvent.tickCounter.getGameTimeDeltaPartialTick(true)
val offHandData = VRClientAPI.instance().getWorldRenderPose()?.offHand ?: return
val centerPos = getCenterPos(offHandData)
for (i in 0..5) {
val pokemon = fakePartyEntities[i]
if (pokemon != null) {
poseStack.pushPose()
val pokemonPos = getPokemonPos(centerPos, offHandData, i)
val renderPos = Vec3(
-cameraPos.x + pokemonPos.x,
-cameraPos.y + pokemonPos.y,
-cameraPos.z + pokemonPos.z,
)
poseStack.translate(renderPos.x, renderPos.y, renderPos.z)
poseStack.scale(0.05f, 0.05f, 0.05f)
poseStack.mulPose(Axis.YN.rotation(offHandData.yaw.toFloat()))
poseStack.mulPose(Axis.XN.rotation(offHandData.pitch.toFloat()))
poseStack.mulPose(Axis.ZP.rotation(offHandData.roll.toFloat() + Math.PI.toFloat()))
mc.entityRenderDispatcher.getRenderer(pokemon).render(pokemon, 0f, partialTick, poseStack, bufferSource,
LightTexture.FULL_BRIGHT)
poseStack.popPose()
}
}
}
private fun getCenterPos(pose: VRBodyPartData): Vec3 {
return pose.pos.subtract(pose.dir.scale(0.125))
}
private fun getPokemonPos(centerPos: Vec3, controllerData: VRBodyPartData, index: Int): Vec3 {
var pos = centerPos
// Move to bottom of hand
var downVecF = Vector3f(0f, -0.0625f, 0f)
downVecF.rotate(Axis.ZP.rotation(controllerData.roll.toFloat()))
downVecF.rotate(Axis.XN.rotation(controllerData.pitch.toFloat()))
downVecF.rotate(Axis.YN.rotation(controllerData.yaw.toFloat()))
pos = pos.add(Vec3(downVecF.x.toDouble(), downVecF.y.toDouble(), downVecF.z.toDouble()))
// Move right or left based on index
val rightF = Vector3f(0f, 0f, 1f) // Default forward vector of Minecraft
rightF.rotate(Axis.YN.rotation(Math.PI.toFloat() / 2f))
rightF.rotate(Axis.ZP.rotation(controllerData.roll.toFloat()))
rightF.rotate(Axis.XN.rotation(controllerData.pitch.toFloat()))
rightF.rotate(Axis.YN.rotation(controllerData.yaw.toFloat()))
val right = Vec3(rightF.x.toDouble(), rightF.y.toDouble(), rightF.z.toDouble())
if (index > 2) {
pos = pos.add(right.scale(-0.0625))
} else {
pos = pos.add(right.scale(0.0625))
}
// Offset by -hand direction if on left, 0 if center, or +hand direction if right
return pos.add(controllerData.dir.scale(DIST_BETWEEN_HORIZONTAL * (index % 3 - 1)))
}
override fun getId(): ResourceLocation {
return INTERACT_ID
}
override fun isActive(
player: LocalPlayer,
hand: InteractionHand,
handPosition: Vec3
): Boolean {
return hand == InteractionHand.MAIN_HAND && selectedIndex > -1
}
override fun onPress(
player: LocalPlayer,
hand: InteractionHand
): Boolean {
// UUID lookup is done at press time since the Pokemon may have switched since initially hovered over, such
// as via commands.
val uuid = CobblemonClient.storage.party.get(selectedIndex)?.uuid ?: return false
CobblemonClient.storage.switchToPokemon(uuid)
return true
}
companion object {
const val DIST_BETWEEN_HORIZONTAL: Double = 0.125
const val DIST_BETWEEN_VERTICAL: Float = 0.05f
const val DIST_SELECT_SQR: Double = 0.1 * 0.1
val INTERACT_ID = ResourceLocation(Cobblemon.MODID, "party_on_arm_interact_module")
private val INSTANCE = PartyOnArm()
fun init() {
PlatformEvents.RENDER.subscribe(Consumer { renderEvent: RenderEvent? -> INSTANCE.onRender(renderEvent!!) })
PlatformEvents.CLIENT_TICK_POST.subscribe(Consumer { tickEvent: ClientTickEvent.Post? ->
INSTANCE.onTick(
tickEvent!!
)
})
VRAPI.instance().register(INSTANCE)
}
}
}
@@ -1,12 +0,0 @@
package com.cobblemon.mod.common.client.integration.vivecraft.proximity
import org.vivecraft.api.data.VRBodyPart
import org.vivecraft.api.data.VRPose
// TODO: Make an PR to Vivecraft that adds this feature directly to Vivecraft
interface ProximityListener {
fun isActive(registration: ProximityRegistration): Boolean = true
fun onEnter(bodyA: VRBodyPart, bodyB: VRBodyPart, pose: VRPose)
fun onExit(bodyA: VRBodyPart, bodyB: VRBodyPart, pose: VRPose)
fun onTick(bodyA: VRBodyPart, bodyB: VRBodyPart, pose: VRPose)
}
@@ -1,79 +0,0 @@
package com.cobblemon.mod.common.client.integration.vivecraft.proximity
import com.cobblemon.mod.common.platform.events.ClientTickEvent
import com.cobblemon.mod.common.platform.events.PlatformEvents
import org.vivecraft.api.client.VRClientAPI
import kotlin.math.acos
// TODO: Make an PR to Vivecraft that adds this feature directly to Vivecraft
class ProximityManager {
private val registrations = mutableListOf<ProximityRegistration>()
private val previousInProximity = mutableMapOf<ProximityRegistration, Boolean>()
private val api: VRClientAPI = VRClientAPI.instance()
fun register(registration: ProximityRegistration) {
registrations += registration
previousInProximity.putIfAbsent(registration, false)
}
fun unregister(registration: ProximityRegistration) {
registrations -= registration
previousInProximity.remove(registration)
}
fun clear() {
registrations.clear()
previousInProximity.clear()
}
fun isActive(registration: ProximityRegistration): Boolean {
return registration.listener.isActive(registration)
}
fun onTick(tickEvent: ClientTickEvent.Post) {
val vrPose = api.getPostTickWorldPose() ?: return
for (reg in registrations) {
if (!isActive(reg)) continue
val poseA = vrPose.getBodyPartData(reg.bodyA) ?: continue
val poseB = vrPose.getBodyPartData(reg.bodyB) ?: continue
val distance = poseA.pos.distanceTo(poseB.pos)
val dot = poseA.dir.dot(poseB.dir).coerceIn(-1.0, 1.0)
val angleDeg = Math.toDegrees(acos(dot)).toFloat()
val angleOk = angleDeg <= reg.angleThreshold
val nowInProximity = distance <= reg.distanceThreshold && angleOk
val wasInProximity = previousInProximity[reg] ?: false
if (!wasInProximity && nowInProximity) {
reg.listener.onEnter(reg.bodyA, reg.bodyB, vrPose)
} else if (wasInProximity && !nowInProximity) {
reg.listener.onExit(reg.bodyA, reg.bodyB, vrPose)
}
if (nowInProximity) {
reg.listener.onTick(reg.bodyA, reg.bodyB, vrPose)
}
previousInProximity[reg] = nowInProximity
}
}
companion object {
private val INSTANCE = ProximityManager()
fun init() {
PlatformEvents.CLIENT_TICK_POST.subscribe { tickEvent: ClientTickEvent.Post? ->
INSTANCE.onTick(
tickEvent!!
)
}
}
fun register(registration: ProximityRegistration) = INSTANCE.register(registration)
fun unregister(registration: ProximityRegistration) = INSTANCE.unregister(registration)
fun clearAll() = INSTANCE.clear()
}
}
@@ -1,12 +0,0 @@
package com.cobblemon.mod.common.client.integration.vivecraft.proximity
import org.vivecraft.api.data.VRBodyPart
// TODO: Make an PR to Vivecraft that adds this feature directly to Vivecraft
data class ProximityRegistration(
val bodyA: VRBodyPart,
val bodyB: VRBodyPart,
val distanceThreshold: Float,
val angleThreshold: Float = 180f,
val listener: ProximityListener
)
@@ -143,18 +143,18 @@ object PartySendBinding : CobblemonBlockingKeyBinding(
}
private fun canAttemptDismount(player: LocalPlayer, selectedPartyPokemon: Pokemon?): Boolean {
if (player.vehicle !is PokemonEntity) return false
val vehicle = player.vehicle as PokemonEntity
if (player != vehicle.controllingPassenger) {
return true
}
val vehicle = player.vehicle as? PokemonEntity ?: return false
if (player != vehicle.controllingPassenger) return true
val isAirRide = vehicle.ridingController?.context?.style == RidingStyle.AIR
val hasLandRide = vehicle.rideProp.behaviours?.get(RidingStyle.LAND) != null
return if (isAirRide && hasLandRide) {
false
} else {
vehicle.pokemon.uuid == selectedPartyPokemon?.uuid
if (!Cobblemon.config.enableInFlightDismounting && isAirRide && hasLandRide) {
return false
}
return vehicle.pokemon.uuid == selectedPartyPokemon?.uuid
}
private fun isRidingPokemon(player: LocalPlayer, ignoreControlling: Boolean = false): Boolean {
@@ -23,7 +23,7 @@ import net.minecraft.world.phys.Vec3
object CalculateSeatPositionsHandler : ClientNetworkPacketHandler<CalculateSeatPositionsPacket> {
private const val FORMAT = "%.2f"
private const val SEAT_PREFIX = "seat"
private const val SEAT_PREFIX = "seat_"
private const val SIMULATION_PERIOD_TICKS = 15 * 20F
class SeatRanges(
@@ -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.client.net.data
import com.cobblemon.mod.common.api.interaction.PokemonInteractions
import com.cobblemon.mod.common.api.net.ClientNetworkPacketHandler
import com.cobblemon.mod.common.net.messages.client.data.PokemonInteractionsSyncPacket
import net.minecraft.client.Minecraft
object PokemonInteractionsSyncHandler : ClientNetworkPacketHandler<PokemonInteractionsSyncPacket> {
override fun handle(
packet: PokemonInteractionsSyncPacket,
client: Minecraft,
) {
PokemonInteractions.speciesInteractions.clear()
PokemonInteractions.speciesInteractions.addAll(packet.speciesInteractions)
PokemonInteractions.generalInteractions.clear()
PokemonInteractions.generalInteractions.addAll(packet.generalInteractions)
}
}
@@ -8,6 +8,8 @@
package com.cobblemon.mod.common.client.persisted
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.Cobblemon.LOGGER
import com.cobblemon.mod.common.config.CobblemonConfig
import java.io.BufferedReader
import java.io.BufferedWriter
@@ -31,7 +33,14 @@ object ClientPersistedData {
fun readSnapshotsData() : SnapshotAcknowledgementData? {
if (snapshotsPath.exists()) {
BufferedReader(InputStreamReader(FileInputStream(snapshotsPath.toFile()))).use { reader ->
return gson.fromJson(reader, SnapshotAcknowledgementData::class.java)
try {
return gson.fromJson(reader, SnapshotAcknowledgementData::class.java)
} catch (exception: Exception) {
LOGGER.error("Failed to load snapshots.json... Resetting File")
val data = SnapshotAcknowledgementData(Cobblemon.VERSION, false)
writeSnapshotsData(data)
return data
}
}
}
@@ -46,16 +55,23 @@ object ClientPersistedData {
fun readRidingPerspectivesData() : RidingPerspectiveData? {
if (ridingPerspectivesPath.exists()) {
BufferedReader(InputStreamReader(FileInputStream(ridingPerspectivesPath.toFile()))).use { reader ->
return gson.fromJson(reader, RidingPerspectiveData::class.java)
try {
return gson.fromJson(reader, RidingPerspectiveData::class.java)
} catch (exception: Exception) {
LOGGER.error("Failed to load riding_perspectives.json... Resetting File")
val data = RidingPerspectiveData()
saveRidingPerspectivesData(data)
return data
}
}
}
return null
}
fun saveRidingPerspectivesData() {
fun saveRidingPerspectivesData(data: RidingPerspectiveData) {
ridingPerspectivesPath.toFile().parentFile.mkdirs()
BufferedWriter(FileWriter(ridingPerspectivesPath.toFile())).use { writer -> gson.toJson(ridingPerspectives, writer) }
BufferedWriter(FileWriter(ridingPerspectivesPath.toFile())).use { writer -> gson.toJson(data, writer) }
}
data class SnapshotAcknowledgementData(val version: String, val dontShowAgain: Boolean)
@@ -64,7 +80,7 @@ object ClientPersistedData {
) {
fun updatePerspective(key: ResourceLocation, cameraType: CameraType) {
perspectives[key] = cameraType
saveRidingPerspectivesData()
saveRidingPerspectivesData(this)
}
}
}
@@ -160,6 +160,7 @@ class DisplayCaseRenderer(ctx: BlockEntityRendererProvider.Context) : BlockEntit
stack.item is BedItem -> PositioningType.BED
stack.item is BannerItem -> PositioningType.BANNER
stack.`is`(CobblemonItemTags.POKE_BALLS) -> PositioningType.POKE_BALL
stack.`is`(CobblemonItemTags.PLAQUES) -> PositioningType.ITEM_MODEL
stack.item is PokedexItem -> PositioningType.POKEDEX
stack.item == CobblemonItems.RELIC_COIN_POUCH -> PositioningType.COIN_POUCH
stack.item == CobblemonItems.PASTURE -> PositioningType.PASTURE
@@ -39,7 +39,11 @@ class PoseAdapter(
override fun deserialize(json: JsonElement, type: Type, ctx: JsonDeserializationContext): Pose {
val model = modelFinder()
val obj = json as JsonObject
val pose = JsonPose(model, obj)
val pose = try {
JsonPose(model, obj)
} catch (e: Exception) {
throw IllegalArgumentException("Failed to deserialize pose from JSON: $json", e)
}
val conditionsList = mutableListOf<(PosableState) -> Boolean>()
@@ -100,4 +104,4 @@ class PoseAdapter(
)
}
}
}
}
@@ -206,7 +206,7 @@ fun generateAdditionalRideBoostsTooltip(stack: ItemStack): MutableList<Component
val resultLines = mutableListOf<Component>()
resultLines.add(rideBoostSeasoningHeader)
for ((stat, value) in boosts) {
for ((stat, value) in boosts.toSortedMap()) {
val statName = stat.displayName.also { it.style = it.style.withColor(stat.flavour.colour) }
val valueText = if (value < 0) {
Component.literal("$value").red()
@@ -329,6 +329,9 @@ class CobblemonConfig {
@CobblemonConfigField(Category.Riding, lang = "remember_riding_camera", CLIENT)
var rememberRidingCamera = false
@CobblemonConfigField(Category.Riding, lang = "enable_in_flight_dismounting", CLIENT)
var enableInFlightDismounting = false
@CobblemonConfigField(Category.Debug, lang = "enable_debug_keys", CLIENT)
var enableDebugKeys = false
@@ -269,9 +269,6 @@ class OmniPathNavigation(val world: Level, val entity: Mob) : GroundPathNavigati
// If we can fly and we're airborne, return the current Y position
return vec.y
}
if (world.getBlockState(blockPos).block == CobblemonBlocks.SACCHARINE_LEAVES && pather.canPathThroughSaccLeaves()) {
return vec.y + 0.5
}
return if ((canFloat()) && blockGetter.getFluidState(blockPos).`is`(FluidTags.WATER)) vec.y + 0.5 else super.getGroundY(vec)
}
@@ -78,6 +78,7 @@ import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerEntity
import net.minecraft.server.level.ServerLevel
import net.minecraft.server.level.ServerPlayer
import net.minecraft.tags.DamageTypeTags.BYPASSES_INVULNERABILITY
import net.minecraft.world.InteractionHand
import net.minecraft.world.InteractionResult
import net.minecraft.world.damagesource.DamageSource
@@ -604,7 +605,7 @@ class NPCEntity(world: Level) : AgeableMob(CobblemonEntities.NPC, world), Posabl
}
override fun isPushable() = isMovable ?: npc.isMovable
override fun isInvulnerableTo(source: DamageSource) = isInvulnerable ?: npc.isInvulnerable
override fun isInvulnerableTo(source: DamageSource) = (isInvulnerable ?: npc.isInvulnerable) && !source.`is`(BYPASSES_INVULNERABILITY)
override fun canBeLeashed() = isLeashable ?: npc.isLeashable
override fun canBeHitByProjectile() = allowProjectileHits ?: npc.allowProjectileHits
@@ -1208,14 +1208,13 @@ open class PokemonEntity(
}
}
if (hand == InteractionHand.MAIN_HAND && player is ServerPlayer) {
if (player.isShiftKeyDown) {
if (hand == InteractionHand.MAIN_HAND) {
if (player.isShiftKeyDown && player is ServerPlayer) {
showInteractionWheel(player, itemStack)
return InteractionResult.sidedSuccess(level().isClientSide)
}
else if (pokemon.getOwnerPlayer() == player) {
// TODO #105
if (this.attemptItemInteraction(player, player.getItemInHand(hand))) return InteractionResult.SUCCESS
else if (this.ownerUUID == player.uuid && this.attemptItemInteraction(player, player.getItemInHand(hand))) {
return InteractionResult.sidedSuccess(level().isClientSide)
}
}
@@ -1391,18 +1390,24 @@ open class PokemonEntity(
return bagItemLike.handleInteraction(player, battlePokemon, stack)
}
if (player !is ServerPlayer || this.isBusy) {
if (this.isBusy) {
return false
}
val interaction = PokemonInteractions.findInteraction(this)
if (interaction != null && !pokemon.isOnInteractionCooldown(interaction.grouping)) {
interaction.effects.forEach { it.applyEffect(this, player) }
pokemon.interactionCooldowns.put(interaction.grouping, runtime.resolveInt(interaction.cooldown))
if (player is ServerPlayer) {
interaction.effects.forEach { it.applyEffect(this, player) }
pokemon.interactionCooldowns.put(interaction.grouping, runtime.resolveInt(interaction.cooldown))
}
return true
}
if (player !is ServerPlayer) {
return false
}
// Evolution item logic
if (pokemon.getOwnerPlayer() == player) {
val context = ItemInteractionEvolution.ItemInteractionContext(stack, player.level())
@@ -30,21 +30,13 @@ import com.cobblemon.mod.common.api.events.battles.BattleStartedEvent
import com.cobblemon.mod.common.api.events.battles.BattleVictoryEvent
import com.cobblemon.mod.common.api.events.fishing.PokerodCastEvent
import com.cobblemon.mod.common.api.events.fishing.PokerodReelEvent
import com.cobblemon.mod.common.api.events.pokemon.CollectEggEvent
import com.cobblemon.mod.common.api.events.pokemon.FossilRevivedEvent
import com.cobblemon.mod.common.api.events.pokemon.HatchEggEvent
import com.cobblemon.mod.common.api.events.pokemon.LevelUpEvent
import com.cobblemon.mod.common.api.events.pokemon.PokemonCapturedEvent
import com.cobblemon.mod.common.api.events.pokemon.PokemonGainedEvent
import com.cobblemon.mod.common.api.events.pokemon.RidePokemonEvent
import com.cobblemon.mod.common.api.events.pokemon.TradeEvent
import com.cobblemon.mod.common.api.events.pokemon.*
import com.cobblemon.mod.common.api.events.pokemon.evolution.EvolutionCompleteEvent
import com.cobblemon.mod.common.api.events.storage.ReleasePokemonEvent
import com.cobblemon.mod.common.api.stats.CobblemonStats.getStat
import com.cobblemon.mod.common.util.getPlayer
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
import java.util.UUID
import java.util.*
object StatHandler : EventHandler {
override fun registerListeners() {
@@ -100,13 +92,13 @@ object StatHandler : EventHandler {
fun onBattleStart(event : BattleStartedEvent) {
event.battle.players.forEach { player ->
player.awardStat(this.getStat(getStat(Cobblemon.statistics.BATTLES_TOTAL)))
player.awardStat(getStat(Cobblemon.statistics.BATTLES_TOTAL))
}
}
fun onDexEntryGain(event : PokemonGainedEvent) {
val player = event.pokemon.getOwnerPlayer()
player?.awardStat(getStat(getStat(Cobblemon.statistics.DEX_ENTRIES)))
player?.awardStat(getStat(Cobblemon.statistics.DEX_ENTRIES))
}
fun onCollectEgg(event : CollectEggEvent) {
@@ -139,12 +131,4 @@ object StatHandler : EventHandler {
fun onReelIn(event : PokerodReelEvent) {
event.player.awardStat(getStat(Cobblemon.statistics.REEL_INS))
}
fun getStat(resourceLocation: ResourceLocation) : ResourceLocation {
val stat = BuiltInRegistries.CUSTOM_STAT.get(resourceLocation)
if (stat == null) {
Cobblemon.LOGGER.error("Could not find stat with id {}", resourceLocation)
}
return stat ?: throw NullPointerException("Could not find stat with id $resourceLocation")
}
}
@@ -14,7 +14,7 @@ import juuxel.adorn.block.variant.CompatBlockVariantSet
class AdornCompatibility : CompatBlockVariantSet() {
override fun getWoodVariants(): List<BlockVariant?>? {
return this.createVariants({ BlockVariant.Wood(it) }, "apricorn")
return this.createVariants({ BlockVariant.Wood(it) }, "apricorn", "saccharine")
}
override fun getModId(): String? {
@@ -1,16 +0,0 @@
package com.cobblemon.mod.common.integration.vivecraft
import com.cobblemon.mod.common.client.integration.vivecraft.listener.swing.PokeBallSwingListener
import com.cobblemon.mod.common.client.integration.vivecraft.listener.swing.PokeRodSwingListener
import org.vivecraft.api.VRAPI
class CobblemonVR {
fun init() {
VRAPI.instance()
.register(PokeBallSwingListener())
VRAPI.instance()
.register(PokeRodSwingListener())
}
}
@@ -23,7 +23,7 @@ import net.minecraft.world.level.Level
import kotlin.math.cos
class PokeBallItem(
val pokeBall: PokeBall
val pokeBall: PokeBall
) : CobblemonItem(Properties().apply {
when (pokeBall.name) {
// Master balls are a netherite product and should be fire immune
@@ -43,7 +43,7 @@ class PokeBallItem(
return InteractionResultHolder.sidedSuccess(itemStack, world.isClientSide)
}
fun throwPokeBall(world: Level, player: ServerPlayer) {
private fun throwPokeBall(world: Level, player: ServerPlayer) {
val pokeBallEntity = EmptyPokeBallEntity(pokeBall, player.level(), player).apply {
val overhandFactor: Float = if (player.xRot < 0) {
5f * cos(player.xRot.toRadians())
@@ -57,5 +57,4 @@ class PokeBallItem(
}
world.addFreshEntity(pokeBallEntity)
}
}
@@ -13,6 +13,7 @@ import com.cobblemon.mod.common.advancement.criterion.PokemonInteractContext
import com.cobblemon.mod.common.api.text.red
import com.cobblemon.mod.common.battles.BagItemActionResponse
import com.cobblemon.mod.common.battles.BagItems
import com.cobblemon.mod.common.battles.BattleRules
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.util.battleLang
import net.minecraft.core.registries.BuiltInRegistries
@@ -41,7 +42,7 @@ interface BagItemLike {
fun handleInteraction(player: ServerPlayer, battlePokemon: BattlePokemon, stack: ItemStack): Boolean {
val battle = battlePokemon.actor.battle
val bagItem = getBagItem(stack) ?: return false
if (!battlePokemon.actor.canFitForcedAction()) {
if (!battlePokemon.actor.canFitForcedAction() || battle.format.ruleSet.contains(BattleRules.BAG_CLAUSE)) {
player.sendSystemMessage(battleLang("bagitem.cannot").red())
return false
}
@@ -11,12 +11,13 @@ package com.cobblemon.mod.common.item.battle
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.item.CobblemonItem
import net.minecraft.world.item.ItemNameBlockItem
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Items
import net.minecraft.world.level.block.Block
class DireHitItem : CobblemonItem(Properties()), SimpleBagItemLike {
class DireHitItem(block: Block) : ItemNameBlockItem(block, Properties()), SimpleBagItemLike {
override val bagItem = object : BagItem {
override val itemName = "item.cobblemon.dire_hit"
override val returnItem = Items.AIR
@@ -11,11 +11,12 @@ package com.cobblemon.mod.common.item.battle
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.item.CobblemonItem
import net.minecraft.world.item.ItemNameBlockItem
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Items
import net.minecraft.world.level.block.Block
class GuardSpecItem : CobblemonItem(Properties()), SimpleBagItemLike {
class GuardSpecItem(block: Block) : ItemNameBlockItem(block, Properties()), SimpleBagItemLike {
override val bagItem = object : BagItem {
override val itemName = "item.cobblemon.guard_spec"
override val returnItem = Items.AIR
@@ -12,11 +12,12 @@ import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
import com.cobblemon.mod.common.api.pokemon.stats.Stat
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.item.CobblemonItem
import net.minecraft.world.item.ItemNameBlockItem
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Items
import net.minecraft.world.level.block.Block
class XStatItem(val stat: Stat, stages: Int = 2) : CobblemonItem(Properties()), SimpleBagItemLike {
class XStatItem(val stat: Stat, stages: Int = 2, block: Block) : ItemNameBlockItem(block, Properties()), SimpleBagItemLike {
override val bagItem = object : BagItem {
override val itemName = "item.cobblemon.x_${stat.identifier.path}"
override val returnItem = Items.AIR
@@ -66,6 +66,7 @@ object CobblemonItemGroups {
@JvmStatic val EVOLUTION_ITEMS get() = BuiltInRegistries.CREATIVE_MODE_TAB.get(EVOLUTION_ITEMS_KEY)
@JvmStatic val BUILDING_BLOCKS_INJECTIONS = this.inject(ResourceKey.create(BuiltInRegistries.CREATIVE_MODE_TAB.key(), ResourceLocation.parse("building_blocks")), this::blocksInjections)
@JvmStatic val COLORED_BLOCKS_INJECTIONS = this.inject(ResourceKey.create(BuiltInRegistries.CREATIVE_MODE_TAB.key(), ResourceLocation.parse("colored_blocks")), this::coloredBlocksInjections)
@JvmStatic val FOOD_INJECTIONS = this.inject(ResourceKey.create(BuiltInRegistries.CREATIVE_MODE_TAB.key(), ResourceLocation.parse("food_and_drinks")), this::foodInjections)
@JvmStatic val TOOLS_AND_UTILITIES_INJECTIONS = this.inject(ResourceKey.create(BuiltInRegistries.CREATIVE_MODE_TAB.key(), ResourceLocation.parse("tools_and_utilities")), this::toolsAndUtilitiesInjections)
@JvmStatic val INGREDIENTS_INJECTIONS = this.inject(ResourceKey.create(BuiltInRegistries.CREATIVE_MODE_TAB.key(), ResourceLocation.parse("ingredients")), this::ingredientsInjections)
@@ -381,6 +382,23 @@ object CobblemonItemGroups {
entries.accept(CobblemonItems.THUNDER_STONE_BLOCK)
entries.accept(CobblemonItems.WATER_STONE_BLOCK)
entries.accept(CobblemonItems.WHITE_PLAQUE)
entries.accept(CobblemonItems.LIGHT_GRAY_PLAQUE)
entries.accept(CobblemonItems.GRAY_PLAQUE)
entries.accept(CobblemonItems.BLACK_PLAQUE)
entries.accept(CobblemonItems.BROWN_PLAQUE)
entries.accept(CobblemonItems.RED_PLAQUE)
entries.accept(CobblemonItems.ORANGE_PLAQUE)
entries.accept(CobblemonItems.YELLOW_PLAQUE)
entries.accept(CobblemonItems.LIME_PLAQUE)
entries.accept(CobblemonItems.GREEN_PLAQUE)
entries.accept(CobblemonItems.CYAN_PLAQUE)
entries.accept(CobblemonItems.LIGHT_BLUE_PLAQUE)
entries.accept(CobblemonItems.BLUE_PLAQUE)
entries.accept(CobblemonItems.PURPLE_PLAQUE)
entries.accept(CobblemonItems.MAGENTA_PLAQUE)
entries.accept(CobblemonItems.PINK_PLAQUE)
CobblemonItems.campfire_pots.forEach(entries::accept)
}
@@ -765,6 +783,25 @@ object CobblemonItemGroups {
}
}
private fun coloredBlocksInjections(injector: Injector) {
injector.putLast(CobblemonItems.WHITE_PLAQUE)
injector.putAfter(CobblemonItems.LIGHT_GRAY_PLAQUE, CobblemonItems.WHITE_PLAQUE)
injector.putAfter(CobblemonItems.GRAY_PLAQUE, CobblemonItems.LIGHT_GRAY_PLAQUE)
injector.putAfter(CobblemonItems.BLACK_PLAQUE, CobblemonItems.GRAY_PLAQUE)
injector.putAfter(CobblemonItems.BROWN_PLAQUE, CobblemonItems.BLACK_PLAQUE)
injector.putAfter(CobblemonItems.RED_PLAQUE, CobblemonItems.BROWN_PLAQUE)
injector.putAfter(CobblemonItems.ORANGE_PLAQUE, CobblemonItems.RED_PLAQUE)
injector.putAfter(CobblemonItems.YELLOW_PLAQUE, CobblemonItems.ORANGE_PLAQUE)
injector.putAfter(CobblemonItems.LIME_PLAQUE, CobblemonItems.YELLOW_PLAQUE)
injector.putAfter(CobblemonItems.GREEN_PLAQUE, CobblemonItems.LIME_PLAQUE)
injector.putAfter(CobblemonItems.CYAN_PLAQUE, CobblemonItems.GREEN_PLAQUE)
injector.putAfter(CobblemonItems.LIGHT_BLUE_PLAQUE, CobblemonItems.CYAN_PLAQUE)
injector.putAfter(CobblemonItems.BLUE_PLAQUE, CobblemonItems.LIGHT_BLUE_PLAQUE)
injector.putAfter(CobblemonItems.PURPLE_PLAQUE, CobblemonItems.BLUE_PLAQUE)
injector.putAfter(CobblemonItems.MAGENTA_PLAQUE, CobblemonItems.PURPLE_PLAQUE)
injector.putAfter(CobblemonItems.PINK_PLAQUE, CobblemonItems.MAGENTA_PLAQUE)
}
private fun foodInjections(injector: Injector) {
injector.putAfter(CobblemonItems.TART_APPLE, Items.ENCHANTED_GOLDEN_APPLE)
injector.putAfter(CobblemonItems.SWEET_APPLE, CobblemonItems.TART_APPLE)
@@ -13,7 +13,6 @@ import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
import com.cobblemon.mod.common.api.item.PokemonSelectingItem
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.item.CobblemonItem
import com.cobblemon.mod.common.item.battle.BagItem
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.giveOrDropItemStack
@@ -25,8 +24,10 @@ import net.minecraft.server.level.ServerPlayer
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.InteractionHand
import net.minecraft.world.InteractionResultHolder
import net.minecraft.world.item.ItemNameBlockItem
import net.minecraft.world.item.Rarity
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.Block
/**
* Items for recovering PP in all moves at once.
@@ -34,9 +35,7 @@ import net.minecraft.world.level.Level
* @author Hiroku
* @since June 30th, 2023
*/
class ElixirItem(
val max: Boolean
) : CobblemonItem(Properties().apply {
class ElixirItem(val max: Boolean, block: Block) : ItemNameBlockItem(block, Properties().apply {
if (max) rarity(Rarity.UNCOMMON)
}), PokemonSelectingItem {
override val bagItem = object : BagItem {
@@ -14,7 +14,6 @@ import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
import com.cobblemon.mod.common.api.item.PokemonAndMoveSelectingItem
import com.cobblemon.mod.common.api.moves.Move
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.item.CobblemonItem
import com.cobblemon.mod.common.item.battle.BagItem
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.giveOrDropItemStack
@@ -26,8 +25,10 @@ import net.minecraft.server.level.ServerPlayer
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.InteractionHand
import net.minecraft.world.InteractionResultHolder
import net.minecraft.world.item.ItemNameBlockItem
import net.minecraft.world.item.Rarity
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.Block
/**
* Item for recovering PP for a specific move in a Pokémon's move list. Opens a move selection GUI.
@@ -35,9 +36,7 @@ import net.minecraft.world.level.Level
* @author Hiroku
* @since June 30th, 2023
*/
class EtherItem(
val max: Boolean
) : CobblemonItem(Properties().apply {
class EtherItem(val max: Boolean, block: Block) : ItemNameBlockItem(block, Properties().apply {
if (max) rarity(Rarity.UNCOMMON)
}), PokemonAndMoveSelectingItem {
override val bagItem = object : BagItem {
@@ -34,8 +34,9 @@ class FreshStartMochiItem : CobblemonItem(Properties()), PokemonSelectingItem {
}
pokemon.feedPokemon(1)
pokemon.evs.forEach {
pokemon.evs[it.key] = 0
pokemon.setEV(it.key, 0)
}
//pokemon.entity?.playSound(CobblemonSounds.MOCHI_USE, 1F, 1F) todo use mochi sounds for fullness levels and replace above
@@ -25,9 +25,7 @@ class MochiItem(stat: Stats): EVIncreaseItem(stat, 4) {
}
pokemon.feedPokemon(1)
stack.consume(1, player)
return super.applyToPokemon(player, stack, pokemon)
}
}
@@ -75,8 +75,7 @@ class PokerodItem(val pokeRodId: ResourceLocation, settings: Properties) : Fishi
if (baitCount > 1) {
baitStack.count = baitCount - 1
stack.set(
CobblemonItemComponents.BAIT, RodBaitComponent(baitStack)
stack.set(CobblemonItemComponents.BAIT, RodBaitComponent(baitStack)
)
}
}
@@ -150,163 +149,90 @@ class PokerodItem(val pokeRodId: ResourceLocation, settings: Properties) : Fishi
}
override fun use(world: Level, user: Player, hand: InteractionHand): InteractionResultHolder<ItemStack> {
val itemStack = user.getItemInHand(hand)
val offHandItem = user.getItemInHand(InteractionHand.OFF_HAND)
attemptApplyOffhandBait(world, user, itemStack, offHandItem)?.let { return it }
// Check if offhand item is valid bait and the rod is not in use, if so then apply bait from offhand
if (!world.isClientSide && user.fishing == null && SpawnBaitEffects.isFishingBait(offHandItem)) {
CobblemonEvents.BAIT_SET_PRE.postThen(BaitSetEvent(itemStack, offHandItem), { event ->
return InteractionResultHolder.fail(itemStack)
}, {
playAttachSound(user)
if (user.fishing != null) {
attemptReel(world, user, hand, itemStack)?.let { return it }
} else {
attemptCast(world, user, hand, itemStack)?.let { return it }
// if there is bait on the rod already then drop it on the ground before applying offhand bait
val baitStack = getBaitStackOnRod(itemStack)
if (baitStack != ItemStack.EMPTY && offHandItem.item != baitStack.item) {
user.drop(baitStack, true) // Drop the full stack
// apply single bait item from offhand
val singleBait = offHandItem.copy()
singleBait.count = 1
setBait(itemStack, singleBait)
offHandItem.shrink(1)
}
})
}
return InteractionResultHolder.sidedSuccess(itemStack, world.isClientSide)
}
fun applyBait(
rodStack: ItemStack,
baitSource: ItemStack,
player: Player?
): Boolean {
if (baitSource.isEmpty || !SpawnBaitEffects.isFishingBait(baitSource)) return false
val existingBait = getBaitStackOnRod(rodStack)
if (!existingBait.isEmpty && existingBait.item == baitSource.item) return false
var applied = false
CobblemonEvents.BAIT_SET_PRE.postThen(
BaitSetEvent(rodStack, baitSource),
{ return false },
{
if (!existingBait.isEmpty && player != null) {
player.drop(existingBait, true)
}
val singleBait = baitSource.copy()
singleBait.count = 1
setBait(rodStack, singleBait)
baitSource.shrink(1)
applied = true
var i: Int
if (user.fishing != null) { // if the bobber is out yet
if (!world.isClientSide) {
CobblemonEvents.POKEROD_REEL.postThen(
PokerodReelEvent(user, itemStack),
{ event -> return InteractionResultHolder.fail(itemStack) },
{ event ->
i = user.fishing!!.retrieve(itemStack)
itemStack.hurtAndBreak(i, user, hand.toEquipmentSlot())
world.playSoundServer(
Vec3(user.x,
user.y,
user.z),
CobblemonSounds.FISHING_ROD_REEL_IN,
SoundSource.PLAYERS,
1.0f,
1.0f / (world.getRandom().nextFloat() * 0.4f + 0.8f)
)
}
)
}
)
return applied
}
fun attemptApplyOffhandBait(
world: Level,
user: Player,
rodStack: ItemStack,
offHandItem: ItemStack
): InteractionResultHolder<ItemStack>? {
if (world.isClientSide) return null
if (user.fishing != null) return null
world.playSound(null as Player?, user.x, user.y, user.z, CobblemonSounds.FISHING_ROD_REEL_IN, SoundSource.PLAYERS, 1.0f, 1.0f)
user.gameEvent(GameEvent.ITEM_INTERACT_FINISH)
} else { // if the bobber is not out yet
val applied = applyBait(
rodStack = rodStack,
baitSource = offHandItem,
player = user
)
if (!world.isClientSide) {
val lureLevel = world.enchantmentRegistry.getHolder(Enchantments.LURE).map { EnchantmentHelper.getItemEnchantmentLevel(it, itemStack) }.orElse(0)
val luckLevel = world.enchantmentRegistry.getHolder(Enchantments.LUCK_OF_THE_SEA).map { EnchantmentHelper.getItemEnchantmentLevel(it, itemStack) }.orElse(0)
if (applied) {
playAttachSound(user)
return InteractionResultHolder.sidedSuccess(rodStack, false)
val bobberEntity = PokeRodFishingBobberEntity(
user,
pokeRodId,
getBaitStackOnRod(itemStack),
world,
luckLevel,
lureLevel,
itemStack
)
CobblemonEvents.POKEROD_CAST_PRE.postThen(
PokerodCastEvent.Pre(itemStack, bobberEntity, getBaitStackOnRod(itemStack)),
{ event -> return InteractionResultHolder.fail(itemStack) },
{ event ->
world.addFreshEntity(bobberEntity)
var baitId = getBaitStackOnRod(itemStack).takeUnless { it.isEmpty }?.itemHolder?.unwrapKey()?.orElse(null)?.location() ?: cobblemonResource("empty_bait")
CobblemonCriteria.CAST_POKE_ROD.trigger(user as ServerPlayer, CastPokeRodContext(baitId))
CobblemonEvents.POKEROD_CAST_POST.post(
PokerodCastEvent.Post(itemStack, bobberEntity, getBaitStackOnRod(itemStack))
)
}
)
}
user.awardStat(Stats.ITEM_USED.get(this))
user.gameEvent(GameEvent.ITEM_INTERACT_START)
}
return null
}
fun attemptReel(
world: Level,
user: Player,
hand: InteractionHand,
itemStack: ItemStack
): InteractionResultHolder<ItemStack>? {
if (user.fishing == null) return null
if (!world.isClientSide) {
CobblemonEvents.POKEROD_REEL.postThen(
PokerodReelEvent(user, itemStack),
{ event -> return InteractionResultHolder.fail(itemStack) },
{ event ->
val i = user.fishing!!.retrieve(itemStack)
itemStack.hurtAndBreak(i, user, hand.toEquipmentSlot())
world.playSoundServer(
Vec3(user.x, user.y, user.z),
CobblemonSounds.FISHING_ROD_REEL_IN,
SoundSource.PLAYERS,
1.0f,
1.0f / (world.getRandom().nextFloat() * 0.4f + 0.8f)
)
}
)
}
world.playSound(
null as Player?,
user.x,
user.y,
user.z,
CobblemonSounds.FISHING_ROD_REEL_IN,
SoundSource.PLAYERS,
1.0f,
1.0f
)
user.gameEvent(GameEvent.ITEM_INTERACT_FINISH)
return InteractionResultHolder.sidedSuccess(itemStack, world.isClientSide)
}
fun attemptCast(
world: Level,
user: Player,
hand: InteractionHand,
itemStack: ItemStack
): InteractionResultHolder<ItemStack>? {
if (user.fishing != null) return null
if (!world.isClientSide) {
val lureLevel = world.enchantmentRegistry.getHolder(Enchantments.LURE)
.map { EnchantmentHelper.getItemEnchantmentLevel(it, itemStack) }
.orElse(0)
val luckLevel = world.enchantmentRegistry.getHolder(Enchantments.LUCK_OF_THE_SEA)
.map { EnchantmentHelper.getItemEnchantmentLevel(it, itemStack) }
.orElse(0)
val bobberEntity = PokeRodFishingBobberEntity(
user,
pokeRodId,
getBaitStackOnRod(itemStack),
world,
luckLevel,
lureLevel,
itemStack
)
CobblemonEvents.POKEROD_CAST_PRE.postThen(
PokerodCastEvent.Pre(itemStack, bobberEntity, getBaitStackOnRod(itemStack)),
{ event -> return InteractionResultHolder.fail(itemStack) },
{ event ->
world.addFreshEntity(bobberEntity)
val baitId = getBaitStackOnRod(itemStack).takeUnless { it.isEmpty }
?.itemHolder?.unwrapKey()?.orElse(null)?.location()
?: cobblemonResource("empty_bait")
CobblemonCriteria.CAST_POKE_ROD.trigger(user as ServerPlayer, CastPokeRodContext(baitId))
CobblemonEvents.POKEROD_CAST_POST.post(
PokerodCastEvent.Post(itemStack, bobberEntity, getBaitStackOnRod(itemStack))
)
}
)
}
user.awardStat(Stats.ITEM_USED.get(this))
user.gameEvent(GameEvent.ITEM_INTERACT_START)
return InteractionResultHolder.sidedSuccess(itemStack, world.isClientSide)
}
@@ -17,7 +17,6 @@ import com.cobblemon.mod.common.api.item.HealingSource
import com.cobblemon.mod.common.api.item.PokemonSelectingItem
import com.cobblemon.mod.common.api.molang.ExpressionLike
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.item.CobblemonItem
import com.cobblemon.mod.common.item.battle.BagItem
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.asExpressionLike
@@ -31,12 +30,12 @@ import net.minecraft.world.item.Items
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.InteractionHand
import net.minecraft.world.InteractionResultHolder
import net.minecraft.world.item.ItemNameBlockItem
import net.minecraft.world.item.Rarity
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.Block
class PotionItem(
val type: PotionType
) : CobblemonItem(Properties().apply {
class PotionItem(val type: PotionType, block: Block) : ItemNameBlockItem(block, Properties().apply {
when (type.name) {
PotionType.MAX_POTION.name -> rarity(Rarity.UNCOMMON)
PotionType.FULL_RESTORE.name -> rarity(Rarity.UNCOMMON)
@@ -35,9 +35,16 @@ class SinisterTeaItem : Item(
val effectsComponent = stack.get(CobblemonItemComponents.MOB_EFFECTS)
if (effectsComponent != null) {
for (effect in effectsComponent.mobEffects) {
val effectCopy = MobEffectInstance(effect)
user.addEffect(effectCopy)
for (raw in effectsComponent.mobEffects) {
val inst = MobEffectInstance(raw)
val holder = inst.effect
val effect = holder.value()
if (effect.isInstantenous) {
effect.applyInstantenousEffect(user as? Player, null, user, inst.amplifier, 1.0)
} else {
user.addEffect(inst)
}
}
}
}
@@ -14,7 +14,6 @@ import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
import com.cobblemon.mod.common.api.item.PokemonSelectingItem
import com.cobblemon.mod.common.api.pokemon.status.Status
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.item.CobblemonItem
import com.cobblemon.mod.common.item.battle.BagItem
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.giveOrDropItemStack
@@ -24,7 +23,9 @@ import net.minecraft.world.item.Items
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.InteractionHand
import net.minecraft.world.InteractionResultHolder
import net.minecraft.world.item.ItemNameBlockItem
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.Block
/**
* An item that cures statuses. Based on the [status] parameter it can be either specific [Status]es or all statuses (none specified).
@@ -32,7 +33,7 @@ import net.minecraft.world.level.Level
* @author Hiroku
* @since June 30th, 2023
*/
class StatusCureItem(val itemName: String, vararg val status: Status) : CobblemonItem(Properties()), PokemonSelectingItem {
class StatusCureItem(val itemName: String, vararg val status: Status, block: Block) : ItemNameBlockItem(block, Properties()), PokemonSelectingItem {
override val bagItem = object : BagItem {
override val itemName = this@StatusCureItem.itemName
override val returnItem = Items.AIR
@@ -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.net.messages.client.data
import com.cobblemon.mod.common.api.interaction.PokemonInteractionSet
import com.cobblemon.mod.common.api.net.NetworkPacket
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.network.RegistryFriendlyByteBuf
class PokemonInteractionsSyncPacket(
val speciesInteractions: List<PokemonInteractionSet>,
val generalInteractions: List<PokemonInteractionSet>,
) : NetworkPacket<PokemonInteractionsSyncPacket> {
override val id = ID
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeCollection(speciesInteractions) { it, speciesInteraction ->
speciesInteraction.encode(it)
}
buffer.writeCollection(generalInteractions) { it, generalInteraction ->
generalInteraction.encode(it)
}
}
companion object {
val ID = cobblemonResource("pokemon_interactions_sync")
fun decode(buffer: RegistryFriendlyByteBuf): PokemonInteractionsSyncPacket {
val speciesInteractions = buffer.readList { it ->
PokemonInteractionSet.decode(it)
}
val generalInteractions = buffer.readList { it ->
PokemonInteractionSet.decode(it)
}
return PokemonInteractionsSyncPacket(
speciesInteractions,
generalInteractions,
)
}
}
}
@@ -29,8 +29,7 @@ class FormUpdatePacket(pokemon: () -> Pokemon?, form: FormData) : SingleUpdatePa
val ID: ResourceLocation = cobblemonResource("packets/form-update")
fun decode(buffer: RegistryFriendlyByteBuf): FormUpdatePacket {
val pokemon = decodePokemon(buffer)
val form = FormData()
form.species = pokemon()!!.species
val form = FormData().also { it.initialize(pokemon()!!.species) }
form.decode(buffer)
return FormUpdatePacket(pokemon, form)
}
@@ -21,7 +21,9 @@ class PokemonStateUpdatePacket(pokemon: () -> Pokemon?, value: PokemonState): Si
value.writeToBuffer(buffer)
}
override fun set(pokemon: Pokemon, value: PokemonState) { pokemon.state = value }
override fun set(pokemon: Pokemon, value: PokemonState) {
pokemon.state = value
}
companion object {
val ID = cobblemonResource("state_update")
@@ -13,30 +13,24 @@ import com.cobblemon.mod.common.api.mark.Marks
import com.cobblemon.mod.common.api.pokeball.PokeBalls
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.client.CobblemonClient
import com.cobblemon.mod.common.entity.PlatformType
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.pokemon.Gender
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.readEnumConstant
import com.cobblemon.mod.common.util.readIdentifier
import com.cobblemon.mod.common.util.readString
import com.cobblemon.mod.common.util.readText
import com.cobblemon.mod.common.util.writeEnumConstant
import com.cobblemon.mod.common.util.writeIdentifier
import com.cobblemon.mod.common.util.writeString
import com.cobblemon.mod.common.util.writeText
import java.util.UUID
import com.cobblemon.mod.common.util.*
import net.minecraft.client.multiplayer.ClientLevel
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.network.chat.MutableComponent
import net.minecraft.network.protocol.game.ClientboundAddEntityPacket
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.entity.Entity
import java.util.*
class SpawnPokemonPacket(
var ownerId: UUID?,
var pokemonUUID: UUID,
var storageUUID: UUID?,
var scaleModifier: Float,
var speciesId: ResourceLocation,
var gender: Gender,
@@ -61,6 +55,7 @@ class SpawnPokemonPacket(
var tickSpawned: Int,
var rideBoosts: Map<RidingStat, Float>,
var rideStamina: Float,
var silent: Boolean,
vanillaSpawnPacket: ClientboundAddEntityPacket,
) : SpawnExtraDataEntityPacket<SpawnPokemonPacket, PokemonEntity>(vanillaSpawnPacket) {
@@ -69,6 +64,7 @@ class SpawnPokemonPacket(
constructor(entity: PokemonEntity, vanillaSpawnPacket: ClientboundAddEntityPacket) : this(
entity.ownerUUID,
entity.pokemon.uuid,
entity.pokemon.storeCoordinates.get()?.store?.uuid,
entity.pokemon.scaleModifier,
entity.exposedSpecies.resourceIdentifier,
entity.pokemon.gender,
@@ -93,12 +89,14 @@ class SpawnPokemonPacket(
entity.tickCount,
entity.entityData.get(PokemonEntity.RIDE_BOOSTS),
entity.entityData.get(PokemonEntity.RIDE_STAMINA),
entity.isSilent,
vanillaSpawnPacket
)
override fun encodeEntityData(buffer: RegistryFriendlyByteBuf) {
buffer.writeNullable(ownerId) { _, v -> buffer.writeUUID(v) }
buffer.writeUUID(this.pokemonUUID)
buffer.writeNullable(this.storageUUID) { pb, value -> pb.writeUUID(value) }
buffer.writeFloat(this.scaleModifier)
buffer.writeIdentifier(this.speciesId)
buffer.writeEnumConstant(this.gender)
@@ -127,10 +125,21 @@ class SpawnPokemonPacket(
{ _, value -> buffer.writeFloat(value) }
)
buffer.writeFloat(rideStamina)
buffer.writeBoolean(silent)
}
override fun applyData(entity: PokemonEntity, level: ClientLevel) {
entity.ownerUUID = ownerId
// Reuse the stored Pokémon instance to avoid creating a duplicate,
// preventing client/entity desync issues.
this@SpawnPokemonPacket.storageUUID?.let {
val storedPokemon = CobblemonClient.storage.locatePokemon(it, this@SpawnPokemonPacket.pokemonUUID)
if (storedPokemon != null) {
entity.pokemon = storedPokemon
}
}
entity.pokemon.apply {
uuid = this@SpawnPokemonPacket.pokemonUUID
scaleModifier = this@SpawnPokemonPacket.scaleModifier
@@ -162,6 +171,7 @@ class SpawnPokemonPacket(
entity.entityData.set(PokemonEntity.RIDE_BOOSTS, rideBoosts)
entity.entityData.set(PokemonEntity.RIDE_STAMINA, rideStamina)
entity.entityData.set(PokemonEntity.SCALE_MODIFIER, scaleModifier)
entity.isSilent = silent
entity.ejectPassengers()
passengers.forEach {
@@ -179,6 +189,7 @@ class SpawnPokemonPacket(
fun decode(buffer: RegistryFriendlyByteBuf): SpawnPokemonPacket {
val ownerId = buffer.readNullable { buffer.readUUID() }
val pokemonUUID = buffer.readUUID()
val storageUUID = buffer.readNullable { buffer.readUUID() }
val scaleModifier = buffer.readFloat()
val speciesId = buffer.readIdentifier()
val gender = buffer.readEnumConstant(Gender::class.java)
@@ -206,11 +217,13 @@ class SpawnPokemonPacket(
{ buffer.readFloat() }
)
val rideStamina = buffer.readFloat()
val silent = buffer.readBoolean()
val vanillaPacket = decodeVanillaPacket(buffer)
return SpawnPokemonPacket(
ownerId,
pokemonUUID,
storageUUID,
scaleModifier,
speciesId,
gender,
@@ -235,6 +248,7 @@ class SpawnPokemonPacket(
tickSpawned,
rideBoosts,
rideStamina,
silent,
vanillaPacket
)
}
@@ -1,16 +0,0 @@
package com.cobblemon.mod.common.net.messages.server.pokeball
import com.cobblemon.mod.common.api.net.NetworkPacket
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.network.RegistryFriendlyByteBuf
class SendOutPokeBallPacket() : NetworkPacket<SendOutPokeBallPacket> {
override val id = ID
override fun encode(buffer: RegistryFriendlyByteBuf) {
}
companion object {
val ID = cobblemonResource("send_out_pokeball")
fun decode(buffer: RegistryFriendlyByteBuf) = SendOutPokeBallPacket()
}
}
@@ -1,16 +0,0 @@
package com.cobblemon.mod.common.net.messages.server.pokerod
import com.cobblemon.mod.common.api.net.NetworkPacket
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.network.RegistryFriendlyByteBuf
class ApplyBaitPacket() : NetworkPacket<ApplyBaitPacket> {
override val id = ID
override fun encode(buffer: RegistryFriendlyByteBuf) {
}
companion object {
val ID = cobblemonResource("apply_bait")
fun decode(buffer: RegistryFriendlyByteBuf) = ApplyBaitPacket()
}
}
@@ -1,17 +0,0 @@
package com.cobblemon.mod.common.net.messages.server.pokerod
import com.cobblemon.mod.common.api.net.NetworkPacket
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.network.RegistryFriendlyByteBuf
class CastPokeRodPacket(val mainHand: Boolean) : NetworkPacket<CastPokeRodPacket> {
override val id = ID
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeBoolean(mainHand)
}
companion object {
val ID = cobblemonResource("cast_pokerod")
fun decode(buffer: RegistryFriendlyByteBuf) = CastPokeRodPacket(buffer.readBoolean())
}
}

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