Compare commits

...
Author SHA1 Message Date
Slippy 8b371127ed 1,000 files later... 2026-04-25 23:55:49 -05:00
Hiroku 8ee0d904ed Merge branch '1.21.1-TMs' into '1.8.0'
Merge TMs into 1.8 FINALLY

See merge request cable-mc/cobblemon!1853
2026-03-25 11:24:07 +00:00
Paul 291196eda7 Removed unused mixin 2026-03-25 20:30:07 +10:00
Hiroku b94e6d65b9 Merge branch '1.8.0' into '1.21.1-TMs'
# Conflicts:
#   CHANGELOG-1.8.md
#   common/src/main/kotlin/com/cobblemon/mod/common/pokemon/Pokemon.kt
#   common/src/main/kotlin/com/cobblemon/mod/common/world/feature/CobblemonFeatures.kt
2026-03-25 10:23:42 +00:00
jackowes 60fdd3325e remove duplicate registration 2026-03-22 17:26:41 -05:00
Paul eedcdafcbe Removed SpeciesLevel data file in favour of expanding the pokedex data 2026-03-22 14:46:44 +10:00
Paul 34595e01a9 Simplified some TM stuff 2026-03-21 22:33:29 +10:00
Paul 60b9ca7b7f Fixed a lol woopsy 2026-03-21 19:21:07 +10:00
Paul 83aea117c6 TM bits of cleanup and some fixes 2026-03-21 16:52:56 +10:00
Plastered_Crab 78b348329c Add more clusters to world genned type gem clusters 2026-03-17 19:17:35 -07:00
Plastered_Crab b64013c995 Type Gem Cluster world gen fix 2026-03-17 18:30:13 -07:00
Plastered_Crab 85003c9829 Get TM Unlocking on Level Up working again - Needs review 2026-03-17 14:12:53 -07:00
Plastered_Crab d9133e854a Clean up TM move sync 2026-03-17 13:13:04 -07:00
Plastered_Crab 05afe191e6 Add new textures for Data Monitor screens 2026-03-10 15:24:57 -07:00
Plastered_Crab e3b5067f31 Last Bug for the TM Machine squashed 2026-03-10 12:05:16 -07:00
Plastered_Crab bfa2d470f0 remove a few config checks 2026-03-10 11:34:47 -07:00
Plastered_Crab 53c0b28deb kill terablast 2026-03-10 11:33:59 -07:00
Plastered_Crab 5e9208d898 Fix small typos 2026-03-10 09:19:19 -07:00
Plastered_Crab 23a91ec2c9 Rescrape TMs to be current 2026-03-10 09:06:15 -07:00
Plastered_Crab 9998a328e3 Edit TM scraper 2026-03-10 09:02:38 -07:00
Plastered_Crab 9f07747cac Cleanup Obtainment methods for TMs 2026-03-10 09:02:22 -07:00
Plastered_Crab a7998bcec5 Cleanup Obtainment methods for TMs 2026-03-10 09:01:59 -07:00
Plastered_Crab 5983b29e9c Added Changelog 2026-03-09 17:54:39 -07:00
Plastered_Crab 749f441913 Merge branch 'main' into 1.21.1-TMs 2026-03-09 17:41:11 -07:00
Plastered_Crab dabbcceb9c Add Configs for having TMs be infinite use 2026-03-09 17:08:24 -07:00
Plastered_Crab 11f8313fc3 Add Configs for unlocking all TM and Level Up Moves and for Pokedex and TM Machine 2026-03-09 14:27:27 -07:00
Plastered_Crab 32b813620b Fix TM Machine Screen Back button to be more readable 2026-03-09 14:25:39 -07:00
Plastered_Crab 8147067b2e Fix Disk Rack West / East interaction 2026-03-09 08:39:31 -07:00
Plastered_Crab 24c6544e83 Sound Engine mixin to not have high octave pitches become flattened 2026-03-06 11:27:57 -08:00
Plastered_Crab b422ee4d29 Disk Jockeyyyyyyyy 2026-03-05 14:41:56 -08:00
Plastered_Crab a83867e153 When Data Monitor breaks it drops the stored disk, but not when moved by piston 2026-03-05 13:11:10 -08:00
Plastered_Crab 523973d2c0 Data monitor consumes the disk from player hand in Survival 2026-03-05 12:47:43 -08:00
Plastered_Crab 746160fa18 Disk Shelf works with Comparators and output a signal based on how many slots are filled 2026-03-05 12:06:05 -08:00
Plastered_Crab 837ba9679b Fix inverted columns and interaction with Disk Shelf 2026-03-05 11:35:33 -08:00
Plastered_Crab db7a319441 Invert Hopper interaction slots and storage array index 2026-03-05 11:00:14 -08:00
Plastered_Crab e284032c7e Give TM command allows for specifying quantity now 2026-03-05 10:05:17 -08:00
Plastered_Crab 3db9dd02f2 Disk Shelf works with Hoppers now 2026-03-05 08:54:30 -08:00
Plastered_Crab e680ec8790 If TM Shelf gets destroyed it drops stored disks 2026-03-04 16:05:57 -08:00
Plastered_Crab dc4f831e02 Fix slot interaction zones in North and South directions 2026-03-04 15:44:42 -08:00
Plastered_Crab b3a00c40b5 Adjust Slot brightness for Disk Shelf 2026-03-04 15:18:50 -08:00
Plastered_Crab f56fb0f50a Add new assets for TM Shelf disks 2026-03-04 14:27:12 -08:00
Plastered_Crab ec732bc03d Fix alignment issues and Lighting issues for TM Shelf 2026-03-04 13:42:43 -08:00
Plastered_Crab 229abd4a13 Fix lighting on rendered slot items 2026-03-04 11:46:18 -08:00
Plastered_Crab 7e92c17178 Fix WIP assets for TM Shelf disks 2026-03-04 11:17:21 -08:00
Plastered_Crab 77aa3208a0 Fix interaction zones on TM Shelf 2026-03-04 10:14:47 -08:00
Plastered_Crab b2ea715195 Update with main and add new Type Gem assets 2026-03-02 12:49:17 -08:00
Polymeta 35e4aafe99 Merge branch 'main' into 1.8.0 2026-02-26 15:46:01 +00:00
Plastered_Crab eab6fa9b40 Merge branch 'main' into 1.21.1-TMs 2026-02-25 13:43:40 -08:00
Plastered_Crab 98de423f91 Add giveTM command 2026-02-14 01:22:15 -08:00
Paul ef4fbecad6 Merge remote-tracking branch 'origin/1.8.0' into 1.8.0 2026-02-14 14:30:41 +10:00
Paul 96075ed34a Merge remote-tracking branch 'origin/main' into 1.8.0 2026-02-14 14:30:11 +10:00
Plastered_Crab e7cd9237f6 Have Move Dex only show Level Up, TM, and Egg moves 2026-02-13 19:43:49 -08:00
Plastered_Crab 84e22c68c7 Add white background for move list area on move Dex and add lang tooltips for filters 2026-02-13 19:16:36 -08:00
Plastered_Crab 300f2fc53c Add new type symbols, add dividing line 2026-02-13 15:43:18 -08:00
Plastered_Crab 2dfb88467b Added new filters to the Pokedex. More work on showing undiscovered/discovered level up moves per species in Move Dex 2026-02-13 13:45:56 -08:00
Plastered_Crab 589aab3b1a Move Dex adjustments 2026-02-13 11:50:27 -08:00
Plastered_Crab e17ca3a422 Clean up some of the Move Dex text 2026-02-13 11:07:58 -08:00
Plastered_Crab 0153846c7b Moves in Move Dex are hidden per species until discovered 2026-02-13 10:37:21 -08:00
Plastered_Crab f598f8d645 Secondary sorting for Move Dex 2026-02-12 21:03:48 -08:00
Plastered_Crab 8e3e047646 EVEN MORE Progress on the Move Dex 2026-02-12 19:28:40 -08:00
Plastered_Crab 551858df57 MORE Progress on the Move Dex 2026-02-12 14:42:39 -08:00
Plastered_Crab a7e3f4a7a1 Progress on the Move Dex 2026-02-12 11:15:52 -08:00
Plastered_Crab 94d64bb5c6 Progress on the Move Dex 2026-02-12 08:36:06 -08:00
Plastered_Crab e569acc648 Merge remote-tracking branch 'origin/1.21.1-TMs' into 1.21.1-TMs
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonNetwork.kt
2026-02-11 13:58:05 -08:00
Plastered_Crab 7c374b6e29 Tweaks to proceed with main merge to TMs 2026-02-11 13:56:22 -08:00
Plastered_Crab 3c128d9009 Merge branch 'main' into 1.21.1-TMs
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonNetwork.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/storage/player/GeneralPlayerData.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/storage/player/adapter/PlayerDataJsonBackend.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/storage/player/adapter/PlayerDataMongoBackend.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/storage/player/client/ClientGeneralPlayerData.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/client/CobblemonClient.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/client/gui/toast/CobblemonToast.kt
2026-02-11 13:40:44 -08:00
Plastered_Crab f5321a34c3 Merge branch 'main' into 1.21.1-TMs
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonNetwork.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/storage/player/GeneralPlayerData.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/storage/player/adapter/PlayerDataJsonBackend.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/storage/player/adapter/PlayerDataMongoBackend.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/storage/player/client/ClientGeneralPlayerData.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/client/CobblemonClient.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/client/gui/toast/CobblemonToast.kt
2026-02-11 12:23:37 -08:00
Plastered_Crab 2754bfc525 Data Monitor ejects disk out from monitor correctly 2026-02-09 09:02:49 -08:00
Plastered_Crab 3d1aecce13 Data Monitor to eject disk outwards and also unlock TM upon ejection 2026-02-09 00:30:31 -08:00
Plastered_Crab 38cd081ad6 Data Monitor to eject disk if player interacts with empty hand or non-disk item 2026-02-04 13:50:57 -08:00
Plastered_Crab 54a14ab805 Add new TM sync from move function for better performance when checking newly learned moves. 2026-02-04 13:49:37 -08:00
Plastered_Crab 633b45287c Sync TM moves upon new move added to a pokemon, and remove redundant sync checks on evolve and level up. 2026-02-04 13:36:28 -08:00
Plastered_Crab 3619f42090 Changes to Fossil machine Multi block to eject disks if the data monitor with one inserted becomes a Fossil Machine 2026-02-03 20:26:39 -08:00
Plastered_Crab 695453ffdb Changes to data monitor to allow disks to be placed inside to change the screen to learn TMs and change the screen texture 2026-02-03 20:25:09 -08:00
Polymeta 3c2bf0e2b3 Merge branch 'cobblemon-structure' into '1.8.0'
Slippy can Cook now!

See merge request cable-mc/cobblemon!2144
2026-01-30 09:45:31 +00:00
AramandPolymeta ac695b1092 Slippy can Cook now! 2026-01-30 09:45:31 +00:00
Plastered_Crab dd7066e8fa Sync New Tms after Level Up and Evolving 2026-01-29 12:34:08 -08:00
Plastered_Crab 416f003927 Remove debugging from Type Gem clusters 2026-01-29 10:44:13 -08:00
Plastered_Crab 3be3dc3de2 Add TM Data Sync upon login via scheduled task 2026-01-29 10:43:29 -08:00
Plastered_Crab a8f273737d Fix all default unlocked TMs 2026-01-26 12:41:02 -08:00
Plastered_Crab a185805b3f Fix scraper to scrape locked vs default TMs properly 2026-01-26 12:40:10 -08:00
Plastered_Crab defab6eaff Brief edit to TM scraper to properly scrape default TMs 2026-01-26 12:29:39 -08:00
Plastered_Crab 72fc1f1fb9 Fix the visual stage of the Type Gem Cluster Item Entity when dropped (with silk touch for example) 2026-01-26 11:53:58 -08:00
Plastered_Crab f96c69ab54 Unlock TMM recipe when interacting with the TMM without knowing the recipe 2026-01-26 11:48:08 -08:00
Plastered_Crab d51039dbb8 Merge branch 'main' into 1.21.1-TMs
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/client/CobblemonClient.kt
#	common/src/main/resources/data/minecraft/tags/block/mineable/pickaxe.json
2026-01-26 10:21:42 -08:00
Polymeta f450d9d2d6 Merge branch 'fix-bucket-reuse' into '1.8.0'
Fixed multi-spawns in a single pass using the same bucket for all of them

See merge request cable-mc/cobblemon!2104
2026-01-22 13:21:39 +00:00
Paul 0752ab3cac Fixed woopsies in changelog 2026-01-22 22:23:01 +10:00
Hiroku fe35a63769 Merge branch 'complex-movesets' into '1.8.0'
Complex movesets, parties, and pools

See merge request cable-mc/cobblemon!2086
2026-01-19 04:09:56 +00:00
Guilherme Pereira 17cab186d9 Merge branch '1.21.1-TMs' of gitlab.com:cable-mc/cobblemon into 1.21.1-TMs 2026-01-14 16:09:42 -03:00
Guilherme Pereira e00136052d add python tm scraping script and add support for tags in tm recipes 2026-01-14 16:09:18 -03:00
Plastered_Crab b23237ca9a Merge branch 'main' into 1.21.1-TMs 2026-01-14 10:32:32 -08:00
Paul 95d238ada0 Fixed multi-spawns in a single pass using the same bucket for all of them 2026-01-02 22:38:46 +10:00
plastered_crab 36456bfb0d Merge branch 'main' into 1.21.1-TMs 2025-12-28 14:35:43 -08:00
plastered_crab d9661c356f Merge remote-tracking branch 'origin/1.21.1-TMs' into 1.21.1-TMs 2025-12-28 14:31:38 -08:00
Test a66fad69cf idle screen 2025-12-23 22:26:37 -05:00
Test 95488ac54b Merge branch '1.21.1-TMs' of https://gitlab.com/cable-mc/cobblemon into 1.21.1-TMs 2025-12-23 16:19:12 -05:00
Test 19f2b71273 idle screen 2025-12-23 16:18:43 -05:00
Guilherme Pereira ba6e136a0b add fabric data generator, move gem blocks loot tables from code to jsons 2025-12-23 14:55:10 -03:00
Guilherme Pereira 28ac437987 gem blocks shenanigans 2025-12-22 16:42:27 -03:00
Plastered_Crab a4935f19bb TMs with the "none" obtainment method are shown in the TM Machine by default 2025-12-21 18:17:26 -08:00
plastered_crab c3e470f1de Merge branch 'main' into 1.21.1-TMs 2025-12-20 20:41:59 -08:00
Plastered_Crab 4139337f85 Remove Batch TM button, but make it so Batching TMs works via redstone signal now. 2025-12-19 09:09:47 -08:00
Plastered_Crab 25bc2dea93 Merge remote-tracking branch 'origin/1.21.1-TMs' into 1.21.1-TMs 2025-12-18 15:23:19 -08:00
Plastered_Crab 9dc3884ea6 Modify and enhance TM Machine Automation batch processes 2025-12-18 15:22:57 -08:00
Guilherme Pereira 89d95c6ab8 Merge branch '1.21.1-TMs' of gitlab.com:cable-mc/cobblemon into 1.21.1-TMs 2025-12-18 18:30:30 -03:00
Guilherme Pereira 7b70aa5a1e add /technicalmachine check command 2025-12-18 18:30:18 -03:00
Plastered_Crab 8191b4d9b6 fix Thunder TM recipe 2025-12-18 09:21:02 -08:00
Guilherme Pereira 84534fd041 add /technicalmachine command 2025-12-17 18:06:03 -03:00
Guilherme Pereira e6e1446eb1 merge all tm toasts into one if a player learn more than one tm at once 2025-12-16 21:01:47 -03:00
plastered_crab 39fbadc4fb Can Silk Touch Type Gem clusters of all stages now 2025-12-15 15:28:12 -08:00
plastered_crab 3c2a9c1dfd Overhaul of the Type Gem Core block growth system (hopefully more performant too) 2025-12-15 15:05:11 -08:00
plastered_crab 921fcbe477 Fix TM Machine and Blank TM recipe unlocks 2025-12-15 13:58:37 -08:00
plastered_crab c90b6c86ab Type Gems and Type Gem Clusters can be placed on any surface for decoration 2025-12-15 13:00:48 -08:00
plastered_crab 3ecf25502b Remove old recipes and add new recipes for Type gem things 2025-12-15 12:54:09 -08:00
plastered_crab b808112e17 Type Gem clusters can be silk touched to decorate with their various stages 2025-12-15 12:29:06 -08:00
plastered_crab 1b2d31db03 Pistons break the Type Gem Clusters to give the right amount of Type Gems 2025-12-15 12:09:07 -08:00
plastered_crab c9f8445d14 Fortune enchantment affects drops from Type Gem cluster blocks and type gem blocks 2025-12-15 12:03:35 -08:00
plastered_crab acd879ebef Alter Type Gem Clusters to drop new amount of type gems based on growth stage 2025-12-15 11:34:25 -08:00
plastered_crab f6889fd8ea Change TypeGemCoreBlock max connected gems before going dormant to 7 2025-12-15 11:25:08 -08:00
plastered_crab 21ccbef6c9 Fix TM Machine and Blank TM recipe to unlock after getting an Amethyst Shard 2025-12-15 11:16:11 -08:00
plastered_crab 9349e6a668 Fix TM Machine recipe 2025-12-15 11:12:27 -08:00
plastered_crab 28a7fe6a04 Marge Main to TM Branch 2025-12-15 11:07:44 -08:00
plastered_crab 18e1ed79ef Merge branch 'main' into 1.21.1-TMs
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonNetwork.kt
#	common/src/main/resources/assets/cobblemon/lang/en_us.json
2025-12-15 10:56:51 -08:00
Paul aafdb7688a Added more molang expressions to a bunch of composed pool properties, fixed some bugs 2025-12-15 14:22:59 +10:00
Paul 73b53f9702 Added egg move selector 2025-12-15 12:07:06 +10:00
Paul 33317a57c7 more doco changes [skip-ci] 2025-12-14 20:33:34 +10:00
Paul 72d8f26cdb Change silly default example weight to the actual default weight 2025-12-14 20:27:30 +10:00
Paul aea1afe145 Rearrange doco 2025-12-14 20:26:21 +10:00
Paul 0fec45b7ea Added npcAspects condition for party pool entries 2025-12-14 20:24:52 +10:00
Paul af98b823aa Documentation 2025-12-14 20:13:04 +10:00
Paul 07d9da2ca9 Added last_suitable_offensive move selector, added alpha moveset builder 2025-12-14 17:29:47 +10:00
Paul c2c86b2ef1 Stack all the cobblemon:move_weights.json from any datapacks onto one big map, like tags 2025-12-14 17:05:58 +10:00
Paul 22733e7117 Added more advanced moveset and party construction for NPCs 2025-12-08 19:31:48 +10:00
plastered_crab 117afcea32 Merge remote-tracking branch 'origin/1.21.1-TMs' into 1.21.1-TMs
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonBlocks.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonNetwork.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/types/ElementalTypes.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/data/CobblemonDataProvider.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/pokemon/evolution/controller/ClientEvolutionController.kt
2025-11-21 19:45:56 -08:00
plastered_crab 3f89750ee5 Merge main to TM Branch 2025-11-21 19:43:35 -08:00
plastered_crab d88be7ae05 Merge branch 'main' into 1.21.1-TMs
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonBlocks.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonItemComponents.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonNetwork.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/types/ElementalTypes.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/data/CobblemonDataProvider.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/item/group/CobblemonItemGroups.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/world/feature/CobblemonPlacedFeatures.kt
2025-11-21 19:32:38 -08:00
Nadja Reitzenstein 24245331db Merge branch 'main' into 1.21.1-TMs
* main: (322 commits)
  changes to /npcdelete uuid prameter and console support
  Translated using Weblate (Japanese)
  Translated using Weblate (Japanese)
  Enforce minimum depth requirement for swim wander, fix mistake in omnipathnav
  Remember the last camera type used for a mount type and switch to it if the config is enabled
  Wait that wasn't meant to still be in there
  API and config and controller options for infinite stamina
  rm unused function
  fix stat color
  Removed an unused sound from sounds.json
  Migrated all the other controllers to support the global config
  Migrate global ride controller settings to use a different solution
  Added global controller settings for riding, moved jet over to it
  add pokémon fullness to pokémon properties
  fix poke puff tooltip and mild multiplier #1711 #1710
  Add offset setting for determining surface level
  Change sprint modifier expression, adjust acceleration formula
  Change how reloadability is checked for /reload, maybe this will fix server stuff
  Persist stamina between dismount and mount
  rm unused imports
  ...
2025-10-01 12:15:20 +02:00
Nadja Reitzenstein d6d10d33e3 Fix file licenses 2025-09-19 18:11:16 +02:00
Nadja Reitzenstein 4d88c9b383 Fix small typos 2025-09-19 17:07:26 +02:00
Nadja Reitzenstein a3f597c589 Minimise the Gitlab-shown diff by undoing formatting changes
This commit will *absolutely* be reverted before we merge, but it helps with reviewing because gitlab doesn't filter whitespaces changes well.
2025-09-19 17:07:26 +02:00
Nadja Reitzenstein 2a3d88c12e Remove stray println 2025-09-19 16:33:44 +02:00
dhi_holoandNadja Reitzenstein 9561f47337 Small Asset Updates
(cherry picked from commit 4bfca0da43)
2025-09-19 16:28:04 +02:00
Nadja Reitzenstein f02c92db22 Merge branch 'main' into 1.21.1-TMs
* main: (62 commits)
  tweak default stat ranges for some flying behaviours
  Added math.sign and math.copy_sigh molang dep update
  Grain Improvements
  update fabric-permissions-api to 0.3.1
  Behaviour tweaks
  Update mochis.json
  IV Drip
  Ride settings editor
  Fix in air dismount
  Fix riding waterwalk
  swap out textures
  Remove unnecessary check on resource
  Changelog [skip-ci]
  Forgor camera offsets for extra seats
  Bronzong ready to scrape.
  Fix double lid on campfire pot open sprites
  Rename CookingPotColor to CampfirePotColor
  Add color property in CampfirePotItem
  Sync cooking pot button color with open / closed state
  Add cooking pot open sprites
  ...
2025-09-19 15:44:49 +02:00
Nadja Reitzenstein 181ef5d50e No-Fixes and formatting 2025-09-19 14:26:31 +02:00
plastered_crab 870397ebb9 Merge branch 'main' into 1.21.1-TMs
# Conflicts:
#	common/src/main/resources/assets/cobblemon/lang/en_us.json
2025-09-08 21:30:19 -07:00
plastered_crab 0ed413070d Merge branch 'main' into 1.21.1-TMs 2025-08-25 21:58:27 -07:00
plastered_crab f66f5ffdf9 Get TMs up to date with main 2025-08-16 20:01:12 -07:00
plastered_crab ca280a7b3e Merge branch 'main' into 1.21.1-TMs
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonBlocks.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/client/CobblemonClient.kt
2025-08-16 19:45:45 -07:00
plastered_crab 85284bff9d Get TMs up to date with main 2025-08-10 17:01:53 -07:00
plastered_crab fa4d33f725 Merge branch 'main' into 1.21.1-TMs
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/api/types/ElementalTypes.kt
2025-08-10 16:55:57 -07:00
plastered_crab 4ce42cdf6d edits to ElementalType.kt 2025-07-29 17:12:14 -07:00
plastered_crab 1b8562ead8 edits to ElementalType.kt 2025-07-29 16:09:25 -07:00
plastered_crab b9d9b906fb Merge branch 'main' into 1.21.1-TMs
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/api/pokemon/moves/Learnset.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/types/ElementalType.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/client/CobblemonClient.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/item/group/CobblemonItemGroups.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/world/feature/CobblemonPlacedFeatures.kt
2025-07-29 08:36:31 -07:00
plastered_crab e9d079a223 Chance to add type gem clusters to works genned Type Gem growths underground 2025-07-09 23:03:13 -07:00
plastered_crab e2e42ee91c Chance to add type gem clusters to works genned Type Gem growths underground 2025-07-09 13:16:56 -07:00
plastered_crab 4b0208201b Chance to add type gem clusters to works genned Type Gem growths underground 2025-07-09 07:19:53 -07:00
plastered_crab bec3f97733 Fix some classes 2025-07-09 06:40:24 -07:00
plastered_crab 16cfe37dac Merge branch 'refs/heads/main' into 1.21.1-TMs
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonNetwork.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/client/CobblemonClient.kt
#	common/src/main/resources/assets/cobblemon/lang/en_us.json
2025-07-09 05:25:07 -07:00
plastered_crab 7381da863c Merge branch 'refs/heads/main' into 1.21.1-TMs 2025-06-29 20:55:28 -07:00
Test b0902825fa teach through drag-dropping in TMM 2025-06-27 01:34:45 -04:00
Test 31c489cba0 Merge branch 'main' of https://gitlab.com/cable-mc/cobblemon into 1.21.1-TMs 2025-06-25 23:30:29 -04:00
Test 6a2f21d75a merge 2025-06-25 23:30:12 -04:00
Test e2c42a57c7 Merge branch '1.21.1-TMs' of https://gitlab.com/cable-mc/cobblemon into 1.21.1-TMs
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonBlocks.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/client/CobblemonClient.kt
2025-06-25 22:48:43 -04:00
Test 8592b53216 tmm refactor 2025-06-25 22:39:42 -04:00
plastered_crab 49dcf0049f Merge branch 'refs/heads/main' into 1.21.1-TMs
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonBlocks.kt
2025-06-20 06:08:06 -07:00
plastered_crab 076bee923f Merge branch 'refs/heads/main' into 1.21.1-TMs
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonBlocks.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonItemComponents.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonMenuType.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/client/CobblemonClient.kt
2025-06-10 18:27:00 -07:00
Plastered_Crab aacdb33827 Nevemind put it here instead 2025-05-30 10:17:02 -07:00
Plastered_Crab 53e9ece930 Player's TM datastore gets synced when their pokemon learn a move 2025-05-30 10:13:08 -07:00
plastered_crab 87f08d9bab 75% saturation on the Type colors 2025-05-29 23:43:01 -07:00
plastered_crab a50a6326af Merge remote-tracking branch 'origin/1.21.1-TMs' into 1.21.1-TMs 2025-05-29 22:32:12 -07:00
plastered_crab 3c0eec833e Type Color and TM asset change 2025-05-29 22:31:27 -07:00
Plastered_Crab 5b0cfdffcb Merge branch 'main' into 1.21.1-TMs 2025-05-29 15:11:22 -07:00
plastered_crab d788e0402e Merge remote-tracking branch 'origin/1.21.1-TMs' into 1.21.1-TMs 2025-05-28 19:04:22 -07:00
Plastered_Crab a347dc5e2a Merge branch 'main' into 1.21.1-TMs 2025-05-28 10:52:13 -07:00
plastered_crab b2fe158a7b Merge branch 'refs/heads/main' into 1.21.1-TMs 2025-05-28 09:33:57 -07:00
plastered_crab faab7e8df0 Change type gem block smelting recipes 2025-05-27 23:22:37 -07:00
plastered_crab 9070e198f1 Type gem clusters are no longer items and can't be placed. 2025-05-27 23:05:14 -07:00
plastered_crab 45b2a0db4d alter type gem clusters to drop type gems when broken 2025-05-27 21:51:16 -07:00
plastered_crab 65d39050ea Merge branch 'refs/heads/main' into 1.21.1-TMs
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonItemComponents.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonNetwork.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/client/CobblemonClient.kt
#	common/src/main/resources/assets/cobblemon/lang/en_us.json
#	common/src/main/resources/assets/cobblemon/sounds.json
2025-05-27 19:57:54 -07:00
plastered_crab 5da16df5ae Changes to Type Gem Cluster logic 2025-05-20 23:41:42 -07:00
plastered_crab 24fc4b05ab Toast appears now when learning a new TM + Licenses 2025-05-20 22:27:44 -07:00
Plastered_Crab 173902feb1 Starting code to display Toasts when unlocking new TMs 2025-05-20 19:14:41 -07:00
Plastered_Crab fd5f08fe84 Call syncTMsFromPokemon from add method in PokemonStore class. 2025-05-20 18:46:56 -07:00
Plastered_Crab d430ad6ca1 Move the syncTMsFromPokemon method to TMMoveManager 2025-05-20 18:21:05 -07:00
Plastered_Crab 1309c18292 Player unlocks TMs by catching pokemon with those moves and TM Machine lists only player learned moves 2025-05-20 18:08:42 -07:00
plastered_crab b6deebac97 Preparing for TM Move Data store 2025-05-19 15:16:01 -07:00
Plastered_Crab 83711f2dcb Merge branch '1.21.1-TMs-(dont-be-shelvish)' into 1.21.1-TMs
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/client/CobblemonClient.kt
2025-04-24 14:11:35 -07:00
Plastered_Crab 18e1a767af Use new TM Shelf Block and starting to get it working 2025-04-24 14:07:38 -07:00
Polymeta 6ae3efb461 fix merge and tidy up
Signed-off-by: Polymeta <40173118+Polymeta@users.noreply.github.com>
2025-04-24 19:29:38 +01:00
Polymeta 84e1934134 Merge branch 'refs/heads/main' into 1.21.1-TMs
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonBlockEntities.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonBlocks.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonItemComponents.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/client/CobblemonClient.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/data/CobblemonDataProvider.kt
#	common/src/main/resources/assets/cobblemon/lang/en_us.json
#	common/src/main/resources/cobblemon-common.accesswidener
#	fabric/src/main/kotlin/com/cobblemon/mod/fabric/CobblemonFabric.kt
2025-04-24 16:58:20 +01:00
Plastered_Crab 9aed9342ee Fix atrocious formatting 2025-04-23 13:33:54 -07:00
Plastered_Crab e4d0bf39ca Added in WIP tooltips for TMs 2025-04-23 13:26:32 -07:00
plastered_crab 847fa6842e Test Bookcase can hold TMs, Upgrade, Dubious Disc, Pokedex, etc (very WIP) 2025-04-22 13:51:15 -07:00
plastered_crab 08aa4f29bd Dubious Disc and Upgrade able to be placed in the Bookshelf too 2025-04-22 10:59:35 -07:00
plastered_crab 539218381b Corrected face being mirrored 2025-04-22 09:21:45 -07:00
plastered_crab 9dff39b4d7 Shelf is no longer upside down lmao 2025-04-22 08:42:24 -07:00
plastered_crab 1e578e94e5 Shelf renders without redish color 2025-04-22 08:32:35 -07:00
plastered_crab ec35c9b9bb shelf face rendered (upside down...) 2025-04-22 07:55:36 -07:00
plastered_crab 0deaeaeb7d fit the TM case textures onto shelves better 2025-04-22 07:22:49 -07:00
plastered_crab ec3d9e1a56 Test code for placing TMs in Chiseled Bookshelves 2025-04-21 17:11:10 -07:00
Polymeta 814f84ae42 licenses
Signed-off-by: Polymeta <40173118+Polymeta@users.noreply.github.com>
2025-04-18 01:11:33 +01:00
Polymeta 904be475ef Merge branch 'refs/heads/main' into 1.21.1-TMs
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonNetwork.kt
#	common/src/main/resources/cobblemon-common.accesswidener
2025-04-18 00:44:25 +01:00
Plastered_Crab 5edecbe81b Add crafting recipes / loot tables type gems, fix collision boxes, break rules, etc 2025-04-17 15:29:42 -07:00
Plastered_Crab f357033404 Fixed 100+ recipes, fixed CraftTMPacketHandler, adjusted GUI for TM Machine, and fixed names for some moves 2025-04-17 12:27:54 -07:00
Plastered_Crab 4800ae6649 Tweaking of some TM recipes 2025-04-17 09:29:21 -07:00
plastered_crab 60b0690c1e Add remaining 10 TM recipes. now all are present for testing 2025-04-17 00:40:00 -07:00
plastered_crab 82e31dd766 Add 330+ TM recipes for testing >:) 2025-04-17 00:00:34 -07:00
Guilherme Pereira 130e767e75 force type gems to grow halfway upon world generation 2025-03-28 05:02:33 -03:00
Guilherme Pereira b1a1d509ec make type gems grow naturally 2025-03-27 19:37:46 -03:00
Plastered_Crab 0698cf495e Type Gem Clusters fill in empty space when they grow grow stunted buds when hitting max capactity 2025-03-26 16:49:00 -07:00
Plastered_Crab 6047660405 Type Gem clusters grow in the same direction as their previous state more often and also fill in gaps with clusters 2025-03-26 14:53:05 -07:00
Plastered_Crab aaf3495f83 New transitional stages for the type gem clusters and fixes for rendering issues 2025-03-26 13:09:59 -07:00
Plastered_Crab ae6529c9fd Type gem clusters grow their respective type gem blocks when placed on the core 2025-03-25 17:05:03 -07:00
plastered_crab 45a96b2a42 Prepare for new Type Gem generation for each type of gem 2025-03-19 16:07:58 -07:00
Apion 4212903c67 Merge branch 'fix-tm-automation' into '1.21.1-TMs'
General Fixes and Implementation of the TM Machine for 1.21.1

See merge request cable-mc/cobblemon!1542
2025-03-01 08:52:01 +00:00
BuildTools be47dbcede Some Requested Changes 2025-03-01 06:05:38 +02:00
Apion 9556c57c69 Fix broken accesswideners 2025-02-23 21:33:54 -08:00
BuildTools f72b65bfac Fix for TMM GUI elements being offset in wrong postion after a previus TMM GUI Fix 2025-02-22 13:13:28 +02:00
BuildTools 6121700c9b Fixed disk in TMM rendering in wrong location after fixing TMM rendering backwards 2025-02-22 12:20:52 +02:00
BuildTools 91c968e743 A better fix for a previos TMM GUI fix 2025-02-22 12:05:30 +02:00
BuildTools 8fed6f4823 Fixed TMM rendering backwards and now the TMM will eject disc from its facing direction proper 2025-02-22 10:31:43 +02:00
BuildTools 34e4b39d75 Added Properties to TMM regarding breaking the block and loot table (based of PC) 2025-02-22 09:52:58 +02:00
BuildTools a21c70ce46 Resizing TMM GUI no longer breaks the GUI Buttons 2025-02-21 23:00:52 +02:00
BuildTools 8865a71f4c TMM will now output signal when ready to craft Blank TM and will drop Filter TM on break 2025-02-21 20:32:52 +02:00
BuildTools 142cadc187 TMM can now craft blank TMs if no TM is set in the filter, as well as allow taking out the filter TM 2025-02-21 19:43:36 +02:00
BuildTools 66f4c2531d Fixed TMM GUI quick move feature to properly work as well as what is considered to be outside the GUI 2025-02-19 20:16:00 +02:00
BuildTools 3f8ddd5f7d TM Machine now turns on/off and plays sound then opened/closed 2025-02-16 22:09:07 +02:00
BuildTools c076e14cbd Made Comparator read TM Machine and it provides power if the TM Machine has the materials to autocraft 2025-02-15 23:00:32 +02:00
BuildTools d724eb068b Fixed missing tooltip render call causing tooltips over items not showing in TM Machine 2025-02-15 13:10:15 +02:00
BuildTools d3ac263e29 Made the item form of TM Machine render properly in usual cases 2025-02-15 12:32:35 +02:00
BuildTools a6b7fe1f17 TM can now finally autocraft TMs based on filtered TM by receiving redstone signal 2025-02-15 09:50:28 +02:00
BuildTools ec714ccb84 Fixed Filtering for Hopper input not working 2025-02-12 19:53:54 +02:00
BuildTools ab0a129ba1 TM Machine GUI and Inventory synced and working properly 2025-02-12 18:45:32 +02:00
Guilherme Pereira a3f272b2aa Merge branch 'main' into 1.21.1-TMs 2025-02-06 03:48:28 -03:00
Guilherme Pereira 77d91ed979 make last type gems growing from core not fully grow into blocks 2025-01-29 03:56:35 -03:00
Guilherme Pereira 9907ba17c1 update licenses 2025-01-27 06:28:51 -03:00
Guilherme Pereira a6f0e19a44 type gem core now generates more gems 2025-01-27 06:27:27 -03:00
Guilherme Pereira 7ee064b04d make type gem bud grow into a block in its final stage 2025-01-27 05:29:14 -03:00
Guilherme Pereira f2e4c735f7 add type gems 2025-01-27 05:04:04 -03:00
Guilherme Pereira 5345bd321f update licenses 2025-01-21 19:08:49 -03:00
Guilherme Pereira eab8d0063b fix tms item sprite colors 2025-01-21 19:08:00 -03:00
Guilherme Pereira 6877977234 tm branch ressurect attempt 2025-01-17 23:23:01 -03:00
plastered_crab 60cb81362e Added some small langs 2025-01-09 07:46:53 -08:00
plastered_crab 088acb8e86 Added TMs, created TechnicalMachineRegistrySyncPacket.kt, TM json files are loading, TM machine GUI working better, altered obtain methods, and more 2025-01-08 20:04:24 -08:00
plastered_crab 77f59f06cb add tm_disk.geo file 2025-01-08 10:49:41 -08:00
plastered_crab a0882c6105 TM branch lives to see another day! (Still needs some issues fixed) 2025-01-08 10:49:21 -08:00
plastered_crab d50eb52172 Merge branch 'refs/heads/main' into 1.21.1-TMs 2025-01-07 19:49:30 -08:00
Plastered_Crab 5e5cadd314 Some more changes to unported files 2024-12-26 16:34:29 -08:00
Plastered_Crab 75b06f82f0 Oops forgor two files (they were not staged oops again) 2024-12-26 16:21:04 -08:00
Plastered_Crab 2ab050aa60 Oops forgor two files 2024-12-26 16:20:25 -08:00
Plastered_Crab 6d2f645e4a More work to port over TM stuff to 1.21.1 2024-12-26 16:10:04 -08:00
Plastered_Crab 9f937e29f2 Merge branch 'main' into 1.21.1-TMs 2024-12-26 13:54:27 -08:00
Plastered_Crab abc61a96c8 Merge branch 'main' into 1.21.1-TMs 2024-12-17 10:21:05 -08:00
Plastered_Crab b1d10822d4 Merge branch 'main' into 1.21.1-TMs 2024-12-12 08:35:22 -08:00
Plastered_Crab 09d984456d Start of new branch for 1.21.1 version of TM Machine code (this was painful) 2024-11-26 15:04:32 -08:00
2168 changed files with 67506 additions and 1478 deletions
+1
View File
@@ -36,6 +36,7 @@ common/src/main/resources/assets/cobblemon/showdown
# Generated files
*/generated/resources/*
/fabric/src/generated/resources/cobblemon-common.accesswidener
/fabric/src/main/generated/*
/.ignored/
common/.fabric
.kotlin/
+31
View File
@@ -0,0 +1,31 @@
# Changelog
## [1.8.0 (MONTH Xth, 2026)](#1-8-0)
### Additions
- Added a new `cobblemon:structure`, which is a feature without any restrictions allowing the Builders to place certain structures in much more specific manner.
- Added Configs for Infinite Use TMs and Unlocking all TMs
- Added Type Gem blocks
- Added Type Gem Clusters
- Added TMs
- Added TM Machine
- Added Deepslate Core Block
- Added /givetm Command
- Added Move Dex to Pokedex
- Added Disk Rack for storing disks and being an audio sequencer for Noteblocks
### Changes
- Data Monitor can accept Disks (TMs, Music Disks, Upgrade, Dubious Disk, etc) to change look
### Fixes
- Fixed rare Pokémon being far too likely to spawn multiple times in a single spawn cycle. The dev told me that the exact bug is too complicated to explain.
### Developer
- Tweaked the spawning selector interface to take a bucket function rather than a bucket directly.
### Molang & Datapacks
### Particles
### Localization
- Updated translations for:
+16
View File
@@ -0,0 +1,16 @@
### Additions
### Changes
- Wild Pokémon now reliably have at least one offensive move, if they can have offensive moves at that level.
### Fixes
### Developer
### Molang & Datapacks
- Added `moveset_builders` datapack folder for different styles of movesets.
- Added `party_pools` datapack folder for defining groups of Pokémon for the purposes of complex NPC party definitions.
- Added `party_compositions` datapack folder for arranging parties on NPCs that rely on party pools.
- Added `composed_pool` NPC party provider type which combines a party pool with a party composition to create more complex NPC parties.
### Particles
@@ -0,0 +1,52 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.mixin;
import com.cobblemon.mod.common.block.entity.TMShelfBlockEntity;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.NoteBlock;
import net.minecraft.world.level.block.state.properties.NoteBlockInstrument;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(NoteBlock.class)
public abstract class NoteBlockMixin {
@Inject(method = "triggerEvent", at = @At("HEAD"), cancellable = true)
private void cobblemon$triggerTMShelfSequencer(BlockState state, Level level, BlockPos pos, int type, int data, CallbackInfoReturnable<Boolean> cir) {
final BlockPos shelfPos = pos.below();
final BlockEntity blockEntity = level.getBlockEntity(shelfPos);
if (!(blockEntity instanceof TMShelfBlockEntity tmShelfBlockEntity)) {
return;
}
if (!level.isClientSide) {
final BlockPos instrumentPos = shelfPos.below();
final SoundEvent soundEvent;
if (level.getBlockState(instrumentPos).isAir()) {
soundEvent = SoundEvents.NOTE_BLOCK_BIT.value();
} else {
final NoteBlockInstrument instrument = level.getBlockState(instrumentPos).instrument();
final Holder<SoundEvent> holder = instrument.getSoundEvent();
soundEvent = holder.value();
}
tmShelfBlockEntity.onNoteBlockPulse(level, pos, soundEvent);
}
// Suppress vanilla note-block playback while it is on top of a TM Shelf.
cir.setReturnValue(false);
}
}
@@ -23,6 +23,7 @@ import net.minecraft.client.sounds.ChannelAccess;
import net.minecraft.client.sounds.SoundEngine;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.sounds.SoundSource;
import net.minecraft.util.Mth;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Final;
@@ -32,6 +33,7 @@ import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
import java.util.Iterator;
@@ -108,6 +110,17 @@ public abstract class SoundEngineMixin implements SoundEngineDuck {
}
}
/**
* TM Shelf can intentionally push note-block pitches above vanilla's 2.0f ceiling for octave-up playback.
* Keep vanilla clamping for all other sounds.
*/
@Inject(method = "calculatePitch", at = @At("HEAD"), cancellable = true)
private void cobblemon$allowExtendedNoteblockPitch(SoundInstance sound, CallbackInfoReturnable<Float> cir) {
if (sound.getLocation().getPath().startsWith("block.note_block.")) {
cir.setReturnValue(Mth.clamp(sound.getPitch(), 0.5F, 4.0F));
}
}
/** Different behavior for resuming SoundInstances while a BattleMusicInstance is being played. We do not want to resume filtered sounds. */
@Inject(method = "resume()V", at = @At("HEAD"), cancellable = true)
public void resume(CallbackInfo cb) {
@@ -62,6 +62,8 @@ import com.cobblemon.mod.common.api.storage.player.adapter.DexDataMongoBackend
import com.cobblemon.mod.common.api.storage.player.adapter.DexDataNbtBackend
import com.cobblemon.mod.common.api.storage.player.adapter.PlayerDataJsonBackend
import com.cobblemon.mod.common.api.storage.player.adapter.PlayerDataMongoBackend
import com.cobblemon.mod.common.api.storage.player.adapter.TMMoveMongoBackend
import com.cobblemon.mod.common.api.storage.player.adapter.TMMoveNbtBackend
import com.cobblemon.mod.common.api.storage.player.factory.CachedPlayerDataStoreFactory
import com.cobblemon.mod.common.api.tags.CobblemonEntityTypeTags
import com.cobblemon.mod.common.api.tags.CobblemonItemTags
@@ -109,13 +111,7 @@ import com.cobblemon.mod.common.pokemon.properties.UnaspectPropertyType
import com.cobblemon.mod.common.pokemon.properties.UncatchableProperty
import com.cobblemon.mod.common.pokemon.stat.CobblemonStatProvider
import com.cobblemon.mod.common.starter.CobblemonStarterHandler
import com.cobblemon.mod.common.util.DataKeys
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.ifDedicatedServer
import com.cobblemon.mod.common.util.isLaterVersion
import com.cobblemon.mod.common.util.party
import com.cobblemon.mod.common.util.requestWallpapers
import com.cobblemon.mod.common.util.server
import com.cobblemon.mod.common.util.*
import com.cobblemon.mod.common.world.feature.CobblemonPlacedFeatures
import com.cobblemon.mod.common.world.feature.ore.CobblemonOrePlacedFeatures
import com.cobblemon.mod.common.world.gamerules.CobblemonGameRules
@@ -244,6 +240,8 @@ object Cobblemon {
DATA_SYNCHRONIZED.subscribe {
storage.onPlayerDataSync(it)
it.tmList().scheduleFullSyncFromStores(it.party(), it.pc())
it.pokedex().scheduleFullSyncFromStores(it.party(), it.pc())
playerDataManager.syncAllToPlayer(it)
starterHandler.handleJoin(it)
it.requestWallpapers()
@@ -306,6 +304,7 @@ object Cobblemon {
this.config.maxPokemonLevel,
this.config.maxPokemonFriendship,
this.config.maxDynamaxLevel,
this.config.unlockAllMoveDexMovesByDefault
).sendToPlayer(player)
}
@@ -375,8 +374,12 @@ object Cobblemon {
val pokedexNbtFactory = CachedPlayerDataStoreFactory(DexDataNbtBackend())
pokedexNbtFactory.setup(server)
val tmMoveFactory = CachedPlayerDataStoreFactory(TMMoveNbtBackend())
tmMoveFactory.setup(server)
playerDataManager.setFactory(generalJsonFactory, PlayerInstancedDataStoreTypes.GENERAL)
playerDataManager.setFactory(pokedexNbtFactory, PlayerInstancedDataStoreTypes.POKEDEX)
playerDataManager.setFactory(tmMoveFactory, PlayerInstancedDataStoreTypes.TM_MOVES)
if (config.storageFormat == "nbt") {
NBTStoreAdapter(pokemonStoreRoot.absolutePath, useNestedFolders = true, folderPerClass = true)
@@ -403,8 +406,14 @@ object Cobblemon {
val pokedexMongoFactory = CachedPlayerDataStoreFactory(DexDataMongoBackend(mongoClient, config.mongoDBDatabaseName, "PokeDexCollection"))
pokedexMongoFactory.setup(server)
val tmMovesMongoFactory = CachedPlayerDataStoreFactory(
TMMoveMongoBackend(mongoClient, config.mongoDBDatabaseName, "TMMovesCollection")
)
tmMovesMongoFactory.setup(server)
playerDataManager.setFactory(generalMongoFactory, PlayerInstancedDataStoreTypes.GENERAL)
playerDataManager.setFactory(pokedexMongoFactory, PlayerInstancedDataStoreTypes.POKEDEX)
playerDataManager.setFactory(tmMovesMongoFactory, PlayerInstancedDataStoreTypes.TM_MOVES)
MongoDBStoreAdapter(mongoClient, config.mongoDBDatabaseName)
} catch (e: ClassNotFoundException) {
LOGGER.error("MongoDB driver not found.")
@@ -588,6 +597,7 @@ object Cobblemon {
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("tm"), TmArgumentType::class, SingletonArgumentInfo.contextFree(TmArgumentType::tm))
}
}
@@ -39,6 +39,9 @@ object CobblemonBlockEntities : PlatformRegistry<Registry<BlockEntityType<*>>, R
@JvmField
val LECTERN: BlockEntityType<LecternBlockEntity> = this.create("lectern", BlockEntityType.Builder.of(::LecternBlockEntity, CobblemonBlocks.LECTERN).build(null))
@JvmField
val TM_SHELF: BlockEntityType<TMShelfBlockEntity> = this.create("tm_shelf", BlockEntityType.Builder.of(::TMShelfBlockEntity, CobblemonBlocks.TM_SHELF).build(null))
@JvmField
val BERRY = this.create("berry", BlockEntityType.Builder.of(::BerryBlockEntity, *CobblemonBlocks.berries().values.toTypedArray()).build(null))
@@ -48,6 +51,8 @@ object CobblemonBlockEntities : PlatformRegistry<Registry<BlockEntityType<*>>, R
val SIGN: BlockEntityType<CobblemonSignBlockEntity> = this.create("sign", BlockEntityType.Builder.of(::CobblemonSignBlockEntity, CobblemonBlocks.APRICORN_SIGN, CobblemonBlocks.APRICORN_WALL_SIGN, CobblemonBlocks.SACCHARINE_SIGN, CobblemonBlocks.SACCHARINE_WALL_SIGN).build(null))
@JvmField
val HANGING_SIGN: BlockEntityType<CobblemonHangingSignBlockEntity> = this.create("hanging_sign", BlockEntityType.Builder.of(::CobblemonHangingSignBlockEntity, CobblemonBlocks.APRICORN_HANGING_SIGN, CobblemonBlocks.APRICORN_WALL_HANGING_SIGN, CobblemonBlocks.SACCHARINE_HANGING_SIGN, CobblemonBlocks.SACCHARINE_WALL_HANGING_SIGN).build(null))
@JvmField
val TM_MACHINE: BlockEntityType<TMMachineBlockEntity> = this.create("tm_machine", BlockEntityType.Builder.of(::TMMachineBlockEntity, CobblemonBlocks.TM_MACHINE).build(null))
@JvmField
val GILDED_CHEST: BlockEntityType<GildedChestBlockEntity> = this.create("chest", BlockEntityType.Builder.of(::GildedChestBlockEntity,
@@ -22,6 +22,7 @@ import com.cobblemon.mod.common.block.sign.CobblemonHangingSignBlock
import com.cobblemon.mod.common.block.sign.CobblemonSignBlock
import com.cobblemon.mod.common.block.sign.CobblemonWallHangingSignBlock
import com.cobblemon.mod.common.block.sign.CobblemonWallSignBlock
import com.cobblemon.mod.common.block.tmmachine.TMMachineBlock
import com.cobblemon.mod.common.block.general.HorizontalRotationCarpetBlock
import com.cobblemon.mod.common.block.general.HorizontalRotationalBlock
import com.cobblemon.mod.common.mixin.invoker.*
@@ -249,31 +250,31 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
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)
val TUMBLESTONE_CLUSTER = tumblestoneBlock("tumblestone_cluster", TumblestoneBlock.STAGE_3, 7, 3, null)
@JvmField
val LARGE_BUDDING_TUMBLESTONE = tumblestoneBlock("large_budding_tumblestone", GrowableStoneBlock.STAGE_2, 5, 3, TUMBLESTONE_CLUSTER)
val LARGE_BUDDING_TUMBLESTONE = tumblestoneBlock("large_budding_tumblestone", TumblestoneBlock.STAGE_2, 5, 3, TUMBLESTONE_CLUSTER)
@JvmField
val MEDIUM_BUDDING_TUMBLESTONE = tumblestoneBlock("medium_budding_tumblestone", GrowableStoneBlock.STAGE_1, 4, 3, LARGE_BUDDING_TUMBLESTONE)
val MEDIUM_BUDDING_TUMBLESTONE = tumblestoneBlock("medium_budding_tumblestone", TumblestoneBlock.STAGE_1, 4, 3, LARGE_BUDDING_TUMBLESTONE)
@JvmField
val SMALL_BUDDING_TUMBLESTONE = tumblestoneBlock("small_budding_tumblestone", GrowableStoneBlock.STAGE_0, 3, 4, MEDIUM_BUDDING_TUMBLESTONE)
val SMALL_BUDDING_TUMBLESTONE = tumblestoneBlock("small_budding_tumblestone", TumblestoneBlock.STAGE_0, 3, 4, MEDIUM_BUDDING_TUMBLESTONE)
@JvmField
val SKY_TUMBLESTONE_CLUSTER = tumblestoneBlock("sky_tumblestone_cluster", GrowableStoneBlock.STAGE_3, 7, 3, null)
val SKY_TUMBLESTONE_CLUSTER = tumblestoneBlock("sky_tumblestone_cluster", TumblestoneBlock.STAGE_3, 7, 3, null)
@JvmField
val LARGE_BUDDING_SKY_TUMBLESTONE = tumblestoneBlock("large_budding_sky_tumblestone", GrowableStoneBlock.STAGE_2, 5, 3, SKY_TUMBLESTONE_CLUSTER)
val LARGE_BUDDING_SKY_TUMBLESTONE = tumblestoneBlock("large_budding_sky_tumblestone", TumblestoneBlock.STAGE_2, 5, 3, SKY_TUMBLESTONE_CLUSTER)
@JvmField
val MEDIUM_BUDDING_SKY_TUMBLESTONE = tumblestoneBlock("medium_budding_sky_tumblestone", GrowableStoneBlock.STAGE_1, 4, 3, LARGE_BUDDING_SKY_TUMBLESTONE)
val MEDIUM_BUDDING_SKY_TUMBLESTONE = tumblestoneBlock("medium_budding_sky_tumblestone", TumblestoneBlock.STAGE_1, 4, 3, LARGE_BUDDING_SKY_TUMBLESTONE)
@JvmField
val SMALL_BUDDING_SKY_TUMBLESTONE = tumblestoneBlock("small_budding_sky_tumblestone", GrowableStoneBlock.STAGE_0, 3, 4, MEDIUM_BUDDING_SKY_TUMBLESTONE)
val SMALL_BUDDING_SKY_TUMBLESTONE = tumblestoneBlock("small_budding_sky_tumblestone", TumblestoneBlock.STAGE_0, 3, 4, MEDIUM_BUDDING_SKY_TUMBLESTONE)
@JvmField
val BLACK_TUMBLESTONE_CLUSTER = tumblestoneBlock("black_tumblestone_cluster", GrowableStoneBlock.STAGE_3, 7, 3, null)
val BLACK_TUMBLESTONE_CLUSTER = tumblestoneBlock("black_tumblestone_cluster", TumblestoneBlock.STAGE_3, 7, 3, null)
@JvmField
val LARGE_BUDDING_BLACK_TUMBLESTONE = tumblestoneBlock("large_budding_black_tumblestone", GrowableStoneBlock.STAGE_2, 5, 3, BLACK_TUMBLESTONE_CLUSTER)
val LARGE_BUDDING_BLACK_TUMBLESTONE = tumblestoneBlock("large_budding_black_tumblestone", TumblestoneBlock.STAGE_2, 5, 3, BLACK_TUMBLESTONE_CLUSTER)
@JvmField
val MEDIUM_BUDDING_BLACK_TUMBLESTONE = tumblestoneBlock("medium_budding_black_tumblestone", GrowableStoneBlock.STAGE_1, 4, 3, LARGE_BUDDING_BLACK_TUMBLESTONE)
val MEDIUM_BUDDING_BLACK_TUMBLESTONE = tumblestoneBlock("medium_budding_black_tumblestone", TumblestoneBlock.STAGE_1, 4, 3, LARGE_BUDDING_BLACK_TUMBLESTONE)
@JvmField
val SMALL_BUDDING_BLACK_TUMBLESTONE = tumblestoneBlock("small_budding_black_tumblestone", GrowableStoneBlock.STAGE_0, 3, 4, MEDIUM_BUDDING_BLACK_TUMBLESTONE)
val SMALL_BUDDING_BLACK_TUMBLESTONE = tumblestoneBlock("small_budding_black_tumblestone", TumblestoneBlock.STAGE_0, 3, 4, MEDIUM_BUDDING_BLACK_TUMBLESTONE)
@JvmField
val TUMBLESTONE_BLOCK = this.create("tumblestone_block", Block(BlockBehaviour.Properties.of().mapColor(MapColor.TERRACOTTA_ORANGE).strength(1.0F).sound(CobblemonSounds.TUMBLESTONE_BLOCK_SOUNDS).requiresCorrectToolForDrops().instrument(NoteBlockInstrument.BASEDRUM)))
@@ -282,6 +283,87 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
@JvmField
val BLACK_TUMBLESTONE_BLOCK = this.create("black_tumblestone_block", Block(BlockBehaviour.Properties.of().mapColor(MapColor.TERRACOTTA_BLACK).strength(1.0F).sound(CobblemonSounds.TUMBLESTONE_BLOCK_SOUNDS).requiresCorrectToolForDrops().instrument(NoteBlockInstrument.BASEDRUM)))
@JvmField
val TYPE_GEM_CORE = this.create("type_gem_core", TypeGemCoreBlock(BlockBehaviour.Properties.of().strength(4.0f, 10.0f).requiresCorrectToolForDrops()))
private val typeGemBlocks = mutableMapOf<ResourceLocation, TypeGemBlock>()
@JvmField
val TYPE_GEM_BLOCK_NORMAL = typeGemBlock("type_gem_block_normal")
@JvmField
val TYPE_GEM_BLOCK_FIRE = typeGemBlock("type_gem_block_fire")
@JvmField
val TYPE_GEM_BLOCK_WATER = typeGemBlock("type_gem_block_water")
@JvmField
val TYPE_GEM_BLOCK_ELECTRIC = typeGemBlock("type_gem_block_electric")
@JvmField
val TYPE_GEM_BLOCK_GRASS = typeGemBlock("type_gem_block_grass")
@JvmField
val TYPE_GEM_BLOCK_ICE = typeGemBlock("type_gem_block_ice")
@JvmField
val TYPE_GEM_BLOCK_FIGHTING = typeGemBlock("type_gem_block_fighting")
@JvmField
val TYPE_GEM_BLOCK_POISON = typeGemBlock("type_gem_block_poison")
@JvmField
val TYPE_GEM_BLOCK_GROUND = typeGemBlock("type_gem_block_ground")
@JvmField
val TYPE_GEM_BLOCK_FLYING = typeGemBlock("type_gem_block_flying")
@JvmField
val TYPE_GEM_BLOCK_PSYCHIC = typeGemBlock("type_gem_block_psychic")
@JvmField
val TYPE_GEM_BLOCK_BUG = typeGemBlock("type_gem_block_bug")
@JvmField
val TYPE_GEM_BLOCK_ROCK = typeGemBlock("type_gem_block_rock")
@JvmField
val TYPE_GEM_BLOCK_GHOST = typeGemBlock("type_gem_block_ghost")
@JvmField
val TYPE_GEM_BLOCK_DRAGON = typeGemBlock("type_gem_block_dragon")
@JvmField
val TYPE_GEM_BLOCK_DARK = typeGemBlock("type_gem_block_dark")
@JvmField
val TYPE_GEM_BLOCK_STEEL = typeGemBlock("type_gem_block_steel")
@JvmField
val TYPE_GEM_BLOCK_FAIRY = typeGemBlock("type_gem_block_fairy")
private val typeGemClusters = mutableMapOf<ResourceLocation, TypeGemClusterBlock>()
@JvmField
val TYPE_GEM_CLUSTER_NORMAL = typeGemCluster("type_gem_cluster_normal", TYPE_GEM_BLOCK_NORMAL, cobblemonResource("normal_gem"))
@JvmField
val TYPE_GEM_CLUSTER_FIRE = typeGemCluster("type_gem_cluster_fire", TYPE_GEM_BLOCK_FIRE, cobblemonResource("fire_gem"))
@JvmField
val TYPE_GEM_CLUSTER_WATER = typeGemCluster("type_gem_cluster_water", TYPE_GEM_BLOCK_WATER, cobblemonResource("water_gem"))
@JvmField
val TYPE_GEM_CLUSTER_ELECTRIC = typeGemCluster("type_gem_cluster_electric", TYPE_GEM_BLOCK_ELECTRIC, cobblemonResource("electric_gem"))
@JvmField
val TYPE_GEM_CLUSTER_GRASS = typeGemCluster("type_gem_cluster_grass", TYPE_GEM_BLOCK_GRASS, cobblemonResource("grass_gem"))
@JvmField
val TYPE_GEM_CLUSTER_ICE = typeGemCluster("type_gem_cluster_ice", TYPE_GEM_BLOCK_ICE, cobblemonResource("ice_gem"))
@JvmField
val TYPE_GEM_CLUSTER_FIGHTING = typeGemCluster("type_gem_cluster_fighting", TYPE_GEM_BLOCK_FIGHTING, cobblemonResource("fighting_gem"))
@JvmField
val TYPE_GEM_CLUSTER_POISON = typeGemCluster("type_gem_cluster_poison", TYPE_GEM_BLOCK_POISON, cobblemonResource("poison_gem"))
@JvmField
val TYPE_GEM_CLUSTER_GROUND = typeGemCluster("type_gem_cluster_ground", TYPE_GEM_BLOCK_GROUND, cobblemonResource("ground_gem"))
@JvmField
val TYPE_GEM_CLUSTER_FLYING = typeGemCluster("type_gem_cluster_flying", TYPE_GEM_BLOCK_FLYING, cobblemonResource("flying_gem"))
@JvmField
val TYPE_GEM_CLUSTER_PSYCHIC = typeGemCluster("type_gem_cluster_psychic", TYPE_GEM_BLOCK_PSYCHIC, cobblemonResource("psychic_gem"))
@JvmField
val TYPE_GEM_CLUSTER_BUG = typeGemCluster("type_gem_cluster_bug", TYPE_GEM_BLOCK_BUG, cobblemonResource("bug_gem"))
@JvmField
val TYPE_GEM_CLUSTER_ROCK = typeGemCluster("type_gem_cluster_rock", TYPE_GEM_BLOCK_ROCK, cobblemonResource("rock_gem"))
@JvmField
val TYPE_GEM_CLUSTER_GHOST = typeGemCluster("type_gem_cluster_ghost", TYPE_GEM_BLOCK_GHOST, cobblemonResource("ghost_gem"))
@JvmField
val TYPE_GEM_CLUSTER_DRAGON = typeGemCluster("type_gem_cluster_dragon", TYPE_GEM_BLOCK_DRAGON, cobblemonResource("dragon_gem"))
@JvmField
val TYPE_GEM_CLUSTER_DARK = typeGemCluster("type_gem_cluster_dark", TYPE_GEM_BLOCK_DARK, cobblemonResource("dark_gem"))
@JvmField
val TYPE_GEM_CLUSTER_STEEL = typeGemCluster("type_gem_cluster_steel", TYPE_GEM_BLOCK_STEEL, cobblemonResource("steel_gem"))
@JvmField
val TYPE_GEM_CLUSTER_FAIRY = typeGemCluster("type_gem_cluster_fairy", TYPE_GEM_BLOCK_FAIRY, cobblemonResource("fairy_gem"))
@JvmField
val POLISHED_TUMBLESTONE = this.create("polished_tumblestone", Block(BlockBehaviour.Properties.of().mapColor(MapColor.TERRACOTTA_ORANGE).strength(1.0F).sound(CobblemonSounds.TUMBLESTONE_BLOCK_SOUNDS).requiresCorrectToolForDrops().instrument(NoteBlockInstrument.BASEDRUM)))
@JvmField
@@ -456,6 +538,7 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
@JvmField
val SOUL_CAMPFIRE = create("soul_campfire", CampfireBlock(BlockBehaviour.Properties.of().sound(SoundType.WOOD).noOcclusion().pushReaction(PushReaction.BLOCK).mapColor(MapColor.PODZOL).strength(2.0F).lightLevel{9}, true))
@JvmField
val RELIC_COIN_POUCH = create("relic_coin_pouch", CoinPouchBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.RELIC_COIN_POUCH_SOUNDS).pushReaction(PushReaction.DESTROY).noOcclusion(), true))
@JvmField
@@ -584,9 +667,25 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
@JvmField
val PC = create("pc", PCBlock(BlockBehaviour.Properties.of().mapColor(MapColor.METAL).sound(SoundType.METAL).pushReaction(PushReaction.BLOCK).strength(2F).noOcclusion().lightLevel { if ((it.getValue(PCBlock.ON) as Boolean) && (it.getValue(PCBlock.PART) == PCBlock.PCPart.TOP)) 13 else 0 }))
val TM_MACHINE = create(
"tm_machine",
TMMachineBlock(
BlockBehaviour.Properties.of()
.mapColor(MapColor.COLOR_GREEN)
.sound(SoundType.METAL)
.pushReaction(PushReaction.BLOCK)
.strength(2F)
.noOcclusion()
)
)
@JvmField
val LECTERN = create("lectern", LecternBlock(BlockBehaviour.Properties.of().mapColor(MapColor.WOOD).instrument(NoteBlockInstrument.BASS).sound(SoundType.WOOD).strength(2.5F).ignitedByLava().lightLevel { if (it.getValue(LecternBlock.EMIT_LIGHT) as Boolean) 13 else 0 }))
@JvmField
val TM_SHELF = create("tm_shelf", TMShelfBlock(BlockBehaviour.Properties.of().mapColor(MapColor.WOOD).instrument(NoteBlockInstrument.BASS).sound(SoundType.METAL).strength(2.5F)))
@JvmField
val DISPLAY_CASE = create("display_case", DisplayCaseBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.DISPLAY_CASE_SOUNDS).noOcclusion().pushReaction(PushReaction.BLOCK).mapColor(MapColor.STONE).strength(0.3F).requiresCorrectToolForDrops()))
@@ -890,4 +989,28 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
val block = BlocksInvoker.createLeavesBlock(SoundType.GRASS)
return this.create(name, block)
}
private fun typeGemBlock(name: String): TypeGemBlock {
val typeGemBlock = this.create(name, TypeGemBlock(BlockBehaviour.Properties.of().strength(3.0f, 6.0f)))
val identifier = cobblemonResource(name)
typeGemBlocks[identifier] = typeGemBlock
return typeGemBlock
}
fun typeGemBlocks() = typeGemBlocks.toMap()
private fun typeGemCluster(name: String, gemBlock: Block, dropItemId: ResourceLocation): Block {
val gemClusterBlock = this.create(name, TypeGemClusterBlock(BlockBehaviour.Properties.of().strength(2.0f, 3.0f).pushReaction(PushReaction.DESTROY), gemBlock, dropItemId))
TypeGemClusterBlock.gemToClusterMap[gemBlock] = gemClusterBlock
val identifier = cobblemonResource(name)
typeGemClusters[identifier] = gemClusterBlock
return gemClusterBlock
}
fun typeGemClusters() = typeGemClusters.toMap()
}
@@ -71,10 +71,12 @@ object CobblemonCommands {
MarkGiveAllCommand.register(dispatcher)
PcSearchCommand.register(dispatcher)
PcTakeCommand.register(dispatcher)
GiveTmCommand.register(dispatcher)
TmCommand.register(dispatcher)
// Possibly lock down registration if and only if under dev environment or running in an environment
// with a certain system environment variable set
CobblemonInfoCommand.register(dispatcher)
CalculateSeatPositionCommand.register(dispatcher)
}
}
}
@@ -19,6 +19,7 @@ import net.minecraft.server.level.ServerPlayer
import net.minecraft.server.packs.PackType
import net.minecraft.server.packs.resources.PreparableReloadListener
import net.minecraft.tags.TagKey
import net.minecraft.world.inventory.MenuType
import net.minecraft.world.level.GameRules
import net.minecraft.world.level.ItemLike
import net.minecraft.world.level.biome.Biome
@@ -69,6 +69,12 @@ object CobblemonItemComponents : PlatformRegistry<Registry<DataComponentType<*>>
.networkSynchronized(FoodColourComponent.PACKET_CODEC)
.build())
@JvmField
val TM_MOVE: DataComponentType<TMMoveComponent> = create("tm_move", DataComponentType.builder<TMMoveComponent>()
.persistent(TMMoveComponent.CODEC)
.networkSynchronized(TMMoveComponent.PACKET_CODEC)
.build())
@JvmField
val INGREDIENT: DataComponentType<IngredientComponent> = create("ingredient", DataComponentType.builder<IngredientComponent>()
.persistent(IngredientComponent.CODEC)
@@ -101,6 +107,7 @@ object CobblemonItemComponents : PlatformRegistry<Registry<DataComponentType<*>>
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:flavour"), FLAVOUR)
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:ride_boosts"), RIDE_BOOST)
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:food_colour"), FOOD_COLOUR)
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:tm_move"), TM_MOVE)
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:ingredient"), INGREDIENT)
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:food"), FOOD)
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:mob_effects"), MOB_EFFECTS)
@@ -457,6 +457,10 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val PC = blockItem("pc", CobblemonBlocks.PC)
@JvmField
val TM_MACHINE = blockItem("tm_machine", CobblemonBlocks.TM_MACHINE)
@JvmField
val TM_SHELF = blockItem("tm_shelf", CobblemonBlocks.TM_SHELF)
@JvmField
val PASTURE = blockItem("pasture", CobblemonBlocks.PASTURE)
@JvmField
val DISPLAY_CASE = blockItem("display_case", CobblemonBlocks.DISPLAY_CASE)
@@ -1208,6 +1212,83 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val BLACK_TUMBLESTONE_BLOCK = blockItem("black_tumblestone_block", CobblemonBlocks.BLACK_TUMBLESTONE_BLOCK)
@JvmField
val TYPE_GEM_CORE = blockItem("type_gem_core", CobblemonBlocks.TYPE_GEM_CORE)
@JvmField
val TYPE_GEM_CLUSTER_NORMAL = blockItem("type_gem_cluster_normal", CobblemonBlocks.TYPE_GEM_CLUSTER_NORMAL)
@JvmField
val TYPE_GEM_CLUSTER_FIRE = blockItem("type_gem_cluster_fire", CobblemonBlocks.TYPE_GEM_CLUSTER_FIRE)
@JvmField
val TYPE_GEM_CLUSTER_WATER = blockItem("type_gem_cluster_water", CobblemonBlocks.TYPE_GEM_CLUSTER_WATER)
@JvmField
val TYPE_GEM_CLUSTER_ELECTRIC = blockItem("type_gem_cluster_electric", CobblemonBlocks.TYPE_GEM_CLUSTER_ELECTRIC)
@JvmField
val TYPE_GEM_CLUSTER_GRASS = blockItem("type_gem_cluster_grass", CobblemonBlocks.TYPE_GEM_CLUSTER_GRASS)
@JvmField
val TYPE_GEM_CLUSTER_ICE = blockItem("type_gem_cluster_ice", CobblemonBlocks.TYPE_GEM_CLUSTER_ICE)
@JvmField
val TYPE_GEM_CLUSTER_FIGHTING = blockItem("type_gem_cluster_fighting", CobblemonBlocks.TYPE_GEM_CLUSTER_FIGHTING)
@JvmField
val TYPE_GEM_CLUSTER_POISON = blockItem("type_gem_cluster_poison", CobblemonBlocks.TYPE_GEM_CLUSTER_POISON)
@JvmField
val TYPE_GEM_CLUSTER_GROUND = blockItem("type_gem_cluster_ground", CobblemonBlocks.TYPE_GEM_CLUSTER_GROUND)
@JvmField
val TYPE_GEM_CLUSTER_FLYING = blockItem("type_gem_cluster_flying", CobblemonBlocks.TYPE_GEM_CLUSTER_FLYING)
@JvmField
val TYPE_GEM_CLUSTER_PSYCHIC = blockItem("type_gem_cluster_psychic", CobblemonBlocks.TYPE_GEM_CLUSTER_PSYCHIC)
@JvmField
val TYPE_GEM_CLUSTER_BUG = blockItem("type_gem_cluster_bug", CobblemonBlocks.TYPE_GEM_CLUSTER_BUG)
@JvmField
val TYPE_GEM_CLUSTER_ROCK = blockItem("type_gem_cluster_rock", CobblemonBlocks.TYPE_GEM_CLUSTER_ROCK)
@JvmField
val TYPE_GEM_CLUSTER_GHOST = blockItem("type_gem_cluster_ghost", CobblemonBlocks.TYPE_GEM_CLUSTER_GHOST)
@JvmField
val TYPE_GEM_CLUSTER_DRAGON = blockItem("type_gem_cluster_dragon", CobblemonBlocks.TYPE_GEM_CLUSTER_DRAGON)
@JvmField
val TYPE_GEM_CLUSTER_DARK = blockItem("type_gem_cluster_dark", CobblemonBlocks.TYPE_GEM_CLUSTER_DARK)
@JvmField
val TYPE_GEM_CLUSTER_STEEL = blockItem("type_gem_cluster_steel", CobblemonBlocks.TYPE_GEM_CLUSTER_STEEL)
@JvmField
val TYPE_GEM_CLUSTER_FAIRY = blockItem("type_gem_cluster_fairy", CobblemonBlocks.TYPE_GEM_CLUSTER_FAIRY)
@JvmField
val TYPE_GEM_BLOCK_NORMAL = blockItem("type_gem_block_normal", CobblemonBlocks.TYPE_GEM_BLOCK_NORMAL)
@JvmField
val TYPE_GEM_BLOCK_FIRE = blockItem("type_gem_block_fire", CobblemonBlocks.TYPE_GEM_BLOCK_FIRE)
@JvmField
val TYPE_GEM_BLOCK_WATER = blockItem("type_gem_block_water", CobblemonBlocks.TYPE_GEM_BLOCK_WATER)
@JvmField
val TYPE_GEM_BLOCK_ELECTRIC = blockItem("type_gem_block_electric", CobblemonBlocks.TYPE_GEM_BLOCK_ELECTRIC)
@JvmField
val TYPE_GEM_BLOCK_GRASS = blockItem("type_gem_block_grass", CobblemonBlocks.TYPE_GEM_BLOCK_GRASS)
@JvmField
val TYPE_GEM_BLOCK_ICE = blockItem("type_gem_block_ice", CobblemonBlocks.TYPE_GEM_BLOCK_ICE)
@JvmField
val TYPE_GEM_BLOCK_FIGHTING = blockItem("type_gem_block_fighting", CobblemonBlocks.TYPE_GEM_BLOCK_FIGHTING)
@JvmField
val TYPE_GEM_BLOCK_POISON = blockItem("type_gem_block_poison", CobblemonBlocks.TYPE_GEM_BLOCK_POISON)
@JvmField
val TYPE_GEM_BLOCK_GROUND = blockItem("type_gem_block_ground", CobblemonBlocks.TYPE_GEM_BLOCK_GROUND)
@JvmField
val TYPE_GEM_BLOCK_FLYING = blockItem("type_gem_block_flying", CobblemonBlocks.TYPE_GEM_BLOCK_FLYING)
@JvmField
val TYPE_GEM_BLOCK_PSYCHIC = blockItem("type_gem_block_psychic", CobblemonBlocks.TYPE_GEM_BLOCK_PSYCHIC)
@JvmField
val TYPE_GEM_BLOCK_BUG = blockItem("type_gem_block_bug", CobblemonBlocks.TYPE_GEM_BLOCK_BUG)
@JvmField
val TYPE_GEM_BLOCK_ROCK = blockItem("type_gem_block_rock", CobblemonBlocks.TYPE_GEM_BLOCK_ROCK)
@JvmField
val TYPE_GEM_BLOCK_GHOST = blockItem("type_gem_block_ghost", CobblemonBlocks.TYPE_GEM_BLOCK_GHOST)
@JvmField
val TYPE_GEM_BLOCK_DRAGON = blockItem("type_gem_block_dragon", CobblemonBlocks.TYPE_GEM_BLOCK_DRAGON)
@JvmField
val TYPE_GEM_BLOCK_DARK = blockItem("type_gem_block_dark", CobblemonBlocks.TYPE_GEM_BLOCK_DARK)
@JvmField
val TYPE_GEM_BLOCK_STEEL = blockItem("type_gem_block_steel", CobblemonBlocks.TYPE_GEM_BLOCK_STEEL)
@JvmField
val TYPE_GEM_BLOCK_FAIRY = blockItem("type_gem_block_fairy", CobblemonBlocks.TYPE_GEM_BLOCK_FAIRY)
@JvmField
val POKEROD_SMITHING_TEMPLATE = create("pokerod_smithing_template", SmithingTemplateItem(
Component.translatable("item.minecraft.fishing_rod").blue(),
@@ -1432,44 +1513,49 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
val RELIC_COIN_POUCH = blockItem("relic_coin_pouch", CobblemonBlocks.RELIC_COIN_POUCH)
@JvmField
val RELIC_COIN_SACK = blockItem("relic_coin_sack", CobblemonBlocks.RELIC_COIN_SACK)
@JvmField
val TECHNICAL_MACHINE = this.create("technical_machine", TechnicalMachineItem(Properties()))
@JvmField
val BLANK_TM = this.create("blank_tm", CobblemonItem(Item.Properties()))
// Type Gems
@JvmField
val NORMAL_GEM = noSettingsItem("normal_gem")
val NORMAL_GEM = this.create("normal_gem", TypeGemItem(CobblemonBlocks.TYPE_GEM_CLUSTER_NORMAL, Item.Properties()))
@JvmField
val FIRE_GEM = noSettingsItem("fire_gem")
val FIRE_GEM = this.create("fire_gem", TypeGemItem(CobblemonBlocks.TYPE_GEM_CLUSTER_FIRE, Item.Properties()))
@JvmField
val WATER_GEM = noSettingsItem("water_gem")
val WATER_GEM = this.create("water_gem", TypeGemItem(CobblemonBlocks.TYPE_GEM_CLUSTER_WATER, Item.Properties()))
@JvmField
val GRASS_GEM = noSettingsItem("grass_gem")
val GRASS_GEM = this.create("grass_gem", TypeGemItem(CobblemonBlocks.TYPE_GEM_CLUSTER_GRASS, Item.Properties()))
@JvmField
val ELECTRIC_GEM = noSettingsItem("electric_gem")
val ELECTRIC_GEM = this.create("electric_gem", TypeGemItem(CobblemonBlocks.TYPE_GEM_CLUSTER_ELECTRIC, Item.Properties()))
@JvmField
val ICE_GEM = noSettingsItem("ice_gem")
val ICE_GEM = this.create("ice_gem", TypeGemItem(CobblemonBlocks.TYPE_GEM_CLUSTER_ICE, Item.Properties()))
@JvmField
val FIGHTING_GEM = noSettingsItem("fighting_gem")
val FIGHTING_GEM = this.create("fighting_gem", TypeGemItem(CobblemonBlocks.TYPE_GEM_CLUSTER_FIGHTING, Item.Properties()))
@JvmField
val POISON_GEM = noSettingsItem("poison_gem")
val POISON_GEM = this.create("poison_gem", TypeGemItem(CobblemonBlocks.TYPE_GEM_CLUSTER_POISON, Item.Properties()))
@JvmField
val GROUND_GEM = noSettingsItem("ground_gem")
val GROUND_GEM = this.create("ground_gem", TypeGemItem(CobblemonBlocks.TYPE_GEM_CLUSTER_GROUND, Item.Properties()))
@JvmField
val FLYING_GEM = noSettingsItem("flying_gem")
val FLYING_GEM = this.create("flying_gem", TypeGemItem(CobblemonBlocks.TYPE_GEM_CLUSTER_FLYING, Item.Properties()))
@JvmField
val PSYCHIC_GEM = noSettingsItem("psychic_gem")
val PSYCHIC_GEM = this.create("psychic_gem", TypeGemItem(CobblemonBlocks.TYPE_GEM_CLUSTER_PSYCHIC, Item.Properties()))
@JvmField
val BUG_GEM = noSettingsItem("bug_gem")
val BUG_GEM = this.create("bug_gem", TypeGemItem(CobblemonBlocks.TYPE_GEM_CLUSTER_BUG, Item.Properties()))
@JvmField
val ROCK_GEM = noSettingsItem("rock_gem")
val ROCK_GEM = this.create("rock_gem", TypeGemItem(CobblemonBlocks.TYPE_GEM_CLUSTER_ROCK, Item.Properties()))
@JvmField
val GHOST_GEM = noSettingsItem("ghost_gem")
val GHOST_GEM = this.create("ghost_gem", TypeGemItem(CobblemonBlocks.TYPE_GEM_CLUSTER_GHOST, Item.Properties()))
@JvmField
val DRAGON_GEM = noSettingsItem("dragon_gem")
val DRAGON_GEM = this.create("dragon_gem", TypeGemItem(CobblemonBlocks.TYPE_GEM_CLUSTER_DRAGON, Item.Properties()))
@JvmField
val DARK_GEM = noSettingsItem("dark_gem")
val DARK_GEM = this.create("dark_gem", TypeGemItem(CobblemonBlocks.TYPE_GEM_CLUSTER_DARK, Item.Properties()))
@JvmField
val STEEL_GEM = noSettingsItem("steel_gem")
val STEEL_GEM = this.create("steel_gem", TypeGemItem(CobblemonBlocks.TYPE_GEM_CLUSTER_STEEL, Item.Properties()))
@JvmField
val FAIRY_GEM = noSettingsItem("fairy_gem")
val FAIRY_GEM = this.create("fairy_gem", TypeGemItem(CobblemonBlocks.TYPE_GEM_CLUSTER_FAIRY, Item.Properties()))
//@JvmField
//val BINDING_SOIL = blockItem("binding_soil", CobblemonBlocks.BINDING_SOIL)
@@ -10,6 +10,7 @@ package com.cobblemon.mod.common
import com.cobblemon.mod.common.block.campfirepot.CookingPotMenu
import com.cobblemon.mod.common.block.tmmachine.TMMachineMenu
import com.cobblemon.mod.common.platform.PlatformRegistry
import net.minecraft.core.Registry
import net.minecraft.core.registries.BuiltInRegistries
@@ -19,7 +20,8 @@ import net.minecraft.world.inventory.MenuType
object CobblemonMenuType : PlatformRegistry<Registry<MenuType<*>>, ResourceKey<Registry<MenuType<*>>>, MenuType<*>>() {
val COOKING_POT = MenuType.register("cooking_pot", ::CookingPotMenu)
val COOKING_POT = MenuType.register("cooking_pot_menu", ::CookingPotMenu)
val TM_MACHINE = MenuType.register("tm_machine_menu", ::TMMachineMenu)
override val registry: Registry<MenuType<*>>
get() = BuiltInRegistries.MENU
@@ -0,0 +1,55 @@
/*
* 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.data.JsonDataRegistry
import com.cobblemon.mod.common.api.moves.MoveSelector
import com.cobblemon.mod.common.api.moves.MovesetBuilder
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.util.adapters.IdentifierAdapter
import com.cobblemon.mod.common.util.adapters.MovesetBuilderAdapter
import com.cobblemon.mod.common.util.adapters.StringToObjectAdapter
import com.cobblemon.mod.common.util.cobblemonResource
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.reflect.TypeToken
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
import net.minecraft.server.packs.PackType
object CobblemonMovesetBuilders : JsonDataRegistry<MovesetBuilder> {
override val gson: Gson = GsonBuilder()
.setPrettyPrinting()
.registerTypeAdapter(ResourceLocation::class.java, IdentifierAdapter)
.registerTypeAdapter(MovesetBuilder::class.java, MovesetBuilderAdapter)
.registerTypeAdapter(MoveSelector::class.java, StringToObjectAdapter(MoveSelector.selectors))
.create()
override val typeToken = TypeToken.get(MovesetBuilder::class.java)
override val resourcePath = "moveset_builders"
override val id: ResourceLocation = cobblemonResource("moveset_builders")
override val type = PackType.SERVER_DATA
override val observable = SimpleObservable<CobblemonMovesetBuilders>()
val movesetBuilders = mutableMapOf<ResourceLocation, MovesetBuilder>()
fun getOrThrow(id: ResourceLocation): MovesetBuilder {
return movesetBuilders[id]
?: throw IllegalArgumentException("Unknown MovesetBuilder id: $id")
}
override fun sync(player: ServerPlayer) { /* These don't sync. But what if they did? Nah probably shouldn't sync. */ }
override fun reload(data: Map<ResourceLocation, MovesetBuilder>) {
movesetBuilders.clear()
data.forEach { (id, value) -> value.id = id }
movesetBuilders.putAll(data)
observable.emit(this)
}
}
@@ -25,15 +25,15 @@ import com.cobblemon.mod.common.client.net.data.RideSettingsSyncHandler
import com.cobblemon.mod.common.client.net.debug.OpenRidingStatsDebugGUIHandler
import com.cobblemon.mod.common.client.net.dialogue.DialogueClosedHandler
import com.cobblemon.mod.common.client.net.dialogue.DialogueOpenedHandler
import com.cobblemon.mod.common.client.net.effect.*
import com.cobblemon.mod.common.client.net.gui.*
import com.cobblemon.mod.common.client.net.effect.PokeSnackBlockParticlesHandler
import com.cobblemon.mod.common.client.net.effect.RunPosableMoLangHandler
import com.cobblemon.mod.common.client.net.effect.SaccharineLogBlockParticlesHandler
import com.cobblemon.mod.common.client.net.effect.SpawnSnowstormEntityParticleHandler
import com.cobblemon.mod.common.client.net.effect.SpawnSnowstormParticleHandler
import com.cobblemon.mod.common.client.net.gui.ExpGainedDataPacketHandler
import com.cobblemon.mod.common.client.net.gui.InteractPokemonUIPacketHandler
import com.cobblemon.mod.common.client.net.gui.PokedexUIPacketHandler
import com.cobblemon.mod.common.client.net.gui.SummaryUIPacketHandler
import com.cobblemon.mod.common.client.net.gui.*
import com.cobblemon.mod.common.client.net.npc.CloseNPCEditorHandler
import com.cobblemon.mod.common.client.net.npc.OpenNPCEditorHandler
import com.cobblemon.mod.common.client.net.orientation.ClientboundUpdateOrientationHandler
@@ -137,9 +137,7 @@ import com.cobblemon.mod.common.net.messages.client.trade.TradeProcessStartedPac
import com.cobblemon.mod.common.net.messages.client.trade.TradeStartedPacket
import com.cobblemon.mod.common.net.messages.client.trade.TradeUpdatedPacket
import com.cobblemon.mod.common.net.messages.client.ui.ExpGainedDataPacket
import com.cobblemon.mod.common.net.messages.client.ui.InteractPokemonUIPacket
import com.cobblemon.mod.common.net.messages.client.ui.PokedexUIPacket
import com.cobblemon.mod.common.net.messages.client.ui.SummaryUIPacket
import com.cobblemon.mod.common.net.messages.client.ui.*
import com.cobblemon.mod.common.net.messages.server.*
import com.cobblemon.mod.common.net.messages.server.battle.BattleSelectActionsPacket
import com.cobblemon.mod.common.net.messages.server.battle.BattleTeamLeavePacket
@@ -150,6 +148,7 @@ import com.cobblemon.mod.common.net.messages.server.battle.SpectateBattlePacket
import com.cobblemon.mod.common.net.messages.server.behaviour.DamageOnCollisionPacket
import com.cobblemon.mod.common.net.messages.server.behaviour.SetEntityBehaviourPacket
import com.cobblemon.mod.common.net.messages.server.block.AdjustBlockEntityViewerCountPacket
import com.cobblemon.mod.common.net.messages.server.block.TMMachineTeachMovePacket
import com.cobblemon.mod.common.net.messages.server.callback.move.MoveSelectCancelledPacket
import com.cobblemon.mod.common.net.messages.server.callback.move.MoveSelectedPacket
import com.cobblemon.mod.common.net.messages.server.callback.party.PartyPokemonSelectedPacket
@@ -205,6 +204,7 @@ import com.cobblemon.mod.common.net.serverhandling.battle.TeamRequestResponseHan
import com.cobblemon.mod.common.net.serverhandling.behaviour.DamageOnCollisionPacketHandler
import com.cobblemon.mod.common.net.serverhandling.behaviour.SetEntityBehaviourHandler
import com.cobblemon.mod.common.net.serverhandling.block.AdjustBlockEntityViewerCountHandler
import com.cobblemon.mod.common.net.serverhandling.block.TMMachineTeachMoveHandler
import com.cobblemon.mod.common.net.serverhandling.callback.move.MoveSelectCancelledHandler
import com.cobblemon.mod.common.net.serverhandling.callback.move.MoveSelectedHandler
import com.cobblemon.mod.common.net.serverhandling.callback.party.PartyPokemonSelectedHandler
@@ -228,6 +228,8 @@ import com.cobblemon.mod.common.net.serverhandling.pasture.UnpasturePokemonHandl
import com.cobblemon.mod.common.net.serverhandling.pokedex.scanner.FinishScanningHandler
import com.cobblemon.mod.common.net.serverhandling.pokedex.scanner.StartScanningHandler
import com.cobblemon.mod.common.net.serverhandling.pokemon.interact.InteractPokemonHandler
import com.cobblemon.mod.common.net.serverhandling.pokemon.update.*
import com.cobblemon.mod.common.net.serverhandling.pokemon.update.*
import com.cobblemon.mod.common.net.serverhandling.pokemon.update.ServerboundUpdateRidingStateHandler
import com.cobblemon.mod.common.net.serverhandling.pokemon.update.SetActiveMarkHandler
import com.cobblemon.mod.common.net.serverhandling.pokemon.update.SetItemHiddenHandler
@@ -570,6 +572,7 @@ object CobblemonNetwork {
// Block entity
list.add(PacketRegisterInfo(AdjustBlockEntityViewerCountPacket.ID, AdjustBlockEntityViewerCountPacket::decode, AdjustBlockEntityViewerCountHandler))
list.add(PacketRegisterInfo(TMMachineTeachMovePacket.ID, TMMachineTeachMovePacket::decode, TMMachineTeachMoveHandler))
// Move select packets
list.add(PacketRegisterInfo(MoveSelectedPacket.ID, MoveSelectedPacket::decode, MoveSelectedHandler))
@@ -606,6 +609,10 @@ object CobblemonNetwork {
// Cooking
list.add(PacketRegisterInfo(ToggleCookingPotLidPacket.ID, ToggleCookingPotLidPacket::decode, ToggleCookingPotLidHandler))
// TM Machine
list.add(PacketRegisterInfo(SetActiveTMPacket.ID, SetActiveTMPacket::decode, SetActiveTMPacketHandler))
list.add(PacketRegisterInfo(SetTMMachineContainerDataPacket.ID, SetTMMachineContainerDataPacket::decode, SetTMMachineContainerDataPacketHandler))
// Behaviour Packets
list.add(PacketRegisterInfo(SetEntityBehaviourPacket.ID, SetEntityBehaviourPacket::decode, SetEntityBehaviourHandler))
list.add(PacketRegisterInfo(DamageOnCollisionPacket.ID, DamageOnCollisionPacket::decode, DamageOnCollisionPacketHandler))
@@ -0,0 +1,47 @@
/*
* 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.data.JsonDataRegistry
import com.cobblemon.mod.common.api.npc.PartyComposition
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.util.adapters.IdentifierAdapter
import com.cobblemon.mod.common.util.cobblemonResource
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.reflect.TypeToken
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
import net.minecraft.server.packs.PackType
object CobblemonPartyCompositions : JsonDataRegistry<PartyComposition> {
override val id: ResourceLocation = cobblemonResource("party_compositions")
override val type = PackType.SERVER_DATA
override val typeToken: TypeToken<PartyComposition> = TypeToken.get(PartyComposition::class.java)
override val resourcePath: String = "party_compositions"
override val observable = SimpleObservable<CobblemonPartyCompositions>()
override val gson: Gson = GsonBuilder()
.registerTypeAdapter(ResourceLocation::class.java, IdentifierAdapter)
.setPrettyPrinting()
.create()
val partyCompositions = mutableMapOf<ResourceLocation, PartyComposition>()
override fun sync(player: ServerPlayer) {
// probably worth syncing which exist, yeah. the deeper details probably not. I'll get to this eventually.
}
override fun reload(data: Map<ResourceLocation, PartyComposition>) {
data.entries.forEach { it.value.id = it.key }
partyCompositions.clear()
partyCompositions.putAll(data)
observable.emit(this)
Cobblemon.LOGGER.info("Loaded ${data.size} party compositions.")
}
}
@@ -0,0 +1,60 @@
/*
* 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.data.JsonDataRegistry
import com.cobblemon.mod.common.api.molang.ExpressionLike
import com.cobblemon.mod.common.api.moves.MovesetBuilder
import com.cobblemon.mod.common.api.npc.PartyPool
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.util.ScriptableIntRange
import com.cobblemon.mod.common.util.adapters.ExpressionLikeAdapter
import com.cobblemon.mod.common.util.adapters.IdentifierAdapter
import com.cobblemon.mod.common.util.adapters.IntRangeAdapter
import com.cobblemon.mod.common.util.adapters.MovesetBuilderReferenceAdapter
import com.cobblemon.mod.common.util.adapters.PokemonPropertiesAdapter
import com.cobblemon.mod.common.util.adapters.ScriptableIntRangeAdapter
import com.cobblemon.mod.common.util.cobblemonResource
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.reflect.TypeToken
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
import net.minecraft.server.packs.PackType
object CobblemonPartyPools : JsonDataRegistry<PartyPool> {
override val id: ResourceLocation = cobblemonResource("party_pools")
override val type = PackType.SERVER_DATA
override val typeToken: TypeToken<PartyPool> = TypeToken.get(PartyPool::class.java)
override val resourcePath: String = "party_pools"
override val observable = SimpleObservable<CobblemonPartyPools>()
override val gson: Gson = GsonBuilder()
.registerTypeAdapter(ResourceLocation::class.java, IdentifierAdapter)
.registerTypeAdapter(PokemonProperties::class.java, PokemonPropertiesAdapter(saveLong = false))
.registerTypeAdapter(IntRange::class.java, IntRangeAdapter)
.registerTypeAdapter(ScriptableIntRange::class.java, ScriptableIntRangeAdapter)
.registerTypeAdapter(MovesetBuilder::class.java, MovesetBuilderReferenceAdapter)
.registerTypeAdapter(ExpressionLike::class.java, ExpressionLikeAdapter)
.setPrettyPrinting()
.create()
val partyPools = mutableMapOf<ResourceLocation, PartyPool>()
override fun sync(player: ServerPlayer) {
// probably worth syncing. Knowing the details might help exploit the server? lot of effort for little though.
}
override fun reload(data: Map<ResourceLocation, PartyPool>) {
data.entries.forEach { it.value.id = it.key }
partyPools.clear()
partyPools.putAll(data)
observable.emit(this)
}
}
@@ -200,6 +200,22 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, ResourceKey<Regi
@JvmField
val TUMBLESTONE_STEP = this.create("block.tumblestone.step")
@JvmField
val TM_USE = this.create("item.tm.use")
@JvmField
val TM_MACHINE_CRAFT = this.create("block.tm_machine.craft")
@JvmField
val TM_MACHINE_OFF = this.create("block.tm_machine.off")
@JvmField
val TM_MACHINE_ON = this.create("block.tm_machine.on")
@JvmField
val TM_MACHINE_OPEN = this.create("block.tm_machine.open")
@JvmField
val TM_MACHINE_CLOSE = this.create("block.tm_machine.close")
@JvmField
val TM_MACHINE_PLACE_DISC = this.create("block.tm_machine.place_disc")
@JvmField
val EVOLUTION_STONE_BLOCK_BREAK = this.create("block.evolution_stone_block.break")
@JvmField
@@ -14,7 +14,7 @@ import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.advancements.critereon.ContextAwarePredicate
import net.minecraft.core.BlockPos
import net.minecraft.server.level.ServerPlayer
import java.util.Optional
import java.util.*
class PlantTumblestoneContext(val pos: BlockPos, val block: TumblestoneBlock)
@@ -29,42 +29,6 @@ class PlantTumblestoneCriterion(
}
override fun matches(player: ServerPlayer, context: PlantTumblestoneContext): Boolean {
return context.block.canGrow(context.pos, player.level())
return context.block.canGrow(player.level().getBlockState(context.pos), context.pos, player.level())
}
}
///**
// * A criterion condition for when a tumblestone block is planted
// * @param id The identifier of the criterion
// * @param predicate The predicate for the criterion
// *
// * @author Aethen
// * @since 02/28/2024
// */
//class PlantTumblestoneCriterionCondition(id: Identifier, predicate: LootContextPredicate) :
// SimpleCriterionCondition<PlantTumblestoneContext>(id, predicate) {
// override fun toJson(json: JsonObject) {
// // Add properties to json if needed. None needed for this criterion
// }
//
// override fun fromJson(json: JsonObject) {
// // Parse properties from json if needed. None needed for this criterion
// }
//
// /**
// * Checks if the tumblestone block can grow at the given position
// * @param player The player that planted the tumblestone block
// * @param context The context of the criterion
// * @return True if the tumblestone block can grow at the given position, false otherwise
// */
// override fun matches(player: ServerPlayer, context: PlantTumblestoneContext): Boolean {
// return context.tumbleStoneBlock.canGrow(context.pos, player.level())
// }
//}
//
///**
// * The context of the [PlantTumblestoneCriterionCondition]
// * @param pos The position of the tumblestone block
// * @param tumbleStoneBlock The tumblestone block
// */
//open class PlantTumblestoneContext(var pos: BlockPos, var tumbleStoneBlock: TumblestoneBlock)
}
@@ -1965,8 +1965,7 @@ object MoLangFunctions {
return@put DoubleValue(if (pokemon.addRideBoost(stat, value)) 1.0 else 0.0)
}
map.put("initialize_moveset") { params ->
val preferLatest = params.getBooleanOrNull(0) ?: true
pokemon.initializeMoveset(preferLatest)
pokemon.initializeMovesetFromDefault()
return@put DoubleValue.ONE
}
map.put("validate_moveset") { params ->
@@ -24,6 +24,7 @@ import net.minecraft.network.RegistryFriendlyByteBuf
class BenchedMoves : Iterable<BenchedMove> {
var changeFunction: ((BenchedMoves) -> Unit) = {}
// var moveAddedFunction: (BenchedMove) -> Unit = {}
private var emit = true
private val benchedMoves = mutableListOf<BenchedMove>()
@@ -58,6 +59,9 @@ class BenchedMoves : Iterable<BenchedMove> {
return false
}
/*if (emit) {
moveAddedFunction(benchedMove)
}*/
doThenEmit { benchedMoves.add(benchedMove) }
return true
}
@@ -174,4 +178,4 @@ data class BenchedMove(val moveTemplate: MoveTemplate, val ppRaisedStages: Int)
Codec.intRange(0, 3).fieldOf(DataKeys.POKEMON_MOVESET_RAISED_PP_STAGES).forGetter(BenchedMove::ppRaisedStages)
).apply(it, ::BenchedMove) }
}
}
}
@@ -0,0 +1,57 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.moves
import com.cobblemon.mod.common.pokemon.FormData
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.resources.ResourceLocation
/**
* This is all you need tbh. A moveset builder that picks moves from up to 4 slots of move selectors.
*
* @author Hiroku
* @since December 5th, 2025
*/
class DefaultMovesetBuilder : MovesetBuilder {
companion object {
val TYPE = cobblemonResource("default")
}
override val type: ResourceLocation = TYPE
override var id: ResourceLocation = cobblemonResource("temp") // replaced on load.
val slot1: List<MoveSelector> = listOf()
val slot2: List<MoveSelector> = listOf()
val slot3: List<MoveSelector> = listOf()
val slot4: List<MoveSelector> = listOf()
override fun build(form: FormData, level: Int): MoveSet {
val chosenMoves = mutableSetOf<MoveTemplate>()
tryAddMoveFromSlot(form, level, slot1, chosenMoves)
tryAddMoveFromSlot(form, level, slot2, chosenMoves)
tryAddMoveFromSlot(form, level, slot3, chosenMoves)
tryAddMoveFromSlot(form, level, slot4, chosenMoves)
if (chosenMoves.isEmpty()) {
chosenMoves.add(Moves.getExceptional())
}
val moveset = MoveSet()
chosenMoves.map(MoveTemplate::create).forEach(moveset::add)
return moveset
}
fun tryAddMoveFromSlot(form: FormData, level: Int, selectors: List<MoveSelector>, chosenMoves: MutableSet<MoveTemplate>) {
for (selector in selectors) {
val move = selector(form, form.moves, level, chosenMoves)
if (move != null) {
chosenMoves.add(move)
return
}
}
}
}
@@ -0,0 +1,148 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.moves
import com.cobblemon.mod.common.api.moves.categories.DamageCategories
import com.cobblemon.mod.common.api.pokemon.moves.Learnset
import com.cobblemon.mod.common.api.pokemon.stats.Stats
import com.cobblemon.mod.common.pokemon.FormData
import com.cobblemon.mod.common.util.weightedSelection
/**
* A function that selects a move from a given [Learnset] for a particular [FormData] at a certain level,
* taking into account any moves that have already been chosen for the moveset.
*
* @author Hiroku
* @since December 5th, 2025
*/
fun interface MoveSelector {
operator fun invoke(form: FormData, learnset: Learnset, level: Int, chosenMoves: Set<MoveTemplate>): MoveTemplate?
companion object {
val NONE = MoveSelector { _, _, _, _ -> null }
val LAST_LEVELUP = MoveSelector { form, learnset, level, chosenMoves ->
val levels = learnset.levelUpMoves.keys.filter { it <= level }.sortedDescending()
levels.forEach {
val moves = (learnset.levelUpMoves[it] ?: return@forEach) - chosenMoves
if (moves.isNotEmpty()) {
return@MoveSelector moves.weightedSelection { it.getSelectionWeight(form) }
}
}
return@MoveSelector null
}
val LAST_OFFENSIVE = MoveSelector { form, learnset, level, chosenMoves ->
val levels = learnset.levelUpMoves.keys.filter { it <= level }.sortedDescending()
levels.forEach {
val moves = (learnset.levelUpMoves[it]?.filter { it.damageCategory != DamageCategories.STATUS } ?: return@forEach) - chosenMoves
if (moves.isNotEmpty()) {
return@MoveSelector moves.weightedSelection { it.getSelectionWeight(form) }
}
}
return@MoveSelector null
}
val LAST_SUITABLE_OFFENSIVE = MoveSelector { form, learnset, level, chosenMoves ->
val levels = learnset.levelUpMoves.keys.filter { it <= level }.sortedDescending()
val suitableCategories = if (form.baseStats[Stats.ATTACK]!! > form.baseStats[Stats.SPECIAL_ATTACK]!!) {
setOf(DamageCategories.PHYSICAL)
} else if (form.baseStats[Stats.SPECIAL_ATTACK]!! > form.baseStats[Stats.ATTACK]!!) {
setOf(DamageCategories.SPECIAL)
} else {
setOf(DamageCategories.PHYSICAL, DamageCategories.SPECIAL)
}
levels.forEach {
val moves = (learnset.levelUpMoves[it]?.filter { it.damageCategory in suitableCategories } ?: return@forEach) - chosenMoves
if (moves.isNotEmpty()) {
return@MoveSelector moves.weightedSelection { it.getSelectionWeight(form) }
}
}
return@MoveSelector null
}
val LAST_STATUS = MoveSelector { form, learnset, level, chosenMoves ->
val levels = learnset.levelUpMoves.keys.filter { it <= level }.sortedDescending()
levels.forEach {
val moves = (learnset.levelUpMoves[it]?.filter { it.damageCategory == DamageCategories.STATUS } ?: return@forEach) - chosenMoves
if (moves.isNotEmpty()) {
return@MoveSelector moves.weightedSelection { it.getSelectionWeight(form) }
}
}
return@MoveSelector null
}
val STAB = MoveSelector { form, learnset, level, chosenMoves ->
val moves = learnset.getLevelUpMovesUpTo(level) - chosenMoves
val stabMoves = moves.filter { it.elementalType in form.types && it.damageCategory != DamageCategories.STATUS }
return@MoveSelector stabMoves.weightedSelection { it.getSelectionWeight(form) }
}
val STAB_PHYSICAL = MoveSelector { form, learnset, level, chosenMoves ->
val moves = learnset.getLevelUpMovesUpTo(level) - chosenMoves
val stabMoves = moves.filter { it.elementalType in form.types && it.damageCategory == DamageCategories.PHYSICAL }
return@MoveSelector stabMoves.weightedSelection { it.getSelectionWeight(form) }
}
val STAB_SPECIAL = MoveSelector { form, learnset, level, chosenMoves ->
val moves = learnset.getLevelUpMovesUpTo(level) - chosenMoves
val stabMoves = moves.filter { it.elementalType in form.types && it.damageCategory == DamageCategories.SPECIAL }
return@MoveSelector stabMoves.weightedSelection { it.getSelectionWeight(form) }
}
val PHYSICAL = MoveSelector { form, learnset, level, chosenMoves ->
val moves = learnset.getLevelUpMovesUpTo(level) - chosenMoves
val physicalMoves = moves.filter { it.damageCategory == DamageCategories.PHYSICAL }
return@MoveSelector physicalMoves.weightedSelection { it.getSelectionWeight(form) }
}
val SPECIAL = MoveSelector { form, learnset, level, chosenMoves ->
val moves = learnset.getLevelUpMovesUpTo(level) - chosenMoves
val specialMoves = moves.filter { it.damageCategory == DamageCategories.SPECIAL }
return@MoveSelector specialMoves.weightedSelection { it.getSelectionWeight(form) }
}
val OFFENSIVE = MoveSelector { form, learnset, level, chosenMoves ->
val moves = learnset.getLevelUpMovesUpTo(level) - chosenMoves
val offensiveMoves = moves.filter { it.damageCategory != DamageCategories.STATUS }
return@MoveSelector offensiveMoves.weightedSelection { it.getSelectionWeight(form) }
}
val RANDOM_LEVELUP = MoveSelector { form, learnset, level, chosenMoves ->
val moves = learnset.getLevelUpMovesUpTo(level) - chosenMoves
return@MoveSelector moves.weightedSelection { it.getSelectionWeight(form) }
}
val STATUS = MoveSelector { form, learnset, level, chosenMoves ->
val moves = learnset.getLevelUpMovesUpTo(level) - chosenMoves
val statusMoves = moves.filter { it.damageCategory == DamageCategories.STATUS }
return@MoveSelector statusMoves.weightedSelection { it.getSelectionWeight(form) }
}
val TM = MoveSelector { form, learnset, level, chosenMoves ->
val moves = learnset.tmMoves - chosenMoves - learnset.getLevelUpMovesUpTo(level)
return@MoveSelector moves.weightedSelection { it.getSelectionWeight(form) }
}
val STAB_TM = MoveSelector { form, learnset, level, chosenMoves ->
val moves = learnset.tmMoves - chosenMoves - learnset.getLevelUpMovesUpTo(level)
val stabMoves = moves.filter { it.elementalType in form.types }
return@MoveSelector stabMoves.weightedSelection { it.getSelectionWeight(form) }
}
val EGG = MoveSelector { form, learnset, level, chosenMoves ->
val moves = learnset.eggMoves - chosenMoves - learnset.getLevelUpMovesUpTo(level)
return@MoveSelector moves.weightedSelection { it.getSelectionWeight(form) }
}
val selectors = mutableMapOf(
"none" to NONE,
"last_levelup" to LAST_LEVELUP,
"last_offensive" to LAST_OFFENSIVE,
"last_suitable_offensive" to LAST_SUITABLE_OFFENSIVE,
"last_status" to LAST_STATUS,
"levelup" to RANDOM_LEVELUP,
"stab" to STAB,
"stab_physical" to STAB_PHYSICAL,
"stab_special" to STAB_SPECIAL,
"physical" to PHYSICAL,
"special" to SPECIAL,
"offensive" to OFFENSIVE,
"status" to STATUS,
"tm" to TM,
"stab_tm" to STAB_TM,
"egg" to EGG
)
}
}
@@ -54,6 +54,7 @@ class MoveSet : Iterable<Move> {
*/
fun getMoves() = moves.filterNotNull()
fun getMovesWithNulls() = moves.toList()
fun getMoveTemplates() = getMoves().map { move -> move.template }
fun hasSpace() = moves.any { it == null }
/**
@@ -222,4 +223,4 @@ class MoveSet : Iterable<Move> {
{ moveSet -> moveSet.toList() }
)
}
}
}
@@ -12,12 +12,12 @@ import com.bedrockk.molang.runtime.struct.MoStruct
import com.bedrockk.molang.runtime.struct.QueryStruct
import com.bedrockk.molang.runtime.value.DoubleValue
import com.bedrockk.molang.runtime.value.StringValue
import com.cobblemon.mod.common.api.moves.animations.ActionEffectTimeline
import com.cobblemon.mod.common.api.moves.categories.DamageCategories
import com.cobblemon.mod.common.api.moves.categories.DamageCategory
import com.cobblemon.mod.common.api.types.ElementalType
import com.cobblemon.mod.common.api.types.ElementalTypes
import com.cobblemon.mod.common.battles.MoveTarget
import com.cobblemon.mod.common.pokemon.FormData
import com.cobblemon.mod.common.util.codec.CodecUtils
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.lang
@@ -53,7 +53,8 @@ open class MoveTemplate(
val pp: Int,
val priority: Int,
val critRatio: Double,
val effectChances: Array<Double>
val effectChances: Array<Double>,
val weight: Float
) {
val struct: MoStruct by lazy {
QueryStruct(hashMapOf())
@@ -69,9 +70,9 @@ open class MoveTemplate(
.addFunction("max_pp") { DoubleValue(maxPp) }
.addFunction("priority") { DoubleValue(priority) }
.addFunction("crit_ratio") { DoubleValue(critRatio) }
.addFunction("weight") { DoubleValue(weight.toDouble()) }
}
val displayName: MutableComponent
get() = lang("move.$name")
val description: MutableComponent
@@ -89,7 +90,8 @@ open class MoveTemplate(
pp = 5,
priority = 0,
critRatio = 0.0,
effectChances = emptyArray()
effectChances = emptyArray(),
weight = 0F,
)
companion object {
@@ -160,4 +162,12 @@ open class MoveTemplate(
}
return this.elementalType
}
fun getSelectionWeight(form: FormData): Float {
return if (this in form.signatureMoves) {
weight * MovesetBuilder.signatureMoveWeightMultiplier
} else {
weight
}
}
}
@@ -10,15 +10,15 @@ package com.cobblemon.mod.common.api.moves
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.data.DataRegistry
import com.cobblemon.mod.common.api.moves.animations.ActionEffects
import com.cobblemon.mod.common.api.moves.categories.DamageCategories
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.api.types.ElementalTypes
import com.cobblemon.mod.common.battles.MoveTarget
import com.cobblemon.mod.common.battles.runner.ShowdownService
import com.cobblemon.mod.common.net.messages.client.data.MovesRegistrySyncPacket
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.fromJson
import com.google.gson.GsonBuilder
import com.google.gson.JsonArray
import com.google.gson.JsonObject
import net.minecraft.resources.ResourceLocation
@@ -46,6 +46,14 @@ object Moves : DataRegistry {
this.idMapping.clear()
this.moveScripts.clear()
val weights = mutableMapOf<String, Number>()
manager.getResourceStack(cobblemonResource("move_weights.json")).forEach {
it.open().use {
val map = GsonBuilder().create().fromJson<Map<String, Number>>(it.reader())
weights.putAll(map)
}
}
ShowdownService.service.resetRegistryData("move")
manager.listResources("moves") { it.path.endsWith(".js") }.forEach { (identifier, resource) ->
resource.open().use { stream ->
@@ -75,6 +83,7 @@ object Moves : DataRegistry {
val priority = jsMove.get("priority").asInt
val critRatio = jsMove.get("critRatio")?.asDouble ?: 1.0
val effectChances = arrayListOf<Double>()
val weight = weights[id]?.toFloat() ?: 50F
val secondariesMember = jsMove.get("secondaries")
val secondaryMember = jsMove.get("secondary")
if (secondariesMember != null && secondariesMember is JsonArray) {
@@ -92,7 +101,20 @@ object Moves : DataRegistry {
effectChances += secondaryMember.get("chance").asDouble
}
}
val move = MoveTemplate(id, num, elementalType, damageCategory, power, target, accuracy, pp, priority, critRatio, effectChances.toTypedArray())
val move = MoveTemplate(
name = id,
num = num,
elementalType = elementalType,
damageCategory = damageCategory,
power = power,
target = target,
accuracy = accuracy,
pp = pp,
priority = priority,
critRatio = critRatio,
effectChances = effectChances.toTypedArray(),
weight = weight
)
this.register(move)
} catch (e: Exception) {
Cobblemon.LOGGER.error("Caught exception trying to resolve the move '{}'", id, e)
@@ -113,7 +135,7 @@ object Moves : DataRegistry {
@JvmStatic
fun getByNameOrDummy(name: String) = allMoves[name.lowercase()] ?: MoveTemplate.dummy(name.lowercase())
@JvmStatic
fun getExceptional() = getByName("tackle") ?: allMoves.values.random()
fun getExceptional() = getByName("tackle") ?: allMoves.values.first()
@JvmStatic
fun count() = allMoves.size
@JvmStatic
@@ -129,5 +151,4 @@ object Moves : DataRegistry {
this.allMoves[move.name] = move
this.idMapping[move.num] = move
}
}
@@ -0,0 +1,38 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.moves
import com.cobblemon.mod.common.pokemon.FormData
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.resources.ResourceLocation
/**
* Builders a moveset for a particular [FormData] and level. Probably only really need [DefaultMovesetBuilder] though.
*
* @author Hiroku
* @since December 5th, 2025
*/
interface MovesetBuilder {
/** The ID of this builder. It's autofilled from the registry loader. */
var id: ResourceLocation
/** The subtype designation for this moveset builder. Generally unused once read from JSON. */
val type: ResourceLocation
/** Builds a brand new moveset for the [form] and [level]. */
fun build(form: FormData, level: Int): MoveSet
companion object {
/** Weight multiplier for moves that are in the form's signatureMoves list. Makes them way more likely to be chosen. */
var signatureMoveWeightMultiplier = 5F
/** Subtypes of MovesetBuilder. I personally doubt you'll need any other subtypes, but hey. */
val movesetBuilderTypes = mutableMapOf<ResourceLocation, Class<out MovesetBuilder>>(
cobblemonResource("default") to DefaultMovesetBuilder::class.java
)
}
}
@@ -8,12 +8,14 @@
package com.cobblemon.mod.common.api.npc
import com.cobblemon.mod.common.api.npc.partyproviders.ComposedPoolPartyProvider
import com.cobblemon.mod.common.api.npc.partyproviders.PoolPartyProvider
import com.cobblemon.mod.common.api.npc.partyproviders.ScriptPartyProvider
import com.cobblemon.mod.common.api.npc.partyproviders.SimplePartyProvider
import com.cobblemon.mod.common.api.storage.party.NPCPartyStore
import com.cobblemon.mod.common.entity.npc.NPCEntity
import com.google.gson.JsonElement
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
/**
@@ -25,14 +27,15 @@ import net.minecraft.server.level.ServerPlayer
*/
interface NPCPartyProvider {
companion object {
val types = mutableMapOf<String, (String) -> NPCPartyProvider>(
SimplePartyProvider.TYPE to { SimplePartyProvider() },
PoolPartyProvider.TYPE to { PoolPartyProvider() },
ScriptPartyProvider.TYPE to { ScriptPartyProvider() }
val types = mutableMapOf(
SimplePartyProvider.TYPE to ::SimplePartyProvider,
PoolPartyProvider.TYPE to ::PoolPartyProvider,
ScriptPartyProvider.TYPE to ::ScriptPartyProvider,
ComposedPoolPartyProvider.TYPE to ::ComposedPoolPartyProvider
)
}
val type: String
val type: ResourceLocation
val isStatic: Boolean
fun provide(npc: NPCEntity, level: Int, players: List<ServerPlayer> = emptyList()): NPCPartyStore
// Why did I opt for manual JSON loading??? I must have had a reason but I can't remember. Maybe for S2C? Use a codec doofus
@@ -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.api.npc
import com.bedrockk.molang.runtime.MoLangRuntime
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.cobblemonResource
import kotlin.random.Random
/**
* A kind of definition for how to compose a party of Pokémon given a [PartyPool].
*
* @author Hiroku
* @since December 7th, 2025
*/
class PartyComposition {
var id = cobblemonResource("dummy_composition")
// val displayName = "cobblemon.party_composition.$id" // dunno if i will use this yet
val scrambleOrder: Boolean = false
val slot1 = listOf<String>()
val slot2 = listOf<String>()
val slot3 = listOf<String>()
val slot4 = listOf<String>()
val slot5 = listOf<String>()
val slot6 = listOf<String>()
fun compose(pool: PartyPool, level: Int, aspects: Set<String>, desiredPokemonCount: Int, runtime: MoLangRuntime, random: Random = Random.Default): List<Pokemon> {
val chosenEntries = mutableListOf<PartyPool.PoolEntry>()
val availableEntries = pool.entries.filter { level in it.npcLevels.resolve(runtime) && (it.npcAspects.isEmpty() || it.npcAspects.all(aspects::contains)) }
for (labelSet in listOf(slot1, slot2, slot3, slot4, slot5, slot6)) {
pool.tryChoosingEntry(labelSet, runtime, random, availableEntries, chosenEntries)
if (chosenEntries.size >= desiredPokemonCount) {
break
}
}
val finalEntries = if (scrambleOrder) {
chosenEntries.shuffled(random)
} else {
chosenEntries
}
val pokemon = finalEntries.map { entry ->
val levelVariation = entry.levelVariation.resolve(runtime)
val lvlVariation = if (levelVariation.first == levelVariation.last) {
levelVariation.first
} else {
levelVariation.random(random)
}
val finalLevel = level + lvlVariation
val properties = entry.pokemon.copy()
properties.level = properties.level ?: finalLevel
val pokemon = properties.create()
entry.movesetBuilders.randomOrNull()?.let(pokemon::initializeMovesetFrom)
pokemon
}
return pokemon
}
}
@@ -0,0 +1,77 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.npc
import com.bedrockk.molang.runtime.MoLangRuntime
import com.cobblemon.mod.common.api.molang.ExpressionLike
import com.cobblemon.mod.common.api.moves.MovesetBuilder
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.util.ScriptableIntRange
import com.cobblemon.mod.common.util.asExpressionLike
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.toProperties
import com.cobblemon.mod.common.util.weightedSelection
import kotlin.random.Random
/**
* A pool of Pokémon to choose from, typically for generating a party for an NPC. The entries
* of the pool work on a weight and label system which is used as part of a [PartyComposition].
*
* @author Hiroku
* @since December 7th, 2025
*/
class PartyPool {
var id = cobblemonResource("dummy")
val displayName = "cobblemon.party_pool.$id"
val entries = mutableListOf<PoolEntry>()
class PoolEntry {
val pokemon: PokemonProperties = "pikachu".toProperties()
val labels: List<String> = emptyList()
val npcAspects: List<String> = emptyList()
val required: List<String> = emptyList()
val excluded: List<String> = emptyList()
val maxSelectableTimes: ExpressionLike = "6".asExpressionLike()
val levelVariation: ScriptableIntRange = ScriptableIntRange(0, 0)
val npcLevels: ScriptableIntRange = ScriptableIntRange(1, 100)
val movesetBuilders: List<MovesetBuilder> = emptyList()
val weight: ExpressionLike = "50".asExpressionLike()
}
/**
* Goes through each of the labels in order, trying to find a valid choice. Once it finds one, it skips the later
* labels and adds the value to [chosenEntries] and returns. If none of the labels could find a valid entry, nothing is added.
*/
fun tryChoosingEntry(
labels: List<String>,
runtime: MoLangRuntime,
random: Random,
availableEntries: List<PoolEntry>,
chosenEntries: MutableList<PoolEntry>,
) {
for (label in labels) {
val entries = availableEntries
.filter { label in it.labels || label.lowercase() == "any" }
.filter { entry ->
// Check required labels
entry.required.all { it in labels } &&
// Check excluded labels
entry.excluded.none { it in labels } &&
// Check max selectable times
chosenEntries.count { it == entry } < entry.maxSelectableTimes.resolveFloat(runtime)
}
if (entries.isNotEmpty()) {
val selectedEntry = entries.weightedSelection(random) { it.weight.resolveFloat(runtime) } ?: return
chosenEntries.add(selectedEntry)
return
}
}
}
}
@@ -0,0 +1,94 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.npc.partyproviders
import com.bedrockk.molang.Expression
import com.bedrockk.molang.runtime.MoLangRuntime
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.CobblemonPartyCompositions
import com.cobblemon.mod.common.CobblemonPartyPools
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.setup
import com.cobblemon.mod.common.api.npc.NPCPartyProvider
import com.cobblemon.mod.common.api.npc.PartyComposition
import com.cobblemon.mod.common.api.npc.PartyPool
import com.cobblemon.mod.common.api.storage.party.NPCPartyStore
import com.cobblemon.mod.common.entity.npc.NPCEntity
import com.cobblemon.mod.common.util.asArrayValue
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.resolveInt
import com.cobblemon.mod.common.util.withQueryValue
import com.google.gson.JsonElement
import com.google.gson.JsonObject
import kotlin.random.Random
import kotlin.ranges.random
import net.minecraft.server.level.ServerPlayer
/**
* An [NPCPartyProvider] which uses a [PartyComposition] and [PartyPool] to generate a party.
*
* There are controls for minimum and maximum Pokémon count, as well as whether to use a fixed random seed for the NPC.
*
* @author Hiroku
* @since December 8th, 2025
*/
class ComposedPoolPartyProvider : NPCPartyProvider {
companion object {
val TYPE = cobblemonResource("composed_pool")
}
override val type = TYPE
override var isStatic: Boolean = true
var useFixedRandom: Boolean = false
var minPokemon: Expression = "1".asExpression()
var maxPokemon: Expression = "6".asExpression()
lateinit var pool: PartyPool
lateinit var composition: PartyComposition
override fun loadFromJSON(json: JsonElement) {
json as JsonObject
isStatic = json.getAsJsonPrimitive("isStatic")?.asBoolean ?: true
useFixedRandom = json.getAsJsonPrimitive("useFixedRandom")?.asBoolean ?: false
pool = CobblemonPartyPools.partyPools[json.get("pool").asString.asIdentifierDefaultingNamespace()]
?: throw IllegalArgumentException("Unknown PartyPool id: ${json.get("pool").asString}")
composition = CobblemonPartyCompositions.partyCompositions[json.get("composition").asString.asIdentifierDefaultingNamespace()]
?: throw IllegalArgumentException("Unknown PartyComposition id: ${json.get("composition").asString}")
minPokemon = json.getAsJsonPrimitive("minPokemon").asString?.asExpression() ?: "1".asExpression()
maxPokemon = json.getAsJsonPrimitive("maxPokemon").asString?.asExpression() ?: "6".asExpression()
}
override fun provide(npc: NPCEntity, level: Int, players: List<ServerPlayer>): NPCPartyStore {
val runtime = MoLangRuntime().setup().withQueryValue("npc", npc.struct)
val random = if (useFixedRandom) Random(npc.uuid.hashCode()) else Random.Default
runtime.withQueryValue("level", DoubleValue(level))
runtime.withQueryValue("players", players.asArrayValue { it.asMoLangValue() })
if (players.size == 1) {
// This is for the convenience, most cases will be pvn one player
runtime.withQueryValue("player", players.first().asMoLangValue())
}
val minPokemon = runtime.resolveInt(this.minPokemon)
val maxPokemon = runtime.resolveInt(this.maxPokemon)
val desiredPokemonCount = (minPokemon..maxPokemon).random(random)
val pokemonList = composition.compose(
pool = pool,
level = level,
aspects = npc.aspects,
desiredPokemonCount = desiredPokemonCount,
runtime = runtime,
random = random
)
val partyStore = NPCPartyStore(npc)
pokemonList.forEach(partyStore::add)
return partyStore
}
}
@@ -48,7 +48,7 @@ import kotlin.random.Random
*/
class PoolPartyProvider : NPCPartyProvider {
companion object {
const val TYPE = "pool"
val TYPE = cobblemonResource("pool")
}
@Transient
@@ -150,10 +150,10 @@ class PoolPartyProvider : NPCPartyProvider {
val instance = selected.pokemon.copy().also { it.level = it.level ?: dictatedLevel ?: randomLevel }.create()
party.add(instance)
}
if (party.none()) {
LOGGER.error("${npc} has no Pokemon on Party")
}
if (party.none()) {
LOGGER.error("$npc had no Pokémon produced by their party provider")
}
}
override fun provide(npc: NPCEntity, level: Int, players: List<ServerPlayer>): NPCPartyStore {
@@ -28,7 +28,7 @@ import net.minecraft.server.level.ServerPlayer
class ScriptPartyProvider : NPCPartyProvider {
companion object {
const val TYPE = "script"
val TYPE = cobblemonResource("script")
}
override val type = TYPE
@@ -16,6 +16,7 @@ import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.storage.party.NPCPartyStore
import com.cobblemon.mod.common.entity.npc.NPCEntity
import com.cobblemon.mod.common.util.asArrayValue
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.getBooleanOrNull
import com.cobblemon.mod.common.util.toProperties
import com.google.gson.JsonElement
@@ -29,7 +30,7 @@ import net.minecraft.server.level.ServerPlayer
*/
class SimplePartyProvider : NPCPartyProvider {
companion object {
const val TYPE = "simple"
val TYPE = cobblemonResource("simple")
}
@Transient
@@ -143,6 +143,8 @@ object CobblemonPermissions {
val CHANGE_BOX_COUNT = this.create("${COMMAND_PREFIX}boxcount", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
@JvmStatic
val SPECTATE_BATTLE = this.create("${COMMAND_PREFIX}spectatebattle", PermissionLevel.ALL_COMMANDS)
@JvmStatic
val TECHNICAL_MACHINE = this.create("${COMMAND_PREFIX}technicalmachine", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
@JvmStatic
val SEE_HIDDEN_NPCS = this.create("seehiddennpcs", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
@@ -29,7 +29,6 @@ abstract class AbstractPokedexManager {
fun deleteSpeciesRecord(speciesId: ResourceLocation) {
speciesRecords.remove(speciesId)
markDirty()
}
fun deleteFormRecord(speciesId: ResourceLocation, formName: String) {
@@ -40,8 +39,6 @@ abstract class AbstractPokedexManager {
deleteSpeciesRecord(speciesId)
return
}
markDirty()
}
fun getSpeciesRecord(speciesId: ResourceLocation): SpeciesDexRecord? {
@@ -164,8 +161,4 @@ abstract class AbstractPokedexManager {
return newValue
}
}
open fun markDirty() {
// Save stuff
}
}
@@ -10,7 +10,9 @@ package com.cobblemon.mod.common.api.pokedex
import com.bedrockk.molang.runtime.struct.QueryStruct
import com.bedrockk.molang.runtime.struct.VariableStruct
import com.bedrockk.molang.runtime.value.DoubleValue
import com.bedrockk.molang.runtime.value.StringValue
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.events.CobblemonEvents
import com.cobblemon.mod.common.api.events.pokemon.PokedexDataChangedEvent
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
@@ -25,6 +27,7 @@ import com.google.common.collect.Sets
import com.mojang.datafixers.util.Either
import com.mojang.serialization.Codec
import com.mojang.serialization.codecs.ListCodec
import com.mojang.serialization.codecs.PrimitiveCodec
import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.network.RegistryFriendlyByteBuf
@@ -41,11 +44,13 @@ class FormDexRecord {
instance.group(
ListCodec(Codec.STRING, 0, 3).fieldOf("genders").forGetter { it.genders.map { it.name } },
ListCodec(Codec.STRING, 0, 2).fieldOf("seenShinyStates").forGetter { it.seenShinyStates.toList() },
PrimitiveCodec.INT.optionalFieldOf("highest_level", -1).forGetter { it.highestLevel },
Codec.STRING.fieldOf("knowledge").forGetter { it.knowledge.name }
).apply(instance) { genders, seenShinyStates, knowledge ->
).apply(instance) { genders, seenShinyStates, highestLevel, knowledge ->
FormDexRecord().also {
it.genders.addAll(genders.map(Gender::valueOf))
it.seenShinyStates.addAll(seenShinyStates)
it.highestLevel = highestLevel
it.knowledge = PokedexEntryProgress.valueOf(knowledge)
}
}
@@ -59,6 +64,9 @@ class FormDexRecord {
private val seenShinyStates =
mutableSetOf<String>() // consider: radiants in the future (radiants should just be a resource pack tbh)
var highestLevel = -1
private set
/** The current awareness of the form that the dex has. */
var knowledge = PokedexEntryProgress.NONE
private set
@@ -80,11 +88,17 @@ class FormDexRecord {
struct = QueryStruct(hashMapOf())
.addFunction("data") { data }
.addFunction("knowledge") { StringValue(knowledge.name) }
.addFunction("has_seen_gender") { params -> genders.contains(Gender.valueOf(params.getString(0).uppercase())) }
.addFunction("has_seen_gender") { params -> DoubleValue(genders.contains(Gender.valueOf(params.getString(0).uppercase()))) }
.addFunction("highest_level") { _ -> DoubleValue(highestLevel) }
.addFunction("set_highest_level") { params ->
highestLevel = maxOf(highestLevel, params.getDouble(0).toInt())
DoubleValue.ONE
}
}
fun clone() = FormDexRecord().also {
it.genders.addAll(genders)
it.highestLevel = highestLevel
it.seenShinyStates.addAll(seenShinyStates)
it.knowledge = knowledge
}
@@ -114,7 +128,7 @@ class FormDexRecord {
fun addAllShinyStatesAndGenders() {
val form = PokemonSpecies.getByIdentifier(speciesDexRecord.id)?.getFormByName(formName)
genders.addAll(form?.possibleGenders ?: listOf(Gender.MALE, Gender.FEMALE))
highestLevel = Cobblemon.config.maxPokemonLevel
seenShinyStates.addAll(listOf("shiny", "normal"))
speciesDexRecord.onFormRecordUpdated(this)
}
@@ -135,6 +149,9 @@ class FormDexRecord {
),
ifSucceeded = {
genders.add(pokedexEntityData.pokemon.gender)
if (knowledge == PokedexEntryProgress.CAUGHT) {
highestLevel = maxOf(highestLevel, pokedexEntityData.pokemon.level)
}
seenShinyStates.add(if (pokedexEntityData.pokemon.shiny) "shiny" else "normal")
if (knowledge.ordinal > this.knowledge.ordinal) {
this.knowledge = knowledge
@@ -159,6 +176,7 @@ class FormDexRecord {
|| (pokedexEntityData.pokemon.shiny && "shiny" !in seenShinyStates)
|| (!pokedexEntityData.pokemon.shiny && "normal" !in seenShinyStates)
|| knowledge.ordinal > this.knowledge.ordinal
|| (highestLevel < pokedexEntityData.pokemon.level && knowledge == PokedexEntryProgress.CAUGHT)
|| speciesDexRecord.wouldBeDifferent(pokedexEntityData)
}
@@ -9,6 +9,8 @@
package com.cobblemon.mod.common.api.pokedex
import com.cobblemon.mod.common.CobblemonNetwork.sendPacket
import com.cobblemon.mod.common.api.scheduling.ScheduledTask
import com.cobblemon.mod.common.api.scheduling.ServerTaskTracker
import com.cobblemon.mod.common.api.storage.player.InstancedPlayerData
import com.cobblemon.mod.common.api.storage.player.PlayerInstancedDataStoreTypes
import com.cobblemon.mod.common.api.storage.player.client.ClientPokedexManager
@@ -48,10 +50,44 @@ class PokedexManager(
fun obtain(pokemon: Pokemon) {
val speciesId = pokemon.species.resourceIdentifier
val formName = pokemon.form.name
getOrCreateSpeciesRecord(speciesId).getOrCreateFormRecord(formName).caught(PokedexEntityData(pokemon = pokemon, disguise = null))
getOrCreateSpeciesRecord(speciesId).getOrCreateFormRecord(formName).obtained(PokedexEntityData(pokemon = pokemon, disguise = null))
}
override fun markDirty() {
fun scheduleFullSyncFromStores(
party: Iterable<Pokemon?>,
pc: Iterable<Pokemon?>,
batchSize: Int = 50
) {
val iterator = sequence {
for (pokemon in party) {
if (pokemon != null) yield(pokemon)
}
for (pokemon in pc) {
if (pokemon != null) yield(pokemon)
}
}.iterator()
if (!iterator.hasNext()) {
return
}
ScheduledTask.Builder()
.tracker(ServerTaskTracker)
.interval(0F)
.infiniteIterations()
.execute { task ->
var processed = 0
while (processed < batchSize && iterator.hasNext()) {
val pokemon = iterator.next()
obtain(pokemon)
processed++
}
if (!iterator.hasNext()) {
task.expire()
}
}
.build()
}
override fun initialize() {
@@ -26,7 +26,8 @@ import net.minecraft.resources.ResourceLocation
/**
* Dex recorded information about a particular Pokémon species. This includes all forms and their information.
* Across all forms there is a single list of known aspects which is generally used to track cosmetic variation
* though technically things like gender and form-motivating aspects will get collected inadvertently.
* though technically things like gender and form-motivating aspects will get collected inadvertently. The highest
* level of the species that the player has owned is also tracked.
*
* @author Hiroku
* @since August 23rd, 2024
@@ -36,10 +37,12 @@ class SpeciesDexRecord {
val CODEC: Codec<SpeciesDexRecord> = RecordCodecBuilder.create { instance ->
instance.group(
ListCodec(PrimitiveCodec.STRING, 0, 512).fieldOf("aspects").forGetter { it.aspects.toList() },
PrimitiveCodec.INT.optionalFieldOf("highestLevel", -1).forGetter { it.highestLevel },
Codec.unboundedMap(Codec.STRING, FormDexRecord.CODEC).fieldOf("formRecords").forGetter { it.formRecords }
).apply(instance) { aspects, formRecords ->
).apply(instance) { aspects, highestLevel, formRecords ->
SpeciesDexRecord().also {
it.aspects.addAll(aspects)
it.highestLevel = highestLevel
it.formRecords.putAll(formRecords)
}
}
@@ -48,13 +51,16 @@ class SpeciesDexRecord {
@Transient
lateinit var id: ResourceLocation
/** The highest level of the species that the player has owned. */
var highestLevel = -1
private set
private val aspects: MutableSet<String> = mutableSetOf()
private val formRecords: MutableMap<String, FormDexRecord> = mutableMapOf()
val isFormRecordsEmpty: Boolean
get() = formRecords.isEmpty()
fun describe(): String {
return "SpeciesDexRecord(aspects=$aspects, formRecords=$formRecords)"
return "SpeciesDexRecord(aspects=$aspects, highestLevel=$highestLevel, formRecords=$formRecords)"
}
@Transient
@@ -77,7 +83,14 @@ class SpeciesDexRecord {
.addFunction("is_caught") { params ->
DoubleValue(formRecords.values.any { it.knowledge.name.equals("CAUGHT", ignoreCase = true) })
}
// more, also worth moving to something like MoLangFunctions.kt so it's easier to API in more values
.addFunction("highest_level") { params ->
DoubleValue(highestLevel)
}
.addFunction("set_highest_level") { params ->
highestLevel = params.getDouble(0).toInt()
DoubleValue.ONE
}
// more, also worth moving to something like MoLangFunctions.kt so it's easier to API in more values
@Transient
lateinit var pokedexManager: AbstractPokedexManager
@@ -94,10 +107,16 @@ class SpeciesDexRecord {
fun addInformation(pokemon: Pokemon, knowledge: PokedexEntryProgress) {
aspects.addAll(pokemon.aspects)
if (knowledge == PokedexEntryProgress.CAUGHT) {
highestLevel = maxOf(highestLevel, pokemon.level)
}
}
fun addInformation(pokedexEntityData: PokedexEntityData, knowledge: PokedexEntryProgress) {
aspects.addAll(pokedexEntityData.pokemon.aspects)
if (knowledge == PokedexEntryProgress.CAUGHT) {
highestLevel = maxOf(highestLevel, pokedexEntityData.pokemon.level)
}
}
fun addAspects(addedAspects: Set<String>) {
@@ -128,6 +147,7 @@ class SpeciesDexRecord {
fun clone() = SpeciesDexRecord().also {
it.aspects.addAll(aspects)
it.highestLevel = highestLevel
it.formRecords.putAll(formRecords.mapValues { it.value.clone() })
}
@@ -139,6 +159,7 @@ class SpeciesDexRecord {
fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeCollection(aspects) { _, it -> buffer.writeString(it) }
buffer.writeInt(highestLevel)
buffer.writeInt(formRecords.size)
for ((formName, formRecord) in formRecords) {
buffer.writeString(formName)
@@ -149,6 +170,7 @@ class SpeciesDexRecord {
fun decode(buffer: RegistryFriendlyByteBuf) {
aspects.clear()
aspects.addAll(buffer.readCollection(Sets::newHashSetWithExpectedSize) { buffer.readString() })
highestLevel = buffer.readInt()
formRecords.clear()
val numForms = buffer.readInt()
for (i in 0 until numForms) {
@@ -11,5 +11,6 @@ package com.cobblemon.mod.common.api.pokedex.filter
enum class SearchByType {
SPECIES,
ABILITIES,
MOVES,
DROPS;
}
@@ -9,14 +9,22 @@
package com.cobblemon.mod.common.api.pokedex.filter
import com.cobblemon.mod.common.api.drop.ItemDropEntry
import com.cobblemon.mod.common.api.moves.MoveTemplate
import com.cobblemon.mod.common.api.pokedex.AbstractPokedexManager
import com.cobblemon.mod.common.api.pokedex.PokedexEntryProgress
import com.cobblemon.mod.common.api.pokedex.entry.PokedexEntry
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.api.pokemon.evolution.Evolution
import com.cobblemon.mod.common.api.storage.player.client.ClientPokedexManager
import com.cobblemon.mod.common.api.tms.TechnicalMachines
import com.cobblemon.mod.common.client.CobblemonClient
import com.cobblemon.mod.common.pokemon.abilities.HiddenAbility
import com.cobblemon.mod.common.pokemon.FormData
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import com.cobblemon.mod.common.util.asTranslated
import com.cobblemon.mod.common.util.itemRegistry
import net.minecraft.client.Minecraft
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.item.ItemStack
/**
@@ -47,6 +55,23 @@ class SearchFilter(val pokedexManager: AbstractPokedexManager, val searchString:
}
return abilityList.any { it.contains(searchString.trim().lowercase()) }
}
SearchByType.MOVES -> {
if (pokedexManager.getHighestKnowledgeFor(entry) !== PokedexEntryProgress.CAUGHT) return false
val search = searchString.trim().lowercase()
val learnedTMs = CobblemonClient.clientTMMoveData.learnedTMs
val forms = pokedexManager.getCaughtForms(entry)
val formData = if (forms.isEmpty()) {
listOf(species.standardForm)
} else {
forms.map { form ->
species.forms.find { it.name.equals(form.displayForm, ignoreCase = true) } ?: species.standardForm
}
}
return formData.any { form ->
val highestLevel = pokedexManager.getSpeciesRecord(form.species.resourceIdentifier)?.getFormRecord(form.name)?.highestLevel ?: -1
hasDiscoveredMove(form, search, highestLevel, pokedexManager, learnedTMs)
}
}
SearchByType.DROPS -> {
if (pokedexManager.getHighestKnowledgeFor(entry) !== PokedexEntryProgress.CAUGHT) return false
val dropsList = mutableListOf<String>()
@@ -68,4 +93,132 @@ class SearchFilter(val pokedexManager: AbstractPokedexManager, val searchString:
}
}
}
private fun hasDiscoveredMove(
form: FormData,
search: String,
highestLevel: Int,
pokedex: AbstractPokedexManager,
learnedTMs: Set<ResourceLocation>
): Boolean {
val evolutionMoveLevels = buildEvolutionMoveLevelIndex(form)
fun matches(move: MoveTemplate): Boolean {
return move.displayName.string.lowercase().contains(search)
}
fun isLevelUpDiscovered(move: MoveTemplate, level: Int): Boolean {
if (highestLevel >= level) return true
for ((evolutionSpeciesId, moveLevels) in evolutionMoveLevels) {
val evolutionLevel = moveLevels[move.name] ?: continue
val evolutionHighest = pokedex.getSpeciesRecord(evolutionSpeciesId)?.highestLevel ?: -1
if (evolutionHighest >= evolutionLevel) return true
}
return false
}
form.moves.levelUpMoves.forEach { (level, moves) ->
for (move in moves) {
if (matches(move) && isLevelUpDiscovered(move, level)) return true
}
}
form.moves.tmMoves.forEach { move ->
val tmId = TechnicalMachines.moveToTM[move]?.id
val discovered = tmId == null || tmId in learnedTMs
if (discovered && matches(move)) return true
}
val alwaysDiscovered = sequenceOf(
form.moves.tutorMoves,
form.moves.eggMoves,
form.moves.evolutionMoves,
form.moves.formChangeMoves,
form.moves.specialMoves,
form.moves.legacyMoves
)
for (moves in alwaysDiscovered) {
for (move in moves) {
if (matches(move)) return true
}
}
return false
}
private fun buildEvolutionMoveLevelIndex(form: FormData): Map<ResourceLocation, Map<String, Int>> {
val evolutionForms = collectEvolutionForms(form)
val levelsBySpecies = mutableMapOf<ResourceLocation, MutableMap<String, Int>>()
for (evolutionForm in evolutionForms) {
val speciesId = evolutionForm.species.resourceIdentifier
val moveLevels = levelsBySpecies.getOrPut(speciesId) { mutableMapOf() }
buildMoveLevelIndex(evolutionForm).forEach { (moveName, level) ->
val current = moveLevels[moveName]
if (current == null || level < current) {
moveLevels[moveName] = level
}
}
}
return levelsBySpecies
}
private fun buildMoveLevelIndex(form: FormData): Map<String, Int> {
val levels = mutableMapOf<String, Int>()
form.moves.levelUpMoves.forEach { (level, moves) ->
moves.forEach { move ->
val current = levels[move.name]
if (current == null || level < current) {
levels[move.name] = level
}
}
}
return levels
}
private fun collectEvolutionForms(rootForm: FormData): List<FormData> {
val results = mutableListOf<FormData>()
val visited = mutableSetOf<String>()
fun key(form: FormData): String {
return "${form.species.resourceIdentifier}|${form.name.lowercase()}"
}
fun nextEvolutions(current: FormData): Set<Evolution> {
return if (current.evolutions.isNotEmpty()) {
current.evolutions
} else {
current.species.evolutions
}
}
fun traverse(current: FormData) {
for (evolution in nextEvolutions(current)) {
val evolutionForm = resolveEvolutionForm(evolution) ?: continue
val evolutionKey = key(evolutionForm)
if (!visited.add(evolutionKey)) continue
results.add(evolutionForm)
traverse(evolutionForm)
}
}
traverse(rootForm)
return results
}
private fun resolveEvolutionForm(evolution: Evolution): FormData? {
val speciesId = evolution.result.species?.asIdentifierDefaultingNamespace() ?: return null
val species = PokemonSpecies.getByIdentifier(speciesId) ?: return null
val formId = evolution.result.form
return if (formId == null) {
species.standardForm
} else {
species.forms.firstOrNull {
it.formOnlyShowdownId().equals(formId, ignoreCase = true) || it.name.equals(formId, ignoreCase = true)
} ?: species.standardForm
}
}
}
@@ -0,0 +1,166 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.pokedex.filter
import com.cobblemon.mod.common.api.moves.MoveTemplate
import com.cobblemon.mod.common.api.pokedex.AbstractPokedexManager
import com.cobblemon.mod.common.api.pokedex.PokedexEntryProgress
import com.cobblemon.mod.common.api.pokedex.entry.PokedexEntry
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.api.pokemon.evolution.Evolution
import com.cobblemon.mod.common.api.tms.TechnicalMachines
import com.cobblemon.mod.common.client.CobblemonClient
import com.cobblemon.mod.common.pokemon.FormData
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import net.minecraft.resources.ResourceLocation
enum class SecondaryPokedexFilterType {
ALL,
CAUGHT,
SEEN,
UNDISCOVERED,
LEVEL_UP_TM_UNDISCOVERED
}
class SecondaryPokedexFilter(
private val pokedexManager: AbstractPokedexManager,
private val filterType: SecondaryPokedexFilterType
) : EntryFilter() {
override fun test(entry: PokedexEntry): Boolean {
return when (filterType) {
SecondaryPokedexFilterType.ALL -> true
SecondaryPokedexFilterType.CAUGHT -> pokedexManager.getHighestKnowledgeFor(entry) >= PokedexEntryProgress.CAUGHT
SecondaryPokedexFilterType.SEEN -> pokedexManager.getHighestKnowledgeFor(entry) == PokedexEntryProgress.ENCOUNTERED
SecondaryPokedexFilterType.UNDISCOVERED -> pokedexManager.getHighestKnowledgeFor(entry) == PokedexEntryProgress.NONE
SecondaryPokedexFilterType.LEVEL_UP_TM_UNDISCOVERED -> hasUndiscoveredLevelUpTM(entry)
}
}
private fun hasUndiscoveredLevelUpTM(entry: PokedexEntry): Boolean {
if (pokedexManager.getHighestKnowledgeFor(entry) != PokedexEntryProgress.CAUGHT) return false
val species = PokemonSpecies.getByIdentifier(entry.speciesId) ?: return false
val forms = pokedexManager.getCaughtForms(entry)
val formData = if (forms.isEmpty()) {
listOf(species.standardForm)
} else {
forms.map { form ->
species.forms.find { it.name.equals(form.displayForm, ignoreCase = true) } ?: species.standardForm
}
}
return formData.any { form ->
val highestLevel = pokedexManager.getSpeciesRecord(form.species.resourceIdentifier)?.getFormRecord(form.name)?.highestLevel ?: -1
hasUndiscoveredLevelUpTM(form, highestLevel)
}
}
private fun hasUndiscoveredLevelUpTM(
form: FormData,
highestLevel: Int
): Boolean {
val evolutionMoveLevels = buildEvolutionMoveLevelIndex(form)
fun isLevelUpDiscovered(move: MoveTemplate, level: Int): Boolean {
if (highestLevel >= level) return true
for ((evolutionSpeciesId, moveLevels) in evolutionMoveLevels) {
val evolutionLevel = moveLevels[move.name] ?: continue
val evolutionHighest = pokedexManager.getSpeciesRecord(evolutionSpeciesId)?.highestLevel ?: -1
if (evolutionHighest >= evolutionLevel) return true
}
return false
}
form.moves.levelUpMoves.forEach { (level, moves) ->
for (move in moves) {
if (TechnicalMachines.moveToTM[move] != null && !isLevelUpDiscovered(move, level)) {
return true
}
}
}
return false
}
private fun buildEvolutionMoveLevelIndex(form: FormData): Map<ResourceLocation, Map<String, Int>> {
val evolutionForms = collectEvolutionForms(form)
val levelsBySpecies = mutableMapOf<ResourceLocation, MutableMap<String, Int>>()
for (evolutionForm in evolutionForms) {
val speciesId = evolutionForm.species.resourceIdentifier
val moveLevels = levelsBySpecies.getOrPut(speciesId) { mutableMapOf() }
buildMoveLevelIndex(evolutionForm).forEach { (moveName, level) ->
val current = moveLevels[moveName]
if (current == null || level < current) {
moveLevels[moveName] = level
}
}
}
return levelsBySpecies
}
private fun buildMoveLevelIndex(form: FormData): Map<String, Int> {
val levels = mutableMapOf<String, Int>()
form.moves.levelUpMoves.forEach { (level, moves) ->
moves.forEach { move ->
val current = levels[move.name]
if (current == null || level < current) {
levels[move.name] = level
}
}
}
return levels
}
private fun collectEvolutionForms(rootForm: FormData): List<FormData> {
val results = mutableListOf<FormData>()
val visited = mutableSetOf<String>()
fun key(form: FormData): String {
return "${form.species.resourceIdentifier}|${form.name.lowercase()}"
}
fun nextEvolutions(current: FormData): Set<Evolution> {
return if (current.evolutions.isNotEmpty()) {
current.evolutions
} else {
current.species.evolutions
}
}
fun traverse(current: FormData) {
for (evolution in nextEvolutions(current)) {
val evolutionForm = resolveEvolutionForm(evolution) ?: continue
val evolutionKey = key(evolutionForm)
if (!visited.add(evolutionKey)) continue
results.add(evolutionForm)
traverse(evolutionForm)
}
}
traverse(rootForm)
return results
}
private fun resolveEvolutionForm(evolution: Evolution): FormData? {
val speciesId = evolution.result.species?.asIdentifierDefaultingNamespace() ?: return null
val species = PokemonSpecies.getByIdentifier(speciesId) ?: return null
val formId = evolution.result.form
return if (formId == null) {
species.standardForm
} else {
species.forms.firstOrNull {
it.formOnlyShowdownId().equals(formId, ignoreCase = true) || it.name.equals(formId, ignoreCase = true)
} ?: species.standardForm
}
}
}
@@ -10,6 +10,7 @@ package com.cobblemon.mod.common.api.pokemon
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.Cobblemon.config
import com.cobblemon.mod.common.CobblemonMovesetBuilders
import com.cobblemon.mod.common.api.abilities.Abilities
import com.cobblemon.mod.common.api.abilities.Ability
import com.cobblemon.mod.common.api.events.CobblemonEvents
@@ -148,6 +149,13 @@ open class PokemonProperties {
props.originalTrainerType = OriginalTrainerType.entries.toList().parsePropertyOfCollection(keyPairs, listOf("originaltrainertype", "ottype"), labelsOptional = true) { it.name.lowercase() }
props.originalTrainer = parsePlayerProperty(keyPairs, listOf("originaltrainer", "ot"))
props.moves = parseString(keyPairs, listOf("moves"))?.split(",")
props.movesetBuilders = parseString(keyPairs, listOf("moveset_builders", "movesetbuilders", "moveset_builder", "movesetbuilder"))?.split(",")?.mapNotNull {
try {
it.asIdentifierDefaultingNamespace()
} catch (_: ResourceLocationException) {
null
}
}
props.heldItem = parseString(keyPairs, listOf("helditem", "held_item"))
props.minPerfectIVs = parseIntProperty(keyPairs, listOf("min_perfect_ivs"))?.coerceIn(0, Stats.PERMANENT.size)
props.scaleModifier = parseFloatProperty(keyPairs, listOf("scale_modifier"))
@@ -356,6 +364,7 @@ open class PokemonProperties {
var originalTrainerType: OriginalTrainerType? = null
var originalTrainer: String? = null // Original Trainer by Username or UUID
var moves: List<String>? = null
var movesetBuilders: List<ResourceLocation>? = null
var heldItem: String? = null
var cosmeticItem: String? = null
var scaleModifier: Float? = null
@@ -462,7 +471,7 @@ open class PokemonProperties {
}
moves?.let { moves ->
if (pokemon.moveSet.getMoves().isEmpty()) {
pokemon.initializeMoveset(preferLatest = true)
pokemon.initializeMovesetFromDefault()
}
val moveTemplates = moves.mapNotNull(Moves::getByName).shuffled()
val replaceableIndices = (0..3).filterNot { pokemon.moveSet[it]?.template?.let(moveTemplates::contains) == true }.shuffled()
@@ -475,6 +484,13 @@ open class PokemonProperties {
pokemon.moveSet.update()
}
}
movesetBuilders?.let { movesetBuilders ->
val builders = movesetBuilders.mapNotNull { CobblemonMovesetBuilders.movesetBuilders[it] }
if (builders.isNotEmpty()) {
val movesetBuilder = builders.random()
pokemon.initializeMovesetFrom(movesetBuilder)
}
}
heldItem?.let { itemKey ->
val server = server() ?: return@let
val parser = ItemParser(server.registryAccess())
@@ -678,6 +694,7 @@ open class PokemonProperties {
originalTrainerType?.let { nbt.putInt(DataKeys.POKEMON_ORIGINAL_TRAINER_TYPE, it.ordinal) }
originalTrainer?.let { nbt.putString(DataKeys.POKEMON_ORIGINAL_TRAINER, it) }
moves?.let { nbt.putString(DataKeys.POKEMON_PROPERTIES_MOVES, it.joinToString(separator = ",")) }
movesetBuilders?.let { nbt.putString(DataKeys.POKEMON_PROPERTIES_MOVESET_BUILDERS, it.joinToString(separator = ",") { source -> source.toString() }) }
heldItem?.let {nbt.putString(DataKeys.POKEMON_PROPERTIES_HELDITEM, it)}
scaleModifier?.let { nbt.putFloat(DataKeys.POKEMON_SCALE_MODIFIER, it) }
val custom = ListTag()
@@ -712,6 +729,7 @@ open class PokemonProperties {
originalTrainerType = if (tag.contains(DataKeys.POKEMON_ORIGINAL_TRAINER_TYPE)) OriginalTrainerType.valueOf(tag.getString(DataKeys.POKEMON_ORIGINAL_TRAINER_TYPE)) else null
originalTrainer = if (tag.contains(DataKeys.POKEMON_ORIGINAL_TRAINER)) tag.getString(DataKeys.POKEMON_ORIGINAL_TRAINER) else null
moves = if (tag.contains(DataKeys.POKEMON_PROPERTIES_MOVES)) tag.getString(DataKeys.POKEMON_PROPERTIES_MOVES).split(",") else null
movesetBuilders = if (tag.contains(DataKeys.POKEMON_PROPERTIES_MOVESET_BUILDERS)) tag.getString(DataKeys.POKEMON_PROPERTIES_MOVESET_BUILDERS).split(",").map { it.asIdentifierDefaultingNamespace() } else null
heldItem = if (tag.contains(DataKeys.POKEMON_PROPERTIES_HELDITEM)) tag.getString(DataKeys.POKEMON_PROPERTIES_HELDITEM) else null
scaleModifier = if (tag.contains(DataKeys.POKEMON_SCALE_MODIFIER)) tag.getFloat(DataKeys.POKEMON_SCALE_MODIFIER) else null
val custom = tag.getList(DataKeys.POKEMON_PROPERTIES_CUSTOM, Tag.TAG_STRING.toInt())
@@ -747,6 +765,7 @@ open class PokemonProperties {
originalTrainerType?.let { json.addProperty(DataKeys.POKEMON_ORIGINAL_TRAINER_TYPE, it.name) }
originalTrainer?.let { json.addProperty(DataKeys.POKEMON_ORIGINAL_TRAINER, it) }
moves?.let { json.addProperty(DataKeys.POKEMON_PROPERTIES_MOVES, it.joinToString(separator = ",")) }
movesetBuilders?.let { json.addProperty(DataKeys.POKEMON_PROPERTIES_MOVESET_BUILDERS, it.joinToString(separator = ",") { source -> source.toString() }) }
heldItem?.let {json.addProperty(DataKeys.POKEMON_PROPERTIES_HELDITEM, it)}
scaleModifier?.let { json.addProperty(DataKeys.POKEMON_SCALE_MODIFIER, it) }
val custom = JsonArray()
@@ -781,6 +800,7 @@ open class PokemonProperties {
originalTrainerType = json.get(DataKeys.POKEMON_ORIGINAL_TRAINER_TYPE)?.asString?.let { OriginalTrainerType.valueOf(it) }
originalTrainer = json.get(DataKeys.POKEMON_ORIGINAL_TRAINER)?.asString
moves = json.get(DataKeys.POKEMON_PROPERTIES_MOVES)?.asString?.split(",")
movesetBuilders = json.get(DataKeys.POKEMON_PROPERTIES_MOVESET_BUILDERS)?.asString?.split(",")?.map { it.asIdentifierDefaultingNamespace() }
heldItem = json.get(DataKeys.POKEMON_PROPERTIES_HELDITEM)?.asString
scaleModifier = json.get(DataKeys.POKEMON_SCALE_MODIFIER)?.asFloat
val custom = json.get(DataKeys.POKEMON_PROPERTIES_CUSTOM)?.asJsonArray
@@ -820,6 +840,7 @@ open class PokemonProperties {
originalTrainer?.let { pieces.add("originaltrainer=$it") }
customProperties.forEach { pieces.add(it.asString()) }
moves?.let { pieces.add("moves=${it.joinToString(separator = ",")}") }
movesetBuilders?.let { pieces.add("movesetbuilders=${it.joinToString(separator = ",") { source -> source.toString() }}") }
heldItem?.let {pieces.add("helditem=$it")}
scaleModifier?.let { pieces.add("scale_modifier=$it") }
return pieces.joinToString(separator)
@@ -23,6 +23,7 @@ import com.cobblemon.mod.common.api.drop.ItemDropMethod
import com.cobblemon.mod.common.api.entity.EntityDimensionsAdapter
import com.cobblemon.mod.common.api.molang.ExpressionLike
import com.cobblemon.mod.common.api.moves.MoveTemplate
import com.cobblemon.mod.common.api.moves.MovesetBuilder
import com.cobblemon.mod.common.api.moves.adapters.MoveTemplateAdapter
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies.getByIdentifier
@@ -131,6 +132,7 @@ object PokemonSpecies : JsonDataRegistry<Species> {
.registerTypeAdapter(RidingBehaviourSettings::class.java, RidingBehaviourSettingsAdapter)
.registerTypeAdapter(RideSoundSettingsList::class.java, RideSoundSettingsListAdapter)
.registerTypeAdapter(ObtainableItemCondition::class.java, ObtainableItemConditionAdapter)
.registerTypeAdapter(MovesetBuilder::class.java, MovesetBuilderReferenceAdapter)
.disableHtmlEscaping()
.enableComplexMapKeySerialization()
.create()
@@ -8,6 +8,7 @@
package com.cobblemon.mod.common.api.pokemon.moves
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.data.ClientDataSynchronizer
import com.cobblemon.mod.common.api.moves.MoveTemplate
import com.cobblemon.mod.common.api.moves.Moves
@@ -19,9 +20,7 @@ import com.google.gson.JsonElement
import net.minecraft.network.RegistryFriendlyByteBuf
open class Learnset : ClientDataSynchronizer<Learnset> {
class Interpreter(
val loadMove: (JsonElement, Learnset) -> Boolean
) {
class Interpreter(val loadMove: (JsonElement, Learnset) -> Boolean) {
companion object {
fun parseFromPrefixIntoList(prefix: String, list: (Learnset) -> MutableList<MoveTemplate>): Interpreter {
return Interpreter { element, learnset ->
@@ -73,8 +72,7 @@ open class Learnset : ClientDataSynchronizer<Learnset> {
eggInterpreter,
tutorInterpreter,
legacyInterpreter,
specialInterpreter,
levelUpInterpreter,
specialInterpreter, levelUpInterpreter,
formChangeInterpreter
)
}
@@ -99,6 +97,20 @@ open class Learnset : ClientDataSynchronizer<Learnset> {
.flatMap { it.value }
.toSet()
fun tmLearnableMoves(): MutableList<MoveTemplate> {
val moves = mutableListOf<MoveTemplate>()
moves.addAll(tmMoves)
moves.addAll(tutorMoves)
moves.addAll(eggMoves)
moves.addAll(getLevelUpMovesUpTo(Cobblemon.config.maxPokemonLevel))
moves.addAll(evolutionMoves)
moves.addAll(formChangeMoves)
return moves
}
fun getAllLegalMoves(): Set<MoveTemplate> {
return levelUpMoves.values.flatten().toSet() +
eggMoves +
@@ -44,7 +44,7 @@ import kotlin.random.Random
* @since July 10th, 2022
*/
open class FlatSelector : SpawningSelector<FlatSelector.SpawnablePositionSelectionData> {
class SelectingSpawnInformation {
class SelectingSpawnInformation(val spawnDetail: SpawnDetail) {
val spawnablePositions = mutableMapOf<SpawnablePosition, Float>()
var highestWeight = 0F
@@ -58,61 +58,81 @@ open class FlatSelector : SpawningSelector<FlatSelector.SpawnablePositionSelecti
fun chooseSpawnablePosition() = spawnablePositions.entries.weightedSelection { it.value }!!.key
}
class SpawnablePositionSelectionData(
val spawnToSpawnablePosition: MutableMap<SpawnDetail, SelectingSpawnInformation>,
var percentSum: Float
): SpawnSelectionData {
val size: Int
get() = spawnToSpawnablePosition.size
class SpawnablePositionBucketSelectionData(
val selectingSpawnInformation: MutableList<SelectingSpawnInformation>
) {
var percentSum: Float = 0F
val seenSpawns = mutableListOf<SpawnDetail>()
fun add(spawnDetail: SpawnDetail, spawnablePosition: SpawnablePosition) {
// Only add to percentSum if this is the first time we've seen this SpawnDetail, otherwise
// the percentage will get amplified for every spawnable position the thing was possible, completely
// ruining the point of this pre-selection percentage.
if (spawnDetail.percentage > 0 && spawnDetail !in seenSpawns) {
seenSpawns.add(spawnDetail)
percentSum += spawnDetail.percentage
}
val spawnInformation = selectingSpawnInformation.find { it.spawnDetail == spawnDetail }
?: SelectingSpawnInformation(spawnDetail).also { selectingSpawnInformation.add(it) }
spawnInformation.add(spawnablePosition, spawnablePosition.getWeight(spawnDetail))
}
fun remove(spawnInformation: SelectingSpawnInformation) {
selectingSpawnInformation -= spawnInformation
if (spawnInformation.spawnDetail.percentage > 0) {
percentSum -= spawnInformation.spawnDetail.percentage
}
}
}
class SpawnablePositionSelectionData(
val spawner: Spawner,
val spawnablePositions: List<SpawnablePosition>,
val selectingSpawnInformation: MutableMap<SpawnBucket, SpawnablePositionBucketSelectionData>,
): SpawnSelectionData {
override val spawnActions = mutableListOf<SpawnAction<*>>()
override val context = mutableMapOf<String, Any>()
fun getDataForBucket(bucket: SpawnBucket): SpawnablePositionBucketSelectionData {
val existing = selectingSpawnInformation[bucket]
if (existing != null) {
return existing
}
val bucketSpawnInformation = SpawnablePositionBucketSelectionData(mutableListOf())
spawnablePositions.forEach { spawnablePosition ->
spawner.getMatchingSpawns(bucket, spawnablePosition).forEach {
bucketSpawnInformation.add(it, spawnablePosition)
}
}
selectingSpawnInformation[bucket] = bucketSpawnInformation
return bucketSpawnInformation
}
override fun removeSpawnDetails(shouldRemove: (SpawnDetail) -> Boolean) {
val toRemove = spawnToSpawnablePosition.entries.filter { shouldRemove(it.key) }
toRemove.forEach { spawnToSpawnablePosition.remove(it.key) }
percentSum -= toRemove.sumOf { it.key.percentage.toDouble().takeIf { it > 0 } ?: 0.0 }.toFloat()
selectingSpawnInformation.flatMap { it.value.selectingSpawnInformation.filter { shouldRemove(it.spawnDetail) } }
.forEach { selectingSpawnInformation[it.spawnDetail.bucket]?.remove(it) }
}
override fun removeSpawnablePositions(shouldRemove: (SpawnDetail, SpawnablePosition) -> Boolean) {
val toRemove = spawnToSpawnablePosition.entries.filter { (spawnDetail, positionData) ->
positionData.spawnablePositions.removeIf { shouldRemove(spawnDetail, it.key) }
val toRemove = selectingSpawnInformation.flatMap { it.value.selectingSpawnInformation }.filter { positionData ->
positionData.spawnablePositions.removeIf { shouldRemove(positionData.spawnDetail, it.key) }
positionData.highestWeight = positionData.spawnablePositions.maxOfOrNull { it.value } ?: 0F
positionData.spawnablePositions.isEmpty()
}
toRemove.forEach { spawnToSpawnablePosition.remove(it.key) }
percentSum -= toRemove.sumOf { it.key.percentage.toDouble().takeIf { it > 0 } ?: 0.0 }.toFloat()
toRemove.forEach { selectingSpawnInformation[it.spawnDetail.bucket]?.remove(it) }
}
}
override fun getSelectionData(
spawner: Spawner,
bucket: SpawnBucket,
spawnablePositions: List<SpawnablePosition>
): SpawnablePositionSelectionData {
val spawnToSpawnablePosition: MutableMap<SpawnDetail, SelectingSpawnInformation> = mutableMapOf()
var percentSum = 0F
val selectingSpawnInformation: MutableMap<SpawnBucket, SpawnablePositionBucketSelectionData> = mutableMapOf()
spawnablePositions.forEach { spawnablePosition ->
spawner.getMatchingSpawns(bucket, spawnablePosition).forEach {
// Only add to percentSum if this is the first time we've seen this SpawnDetail, otherwise
// the percentage will get amplified for every spawnable position the thing was possible, completely
// ruining the point of this pre-selection percentage.
if (it.percentage > 0 && !spawnToSpawnablePosition.containsKey(it)) {
percentSum += it.percentage
}
val selectingSpawnInformation = spawnToSpawnablePosition.getOrPut(
it,
::SelectingSpawnInformation
)
selectingSpawnInformation.add(spawnablePosition, spawnablePosition.getWeight(it))
}
}
return SpawnablePositionSelectionData(spawnToSpawnablePosition, percentSum)
return SpawnablePositionSelectionData(spawner, spawnablePositions, selectingSpawnInformation)
}
override fun selectSpawnAction(
@@ -120,8 +140,8 @@ open class FlatSelector : SpawningSelector<FlatSelector.SpawnablePositionSelecti
bucket: SpawnBucket,
selectionData: SpawnablePositionSelectionData,
): SpawnAction<*>? {
val spawnToSpawnablePosition = selectionData.spawnToSpawnablePosition
var percentSum = selectionData.percentSum
val selectingSpawnInformation = selectionData.getDataForBucket(bucket) ?: return null
var percentSum = selectingSpawnInformation.percentSum
// First pass is doing percentage checks.
if (percentSum > 0) {
@@ -129,7 +149,7 @@ open class FlatSelector : SpawningSelector<FlatSelector.SpawnablePositionSelecti
if (percentSum > 100) {
Cobblemon.LOGGER.warn(
"""
A spawn list for ${spawner.name} exceeded 100% on percentage sums...
A spawn list for ${spawner.name} exceeded 100% on percentage sums in bucket ${bucket.name}...
This means you don't understand how this option works.
""".trimIndent()
)
@@ -140,13 +160,13 @@ open class FlatSelector : SpawningSelector<FlatSelector.SpawnablePositionSelecti
* It's [0, 1) and I want (0, 1]
* See half-open intervals here https://en.wikipedia.org/wiki/Interval_(mathematics)#Terminology
*/
val selectedPercentage = 100 - Random.Default.nextFloat() * 100
val selectedPercentage = 100 - Random.nextFloat() * 100
percentSum = 0F
for ((spawnDetail, info) in spawnToSpawnablePosition) {
if (spawnDetail.percentage > 0) {
percentSum += spawnDetail.percentage
for (info in selectingSpawnInformation.selectingSpawnInformation) {
if (info.spawnDetail.percentage > 0) {
percentSum += info.spawnDetail.percentage
if (percentSum >= selectedPercentage) {
return spawnDetail.choose(
return info.spawnDetail.choose(
spawnablePosition = info.chooseSpawnablePosition(),
bucket = bucket,
selectionData = selectionData
@@ -156,9 +176,9 @@ open class FlatSelector : SpawningSelector<FlatSelector.SpawnablePositionSelecti
}
}
val selectedSpawn = spawnToSpawnablePosition.entries.toList().weightedSelection { it.value.highestWeight }!!
return selectedSpawn.key.choose(
spawnablePosition = selectedSpawn.value.chooseSpawnablePosition(),
val selectedSpawn = selectingSpawnInformation.selectingSpawnInformation.toList().weightedSelection { it.highestWeight }!!
return selectedSpawn.spawnDetail.choose(
spawnablePosition = selectedSpawn.chooseSpawnablePosition(),
bucket = bucket,
selectionData = selectionData
)
@@ -169,16 +189,16 @@ open class FlatSelector : SpawningSelector<FlatSelector.SpawnablePositionSelecti
bucket: SpawnBucket,
spawnablePositions: List<SpawnablePosition>
): Map<SpawnDetail, Float> {
val selectionData = getSelectionData(spawner, bucket, spawnablePositions)
val selectionData = getSelectionData(spawner, spawnablePositions).getDataForBucket(bucket)
if (selectionData.size == 0) {
if (selectionData.selectingSpawnInformation.isEmpty()) {
return emptyMap()
}
val totalWeights = mutableMapOf<SpawnDetail, Float>()
for ((spawnDetail, info) in selectionData.spawnToSpawnablePosition.entries) {
totalWeights[spawnDetail] = info.highestWeight
for (info in selectionData.selectingSpawnInformation) {
totalWeights[info.spawnDetail] = info.highestWeight
}
return totalWeights
@@ -18,7 +18,6 @@ import com.cobblemon.mod.common.api.spawning.spawner.Spawner
import com.cobblemon.mod.common.util.removeIf
import com.cobblemon.mod.common.util.weightedSelection
import kotlin.random.Random
import kotlin.reflect.full.createInstance
/**
* A spawning selector that compiles a distinct list of all spawn details that
@@ -33,9 +32,9 @@ import kotlin.reflect.full.createInstance
* of an entire spawnable position type to favour whichever are the more populous.
*
* The weight for a spawn when doing primary selection is whichever spawnable position-influenced
* weight is highest (as weight multipliers exist per spawnable position) and then the
* selection of which spawnable position to spawn the primary selected spawn uses the spawnable
* position-adjusted weight.
* weight is highest (as weight multipliers exist per spawnable position). After the spawn has been
* chosen, the selection of which spawnable position to spawn it at is selected via another weighted
* selection using spawnable position-adjusted weights.
*
* At a glance:
* - Spawn detail selection is flat across spawnable position quantity
@@ -45,7 +44,6 @@ import kotlin.reflect.full.createInstance
* @since July 10th, 2022
*/
open class FlatSpawnablePositionWeightedSelector : SpawningSelector<FlatSpawnablePositionWeightedSelector.SeparatedSelectionData> {
open fun getWeight(spawnablePositionType: SpawnablePositionType<*>) = spawnablePositionType.getWeight()
class SelectingSpawnInformation {
val spawnablePositionWeights = mutableMapOf<SpawnablePosition, Float>()
@@ -62,13 +60,25 @@ open class FlatSpawnablePositionWeightedSelector : SpawningSelector<FlatSpawnabl
fun chooseSpawnablePosition() = spawnablePositionWeights.keys.toList().weightedSelection { spawnablePositionWeights[it]!! }!!
}
class SpawnablePositionSelectionData(
val spawnToSpawnablePosition: MutableMap<SpawnDetail, SelectingSpawnInformation>,
var percentSum: Float
) {
class SpawnablePositionSelectionData {
val spawnToSpawnablePosition = mutableMapOf<SpawnDetail, SelectingSpawnInformation>()
var percentSum: Float = 0F
val seenSpawns = mutableListOf<SpawnDetail>()
val size: Int
get() = spawnToSpawnablePosition.size
fun add(spawnDetail: SpawnDetail, spawnablePosition: SpawnablePosition) {
// Only add to percentSum if this is the first time we've seen this SpawnDetail for this spawnable
// position type, otherwise the percentage will get amplified for every spawnable position the thing
// was possible, completely ruining the point of this pre-selection percentage.
if (spawnDetail.percentage > 0 && spawnDetail !in seenSpawns) {
percentSum += spawnDetail.percentage
seenSpawns.add(spawnDetail)
}
val selectingSpawnInformation = spawnToSpawnablePosition.getOrPut(spawnDetail, ::SelectingSpawnInformation)
selectingSpawnInformation.add(spawnDetail, spawnablePosition, 1F)
}
fun removeSpawnDetails(shouldRemove: (SpawnDetail) -> Boolean) {
val toRemove = spawnToSpawnablePosition.entries.filter { shouldRemove(it.key) }
toRemove.forEach { spawnToSpawnablePosition.remove(it.key) }
@@ -89,67 +99,68 @@ open class FlatSpawnablePositionWeightedSelector : SpawningSelector<FlatSpawnabl
}
class SeparatedSelectionData(
val spawnablePositionTypeToSpawns: MutableMap<SpawnablePositionType<*>, SpawnablePositionSelectionData>
): SpawnSelectionData {
val spawner: Spawner,
val spawnablePositions: List<SpawnablePosition>
) : SpawnSelectionData {
override val spawnActions = mutableListOf<SpawnAction<*>>()
override val context = mutableMapOf<String, Any>()
val bucketData = mutableMapOf<SpawnBucket, MutableMap<SpawnablePositionType<*>, SpawnablePositionSelectionData>>()
override fun removeSpawnDetails(shouldRemove: (SpawnDetail) -> Boolean) {
spawnablePositionTypeToSpawns.values.forEach { it.removeSpawnDetails(shouldRemove) }
spawnablePositionTypeToSpawns.removeIf { it.value.size == 0 }
bucketData.values.forEach {
it.values.forEach { it.removeSpawnDetails(shouldRemove) }
it.removeIf { it.value.size == 0 }
}
}
override fun removeSpawnablePositions(shouldRemove: (SpawnDetail, SpawnablePosition) -> Boolean) {
spawnablePositionTypeToSpawns.values.forEach { it.removeSpawnablePositions(shouldRemove) }
spawnablePositionTypeToSpawns.removeIf { it.value.size == 0 }
bucketData.values.forEach {
it.values.forEach { it.removeSpawnablePositions(shouldRemove) }
it.removeIf { it.value.size == 0 }
}
}
fun getDataForBucket(bucket: SpawnBucket): MutableMap<SpawnablePositionType<*>, SpawnablePositionSelectionData> {
bucketData[bucket]?.let { return it }
val spawnablePositionTypeToSpawns = mutableMapOf<SpawnablePositionType<*>, SpawnablePositionSelectionData>()
spawnablePositions.forEach { spawnablePosition ->
val spawnablePositionType = SpawnablePosition.getByClass(spawnablePosition)!!
val possible = spawner.getMatchingSpawns(bucket, spawnablePosition)
if (possible.isNotEmpty()) {
val spawnablePositionSelectionData = spawnablePositionTypeToSpawns
.getOrPut(spawnablePositionType, ::SpawnablePositionSelectionData)
possible.forEach { spawnablePositionSelectionData.add(it, spawnablePosition) }
}
}
bucketData[bucket] = spawnablePositionTypeToSpawns
return spawnablePositionTypeToSpawns
}
}
override fun getSelectionData(
spawner: Spawner,
bucket: SpawnBucket,
spawnablePositions: List<SpawnablePosition>
): SeparatedSelectionData {
val spawnablePositionTypeToSpawns = mutableMapOf<SpawnablePositionType<*>, SpawnablePositionSelectionData>()
spawnablePositions.forEach { spawnablePosition ->
val spawnablePositionType = SpawnablePosition.getByClass(spawnablePosition)!!
val possible = spawner.getMatchingSpawns(bucket, spawnablePosition)
if (possible.isNotEmpty()) {
val spawnablePositionSelectionData = spawnablePositionTypeToSpawns.getOrPut(spawnablePositionType) { SpawnablePositionSelectionData(mutableMapOf(), 0F) }
possible.forEach {
// Only add to percentSum if this is the first time we've seen this SpawnDetail for this spawnable
// position type, otherwise the percentage will get amplified for every spawnable position the thing
// was possible, completely ruining the point of this pre-selection percentage.
if (it.percentage > 0 && !spawnablePositionSelectionData.spawnToSpawnablePosition.containsKey(it)) {
spawnablePositionSelectionData.percentSum += it.percentage
}
val selectingSpawnInformation = spawnablePositionSelectionData.spawnToSpawnablePosition.getOrPut(
it,
SelectingSpawnInformation::class::createInstance
)
selectingSpawnInformation.add(it, spawnablePosition, getWeight(spawnablePositionType))
}
}
}
return SeparatedSelectionData(spawnablePositionTypeToSpawns)
}
) = SeparatedSelectionData(spawner, spawnablePositions)
override fun selectSpawnAction(
spawner: Spawner,
bucket: SpawnBucket,
selectionData: SeparatedSelectionData
): SpawnAction<*>? {
if (selectionData.spawnablePositionTypeToSpawns.isEmpty()) {
val bucketData = selectionData.getDataForBucket(bucket)
if (bucketData.isEmpty()) {
return null
}
// Which spawnable position type should we use?
val spawnablePositionSelectionData = selectionData.spawnablePositionTypeToSpawns.entries.toList()
.weightedSelection { getWeight(it.key) * it.value.size }
val spawnablePositionSelectionData = bucketData.entries.toList()
.weightedSelection { it.key.getWeight() * it.value.size }
?.value
?: return null
@@ -161,7 +172,7 @@ open class FlatSpawnablePositionWeightedSelector : SpawningSelector<FlatSpawnabl
if (percentSum > 100) {
LOGGER.warn(
"""
A spawn list for ${spawner.name} exceeded 100% on percentage sums...
A spawn list for ${spawner.name} exceeded 100% on percentage sums in bucket ${bucket.name}...
This means you don't understand how this option works.
""".trimIndent()
)
@@ -173,7 +184,7 @@ open class FlatSpawnablePositionWeightedSelector : SpawningSelector<FlatSpawnabl
* It's [0, 1) and I want (0, 1]
* See half-open intervals here https://en.wikipedia.org/wiki/Interval_(mathematics)#Terminology
*/
val selectedPercentage = 100 - Random.Default.nextFloat() * 100
val selectedPercentage = 100 - Random.nextFloat() * 100
percentSum = 0F
for ((spawnDetail, info) in spawnToSpawnablePosition) {
if (spawnDetail.percentage > 0) {
@@ -224,7 +235,8 @@ open class FlatSpawnablePositionWeightedSelector : SpawningSelector<FlatSpawnabl
val percentageWeight = (rescaledTotalWeight - totalWeight) / percentSum
for ((spawnDetail, info) in spawnToSpawnablePosition.entries) {
totalWeights[spawnDetail] = info.highestWeight + if (spawnDetail.percentage > 0) spawnDetail.percentage * percentageWeight else 0F
totalWeights[spawnDetail] =
info.highestWeight + if (spawnDetail.percentage > 0) spawnDetail.percentage * percentageWeight else 0F
}
return totalWeights
@@ -235,18 +247,18 @@ open class FlatSpawnablePositionWeightedSelector : SpawningSelector<FlatSpawnabl
bucket: SpawnBucket,
spawnablePositions: List<SpawnablePosition>
): Map<SpawnDetail, Float> {
val selectionData = getSelectionData(spawner, bucket, spawnablePositions)
val selectionData = getSelectionData(spawner, spawnablePositions).getDataForBucket(bucket)
if (selectionData.spawnablePositionTypeToSpawns.isEmpty()) {
if (selectionData.isEmpty()) {
return mapOf()
}
val totalWeights = mutableMapOf<SpawnDetail, Float>()
val totalSpawnablePositionWeight = selectionData.spawnablePositionTypeToSpawns.keys.sumOf { getWeight(it).toDouble() }.toFloat()
val totalSpawnablePositionWeight = selectionData.keys.sumOf { it.getWeight().toDouble() }.toFloat()
for ((spawnablePositionType, spawnPositionSelectionData) in selectionData.spawnablePositionTypeToSpawns) {
val spawnPositionWeightCorrection = getWeight(spawnablePositionType) / totalSpawnablePositionWeight
for ((spawnablePositionType, spawnPositionSelectionData) in selectionData) {
val spawnPositionWeightCorrection = spawnablePositionType.getWeight() / totalSpawnablePositionWeight
val spawnPositionProbabilities = getProbabilitiesFromSpawnablePositionType(spawner, spawnPositionSelectionData)
spawnPositionProbabilities.entries.forEach {
@@ -29,7 +29,7 @@ interface SpawningSelector<T : SpawnSelectionData> {
val DEFAULT = FlatSpawnablePositionWeightedSelector()
}
fun getSelectionData(spawner: Spawner, bucket: SpawnBucket, spawnablePositions: List<SpawnablePosition>): T
fun getSelectionData(spawner: Spawner, spawnablePositions: List<SpawnablePosition>): T
fun selectSpawnAction(
spawner: Spawner,
@@ -37,12 +37,18 @@ interface SpawningSelector<T : SpawnSelectionData> {
selectionData: T
): SpawnAction<*>?
fun select(spawner: Spawner, bucket: SpawnBucket, spawnablePositions: List<SpawnablePosition>, maxSpawns: Int): List<SpawnAction<*>> {
val selectionData = getSelectionData(spawner, bucket, spawnablePositions)
fun select(
spawner: Spawner,
bucketSelector: () -> SpawnBucket,
spawnablePositions: List<SpawnablePosition>,
maxSpawns: Int
): List<SpawnAction<*>> {
val selectionData = getSelectionData(spawner, spawnablePositions)
val spawnActions = selectionData.spawnActions
while (spawnActions.size < maxSpawns) {
val bucket = bucketSelector()
val spawnAction = selectSpawnAction(
spawner = spawner,
bucket = bucket,
@@ -29,13 +29,6 @@ import com.cobblemon.mod.common.util.isBoxLoaded
import com.cobblemon.mod.common.util.squeezeWithinBounds
import com.cobblemon.mod.common.util.toVec3f
import com.cobblemon.mod.common.util.weightedSelection
import kotlin.Any
import kotlin.Boolean
import kotlin.Float
import kotlin.Int
import kotlin.Pair
import kotlin.String
import kotlin.collections.plus
import kotlin.math.max
import net.minecraft.core.BlockPos
import net.minecraft.core.SectionPos
@@ -95,10 +88,9 @@ interface Spawner {
): SpawnAction<*>? {
influences.removeIf { it.isExpired() }
spawnablePosition.influences.addAll(influences)
val bucket = chooseBucket(cause, spawnablePosition.influences)
return selector.select(
spawner = this,
bucket = bucket,
bucketSelector = { chooseBucket(cause, spawnablePosition.influences) },
spawnablePositions = listOf(spawnablePosition),
maxSpawns = 1
).firstOrNull()
@@ -116,9 +108,9 @@ interface Spawner {
val areaBox = AABB.ofSize(
Vec3(constrainedArea.getCenter().toVec3f()),
ENTITY_LIMIT_CHUNK_RANGE * 16.0 * 2,
ENTITY_LIMIT_CHUNK_RANGE * 16.0,
1000.0,
ENTITY_LIMIT_CHUNK_RANGE * 16.0 * 2
ENTITY_LIMIT_CHUNK_RANGE * 16.0
)
if (!constrainedArea.world.isBoxLoaded(areaBox)) {
@@ -140,11 +132,10 @@ interface Spawner {
val zone = generator.generate(this, constrainedArea)
val spawnablePositions = resolver.resolve(this, prioritizedAreaCalculators, zone)
val influences = influences + zone.unconditionalInfluences
val bucket = chooseBucket(zoneInput.cause, influences)
return selector.select(
spawner = this,
bucket = bucket,
bucketSelector = { chooseBucket(zoneInput.cause, influences) },
spawnablePositions = spawnablePositions,
maxSpawns = maxSpawns
)
@@ -77,6 +77,7 @@ abstract class PokemonStore<T : StorePosition> : Iterable<Pokemon> {
remove(pokemon)
val position = getFirstAvailablePosition() ?: return false // Couldn't fit, shrug emoji
set(position, pokemon)
Cobblemon.playerDataManager.getTMData(uuid)?.syncTMsFromPokemon(pokemon)
return true
}
@@ -170,4 +171,4 @@ abstract class PokemonStore<T : StorePosition> : Iterable<Pokemon> {
* each change, accessed by [Pokemon.getChangeObservable].
*/
abstract fun getAnyChangeObservable(): Observable<Unit>
}
}
@@ -332,7 +332,7 @@ open class PCStore(
val toast = Toast(
title = lang("wallpaper_unlocked"),
description = unlockableWallpaper.displayName?.let { "\"".text().add(it.asTranslated()).add("\"") } ?: lang("unknown_wallpaper") ,
icon = ItemStack(CobblemonItems.PC)
icons = listOf(ItemStack(CobblemonItems.PC))
)
toast.addListeners(player)
toast.expireAfter(5F)
@@ -8,14 +8,17 @@
package com.cobblemon.mod.common.api.storage.player
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.Cobblemon.MODID
import com.cobblemon.mod.common.api.pokedex.PokedexManager
import com.cobblemon.mod.common.api.scheduling.ScheduledTask
import com.cobblemon.mod.common.api.scheduling.ServerTaskTracker
import com.cobblemon.mod.common.api.storage.player.client.ClientInstancedPlayerData
import com.cobblemon.mod.common.api.storage.player.factory.CachedPlayerDataStoreFactory
import com.cobblemon.mod.common.api.tms.TMMoveManager
import com.google.common.util.concurrent.ThreadFactoryBuilder
import java.util.UUID
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
import net.minecraft.server.MinecraftServer
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.entity.player.Player
@@ -30,7 +33,6 @@ import net.minecraft.world.entity.player.Player
* @author Apion
* @since February 21, 2024
*/
open class PlayerInstancedDataStoreManager {
val saveExecutor = Executors.newSingleThreadExecutor(
ThreadFactoryBuilder()
@@ -53,19 +55,27 @@ open class PlayerInstancedDataStoreManager {
//Should put this somewhere else
saveTasks[PlayerInstancedDataStoreTypes.GENERAL] = ScheduledTask.Builder()
.execute { saveAllOfOneType(PlayerInstancedDataStoreTypes.GENERAL) }
.delay(30f)
.interval(120f)
.delay(30F)
.interval(120F)
.infiniteIterations()
.tracker(ServerTaskTracker)
.build()
saveTasks[PlayerInstancedDataStoreTypes.POKEDEX] = ScheduledTask.Builder()
.execute { saveAllOfOneType(PlayerInstancedDataStoreTypes.POKEDEX) }
.delay(30f)
.interval(120f)
.delay(30F)
.interval(120F)
.infiniteIterations()
.tracker(ServerTaskTracker)
.build()
saveTasks[PlayerInstancedDataStoreTypes.TM_MOVES] = ScheduledTask.Builder()
.execute { saveAllOfOneType(PlayerInstancedDataStoreTypes.TM_MOVES) }
.delay(30F)
.interval(120F)
.infiniteIterations()
.tracker(ServerTaskTracker)
.build()
}
open fun get(playerId: UUID, dataType: PlayerInstancedDataStoreType): InstancedPlayerData {
@@ -126,4 +136,16 @@ open class PlayerInstancedDataStoreManager {
open fun getPokedexData(playerId: UUID): PokedexManager {
return get(playerId, PlayerInstancedDataStoreTypes.POKEDEX) as PokedexManager
}
}
fun getTMData(player: ServerPlayer): TMMoveManager {
return getTMData(player.uuid)
}
fun getTMData(playerId: UUID): TMMoveManager {
val data = get(playerId, PlayerInstancedDataStoreTypes.TM_MOVES)
if (data !is TMMoveManager) {
Cobblemon.LOGGER.warn("getTMData: No TMMoveManager found for $playerId (got ${data?.javaClass?.name})")
}
return data as TMMoveManager
}
}
@@ -8,8 +8,10 @@
package com.cobblemon.mod.common.api.storage.player
import com.cobblemon.mod.common.api.storage.player.client.ClientGeneralPlayerData
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.storage.player.client.ClientPokedexManager
import com.cobblemon.mod.common.api.storage.player.client.ClientGeneralPlayerData
import com.cobblemon.mod.common.api.storage.player.client.ClientTMMoveManager
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.resources.ResourceLocation
@@ -27,11 +29,22 @@ object PlayerInstancedDataStoreTypes {
ClientPokedexManager::runAction,
ClientPokedexManager::runIncremental
))
val TM_MOVES = register(PlayerInstancedDataStoreType(
cobblemonResource("tm_moves"),
ClientTMMoveManager::decode,
ClientTMMoveManager::runAction,
ClientTMMoveManager::runIncremental
))
fun register(type: PlayerInstancedDataStoreType): PlayerInstancedDataStoreType {
Cobblemon.LOGGER.debug("Registering PlayerInstancedDataStoreType: {}", type.id)
types[type.id] = type
return type
}
fun getTypeById(id: ResourceLocation) = types[id]
}
fun getTypeById(id: ResourceLocation): PlayerInstancedDataStoreType? {
val type = types[id]
if (type == null) Cobblemon.LOGGER.warn("Unknown PlayerInstancedDataStoreType ID: {}", id)
return type
}
}
@@ -0,0 +1,35 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.storage.player.adapter
import com.cobblemon.mod.common.api.storage.player.PlayerInstancedDataStoreTypes
import com.cobblemon.mod.common.api.tms.TMMoveManager
import com.cobblemon.mod.common.util.adapters.CodecBackedAdapter
import com.google.gson.GsonBuilder
import com.google.gson.reflect.TypeToken
import java.util.UUID
class TMMoveJsonBackend : JsonBackedPlayerDataStoreBackend<TMMoveManager>("tm_moves", PlayerInstancedDataStoreTypes.TM_MOVES) {
override val gson = GsonBuilder()
.setPrettyPrinting()
.disableHtmlEscaping()
.registerTypeAdapter(TMMoveManager::class.java, CodecBackedAdapter(TMMoveManager.CODEC))
.create()
override val classToken = TypeToken.get(TMMoveManager::class.java)
override val defaultData = defaultDataFunc
override fun initialize(store: TMMoveManager) {
store.initialize()
}
companion object {
val defaultDataFunc = { uuid: UUID -> TMMoveManager(uuid) }
}
}
@@ -0,0 +1,42 @@
/*
* 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.storage.player.adapter
import com.cobblemon.mod.common.api.storage.player.PlayerInstancedDataStoreTypes
import com.cobblemon.mod.common.api.tms.TMMoveManager
import com.cobblemon.mod.common.util.adapters.CodecBackedAdapter
import com.google.gson.GsonBuilder
import com.google.gson.reflect.TypeToken
import com.mongodb.client.MongoClient
import java.util.UUID
class TMMoveMongoBackend(
mongoClient: MongoClient,
databaseName: String,
collectionName: String
) : MongoBackedPlayerDataStoreBackend<TMMoveManager>(
mongoClient,
databaseName,
collectionName,
PlayerInstancedDataStoreTypes.TM_MOVES
) {
override val gson = GsonBuilder()
.registerTypeAdapter(TMMoveManager::class.java, CodecBackedAdapter(TMMoveManager.CODEC))
.create()
override val classToken = TypeToken.get(TMMoveManager::class.java)
override val defaultData = defaultDataFunc
override fun initialize(store: TMMoveManager) {
store.initialize()
}
companion object {
val defaultDataFunc = { uuid: UUID -> TMMoveManager(uuid) }
}
}
@@ -0,0 +1,18 @@
/*
* 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.storage.player.adapter
import com.cobblemon.mod.common.api.storage.player.PlayerInstancedDataStoreTypes
import com.cobblemon.mod.common.api.tms.TMMoveManager
import java.util.UUID
class TMMoveNbtBackend : NbtBackedPlayerData<TMMoveManager>("tm_moves", PlayerInstancedDataStoreTypes.TM_MOVES) {
override val codec = TMMoveManager.CODEC
override val defaultData = TMMoveJsonBackend.defaultDataFunc
}
@@ -0,0 +1,49 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.storage.player.client
import com.cobblemon.mod.common.api.storage.player.PlayerInstancedDataStoreTypes
import com.cobblemon.mod.common.client.CobblemonClient
import com.cobblemon.mod.common.net.messages.client.SetClientPlayerDataPacket
import com.cobblemon.mod.common.util.readString
import com.cobblemon.mod.common.util.writeString
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
class ClientTMMoveManager(
val learnedTMs: MutableSet<ResourceLocation>
) : ClientInstancedPlayerData {
override fun encode(buf: RegistryFriendlyByteBuf) {
buf.writeCollection(learnedTMs) { _, id -> buf.writeString(id.toString()) }
}
companion object {
fun decode(buf: RegistryFriendlyByteBuf): SetClientPlayerDataPacket {
val tms = buf.readCollection(
{ mutableSetOf<ResourceLocation>() },
{ buf.readString().let(ResourceLocation::tryParse) ?: ResourceLocation("minecraft", "empty") }
)
return SetClientPlayerDataPacket(
PlayerInstancedDataStoreTypes.TM_MOVES,
ClientTMMoveManager(tms)
)
}
fun runAction(data: ClientInstancedPlayerData) {
if (data !is ClientTMMoveManager) return
CobblemonClient.clientTMMoveData = data
}
fun runIncremental(data: ClientInstancedPlayerData) {
if (data !is ClientTMMoveManager) return
CobblemonClient.clientTMMoveData.learnedTMs.addAll(data.learnedTMs)
}
}
}
@@ -40,8 +40,6 @@ class CachedPlayerDataStoreFactory<T : InstancedPlayerData>(val backend: PlayerD
}
}
override fun saveAll() {
cache.forEach { (_, pd) -> backend.save(pd) }
cache.removeIf { (uuid, _) -> uuid.getPlayer() == null }
@@ -91,6 +91,7 @@ object CobblemonBlockTags {
@JvmField val TUMBLESTONE_BRICKS = createTag("tumblestone_bricks")
@JvmField val TUMBLESTONES = createTag("tumblestones")
@JvmField val TUMBLESTONE_HEAT_SOURCE = createTag("tumblestone_heat_source")
@JvmField val TYPE_GEM_BLOCKS = createTag("type_gem_blocks")
@JvmField val WALL_HANGING_SIGNS = createTag("wall_hanging_signs")
@JvmField val WALL_SIGNS = createTag("wall_signs")
@JvmField val WATER_STONE_ORES = createTag("water_stone_ores")
@@ -0,0 +1,53 @@
/*
* 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.tms
import com.cobblemon.mod.common.tms.obtain.DefaultObtainMethod
import com.cobblemon.mod.common.tms.obtain.PlayerHasAdvancementObtainMethod
import com.cobblemon.mod.common.tms.obtain.PlayerYObtainMethod
import com.cobblemon.mod.common.tms.obtain.UnlockableObtainMethod
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.server.level.ServerPlayer
interface ObtainMethod {
val passive: Boolean
/**
* Checks if this [ObtainMethod] matches the given player.
*/
fun matches(player: ServerPlayer): Boolean
/**
* Serializes this [ObtainMethod] to the buffer.
*/
fun writeToBuffer(buffer: RegistryFriendlyByteBuf)
companion object {
/**
* Deserializes an [ObtainMethod] from the buffer.
*/
fun readFromBuffer(buffer: RegistryFriendlyByteBuf): ObtainMethod {
val variant = buffer.readUtf()
return when (variant) {
"cobblemon:unlockable" -> UnlockableObtainMethod()
"cobblemon:default" -> DefaultObtainMethod()
"cobblemon:advancement" -> {
val advancement = buffer.readResourceLocation()
PlayerHasAdvancementObtainMethod(advancement)
}
"cobblemon:y_level" -> {
val yLevel = buffer.readVarInt()
val operator = buffer.readUtf()
PlayerYObtainMethod(yLevel, operator)
}
else -> throw IllegalArgumentException("Unknown ObtainMethod variant: $variant")
}
}
}
}
@@ -0,0 +1,177 @@
/*
* 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.tms
import com.cobblemon.mod.common.CobblemonNetwork.sendPacket
import com.cobblemon.mod.common.api.moves.MoveTemplate
import com.cobblemon.mod.common.api.scheduling.ScheduledTask
import com.cobblemon.mod.common.api.scheduling.ServerTaskTracker
import com.cobblemon.mod.common.api.storage.player.InstancedPlayerData
import com.cobblemon.mod.common.api.storage.player.PlayerInstancedDataStoreTypes
import com.cobblemon.mod.common.api.storage.player.client.ClientTMMoveManager
import com.cobblemon.mod.common.net.messages.client.SetClientPlayerDataPacket
import com.cobblemon.mod.common.net.messages.client.toast.ToastPacket
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.getPlayer
import com.cobblemon.mod.common.util.lang
import com.mojang.serialization.Codec
import com.mojang.serialization.codecs.RecordCodecBuilder
import java.util.UUID
import net.minecraft.network.chat.Component
import net.minecraft.resources.ResourceLocation
class TMMoveManager(
override val uuid: UUID,
val learnedTMs: MutableSet<ResourceLocation> = mutableSetOf()
) : InstancedPlayerData {
val storeType = PlayerInstancedDataStoreTypes.TM_MOVES
override fun toClientData() = ClientTMMoveManager(learnedTMs.toMutableSet())
fun toClientDataFrom(set: Set<ResourceLocation>) = ClientTMMoveManager(set.toMutableSet())
fun syncTMsFromPokemon(pokemon: Pokemon) {
learn(getLearnableTMsFromPokemon(pokemon))
}
fun syncTMFromMove(moveTemplate: MoveTemplate) {
val tmId = TechnicalMachines.moveToTM[moveTemplate]?.id ?: return
learn(listOf(tmId))
}
fun getLearnableTMsFromPokemon(pokemon: Pokemon): Collection<ResourceLocation> {
return TechnicalMachines.tmMap.values
.filter { tm -> tm.moveName in pokemon.allAccessibleMoves }
.map(TechnicalMachine::id)
}
fun scheduleFullSyncFromStores(
party: Iterable<Pokemon?>,
pc: Iterable<Pokemon?>,
batchSize: Int = 25
) {
val iterator = sequence {
for (pokemon in party) {
if (pokemon != null) yield(pokemon)
}
for (pokemon in pc) {
if (pokemon != null) yield(pokemon)
}
}.iterator()
if (!iterator.hasNext()) return
val tmIds = mutableSetOf<ResourceLocation>()
ScheduledTask.Builder()
.tracker(ServerTaskTracker)
.interval(0F)
.infiniteIterations()
.execute { task ->
var processed = 0
while (processed < batchSize && iterator.hasNext()) {
val pokemon = iterator.next()
for (move in pokemon.allAccessibleMoves) {
TechnicalMachines.moveToTM[move]?.id?.let { tmIds.add(it) }
}
processed++
}
if (!iterator.hasNext()) {
learn(tmIds)
task.expire()
}
}
.build()
}
fun learn(tmIds: Collection<ResourceLocation>): Boolean {
val newLearnedTms = mutableListOf<ResourceLocation>()
for (tmId in tmIds) {
if (learnedTMs.add(tmId)) {
newLearnedTms.add(tmId)
}
}
if (newLearnedTms.isEmpty()) return false
syncClient(newLearnedTms.toSet())
sendTMToast(newLearnedTms)
return true
}
fun unlearn(tmIds: Collection<ResourceLocation>): Boolean {
val removedTms = mutableListOf<ResourceLocation>()
for (tmId in tmIds) {
if (learnedTMs.remove(tmId)) {
removedTms.add(tmId)
}
}
if (removedTms.isEmpty()) return false
syncClient(learnedTMs, isIncremental = false)
return true
}
fun sendTMToast(tmIds: List<ResourceLocation>) {
val player = uuid.getPlayer() ?: return
var moveName: Component = Component.empty()
val icons = tmIds.mapNotNull {
val tm = TechnicalMachines.tmMap[it] ?: return@mapNotNull null
moveName = tm.translatedMoveName()
return@mapNotNull tm.createItemStack()
}
val description = if (icons.size == 1) moveName else lang("tms.check_tmm")
val packet = ToastPacket(
title = lang("tms.new_tms_learned"),
description = description,
icons = icons,
frameTexture = ResourceLocation.parse("minecraft:toast/advancement"),
progress = -1F,
progressColor = 0x00FF00,
// Keeping it a fixed UUID makes it so the ToastTracker merges different toasts on the client
uuid = UUID.nameUUIDFromBytes("tm_toast".toByteArray()),
behaviour = ToastPacket.Behaviour.SHOW_OR_UPDATE,
durationMs = 4000L
)
player.sendPacket(packet)
}
fun syncClient(updateSet: Set<ResourceLocation> = learnedTMs, isIncremental: Boolean = true) {
uuid.getPlayer()?.sendPacket(
SetClientPlayerDataPacket(
type = storeType,
playerData = toClientDataFrom(updateSet),
isIncremental = isIncremental
)
)
}
companion object {
val CODEC: Codec<TMMoveManager> = RecordCodecBuilder.create { instance ->
instance.group(
Codec.STRING.xmap(UUID::fromString, UUID::toString)
.fieldOf("uuid")
.forGetter { it.uuid },
Codec.list(ResourceLocation.CODEC)
.xmap({ it.toMutableSet() }, { it.toList() })
.fieldOf("learnedTMs")
.forGetter { it.learnedTMs }
).apply(instance, ::TMMoveManager)
}
}
}
@@ -0,0 +1,126 @@
/*
* 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.tms
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.api.moves.MoveTemplate
import com.cobblemon.mod.common.api.types.ElementalType
import com.cobblemon.mod.common.api.types.ElementalTypes
import com.cobblemon.mod.common.client.CobblemonClient
import com.cobblemon.mod.common.item.components.TMMoveComponent
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.tms.obtain.DefaultObtainMethod
import com.cobblemon.mod.common.util.lang
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
import net.minecraft.network.chat.MutableComponent
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.ItemStack
class TechnicalMachine(
val moveName: MoveTemplate,
val recipe: List<TechnicalMachineRecipe>?,
val obtainMethods: List<ObtainMethod> = emptyList(),
val type: String
) {
companion object {
/**
* Filters all available [TechnicalMachines] based on three optional arguments
*
* @param search A [String] to check against move names
* @param type An [ElementalType] to check for
* @param pokemon A [Pokemon] to check the [Learnset] of
* @return A [MutableSet] of [TechnicalMachine] that passed the filter
*/
fun filterTms(
search: String?,
type: ElementalType?,
pokemon: Pokemon?,
player: Player? = null,
includeUnlearned: Boolean = false
): MutableSet<TechnicalMachine> {
val tms = TechnicalMachines.tmMap.values.toMutableSet()
type?.let {
val iterator = tms.iterator()
while (iterator.hasNext()) {
val tm = iterator.next()
if (ElementalTypes.get(tm.type) != type) {
iterator.remove()
}
}
}
pokemon?.let {
val iterator = tms.iterator()
while (iterator.hasNext()) {
val tm = iterator.next()
if (tm.moveName !in pokemon.form.moves.tmLearnableMoves()) {
iterator.remove()
}
}
}
search?.let {
val iterator = tms.iterator()
while (iterator.hasNext()) {
val tm = iterator.next()
if (!tm.translatedMoveName().string.contains(search, ignoreCase = true)) {
iterator.remove()
}
}
}
if (player != null && !includeUnlearned) {
val learnedTMs = CobblemonClient.clientTMMoveData.learnedTMs
tms.retainAll { tm -> tm.isPassivelyObtained() || tm.id in learnedTMs }
}
return tms
}
}
lateinit var id: ResourceLocation
/**
* Gets the recipe list limited to the provided amount to match the slot amount of the TM Machine.
*
* @param limit The amount of recipe item slots to take.
* @return The recipe list limited to 3 ingredients.
*/
fun getClampedRecipe(limit: Int = 3): List<TechnicalMachineRecipe>? = recipe?.take(limit)
/**
* Unlocks this [TechnicalMachine] for the player.
*
* @param player The [ServerPlayer] to give this [TechnicalMachine] to.
* @return Whether the player was successfully granted the [TechnicalMachine]
*/
fun unlock(player: ServerPlayer): Boolean {
if (!obtainMethods.any { it is DefaultObtainMethod }) {
player.sendSystemMessage(lang("tms.unlock_tm", moveName.displayName))
}
return true
}
fun isPassivelyObtained() = obtainMethods.any { it is DefaultObtainMethod }
/**
* Returns a [MutableComponent] of the translated move name of this [TechnicalMachine]
*
* @return This [TechnicalMachine]'s move name, translated
*/
fun translatedMoveName(): MutableComponent = moveName.displayName
fun createItemStack(): ItemStack {
val stack = ItemStack(CobblemonItems.TECHNICAL_MACHINE)
TMMoveComponent.setTMMove(stack, moveName)
return stack
}
}
@@ -0,0 +1,30 @@
/*
* 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.tms
import com.cobblemon.mod.common.api.moves.MoveTemplate
/**
* DTO used for [TechnicalMachine] JSON loading because Gson cannot deserialize Minecraft Ingredients.
*/
data class TechnicalMachineDTO(
val moveName: MoveTemplate,
val recipe: List<TechnicalMachineRecipeDTO>?,
val obtainMethods: List<ObtainMethod> = emptyList(),
val type: String
) {
fun toTechnicalMachine(): TechnicalMachine {
return TechnicalMachine(
moveName = moveName,
recipe = recipe?.map { it.toTechnicalMachineRecipe() },
obtainMethods = obtainMethods,
type = type
)
}
}
@@ -0,0 +1,19 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.tms
import net.minecraft.world.item.crafting.Ingredient
/**
* Represents the ingredients necessary to craft a [TechnicalMachine] in the [TMBlock]
*/
data class TechnicalMachineRecipe(
val ingredient: Ingredient,
val count: Int
)
@@ -0,0 +1,50 @@
/*
* 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.tms
import com.google.gson.annotations.SerializedName
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.core.registries.Registries
import net.minecraft.resources.ResourceLocation
import net.minecraft.tags.TagKey
import net.minecraft.world.item.crafting.Ingredient
/**
* DTO used for [TechnicalMachineRecipe] JSON loading because Gson cannot deserialize Minecraft Ingredients.
*/
data class TechnicalMachineRecipeDTO(
val item: ResourceLocation?,
val tag: ResourceLocation?,
@SerializedName("count") private val _count: Int?,
) {
private val count: Int
get() = _count ?: 1
fun toTechnicalMachineRecipe(): TechnicalMachineRecipe {
val ingredient = when {
item != null ->
Ingredient.of(
BuiltInRegistries.ITEM.get(item)
)
tag != null ->
Ingredient.of(
TagKey.create(Registries.ITEM, tag)
)
else ->
error("Recipe entry must define either 'item' or 'tag'")
}
return TechnicalMachineRecipe(
ingredient = ingredient,
count = count
)
}
}
@@ -0,0 +1,74 @@
/*
* 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.tms
import com.cobblemon.mod.common.api.data.JsonDataRegistry
import com.cobblemon.mod.common.api.moves.MoveTemplate
import com.cobblemon.mod.common.api.moves.adapters.MoveTemplateAdapter
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.util.adapters.TMObtainMethodAdapter
import com.cobblemon.mod.common.util.adapters.IdentifierAdapter
import com.cobblemon.mod.common.util.cobblemonResource
import com.google.gson.GsonBuilder
import com.google.gson.reflect.TypeToken
import java.io.BufferedReader
import java.util.concurrent.ExecutionException
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
import net.minecraft.server.packs.PackType
object TechnicalMachines : JsonDataRegistry<TechnicalMachine> {
override val gson = GsonBuilder()
.registerTypeAdapter(ResourceLocation::class.java, IdentifierAdapter)
.registerTypeAdapter(ObtainMethod::class.java, TMObtainMethodAdapter)
.registerTypeAdapter(MoveTemplate::class.java, MoveTemplateAdapter)
.create()
override val typeToken = TypeToken.get(TechnicalMachine::class.java)
override val resourcePath = "tms"
override val id = cobblemonResource("technical_machines")
override val type = PackType.SERVER_DATA
override val observable = SimpleObservable<TechnicalMachines>()
val tmMap = mutableMapOf<ResourceLocation, TechnicalMachine>()
val moveToTM = mutableMapOf<MoveTemplate, TechnicalMachine>()
/**
* Overridden to deserialize a DTO first and convert it to a runtime
* [TechnicalMachine], since Gson cannot deserialize Minecraft Ingredients
* directly.
*/
override fun parse(
reader: BufferedReader,
identifier: ResourceLocation
): TechnicalMachine {
try {
val technicalMachineDto = gson.fromJson(reader, TechnicalMachineDTO::class.java)
val technicalMachine = technicalMachineDto.toTechnicalMachine()
return technicalMachine
} catch (exception: Exception) {
throw ExecutionException(
"Error loading Technical Machine JSON: $identifier",
exception
)
}
}
override fun reload(data: Map<ResourceLocation, TechnicalMachine>) {
data.forEach { (id, tm) ->
tmMap[id] = tm
tm.id = id
moveToTM[tm.moveName] = tm
}
}
fun getByResourceLocation(resourceLocation: ResourceLocation) = tmMap[resourceLocation]
fun getAllResourceLocations() = tmMap.keys.toSet()
override fun sync(player: ServerPlayer) {}
}
@@ -28,7 +28,7 @@ import net.minecraft.world.item.ItemStack
*
* @param title The [Component] representing the toast title, this can be a [Component.empty].
* @param description The [Component] representing the toast description, this can be a [Component.empty].
* @param icon The [ItemStack] that is rendered as the toast icon, this can be a [ItemStack.EMPTY].
* @param icons The [ItemStack] that is rendered as the toast icon, this can be a [ItemStack.EMPTY].
* @param frameTexture The texture of the frame, default is the [VANILLA_BACKGROUND_SPRITE].
* @param progress The value of the progress bar, this accepts a value between 0.0F and 1.0F, any other value will not render a progress bar.
* @param progressColor The progress bar color in RGB, default is the [VANILLA_PROGRESS_COLOR].
@@ -38,7 +38,7 @@ import net.minecraft.world.item.ItemStack
class Toast(
title: Component,
description: Component,
icon: ItemStack,
icons: List<ItemStack>,
frameTexture: ResourceLocation = VANILLA_BACKGROUND_SPRITE,
progress: Float = -1F,
progressColor: Int = VANILLA_PROGRESS_COLOR
@@ -57,7 +57,7 @@ class Toast(
/**
* The [ItemStack] that is rendered as the toast icon, this can be a [ItemStack.EMPTY].
*/
var icon: ItemStack by Delegates.observable(icon) { _, old, new -> if (old != new) this.launchUpdate() }
var icons: List<ItemStack> by Delegates.observable(icons) { _, old, new -> if (old != new) this.launchUpdate() }
/**
* The texture of the frame.
@@ -185,7 +185,7 @@ class Toast(
private fun toPacket(behaviour: ToastPacket.Behaviour): ToastPacket = ToastPacket(
this.title,
this.description,
this.icon,
this.icons,
this.frameTexture,
this.progress,
this.progressColor,
@@ -29,9 +29,12 @@ class ElementalType(
val displayName: MutableComponent,
val hue: Int,
val textureXMultiplier: Int,
val primaryColor: Int,
val secondaryColor: Int,
val resourceLocation: ResourceLocation = ResourceLocation.fromNamespaceAndPath(Cobblemon.MODID, "ui/types.png"),
val showdownId: String = ShowdownIdentifiable.REGEX.replace(name.lowercase(), "")
) : ShowdownIdentifiable {
val showdownId: String = ShowdownIdentifiable.REGEX.replace(name.lowercase(), ""),
val typeGem: ResourceLocation = ResourceLocation.fromNamespaceAndPath(Cobblemon.MODID, showdownId + "_gem"),
) : ShowdownIdentifiable {
override fun showdownId(): String {
return showdownId
@@ -23,7 +23,9 @@ object ElementalTypes {
name = "Normal",
displayName = Component.translatable("cobblemon.type.normal"),
hue = 0xE8E8DA,
textureXMultiplier = 0
textureXMultiplier = 0,
primaryColor = 0x8FA689,
secondaryColor = 0xFFFBE2
)
@JvmField
@@ -31,7 +33,9 @@ object ElementalTypes {
name = "Fire",
displayName = Component.translatable("cobblemon.type.fire"),
hue = 0xFF6E21,
textureXMultiplier = 1
textureXMultiplier = 1,
primaryColor = 0xF92D2D,
secondaryColor = 0xF99B1D
)
@JvmField
@@ -39,7 +43,9 @@ object ElementalTypes {
name = "Water",
displayName = Component.translatable("cobblemon.type.water"),
hue = 0x3FA5FF,
textureXMultiplier = 2
textureXMultiplier = 2,
primaryColor = 0x377AF6,
secondaryColor = 0x50DDF9
)
@JvmField
@@ -47,7 +53,9 @@ object ElementalTypes {
name = "Grass",
displayName = Component.translatable("cobblemon.type.grass"),
hue = 0x62D14F,
textureXMultiplier = 3
textureXMultiplier = 3,
primaryColor = 0x0EAE95,
secondaryColor = 0xC6F554
)
@JvmField
@@ -55,7 +63,9 @@ object ElementalTypes {
name = "Electric",
displayName = Component.translatable("cobblemon.type.electric"),
hue = 0xFFD314,
textureXMultiplier = 4
textureXMultiplier = 4,
primaryColor = 0xF7AB19,
secondaryColor = 0xFFF257
)
@JvmField
@@ -63,7 +73,9 @@ object ElementalTypes {
name = "Ice",
displayName = Component.translatable("cobblemon.type.ice"),
hue = 0x54F2F2,
textureXMultiplier = 5
textureXMultiplier = 5,
primaryColor = 0x41BAED,
secondaryColor = 0x91F4F9
)
@JvmField
@@ -71,7 +83,9 @@ object ElementalTypes {
name = "Fighting",
displayName = Component.translatable("cobblemon.type.fighting"),
hue = 0xEF565D,
textureXMultiplier = 6
textureXMultiplier = 6,
primaryColor = 0xC8356B,
secondaryColor = 0xF36A3F
)
@JvmField
@@ -79,7 +93,9 @@ object ElementalTypes {
name = "Poison",
displayName = Component.translatable("cobblemon.type.poison"),
hue = 0xD651FF,
textureXMultiplier = 7
textureXMultiplier = 7,
primaryColor = 0x7B41DD,
secondaryColor = 0xE568F4
)
@JvmField
@@ -87,7 +103,9 @@ object ElementalTypes {
name = "Ground",
displayName = Component.translatable("cobblemon.type.ground"),
hue = 0xF4A453,
textureXMultiplier = 8
textureXMultiplier = 8,
primaryColor = 0xE08835,
secondaryColor = 0xFFD057
)
@JvmField
@@ -95,7 +113,9 @@ object ElementalTypes {
name = "Flying",
displayName = Component.translatable("cobblemon.type.flying"),
hue = 0xB8B2FF,
textureXMultiplier = 9
textureXMultiplier = 9,
primaryColor = 0x6083F6,
secondaryColor = 0xD5CCFA
)
@JvmField
@@ -103,7 +123,9 @@ object ElementalTypes {
name = "Psychic",
displayName = Component.translatable("cobblemon.type.psychic"),
hue = 0xFF5E9E,
textureXMultiplier = 10
textureXMultiplier = 10,
primaryColor = 0xE553CE,
secondaryColor = 0xF69DD5
)
@JvmField
@@ -111,7 +133,9 @@ object ElementalTypes {
name = "Bug",
displayName = Component.translatable("cobblemon.type.bug"),
hue = 0xD3D319,
textureXMultiplier = 11
textureXMultiplier = 11,
primaryColor = 0x9FBE1B,
secondaryColor = 0xE9FB5B
)
@JvmField
@@ -119,7 +143,9 @@ object ElementalTypes {
name = "Rock",
displayName = Component.translatable("cobblemon.type.rock"),
hue = 0xB7A16E,
textureXMultiplier = 12
textureXMultiplier = 12,
primaryColor = 0x9F814A,
secondaryColor = 0xEECB7A
)
@JvmField
@@ -127,7 +153,9 @@ object ElementalTypes {
name = "Ghost",
displayName = Component.translatable("cobblemon.type.ghost"),
hue = 0x9C80F7,
textureXMultiplier = 13
textureXMultiplier = 13,
primaryColor = 0x4F57DD,
secondaryColor = 0xC182F1
)
@JvmField
@@ -135,7 +163,9 @@ object ElementalTypes {
name = "Dragon",
displayName = Component.translatable("cobblemon.type.dragon"),
hue = 0x7580FF,
textureXMultiplier = 14
textureXMultiplier = 14,
primaryColor = 0x4E68E2,
secondaryColor = 0x47C7F7
)
@JvmField
@@ -143,7 +173,9 @@ object ElementalTypes {
name = "Dark",
displayName = Component.translatable("cobblemon.type.dark"),
hue = 0x587DA0,
textureXMultiplier = 15
textureXMultiplier = 15,
primaryColor = 0x465B99,
secondaryColor = 0x689EBF
)
@JvmField
@@ -151,7 +183,9 @@ object ElementalTypes {
name = "Steel",
displayName = Component.translatable("cobblemon.type.steel"),
hue = 0xABD1F4,
textureXMultiplier = 16
textureXMultiplier = 16,
primaryColor = 0x628CB9,
secondaryColor = 0xD6F7F3
)
@JvmField
@@ -159,16 +193,20 @@ object ElementalTypes {
name = "Fairy",
displayName = Component.translatable("cobblemon.type.fairy"),
hue = 0xFF7FE5,
textureXMultiplier = 17
textureXMultiplier = 17,
primaryColor = 0xE2678C,
secondaryColor = 0xF5A7B7
)
@JvmStatic
fun register(name: String, displayName: MutableComponent, hue: Int, textureXMultiplier: Int): ElementalType {
fun register(name: String, displayName: MutableComponent, hue: Int, textureXMultiplier: Int, primaryColor: Int, secondaryColor: Int): ElementalType {
return ElementalType(
name = name,
displayName = displayName,
hue = hue,
textureXMultiplier = textureXMultiplier
textureXMultiplier = textureXMultiplier,
primaryColor = primaryColor,
secondaryColor = secondaryColor
).also {
allTypes.add(it)
}
@@ -273,4 +311,4 @@ object ElementalTypes {
@JvmName("getFAIRY")
@Deprecated("Use ElementalTypes.FAIRY, provided for backwards compatibility until Cobblemon 1.8.")
fun getFAIRY() = FAIRY
}
}
@@ -21,6 +21,7 @@ import net.minecraft.world.level.block.Blocks
import net.minecraft.world.level.block.DirectionalBlock
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.StateDefinition
import net.minecraft.world.level.block.state.properties.Property
import net.minecraft.world.phys.shapes.CollisionContext
import net.minecraft.world.phys.shapes.VoxelShape
@@ -42,6 +43,8 @@ abstract class GrowableStoneBlock(
val nextStage: Block?
) : DirectionalBlock(settings) {
abstract val growthChance: Int
private val upShape: VoxelShape = Block.box(
xzOffset.toDouble(),
0.0,
@@ -95,20 +98,26 @@ abstract class GrowableStoneBlock(
.setValue(FACING, Direction.DOWN))
}
abstract fun canGrow(pos: BlockPos, world: BlockGetter): Boolean
abstract fun canGrow(state: BlockState, pos: BlockPos, world: BlockGetter): Boolean
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
builder.add(FACING)
}
override fun isRandomlyTicking(state: BlockState): Boolean = stage < MAX_STAGE
override fun randomTick(state: BlockState, world: ServerLevel, pos: BlockPos, random: RandomSource) {
if (world.random.nextInt(5) == 0 && canGrow(pos, world)) {
if (world.random.nextInt(growthChance) == 0 && canGrow(state, pos, world)) {
val block = nextStage
if (block != null) {
val newState = block.defaultBlockState().setValue(FACING, state.getValue(FACING)) as BlockState
var newState = block.defaultBlockState()
@Suppress("UNCHECKED_CAST")
for (property in state.properties) {
if (newState.hasProperty(property)) {
newState = newState.setValue(property as Property<Comparable<Any>>, state.getValue(property) as Comparable<Any>)
}
}
world.setBlockAndUpdate(pos, newState)
}
}
@@ -155,14 +164,4 @@ abstract class GrowableStoneBlock(
else -> upShape
}
}
companion object {
const val STAGE_0 = 0
const val STAGE_1 = 1
const val STAGE_2 = 2
const val STAGE_3 = 3
const val MAX_STAGE = STAGE_3
const val MIN_STAGE = STAGE_0
}
}
@@ -16,6 +16,11 @@ import com.mojang.serialization.MapCodec
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.util.StringRepresentable
import net.minecraft.world.InteractionHand
import net.minecraft.world.InteractionResult
import net.minecraft.world.ItemInteractionResult
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.context.BlockPlaceContext
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.Level
@@ -28,6 +33,7 @@ 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.EnumProperty
import net.minecraft.world.level.pathfinder.PathComputationType
import net.minecraft.world.phys.BlockHitResult
import net.minecraft.world.phys.shapes.CollisionContext
import net.minecraft.world.phys.shapes.Shapes
import net.minecraft.world.phys.shapes.VoxelShape
@@ -35,7 +41,8 @@ import net.minecraft.world.phys.shapes.VoxelShape
class MonitorBlock(settings: Properties) : MultiblockBlock(settings) {
init {
registerDefaultState(stateDefinition.any()
.setValue(HORIZONTAL_FACING, Direction.NORTH))
.setValue(HORIZONTAL_FACING, Direction.NORTH)
.setValue(SCREEN, MonitorScreen.OFF))
}
override fun createMultiBlockEntity(
@@ -55,6 +62,47 @@ class MonitorBlock(settings: Properties) : MultiblockBlock(settings) {
return defaultBlockState().setValue(HORIZONTAL_FACING, blockPlaceContext.horizontalDirection)
}
override fun useWithoutItem(
state: BlockState,
level: Level,
pos: BlockPos,
player: Player,
hit: BlockHitResult
): InteractionResult {
val entity = level.getBlockEntity(pos) as? FossilMultiblockEntity ?: return InteractionResult.PASS
return entity.handleUseWithoutItem(state, level, pos, player)
}
override fun useItemOn(
stack: ItemStack,
state: BlockState,
level: Level,
pos: BlockPos,
player: Player,
hand: InteractionHand,
hit: BlockHitResult
): ItemInteractionResult {
val entity = level.getBlockEntity(pos) as? FossilMultiblockEntity
?: return ItemInteractionResult.SKIP_DEFAULT_BLOCK_INTERACTION
val result = entity.handleUseItem(stack, state, level, pos, player, hand)
if (result == ItemInteractionResult.PASS_TO_DEFAULT_BLOCK_INTERACTION) {
return if (entity.handleUseWithoutItem(state, level, pos, player).consumesAction()) {
ItemInteractionResult.sidedSuccess(level.isClientSide)
} else {
ItemInteractionResult.PASS_TO_DEFAULT_BLOCK_INTERACTION
}
}
return result
}
override fun onRemove(state: BlockState, level: Level, pos: BlockPos, newState: BlockState, movedByPiston: Boolean) {
if (state.block != newState.block && !level.isClientSide && !movedByPiston) {
val entity = level.getBlockEntity(pos) as? FossilMultiblockEntity
entity?.dropDisk(level, pos, state)
}
super.onRemove(state, level, pos, newState, movedByPiston)
}
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
builder.add(HORIZONTAL_FACING)
builder.add(SCREEN)
@@ -143,8 +191,27 @@ class MonitorBlock(settings: Properties) : MultiblockBlock(settings) {
BLUE_PROGRESS_7,
BLUE_PROGRESS_8,
BLUE_PROGRESS_9,
GREEN_PROGRESS_9;
GREEN_PROGRESS_9,
TM_NORMAL,
TM_FIRE,
TM_WATER,
TM_GRASS,
TM_ELECTRIC,
TM_ICE,
TM_FIGHTING,
TM_POISON,
TM_GROUND,
TM_FLYING,
TM_PSYCHIC,
TM_BUG,
TM_ROCK,
TM_GHOST,
TM_DRAGON,
TM_DARK,
TM_STEEL,
TM_FAIRY,
MUSIC;
override fun getSerializedName(): String = this.name.lowercase()
}
}
}
@@ -0,0 +1,114 @@
/*
* 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.entity.TMShelfBlockEntity
import com.mojang.serialization.MapCodec
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.world.InteractionHand
import net.minecraft.world.InteractionResult
import net.minecraft.world.ItemInteractionResult
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.context.BlockPlaceContext
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.*
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.StateDefinition
import net.minecraft.world.level.block.state.properties.BooleanProperty
import net.minecraft.world.level.block.state.properties.DirectionProperty
import net.minecraft.world.Containers
import net.minecraft.world.phys.BlockHitResult
import net.minecraft.world.phys.shapes.VoxelShape
class TMShelfBlock(properties: Properties) : BaseEntityBlock(properties) {
companion object {
val CODEC: MapCodec<TMShelfBlock> = simpleCodec(::TMShelfBlock)
val FACING: DirectionProperty = HorizontalDirectionalBlock.FACING
val SLOT_OCCUPIED_PROPERTIES = (0 until 14).map {
BooleanProperty.create("slot_${it}_occupied")
}
}
init {
val baseState = stateDefinition.any().setValue(FACING, Direction.NORTH)
registerDefaultState(SLOT_OCCUPIED_PROPERTIES.fold(baseState) { state, prop ->
state.setValue(prop, false)
})
}
override fun codec() = CODEC
override fun newBlockEntity(pos: BlockPos, state: BlockState): BlockEntity {
return TMShelfBlockEntity(pos, state)
}
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
builder.add(FACING)
SLOT_OCCUPIED_PROPERTIES.forEach { builder.add(it) }
}
override fun getRenderShape(state: BlockState): RenderShape = RenderShape.MODEL
override fun getStateForPlacement(ctx: BlockPlaceContext): BlockState {
return defaultBlockState().setValue(FACING, ctx.horizontalDirection.opposite)
}
override fun useWithoutItem(
state: BlockState, level: Level, pos: BlockPos,
player: Player, hit: BlockHitResult
): InteractionResult {
val entity = level.getBlockEntity(pos) as? TMShelfBlockEntity ?: return InteractionResult.PASS
return entity.handleUseWithoutItem(state, level, pos, player, hit)
}
override fun useItemOn(
stack: ItemStack, state: BlockState, level: Level, pos: BlockPos,
player: Player, hand: InteractionHand, hit: BlockHitResult
): ItemInteractionResult {
val entity = level.getBlockEntity(pos) as? TMShelfBlockEntity ?: return ItemInteractionResult.SKIP_DEFAULT_BLOCK_INTERACTION
return entity.handleUseItem(stack, state, level, pos, player, hit)
}
override fun hasAnalogOutputSignal(state: BlockState) = true
override fun getAnalogOutputSignal(state: BlockState, level: Level, pos: BlockPos): Int {
val entity = level.getBlockEntity(pos) as? TMShelfBlockEntity
return entity?.items?.count { !it.isEmpty }?.coerceIn(0, 14) ?: 0
}
override fun onRemove(state: BlockState, level: Level, pos: BlockPos, newState: BlockState, movedByPiston: Boolean) {
if (!state.`is`(newState.block)) {
if (!level.isClientSide && !movedByPiston) {
val entity = level.getBlockEntity(pos) as? TMShelfBlockEntity
if (entity != null) {
for (index in entity.items.indices) {
val stack = entity.items[index]
if (!stack.isEmpty) {
Containers.dropItemStack(level, pos.x + 0.5, pos.y + 0.5, pos.z + 0.5, stack)
entity.items[index] = ItemStack.EMPTY
}
}
level.updateNeighbourForOutputSignal(pos, this)
}
}
super.onRemove(state, level, pos, newState, movedByPiston)
} else {
super.onRemove(state, level, pos, newState, movedByPiston)
}
}
override fun getShape(
state: BlockState, level: BlockGetter,
pos: BlockPos, context: net.minecraft.world.phys.shapes.CollisionContext
): VoxelShape = Block.box(0.0, 0.0, 0.0, 16.0, 16.0, 16.0)
}
@@ -44,15 +44,25 @@ class TumblestoneBlock(
PrimitiveCodec.INT.fieldOf("xzOffset").forGetter { it.xzOffset },
Block.CODEC.fieldOf("nextStage").forGetter { it.nextStage }
).apply(it, ::TumblestoneBlock) }
const val STAGE_0 = 0
const val STAGE_1 = 1
const val STAGE_2 = 2
const val STAGE_3 = 3
const val MAX_STAGE = STAGE_3
const val MIN_STAGE = STAGE_0
}
override val growthChance = 5
init {
registerDefaultState(stateDefinition.any()
.setValue(FACING, Direction.DOWN)
.setValue(WATERLOGGED, false))
}
override fun canGrow(pos: BlockPos, world: BlockGetter): Boolean {
override fun canGrow(state: BlockState, pos: BlockPos, world: BlockGetter): Boolean {
if (stage == MAX_STAGE) return false
val iterator: Iterator<BlockPos> =
BlockPos.betweenClosed(pos.offset(-1, -1, -1), pos.offset(1, 1, 1))
@@ -66,6 +76,8 @@ class TumblestoneBlock(
return true
}
override fun isRandomlyTicking(state: BlockState): Boolean = stage < MAX_STAGE
override fun codec(): MapCodec<out DirectionalBlock> {
return CODEC
}
@@ -0,0 +1,23 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.block
import com.cobblemon.mod.common.CobblemonBlocks
import com.cobblemon.mod.common.CobblemonItems
import net.minecraft.world.item.Item
import net.minecraft.world.level.block.Block
class TypeGemBlock(
settings: Properties
) : Block(settings) {
companion object {
}
}
@@ -0,0 +1,284 @@
/*
* 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.CobblemonItems
import com.cobblemon.mod.common.api.tags.CobblemonBlockTags
import com.mojang.serialization.MapCodec
import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.core.component.DataComponents
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerLevel
import net.minecraft.util.RandomSource
import net.minecraft.world.item.BlockItem
import net.minecraft.world.item.Item
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Items
import net.minecraft.world.item.component.BlockItemStateProperties
import net.minecraft.world.item.component.CustomModelData
import net.minecraft.world.item.context.BlockPlaceContext
import net.minecraft.world.item.enchantment.Enchantments
import net.minecraft.world.item.enchantment.ItemEnchantments
import net.minecraft.world.level.LevelAccessor
import net.minecraft.world.level.LevelReader
import net.minecraft.world.level.block.AirBlock
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.Blocks
import net.minecraft.world.level.block.DirectionalBlock
import net.minecraft.world.level.block.Mirror
import net.minecraft.world.level.block.Rotation
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.StateDefinition
import net.minecraft.world.level.block.state.properties.BooleanProperty
import net.minecraft.world.level.block.state.properties.DirectionProperty
import net.minecraft.world.level.block.state.properties.IntegerProperty
import net.minecraft.world.level.material.PushReaction
import net.minecraft.world.level.storage.loot.LootContext
import net.minecraft.world.level.storage.loot.LootParams
import net.minecraft.world.level.storage.loot.parameters.LootContextParams
import net.minecraft.world.phys.shapes.Shapes
import net.minecraft.world.phys.shapes.VoxelShape
import javax.swing.text.html.HTML.Attribute.SHAPES
import kotlin.io.path.Path
class TypeGemClusterBlock(
settings: Properties,
val nextStage: Block,
val dropItemId: ResourceLocation
) : DirectionalBlock(settings.pushReaction(PushReaction.DESTROY)) {
companion object {
val CODEC: MapCodec<TypeGemClusterBlock> = RecordCodecBuilder.mapCodec { instance ->
instance.group(
propertiesCodec(),
Block.CODEC.fieldOf("nextStage").forGetter { it.nextStage },
ResourceLocation.CODEC.fieldOf("dropItem").forGetter { it.dropItemId }
).apply(instance) { settings, nextStage, dropItemId ->
TypeGemClusterBlock(settings, nextStage, dropItemId).also {
gemToClusterMap[nextStage] = it
}
}
}
val gemToClusterMap: MutableMap<Block, TypeGemClusterBlock> = mutableMapOf()
val SHOULD_GROW: BooleanProperty = BooleanProperty.create("should_grow")
val FACING: DirectionProperty = DirectionalBlock.FACING
val STAGE: IntegerProperty = IntegerProperty.create("stage", 0, 3)
val STUNTED: BooleanProperty = BooleanProperty.create("stunted")
fun clusterFromGemBlock(gemBlock: Block): TypeGemClusterBlock? {
return gemToClusterMap[gemBlock]
}
}
init {
registerDefaultState(stateDefinition.any()
.setValue(FACING, Direction.UP)
.setValue(SHOULD_GROW, true)
.setValue(STAGE, 0)
.setValue(STUNTED, false)
)
}
override fun getStateForPlacement(context: BlockPlaceContext): BlockState? {
val facing = context.clickedFace
val world = context.level
val pos = context.clickedPos.relative(facing.opposite)
val blockBelow = world.getBlockState(pos).block
val shouldGrow = blockBelow is TypeGemCoreBlock
return defaultBlockState()
.setValue(FACING, facing)
.setValue(SHOULD_GROW, shouldGrow)
.setValue(STAGE, 0)
.setValue(STUNTED, false)
}
override fun isRandomlyTicking(state: BlockState): Boolean = state.getValue(SHOULD_GROW)
override fun randomTick(state: BlockState, level: ServerLevel, pos: BlockPos, random: RandomSource) {
advanceGrowth(state, level, pos, random)
}
fun advanceGrowth(state: BlockState, level: LevelAccessor, pos: BlockPos, random: RandomSource) {
if (!state.getValue(SHOULD_GROW)) return
val currentStage = state.getValue(STAGE)
if (currentStage < 3) {
// Progress through stages regardless of STUNTED
level.setBlock(pos, state.setValue(STAGE, currentStage + 1), Block.UPDATE_ALL)
return
}
// At STAGE_3
val isStunted = state.getValue(STUNTED)
val facing = state.getValue(FACING)
if (isStunted) {
// println("[TypeGemClusterBlock] Cluster at $pos is STUNTED at STAGE_3. Finalizing growth.")
level.setBlock(pos, state.setValue(SHOULD_GROW, false), Block.UPDATE_ALL)
return
}
// Log type of cluster based on nextStage
val clusterType = level.registryAccess()
.registryOrThrow(BuiltInRegistries.BLOCK.key())
.getKey(nextStage)
.toString()
// println("[TypeGemClusterBlock] Attempting to grow into TypeGemBlock at $pos")
// println("[TypeGemClusterBlock] Cluster type: $clusterType")
// Check neighboring gem blocks
val hasConflict = Direction.entries
.filter { it != facing.opposite }
.any { dir ->
val neighborPos = pos.relative(dir)
val neighborState = level.getBlockState(neighborPos)
val neighborBlockId = BuiltInRegistries.BLOCK.getKey(neighborState.block)
// println("[TypeGemClusterBlock] Neighbor at $dir -> $neighborBlockId")
isGem(neighborState)
}
if (hasConflict) {
// println("[TypeGemClusterBlock] Found nearby TypeGemBlock(s). Stunting cluster at $pos.")
level.setBlock(
pos,
state.setValue(STUNTED, true).setValue(SHOULD_GROW, false),
Block.UPDATE_ALL
)
} else {
// println("[TypeGemClusterBlock] No conflicts. Converting cluster to TypeGemBlock.")
var nextState = nextStage.defaultBlockState()
if (nextState.hasProperty(FACING)) {
nextState = nextState.setValue(FACING, facing)
}
level.setBlock(pos, nextState, Block.UPDATE_ALL)
attachDecorativeClusters(level, pos, random)
}
}
private fun isGem(state: BlockState): Boolean {
return state.`is`(CobblemonBlockTags.TYPE_GEM_BLOCKS)
}
private fun attachDecorativeClusters(level: LevelAccessor, gemPos: BlockPos, random: RandomSource) {
val openDirections = Direction.entries.filter { direction ->
level.getBlockState(gemPos.relative(direction)).isAir
}.toMutableList()
if (openDirections.isEmpty()) {
return
}
val minClusters = maxOf(1, (openDirections.size * 0.75f).toInt())
val clusterCount = random.nextInt(minClusters, openDirections.size + 1)
repeat(clusterCount) {
if (openDirections.isEmpty()) return@repeat
val index = random.nextInt(openDirections.size)
val direction = openDirections.removeAt(index)
val clusterPos = gemPos.relative(direction)
val stage = random.nextInt(0, 4)
val clusterState = defaultBlockState()
.setValue(FACING, direction)
.setValue(STAGE, stage)
.setValue(SHOULD_GROW, false) // so we can reduce lag... hopefully
.setValue(STUNTED, false)
level.setBlock(clusterPos, clusterState, Block.UPDATE_ALL)
}
}
override fun getDrops(state: BlockState, params: LootParams.Builder): List<ItemStack> {
val drops = super.getDrops(state, params)
for (stack in drops) {
val blockItem = stack.item as? BlockItem ?: continue
if (blockItem.block !is TypeGemClusterBlock) continue
stack.set(
DataComponents.BLOCK_STATE,
BlockItemStateProperties.EMPTY
.with(STAGE, state.getValue(STAGE))
.with(SHOULD_GROW, false)
.with(STUNTED, state.getValue(STUNTED))
)
stack.set(
DataComponents.CUSTOM_MODEL_DATA,
CustomModelData(state.getValue(STAGE))
)
}
return drops
}
override fun getShape(
state: BlockState,
level: net.minecraft.world.level.BlockGetter,
pos: BlockPos,
context: net.minecraft.world.phys.shapes.CollisionContext
): VoxelShape {
val stage = state.getValue(STAGE)
val facing = state.getValue(FACING)
val (min, max) = when (stage) {
0 -> 5.0 to 11.0
1 -> 4.0 to 12.0
2 -> 3.0 to 13.0
3 -> 2.0 to 14.0
else -> return Shapes.empty()
}
return when (facing) {
Direction.UP -> Shapes.box(min / 16, 0.0, min / 16, max / 16, (min + 1) / 16, max / 16)
Direction.DOWN -> Shapes.box(min / 16, (16 - (min + 1)) / 16, min / 16, max / 16, 1.0, max / 16)
Direction.NORTH -> Shapes.box(min / 16, min / 16, (16 - (min + 1)) / 16, max / 16, max / 16, 1.0)
Direction.SOUTH -> Shapes.box((16 - max) / 16, min / 16, 0.0, (16 - min) / 16, max / 16, (min + 1) / 16)
Direction.WEST -> Shapes.box((16 - (min + 1)) / 16, min / 16, min / 16, 1.0, max / 16, max / 16)
Direction.EAST -> Shapes.box(0.0, min / 16, min / 16, (min + 1) / 16, max / 16, max / 16)
}
}
override fun updateShape(state: BlockState, direction: Direction, neighborState: BlockState, level: LevelAccessor, pos: BlockPos, neighborPos: BlockPos): BlockState {
val facing = state.getValue(FACING)
if (direction == facing.opposite && !canSurvive(state, level, pos)) {
return Blocks.AIR.defaultBlockState()
}
return super.updateShape(state, direction, neighborState, level, pos, neighborPos)
}
override fun rotate(state: BlockState, rotation: Rotation): BlockState {
return state.setValue(FACING, rotation.rotate(state.getValue(FACING)))
}
override fun mirror(state: BlockState, mirror: Mirror): BlockState {
return state.rotate(mirror.getRotation(state.getValue(FACING)))
}
override fun canSurvive(state: BlockState, level: LevelReader, pos: BlockPos): Boolean {
val supportPos = pos.relative(state.getValue(FACING).opposite)
val supportBlock = level.getBlockState(supportPos).block
return supportBlock !is AirBlock
}
override fun codec(): MapCodec<out DirectionalBlock> = CODEC
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
super.createBlockStateDefinition(builder)
builder.add(FACING, SHOULD_GROW, STAGE, STUNTED)
}
}
@@ -0,0 +1,215 @@
/*
* 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.CobblemonBlocks
import com.cobblemon.mod.common.api.tags.CobblemonBlockTags
import com.cobblemon.mod.common.block.TypeGemClusterBlock.Companion.SHOULD_GROW
import com.cobblemon.mod.common.block.TypeGemClusterBlock.Companion.STUNTED
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerLevel
import net.minecraft.util.RandomSource
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.LevelAccessor
import net.minecraft.world.level.WorldGenLevel
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.DirectionalBlock
import net.minecraft.world.level.block.state.BlockState
import java.util.*
class TypeGemCoreBlock(properties: Properties) : Block(properties) {
companion object {
const val MAX_CONNECTED_GEMS = 7
const val MIN_DISTANCE_BETWEEN_GEMS = 1
const val CONTINUATION_CHANCE = 0.8f
val BLOCK_TO_CLUSTER: Map<ResourceLocation, Block> by lazy {
mapOf(
ResourceLocation.parse("cobblemon:type_gem_block_normal") to CobblemonBlocks.TYPE_GEM_CLUSTER_NORMAL,
ResourceLocation.parse("cobblemon:type_gem_block_fire") to CobblemonBlocks.TYPE_GEM_CLUSTER_FIRE,
ResourceLocation.parse("cobblemon:type_gem_block_water") to CobblemonBlocks.TYPE_GEM_CLUSTER_WATER,
ResourceLocation.parse("cobblemon:type_gem_block_electric") to CobblemonBlocks.TYPE_GEM_CLUSTER_ELECTRIC,
ResourceLocation.parse("cobblemon:type_gem_block_grass") to CobblemonBlocks.TYPE_GEM_CLUSTER_GRASS,
ResourceLocation.parse("cobblemon:type_gem_block_ice") to CobblemonBlocks.TYPE_GEM_CLUSTER_ICE,
ResourceLocation.parse("cobblemon:type_gem_block_fighting") to CobblemonBlocks.TYPE_GEM_CLUSTER_FIGHTING,
ResourceLocation.parse("cobblemon:type_gem_block_poison") to CobblemonBlocks.TYPE_GEM_CLUSTER_POISON,
ResourceLocation.parse("cobblemon:type_gem_block_ground") to CobblemonBlocks.TYPE_GEM_CLUSTER_GROUND,
ResourceLocation.parse("cobblemon:type_gem_block_flying") to CobblemonBlocks.TYPE_GEM_CLUSTER_FLYING,
ResourceLocation.parse("cobblemon:type_gem_block_psychic") to CobblemonBlocks.TYPE_GEM_CLUSTER_PSYCHIC,
ResourceLocation.parse("cobblemon:type_gem_block_bug") to CobblemonBlocks.TYPE_GEM_CLUSTER_BUG,
ResourceLocation.parse("cobblemon:type_gem_block_rock") to CobblemonBlocks.TYPE_GEM_CLUSTER_ROCK,
ResourceLocation.parse("cobblemon:type_gem_block_ghost") to CobblemonBlocks.TYPE_GEM_CLUSTER_GHOST,
ResourceLocation.parse("cobblemon:type_gem_block_dragon") to CobblemonBlocks.TYPE_GEM_CLUSTER_DRAGON,
ResourceLocation.parse("cobblemon:type_gem_block_dark") to CobblemonBlocks.TYPE_GEM_CLUSTER_DARK,
ResourceLocation.parse("cobblemon:type_gem_block_steel") to CobblemonBlocks.TYPE_GEM_CLUSTER_STEEL,
ResourceLocation.parse("cobblemon:type_gem_block_fairy") to CobblemonBlocks.TYPE_GEM_CLUSTER_FAIRY
)
}
fun setStuntState(level: LevelAccessor, gemBlocks: List<Pair<BlockState, BlockPos>>, stunted: Boolean) {
for ((_, gemPos) in gemBlocks) {
for (dir in Direction.entries) {
val neighborPos = gemPos.relative(dir)
val neighborState = level.getBlockState(neighborPos)
if (neighborState.block is TypeGemClusterBlock) {
if (neighborState.hasProperty(STUNTED) && neighborState.getValue(STUNTED) != stunted) {
var updatedState = neighborState.setValue(STUNTED, stunted)
if (updatedState.hasProperty(SHOULD_GROW)) {
updatedState = updatedState.setValue(SHOULD_GROW, !stunted)
}
level.setBlock(neighborPos, updatedState, 3)
}
}
}
}
}
fun getConnectedGemBlocks(level: BlockGetter, pos: BlockPos): List<Pair<BlockState, BlockPos>> {
val connectedGems = mutableListOf<Pair<BlockState, BlockPos>>()
val visited = mutableSetOf<BlockPos>()
val queue: Queue<BlockPos> = LinkedList()
visited.add(pos)
queue.add(pos)
connectedGems.add(level.getBlockState(pos) to pos)
while (queue.isNotEmpty()) {
val current = queue.poll()
for (direction in Direction.entries) {
val neighbor = current.relative(direction)
if (visited.add(neighbor)) {
val state = level.getBlockState(neighbor)
if (state.`is`(CobblemonBlockTags.TYPE_GEM_BLOCKS)) {
connectedGems.add(state to neighbor)
queue.add(neighbor)
}
}
}
}
return connectedGems
}
}
override fun randomTick(state: BlockState, level: ServerLevel, pos: BlockPos, random: RandomSource) {
grow(level, pos, random)
}
fun forceGrow(level: WorldGenLevel, pos: BlockPos, random: RandomSource, percentage: Float) {
val desiredLength = MAX_CONNECTED_GEMS * percentage
while (true) {
val growInfo = grow(level, pos, random, true)
val grown = growInfo.first
val clusterSize = growInfo.second
if (!grown || clusterSize >= desiredLength) break
}
}
private fun grow(
level: WorldGenLevel,
pos: BlockPos,
random: RandomSource,
forced: Boolean = false
): Pair<Boolean, Int> {
val connectedGems = getConnectedGemBlocks(level, pos) // maybe we can store this and access it for better performance? check for changes in list rather than checking each time grow is called? idk
val gemCount = connectedGems.size
val overLimit = gemCount >= MAX_CONNECTED_GEMS
if (overLimit) {
// if there is no open air pockets to grow any new clusters then exit early
if (!hasBreathingRoom(level, connectedGems)) {
return Pair(false, gemCount)
}
// max type gem block count met, tell clusters to grow but not turn into blocks
setStuntState(level, connectedGems, true)
//return Pair(false, gemCount)
} else {
// make sure connected clusters know they can grow and turn into blocks
setStuntState(level, connectedGems, false)
}
val javaRandom = java.util.Random(random.nextLong())
for ((gemState, gemPos) in connectedGems.shuffled(javaRandom)) {
val registryKey = BuiltInRegistries.BLOCK.getKey(gemState.block)
val clusterBlock = BLOCK_TO_CLUSTER[registryKey]
for (dir in Direction.entries.shuffled(javaRandom)) {
val targetPos = gemPos.relative(dir)
val targetState = level.getBlockState(targetPos)
if (!targetState.isAir) continue
if (!isPositionValidForGrowth(level, targetPos)) continue
// Place gem cluster
if (clusterBlock != null) {
var placeState = clusterBlock.defaultBlockState()
.setValue(DirectionalBlock.FACING, dir)
.setValue(TypeGemClusterBlock.STAGE, 0)
.setValue(SHOULD_GROW, true)
level.setBlock(targetPos, placeState, UPDATE_ALL)
}
if (forced && clusterBlock != null) {
forceAdvanceClusterGrowth(level, targetPos, random)
}
val updatedGemCount = if (forced) getConnectedGemBlocks(level, pos).size else connectedGems.size
return Pair(true, updatedGemCount)
}
}
// println("[TypeGemCoreBlock] No valid spot found for gem placement.")
return Pair(false, connectedGems.size)
}
private fun forceAdvanceClusterGrowth(level: WorldGenLevel, clusterPos: BlockPos, random: RandomSource) {
repeat(5) {
val clusterState = level.getBlockState(clusterPos)
val clusterBlock = clusterState.block as? TypeGemClusterBlock ?: return
clusterBlock.advanceGrowth(clusterState, level, clusterPos, random)
}
}
private fun hasBreathingRoom(level: WorldGenLevel, gems: List<Pair<BlockState, BlockPos>>): Boolean {
for ((_, gemPos) in gems) {
for (dir in Direction.entries) {
if (level.getBlockState(gemPos.relative(dir)).isAir) {
return true
}
}
}
return false
}
private fun isPositionValidForGrowth(level: WorldGenLevel, pos: BlockPos): Boolean {
return Direction.entries.none { dir ->
val nearby = pos.relative(dir)
isGem(level.getBlockState(nearby)) &&
pos.distManhattan(nearby) < MIN_DISTANCE_BETWEEN_GEMS
}
}
private fun isGem(state: BlockState): Boolean {
return state.`is`(CobblemonBlockTags.TYPE_GEM_BLOCKS)
}
override fun isRandomlyTicking(state: BlockState): Boolean = true
}
@@ -12,15 +12,35 @@ import com.cobblemon.mod.common.CobblemonBlockEntities
import com.cobblemon.mod.common.api.multiblock.MultiblockEntity
import com.cobblemon.mod.common.api.multiblock.MultiblockStructure
import com.cobblemon.mod.common.api.multiblock.builder.MultiblockStructureBuilder
import com.cobblemon.mod.common.api.tms.TechnicalMachines
import com.cobblemon.mod.common.api.types.ElementalTypes
import com.cobblemon.mod.common.block.MonitorBlock
import com.cobblemon.mod.common.block.multiblock.FossilMultiblockStructure
import com.cobblemon.mod.common.item.components.TMMoveComponent
import com.cobblemon.mod.common.item.interactive.TechnicalMachineItem
import com.cobblemon.mod.common.util.DataKeys
import com.cobblemon.mod.common.util.tmList
import net.minecraft.core.BlockPos
import net.minecraft.core.HolderLookup
import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.NbtOps
import net.minecraft.nbt.NbtUtils
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.level.ChunkPos
import net.minecraft.world.Containers
import net.minecraft.world.InteractionResult
import net.minecraft.world.ItemInteractionResult
import net.minecraft.world.InteractionHand
import net.minecraft.world.entity.item.ItemEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Items
import net.minecraft.world.level.block.entity.BlockEntityType
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.HorizontalDirectionalBlock
import net.minecraft.world.level.block.state.properties.BlockStateProperties
import net.minecraft.world.level.gameevent.GameEvent
open class FossilMultiblockEntity(
pos: BlockPos,
@@ -28,8 +48,30 @@ open class FossilMultiblockEntity(
multiblockBuilder: MultiblockStructureBuilder,
type: BlockEntityType<*> = CobblemonBlockEntities.FOSSIL_MULTIBLOCK
) : MultiblockEntity(type, pos, state, multiblockBuilder) {
private val musicDiscs = setOf(
Items.MUSIC_DISC_13,
Items.MUSIC_DISC_CAT,
Items.MUSIC_DISC_BLOCKS,
Items.MUSIC_DISC_CHIRP,
Items.MUSIC_DISC_FAR,
Items.MUSIC_DISC_MALL,
Items.MUSIC_DISC_MELLOHI,
Items.MUSIC_DISC_STAL,
Items.MUSIC_DISC_STRAD,
Items.MUSIC_DISC_WARD,
Items.MUSIC_DISC_11,
Items.MUSIC_DISC_WAIT,
Items.MUSIC_DISC_OTHERSIDE,
Items.MUSIC_DISC_5,
Items.MUSIC_DISC_RELIC,
Items.MUSIC_DISC_PRECIPICE,
Items.MUSIC_DISC_CREATOR,
Items.MUSIC_DISC_CREATOR_MUSIC_BOX,
Items.MUSIC_DISC_PIGSTEP
)
override var masterBlockPos: BlockPos? = null
private var diskStack: ItemStack = ItemStack.EMPTY
override var multiblockStructure: MultiblockStructure? = null
set(structure) {
@@ -76,6 +118,164 @@ open class FossilMultiblockEntity(
} else {
null
}
diskStack = if (nbt.contains(DataKeys.MONITOR_DISK)) {
ItemStack.parse(registryLookup, nbt.get(DataKeys.MONITOR_DISK)).orElse(ItemStack.EMPTY)
} else {
ItemStack.EMPTY
}
updateMonitorScreen()
}
}
override fun saveAdditional(nbt: CompoundTag, registryLookup: HolderLookup.Provider) {
super.saveAdditional(nbt, registryLookup)
if (!diskStack.isEmpty) {
nbt.put(DataKeys.MONITOR_DISK, ItemStack.CODEC.encodeStart(NbtOps.INSTANCE, diskStack).orThrow)
}
}
fun handleUseItem(
stack: ItemStack,
state: BlockState,
level: Level,
pos: BlockPos,
player: Player,
hand: InteractionHand
): ItemInteractionResult {
val handStack = player.getItemInHand(hand)
if (!isValidDisk(handStack)) return ItemInteractionResult.PASS_TO_DEFAULT_BLOCK_INTERACTION
if (multiblockStructure != null) return ItemInteractionResult.PASS_TO_DEFAULT_BLOCK_INTERACTION
if (level.isClientSide) return ItemInteractionResult.SUCCESS
val newDisk = handStack.copyWithCount(1)
// In creative, prevent duplicate ejection spam when repeatedly inserting the same disk.
if (player.isCreative && !diskStack.isEmpty && ItemStack.isSameItemSameComponents(diskStack, newDisk)) {
return ItemInteractionResult.sidedSuccess(level.isClientSide)
}
val oldStack = diskStack
if (!oldStack.isEmpty) {
if (player is ServerPlayer) {
unlockTmForPlayer(player, oldStack)
}
ejectDiskStack(level, pos, state, oldStack)
}
diskStack = newDisk
if (!player.isCreative) {
handStack.shrink(1)
}
if (player is ServerPlayer) {
unlockTmForPlayer(player, diskStack)
}
updateMonitorScreen()
markUpdated(level, pos, state)
return ItemInteractionResult.sidedSuccess(level.isClientSide)
}
fun handleUseWithoutItem(
state: BlockState,
level: Level,
pos: BlockPos,
player: Player
): InteractionResult {
if (diskStack.isEmpty) return InteractionResult.PASS
if (multiblockStructure != null) return InteractionResult.PASS
if (player is ServerPlayer) {
unlockTmForPlayer(player, diskStack)
}
ejectDiskStack(level, pos, state, diskStack)
diskStack = ItemStack.EMPTY
updateMonitorScreen()
markUpdated(level, pos, state)
return InteractionResult.sidedSuccess(level.isClientSide)
}
fun dropDisk(level: Level, pos: BlockPos, state: BlockState) {
if (diskStack.isEmpty) return
ejectDiskStack(level, pos, state, diskStack)
diskStack = ItemStack.EMPTY
}
private fun ejectDiskStack(level: Level, pos: BlockPos, state: BlockState, stack: ItemStack) {
val facing = if (state.hasProperty(BlockStateProperties.HORIZONTAL_FACING)) {
state.getValue(HorizontalDirectionalBlock.FACING)
} else {
null
}
if (facing == null) {
Containers.dropItemStack(level, pos.x + 0.5, pos.y + 0.5, pos.z + 0.5, stack)
return
}
val ejectFacing = facing.opposite
val x = pos.x + 0.5 + ejectFacing.stepX * 0.6
val y = pos.y + 0.5
val z = pos.z + 0.5 + ejectFacing.stepZ * 0.6
val itemEntity = ItemEntity(level, x, y, z, stack)
itemEntity.setDeltaMovement(ejectFacing.stepX * 0.15, 0.05, ejectFacing.stepZ * 0.15)
level.addFreshEntity(itemEntity)
}
private fun isValidDisk(stack: ItemStack): Boolean {
return stack.item is TechnicalMachineItem || musicDiscs.contains(stack.item)
}
private fun unlockTmForPlayer(player: ServerPlayer, stack: ItemStack) {
if (stack.item !is TechnicalMachineItem) return
val move = TMMoveComponent.getTMMove(stack) ?: return
val tmId = TechnicalMachines.moveToTM[move]?.id ?: return
player.tmList()?.learn(listOf(tmId))
}
private fun updateMonitorScreen() {
val level = level ?: return
if (level.isClientSide) return
if (multiblockStructure != null) return
val state = blockState
if (!state.hasProperty(MonitorBlock.SCREEN)) return
val targetScreen = getDiskScreen()
if (state.getValue(MonitorBlock.SCREEN) != targetScreen) {
level.setBlockAndUpdate(blockPos, state.setValue(MonitorBlock.SCREEN, targetScreen))
}
}
private fun getDiskScreen(): MonitorBlock.MonitorScreen {
if (diskStack.isEmpty) return MonitorBlock.MonitorScreen.OFF
if (musicDiscs.contains(diskStack.item)) return MonitorBlock.MonitorScreen.MUSIC
val move = TMMoveComponent.getTMMove(diskStack) ?: return MonitorBlock.MonitorScreen.TM_NORMAL
return when (move.elementalType) {
ElementalTypes.FIRE -> MonitorBlock.MonitorScreen.TM_FIRE
ElementalTypes.WATER -> MonitorBlock.MonitorScreen.TM_WATER
ElementalTypes.GRASS -> MonitorBlock.MonitorScreen.TM_GRASS
ElementalTypes.ELECTRIC -> MonitorBlock.MonitorScreen.TM_ELECTRIC
ElementalTypes.ICE -> MonitorBlock.MonitorScreen.TM_ICE
ElementalTypes.FIGHTING -> MonitorBlock.MonitorScreen.TM_FIGHTING
ElementalTypes.POISON -> MonitorBlock.MonitorScreen.TM_POISON
ElementalTypes.GROUND -> MonitorBlock.MonitorScreen.TM_GROUND
ElementalTypes.FLYING -> MonitorBlock.MonitorScreen.TM_FLYING
ElementalTypes.PSYCHIC -> MonitorBlock.MonitorScreen.TM_PSYCHIC
ElementalTypes.BUG -> MonitorBlock.MonitorScreen.TM_BUG
ElementalTypes.ROCK -> MonitorBlock.MonitorScreen.TM_ROCK
ElementalTypes.GHOST -> MonitorBlock.MonitorScreen.TM_GHOST
ElementalTypes.DRAGON -> MonitorBlock.MonitorScreen.TM_DRAGON
ElementalTypes.DARK -> MonitorBlock.MonitorScreen.TM_DARK
ElementalTypes.STEEL -> MonitorBlock.MonitorScreen.TM_STEEL
ElementalTypes.FAIRY -> MonitorBlock.MonitorScreen.TM_FAIRY
else -> MonitorBlock.MonitorScreen.TM_NORMAL
}
}
private fun markUpdated(level: Level, pos: BlockPos, state: BlockState) {
level.sendBlockUpdated(pos, state, state, 3)
level.gameEvent(GameEvent.BLOCK_CHANGE, pos, GameEvent.Context.of(state))
setChanged()
}
}
@@ -0,0 +1,383 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.block.entity
import com.cobblemon.mod.common.CobblemonBlockEntities
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.moves.Moves
import com.cobblemon.mod.common.api.tms.TechnicalMachines
import com.cobblemon.mod.common.block.tmmachine.TMMachineBlock
import com.cobblemon.mod.common.block.tmmachine.TMMachineMenu
import com.cobblemon.mod.common.client.gui.tmmachine.TMMachineScreen
import com.cobblemon.mod.common.item.components.TMMoveComponent
import com.cobblemon.mod.common.util.itemRegistry
import net.minecraft.core.BlockPos
import net.minecraft.core.HolderLookup
import net.minecraft.core.NonNullList
import net.minecraft.network.chat.Component
import net.minecraft.network.protocol.Packet
import net.minecraft.network.protocol.game.ClientGamePacketListener
import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket
import net.minecraft.world.ContainerHelper
import net.minecraft.world.SimpleContainer
import net.minecraft.world.entity.player.Inventory
import net.minecraft.world.entity.player.Player
import net.minecraft.world.inventory.AbstractContainerMenu
import net.minecraft.world.item.ItemStack
import net.minecraft.world.level.block.entity.BaseContainerBlockEntity
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.nbt.CompoundTag
import net.minecraft.sounds.SoundEvent
import net.minecraft.sounds.SoundSource
import net.minecraft.world.Container
import net.minecraft.world.inventory.ContainerData
import net.minecraft.world.level.Level
class TMMachineBlockEntity(pos: BlockPos, state: BlockState) :
BaseContainerBlockEntity(CobblemonBlockEntities.TM_MACHINE, pos, state) {
companion object {
const val BURN_ACTIVE_TAG = "burnActive"
const val BURN_PROGRESS_TAG = "burnProgress"
const val REPEAT_PROCESS_TAG = "repeatProcess"
const val ACTIVE_MOVE_TAG = "activeMove"
const val BURN_PROGRESS_PER_TICK = 2
const val BURN_TOTAL_TIME = 200
const val TOTAL_PROCESS_TIME =
BURN_TOTAL_TIME + ((TMMachineScreen.CRAFT_TICKS + TMMachineScreen.RESET_DISC_TICKS) * BURN_PROGRESS_PER_TICK)
// Container data IDs
const val BURN_PROGRESS_INDEX = 0
const val BURN_ACTIVE_INDEX = 1
const val REPEAT_PROCESS_INDEX = 2
const val POSITION_X_INDEX = 3
const val POSITION_Y_INDEX = 4
const val POSITION_Z_INDEX = 5
fun serverTick(level: Level, pos: BlockPos, state: BlockState, blockEntity: TMMachineBlockEntity) {
if (level.isClientSide) return
// check if the TMM is powered by redstone. For toggling automation
val powered = level.hasNeighborSignal(pos)
val data = blockEntity.containerData
// Progress while active
if (data.get(BURN_ACTIVE_INDEX) == 1) {
val currentProgress = data.get(BURN_PROGRESS_INDEX)
if (currentProgress < TOTAL_PROCESS_TIME) {
val progressPerTick = if (currentProgress >= BURN_TOTAL_TIME) 1 else BURN_PROGRESS_PER_TICK
data.set(BURN_PROGRESS_INDEX, currentProgress + progressPerTick)
// comparator progress bar updates
level.updateNeighbourForOutputSignal(pos, state.block)
}
}
// auto start with batching when powered
if (powered && data.get(BURN_ACTIVE_INDEX) == 0) {
if (blockEntity.canCraftSelectedTM()) {
data.set(BURN_ACTIVE_INDEX, 1)
data.set(BURN_PROGRESS_INDEX, 0)
blockEntity.setChanged()
level.updateNeighbourForOutputSignal(pos, state.block)
}
}
val burnProgressValue = data.get(BURN_PROGRESS_INDEX)
val postCraftTicks = if (burnProgressValue >= BURN_TOTAL_TIME) (burnProgressValue - BURN_TOTAL_TIME) else 0
if (postCraftTicks > 0) {
if (postCraftTicks == TMMachineScreen.CRAFT_TICKS) {
blockEntity.craftTM()
level.updateNeighbourForOutputSignal(pos, state.block)
}
if (postCraftTicks >= (TMMachineScreen.CRAFT_TICKS + TMMachineScreen.RESET_DISC_TICKS)) {
data.set(BURN_PROGRESS_INDEX, 0)
// use batch processing while powered with redstone
val keepRunning = powered && blockEntity.canCraftSelectedTM()
data.set(BURN_ACTIVE_INDEX, if (keepRunning) 1 else 0)
blockEntity.setChanged()
level.updateNeighbourForOutputSignal(pos, state.block)
}
}
}
}
var tmMachineInventory = TMMachineBlockInventory(this)
var partialTicks: Float = 0F
var burnProgress: Int = 0
var burnActive: Boolean = false
var repeatProcess: Boolean = false
var activeMove: String = ""
var containerData: ContainerData = object : ContainerData {
override fun getCount() = 6
override fun get(index: Int): Int {
return when (index) {
BURN_PROGRESS_INDEX -> burnProgress
BURN_ACTIVE_INDEX -> if (burnActive) 1 else 0
REPEAT_PROCESS_INDEX -> if (repeatProcess) 1 else 0
POSITION_X_INDEX -> blockPos.x
POSITION_Y_INDEX -> blockPos.y
POSITION_Z_INDEX -> blockPos.z
else -> 0
}
}
override fun set(index: Int, value: Int) {
when (index) {
BURN_PROGRESS_INDEX -> burnProgress = value
BURN_ACTIVE_INDEX -> burnActive = value == 1
REPEAT_PROCESS_INDEX -> repeatProcess = value == 1
}
}
}
private fun canCraftSelectedTM(): Boolean {
val move = Moves.getByName(activeMove) ?: return false
val tm = TechnicalMachines.moveToTM[move] ?: return false
// We must have the Output be empty or stackable and not full
val resultStack = getItem(TMMachineMenu.RESULT_SLOT)
val crafted = ItemStack(CobblemonItems.TECHNICAL_MACHINE).also { TMMoveComponent.setTMMove(it, tm.moveName) }
val outputOk = resultStack.isEmpty || (
resultStack.count < resultStack.maxStackSize &&
ItemStack.isSameItemSameComponents(resultStack, crafted)
)
if (!outputOk) return false
// Blank disc slot
if (getItem(TMMachineMenu.BLANK_TM_SLOT).item != CobblemonItems.BLANK_TM) return false
// Ingredient slots
val recipes = tm.getClampedRecipe() ?: emptyList()
for ((index, recipe) in recipes.withIndex()) {
val slot = TMMachineMenu.INGREDIENT_SLOTS.first + index
val provided = getItem(slot)
if (!recipe.ingredient.test(provided) || provided.count < recipe.count) return false
}
return true
}
private fun craftTM() {
if (!(level?.isClientSide ?: true)) {
Moves.getByName(activeMove)?.let { move ->
TechnicalMachines.moveToTM[move]?.let { tm ->
val resultStack = getItem(TMMachineMenu.RESULT_SLOT)
if (!resultStack.isEmpty && resultStack.count >= resultStack.maxStackSize) return
// Craft the TM
val craftedTmStack = ItemStack(CobblemonItems.TECHNICAL_MACHINE)
.also { TMMoveComponent.setTMMove(it, tm.moveName) }
if (!resultStack.isEmpty && !ItemStack.isSameItemSameComponents(resultStack, craftedTmStack)) return
val blankTmStack = getItem(TMMachineMenu.BLANK_TM_SLOT)
if (blankTmStack.item != CobblemonItems.BLANK_TM) return
val recipes = tm.getClampedRecipe() ?: emptyList()
for ((index, recipe) in recipes.withIndex()) {
val slot = TMMachineMenu.INGREDIENT_SLOTS.first + index
val provided = getItem(slot)
if (!recipe.ingredient.test(provided) || provided.count < recipe.count) return
}
// Consume ingredients
blankTmStack.shrink(1)
for ((index, ingredient) in recipes.withIndex()) {
getItem(TMMachineMenu.INGREDIENT_SLOTS.first + index).shrink(ingredient.count)
}
// Add crafted TM to result slot
if (resultStack.isEmpty) {
setItem(TMMachineMenu.RESULT_SLOT, craftedTmStack)
} else if (resultStack.count < resultStack.maxStackSize) {
resultStack.grow(1)
}
tmMachineInventory.setChanged()
setChanged()
}
}
}
}
override fun createMenu(containerId: Int, inventory: Inventory): AbstractContainerMenu {
return TMMachineMenu(containerId, inventory, tmMachineInventory, containerData)
}
override fun saveAdditional(compound: CompoundTag, registries: HolderLookup.Provider) {
super.saveAdditional(compound, registries)
compound.putBoolean(BURN_ACTIVE_TAG, burnActive)
compound.putBoolean(REPEAT_PROCESS_TAG, repeatProcess)
compound.putInt(BURN_PROGRESS_TAG, burnProgress)
compound.putString(ACTIVE_MOVE_TAG, activeMove)
ContainerHelper.saveAllItems(compound, tmMachineInventory.items, registries)
}
override fun loadAdditional(compound: CompoundTag, registries: HolderLookup.Provider) {
super.loadAdditional(compound, registries)
burnActive = compound.getBoolean(BURN_ACTIVE_TAG)
repeatProcess = compound.getBoolean(REPEAT_PROCESS_TAG)
burnProgress = compound.getInt(BURN_PROGRESS_TAG)
activeMove = compound.getString(ACTIVE_MOVE_TAG)
ContainerHelper.loadAllItems(compound, tmMachineInventory.items, registries)
}
override fun getDisplayName(): Component = Component.translatable("block.cobblemon.tm_machine")
override fun getDefaultName(): Component = Component.translatable("cobblemon.container.tm_machine")
override fun getItems(): NonNullList<ItemStack> = tmMachineInventory.items
override fun setItems(items: NonNullList<ItemStack>) {
for (i in items.indices) {
tmMachineInventory.setItem(i, items[i])
}
}
override fun getUpdatePacket(): Packet<ClientGamePacketListener> = ClientboundBlockEntityDataPacket.create(this)
override fun getUpdateTag(registryLookup: HolderLookup.Provider): CompoundTag = saveWithoutMetadata(registryLookup)
override fun getContainerSize(): Int = tmMachineInventory.containerSize
override fun isEmpty(): Boolean = tmMachineInventory.isEmpty
override fun getItem(slot: Int): ItemStack = tmMachineInventory.getItem(slot)
override fun removeItem(slot: Int, amount: Int): ItemStack = tmMachineInventory.removeItem(slot, amount)
override fun removeItemNoUpdate(slot: Int): ItemStack = tmMachineInventory.removeItemNoUpdate(slot)
override fun setItem(slot: Int, stack: ItemStack) = tmMachineInventory.setItem(slot, stack)
override fun setChanged() {
var currentState = blockState
level?.getBlockState(worldPosition)?.let { state ->
currentState = state
var updated = false
if (state.hasProperty(TMMachineBlock.EMPTY)) {
val isEmpty = getItem(TMMachineMenu.BLANK_TM_SLOT).isEmpty
if (currentState.getValue(TMMachineBlock.EMPTY) != isEmpty) {
if (!isEmpty) playSound(CobblemonSounds.TM_MACHINE_PLACE_DISC, 0.5F)
currentState = currentState.setValue(TMMachineBlock.EMPTY, isEmpty)
updated = true
}
}
if (state.hasProperty(TMMachineBlock.ACTIVE)) {
val isActive = containerData.get(BURN_ACTIVE_INDEX) == 1
if (currentState.getValue(TMMachineBlock.ACTIVE) != isActive) {
currentState = currentState.setValue(TMMachineBlock.ACTIVE, isActive)
updated = true
}
}
if (updated) level?.setBlockAndUpdate(worldPosition, currentState)
}
// Notify the block entity's level that this block entity has changed
level?.blockEntityChanged(worldPosition)
// Mark the chunk containing this block entity as dirty, ensuring it is saved
level?.getChunkAt(worldPosition)?.isUnsaved = true
// Update Neighbours
level?.updateNeighborsAt(blockPos, currentState.block)
}
override fun stillValid(player: Player): Boolean = tmMachineInventory.stillValid(player)
override fun canPlaceItem(slot: Int, stack: ItemStack): Boolean = tmMachineInventory.canPlaceItem(slot, stack)
override fun canTakeItem(target: Container, slot: Int, stack: ItemStack): Boolean =
tmMachineInventory.canTakeItem(target, slot, stack)
fun playSound(soundEvent: SoundEvent, volume: Float = 1F, pitch: Float = 1F) {
level?.playSound(null, worldPosition, soundEvent, SoundSource.BLOCKS, volume, pitch)
}
fun isReadyToCraft(): Boolean = canCraftSelectedTM()
fun isOutputBlocked(): Boolean {
val level = level ?: return false
val move = Moves.getByName(activeMove) ?: return false
val tm = TechnicalMachines.moveToTM[move] ?: return false
val resultStack = getItem(TMMachineMenu.RESULT_SLOT)
if (resultStack.isEmpty) return false
val crafted = ItemStack(CobblemonItems.TECHNICAL_MACHINE)
.also { TMMoveComponent.setTMMove(it, tm.moveName) }
// If wrong TM in output OR output is full
if (!ItemStack.isSameItemSameComponents(resultStack, crafted)) return true
return resultStack.count >= resultStack.maxStackSize
}
class TMMachineBlockInventory(val blockEntity: TMMachineBlockEntity) : SimpleContainer(6) {
override fun canTakeItem(target: Container, slot: Int, stack: ItemStack) = slot == 0
override fun canPlaceItem(slot: Int, stack: ItemStack): Boolean {
val powered = blockEntity.level?.hasNeighborSignal(blockEntity.blockPos) == true
val allowAutomation = blockEntity.containerData.get(BURN_ACTIVE_INDEX) == 1 || powered
if (!allowAutomation) return false
Moves.getByName(blockEntity.activeMove)?.let {
val tm = TechnicalMachines.moveToTM[it] ?: return false
val item = stack.item
return when (slot) {
1 -> item == CobblemonItems.BLANK_TM
2, 3, 4 -> {
val recipe = tm.getClampedRecipe()
val recipeIndex = slot - 2
if (recipe == null || recipeIndex >= recipe.size) {
return false
}
recipe[recipeIndex].ingredient.test(stack)
}
else -> false
}
}
return false
}
override fun setChanged() {
super.setChanged()
blockEntity.setChanged()
val allowAutomation =
blockEntity.containerData.get(BURN_ACTIVE_INDEX) == 1 || blockEntity.containerData.get(
REPEAT_PROCESS_INDEX
) == 1
// force comparators to refresh if inventory changes and batch mode is on
if (allowAutomation) {
blockEntity.level?.updateNeighbourForOutputSignal(blockEntity.blockPos, blockEntity.blockState.block)
}
}
override fun startOpen(player: Player) {
blockEntity.startOpen(player)
}
override fun stopOpen(player: Player) {
blockEntity.stopOpen(player)
}
}
}
@@ -0,0 +1,302 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.block.entity
import com.cobblemon.mod.common.CobblemonBlockEntities
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.block.TMShelfBlock
import com.cobblemon.mod.common.item.interactive.TechnicalMachineItem
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.core.HolderLookup
import net.minecraft.core.NonNullList
import net.minecraft.nbt.CompoundTag
import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket
import net.minecraft.sounds.SoundEvent
import net.minecraft.sounds.SoundSource
import net.minecraft.world.ContainerHelper
import net.minecraft.world.Containers
import net.minecraft.world.InteractionResult
import net.minecraft.world.ItemInteractionResult
import net.minecraft.world.WorldlyContainer
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Items
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.phys.BlockHitResult
import net.minecraft.world.level.gameevent.GameEvent
import java.util.OptionalInt
import kotlin.math.pow
class TMShelfBlockEntity(pos: BlockPos, state: BlockState) : BlockEntity(CobblemonBlockEntities.TM_SHELF, pos, state), WorldlyContainer {
val items: NonNullList<ItemStack> = NonNullList.withSize(14, ItemStack.EMPTY)
var lastInteractedSlot: Int = -1
private val accessibleSlots = IntArray(14) { it }
private var lastNotePulseTick: Long = Long.MIN_VALUE
fun handleUseItem(stack: ItemStack, state: BlockState, level: Level, pos: BlockPos, player: Player, hit: BlockHitResult): ItemInteractionResult {
if (stack.isEmpty) return ItemInteractionResult.PASS_TO_DEFAULT_BLOCK_INTERACTION
val slot = getHitSlot(hit, state).orElse(-1)
if (slot !in 0 until 14) return ItemInteractionResult.SKIP_DEFAULT_BLOCK_INTERACTION
if (!isValidItem(stack)) return ItemInteractionResult.PASS_TO_DEFAULT_BLOCK_INTERACTION
val oldStack = items[slot]
if (!oldStack.isEmpty) {
if (!player.addItem(oldStack)) {
Containers.dropItemStack(level, pos.x + 0.5, pos.y + 0.5, pos.z + 0.5, oldStack)
}
}
items[slot] = stack.copyWithCount(1)
stack.shrink(1)
lastInteractedSlot = slot
updateBlockState(level, pos)
markUpdated()
return ItemInteractionResult.sidedSuccess(level.isClientSide)
}
fun handleUseWithoutItem(state: BlockState, level: Level, pos: BlockPos, player: Player, hit: BlockHitResult): InteractionResult {
val slot = getHitSlot(hit, state).orElse(-1)
if (slot !in 0 until 14 || items[slot].isEmpty) return InteractionResult.PASS
val removed = items[slot]
items[slot] = ItemStack.EMPTY
if (!player.addItem(removed)) {
Containers.dropItemStack(level, pos.x + 0.5, pos.y + 0.5, pos.z + 0.5, removed)
}
lastInteractedSlot = slot
updateBlockState(level, pos)
return InteractionResult.sidedSuccess(level.isClientSide)
}
private fun updateBlockState(level: Level, pos: BlockPos) {
val newState = TMShelfBlock.SLOT_OCCUPIED_PROPERTIES.fold(blockState) { acc, prop ->
val index = TMShelfBlock.SLOT_OCCUPIED_PROPERTIES.indexOf(prop)
acc.setValue(prop, !items[index].isEmpty)
}
level.setBlock(pos, newState, 3)
level.updateNeighbourForOutputSignal(pos, newState.block)
level.gameEvent(GameEvent.BLOCK_CHANGE, pos, GameEvent.Context.of(newState))
}
private fun getHitSlot(hit: BlockHitResult, state: BlockState): OptionalInt {
val facing = state.getValue(TMShelfBlock.FACING)
if (hit.direction != facing) {
return OptionalInt.empty()
}
val relative = hit.location.subtract(hit.blockPos.x.toDouble(), hit.blockPos.y.toDouble(), hit.blockPos.z.toDouble())
val (x, y) = when (facing) {
// Face-local X where 0 = visual left and 1 = visual right.
Direction.NORTH -> 1.0 - relative.x to relative.y
Direction.SOUTH -> relative.x to relative.y
Direction.WEST -> 1.0 - relative.z to relative.y
Direction.EAST -> relative.z to relative.y
else -> return OptionalInt.empty()
}
// Have interaction segments match the renderer areas visually
val leftSlotMinX = 1.0 / 16.0
val leftSlotMaxX = 7.0 / 16.0
val rightSlotMinX = 9.0 / 16.0
val rightSlotMaxX = 15.0 / 16.0
val slotMinY = 1.0 / 16.0
val slotMaxY = 15.0 / 16.0
val slotHeight = 2.0 / 16.0
val col = when {
x in leftSlotMinX..leftSlotMaxX -> 0
x in rightSlotMinX..rightSlotMaxX -> 1
else -> return OptionalInt.empty()
}
if (y < slotMinY || y > slotMaxY) {
return OptionalInt.empty()
}
val rowFromBottom = ((y - slotMinY) / slotHeight).toInt().coerceIn(0, 6)
val row = 6 - rowFromBottom
return OptionalInt.of(row * 2 + col)
}
fun isValidItem(stack: ItemStack): Boolean {
return stack.item is TechnicalMachineItem
|| stack.item == CobblemonItems.UPGRADE
|| stack.item == CobblemonItems.DUBIOUS_DISC
|| stack.item == Items.MUSIC_DISC_13
|| stack.item == Items.MUSIC_DISC_CAT
|| stack.item == Items.MUSIC_DISC_BLOCKS
|| stack.item == Items.MUSIC_DISC_CHIRP
|| stack.item == Items.MUSIC_DISC_FAR
|| stack.item == Items.MUSIC_DISC_MALL
|| stack.item == Items.MUSIC_DISC_MELLOHI
|| stack.item == Items.MUSIC_DISC_STAL
|| stack.item == Items.MUSIC_DISC_STRAD
|| stack.item == Items.MUSIC_DISC_WARD
|| stack.item == Items.MUSIC_DISC_11
|| stack.item == Items.MUSIC_DISC_WAIT
|| stack.item == Items.MUSIC_DISC_OTHERSIDE
|| stack.item == Items.MUSIC_DISC_5
|| stack.item == Items.MUSIC_DISC_RELIC
|| stack.item == Items.MUSIC_DISC_PRECIPICE
|| stack.item == Items.MUSIC_DISC_CREATOR
|| stack.item == Items.MUSIC_DISC_CREATOR_MUSIC_BOX
|| stack.item == Items.MUSIC_DISC_PIGSTEP
}
fun onNoteBlockPulse(level: Level, noteBlockPos: BlockPos, soundEvent: SoundEvent) {
val gameTime = level.gameTime
if (lastNotePulseTick == gameTime) {
return
}
lastNotePulseTick = gameTime
val octaveUp = !items[1].isEmpty
val octaveDown = !items[12].isEmpty
val octaveShift = when {
octaveUp && !octaveDown -> 12
octaveDown && !octaveUp -> -12
else -> 0
}
NOTE_SLOT_TO_SEMITONE.forEach { (slot, semitone) ->
if (items[slot].isEmpty) return@forEach
val pitch = 2.0.pow((semitone + octaveShift) / 12.0).toFloat()
level.playSound(null, noteBlockPos, soundEvent, SoundSource.BLOCKS, 3.0f, pitch)
}
}
fun markUpdated() {
level?.setBlock(blockPos, blockState, 3)
level?.sendBlockUpdated(blockPos, blockState, blockState, 3)
setChanged()
}
override fun clearContent() {
var changed = false
for (index in items.indices) {
if (!items[index].isEmpty) {
items[index] = ItemStack.EMPTY
changed = true
}
}
if (changed) {
level?.let { updateBlockState(it, blockPos) }
markUpdated()
}
}
override fun getContainerSize(): Int = items.size
override fun isEmpty(): Boolean = items.all { it.isEmpty }
override fun getItem(slot: Int): ItemStack {
if (slot !in items.indices) return ItemStack.EMPTY
return items[slot]
}
override fun removeItem(slot: Int, amount: Int): ItemStack {
val removed = ContainerHelper.removeItem(items, slot, amount)
if (!removed.isEmpty) {
if (items[slot].isEmpty) {
lastInteractedSlot = slot
}
level?.let { updateBlockState(it, blockPos) }
markUpdated()
}
return removed
}
override fun removeItemNoUpdate(slot: Int): ItemStack {
val removed = ContainerHelper.takeItem(items, slot)
if (!removed.isEmpty) {
level?.let { updateBlockState(it, blockPos) }
markUpdated()
}
return removed
}
override fun setItem(slot: Int, stack: ItemStack) {
if (slot !in items.indices) return
if (!stack.isEmpty && !isValidItem(stack)) return
items[slot] = if (stack.isEmpty) ItemStack.EMPTY else stack.copyWithCount(1)
lastInteractedSlot = slot
level?.let { updateBlockState(it, blockPos) }
markUpdated()
}
override fun stillValid(player: Player): Boolean {
return level?.getBlockEntity(blockPos) === this &&
player.distanceToSqr(blockPos.x + 0.5, blockPos.y + 0.5, blockPos.z + 0.5) <= 64.0
}
override fun getMaxStackSize(): Int = 1
override fun canPlaceItem(slot: Int, stack: ItemStack): Boolean {
return slot in items.indices && items[slot].isEmpty && isValidItem(stack)
}
override fun getSlotsForFace(side: Direction): IntArray = accessibleSlots
override fun canPlaceItemThroughFace(slot: Int, stack: ItemStack, direction: Direction?): Boolean {
return canPlaceItem(slot, stack)
}
override fun canTakeItemThroughFace(slot: Int, stack: ItemStack, direction: Direction): Boolean {
return slot in items.indices
}
override fun getUpdateTag(registries: HolderLookup.Provider): CompoundTag {
return saveWithoutMetadata(registries)
}
override fun getUpdatePacket(): ClientboundBlockEntityDataPacket {
return ClientboundBlockEntityDataPacket.create(this)
}
override fun saveAdditional(tag: CompoundTag, registries: HolderLookup.Provider) {
super.saveAdditional(tag, registries)
ContainerHelper.saveAllItems(tag, items, true, registries)
tag.putInt("LastInteractedSlot", lastInteractedSlot)
}
override fun loadAdditional(tag: CompoundTag, registries: HolderLookup.Provider) {
super.loadAdditional(tag, registries)
items.clear()
ContainerHelper.loadAllItems(tag, items, registries)
lastInteractedSlot = tag.getInt("LastInteractedSlot")
}
companion object {
// C=0 chromatic semitone mapping.
private val NOTE_SLOT_TO_SEMITONE = linkedMapOf(
0 to 5, // F
2 to 4, // E
4 to 3, // D#
6 to 2, // D
8 to 1, // C#
10 to 0, // C
3 to 11, // B
5 to 10, // A#
7 to 9, // A
9 to 8, // G#
11 to 7, // G
13 to 6 // F#
)
}
}
@@ -14,7 +14,9 @@ import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.multiblock.MultiblockEntity
import com.cobblemon.mod.common.api.multiblock.builder.MultiblockStructureBuilder
import com.cobblemon.mod.common.api.multiblock.condition.BlockRelativeCondition
import com.cobblemon.mod.common.block.MonitorBlock
import com.cobblemon.mod.common.block.RestorationTankBlock
import com.cobblemon.mod.common.block.entity.FossilMultiblockEntity
import com.cobblemon.mod.common.util.DataKeys
import com.cobblemon.mod.common.util.blockPositionsAsList
import net.minecraft.advancements.critereon.BlockPredicate
@@ -121,6 +123,13 @@ class FossilMultiblockBuilder(val centerPos: BlockPos) : MultiblockStructureBuil
val analyzerEntity = world.getBlockEntity(fossilAnalyzerPos) as? MultiblockEntity
val tankBaseEntity = world.getBlockEntity(restorationTankPos) as? MultiblockEntity
val tankTopEntity = world.getBlockEntity(restorationTankPos.above()) as? MultiblockEntity
if (monitorEntity is FossilMultiblockEntity) {
val monitorState = world.getBlockState(monitorPos)
monitorEntity.dropDisk(world, monitorPos, monitorState)
if (monitorState.hasProperty(MonitorBlock.SCREEN)) {
world.setBlockAndUpdate(monitorPos, monitorState.setValue(MonitorBlock.SCREEN, MonitorBlock.MonitorScreen.OFF))
}
}
val structure = FossilMultiblockStructure(monitorPos, fossilAnalyzerPos, restorationTankPos)
structure.tankConnectorDirection = dirsToCheck.filter {
@@ -168,4 +177,4 @@ class FossilMultiblockBuilder(val centerPos: BlockPos) : MultiblockStructureBuil
//lol thanks mojang for not allowing nbt puts to be chained
}
}
}
@@ -0,0 +1,255 @@
/*
* 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.tmmachine
import com.cobblemon.mod.common.CobblemonBlockEntities
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.block.entity.TMMachineBlockEntity
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.playSoundServer
import com.cobblemon.mod.common.util.toVec3d
import com.mojang.serialization.MapCodec
import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.Containers
import net.minecraft.world.InteractionResult
import net.minecraft.world.entity.Entity
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.context.BlockPlaceContext
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.BaseEntityBlock
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.RenderShape
import net.minecraft.world.level.block.SimpleWaterloggedBlock
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.entity.BlockEntityTicker
import net.minecraft.world.level.block.entity.BlockEntityType
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.StateDefinition
import net.minecraft.world.level.block.state.properties.BlockStateProperties
import net.minecraft.world.level.block.state.properties.BooleanProperty
import net.minecraft.world.level.gameevent.GameEvent
import net.minecraft.world.level.material.FluidState
import net.minecraft.world.level.material.Fluids
import net.minecraft.world.phys.BlockHitResult
import net.minecraft.world.phys.shapes.CollisionContext
import net.minecraft.world.phys.shapes.Shapes
import net.minecraft.world.phys.shapes.VoxelShape
import org.jetbrains.annotations.Nullable
class TMMachineBlock(properties: Properties) : BaseEntityBlock(properties), SimpleWaterloggedBlock {
companion object {
val EMPTY: BooleanProperty = BooleanProperty.create("empty")
val FACING = BlockStateProperties.HORIZONTAL_FACING
val WATERLOGGED = BlockStateProperties.WATERLOGGED
val ACTIVE: BooleanProperty = BooleanProperty.create("active")
val OPEN: BooleanProperty = BooleanProperty.create("open")
private val SHAPE: VoxelShape = Shapes.box(0.0, 0.0, 0.0, 1.0, 0.375, 1.0)
private val SHAPE_OPEN: VoxelShape = Shapes.box(0.0, 0.0, 0.0, 1.0, 0.3125, 1.0)
private val SHAPE_OPEN_NORTH: VoxelShape = Shapes.or(
Shapes.box(0.0, 0.0, 0.0, 1.0, 0.3125, 1.0),
Shapes.box(0.0, 0.3125, 0.8125, 1.0, 0.4375, 0.9375),
Shapes.box(0.0, 0.4375, 0.6875, 1.0, 0.5625, 0.8125),
Shapes.box(0.0, 0.5625, 0.5625, 1.0, 0.6875, 0.6875),
Shapes.box(0.0, 0.6875, 0.4375, 1.0, 0.8125, 0.5625),
Shapes.box(0.0, 0.8125, 0.3125, 1.0, 0.9375, 0.4375),
Shapes.box(0.0, 0.9375, 0.3125, 1.0, 1.0, 0.375)
)
private val SHAPE_OPEN_SOUTH: VoxelShape = Shapes.or(
Shapes.box(0.0, 0.0, 0.0, 1.0, 0.3125, 1.0),
Shapes.box(0.0, 0.3125, 0.0625, 1.0, 0.4375, 0.1875),
Shapes.box(0.0, 0.4375, 0.1875, 1.0, 0.5625, 0.3125),
Shapes.box(0.0, 0.5625, 0.3125, 1.0, 0.6875, 0.4375),
Shapes.box(0.0, 0.6875, 0.4375, 1.0, 0.8125, 0.5625),
Shapes.box(0.0, 0.8125, 0.5625, 1.0, 0.9375, 0.6875),
Shapes.box(0.0, 0.9375, 0.625, 1.0, 1.0, 0.6875)
)
private val SHAPE_OPEN_EAST: VoxelShape = Shapes.or(
Shapes.box(0.0, 0.0, 0.0, 1.0, 0.3125, 1.0),
Shapes.box(0.0625, 0.3125, 0.0, 0.1875, 0.4375, 1.0),
Shapes.box(0.1875, 0.4375, 0.0, 0.3125, 0.5625, 1.0),
Shapes.box(0.3125, 0.5625, 0.0, 0.4375, 0.6875, 1.0),
Shapes.box(0.4375, 0.6875, 0.0, 0.5625, 0.8125, 1.0),
Shapes.box(0.5625, 0.8125, 0.0, 0.6875, 0.9375, 1.0),
Shapes.box(0.625, 0.9375, 0.0, 0.6875, 1.0, 1.0)
)
private val SHAPE_OPEN_WEST: VoxelShape = Shapes.or(
Shapes.box(0.0, 0.0, 0.0, 1.0, 0.3125, 1.0),
Shapes.box(0.8125, 0.3125, 0.0, 0.9375, 0.4375, 1.0),
Shapes.box(0.6875, 0.4375, 0.0, 0.8125, 0.5625, 1.0),
Shapes.box(0.5625, 0.5625, 0.0, 0.6875, 0.6875, 1.0),
Shapes.box(0.4375, 0.6875, 0.0, 0.5625, 0.8125, 1.0),
Shapes.box(0.3125, 0.8125, 0.0, 0.4375, 0.9375, 1.0),
Shapes.box(0.3125, 0.9375, 0.0, 0.375, 1.0, 1.0)
)
val CODEC: MapCodec<TMMachineBlock> = RecordCodecBuilder.mapCodec { instance ->
instance.group(
Properties.CODEC.fieldOf("properties").forGetter { it.properties }
).apply(instance, ::TMMachineBlock)
}
}
init {
registerDefaultState(stateDefinition.any()
.setValue(FACING, Direction.NORTH)
.setValue(WATERLOGGED, false)
.setValue(ACTIVE, false)
.setValue(EMPTY, true))
}
override fun <T : BlockEntity> getTicker(level: Level, state: BlockState, blockEntityType: BlockEntityType<T>): BlockEntityTicker<T>? {
return createTicker(level, blockEntityType, CobblemonBlockEntities.TM_MACHINE)
}
@Nullable
protected fun <T : BlockEntity> createTicker(level: Level, serverType: BlockEntityType<T>, clientType: BlockEntityType<out TMMachineBlockEntity>): BlockEntityTicker<T>? {
return if (!level.isClientSide) createTickerHelper(serverType, clientType, TMMachineBlockEntity::serverTick)
else null
}
override fun fallOn(level: Level, state: BlockState, pos: BlockPos, entity: Entity, fallDistance: Float) {
if (!level.isClientSide && (entity is LivingEntity) &&
(entity.bbWidth * entity.bbWidth * entity.bbHeight > 0.512F) &&
(entity.y >= (pos.y + 0.55)) &&
state.hasProperty(OPEN) && state.getValue(OPEN)
) {
level.setBlockAndUpdate(pos, state.setValue(OPEN, false))
level.playSoundServer(position = pos.toVec3d(), sound = CobblemonSounds.TM_MACHINE_CLOSE, volume = 1F, pitch = 1F)
}
}
override fun newBlockEntity(pos: BlockPos, state: BlockState): BlockEntity = TMMachineBlockEntity(pos, state)
override fun getStateForPlacement(context: BlockPlaceContext): BlockState {
return defaultBlockState()
.setValue(FACING, context.horizontalDirection.opposite)
.setValue(WATERLOGGED, context.level.getFluidState(context.clickedPos).type == Fluids.WATER)
.setValue(EMPTY, true)
.setValue(ACTIVE, false)
.setValue(OPEN, false)
}
override fun useWithoutItem(state: BlockState, level: Level, pos: BlockPos, player: Player, hit: BlockHitResult): InteractionResult {
if (!level.isClientSide) {
if (player.isCrouching) {
if (state.hasProperty(OPEN)) {
val isOpen = state.getValue(OPEN)
level.setBlockAndUpdate(pos, state.setValue(OPEN, !isOpen))
level.playSoundServer(
position = pos.toVec3d(),
sound = if (!isOpen) CobblemonSounds.TM_MACHINE_CLOSE else CobblemonSounds.TM_MACHINE_OPEN,
volume = 1F,
pitch = 1F
)
}
} else {
val serverPlayer = player as? ServerPlayer
if (serverPlayer != null) {
val recipeId = cobblemonResource("tm_machine")
val recipeOpt = level.server?.recipeManager?.byKey(recipeId)
recipeOpt?.ifPresent { recipe ->
if (!serverPlayer.recipeBook.contains(recipe)) {
serverPlayer.awardRecipes(listOf(recipe))
}
}
}
val blockEntity = level.getBlockEntity(pos)
if (blockEntity is TMMachineBlockEntity) {
player.openMenu(blockEntity)
level.playSoundServer(
position = pos.toVec3d(),
sound = CobblemonSounds.TM_MACHINE_ON,
volume = 0.5F,
pitch = 1F
)
level.gameEvent(player, GameEvent.BLOCK_OPEN, pos)
}
}
}
return InteractionResult.sidedSuccess(level.isClientSide)
}
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
builder.add(FACING, WATERLOGGED, EMPTY, ACTIVE, OPEN)
}
override fun codec(): MapCodec<TMMachineBlock> = CODEC
override fun getRenderShape(state: BlockState): RenderShape = RenderShape.MODEL
override fun getFluidState(state: BlockState): FluidState =
if (state.getValue(WATERLOGGED)) Fluids.WATER.defaultFluidState() else super.getFluidState(state)
override fun getCollisionShape(state: BlockState, level: BlockGetter, pos: BlockPos, context: CollisionContext): VoxelShape {
return if (state.getValue(OPEN)) {
when (state.getValue(FACING)) {
Direction.NORTH -> SHAPE_OPEN_NORTH
Direction.SOUTH -> SHAPE_OPEN_SOUTH
Direction.WEST -> SHAPE_OPEN_WEST
Direction.EAST -> SHAPE_OPEN_EAST
else -> Shapes.empty()
}
} else SHAPE
}
override fun getShape(state: BlockState, getter: BlockGetter, pos: BlockPos, context: CollisionContext): VoxelShape = if (state.getValue(OPEN)) SHAPE_OPEN else SHAPE
override fun onRemove(state: BlockState, level: Level, pos: BlockPos, newState: BlockState, movedByPiston: Boolean) {
Containers.dropContentsOnDestroy(state, newState, level, pos)
super.onRemove(state, level, pos, newState, movedByPiston)
}
override fun isSignalSource(state: BlockState): Boolean = false
override fun getSignal(state: BlockState, level: BlockGetter, pos: BlockPos, direction: Direction): Int = 0
override fun hasAnalogOutputSignal(state: BlockState): Boolean = true
/*override fun getAnalogOutputSignal(state: BlockState, level: Level, pos: BlockPos): Int {
val be = level.getBlockEntity(pos) as? TMMachineBlockEntity ?: return 0
// Have comparator output act like a progress bar when burning because that might be super dope to see without going into the menu
val scaled = (be.burnProgress.toFloat() / TMMachineBlockEntity.TOTAL_PROCESS_TIME.toFloat() * 15f).toInt()
return scaled.coerceIn(0, 15)
}*/
override fun getAnalogOutputSignal(state: BlockState, level: Level, pos: BlockPos): Int {
val be = level.getBlockEntity(pos) as? TMMachineBlockEntity ?: return 0
// Have comparator output act like a progress bar when burning because that might be super dope to see without going into the menu
if (be.burnActive) {
val scaled = (be.burnProgress.toFloat() / TMMachineBlockEntity.TOTAL_PROCESS_TIME.toFloat() * 15F).toInt()
return scaled.coerceIn(0, 15)
}
// if batch mode is off but it is ready to craft then show signal of 15
if (!be.repeatProcess) {
if (be.isReadyToCraft()) return 15
else return 0
}
// When not burning but idling in batch mode maybe show some cool states? Could be nice
return when {
be.isOutputBlocked() -> 8
else -> 4
}
}
}
@@ -0,0 +1,210 @@
/*
* 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.tmmachine
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.CobblemonMenuType
import com.cobblemon.mod.common.api.moves.Moves
import com.cobblemon.mod.common.api.tms.TechnicalMachines
import com.cobblemon.mod.common.block.entity.TMMachineBlockEntity
import com.cobblemon.mod.common.util.itemRegistry
import net.minecraft.server.level.ServerLevel
import net.minecraft.util.Mth
import net.minecraft.world.Container
import net.minecraft.world.SimpleContainer
import net.minecraft.world.entity.player.Inventory
import net.minecraft.world.entity.player.Player
import net.minecraft.world.inventory.*
import net.minecraft.world.item.ItemStack
class TMMachineMenu(menuType: MenuType<*>, syncId: Int) : AbstractContainerMenu(menuType, syncId), ContainerListener {
companion object {
const val RESULT_SLOT = 0
const val BLANK_TM_SLOT = 1
val INGREDIENT_SLOTS = 2..4
}
var playerInventory: Inventory? = null
var tmMachineEntity: TMMachineBlockEntity? = null
var inventory: Container? = null
var containerData: ContainerData? = null
constructor(syncId: Int, playerInventory: Inventory) : this(CobblemonMenuType.TM_MACHINE, syncId) {
this.playerInventory = playerInventory
this.inventory = SimpleContainer(5)
this.containerData = SimpleContainerData(6)
this.tmMachineEntity = (inventory as? TMMachineBlockEntity.TMMachineBlockInventory)?.blockEntity
this.addDataSlots(containerData!!)
inventory?.startOpen(playerInventory.player)
val startX = 15
val startY = 144
val slotWidth = 18
val slotHeight = 18
for (row in 0..2) {
for (col in 0..8) {
this.addSlot(Slot(playerInventory, 9 + (row * 9) + col, startX + (slotWidth * col), startY + (slotHeight * row)))
}
}
for (col in 0..8) {
this.addSlot(Slot(playerInventory, col, startX + (slotWidth * col), startY + 58))
}
val container = inventory
container?.let {
this.addSlot(TMResultSlot(container, RESULT_SLOT, startX + 37, startY - 28)) // Output slot
this.addSlot(BlankTMSlot(container, BLANK_TM_SLOT, startX + 90, startY - 28)) // Blank TM
for (index in INGREDIENT_SLOTS) {
this.addSlot(Slot(container, index, startX + 114 + (18 * (index - INGREDIENT_SLOTS.first)), startY - 28))
}
}
this.addSlotListener(this)
}
constructor(syncId: Int, playerInventory: Inventory, inventory: Container, containerData: ContainerData) : this(CobblemonMenuType.TM_MACHINE, syncId) {
this.playerInventory = playerInventory
this.inventory = inventory
this.containerData = containerData
this.tmMachineEntity = (inventory as? TMMachineBlockEntity.TMMachineBlockInventory)?.blockEntity
this.addDataSlots(containerData)
inventory.startOpen(playerInventory.player)
val startX = 15
val startY = 144
val slotWidth = 18
val slotHeight = 18
for (row in 0..2) {
for (col in 0..8) {
this.addSlot(Slot(playerInventory, 9 + (row * 9) + col, startX + (slotWidth * col), startY + (slotHeight * row)))
}
}
for (col in 0..8) {
this.addSlot(Slot(playerInventory, col, startX + (slotWidth * col), startY + 58))
}
val container = inventory
container.let {
this.addSlot(TMResultSlot(container, RESULT_SLOT, startX + 37, startY - 28)) // Output slot
this.addSlot(BlankTMSlot(container, BLANK_TM_SLOT, startX + 90, startY - 28)) // Blank TM
for (index in INGREDIENT_SLOTS) {
this.addSlot(Slot(container, index, startX + 114 + (18 * (index - INGREDIENT_SLOTS.first)), startY - 28))
}
}
this.addSlotListener(this)
}
override fun quickMoveStack(player: Player, index: Int): ItemStack {
var itemStack = ItemStack.EMPTY
val slot = this.slots[index]
val maxPlayerInventoryIndex = Inventory.INVENTORY_SIZE - 1
if (slot.hasItem()) {
val movedStack = slot.item
itemStack = movedStack.copy()
// If in container slots, move to player inventory
// 36: Output slot, 37: Blank TM slot, 38-40: Ingredient slots
if (index in 36..40) {
if (!this.moveItemStackTo(movedStack, 0, maxPlayerInventoryIndex, false)) return ItemStack.EMPTY
}
// If in player inventory, move to container slots. 'endIndex' is non-inclusive
else if (index <= maxPlayerInventoryIndex) {
if (!this.moveItemStackTo(movedStack, 37, 40 + 1, false)) return ItemStack.EMPTY
}
if (movedStack.isEmpty) {
slot.setByPlayer(ItemStack.EMPTY)
} else {
slot.setChanged()
}
}
return itemStack
}
override fun stillValid(player: Player): Boolean {
return player.let { inventory?.stillValid(it) } ?: false
}
fun getBurnProgressValue(): Int = this.containerData?.get(TMMachineBlockEntity.BURN_PROGRESS_INDEX) ?: 0
fun getBurnProgressRatio(): Float {
val amount = getBurnProgressValue()
val max = TMMachineBlockEntity.BURN_TOTAL_TIME
return if (amount != 0) {
Mth.clamp((amount.toFloat() / max.toFloat()), 0.0F, 1.0F)
} else {
0.0F
}
}
fun getPostCraftTicks(): Int {
return if (getBurnProgressValue() >= TMMachineBlockEntity.BURN_TOTAL_TIME) {
getBurnProgressValue() - TMMachineBlockEntity.BURN_TOTAL_TIME
} else 0
}
override fun removed(player: Player) {
super.removed(player)
tmMachineEntity?.setChanged()
}
class TMResultSlot(container: Container, slot: Int, x: Int, y: Int) : Slot(container, slot, x, y) {
override fun mayPlace(stack: ItemStack): Boolean = false
}
class BlankTMSlot(container: Container, slot: Int, x: Int, y: Int) : Slot(container, slot, x, y) {
override fun mayPlace(stack: ItemStack): Boolean = super.mayPlace(stack) && (stack.item == CobblemonItems.BLANK_TM)
}
override fun slotChanged(containerToSend: AbstractContainerMenu, dataSlotIndex: Int, stack: ItemStack) {
val level = playerInventory?.player?.level()
// If ingredient slots have been modified and level is server side and process is active
// 36: Output slot, 37: Blank TM slot, 38-40: Ingredient slots
if ((dataSlotIndex in 37 .. 40) && (level?.isClientSide == false) && (containerData?.get(TMMachineBlockEntity.BURN_ACTIVE_INDEX) == 1)) {
// Stop process if there are not enough ingredients after slots have been modified
var validCost = !(inventory?.getItem(BLANK_TM_SLOT)?.isEmpty ?: true)
if (validCost) {
Moves.getByName(tmMachineEntity?.activeMove ?: "")?.let { moveTemplate ->
TechnicalMachines.moveToTM[moveTemplate]?.let { tm ->
val recipes = tm.getClampedRecipe() ?: emptyList()
for ((index, recipe) in recipes.withIndex()) {
val slot = INGREDIENT_SLOTS.first + index
val provided = inventory?.getItem(slot) ?: ItemStack.EMPTY
if (!recipe.ingredient.test(provided) || provided.count < recipe.count) {
validCost = false
break
}
}
}
}
}
if (!validCost) {
containerData?.set(TMMachineBlockEntity.BURN_ACTIVE_INDEX, 0)
containerData?.set(TMMachineBlockEntity.BURN_PROGRESS_INDEX, 0)
}
}
tmMachineEntity?.setChanged()
inventory?.setChanged()
playerInventory?.setChanged()
broadcastChanges()
}
override fun dataChanged(containerMenu: AbstractContainerMenu, dataSlotIndex: Int, value: Int) {
broadcastChanges()
}
}
@@ -19,6 +19,7 @@ import com.cobblemon.mod.common.api.berry.Berries
import com.cobblemon.mod.common.api.scheduling.ClientTaskTracker
import com.cobblemon.mod.common.api.storage.player.client.ClientGeneralPlayerData
import com.cobblemon.mod.common.api.storage.player.client.ClientPokedexManager
import com.cobblemon.mod.common.api.storage.player.client.ClientTMMoveManager
import com.cobblemon.mod.common.api.tags.CobblemonItemTags
import com.cobblemon.mod.common.block.entity.TintBlockEntity
import com.cobblemon.mod.common.client.battle.ClientBattle
@@ -27,6 +28,7 @@ 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.gui.tmmachine.TMMachineScreen
import com.cobblemon.mod.common.client.gui.party.PartyTutorialToasts
import com.cobblemon.mod.common.client.particle.BedrockParticleOptionsRepository
import com.cobblemon.mod.common.client.render.ClientPlayerIcon
@@ -40,7 +42,9 @@ import com.cobblemon.mod.common.client.render.block.HealingMachineRenderer
import com.cobblemon.mod.common.client.render.block.LecternBlockEntityRenderer
import com.cobblemon.mod.common.client.render.block.PokeSnackBlockEntityRenderer
import com.cobblemon.mod.common.client.render.block.RestorationTankRenderer
import com.cobblemon.mod.common.client.render.block.TMShelfBlockEntityRenderer
import com.cobblemon.mod.common.client.render.boat.CobblemonBoatRenderer
import com.cobblemon.mod.common.client.render.color.TechnicalMachineItemColorProvider
import com.cobblemon.mod.common.client.render.color.AprijuiceItemColorProvider
import com.cobblemon.mod.common.client.render.color.PokeBaitItemColorProvider
import com.cobblemon.mod.common.client.render.color.PokeSnackItemColorProvider
@@ -69,6 +73,7 @@ import com.cobblemon.mod.common.client.tooltips.FishingRodTooltipGenerator
import com.cobblemon.mod.common.client.tooltips.PokePuffTooltipGenerator
import com.cobblemon.mod.common.client.tooltips.RecipeSeasoningAbsorptionTooltipGenerator
import com.cobblemon.mod.common.client.tooltips.SeasoningTooltipGenerator
import com.cobblemon.mod.common.client.tooltips.TechnicalMachineTooltipGenerator
import com.cobblemon.mod.common.client.tooltips.TooltipManager
import com.cobblemon.mod.common.client.trade.ClientTrade
import com.cobblemon.mod.common.entity.boat.CobblemonBoatType
@@ -89,6 +94,7 @@ import net.minecraft.client.renderer.blockentity.HangingSignRenderer
import net.minecraft.client.renderer.blockentity.SignRenderer
import net.minecraft.client.renderer.entity.EntityRenderer
import net.minecraft.client.renderer.entity.LivingEntityRenderer
import net.minecraft.client.renderer.entity.layers.RenderLayer
import net.minecraft.client.resources.PlayerSkin
import net.minecraft.server.packs.resources.ResourceManager
import net.minecraft.world.InteractionHand
@@ -103,6 +109,7 @@ object CobblemonClient {
var battle: ClientBattle? = null
var clientPlayerData = ClientGeneralPlayerData()
var clientPokedexData = ClientPokedexManager(mutableMapOf())
var clientTMMoveData = ClientTMMoveManager(mutableSetOf())
/** If true then we won't bother them anymore about choosing a starter even if it's a thing they can do. */
var checkedStarterScreen = false
@@ -119,6 +126,7 @@ object CobblemonClient {
requests = ClientPlayerActionRequests()
teamData = ClientPlayerTeamData()
clientPokedexData = ClientPokedexManager(mutableMapOf())
clientTMMoveData = ClientTMMoveManager(mutableSetOf())
storage.onLogin()
// CobblemonDataProvider.canReload = false
}
@@ -206,6 +214,7 @@ object CobblemonClient {
TooltipManager.registerTooltipGenerator(SeasoningTooltipGenerator)
TooltipManager.registerTooltipGenerator(FishingRodTooltipGenerator)
TooltipManager.registerTooltipGenerator(AprijuiceTooltipGenerator)
TooltipManager.registerTooltipGenerator(TechnicalMachineTooltipGenerator)
TooltipManager.registerTooltipGenerator(PokePuffTooltipGenerator)
}
@@ -297,6 +306,24 @@ object CobblemonClient {
CobblemonBlocks.MEDIUM_BUDDING_BLACK_TUMBLESTONE,
CobblemonBlocks.LARGE_BUDDING_BLACK_TUMBLESTONE,
CobblemonBlocks.BLACK_TUMBLESTONE_CLUSTER,
CobblemonBlocks.TYPE_GEM_CLUSTER_NORMAL,
CobblemonBlocks.TYPE_GEM_CLUSTER_FIRE,
CobblemonBlocks.TYPE_GEM_CLUSTER_WATER,
CobblemonBlocks.TYPE_GEM_CLUSTER_ELECTRIC,
CobblemonBlocks.TYPE_GEM_CLUSTER_GRASS,
CobblemonBlocks.TYPE_GEM_CLUSTER_ICE,
CobblemonBlocks.TYPE_GEM_CLUSTER_FIGHTING,
CobblemonBlocks.TYPE_GEM_CLUSTER_POISON,
CobblemonBlocks.TYPE_GEM_CLUSTER_GROUND,
CobblemonBlocks.TYPE_GEM_CLUSTER_FLYING,
CobblemonBlocks.TYPE_GEM_CLUSTER_PSYCHIC,
CobblemonBlocks.TYPE_GEM_CLUSTER_BUG,
CobblemonBlocks.TYPE_GEM_CLUSTER_ROCK,
CobblemonBlocks.TYPE_GEM_CLUSTER_GHOST,
CobblemonBlocks.TYPE_GEM_CLUSTER_DRAGON,
CobblemonBlocks.TYPE_GEM_CLUSTER_DARK,
CobblemonBlocks.TYPE_GEM_CLUSTER_STEEL,
CobblemonBlocks.TYPE_GEM_CLUSTER_FAIRY,
CobblemonBlocks.SMALL_BUDDING_SKY_TUMBLESTONE,
CobblemonBlocks.MEDIUM_BUDDING_SKY_TUMBLESTONE,
CobblemonBlocks.LARGE_BUDDING_SKY_TUMBLESTONE,
@@ -362,7 +389,8 @@ object CobblemonClient {
CobblemonBlocks.X_DEFENSE,
CobblemonBlocks.X_SP_ATK,
CobblemonBlocks.X_SP_DEF,
CobblemonBlocks.X_SPEED
CobblemonBlocks.X_SPEED,
CobblemonBlocks.TM_MACHINE
)
this.createBoatModelLayers()
@@ -390,6 +418,7 @@ object CobblemonClient {
private fun registerMenuScreens() {
MenuScreens.register(CobblemonMenuType.COOKING_POT, ::CookingPotScreen)
MenuScreens.register(CobblemonMenuType.TM_MACHINE, ::TMMachineScreen)
}
private fun registerBlockEntityRenderers() {
@@ -413,6 +442,7 @@ object CobblemonClient {
this.implementation.registerBlockEntityRenderer(CobblemonBlockEntities.LECTERN, ::LecternBlockEntityRenderer)
this.implementation.registerBlockEntityRenderer(CobblemonBlockEntities.CAMPFIRE, ::CampfireBlockEntityRenderer)
this.implementation.registerBlockEntityRenderer(CobblemonBlockEntities.POKE_SNACK, ::PokeSnackBlockEntityRenderer)
this.implementation.registerBlockEntityRenderer(CobblemonBlockEntities.TM_SHELF, ::TMShelfBlockEntityRenderer)
}
private fun registerEntityRenderers() {
@@ -421,14 +451,14 @@ object CobblemonClient {
LOGGER.info("Registering PokéBall renderer")
this.implementation.registerEntityRenderer(CobblemonEntities.EMPTY_POKEBALL, ::PokeBallRenderer)
LOGGER.info("Registering Boat renderer")
this.implementation.registerEntityRenderer(CobblemonEntities.BOAT) { ctx -> CobblemonBoatRenderer(ctx, false) }
LOGGER.info("Registering Boat with Chest renderer")
this.implementation.registerEntityRenderer(CobblemonEntities.CHEST_BOAT) { ctx ->
this.implementation.registerEntityRenderer(CobblemonEntities.BOAT) { ctx ->
CobblemonBoatRenderer(
ctx,
true
)
}
LOGGER.info("Registering Boat with Chest renderer")
this.implementation.registerEntityRenderer(CobblemonEntities.CHEST_BOAT) { ctx -> CobblemonBoatRenderer(ctx, true) }
LOGGER.info("Registering Generic Bedrock renderer")
this.implementation.registerEntityRenderer(CobblemonEntities.GENERIC_BEDROCK_ENTITY, ::GenericBedrockRenderer)
LOGGER.info("Registering Generic Bedrock Entity renderer")
@@ -445,6 +475,7 @@ object CobblemonClient {
implementation.registerItemColors(PokeBaitItemColorProvider, CobblemonItems.POKE_BAIT)
implementation.registerItemColors(PonigiriItemColorProvider, CobblemonItems.PONIGIRI)
implementation.registerItemColors(SinisterTeaItemColorProvider, CobblemonItems.SINISTER_TEA)
implementation.registerItemColors(TechnicalMachineItemColorProvider, CobblemonItems.TECHNICAL_MACHINE)
}
fun reloadCodedAssets(resourceManager: ResourceManager) {
@@ -481,4 +512,4 @@ object CobblemonClient {
)
}
}
}
}
@@ -93,7 +93,7 @@ class PartyOverlay : Gui(Minecraft.getInstance()) {
val starterToast = CobblemonToast(
Mth.createInsecureUUID(),
CobblemonItems.POKE_BALL.defaultInstance,
listOf(CobblemonItems.POKE_BALL.defaultInstance),
lang("ui.starter.choose_starter_title", SummaryBinding.boundKey().displayName).red(),
lang("ui.starter.choose_starter_description", SummaryBinding.boundKey().displayName).darkGray(),
AdvancementToast.BACKGROUND_SPRITE,
@@ -138,7 +138,8 @@ abstract class BattleGimmickButton(gimmick: ShowdownMoveset.Gimmick, val x: Floa
pp = gimmickTemplate?.pp ?: moveTemplate.pp,
priority = gimmickTemplate?.priority ?: moveTemplate.priority,
critRatio = gimmickTemplate?.critRatio ?: moveTemplate.critRatio,
effectChances = gimmickTemplate?.effectChances ?: moveTemplate.effectChances
effectChances = gimmickTemplate?.effectChances ?: moveTemplate.effectChances,
weight = gimmickTemplate?.weight ?: moveTemplate.weight
)
}
}
@@ -10,7 +10,6 @@ package com.cobblemon.mod.common.client.gui.interact.moveselect
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.moves.MoveTemplate
import com.cobblemon.mod.common.api.moves.Moves
import com.cobblemon.mod.common.api.text.bold
import com.cobblemon.mod.common.api.text.gold
import com.cobblemon.mod.common.api.text.red
@@ -21,6 +20,7 @@ import com.cobblemon.mod.common.client.gui.MoveCategoryIcon
import com.cobblemon.mod.common.client.gui.TypeIcon
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.lang
import com.cobblemon.mod.common.util.math.toRGB
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.components.Button
@@ -31,96 +31,92 @@ import net.minecraft.util.Mth
class MoveSlotButton(
x: Int, y: Int,
val move: MoveTemplate,
val pp: Int,
val ppMax: Int,
val enabled: Boolean = true,
var move: MoveTemplate? = null,
var pp: Int = 0,
var ppMax: Int = 0,
var ppAsFraction: Boolean = true,
var enabled: Boolean = true,
onPress: OnPress
) : Button(x, y, WIDTH, HEIGHT, Component.literal("Move"), onPress, CreateNarration { "".text() }), CobblemonRenderable {
) : Button(x, y, WIDTH, HEIGHT, move?.name?.text() ?: lang("ui.moves"), onPress, CreateNarration { move?.name?.text() ?: lang("ui.moves") }), CobblemonRenderable {
companion object {
private val moveResource = cobblemonResource("textures/gui/summary/summary_move.png")
private val moveDisabledResource = cobblemonResource("textures/gui/summary/summary_move_disabled.png")
private val moveOverlayResource = cobblemonResource("textures/gui/summary/summary_move_overlay.png")
const val WIDTH = 108
const val HEIGHT = 22
}
override fun renderWidget(context: GuiGraphics, pMouseX: Int, pMouseY: Int, pPartialTicks: Float) {
isHovered = pMouseX >= x && pMouseY >= y && pMouseX < x + width && pMouseY < y + height && enabled
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, pPartialTicks: Float) {
move?.let {
isHovered = isMouseOver(mouseX.toDouble(), mouseY.toDouble()) && enabled
val rgb = it.elementalType.hue.toRGB()
val alpha = if (enabled) 1.0F else 0.7F
val matrices = context.pose()
val moveTemplate = Moves.getByNameOrDummy(move.name)
val rgb = moveTemplate.elementalType.hue.toRGB()
blitk(
matrixStack = matrices,
texture = if (enabled) moveResource else moveDisabledResource,
x = x,
y = y,
width = WIDTH,
height = HEIGHT,
vOffset = if (isHovered) HEIGHT else 0,
textureHeight = HEIGHT * 2,
red = rgb.first,
green = rgb.second,
blue = rgb.third
)
val alpha = if (enabled) 1.0 else 0.5
blitk(
matrixStack = matrices,
texture = moveOverlayResource,
x = x,
y = y,
width = WIDTH,
height = HEIGHT
)
val matrices = context.pose()
blitk(
matrixStack = matrices,
texture = moveResource,
x = x,
y = y,
width = WIDTH,
height = HEIGHT,
vOffset = if (isHovered) HEIGHT else 0,
textureHeight = HEIGHT * 2,
red = rgb.first,
green = rgb.second,
blue = rgb.third,
alpha = alpha
)
if (pp != -1 && ppMax != -1) {
var movePPText = lang("ui.moves.pp", pp).bold()
if (ppAsFraction) {
movePPText = Component.literal("$pp/$ppMax").bold()
blitk(
matrixStack = matrices,
texture = moveOverlayResource,
x = x,
y = y,
width = WIDTH,
height = HEIGHT,
alpha = alpha
)
if (pp <= Mth.floor(ppMax / 2F)) {
movePPText = if (pp == 0) movePPText.red() else movePPText.gold()
}
}
if (pp != -1 && ppMax != -1) {
var movePPText = Component.literal("$pp/$ppMax").bold()
if (pp <= Mth.floor(ppMax / 2F)) {
movePPText = if (pp == 0) movePPText.red() else movePPText.gold()
drawScaledText(
context = context,
font = CobblemonResources.DEFAULT_LARGE,
text = movePPText,
x = x + 93,
y = y + 13,
centered = true,
opacity = alpha
)
}
// Type Icon
TypeIcon(x = x + 2, y = y + 2, type = it.elementalType, opacity = alpha).render(context)
// Move Category
MoveCategoryIcon(x = x + 66, y = y + 13.5, category = it.damageCategory, opacity = alpha).render(context)
// Move Name
drawScaledText(
context = context,
font = CobblemonResources.DEFAULT_LARGE,
text = movePPText,
x = x + 93,
y = y + 13,
centered = true
text = it.displayName.bold(),
x = x + 28,
y = y + 2,
shadow = true,
opacity = alpha
)
}
// Type Icon
TypeIcon(
x = x + 2,
y = y + 2,
type = moveTemplate.elementalType
).render(context)
// Move Category
MoveCategoryIcon(
x = x + 66,
y = y + 13.5,
category = move.damageCategory
).render(context)
// Move Name
drawScaledText(
context = context,
font = CobblemonResources.DEFAULT_LARGE,
text = move.displayName.bold(),
x = x + 28,
y = y + 2,
shadow = true
)
}
override fun playDownSound(soundManager: SoundManager) {}
}
}
@@ -45,7 +45,7 @@ object PartyTutorialToasts {
private val toast = CobblemonToast(
Mth.createInsecureUUID(),
CobblemonItems.POKEDEX_RED.defaultInstance,
listOf(CobblemonItems.POKEDEX_RED.defaultInstance),
lang("ui.party.party_select_title").red(),
lang(
"ui.party.party_select_description",
@@ -96,4 +96,4 @@ object PartyTutorialToasts {
private fun hideToast() {
if (toastAttached) toast.nextVisibility = Toast.Visibility.HIDE
}
}
}
@@ -22,6 +22,8 @@ import com.cobblemon.mod.common.api.pokedex.entry.PokedexForm
import com.cobblemon.mod.common.api.pokedex.filter.EntryFilter
import com.cobblemon.mod.common.api.pokedex.filter.SearchByType
import com.cobblemon.mod.common.api.pokedex.filter.SearchFilter
import com.cobblemon.mod.common.api.pokedex.filter.SecondaryPokedexFilter
import com.cobblemon.mod.common.api.pokedex.filter.SecondaryPokedexFilterType
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.api.storage.player.client.ClientPokedexManager
import com.cobblemon.mod.common.api.text.bold
@@ -39,6 +41,7 @@ import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.TAB_ABILI
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.TAB_DESCRIPTION
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.TAB_DROPS
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.TAB_ICON_SIZE
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.TAB_MOVES
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.TAB_SIZE
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.TAB_STATS
import com.cobblemon.mod.common.client.gui.pokedex.widgets.*
@@ -83,12 +86,14 @@ class PokedexGUI private constructor(
private val arrowDownIcon = cobblemonResource("textures/gui/pokedex/arrow_down.png")
private val tabSelectArrow = cobblemonResource("textures/gui/pokedex/select_arrow.png")
private val secondaryFilterIcon = cobblemonResource("textures/gui/pokedex/filter_icon_button.png")
private val tabIcons = arrayOf(
cobblemonResource("textures/gui/pokedex/tab_info.png"),
cobblemonResource("textures/gui/pokedex/tab_abilities.png"),
cobblemonResource("textures/gui/pokedex/tab_size.png"),
cobblemonResource("textures/gui/pokedex/tab_stats.png"),
cobblemonResource("textures/gui/pokedex/tab_drops.png")
cobblemonResource("textures/gui/pokedex/tab_drops.png"),
cobblemonResource("textures/gui/pokedex/tab_moves.png")
)
/**
@@ -127,11 +132,13 @@ class PokedexGUI private constructor(
private lateinit var regionSelectWidgetUp: ScaledButton
private lateinit var regionSelectWidgetDown: ScaledButton
private lateinit var searchByTypeButton: ScaledButton
private lateinit var secondaryFilterButton: ScaledButton
private lateinit var scrollScreen: EntriesScrollingWidget
private lateinit var pokemonInfoWidget: PokemonInfoWidget
private lateinit var searchWidget: SearchWidget
private var selectedSearchByType: SearchByType = SearchByType.SPECIES
private var selectedSecondaryFilter: SecondaryPokedexFilterType = SecondaryPokedexFilterType.ALL
private val tabButtons: MutableList<ScaledButton> = mutableListOf()
lateinit var tabInfoElement: GuiEventListener
@@ -163,7 +170,7 @@ class PokedexGUI private constructor(
displaytabInfoElement(tabInfoIndex, false)
if (::searchWidget.isInitialized) removeWidget(searchWidget)
searchWidget = SearchWidget(x + 26, y + 28, 128, HEADER_BAR_HEIGHT, update = ::updateFilters)
searchWidget = SearchWidget(x + 26, y + 28, 120, HEADER_BAR_HEIGHT, update = ::updateFilters)
addRenderableWidget(searchWidget)
if (::regionSelectWidgetUp.isInitialized) removeWidget(regionSelectWidgetUp)
@@ -208,6 +215,23 @@ class PokedexGUI private constructor(
)
addRenderableWidget(searchByTypeButton)
if (::secondaryFilterButton.isInitialized) removeWidget(secondaryFilterButton)
secondaryFilterButton = ScaledButton(
buttonX = (x + 146.5).toFloat(),
buttonY = (y + 29.5).toFloat(),
buttonWidth = 16,
buttonHeight = 16,
scale = SCALE,
resource = secondaryFilterIcon,
clickAction = {
val filters = SecondaryPokedexFilterType.entries.toList()
val selectedIndex = filters.indexOf(selectedSecondaryFilter)
selectedSecondaryFilter = filters[if (selectedIndex == filters.lastIndex) 0 else (selectedIndex + 1)]
updateFilters()
}
)
addRenderableWidget(secondaryFilterButton)
updateFilters(true)
}
@@ -309,8 +333,7 @@ class PokedexGUI private constructor(
blitk(
matrixStack = matrices,
texture = tabSelectArrow,
x = (x + 198 + (25 * tabInfoIndex)) / SCALE,
// (x + 191.5 + (22 * tabInfoIndex)) / SCALE for 6 tabs
x = (x + 191.5F + (22F * tabInfoIndex)) / SCALE,
y = (y + 177) / SCALE,
width = 12,
height = 6,
@@ -332,6 +355,18 @@ class PokedexGUI private constructor(
-14
)
}
if (secondaryFilterButton.isButtonHovered(mouseX, mouseY)) {
val filterText = lang("ui.pokedex.filter.${selectedSecondaryFilter.name.lowercase()}").bold()
renderTooltip(
context,
filterText,
mouseX,
mouseY,
delta,
-14
)
}
}
override fun onClose() {
@@ -344,6 +379,7 @@ class PokedexGUI private constructor(
val canDisplayEntry = true //selectedForm?.unlockForms
if (::pokemonInfoWidget.isInitialized
&& !pokemonInfoWidget.isSuppressed
&& pokemonInfoWidget.isWithinPortraitSpace(mouseX, mouseY)
&& canDisplayEntry == true
) {
@@ -375,7 +411,7 @@ class PokedexGUI private constructor(
}
override fun tick() {
if (::pokemonInfoWidget.isInitialized) pokemonInfoWidget.tick()
if (::pokemonInfoWidget.isInitialized && !pokemonInfoWidget.isSuppressed) pokemonInfoWidget.tick()
}
fun updatePokedexRegion(nextIndex: Boolean) {
@@ -441,6 +477,7 @@ class PokedexGUI private constructor(
fun getFilters(): Collection<EntryFilter> {
val filters: MutableList<EntryFilter> = mutableListOf()
filters.add(SecondaryPokedexFilter(CobblemonClient.clientPokedexData, selectedSecondaryFilter))
filters.add(SearchFilter(CobblemonClient.clientPokedexData, searchWidget.value, selectedSearchByType))
return filters
@@ -462,7 +499,7 @@ class PokedexGUI private constructor(
for (i in tabIcons.indices) {
tabButtons.add(ScaledButton(
x + 197F + (i * 25F), // x + 190.5F + (i * 22F) for 6 tabs
x + 190.5F + (i * 22F),
y + 181.5F,
TAB_ICON_SIZE,
TAB_ICON_SIZE,
@@ -497,6 +534,9 @@ class PokedexGUI private constructor(
}
tabInfoIndex = tabIndex
if (::pokemonInfoWidget.isInitialized) {
pokemonInfoWidget.isSuppressed = tabInfoIndex == TAB_MOVES
}
if (::tabInfoElement.isInitialized) removeWidget(tabInfoElement)
val x = (width - BASE_WIDTH) / 2
@@ -520,11 +560,19 @@ class PokedexGUI private constructor(
TAB_DROPS -> {
tabInfoElement = DropsScrollingWidget(x + 189, y + 135)
}
TAB_MOVES -> {
tabInfoElement = MovesLearnsetWidget(x + 180, y + 28)
}
}
val element = tabInfoElement
if (element is Renderable && element is NarratableEntry) {
addRenderableWidget(element)
}
// Keep tab icons above the moves overlay.
if (tabButtons.isNotEmpty()) {
tabButtons.forEach { removeWidget(it) }
tabButtons.forEach { addRenderableWidget(it) }
}
if (update) updateTabInfoElement()
}
@@ -578,6 +626,17 @@ class PokedexGUI private constructor(
(tabInfoElement as DropsScrollingWidget).dropTable = form.drops
(tabInfoElement as DropsScrollingWidget).setEntries()
}
TAB_MOVES -> {
val availableForms = selectedEntry?.let { CobblemonClient.clientPokedexData.getEncounteredForms(it) } ?: emptyList()
val activeForm = this.selectedForm ?: availableForms.firstOrNull()
val fallbackForm = activeForm ?: PokedexForm().apply { displayForm = form.name }
(tabInfoElement as MovesLearnsetWidget).setLearnset(
species = species,
form = form,
availableForms = availableForms,
selectedForm = fallbackForm
) { next -> cycleSelectedForm(next) }
}
// TAB_MOVES -> {
// form.moves.getLevelUpMovesUpTo(100)
// }
@@ -600,6 +659,29 @@ class PokedexGUI private constructor(
displaytabInfoElement(tabInfoIndex)
}
fun cycleSelectedForm(next: Boolean) {
val entry = selectedEntry ?: return
val forms = CobblemonClient.clientPokedexData.getEncounteredForms(entry)
if (forms.isEmpty()) return
val current = selectedForm ?: forms.first()
val currentIndex = forms.indexOfFirst { it.displayForm.equals(current.displayForm, ignoreCase = true) }
.let { if (it == -1) 0 else it }
val nextIndex = if (next) {
(currentIndex + 1) % forms.size
} else {
(currentIndex - 1 + forms.size) % forms.size
}
val newForm = forms[nextIndex]
selectedForm = newForm
if (::pokemonInfoWidget.isInitialized) {
pokemonInfoWidget.setSelectedForm(newForm)
}
updateTabInfoElement()
}
fun canSelectTab(tabIndex: Int): Boolean {
val selectedForm = this.selectedForm ?: return false
val selectedEntry = this.selectedEntry ?: return false
@@ -621,4 +703,4 @@ class PokedexGUI private constructor(
fun playSound(soundEvent: SoundEvent) {
Minecraft.getInstance().soundManager.play(SimpleSoundInstance.forUI(soundEvent, 1.0F))
}
}
}
@@ -35,9 +35,10 @@ object PokedexGUIConstants {
const val TAB_SIZE = 2
const val TAB_STATS = 3
const val TAB_DROPS = 4
const val TAB_MOVES = 5
// RowScrollingWidget
const val SCROLL_BASE_HEIGHT = 141
const val SCROLL_SLOT_SIZE = 25
const val SCROLL_SLOT_SPACING = 2
}
}
@@ -125,6 +125,7 @@ class PokemonInfoWidget(val pX: Int, val pY: Int, val updateForm: (PokedexForm)
var ticksElapsed = 0
var pokeBallBackgroundFrame = 0
var isSuppressed = false
class VariationButtonWrapper(
val parent: PokemonInfoWidget,
@@ -259,6 +260,9 @@ class PokemonInfoWidget(val pX: Int, val pY: Int, val updateForm: (PokedexForm)
).apply { addWidget(this) }
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
if (isSuppressed) {
return
}
val currentEntry = this.currentEntry ?: return
val hasKnowledge = CobblemonClient.clientPokedexData.getKnowledgeForSpecies(currentEntry.speciesId) != PokedexEntryProgress.NONE
@@ -512,6 +516,40 @@ class PokemonInfoWidget(val pX: Int, val pY: Int, val updateForm: (PokedexForm)
matrices.popPose()
}
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
if (isSuppressed) {
return false
}
return super.mouseClicked(mouseX, mouseY, button)
}
override fun mouseReleased(mouseX: Double, mouseY: Double, button: Int): Boolean {
if (isSuppressed) {
return false
}
return super.mouseReleased(mouseX, mouseY, button)
}
override fun mouseScrolled(mouseX: Double, mouseY: Double, horizontalAmount: Double, verticalAmount: Double): Boolean {
if (isSuppressed) {
return false
}
return super.mouseScrolled(mouseX, mouseY, horizontalAmount, verticalAmount)
}
override fun mouseDragged(mouseX: Double, mouseY: Double, button: Int, deltaX: Double, deltaY: Double): Boolean {
if (isSuppressed) {
return false
}
return super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)
}
override fun mouseMoved(mouseX: Double, mouseY: Double) {
if (!isSuppressed) {
super.mouseMoved(mouseX, mouseY)
}
}
fun setDexEntry(pokedexEntry : PokedexEntry) {
this.currentEntry = pokedexEntry
this.renderablePokemon = null
@@ -624,6 +662,19 @@ class PokemonInfoWidget(val pX: Int, val pY: Int, val updateForm: (PokedexForm)
updateAspects()
}
fun setSelectedForm(newForm: PokedexForm) {
if (visibleForms.isEmpty() || currentEntry == null) {
return
}
val index = visibleForms.indexOfFirst { it.displayForm.equals(newForm.displayForm, ignoreCase = true) }
if (index == -1) {
return
}
selectedFormIndex = index
setupButtons(currentEntry!!, visibleForms[selectedFormIndex])
updateAspects()
}
fun updateAspects() {
if (visibleForms.isEmpty()) {
return
@@ -706,4 +757,4 @@ class PokemonInfoWidget(val pX: Int, val pY: Int, val updateForm: (PokedexForm)
fun playSound(soundEvent: SoundEvent) {
Minecraft.getInstance().soundManager.play(SimpleSoundInstance.forUI(soundEvent, 1.0F))
}
}
}
@@ -36,9 +36,9 @@ class MoveSlotWidget(
): SoundlessWidget(pX, pY, MOVE_WIDTH, MOVE_HEIGHT, Component.literal(move?.name ?: "")) {
companion object {
private val moveResource = cobblemonResource("textures/gui/summary/summary_move.png")
private val moveOverlayResource = cobblemonResource("textures/gui/summary/summary_move_overlay.png")
private val moveSelectedOverlayResource = cobblemonResource("textures/gui/summary/summary_move_selected_overlay.png")
val moveResource = cobblemonResource("textures/gui/summary/summary_move.png")
val moveOverlayResource = cobblemonResource("textures/gui/summary/summary_move_overlay.png")
val moveSelectedOverlayResource = cobblemonResource("textures/gui/summary/summary_move_selected_overlay.png")
const val MOVE_WIDTH = 108
const val MOVE_HEIGHT = 22
@@ -50,6 +50,7 @@ class MoveSlotWidget(
}.apply {
addWidget(this)
}
private val moveDownButton = ReorderMoveButton(x, y, false) {
movesWidget.selectMove(null)
movesWidget.reorderMove(this, false)
@@ -67,46 +68,46 @@ class MoveSlotWidget(
}.apply {
addWidget(this)
}
val elementalType:ElementalType = Moves.getByNameOrDummy(move?.name ?: "").getEffectiveElementalType(pokemon)
override fun renderWidget(context: GuiGraphics, pMouseX: Int, pMouseY: Int, pPartialTicks: Float) {
val matrices = context.pose()
isHovered = pMouseX >= x && pMouseY >= y && pMouseX < x + width && pMouseY < y + height
if (move != null) {
val moveTemplate = Moves.getByNameOrDummy(move.name)
val rgb = elementalType.hue.toRGB()
if (movesWidget.selectedMove == move) {
blitk(
matrixStack = matrices,
texture = moveSelectedOverlayResource,
x = x - 1,
y = y - 1,
width = MOVE_WIDTH + 2,
height = MOVE_HEIGHT + 2
matrixStack = matrices,
texture = moveSelectedOverlayResource,
x = x - 1,
y = y - 1,
width = MOVE_WIDTH + 2,
height = MOVE_HEIGHT + 2
)
}
blitk(
matrixStack = matrices,
texture = moveResource,
x = x,
y = y,
width = MOVE_WIDTH,
height = MOVE_HEIGHT,
vOffset = if (isHovered) MOVE_HEIGHT else 0,
textureHeight = MOVE_HEIGHT * 2,
red = rgb.first,
green = rgb.second,
blue = rgb.third
matrixStack = matrices,
texture = moveResource,
x = x,
y = y,
width = MOVE_WIDTH,
height = MOVE_HEIGHT,
vOffset = if (isHovered) MOVE_HEIGHT else 0,
textureHeight = MOVE_HEIGHT * 2,
red = rgb.first,
green = rgb.second,
blue = rgb.third
)
blitk(
matrixStack = matrices,
texture = moveOverlayResource,
x = x,
y = y,
width = MOVE_WIDTH,
height = MOVE_HEIGHT
matrixStack = matrices,
texture = moveOverlayResource,
x = x,
y = y,
width = MOVE_WIDTH,
height = MOVE_HEIGHT
)
var movePPText = Component.literal("${move.currentPp}/${move.maxPp}").bold()
@@ -116,36 +117,28 @@ class MoveSlotWidget(
}
drawScaledText(
context = context,
font = CobblemonResources.DEFAULT_LARGE,
text = movePPText,
x = x + 93,
y = y + 13,
centered = true
context = context,
font = CobblemonResources.DEFAULT_LARGE,
text = movePPText,
x = x + 93,
y = y + 13,
centered = true
)
// Type Icon
TypeIcon(
x = x + 2,
y = y + 2,
type = elementalType
).render(context)
TypeIcon(x = x + 2, y = y + 2, type = elementalType).render(context)
// Move Category
MoveCategoryIcon(
x = x + 66,
y = y + 13.5,
category = move.damageCategory
).render(context)
MoveCategoryIcon(x = x + 66, y = y + 13.5, category = move.damageCategory).render(context)
// Move Name
drawScaledText(
context = context,
font = CobblemonResources.DEFAULT_LARGE,
text = move.displayName.bold(),
x = x + 28,
y = y + 2,
shadow = true
context = context,
font = CobblemonResources.DEFAULT_LARGE,
text = move.displayName.bold(),
x = x + 28,
y = y + 2,
shadow = true
)
// Reorder Buttons
@@ -158,9 +151,7 @@ class MoveSlotWidget(
}
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
if (isHovered) {
movesWidget.selectMove(move)
}
if (isHovered) movesWidget.selectMove(move)
return super.mouseClicked(mouseX, mouseY, button)
}
}
}
@@ -162,7 +162,7 @@ class MoveSwapScreen(
drawScaledText(
context = context,
text = pane.movesWidget.format(move.accuracy).text(),
text = MovesWidget.format(move.accuracy).text(),
x = rowLeft + 37,
y = tweakedRowTop + 12,
scale = MovesWidget.SCALE,
@@ -171,7 +171,7 @@ class MoveSwapScreen(
drawScaledText(
context = context,
text = pane.movesWidget.format(move.effectChances.firstOrNull() ?: 0.0).text(),
text = MovesWidget.format(move.effectChances.firstOrNull() ?: 0.0).text(),
x = rowLeft + 60.5,
y = tweakedRowTop + 12,
scale = MovesWidget.SCALE,
@@ -232,4 +232,4 @@ class MoveSwapScreen(
return false
}
}
}
}
@@ -39,6 +39,11 @@ class MovesWidget(
it.roundingMode = RoundingMode.CEILING
}
fun format(input: Double): String {
if (input <= 0) return "—"
return "${decimalFormat.format(input)}%"
}
private val movesBaseResource = cobblemonResource("textures/gui/summary/summary_moves_base.png")
val movesPowerIconResource = cobblemonResource("textures/gui/summary/summary_moves_icon_power.png")
val movesAccuracyIconResource = cobblemonResource("textures/gui/summary/summary_moves_icon_accuracy.png")
@@ -208,11 +213,6 @@ class MovesWidget(
)
}
fun format(input: Double): String {
if (input <= 0) return "—"
return "${decimalFormat.format(input)}%"
}
fun selectMove(move: Move?) {
selectedMove = if (selectedMove == move || move == null) null else move
descriptionScrollList.setMoveDescription(selectedMove?.description ?: "".text())
@@ -232,4 +232,4 @@ class MovesWidget(
if (descriptionScrollList.isHovered) descriptionScrollList.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)
return super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)
}
}
}
@@ -0,0 +1,74 @@
/*
* 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.gui.tmmachine
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.components.Button
import net.minecraft.client.gui.narration.NarrationElementOutput
import net.minecraft.client.resources.sounds.SimpleSoundInstance
import net.minecraft.client.sounds.SoundManager
import net.minecraft.resources.ResourceLocation
class IconButton(
var buttonX: Float,
var buttonY: Float,
val buttonWidth: Number,
val buttonHeight: Number,
var resource: ResourceLocation? = null,
val scale: Float = 0.5F,
val hoverExtendRight: Int = 0,
val silent: Boolean = false,
val clickAction: OnPress
): Button(buttonX.toInt(), buttonY.toInt(), buttonWidth.toInt(), buttonHeight.toInt(), "".text(), clickAction, DEFAULT_NARRATION), CobblemonRenderable {
override fun mouseDragged(d: Double, e: Double, i: Int, f: Double, g: Double) = false
override fun defaultButtonNarrationText(builder: NarrationElementOutput) {}
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, partialTicks: Float) {
resource?.let {
blitk(
matrixStack = context.pose(),
texture = it,
x = buttonX / scale,
y = buttonY / scale,
width = buttonWidth,
height = buttonHeight,
vOffset = if (isMouseOver(mouseX.toDouble(), mouseY.toDouble())) buttonHeight else 0,
textureHeight = buttonHeight.toFloat() * 2,
scale = scale
)
}
}
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
if (active && visible && button == 0 && isMouseOver(mouseX, mouseY)) {
playDownSound(Minecraft.getInstance().soundManager)
onClick(mouseX, mouseY)
return true
}
return false
}
override fun isMouseOver(mouseX: Double, mouseY: Double): Boolean {
val minX = x.toDouble()
val maxX = minX + width + hoverExtendRight
val minY = y.toDouble()
val maxY = minY + (height * scale)
return active && visible && mouseX >= minX && mouseX < maxX && mouseY >= minY && mouseY < maxY
}
override fun playDownSound(soundManager: SoundManager) {
if (visible && !this.silent) soundManager.play(SimpleSoundInstance.forUI(CobblemonSounds.GUI_CLICK, 1.0F))
}
}
@@ -0,0 +1,77 @@
/*
* 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.gui.tmmachine
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.text.bold
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.gui.pc.TextWidget
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.lang
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.resources.sounds.SimpleSoundInstance
import net.minecraft.network.chat.Component
class MoveSearchWidget(
private var pX: Int,
private var pY: Int,
text: Component = "MoveSearchWidget".text(),
update: () -> (Unit)
): TextWidget(pX, pY, text = text, update = update) {
companion object {
const val ICON_SIZE = 16
const val SCALE = 0.5F
private val iconFilterResource = cobblemonResource("textures/gui/tmmachine/icon_search.png")
private val labelFilter = lang("ui.pokedex.search")
}
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
blitk(
matrixStack = context.pose(),
x = (x - 10.5) / SCALE,
y = (y + 2.5) / SCALE,
texture = iconFilterResource,
width = ICON_SIZE,
height = ICON_SIZE,
vOffset = if (isFocused || !value.isEmpty() || iconHovered(mouseX, mouseY)) ICON_SIZE else 0,
textureHeight = ICON_SIZE * 2,
scale = SCALE
)
val input = applyTextCursor(value, labelFilter)
drawScaledText(
context = context,
font = CobblemonResources.DEFAULT_LARGE,
text = input.bold(),
x = pX,
y = pY + 2,
shadow = true
)
renderCursor(context, input)
}
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
if (iconHovered(mouseX.toInt(), mouseY.toInt())) {
Minecraft.getInstance().soundManager.play(SimpleSoundInstance.forUI(CobblemonSounds.GUI_CLICK, 1.0F, 0.25F))
// Simulate clicking on text area if icon is clicked
if (!isFocused) return super.mouseClicked(x + (width / 2.0), y + (height / 2.0), button)
}
return super.mouseClicked(mouseX, mouseY, button)
}
fun iconHovered(mouseX: Int, mouseY: Int): Boolean = mouseX >= (x - 10.5) && mouseY >= (y + 2.5)
&& mouseX <= ((x - 10.5) + (ICON_SIZE * SCALE)) && mouseY <= ((y + 2.5) + (ICON_SIZE * SCALE))
}
@@ -0,0 +1,200 @@
/*
* 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.gui.tmmachine
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.reactive.SettableObservable
import com.cobblemon.mod.common.api.text.bold
import com.cobblemon.mod.common.api.tms.TechnicalMachine
import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.gui.MoveCategoryIcon
import com.cobblemon.mod.common.client.gui.ScrollingWidget
import com.cobblemon.mod.common.client.gui.TypeIcon
import com.cobblemon.mod.common.client.gui.summary.widgets.screens.moves.MoveSlotWidget
import com.cobblemon.mod.common.client.gui.tmmachine.MovesScrollingWidget.ScrollSlot
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.lang
import com.cobblemon.mod.common.util.math.toRGB
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.resources.sounds.SimpleSoundInstance
import net.minecraft.network.chat.Component
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.FastColor
import net.minecraft.util.Mth
class MovesScrollingWidget(val pX: Int, val pY: Int, val tmList: SettableObservable<MutableList<TechnicalMachine>>, val setTM: (TechnicalMachine?, Boolean) -> (Unit)): ScrollingWidget<ScrollSlot>(
width = WIDTH,
height = HEIGHT,
left = pX,
top = pY - HEIGHT,
slotHeight = SLOT_HEIGHT + SLOT_SPACING
) {
companion object {
const val WIDTH = 115
const val HEIGHT = 82
const val SLOT_HEIGHT = 22
const val SLOT_SPACING = 4
val searchBar = cobblemonResource("textures/gui/tmmachine/search_bar.png")
}
var highlightedMoveId: ResourceLocation? = null
init {
tmList.subscribeIncludingCurrent { tmList ->
replaceEntries(tmList.map { tm -> ScrollSlot(tm, setTM) })
}
}
fun setSlotHighlighted(id: ResourceLocation?) {
highlightedMoveId = id
for (child in children()) {
if (child is ScrollSlot) {
child.highlighted = child.tm.id == highlightedMoveId
}
}
}
override fun getScrollbarPosition(): Int = rowLeft + width - 3
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
blitk(
matrixStack = context.pose(),
texture = searchBar,
x = left - 4,
y = y - 19,
width = 120,
height = 19
)
super.renderWidget(context, mouseX, mouseY, delta)
}
override fun renderScrollbar(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
val xLeft = this.scrollbarPosition
val xRight = xLeft + 3
val barHeight = this.bottom - this.y
var yBottom = ((barHeight * barHeight).toFloat() / this.maxPosition.toFloat()).toInt()
yBottom = Mth.clamp(yBottom, 32, barHeight - 8)
var yTop = scrollAmount.toInt() * (barHeight - yBottom) / this.maxScroll + this.y
if (yTop < this.y) {
yTop = this.y
}
context.fill(xLeft, this.y, xRight, this.bottom, FastColor.ARGB32.color(255, 75, 75, 75)) // background
context.fill(xLeft,yTop, xRight, yTop + yBottom, FastColor.ARGB32.color(255, 141, 141, 141)) // base
}
override fun getEntry(index: Int): ScrollSlot = children()[index] as ScrollSlot
class ScrollSlot(val tm: TechnicalMachine, val setTM : (TechnicalMachine?, clicked: Boolean) -> Unit): Slot<ScrollSlot>() {
var posX: Int = 0
var posY: Int = 0
var highlighted = false
override fun render(context: GuiGraphics, index: Int, y: Int, x: Int, entryWidth: Int, entryHeight: Int, mouseX: Int, mouseY: Int, hovered: Boolean, tickDelta: Float) {
posX = x
posY = y
val moveTemplate = tm.moveName
val rgb = moveTemplate.elementalType.hue.toRGB()
val isHovered = hovered && (mouseX < x + (108)) // Prevent hover when over scrollbar
val startPosY = y + SLOT_SPACING
val startPosX = x + 1 // Hover border for move renders at x, so render move element to the right by 1
val matrices = context.pose()
blitk(
matrixStack = matrices,
texture = MoveSlotWidget.moveResource,
x = startPosX,
y = startPosY,
width = MoveSlotWidget.MOVE_WIDTH,
height = MoveSlotWidget.MOVE_HEIGHT,
vOffset = if (isHovered) MoveSlotWidget.MOVE_HEIGHT else 0,
textureHeight = MoveSlotWidget.MOVE_HEIGHT * 2,
red = rgb.first,
green = rgb.second,
blue = rgb.third
)
blitk(
matrixStack = matrices,
texture = MoveSlotWidget.moveOverlayResource,
x = startPosX,
y = startPosY,
width = MoveSlotWidget.MOVE_WIDTH,
height = MoveSlotWidget.MOVE_HEIGHT
)
if (highlighted || isHovered) {
blitk(
matrixStack = matrices,
texture = MoveSlotWidget.moveSelectedOverlayResource,
x = startPosX - 1,
y = startPosY - 1,
width = MoveSlotWidget.MOVE_WIDTH + 2,
height = MoveSlotWidget.MOVE_HEIGHT + 2
)
}
drawScaledText(
context = context,
font = CobblemonResources.DEFAULT_LARGE,
text = lang("ui.moves.pp", moveTemplate.pp).bold(),
x = startPosX + 93,
y = startPosY + 13,
centered = true
)
// Type Icon
TypeIcon(
x = x + 2,
y = startPosY + 2,
type = moveTemplate.elementalType
).render(context)
// Move Category
MoveCategoryIcon(
x = x + 66,
y = startPosY + 13.5,
category = moveTemplate.damageCategory
).render(context)
// Move Name
drawScaledText(
context = context,
font = CobblemonResources.DEFAULT_LARGE,
text = moveTemplate.displayName.bold(),
x = x + 28,
y = startPosY + 2,
shadow = true
)
if (isHovered) {
setTM(tm, false)
}
}
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
// Prevent clicking through scroll bar
if (mouseX < posX + (108)) {
setTM(tm, true)
Minecraft.getInstance().soundManager.play(SimpleSoundInstance.forUI(CobblemonSounds.GUI_CLICK, 1.0F))
}
return super.mouseClicked(mouseX, mouseY, button)
}
override fun getNarration(): Component {
return tm.moveName.displayName
}
}
}
@@ -0,0 +1,89 @@
/*
* 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.gui.tmmachine
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.components.Button
import net.minecraft.client.gui.narration.NarrationElementOutput
import net.minecraft.client.resources.sounds.SimpleSoundInstance
import net.minecraft.client.sounds.SoundManager
class StartButton(
var buttonX: Float,
var buttonY: Float,
val clickAction: OnPress
): Button(buttonX.toInt(), buttonY.toInt(), WIDTH, HEIGHT, "".text(), clickAction, DEFAULT_NARRATION), CobblemonRenderable {
companion object {
const val WIDTH = 22
const val HEIGHT = 12
val base = cobblemonResource("textures/gui/tmmachine/button_start.png")
val baseStop = cobblemonResource("textures/gui/tmmachine/button_stop.png")
val baseDisabled = cobblemonResource("textures/gui/tmmachine/button_start_disabled.png")
val iconStart = cobblemonResource("textures/gui/tmmachine/button_icon_start.png")
val iconStop = cobblemonResource("textures/gui/tmmachine/button_icon_stop.png")
val iconRepeat = cobblemonResource("textures/gui/tmmachine/button_icon_repeat.png")
}
var disabled = false
var processing = false
var shouldRepeat = false
override fun mouseDragged(d: Double, e: Double, i: Int, f: Double, g: Double) = false
override fun defaultButtonNarrationText(builder: NarrationElementOutput) {}
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, partialTicks: Float) {
val resource = if (disabled) baseDisabled
else (if (processing) baseStop else base)
blitk(
matrixStack = context.pose(),
texture = resource,
x = buttonX,
y = buttonY,
width = WIDTH,
height = HEIGHT,
vOffset = if (isMouseOver(mouseX.toDouble(), mouseY.toDouble())) HEIGHT else 0,
textureHeight = HEIGHT * 2,
)
blitk(
matrixStack = context.pose(),
texture = if (processing) iconStop else (if (shouldRepeat) iconRepeat else iconStart),
x = buttonX,
y = buttonY,
width = WIDTH,
height = HEIGHT
)
}
/*override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
if (active && visible && !disabled && isMouseOver(mouseX, mouseY)) {
super.mouseClicked(mouseX, mouseY, button)
}
return false
}*/
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
if (!active || !visible || disabled) return false
return super.mouseClicked(mouseX, mouseY, button)
}
override fun playDownSound(soundManager: SoundManager) {
if (visible && !disabled) {
soundManager.play(SimpleSoundInstance.forUI(CobblemonSounds.GUI_CLICK, 1.0F))
}
}
}
@@ -0,0 +1,187 @@
/*
* 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.gui.tmmachine
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.client.gui.drawProfilePokemon
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.client.render.drawScaledTextJustifiedRight
import com.cobblemon.mod.common.client.render.models.blockbench.FloatingState
import com.cobblemon.mod.common.client.render.renderScaledGuiItemIcon
import com.cobblemon.mod.common.pokemon.Gender
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.lang
import com.cobblemon.mod.common.util.math.fromEulerXYZDegrees
import net.minecraft.client.gui.components.Button
import net.minecraft.client.sounds.SoundManager
import net.minecraft.network.chat.Component
import net.minecraft.network.chat.MutableComponent
import net.minecraft.resources.ResourceLocation
import org.joml.Quaternionf
import org.joml.Vector3f
class TMPartySlotWidget(
pX: Number,
pY: Number,
val pokemon: Pokemon?,
onPress: OnPress
) : Button(pX.toInt(), pY.toInt(), WIDTH, HEIGHT, Component.literal("PartyMember"), onPress, DEFAULT_NARRATION) {
val state = FloatingState()
var teachable: Int? = null
var clickable: Boolean = true
companion object {
const val WIDTH = 57
const val HEIGHT = 31
private const val PORTRAIT_DIAMETER = 30
const val HALF_SCALE = 0.5F
const val CAN_LEARN = 0
const val CANNOT_LEARN = 1
const val LEARNED = 2
private val slotResource = cobblemonResource("textures/gui/tmmachine/party_slot.png")
private val slotLearnableResource = cobblemonResource("textures/gui/tmmachine/party_slot_learnable.png")
private val slotLearnableDisabledResource = cobblemonResource("textures/gui/tmmachine/party_slot_learnable_disabled.png")
private val slotDisabledResource = cobblemonResource("textures/gui/tmmachine/party_slot_disabled.png")
private val slotLearnedResource = cobblemonResource("textures/gui/tmmachine/party_slot_learned.png")
val genderIconMale = cobblemonResource("textures/gui/pc/gender_icon_male.png")
val genderIconFemale = cobblemonResource("textures/gui/pc/gender_icon_female.png")
private val canLearnLabel = lang("ui.tm_machine.can_learn")
private val cannotLearnLabel = lang("ui.tm_machine.cannot_learn")
private val learnedLabel = lang("ui.tm_machine.learned")
}
private fun getResource(): ResourceLocation = when (teachable) {
CAN_LEARN -> if (clickable) slotLearnableResource else slotLearnableDisabledResource
CANNOT_LEARN -> slotDisabledResource
LEARNED -> slotLearnedResource
else -> slotResource
}
private fun getLabel(): MutableComponent? = when (teachable) {
CAN_LEARN -> canLearnLabel
CANNOT_LEARN -> cannotLearnLabel
LEARNED -> learnedLabel
else -> null
}
override fun renderWidget(context: net.minecraft.client.gui.GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
state.currentAspects = pokemon?.aspects ?: emptySet()
val matrices = context.pose()
if (pokemon != null) {
blitk(
matrixStack = matrices,
texture = getResource(),
x = x,
y = y,
width = width,
height = height,
vOffset = if (clickable && isMouseOver(mouseX.toDouble(), mouseY.toDouble())) height else 0,
textureHeight = height * 2,
)
val ballIcon = cobblemonResource("textures/gui/ball/" + pokemon.caughtBall.name.path + ".png")
val ballHeight = 22
blitk(
matrixStack = matrices,
texture = ballIcon,
x = (x - 2) / HALF_SCALE,
y = (y - 3) / HALF_SCALE,
height = ballHeight,
width = 18,
textureHeight = ballHeight * 2,
scale = HALF_SCALE
)
// Render Pokémon
matrices.pushPose()
matrices.translate(x + (PORTRAIT_DIAMETER / 2.0) - 2, y - 1.0, 0.0)
matrices.scale(2.5F, 2.5F, 1F)
drawProfilePokemon(
species = pokemon.species.resourceIdentifier,
matrixStack = matrices,
rotation = Quaternionf().fromEulerXYZDegrees(Vector3f(13F, 35F, 0F)),
state = state,
scale = 4.5F,
partialTicks = delta
)
matrices.popPose()
drawScaledTextJustifiedRight(
context = context,
text = lang("ui.lv.number", pokemon.level),
x = x + 54,
y = y + 13,
shadow = true,
scale = HALF_SCALE
)
if (pokemon.gender != Gender.GENDERLESS) {
blitk(
matrixStack = matrices,
texture = if (pokemon.gender == Gender.MALE) genderIconMale else genderIconFemale,
x = (x + 51) / HALF_SCALE,
y = (y + 19) / HALF_SCALE,
height = 8,
width = 6,
scale = HALF_SCALE
)
}
drawScaledTextJustifiedRight(
context = context,
text = pokemon.getDisplayName(),
x = x + ( if (pokemon.gender != Gender.GENDERLESS) 50 else 54),
y = y + 19,
shadow = true,
scale = HALF_SCALE
)
getLabel()?.let { label ->
drawScaledText(
context = context,
text = label,
x = x + (width / 2F),
y = y + 26,
centered = true,
scale = HALF_SCALE
)
}
// Held Item
val heldItem = pokemon.heldItemNoCopy()
if (!heldItem.isEmpty) {
renderScaledGuiItemIcon(
itemStack = heldItem,
x = x + 15.0,
y = y + 13.5,
scale = HALF_SCALE.toDouble(),
matrixStack = matrices
)
}
} else {
blitk(
matrixStack = matrices,
texture = slotDisabledResource,
x = x + 55,
y = y,
width = 5,
height = height,
textureWidth = width,
textureHeight = height * 2
)
}
}
override fun playDownSound(soundManager: SoundManager) {}
}

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