Compare commits

..
Author SHA1 Message Date
Lily 4faae33d43 mod station groundwork 2024-03-06 15:47:16 -05:00
Lily bd8f761ddb Merge branch 'tm-machine' into mod_station
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonItems.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/client/CobblemonClient.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/command/TestCommand.kt
#	common/src/main/resources/assets/cobblemon/lang/en_us.json
2024-03-06 15:07:52 -05:00
Paul 46798d694e Fixed generic entities not rendering, fixed Torkoal particles going upside down, fixed idle animation particles not playing, fixed loop particles not looping 2024-03-04 11:26:20 +10:00
Hiroku 056dd0fd4b Merge branch 'JsonScriptFixes' into 'main'
tiny Json script fixes

See merge request cable-mc/cobblemon!853
2024-03-03 07:54:12 +00:00
WaldleuferandHiroku 3ac00d976b tiny Json script fixes 2024-03-03 07:54:11 +00:00
Paul b30a76855b Fixed cancelling spawn events not actually cancelling the action, closes #612 2024-03-03 17:18:08 +10:00
Paul b6c964e03b Fixed Forge runs, again 2024-03-03 14:40:18 +10:00
Paul 3ed46cb7af Removed unused option in config (maxHorizontalSpace) 2024-03-03 13:38:59 +10:00
Paul cf9efe9b8c Fixed Snowstorm particles sometimes not appearing 2024-03-03 13:28:40 +10:00
Paul 02b9431731 Attach the entity from the entity particle packet to the storm (oops) 2024-03-03 13:14:13 +10:00
Paul a30ae0ccef Added query functions for entity_width, height, size, and radius to entity tracked particle storms 2024-03-03 12:46:28 +10:00
Hiroku a2905f4d37 Merge branch 'particle-events' into 'main'
Added "event" support to bedrock particles, locator support in the command,...

Closes #767

See merge request cable-mc/cobblemon!900
2024-03-03 02:00:36 +00:00
Paul b3aba1532c Added "event" support to bedrock particles, locator support in the command, and fixed inwards motion direction 2024-03-03 11:28:54 +10:00
Hiroku 6369bdb612 Merge branch '1.5_Structures' into 'main'
Added most of the 1.5 Structures

See merge request cable-mc/cobblemon!877
2024-03-03 00:20:02 +00:00
Hiroku b5db5605ef Merge branch 'FossilAnalyzerDirRenderFix' into 'main'
Fossils are now positioned correctly in all possible block facing directions

See merge request cable-mc/cobblemon!899
2024-03-02 23:48:29 +00:00
JazzMcNade 0207459a0f Fossils are now positioned correctly in all possible block facing directions 2024-03-02 16:37:44 -06:00
Hiroku 0e2fdfbce8 Merge branch 'ability-fix' into 'main'
Abilities fix

See merge request cable-mc/cobblemon!891
2024-03-02 05:20:10 +00:00
Hiroku 8c9da885c7 Merge branch 'main' into 'ability-fix'
# Conflicts:
#   CHANGELOG.md
2024-03-02 04:16:11 +00:00
Hiroku a5b6529f3c Merge branch 'display_case' into 'main'
Display case

See merge request cable-mc/cobblemon!879
2024-03-02 04:10:44 +00:00
Paul b430b7db76 Corrected Gimmighoul chest battle behaviour on server environments 2024-03-02 12:02:35 +10:00
Hiroku 8015ff0e20 Merge branch 'fix-grow-tumblestone-advancement' into 'main'
Grow Tumblestone Advancement triggers when placed near Lava or Magma

See merge request cable-mc/cobblemon!893
2024-03-02 01:21:55 +00:00
Dhyan PatelandHiroku ee78657e3d Grow Tumblestone Advancement triggers when placed near Lava or Magma 2024-03-02 01:21:55 +00:00
Hiroku 6cc84cd38c Merge branch 'tooltip-fix-fix' into 'main'
fix a small issue with appending tooltips

See merge request cable-mc/cobblemon!897
2024-03-02 00:28:30 +00:00
miasmus 31d6ff541e fix a small issue with appending tooltips 2024-03-01 16:36:31 -05:00
jeffw773 e1a4086f24 - push new changes for Slippy 2024-02-29 20:11:09 -08:00
Paul b1992a94d3 Fixed piglin entity mixin on Forge 2024-03-01 11:10:47 +10:00
Hiroku b41c4c21a8 Merge branch 'publish-versioning-fix' into 'main'
fix versioning

See merge request cable-mc/cobblemon!894
2024-02-28 22:07:33 +00:00
miasmus e1893f50ae fix versioning 2024-02-28 16:52:13 -05:00
Lily a458b31c9f Merge branch 'main' into display_case
# Conflicts:
#	.gitignore
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonBlocks.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonItems.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonSounds.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/client/CobblemonClient.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/client/gui/battle/subscreen/BattleGeneralActionSelection.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/item/group/CobblemonItemGroups.kt
#	common/src/main/resources/assets/cobblemon/lang/en_us.json
#	common/src/main/resources/assets/cobblemon/models/item/relic_coin_pouch.json
#	common/src/main/resources/assets/cobblemon/sounds.json
2024-02-28 14:07:19 -05:00
Lily 03b5336eb8 refactor getPositioningType to avoid using tags 2024-02-28 14:02:29 -05:00
Licious c24ab2049d Merge branch 'main' into ability-fix
# Conflicts:
#	CHANGELOG.md
2024-02-26 11:45:32 +00:00
Licious b7b1850a4e Clarify changelog. 2024-02-26 11:41:49 +00:00
Licious 8995c107f7 Fix abilities, add ability capsule & patch. 2024-02-26 11:38:22 +00:00
Lily ac65910380 updated the relic coin pouch rendering inside display case 2024-02-22 17:18:20 -05:00
Lily ba6847be9a Merge branch 'gimmighoul_gholdengo' into display_case
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonBlocks.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonSounds.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/resources/assets/cobblemon/lang/en_us.json
#	common/src/main/resources/assets/cobblemon/sounds.json
2024-02-22 16:27:39 -05:00
Lily 5ddbcd1830 various changes to display case 2024-02-22 15:03:17 -05:00
Lily 47605646d0 Merge branch 'main' into display_case 2024-02-22 14:26:33 -05:00
Lily d9fdf089c2 fix display case z-fighting 2024-02-22 14:00:15 -05:00
Lily e9a405694c display case sounds 2024-02-21 18:47:20 -05:00
Licious d5af099120 Merge branch 'main' into tm-machine 2024-02-21 20:34:34 +00:00
Lily 825e1ee110 Merge branch 'type_gem' into tm-machine
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/client/CobblemonClient.kt
#	common/src/main/resources/assets/cobblemon/lang/en_us.json
2024-02-20 17:54:13 -05:00
Lily ee0dc24207 temporarily removed type gems 2024-02-19 20:52:26 -05:00
Lily e5d702bb5a added new type gem sprites 2024-02-19 20:41:41 -05:00
Lily fb34d4bd23 suppressed gimmighoul's desire to be a necromancer 2024-02-16 20:50:03 -05:00
Paul 6ca1337ff9 Merge branch 'gimmighoul_gholdengo' into 1.5_Structures 2024-02-17 10:29:18 +10:00
Lily 0d344337dd Merge branch 'main' into display_case
# Conflicts:
#	.gitignore
#	CHANGELOG.md
#	common/src/main/kotlin/com/cobblemon/mod/common/client/CobblemonClient.kt
2024-02-16 11:55:15 -05:00
Lily c6e514216c added new obtain methods 2024-02-16 11:26:36 -05:00
Paul 3d27681aaa Randomize Pokémon spawn positions and position gimmighoul reveals better 2024-02-16 16:31:00 +10:00
Paul 352202203a Merge branch 'gimmighoul_gholdengo' into 1.5_Structures 2024-02-16 15:04:51 +10:00
Paul 62cfbd7087 Licenses 2024-02-16 15:03:53 +10:00
Paul 5a64e8ea1b Animated the trap chest opening 2024-02-16 15:03:15 +10:00
Paul 8dc9863a5d Conflict resolution and cleanup 2024-02-16 14:39:22 +10:00
Myslippy 9280258413 Implemented most of the Fossil & Ruin Structures 2024-02-16 03:13:32 +00:00
Paul 51a11163dd Merge remote-tracking branch 'origin/main' into gimmighoul_gholdengo
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonBlocks.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonSounds.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/events/CobblemonEvents.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/client/CobblemonClient.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/client/render/block/FossilTubeRenderer.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/client/render/models/blockbench/bedrock/animation/BedrockStatefulAnimation.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/item/group/CobblemonItemGroups.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/pokemon/Species.kt
#	common/src/main/resources/assets/cobblemon/lang/en_us.json
#	common/src/main/resources/assets/cobblemon/sounds.json
2024-02-16 12:43:35 +10:00
Lily d5ad84a495 refactored PositioningType 2024-02-13 19:02:53 -05:00
Lily b01086270a fix positioning on certain items that use block entity renderers 2024-02-12 19:18:18 -05:00
Lily 8d5eb5883d gilded chests now get blocked with blocks above them like regular chests do 2024-02-12 14:57:24 -05:00
JazzMcNade 0d12bf1190 Relic Coins Sacks can be bartered with Piglins for loot.
Can always remove this if undesired or make it a tag, change the item, etc.
2024-02-10 21:23:05 -06:00
Lily 4f773f63cb fix hopper dupe bug 2024-02-09 22:04:01 -05:00
Lily 3f3ae74535 update .gitignore 2024-02-09 19:58:44 -05:00
Lily 7429fea802 fixed crash on dedicated servers (again) + made cases compatible with hoppers 2024-02-09 19:46:23 -05:00
Lily ac5cd47715 multiple display case bugfixes 2024-02-09 17:59:57 -05:00
JazzMcNade c635a1d331 Removed duped block tag entry 2024-02-09 01:08:35 -06:00
JazzMcNade cf2173a281 Opening a gilded chest now aggros piglins 2024-02-09 00:15:55 -06:00
JazzMcNade 710d37f453 Added piglin related tags for glided chests and relic coin related items 2024-02-08 23:52:36 -06:00
Lily 1fb3416211 implemented the display case 2024-02-08 22:46:16 -05:00
Lily f80a2850bb template type gem block 2024-02-05 19:42:36 -05:00
Lily 64fe6cefde Merge branch 'crystal_block' into tm-machine 2024-02-05 19:24:12 -05:00
Lily 26f337c0fd unlock tms when the player obtains them 2024-02-05 16:48:44 -05:00
JazzMcNade ac1c7d8b78 Mobs can now pathfind around Gilded Chests 2024-02-04 20:00:20 -06:00
Lily 92dd85643e Merge branch 'amo/sendout-upgrade' into gimmighoul_gholdengo
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/api/events/CobblemonEvents.kt
2024-02-03 19:45:47 -05:00
Lily 8849de1bfd Merge remote-tracking branch 'origin/amo/sendout-upgrade' into amo/sendout-upgrade
# Conflicts:
#	common/src/main/resources/assets/cobblemon/bedrock/particles/balls/capture/capturestar.particle.json
#	common/src/main/resources/assets/cobblemon/bedrock/particles/balls/capture/hisui/hisuicapturestar.particle.json
2024-02-03 19:44:41 -05:00
Lily 2a349697b4 colored gilded chest variants :3 2024-02-01 20:28:12 -05:00
jeffw773 9fc81c1b33 - Amethyst Shards now only feed into the correct slot 2024-02-01 09:04:02 -08:00
JazzMcNade 5c599dbb9a TM machine monitor switches on and off with player interaction. 2024-02-01 01:08:34 -06:00
jeffw773 d5a94ff784 - Changes to Blockstate when being used by player
- Plays sound when crafted with automation (might not be wanted later idk)
2024-01-31 16:16:52 -08:00
jeffw773 024f299486 - Comparator output from TMM now updates with respect to actions done by TM Machine 2024-01-31 12:06:23 -08:00
jeffw773 9a2b588568 - TM Machine can now Hopper out created TMs and spit out TMs and craft with Redstone signals if wanted 2024-01-31 10:53:26 -08:00
JazzMcNade b6fb5e8776 manual crafting 2024-01-31 03:00:40 -06:00
jeffw773 987b9bb275 - Sync fixes for TMM Inventory and UI slots 2024-01-30 14:35:33 -08:00
Lily 03bda4e27d fixes to be compatible with the updated held item event 2024-01-30 17:33:51 -05:00
apion b04ab8b95b Took some stuff out of CobblemonClient and put it in TMIten (clean up) 2024-01-30 16:52:22 -05:00
Lily 5d8abbd4c7 Merge branch 'main' into gimmighoul_gholdengo
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/client/CobblemonClient.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/pokemon/Pokemon.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/pokemon/evolution/adapters/CobbledRequirementAdapter.kt
2024-01-30 16:51:56 -05:00
Lily 6b167a7a17 gilded chest variants are implemented, waiting for textures 2024-01-30 16:46:22 -05:00
Lily dab0623d61 Merge remote-tracking branch 'origin/tm-machine' into tm-machine 2024-01-30 14:16:21 -05:00
Lily cb5b6ec68b advancement obtain method 2024-01-30 14:16:11 -05:00
JazzMcNade ea57c9dc5d Fix for removeStack 2024-01-30 01:56:35 -06:00
apion d36c24075c Fixed rotations? 2024-01-29 16:48:21 -05:00
Lily d1a8122296 fixed rotation for gilded chest as well 2024-01-29 12:44:41 -05:00
Lily a8e6e684ab updated relic_coin_pouch.png 2024-01-29 12:22:55 -05:00
Lily 188fc2b5d9 fixed inconsistencies between the actual value of the facing property and the direction the coin pouch was facing 2024-01-29 12:19:50 -05:00
jeffw773 9abab091d9 - Materials feed into correct slots on TMM from hoppers 2024-01-28 13:01:49 -08:00
JazzMcNade e2e50d2f1b tm block now has a persistent inventory 2024-01-27 20:37:13 -06:00
jeffw773 4ac88ba192 - fixes to TM instances and isReadyToCraft conditional 2024-01-27 13:00:34 -08:00
jeffw773 37eccce8b1 - tweaks to tms 2024-01-27 12:14:13 -08:00
jeffw773 e44db7c380 - Filter Disc renders properly with client 2024-01-27 11:05:21 -08:00
JazzMcNade d57bb42720 Fix for shift click not always applying the filter 2024-01-27 05:13:19 -06:00
jeffw773 f0c44f040f Merge remote-tracking branch 'origin/tm-machine' into tm-machine 2024-01-26 21:52:45 -08:00
jeffw773 2d6e2f87ed - Hoppers and comparators are working for making TMs and Blank TMs 2024-01-26 21:52:27 -08:00
apion 8b4193a5db Fixed rotations 2024-01-26 22:54:33 -05:00
apion 667dfe32e3 More fun stuff 2024-01-26 22:27:13 -05:00
apion 218253765e Merge branch 'main' into tm-machine 2024-01-26 18:50:37 -05:00
Lily 7a15e6b004 Merge remote-tracking branch 'origin/amo/sendout-upgrade' into amo/sendout-upgrade
# Conflicts:
#	CHANGELOG.md
#	common/src/main/resources/assets/cobblemon/bedrock/particles/balls/capture/hisui/hisuicapturestar.particle.json
2024-01-26 17:46:34 -05:00
Lily 6ee6beddeb capture particles... again 2024-01-26 17:45:31 -05:00
Lily b92960da77 Merge branch 'main' into amo/sendout-upgrade 2024-01-26 17:29:13 -05:00
apion ff082adf67 Start working on rendering different color disks in the render machine 2024-01-26 17:27:49 -05:00
apion 66959816ef Merge branch 'main' into tm-machine
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonNetwork.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonSounds.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/storage/player/PlayerData.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
#	common/src/main/resources/cobblemon-common.accesswidener
2024-01-26 15:52:23 -05:00
jeffw773 0e85835a87 Merge remote-tracking branch 'origin/tm-machine' into tm-machine 2024-01-26 12:44:35 -08:00
jeffw773 e165d4958d - TM Machine Inventory Disc 2024-01-26 12:44:18 -08:00
JazzMcNade e0736e1988 TM Block is no longer treated as 2 blocks tall for placement. 2024-01-26 02:04:24 -06:00
jeffw773 724ff86a21 - TM Machine Inventory Test 2024-01-25 23:03:38 -08:00
jeffw773 97427fa58b - TM Machine Entity functioning 2024-01-25 17:04:38 -08:00
jeffw773 c9d47a3e21 - More TM Mechanics 2024-01-24 22:30:56 -08:00
jeffw773 c5c884ca2e - Testing New TM Functionality 2024-01-24 15:58:42 -08:00
jeffw773 f086c32b95 - TM Machine Testing 2024-01-24 14:25:49 -08:00
jeffw773 ecea176300 - Added placeholder Type Gem sprites for TM Materials 2024-01-23 16:11:39 -08:00
jeffw773 c00413bb32 - Added Back buttons to TM Machine 2024-01-23 14:28:57 -08:00
jeffw773 be78012ce6 - TM Machine can now scroll through TMs
- Fixed how Scroll List and TM entries are handled so they can be interacted with properly
2024-01-23 11:01:05 -08:00
jeffw773 3b285cc8bd - Changes to Relic Coin sack default placement direction for Structures 2024-01-19 10:51:21 -08:00
jeffw773 4dead98e6b - Pesky little chest Gimmieghouls spawn facing the direction of the chest block now 2024-01-18 18:16:09 -08:00
jeffw773 db36f8da2a - Gimmieghoul Chest and Gilded Chest now face the player when placed down 2024-01-18 16:07:45 -08:00
Paul 7215860d74 Merge remote-tracking branch 'origin/gimmighoul_gholdengo' into gimmighoul_gholdengo 2024-01-14 08:08:04 +10:00
Paul dc899ffc1b Fixed gimmighoul stuff for realsies this time 2024-01-14 08:07:49 +10:00
Lily 88fe55bbb9 pouch blocks and bugfixes 2024-01-13 16:23:39 -05:00
Paul 3d3c421b5c Fixed syncing issues with species feature providers 2024-01-12 18:15:10 +10:00
jeffw773 3844dd6314 - Added mouseClicked, mouseScrolled, mouseDragged to TMScrollingList (but still no work) 2024-01-11 12:34:14 -08:00
Lily 080077189f tm machine ui 2024-01-10 19:45:04 -05:00
Lily 89a4edb327 drops and creative inv 2024-01-09 15:59:33 -05:00
Lily ab37580258 gimmighoul chests crash ur game a little bit less now :o 2024-01-09 14:56:50 -05:00
Lily c116c01fd6 licenses 2024-01-09 14:16:09 -05:00
Lily e3ad8f8079 gimmi now challenges the player on reveal 2024-01-09 13:54:35 -05:00
Lily a491d508b6 gilded chest visuals, loot tables/recipes, sounds, etc. 2024-01-09 12:38:58 -05:00
apion 02fb825328 Update Gimmighoul model, add disguised chest block entity, gilded chests now save contents 2024-01-08 14:05:09 -05:00
Paul 8df6eea107 Fixed various issues with the gilded chest and its animations 2024-01-08 11:00:20 +10:00
apion 1376bb68f4 Tried again, failed again 2024-01-07 11:53:38 -05:00
apion beb8f2ccf1 Merge branch 'main' into gimmighoul_gholdengo
# Conflicts:
#	CHANGELOG.md
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonNetwork.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonSounds.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/storage/party/PlayerPartyStore.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/client/CobblemonClient.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/client/render/models/blockbench/PoseableEntityModel.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/client/render/models/blockbench/repository/PokemonModelRepository.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/entity/pokemon/PokemonEntity.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/net/messages/PokemonDTO.kt
#	common/src/main/resources/assets/cobblemon/lang/en_us.json
#	common/src/main/resources/assets/cobblemon/sounds.json
#	common/src/main/resources/data/cobblemon/species/generation9/gimmighoul.json
2024-01-07 10:41:34 -05:00
jeffw773 4fa86c7742 - Added Eiko's new TM Machine Textures and fixed inventory slot locations 2024-01-04 13:11:00 -08:00
Lily b0d57dd875 coin bags and blocks and stuff 2024-01-04 15:56:58 -05:00
Lily 6c09e2b553 Merge remote-tracking branch 'origin/gimmighoul_gholdengo' into gimmighoul_gholdengo 2024-01-04 13:47:21 -05:00
Lily 593104fb0a BIG COIN BAG!! 2024-01-04 13:45:43 -05:00
apion e314664731 Throw some stuff in and hope Hiro can fix it (I need a hero! I'm holding out for a hero...) 2024-01-04 07:31:29 -05:00
apion d0a38a9704 Add chest assets, do some work on getting chest animation working 2024-01-04 06:48:59 -05:00
jeffw773 3c9cff5027 - Tweaked position of TM Machine GUI slightly as well as tooltips and inventory slots 2024-01-03 23:02:07 -08:00
Lily 6e155d7651 Merge remote-tracking branch 'origin/gimmighoul_gholdengo' into gimmighoul_gholdengo 2024-01-04 00:04:41 -05:00
Lily ae8f8a2fb8 working run button!!! wow!!! 2024-01-04 00:04:26 -05:00
apion 5a6a83aac1 Cleanup, add new model 2024-01-03 23:50:55 -05:00
Lily a1047e05d4 psychic trim update + updated type icons 2024-01-03 20:19:38 -05:00
Lily b7c1157c5c slight changes to the TM UI 2024-01-03 13:47:41 -05:00
apion cc8a51abcd TM Menu actually rendering now 2024-01-03 06:45:25 -05:00
apion 38174155bf Improved registry process for HandledScreens 2024-01-03 03:05:58 -05:00
Lily 2698006f93 filterTms function for when the UI is implemented 2024-01-03 00:30:50 -05:00
Lily aa38674fc8 TMM sounds and subtitles 2024-01-02 22:40:04 -05:00
apion 9ca0742386 Added fixed TMM model, added system to register HandledScreens 2024-01-02 21:53:50 -05:00
Lily 32d291890e TM colors and type gem items 2024-01-02 18:36:04 -05:00
Lily 188ae2ac85 changes to the gimmighoul evolution system. added relic coins, updated bars, removed NetheriteMaterials and GoldMaterials 2024-01-02 14:09:52 -05:00
Lily 381fd1d5e2 tm machine visuals 2024-01-02 12:42:32 -05:00
Lily a2fbe16ede textures partially 2024-01-02 12:20:32 -05:00
Lily 0b478ebd5a working coloring for tm items based on two ints given in the tm json file 2024-01-02 11:40:27 -05:00
Lily b0b07e8edd functional obtain methods for tms, with an option to passively check them 2024-01-01 15:40:07 -05:00
Lily 95a0f9603c gilded chest model stuffs 2023-12-27 14:54:28 -05:00
Lily 824773150a more tm item stuff 2023-12-15 16:54:28 -05:00
Lily 3dc285cc70 tm item?!?!?! what the flip?!? 2023-12-11 19:48:36 -05:00
Lily d6fbb70db0 add level ranges to DisguisedPokemonBlock 2023-12-11 16:10:13 -05:00
Lily 150a5c668a redid disguised gimmighouls 2023-12-11 14:55:43 -05:00
apion e12d0bea66 Added TMBlock (lol) 2023-12-01 01:19:28 -05:00
Lily 1bf01a2826 more bar stuff 2023-11-30 15:16:32 -05:00
Lily 4d1bf677a1 bar color update 2023-11-29 21:49:06 -05:00
Lily 069f56acfd gholdengo/gimmighoul are basically done, gilded chests still unfinished 2023-11-29 20:32:20 -05:00
Lily 19cdc8fbef Merge remote-tracking branch 'origin/gimmighoul_gholdengo' into gimmighoul_gholdengo 2023-11-27 17:31:27 -05:00
Lily 1408b94f06 started on gilded chests 2023-11-27 17:31:17 -05:00
Paul de081dc514 Added aspects support for int range features 2023-11-27 11:53:06 +10:00
apion 5bc4994091 Added learned tms to PlayerData, added a way to obtain TMs 2023-11-22 05:01:09 -05:00
apion 2b992dfd51 Setup TM registry 2023-11-22 03:22:36 -05:00
Lily 1538f12934 more gimmighoul changes 2023-11-14 20:59:10 -05:00
Paul 857f67604c Fixed some logic in held item event + gimmighoul item 2023-11-14 19:30:27 +10:00
Paul ae49e3f977 Fixed gold materials data registry not being loaded 2023-11-14 19:18:45 +10:00
Lily ec01a03820 pushed some stuff idk what to put here 2023-11-14 00:47:15 -05:00
Lily 96cc3671da Merge branch 'held_item_event' into gimmighoul_gholdengo 2023-11-13 18:36:09 -05:00
Lily ad7c9c560c add an event for updating a pokemon's held item 2023-11-13 18:33:45 -05:00
Tyzillion ee15ff6b35 Corrected Gimmighoul chest and roaming size. 2023-11-13 16:49:00 -05:00
Tyzillion ca83103703 Bling, bling, every time I come around your city. 2023-11-13 16:33:07 -05:00
Tyzillion 1aeb894b09 Merge branch 'main' of https://gitlab.com/cable-mc/cobblemon into gimmighoul_gholdengo 2023-11-13 13:42:38 -05:00
Lily 8e9ab26963 json files for gold and netherite materials (doesnt work in-game yet) 2023-11-13 18:09:34 +00:00
Paul 44b4fbcfe3 Minor license thing 2023-11-13 20:04:29 +10:00
Paul 42cc1dd8a9 Upgraded species features so they can be synced and rendered in summary screen 2023-11-13 19:39:17 +10:00
Lily d69310e52c re-do the way the other stats screen draws stuff 2023-11-10 20:15:40 -05:00
Lily a0f89274d1 started work on SummaryValue properties (CW: messy, incomplete systems) 2023-11-10 18:57:11 -05:00
1035 changed files with 40415 additions and 1165 deletions
+5
View File
@@ -35,5 +35,10 @@ common/src/main/resources/assets/cobblemon/showdown
/fabric/src/generated/resources/cobblemon-common.accesswidener
/.ignored/
# Logs
common/logs
fabric/logs
forge/logs
# MacOS
.DS_Store
+12 -2
View File
@@ -14,6 +14,7 @@
- Added the hidden "True Vivillionaire" advancement.
- The "Poké Ball" Vivillon variant is now obtainable by evolving a Spewpa after obtaining the "Aspiring Vivillionaire" advancement.
- Added dynamic lighting support for any Species or Forms when using [LambDynamicLights](https://modrinth.com/mod/lambdynamiclights) or [Dynamic Lights Reforged](https://www.curseforge.com/minecraft/mc-mods/dynamiclights-reforged), at this time we don't ship with any defaults for this feature but addon creators can make use of this feature.
- Added a Display Case block! Use it to display your archaeological findings, or other items.
- Added 6 Pokémon-themed Pottery Sherds, obtained from ruin structures.
- Added a Pokémon-themed armor trim, obtained from ruin structures.
- You can now find Nether Fire Stone Ore in, surprisingly, the Nether.
@@ -23,11 +24,13 @@
- Added the EV Boosting Feathers.
- Added Type Gems.
- Added various new tags for searchability and better compatibility.
- Added the [Ability Capsule](https://bulbapedia.bulbagarden.net/wiki/Ability_Capsule) and [Patch](https://bulbapedia.bulbagarden.net/wiki/Ability_Patch), these have no acquisition method for now and only work as intended with the traditional ability format of 1 to 2 common abilities and 1 hidden ability.
- Added Gilded Chests, available in all Apricorn colors.
- Added Relic Coins, Relic Coin Pouches, and Relic Coin Sacks.
- Added Gimmighoul.
- Using Relic Coins, you can increase Gimmighoul's Coin Stash. Upon reaching 999, it can be evolved into Gholdengo.
- Additionally, if you give Gimmighoul a Netherite Scrap, you can increase its Netherite Stash. Who knows what might happen if it fills up?
- Completed the advancement for placing a Tumblestone near Lava or Magma to make it grow.
### Changes
- Pokémon now transition more smoothly between different poses.
@@ -41,7 +44,6 @@
- Apricorn Sprouts can now be planted directly on Apricorn leaves to create a new Apricorn of the same color.
- Added tooltips to interaction GUIs to make it clearer what they do.
- Poké Ball recipes now use tags for their center ingredient, rather than specificied Copper Ingot, Iron Ingot, Gold Ingot, and Diamond, allowing for further datapack customisation.
- Using the run button in battle will now actually flee the battle.
### Fixes
- Re-added the recipe for the Iron Vitamin.
@@ -62,6 +64,8 @@
- Fixed incompatibility with [Just Enough Resources](https://modrinth.com/mod/just-enough-resources-jer).
- Fixed incompatibility with [Visual Overhaul](https://modrinth.com/mod/visual-overhaul).
- Fixed an issue that allowed Pokemon to spawn on rails.
- Fixed the data set on Pokémon when abilities are given via properties such as in ``/spawnpokemon``.
- Fixed sometimes Pokémon losing hidden abilities when evolving, this fix will only take effect to newly created Pokémon.
### Developer
- Significantly changed the way properties in PokemonEntity work. This could break some plugins (not in a big way, but changes will be needed).
@@ -74,6 +78,11 @@
- Added the ``HeldItemEvent``, this comes with 2 implementation ``HeldItemEvent.Pre`` and ``HeldItemEvent.Post``.
- API to support dynamic lighting in any mod that implements this feature has been added to all Species & Forms, see LightingData class for details.
- Added a Mixin for the BrewingStandScreenHandler to fix the incorrect handling of quickMove in Fabric.
- Corrections to the ability implementation have been made that make ``Ability.forced`` function as intended and never reroll an ability for a Pokémon, please check your implementations to ensure the corrected behavior is intended for your use case.
- ``Pokemon.ability`` no longer has a public setter, please migrate to using ``Pokemon#updateAbility``, this handles the ability coordinate storage for you when necessary.
- ``Ability.forced``, ``Ability.index`` and ``Ability.priority`` have all had their setters internalized, there is no longer any need to manually adjust these migrate to the method mentioned above to handle that process for you.
- ``AbilityChanger`` has been added to API alongside some implementations, this is subject to change as the ability capsule and patch currently only expect the traditional behaviour in the Pokémon games of 1 to 2 regular abilities and 1 hidden.
- ``Pokemon#rollAbility`` has been added, this rerolls a legal ability for the Pokémon.
### Datapack & Resourcepack Creators
- The maximum amount of fossils that can fit in the Fossil Analyzer can be adjusted in the config.
@@ -108,6 +117,7 @@
}
```
- Following up on this change ``light_source`` was removed as a possible shoulder effect due to becoming unnecessary and never having had a default implementation.
- Added the item tag ``cobblemon:ability_changers``, contains the ``cobblemon:ability_capsule`` & ``cobblemon:ability_patch`` by default.
## [1.4.1 (December 23rd, 2023)](#1-4-1)
@@ -1043,4 +1053,4 @@ Thank you so much to all of our community translators that bring the mod to the
- Added species, ability, and move translations for `ko_ko`, `jp_jp`, `fr_fr`, `es_es`, `it_it`, and `zh_cn`.
## [1.0.0 (November 12th, 2022)](#1-0-0)
- Initial release.
- Initial release.
+2 -2
View File
@@ -9,5 +9,5 @@ version = "${project.property("mod_version")}+${project.property("mc_version")}"
val isSnapshot = project.property("snapshot")?.equals("true") ?: false
if (isSnapshot) {
version = "$version-SNAPSHOT-${versioning.info.branch}-${versioning.info.build}"
}
version = "$version-${versioning.info.branch}-${versioning.info.build}-SNAPSHOT"
}
+1
View File
@@ -0,0 +1 @@
[19:57:25] [Test worker/INFO]: Booting LaxPermissionValidator, permissions will be checked using Minecrafts permission level system, see https://minecraft.fandom.com/wiki/Permission_level
@@ -9,18 +9,23 @@
package com.cobblemon.mod.common.mixin;
import com.cobblemon.mod.common.CobblemonItems;
import net.minecraft.entity.EquipmentSlot;
import net.minecraft.entity.mob.PiglinEntity;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(PiglinEntity.class)
public class PiglinEntityMixin {
@Redirect(method = "equipToOffHand", at = @At(value = "INVOKE", target = "Lnet/minecraft/item/ItemStack;isOf(Lnet/minecraft/item/Item;)Z"))
public boolean cobblemon$isValidBarteringItem(ItemStack stack, Item item) {
//TODO: Make a tag?
return stack.isOf(item) || stack.isOf(CobblemonItems.RELIC_COIN_POUCH);
public abstract class PiglinEntityMixin {
@Inject(method = "equipToOffHand", at = @At(value = "HEAD"), cancellable = true)
public void cobblemon$isValidBarteringItem(ItemStack stack, CallbackInfo ci) {
PiglinEntity entity = (PiglinEntity)(Object) this;
if (stack.isOf(CobblemonItems.RELIC_COIN_POUCH)) {
entity.equipStack(EquipmentSlot.OFFHAND, stack);
entity.updateDropChances(EquipmentSlot.OFFHAND);
ci.cancel();
}
}
}
@@ -0,0 +1,20 @@
package com.cobblemon.mod.common.mixin.invoker;
import net.minecraft.block.AbstractBlock;
import net.minecraft.block.BlockSetType;
import net.minecraft.block.PressurePlateBlock;
import net.minecraft.resource.featuretoggle.FeatureSet;
import net.minecraft.screen.ScreenHandler;
import net.minecraft.screen.ScreenHandlerType;
import net.minecraft.screen.ScreenHandlerType.Factory;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.gen.Invoker;
@Mixin(ScreenHandlerType.class)
public interface ScreenHandlerTypeInvoker {
@Invoker("<init>")
static <T extends ScreenHandler> ScreenHandlerType<T> cobblemon$create(Factory<T> factory, FeatureSet requiredFeatures) {
throw new UnsupportedOperationException();
}
}
@@ -83,6 +83,7 @@ import com.cobblemon.mod.common.config.starter.StarterConfig
import com.cobblemon.mod.common.data.CobblemonDataProvider
import com.cobblemon.mod.common.events.AdvancementHandler
import com.cobblemon.mod.common.events.ServerTickHandler
import com.cobblemon.mod.common.item.CobblemonItem
import com.cobblemon.mod.common.item.PokeBallItem
import com.cobblemon.mod.common.net.messages.client.settings.ServerSettingsPacket
import com.cobblemon.mod.common.permission.LaxPermissionValidator
@@ -231,6 +232,9 @@ object Cobblemon {
}
}
// Register the grow_tumblestone advancement
PlatformEvents.RIGHT_CLICK_BLOCK.subscribe { AdvancementHandler.onTumbleStonePlaced(it) }
PlatformEvents.CHANGE_DIMENSION.subscribe {
it.player.party().forEach { pokemon -> pokemon.entity?.recallWithAnimation() }
}
@@ -17,6 +17,7 @@ import com.cobblemon.mod.common.block.entity.FossilMultiblockEntity
import com.cobblemon.mod.common.block.entity.RestorationTankBlockEntity
import com.cobblemon.mod.common.block.multiblock.FossilMultiblockBuilder
import com.cobblemon.mod.common.block.entity.*
import com.cobblemon.mod.common.block.entity.DisplayCaseBlockEntity
import com.cobblemon.mod.common.platform.PlatformRegistry
import net.minecraft.block.entity.BlockEntityType
import net.minecraft.registry.Registries
@@ -44,6 +45,8 @@ object CobblemonBlockEntities : PlatformRegistry<Registry<BlockEntityType<*>>, R
val SIGN: BlockEntityType<CobblemonSignBlockEntity> = this.create("sign", BlockEntityType.Builder.create(::CobblemonSignBlockEntity, CobblemonBlocks.APRICORN_SIGN, CobblemonBlocks.APRICORN_WALL_SIGN).build(null))
@JvmField
val HANGING_SIGN: BlockEntityType<CobblemonHangingSignBlockEntity> = this.create("hanging_sign", BlockEntityType.Builder.create(::CobblemonHangingSignBlockEntity, CobblemonBlocks.APRICORN_HANGING_SIGN, CobblemonBlocks.APRICORN_WALL_HANGING_SIGN).build(null))
@JvmField
val TM_BLOCK: BlockEntityType<TMBlockEntity> = this.create("tm_block", BlockEntityType.Builder.create(::TMBlockEntity, CobblemonBlocks.TM_MACHINE).build(null))
@JvmField
val GILDED_CHEST: BlockEntityType<GildedChestBlockEntity> = this.create("chest", BlockEntityType.Builder.create(::GildedChestBlockEntity,
@@ -77,4 +80,9 @@ object CobblemonBlockEntities : PlatformRegistry<Registry<BlockEntityType<*>>, R
CobblemonBlocks.FOSSIL_ANALYZER
).build(null)
)
@JvmField
val DISPLAY_CASE: BlockEntityType<DisplayCaseBlockEntity> = this.create("display_case",
BlockEntityType.Builder.create(::DisplayCaseBlockEntity, CobblemonBlocks.DISPLAY_CASE).build(null)
)
}
@@ -385,8 +385,44 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, RegistryKey<Registry<
.luminance { if ((it.get(PCBlock.ON) as Boolean) && (it.get(PCBlock.PART) == PCBlock.PCPart.TOP)) 10 else 0 }
)
)
val TM_MACHINE = create(
"tm_machine",
TMBlock(
AbstractBlock.Settings.create()
.mapColor(MapColor.GREEN)
.sounds(BlockSoundGroup.METAL)
.pistonBehavior(PistonBehavior.BLOCK)
.nonOpaque()
.luminance { if (it.get(TMBlock.ON) as Boolean) 10 else 0 }
)
)
@JvmField
val DISPLAY_CASE = create(
"display_case",
DisplayCaseBlock(
AbstractBlock.Settings.create()
.sounds(CobblemonSounds.DISPLAY_CASE_SOUNDS)
.nonOpaque()
.pistonBehavior(PistonBehavior.BLOCK)
.mapColor(MapColor.STONE_GRAY)
.strength(0.3f)
)
)
@JvmField
val MODIFICATION_STATION = create(
"modification_station",
ModStationBlock(
AbstractBlock.Settings.create()
.sounds(BlockSoundGroup.WOOD)
.nonOpaque()
.pistonBehavior(PistonBehavior.BLOCK)
.mapColor(MapColor.BROWN)
.strength(0.3f)
)
)
val RED_MINT = create("red_mint", MintBlock(MintType.RED, AbstractBlock.Settings.create().mapColor(MapColor.RED).noCollision().ticksRandomly().breakInstantly().sounds(CobblemonSounds.MINT_SOUNDS)))
@JvmField
val BLUE_MINT = create("blue_mint", MintBlock(MintType.BLUE, AbstractBlock.Settings.create().mapColor(MapColor.BLUE).noCollision().ticksRandomly().breakInstantly().sounds(CobblemonSounds.MINT_SOUNDS)))
@@ -420,6 +456,67 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, RegistryKey<Registry<
@JvmField
val POTTED_PEP_UP_FLOWER = this.create("potted_pep_up_flower", BlocksInvoker.createFlowerPotBlock(PEP_UP_FLOWER))
@JvmField
val NORMAL_TYPE_GEM_BLOCK = this.create("normal_type_gem_block", Block(AbstractBlock.Settings.copy(Blocks.AMETHYST_BLOCK)))
@JvmField
val NORMAL_TYPE_GEM = typeGem(
"normal_type_gem",
GrowableStoneBlock.STAGE_3,
7,
3,
null,
NORMAL_TYPE_GEM_BLOCK
)
@JvmField
val LARGE_BUDDING_NORMAL_TYPE_GEM = typeGem(
"large_budding_normal_type_gem",
GrowableStoneBlock.STAGE_2,
5,
3,
NORMAL_TYPE_GEM,
NORMAL_TYPE_GEM_BLOCK
)
@JvmField
val MEDIUM_BUDDING_NORMAL_TYPE_GEM = typeGem(
"medium_budding_normal_type_gem",
GrowableStoneBlock.STAGE_1,
4,
3,
LARGE_BUDDING_NORMAL_TYPE_GEM,
NORMAL_TYPE_GEM_BLOCK
)
@JvmField
val SMALL_BUDDING_NORMAL_TYPE_GEM = typeGem(
"small_budding_normal_type_gem",
GrowableStoneBlock.STAGE_0,
3,
4,
MEDIUM_BUDDING_NORMAL_TYPE_GEM,
NORMAL_TYPE_GEM_BLOCK
)
private fun typeGem(
name: String,
stage: Int,
height: Int,
xzOffset: Int,
nextStage: Block?,
baseBlock: Block
): TypeGemBlock {
return this.create(name, TypeGemBlock(
settings = AbstractBlock.Settings.create()
.pistonBehavior(PistonBehavior.DESTROY)
.nonOpaque()
.strength(1.5F)
.sounds(CobblemonSounds.TYPE_GEM_SOUNDS),
stage = stage,
height = height,
xzOffset = xzOffset,
nextStage = nextStage,
baseBlock = baseBlock
))
}
/**
* Returns a map of all the blocks that can be stripped with an axe in the format of input - output.
*
@@ -430,8 +527,6 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, RegistryKey<Registry<
APRICORN_LOG to STRIPPED_APRICORN_LOG
)
private fun apricornBlock(name: String, apricorn: Apricorn): ApricornBlock = this.create(name, ApricornBlock(AbstractBlock.Settings.create().mapColor(apricorn.mapColor()).ticksRandomly().strength(Blocks.OAK_LOG.hardness, Blocks.OAK_LOG.blastResistance).sounds(BlockSoundGroup.WOOD).nonOpaque(), apricorn))
private fun tumblestoneBlock(name: String, stage: Int, height: Int, xzOffset: Int, nextStage: Block?) : Block {
@@ -26,6 +26,7 @@ import net.minecraft.registry.tag.TagKey
import net.minecraft.resource.ResourceManager
import net.minecraft.resource.ResourceReloader
import net.minecraft.resource.ResourceType
import net.minecraft.screen.ScreenHandlerType
import net.minecraft.server.MinecraftServer
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.text.Text
@@ -158,6 +159,8 @@ interface CobblemonImplementation {
*/
fun registerResourceReloader(identifier: Identifier, reloader: ResourceReloader, type: ResourceType, dependencies: Collection<Identifier>)
fun registerScreenHandlerType(identifier: Identifier, screenHandlerType: ScreenHandlerType<*>)
/**
* TODO
*
@@ -8,6 +8,7 @@
package com.cobblemon.mod.common
import com.cobblemon.mod.common.api.item.ability.AbilityChanger
import com.cobblemon.mod.common.api.mulch.MulchVariant
import com.cobblemon.mod.common.api.pokeball.PokeBalls
import com.cobblemon.mod.common.api.pokemon.Natures
@@ -29,6 +30,7 @@ import com.cobblemon.mod.common.item.berry.PortionHealingBerryItem
import com.cobblemon.mod.common.item.berry.StatusCuringBerryItem
import com.cobblemon.mod.common.item.berry.VolatileCuringBerryItem
import com.cobblemon.mod.common.item.interactive.*
import com.cobblemon.mod.common.item.interactive.ability.AbilityChangeItem
import com.cobblemon.mod.common.platform.PlatformRegistry
import com.cobblemon.mod.common.pokeball.PokeBall
import com.cobblemon.mod.common.pokemon.helditem.CobblemonHeldItemManager
@@ -256,8 +258,13 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, RegistryKey<Registry<It
val HEALING_MACHINE = blockItem("healing_machine", CobblemonBlocks.HEALING_MACHINE)
@JvmField
val PC = blockItem("pc", CobblemonBlocks.PC)
val TM_MACHINE = blockItem("tm_machine", CobblemonBlocks.TM_MACHINE)
@JvmField
val PASTURE = blockItem("pasture", CobblemonBlocks.PASTURE)
@JvmField
val DISPLAY_CASE = blockItem("display_case", CobblemonBlocks.DISPLAY_CASE)
@JvmField
val MOD_STATION = blockItem("modification_station", CobblemonBlocks.MODIFICATION_STATION)
// Evolution items
@JvmField val LINK_CABLE = create("link_cable", LinkCableItem())
@@ -558,6 +565,11 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, RegistryKey<Registry<It
@JvmField val ELIXIR = create("elixir", ElixirItem(max = false))
@JvmField val MAX_ELIXIR = create("max_elixir", ElixirItem(max = true))
@JvmField
val ABILITY_CAPSULE = this.create("ability_capsule", AbilityChangeItem(AbilityChanger.COMMON_ABILITY))
@JvmField
val ABILITY_PATCH = this.create("ability_patch", AbilityChangeItem(AbilityChanger.HIDDEN_ABILITY))
/**
* Evolution Ores and Stones
*/
@@ -858,6 +870,10 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, RegistryKey<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(Item.Settings()))
@JvmField
val BLANK_TM = this.create("blank_tm", CobblemonItem(Item.Settings()))
// Type Gems
@JvmField
@@ -43,6 +43,8 @@ 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.SpawnSnowstormEntityParticleHandler
import com.cobblemon.mod.common.client.net.effect.SpawnSnowstormParticleHandler
import com.cobblemon.mod.common.client.net.gui.CraftBlankTMPacketHandler
import com.cobblemon.mod.common.client.net.gui.CraftTMPacketHandler
import com.cobblemon.mod.common.client.net.gui.InteractPokemonUIPacketHandler
import com.cobblemon.mod.common.client.net.gui.SummaryUIPacketHandler
import com.cobblemon.mod.common.client.net.pasture.ClosePastureHandler
@@ -131,11 +133,12 @@ import com.cobblemon.mod.common.net.messages.client.trade.TradeOfferExpiredPacke
import com.cobblemon.mod.common.net.messages.client.trade.TradeOfferNotificationPacket
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.CraftBlankTMPacket
import com.cobblemon.mod.common.net.messages.client.ui.CraftTMPacket
import com.cobblemon.mod.common.net.messages.client.ui.InteractPokemonUIPacket
import com.cobblemon.mod.common.net.messages.client.ui.SummaryUIPacket
import com.cobblemon.mod.common.net.messages.server.*
import com.cobblemon.mod.common.net.messages.server.battle.BattleSelectActionsPacket
import com.cobblemon.mod.common.net.messages.server.battle.FleeBattlePacket
import com.cobblemon.mod.common.net.messages.server.battle.RemoveSpectatorPacket
import com.cobblemon.mod.common.net.messages.server.battle.SpectateBattlePacket
import com.cobblemon.mod.common.net.messages.server.callback.move.MoveSelectCancelledPacket
@@ -171,7 +174,6 @@ import com.cobblemon.mod.common.net.messages.server.trade.UpdateTradeOfferPacket
import com.cobblemon.mod.common.net.serverhandling.ChallengeHandler
import com.cobblemon.mod.common.net.serverhandling.RequestInteractionsHandler
import com.cobblemon.mod.common.net.serverhandling.battle.BattleSelectActionsHandler
import com.cobblemon.mod.common.net.serverhandling.battle.FleeBattlePacketHandler
import com.cobblemon.mod.common.net.serverhandling.battle.RemoveSpectatorHandler
import com.cobblemon.mod.common.net.serverhandling.battle.SpectateBattleHandler
import com.cobblemon.mod.common.net.serverhandling.callback.move.MoveSelectCancelledHandler
@@ -409,7 +411,6 @@ object CobblemonNetwork : NetworkManager {
this.createServerBound(BattleSelectActionsPacket.ID, BattleSelectActionsPacket::decode, BattleSelectActionsHandler)
this.createServerBound(SpectateBattlePacket.ID, SpectateBattlePacket::decode, SpectateBattleHandler)
this.createServerBound(RemoveSpectatorPacket.ID, RemoveSpectatorPacket::decode, RemoveSpectatorHandler)
this.createServerBound(FleeBattlePacket.ID, FleeBattlePacket::decode, FleeBattlePacketHandler)
// Trade
this.createServerBound(AcceptTradeRequestPacket.ID, AcceptTradeRequestPacket::decode, AcceptTradeRequestHandler)
@@ -435,6 +436,10 @@ object CobblemonNetwork : NetworkManager {
this.createServerBound(PartyPokemonMoveSelectedPacket.ID, PartyPokemonMoveSelectedPacket::decode, PartyPokemonMoveSelectedHandler)
this.createServerBound(PartyMoveSelectCancelledPacket.ID, PartyMoveSelectCancelledPacket::decode, PartyMoveSelectCancelledHandler)
// TM Machine packets
this.createServerBound(CraftTMPacket.ID, CraftTMPacket::decode, CraftTMPacketHandler)
this.createServerBound(CraftBlankTMPacket.ID, CraftBlankTMPacket::decode, CraftBlankTMPacketHandler)
// Dialogue packets
this.createServerBound(EscapeDialoguePacket.ID, EscapeDialoguePacket::decode, EscapeDialogueHandler)
this.createServerBound(InputToDialoguePacket.ID, InputToDialoguePacket::decode, InputToDialogueHandler)
@@ -240,6 +240,19 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, RegistryKey<Regi
TUMBLESTONE_HIT,
TUMBLESTONE_STEP
)
@JvmField
val TM_USE = this.create("tm.use")
@JvmField
val TMM_CRAFT = this.create("tmm.craft")
@JvmField
val TMM_CRAFT_BLANK = this.create("tmm.craft_blank")
@JvmField
val TMM_OFF = this.create("tmm.off")
@JvmField
val TMM_ON = this.create("tmm.on")
@JvmField
val TYPE_GEM_SOUNDS = TUMBLESTONE_BLOCK_SOUNDS
@JvmField
val BERRY_BUSH_SOUNDS = BlockSoundGroup(1f, 1f,
@@ -295,5 +308,27 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, RegistryKey<Regi
GILDED_CHEST_STEP
)
@JvmField
val DISPLAY_CASE_ADD_ITEM = this.create("display_case.add_item")
@JvmField
val DISPLAY_CASE_REMOVE_ITEM = this.create("display_case.remove_item")
@JvmField
val DISPLAY_CASE_BREAK = this.create("display_case.break")
@JvmField
val DISPLAY_CASE_HIT = this.create("display_case.hit")
@JvmField
val DISPLAY_CASE_PLACE = this.create("display_case.place")
@JvmField
val DISPLAY_CASE_STEP = this.create("display_case.step")
@JvmField
val DISPLAY_CASE_SOUNDS = BlockSoundGroup(1f, 1f,
DISPLAY_CASE_BREAK,
DISPLAY_CASE_STEP,
DISPLAY_CASE_PLACE,
DISPLAY_CASE_HIT,
DISPLAY_CASE_STEP
)
private fun create(name: String): SoundEvent = this.create(name, SoundEvent.of(cobblemonResource(name)))
}
@@ -113,6 +113,14 @@ object CobblemonCriteria {
)
)
// Advancement criteria for [grow_tumblestone.json]
val PLANT_TUMBLESTONE = this.create(
SimpleCriterionTrigger(
cobblemonResource("plant_tumblestone"),
PlantTumblestoneCriterionCondition::class.java
)
)
private fun <T : Criterion<*>> create(criteria: T): T = Cobblemon.implementation.registerCriteria(criteria)
}
@@ -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.advancement.criterion
import com.cobblemon.mod.common.block.TumblestoneBlock
import com.google.gson.JsonObject
import net.minecraft.predicate.entity.LootContextPredicate
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.util.Identifier
import net.minecraft.util.math.BlockPos
/**
* 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: ServerPlayerEntity, context: PlantTumblestoneContext): Boolean {
return context.tumbleStoneBlock.canGrow(context.pos, player.world)
}
}
/**
* 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)
@@ -9,6 +9,7 @@
package com.cobblemon.mod.common.api.abilities
import com.cobblemon.mod.common.api.Priority
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.DataKeys
import com.google.gson.JsonObject
import net.minecraft.nbt.NbtCompound
@@ -21,7 +22,7 @@ import net.minecraft.nbt.NbtCompound
* @author Qu
* @since January 9th, 2022
*/
open class Ability internal constructor(var template: AbilityTemplate, var forced: Boolean) {
open class Ability internal constructor(var template: AbilityTemplate, forced: Boolean) {
val name: String
get() = template.name
@@ -34,16 +35,26 @@ open class Ability internal constructor(var template: AbilityTemplate, var force
/**
* This represents the last known index of this backing ability in the species data.
* If -1 the ability will attempt to find the correct index or reroll itself, this is done in order to be compatible with existing data
* The value can be mutated however this is not recommended by API users.
* @see [Pokemon.updateAbility].
*/
var forced: Boolean = forced
internal set
/**
* This represents the last known index of this backing ability in the species data.
*
* @see [Pokemon.updateAbility].
*/
var index: Int = -1
internal set
/**
* The last known priority of this ability in the species data.
* The value can be mutated however this is not recommended by API users.
*
* @see [Pokemon.updateAbility].
*/
var priority = Priority.LOWEST
internal set
open fun saveToNBT(nbt: NbtCompound): NbtCompound {
nbt.putString(DataKeys.POKEMON_ABILITY_NAME, name)
@@ -62,19 +73,23 @@ open class Ability internal constructor(var template: AbilityTemplate, var force
}
open fun loadFromNBT(nbt: NbtCompound): Ability {
forced = nbt.getBoolean(DataKeys.POKEMON_ABILITY_FORCED)
this.template = Abilities.getOrException(nbt.getString(DataKeys.POKEMON_ABILITY_NAME))
this.forced = nbt.getBoolean(DataKeys.POKEMON_ABILITY_FORCED)
if (nbt.contains(DataKeys.POKEMON_ABILITY_INDEX) && nbt.contains(DataKeys.POKEMON_ABILITY_PRIORITY)) {
this.index = nbt.getInt(DataKeys.POKEMON_ABILITY_INDEX)
this.priority = Priority.valueOf(nbt.getString(DataKeys.POKEMON_ABILITY_PRIORITY))
}
return this
}
open fun loadFromJSON(json: JsonObject): Ability {
forced = json.get(DataKeys.POKEMON_ABILITY_FORCED)?.asBoolean ?: false
this.template = Abilities.getOrException(json.get(DataKeys.POKEMON_ABILITY_NAME).asString)
this.forced = json.get(DataKeys.POKEMON_ABILITY_FORCED)?.asBoolean ?: false
if (json.has(DataKeys.POKEMON_ABILITY_INDEX) && json.has(DataKeys.POKEMON_ABILITY_PRIORITY)) {
this.index = json.get(DataKeys.POKEMON_ABILITY_INDEX).asInt
this.priority = Priority.valueOf(json.get(DataKeys.POKEMON_ABILITY_PRIORITY).asString)
}
return this
}
}
@@ -28,7 +28,6 @@ class AbilityTemplate(
*/
fun create(forced: Boolean = false) = builder(this, forced)
/**
* Returns the Ability and loads the given NBT Tag into it.
*
@@ -42,4 +41,5 @@ class AbilityTemplate(
* Ability extensions need to write and read their needed data from here.
*/
fun create(json: JsonObject) = create().loadFromJSON(json)
}
@@ -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.item.ability
import com.cobblemon.mod.common.api.abilities.*
import com.cobblemon.mod.common.item.interactive.ability.AbilityTypeChanger
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.pokemon.abilities.HiddenAbility
import com.cobblemon.mod.common.pokemon.abilities.HiddenAbilityType
/**
* Represents a change operation for a [Pokemon.ability].
* This is backed by the associated [PotentialAbility].
*
* @param T The type of [PotentialAbility].
*/
interface AbilityChanger<T : PotentialAbility> {
/**
* The [PotentialAbilityType] of type [T].
*/
val type: PotentialAbilityType<T>
/**
* Collects all the possible targets for the [type] and the given [pokemon].
*
* @param pokemon The [Pokemon] being queried.
* @return A set containing all their legal abilities associated with [type], can be empty.
*/
fun queryPossible(pokemon: Pokemon): Set<AbilityTemplate>
/**
* Attempts to update the ability of the given [pokemon].
*
* @param pokemon The [Pokemon] attempting to change an ability.
* @return If the operation was successful.
*/
fun performChange(pokemon: Pokemon): Boolean
/**
* Checks if the current [PotentialAbilityType] of a [Pokemon.ability] is possible to swap from with this.
*
* @param type The [PotentialAbilityType] or null if the ability is forced and/or illegal.
* @return If the operation can be performed.
*/
fun canChangeFrom(type: PotentialAbilityType<*>?): Boolean
companion object {
/**
* An implementation of [AbilityChanger] for [CommonAbility].
*
* While this can be used in custom data scenarios this functions most "game-like" if the Pokémon only has 2 common abilities.
*/
@JvmStatic
val COMMON_ABILITY: AbilityChanger<CommonAbility> = AbilityTypeChanger(CommonAbilityType) { other -> other == CommonAbilityType }
/**
* An implementation of [AbilityChanger] for [HiddenAbility].
*
* While this can be used in custom data scenarios this functions most "game-like" if the Pokémon only has 1 hidden ability.
*/
@JvmStatic
val HIDDEN_ABILITY: AbilityChanger<HiddenAbility> = AbilityTypeChanger(HiddenAbilityType) { other -> other == CommonAbilityType || other == HiddenAbilityType }
}
}
@@ -13,6 +13,7 @@ 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.abilities.Abilities
import com.cobblemon.mod.common.api.abilities.Ability
import com.cobblemon.mod.common.api.pokeball.PokeBalls
import com.cobblemon.mod.common.api.pokemon.aspect.AspectProvider
import com.cobblemon.mod.common.api.pokemon.stats.Stats
@@ -294,6 +295,18 @@ open class PokemonProperties {
)
fun apply(pokemon: Pokemon) {
// Custom properties check could be duplicated but can't assume 3rd party hasn't done anything type specific
this.customProperties.forEach { it.apply(pokemon) }
this.commonApply(pokemon)
}
fun apply(pokemonEntity: PokemonEntity) {
// Custom properties check could be duplicated but can't assume 3rd party hasn't done anything type specific
this.customProperties.forEach { it.apply(pokemonEntity) }
this.commonApply(pokemonEntity.pokemon)
}
private fun commonApply(pokemon: Pokemon) {
species?.let {
return@let try {
if (it == "random") {
@@ -313,8 +326,8 @@ open class PokemonProperties {
friendship?.let { pokemon.setFriendship(it) }
pokeball?.let { PokeBalls.getPokeBall(it.asIdentifierDefaultingNamespace())?.let { pokeball -> pokemon.caughtBall = pokeball } }
nature?.let { Natures.getNature(it.asIdentifierDefaultingNamespace())?.let { nature -> pokemon.nature = nature } }
ability?.let { Abilities.getOrException(it).create(true).let { ability -> pokemon.ability = ability } }
status?.let { Statuses.getStatus(it)?.let { pokemon.applyStatus(it as PersistentStatus) } }
ability?.let { this.createAbility(it, pokemon.form)?.let(pokemon::updateAbility) }
status?.let { Statuses.getStatus(it)?.let { status -> if (status is PersistentStatus) pokemon.applyStatus(status) } }
customProperties.forEach { it.apply(pokemon) }
ivs?.let { ivs ->
ivs.forEach { stat ->
@@ -355,69 +368,17 @@ open class PokemonProperties {
pokemon.updateAspects()
}
fun apply(pokemonEntity: PokemonEntity) {
species?.let {
return@let try {
if (it == "random") {
PokemonSpecies.species.random()
} else {
PokemonSpecies.getByIdentifier(it.asIdentifierDefaultingNamespace())
}
} catch (e: InvalidIdentifierException) {
null
}
}?.let { pokemonEntity.pokemon.species = it }
nickname?.let { pokemonEntity.pokemon.nickname = it }
form?.let { formID -> pokemonEntity.pokemon.species.forms.firstOrNull { it.formOnlyShowdownId().equals(formID, true) } }?.let { form -> pokemonEntity.pokemon.form = form }
level?.let { pokemonEntity.pokemon.level = it }
shiny?.let { pokemonEntity.pokemon.shiny = it }
gender?.let { pokemonEntity.pokemon.gender = it }
friendship?.let { pokemonEntity.pokemon.setFriendship(it) }
pokeball?.let { PokeBalls.getPokeBall(it.asIdentifierDefaultingNamespace())?.let { pokeball -> pokemonEntity.pokemon.caughtBall = pokeball } }
nature?.let { Natures.getNature(it.asIdentifierDefaultingNamespace())?.let { nature -> pokemonEntity.pokemon.nature = nature } }
ability?.let { Abilities.getOrException(it).create(true).let { ability -> pokemonEntity.pokemon.ability = ability } }
status?.let { Statuses.getStatus(it)?.let { pokemonEntity.pokemon.applyStatus(it as PersistentStatus) } }
customProperties.forEach { it.apply(pokemonEntity) }
ivs?.let { ivs ->
ivs.forEach { stat ->
pokemonEntity.pokemon.setIV(stat.key, stat.value)
}
}
evs?.let { evs ->
evs.forEach { stat ->
pokemonEntity.pokemon.setEV(stat.key, stat.value)
}
}
teraType?.let { ElementalTypes.get(it)?.let { type -> pokemonEntity.pokemon.teraType = type } }
dmaxLevel?.let { pokemonEntity.pokemon.dmaxLevel = it }
gmaxFactor?.let { pokemonEntity.pokemon.gmaxFactor = it }
tradeable?.let { pokemonEntity.pokemon.tradeable = it }
// If it has been explicitly set to NONE, clear the stored information, including the originalTrainer property
originalTrainerType?.let {
if (it == OriginalTrainerType.NONE) {
pokemonEntity.pokemon.removeOriginalTrainer()
originalTrainer = null
}
}
originalTrainer?.let { ot ->
val type = originalTrainerType ?: pokemonEntity.pokemon.originalTrainerType
when (type) {
OriginalTrainerType.PLAYER -> {
when (ot.length) {
in 3..16 -> server()?.userCache?.findByName(ot)?.get()?.id // OT is a Username
36 -> UUID.fromString(ot) // OT is a UUID
else -> null // OT is invalid
}?.let { uuid -> pokemonEntity.pokemon.setOriginalTrainer(uuid) }
}
OriginalTrainerType.NPC -> pokemonEntity.pokemon.setOriginalTrainer(ot)
else -> {}
}
pokemonEntity.pokemon.refreshOriginalTrainer()
}
pokemonEntity.pokemon.updateAspects()
fun matches(pokemon: Pokemon): Boolean {
// Custom properties check could be duplicated but can't assume 3rd party hasn't done anything type specific
return this.commonMatches(pokemon) && this.customProperties.none { !it.matches(pokemon) }
}
fun matches(pokemon: Pokemon): Boolean {
fun matches(pokemonEntity: PokemonEntity): Boolean {
// Custom properties check could be duplicated but can't assume 3rd party hasn't done anything type specific
return this.commonMatches(pokemonEntity.pokemon) && this.customProperties.none { !it.matches(pokemonEntity) }
}
private fun commonMatches(pokemon: Pokemon): Boolean {
level?.takeIf { it != pokemon.level }?.let { return false }
shiny?.takeIf { it != pokemon.shiny }?.let { return false }
gender?.takeIf { it != pokemon.gender }?.let { return false }
@@ -454,53 +415,17 @@ open class PokemonProperties {
tradeable?.takeIf { it != pokemon.tradeable }?.let { return false }
originalTrainer?.takeIf { it != pokemon.originalTrainer }?.let{ return false }
originalTrainerType?.takeIf { it != pokemon.originalTrainerType }?.let{ return false }
return customProperties.none { !it.matches(pokemon) }
}
fun matches(pokemonEntity: PokemonEntity): Boolean {
level?.takeIf { it != pokemonEntity.pokemon.level }?.let { return false }
shiny?.takeIf { it != ("shiny" in pokemonEntity.pokemon.aspects) }?.let { return false }
gender?.takeIf { it != pokemonEntity.pokemon.gender }?.let { return false }
species?.run {
try {
val species = if (this == "random") {
PokemonSpecies.species.random()
} else {
PokemonSpecies.getByIdentifier(this.asIdentifierDefaultingNamespace()) ?: return@run
}
if (pokemonEntity.pokemon.species != species) {
return false
}
} catch (e: InvalidIdentifierException) {}
}
nickname?.takeIf { it != pokemonEntity.pokemon.nickname }?.let { return false }
form?.takeIf { !it.equals(pokemonEntity.pokemon.form.name, true) }?.let { return false }
friendship?.takeIf { it != pokemonEntity.pokemon.friendship }?.let { return false }
pokeball?.takeIf { it != pokemonEntity.pokemon.caughtBall.name.toString() }?.let { return false }
nature?.takeIf { it != pokemonEntity.pokemon.nature.name.toString() }?.let { return false }
ability?.takeIf { it != pokemonEntity.pokemon.ability.name }?.let { return false }
status?.takeIf { it != pokemonEntity.pokemon.status?.status?.showdownName }?.let { return false }
ivs?.forEach{ stat ->
if (stat.value != pokemonEntity.pokemon.ivs[stat.key]) { return false }
}
evs?.forEach{ stat ->
if (stat.value != pokemonEntity.pokemon.evs[stat.key]) { return false }
}
teraType?.takeIf { it != pokemonEntity.pokemon.teraType.name }?.let { return false }
dmaxLevel?.takeIf { it != pokemonEntity.pokemon.dmaxLevel }?.let { return false }
gmaxFactor?.takeIf { it != pokemonEntity.pokemon.gmaxFactor }?.let { return false }
tradeable?.takeIf { it != pokemonEntity.pokemon.tradeable }?.let { return false }
originalTrainer?.takeIf { it != pokemonEntity.pokemon.originalTrainer }?.let{ return false }
originalTrainerType?.takeIf { it != pokemonEntity.pokemon.originalTrainerType }?.let{ return false }
return customProperties.none { !it.matches(pokemonEntity) }
return true
}
// TODO Review the need for this, usage for us is in commands that deviate from the behavior established by multiple Pokémon query equivalent
fun isSubSetOf(properties: PokemonProperties): Boolean {
level?.takeIf { it != properties.level }?.let { return false }
shiny?.takeIf { it != ("shiny" in properties.aspects) }?.let { return false }
gender?.takeIf { it != properties.gender }?.let { return false }
species?.run {
try {
// TODO get context on this bit, it's highly unlikely two randoms would result in the same Pokémon, doesn't mean the prop is not equal?
val species = if (this == "random") {
PokemonSpecies.species.random()
} else {
@@ -509,7 +434,7 @@ open class PokemonProperties {
if (properties.species != species.toString()) {
return false
}
} catch (e: InvalidIdentifierException) {}
} catch (_: InvalidIdentifierException) {}
}
nickname?.takeIf { it != properties.nickname }?.let { return false }
form?.takeIf { !it.equals(properties.form, true) }?.let { return false }
@@ -558,13 +483,11 @@ open class PokemonProperties {
else baseTypes.random()
}
// If the config value is at least 1, then do 1/x and use that as the property chance
private fun Float.checkRate(): Boolean = this >= 1 && (Random.Default.nextFloat() < 1 / this)
fun createEntity(world: World): PokemonEntity {
return PokemonEntity(world, create())
}
// TODO Codecs at some point
fun saveToNBT(): NbtCompound {
val nbt = NbtCompound()
originalString.let { nbt.putString(DataKeys.POKEMON_PROPERTIES_ORIGINAL_TEXT, it) }
@@ -593,6 +516,7 @@ open class PokemonProperties {
return nbt
}
// TODO Codecs at some point
fun loadFromNBT(tag: NbtCompound): PokemonProperties {
originalString = tag.getString(DataKeys.POKEMON_PROPERTIES_ORIGINAL_TEXT)
level = if (tag.contains(DataKeys.POKEMON_LEVEL)) tag.getInt(DataKeys.POKEMON_LEVEL) else null
@@ -621,6 +545,7 @@ open class PokemonProperties {
return this
}
// TODO Codecs at some point
fun saveToJSON(): JsonObject {
val json = JsonObject()
originalString.let { json.addProperty(DataKeys.POKEMON_PROPERTIES_ORIGINAL_TEXT, it) }
@@ -650,6 +575,7 @@ open class PokemonProperties {
return json
}
// TODO Codecs at some point
fun loadFromJSON(json: JsonObject): PokemonProperties {
originalString = json.get(DataKeys.POKEMON_PROPERTIES_ORIGINAL_TEXT)?.asString ?: ""
level = json.get(DataKeys.POKEMON_LEVEL)?.asInt
@@ -727,4 +653,21 @@ open class PokemonProperties {
fun copy(): PokemonProperties {
return PokemonProperties().loadFromJSON(saveToJSON())
}
// If the config value is at least 1, then do 1/x and use that as the property chance
private fun Float.checkRate(): Boolean = this >= 1 && (Random.Default.nextFloat() < 1 / this)
/**
* Attempts to find an ability by ID and resolve if it should be forced or if it's legal for the given form.
*
* @param id The literal ID of the [Ability] being mapped.
* @param form The [FormData] whose [FormData.abilities] is queried for legality.
* @return The [Ability] with the [Ability.forced] state necessary for the given [form].
*/
private fun createAbility(id: String, form: FormData): Ability? {
val ability = Abilities.get(id) ?: return null
val potentialAbility = form.abilities.firstOrNull { potential -> potential.template == ability } ?: return ability.create(true)
return potentialAbility.template.create(false)
}
}
@@ -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
@@ -93,6 +94,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
}
// We only sync level up moves atm
override fun shouldSynchronize(other: Learnset) = other.levelUpMoves != this.levelUpMoves
@@ -20,7 +20,6 @@ import com.mojang.serialization.codecs.PrimitiveCodec
import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.network.PacketByteBuf
import net.minecraft.util.Identifier
import net.minecraft.util.math.Vec3d
/**
* Effect details for the actual particles of a particle effect.
@@ -44,48 +43,106 @@ class BedrockParticle(
var cameraMode: ParticleCameraMode = RotateXYZCameraMode(),
var tinting: ParticleTinting = ExpressionParticleTinting(),
var collision: ParticleCollision = ParticleCollision(),
var environmentLighting: Boolean = false
var environmentLighting: Boolean = false,
var creationEvents: MutableList<SimpleEventTrigger> = mutableListOf(),
var expirationEvents: MutableList<SimpleEventTrigger> = mutableListOf(),
var timeline: EventTriggerTimeline = EventTriggerTimeline(mutableMapOf())
) {
class ExpressionSet(
val sizeX: Expression,
val sizeY: Expression,
val maxAge: Expression,
val killExpression: Expression,
val updateExpressions: MutableList<Expression>,
val renderExpressions: MutableList<Expression>
)
/*
"creation_event": "event_sendspark",
"expiration_event": ["event_spark", "event_afterspark", "event_capturestar"],
"timeline": {
"21.00": "event_spark"
}
*/
class EventSet(
val creationEvents: MutableList<SimpleEventTrigger>,
val expirationEvents: MutableList<SimpleEventTrigger>,
val timeline: EventTriggerTimeline
)
// kill plane maybe
companion object {
val CODEC: Codec<BedrockParticle> = RecordCodecBuilder.create { instance ->
val EXPRESSION_SET_CODEC = RecordCodecBuilder.create<ExpressionSet> { instance ->
instance.group(
Identifier.CODEC.fieldOf("texture").forGetter { it.texture },
PrimitiveCodec.STRING.fieldOf("material").forGetter { it.material.name },
ParticleUVMode.codec.fieldOf("uvMode").forGetter { it.uvMode },
EXPRESSION_CODEC.fieldOf("sizeX").forGetter { it.sizeX },
EXPRESSION_CODEC.fieldOf("sizeY").forGetter { it.sizeY },
EXPRESSION_CODEC.fieldOf("maxAge").forGetter { it.maxAge },
EXPRESSION_CODEC.fieldOf("killExpression").forGetter { it.killExpression },
ListCodec(EXPRESSION_CODEC).fieldOf("updateExpressions").forGetter { it.updateExpressions },
ListCodec(EXPRESSION_CODEC).fieldOf("renderExpressions").forGetter { it.renderExpressions },
ListCodec(EXPRESSION_CODEC).fieldOf("renderExpressions").forGetter { it.renderExpressions }
).apply(instance, ::ExpressionSet)
}
val EVENT_SET_CODEC = RecordCodecBuilder.create<EventSet> { instance ->
instance.group(
ListCodec(SimpleEventTrigger.CODEC).fieldOf("creationEvents").forGetter { it.creationEvents },
ListCodec(SimpleEventTrigger.CODEC).fieldOf("expirationEvents").forGetter { it.expirationEvents },
EventTriggerTimeline.CODEC.fieldOf("timeline").forGetter { it.timeline }
).apply(instance, ::EventSet)
}
val CODEC: Codec<BedrockParticle> = RecordCodecBuilder.create { instance ->
instance.group(
Identifier.CODEC.fieldOf("texture").forGetter { it.texture },
PrimitiveCodec.STRING.fieldOf("material").forGetter { it.material.name },
ParticleUVMode.codec.fieldOf("uvMode").forGetter { it.uvMode },
EXPRESSION_SET_CODEC.fieldOf("expressionSet").forGetter {
ExpressionSet(
it.sizeX,
it.sizeY,
it.maxAge,
it.killExpression,
it.updateExpressions,
it.renderExpressions
)
},
ParticleMotion.codec.fieldOf("motion").forGetter { it.motion },
ParticleRotation.codec.fieldOf("rotation").forGetter { it.rotation },
ParticleViewDirection.codec.fieldOf("viewDirection").forGetter { it.viewDirection },
ParticleCameraMode.codec.fieldOf("cameraMode").forGetter { it.cameraMode },
ParticleTinting.codec.fieldOf("tinting").forGetter { it.tinting },
ParticleCollision.CODEC.fieldOf("collision").forGetter { it.collision },
PrimitiveCodec.BOOL.fieldOf("environmentLighting").forGetter { it.environmentLighting }
PrimitiveCodec.BOOL.fieldOf("environmentLighting").forGetter { it.environmentLighting },
EVENT_SET_CODEC.fieldOf("eventSet").forGetter {
EventSet(
it.creationEvents,
it.expirationEvents,
it.timeline
)
}
).apply(instance) {
texture, materialStr, uvMode, sizeX, sizeY, maxAge, killExpression, updateExpressions,
renderExpressions, motion, rotation, viewDirection, cameraMode, tinting, collision, environmentLighting ->
texture, materialStr, uvMode, expressionSet, motion, rotation, viewDirection, cameraMode, tinting, collision, environmentLighting, eventSet ->
BedrockParticle(
texture = texture,
material = ParticleMaterial.valueOf(materialStr),
uvMode = uvMode,
sizeX = sizeX,
sizeY = sizeY,
maxAge = maxAge,
killExpression = killExpression,
updateExpressions = updateExpressions,
renderExpressions = renderExpressions,
sizeX = expressionSet.sizeX,
sizeY = expressionSet.sizeY,
maxAge = expressionSet.maxAge,
killExpression = expressionSet.killExpression,
updateExpressions = expressionSet.updateExpressions,
renderExpressions = expressionSet.renderExpressions,
motion = motion,
rotation = rotation,
viewDirection = viewDirection,
cameraMode = cameraMode,
tinting = tinting,
collision = collision,
environmentLighting = environmentLighting
environmentLighting = environmentLighting,
creationEvents = eventSet.creationEvents,
expirationEvents = eventSet.expirationEvents,
timeline = eventSet.timeline
)
}
}
@@ -108,6 +165,9 @@ class BedrockParticle(
ParticleTinting.writeToBuffer(buffer, tinting)
collision.writeToBuffer(buffer)
buffer.writeBoolean(environmentLighting)
buffer.writeCollection(creationEvents) { pb, event -> event.encode(pb) }
buffer.writeCollection(expirationEvents) { pb, event -> event.encode(pb) }
timeline.encode(buffer)
}
fun readFromBuffer(buffer: PacketByteBuf) {
@@ -127,5 +187,8 @@ class BedrockParticle(
tinting = ParticleTinting.readFromBuffer(buffer)
collision.readFromBuffer(buffer)
environmentLighting = buffer.readBoolean()
creationEvents = buffer.readList { SimpleEventTrigger("").also { it.decode(buffer) } }
expirationEvents = buffer.readList { SimpleEventTrigger("").also { it.decode(buffer) } }
timeline.decode(buffer)
}
}
@@ -10,7 +10,9 @@ package com.cobblemon.mod.common.api.snowstorm
import com.mojang.serialization.Codec
import com.mojang.serialization.codecs.ListCodec
import com.mojang.serialization.codecs.PrimitiveCodec
import com.mojang.serialization.codecs.RecordCodecBuilder
import com.mojang.serialization.codecs.UnboundedMapCodec
import net.minecraft.network.PacketByteBuf
import net.minecraft.util.Identifier
@@ -27,7 +29,8 @@ class BedrockParticleEffect(
var emitter: BedrockParticleEmitter = BedrockParticleEmitter(),
var particle: BedrockParticle = BedrockParticle(),
var curves: MutableList<MoLangCurve> = mutableListOf(),
var space: ParticleSpace = ParticleSpace()
var space: ParticleSpace = ParticleSpace(),
var events: MutableMap<String, ParticleEvent> = mutableMapOf()
) {
companion object {
val CODEC: Codec<BedrockParticleEffect> = RecordCodecBuilder.create { instance ->
@@ -36,14 +39,16 @@ class BedrockParticleEffect(
BedrockParticleEmitter.CODEC.fieldOf("emitter").forGetter { it.emitter },
BedrockParticle.CODEC.fieldOf("particle").forGetter { it.particle },
ListCodec(MoLangCurve.codec).fieldOf("curves").forGetter { it.curves },
ParticleSpace.CODEC.fieldOf("space").forGetter { it.space }
).apply(instance) { id, emitter, particle, curves, space ->
ParticleSpace.CODEC.fieldOf("space").forGetter { it.space },
UnboundedMapCodec(PrimitiveCodec.STRING, ParticleEvent.CODEC).fieldOf("events").forGetter { it.events }
).apply(instance) { id, emitter, particle, curves, space, events ->
BedrockParticleEffect(
id = id,
emitter = emitter,
particle = particle,
curves = curves.toMutableList(),
space = space
space = space,
events = events
)
}
}
@@ -55,6 +60,7 @@ class BedrockParticleEffect(
particle.writeToBuffer(buffer)
buffer.writeCollection(curves) { pb, curve -> MoLangCurve.writeToBuffer(buffer, curve) }
space.writeToBuffer(buffer)
buffer.writeMap(events, { _, v -> buffer.writeString(v) }) { _, event -> event.encode(buffer) }
}
fun readFromBuffer(buffer: PacketByteBuf) {
@@ -63,5 +69,6 @@ class BedrockParticleEffect(
particle.readFromBuffer(buffer)
curves = buffer.readList { MoLangCurve.readFromBuffer(buffer) }
space.readFromBuffer(buffer)
events = buffer.readMap({ buffer.readString() }) { ParticleEvent().also { it.decode(buffer) } }
}
}
@@ -29,7 +29,12 @@ class BedrockParticleEmitter(
var updateExpressions: MutableList<Expression> = mutableListOf(),
var rate: ParticleEmitterRate = InstantParticleEmitterRate(),
var shape: ParticleEmitterShape = SphereParticleEmitterShape(),
var lifetime: ParticleEmitterLifetime = OnceEmitterLifetime(NumberExpression(1.0))
var lifetime: ParticleEmitterLifetime = OnceEmitterLifetime(NumberExpression(1.0)),
var eventTimeline: EventTriggerTimeline = EventTriggerTimeline(mutableMapOf()),
var creationEvents: MutableList<SimpleEventTrigger> = mutableListOf(),
var expirationEvents: MutableList<SimpleEventTrigger> = mutableListOf(),
var travelDistanceEvents: EventTriggerTimeline = EventTriggerTimeline(mutableMapOf()),
var loopingTravelDistanceEvents: MutableList<LoopingTravelDistanceEventTrigger> = mutableListOf()
) {
companion object {
val CODEC: Codec<BedrockParticleEmitter> = RecordCodecBuilder.create { instance ->
@@ -38,14 +43,24 @@ class BedrockParticleEmitter(
ListCodec(EXPRESSION_CODEC).fieldOf("updateExpressions").forGetter { it.updateExpressions },
ParticleEmitterRate.codec.fieldOf("rate").forGetter { it.rate },
ParticleEmitterShape.codec.fieldOf("shape").forGetter { it.shape },
ParticleEmitterLifetime.codec.fieldOf("lifetime").forGetter { it.lifetime }
).apply(instance) { startExpressions, updateExpressions, rate, shape, lifetime ->
ParticleEmitterLifetime.codec.fieldOf("lifetime").forGetter { it.lifetime },
EventTriggerTimeline.CODEC.fieldOf("eventTimeline").forGetter { it.eventTimeline },
ListCodec(SimpleEventTrigger.CODEC).fieldOf("creationEvents").forGetter { it.creationEvents },
ListCodec(SimpleEventTrigger.CODEC).fieldOf("expirationEvents").forGetter { it.expirationEvents },
EventTriggerTimeline.CODEC.fieldOf("travelDistanceEvents").forGetter { it.travelDistanceEvents },
ListCodec(LoopingTravelDistanceEventTrigger.CODEC).fieldOf("loopingTravelDistanceEvents").forGetter { it.loopingTravelDistanceEvents }
).apply(instance) { startExpressions, updateExpressions, rate, shape, lifetime, eventTimeline, creationEvents, expirationEvents, travelDistanceEvents, loopingTravelDistanceEvents ->
BedrockParticleEmitter(
startExpressions = startExpressions,
updateExpressions = updateExpressions,
shape = shape,
rate = rate,
lifetime = lifetime
lifetime = lifetime,
eventTimeline = eventTimeline,
creationEvents = creationEvents,
expirationEvents = expirationEvents,
travelDistanceEvents = travelDistanceEvents,
loopingTravelDistanceEvents = loopingTravelDistanceEvents
)
}
}
@@ -57,6 +72,11 @@ class BedrockParticleEmitter(
ParticleEmitterRate.writeToBuffer(buffer, rate)
ParticleEmitterShape.writeToBuffer(buffer, shape)
ParticleEmitterLifetime.writeToBuffer(buffer, lifetime)
eventTimeline.encode(buffer)
buffer.writeCollection(creationEvents) { pb, event -> event.encode(pb) }
buffer.writeCollection(expirationEvents) { pb, event -> event.encode(pb) }
travelDistanceEvents.encode(buffer)
buffer.writeCollection(loopingTravelDistanceEvents) { pb, event -> event.encode(pb) }
}
fun readFromBuffer(buffer: PacketByteBuf) {
@@ -65,5 +85,10 @@ class BedrockParticleEmitter(
rate = ParticleEmitterRate.readFromBuffer(buffer)
shape = ParticleEmitterShape.readFromBuffer(buffer)
lifetime = ParticleEmitterLifetime.readFromBuffer(buffer)
eventTimeline.decode(buffer)
creationEvents = buffer.readList { SimpleEventTrigger("").also { it.decode(buffer) } }
expirationEvents = buffer.readList { SimpleEventTrigger("").also { it.decode(buffer) } }
travelDistanceEvents.decode(buffer)
loopingTravelDistanceEvents = buffer.readList { LoopingTravelDistanceEventTrigger(0.0, mutableListOf()).also { it.decode(buffer) } }
}
}
@@ -136,6 +136,11 @@ class LoopingEmitterLifetime(var activeTime: Expression = 1.0.asExpression(), va
val interval = activeTimeValue + sleepTime
val displacement = emitterAge % interval
runtime.environment.setSimpleVariable("emitter_lifetime", activeTime)
if (emitterAge > activeTimeValue && sleepTime == 0.0) {
return ParticleEmitterAction.STOP
}
return if (displacement < activeTimeValue) {
ParticleEmitterAction.GO
} else {
@@ -145,7 +145,7 @@ class PointParticleEmitterShape(
override val type = ParticleEmitterShapeType.POINT
override fun getNewParticlePosition(runtime: MoLangRuntime, entity: Entity?) = runtime.resolveVec3d(offset)
override fun getCenter(runtime: MoLangRuntime, entity: Entity?): Vec3d {
return getNewParticlePosition(runtime, entity) // Questionable
return Vec3d.ZERO
}
override fun <T> encode(ops: DynamicOps<T>) = CODEC.encodeStart(ops, this)
@@ -0,0 +1,174 @@
/*
* 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.snowstorm
import com.bedrockk.molang.runtime.MoLangRuntime
import com.cobblemon.mod.common.api.molang.ExpressionLike
import com.cobblemon.mod.common.api.molang.MoLangFunctions.getQueryStruct
import com.cobblemon.mod.common.api.molang.MoLangFunctions.setup
import com.cobblemon.mod.common.api.net.Decodable
import com.cobblemon.mod.common.api.net.Encodable
import com.cobblemon.mod.common.client.particle.BedrockParticleEffectRepository
import com.cobblemon.mod.common.client.particle.ParticleStorm
import com.cobblemon.mod.common.client.render.MatrixWrapper
import com.cobblemon.mod.common.client.render.SnowstormParticle
import com.cobblemon.mod.common.util.asExpressionLike
import com.mojang.serialization.Codec
import com.mojang.serialization.codecs.PrimitiveCodec
import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.network.PacketByteBuf
import net.minecraft.sound.SoundCategory
import net.minecraft.sound.SoundEvent
import net.minecraft.util.Identifier
import net.minecraft.util.math.Vec3d
/**
* An event that can be referenced from various particle event triggers. The events are not necessarily particles,
* but can be combinations of particles, sounds, and MoLang expressions.
*
* @author Hiroku
* @since March 2nd, 2024
*/
class ParticleEvent(
var particleEffect: EventParticleEffect? = null,
var soundEffect: EventSoundEffect? = null,
var expression: ExpressionLike? = null
): Encodable, Decodable {
companion object {
val CODEC: Codec<ParticleEvent> = RecordCodecBuilder.create { instance ->
instance.group(
EventParticleEffect.CODEC.optionalFieldOf("particle_effect", null).forGetter { it.particleEffect },
EventSoundEffect.CODEC.optionalFieldOf("sound_effect", null).forGetter { it.soundEffect },
PrimitiveCodec.STRING.optionalFieldOf("expression", null).forGetter { it.expression?.toString() }
).apply(instance) { particleEffect, soundEffect, expression ->
ParticleEvent(
particleEffect,
soundEffect,
expression?.asExpressionLike()
)
}
}
}
override fun encode(buffer: PacketByteBuf) {
buffer.writeNullable(particleEffect) { pb, effect ->
pb.writeIdentifier(effect.effect)
pb.writeEnumConstant(effect.type)
pb.writeNullable(effect.expression) { pb, expr -> pb.writeString(expr.toString()) }
}
buffer.writeNullable(soundEffect) { pb, effect -> pb.writeIdentifier(effect.sound) }
buffer.writeNullable(expression) { pb, expr -> pb.writeString(expr.toString()) }
}
override fun decode(buffer: PacketByteBuf) {
particleEffect = buffer.readNullable { pb -> EventParticleEffect(
pb.readIdentifier(),
pb.readEnumConstant(EventParticleEffect.EventParticleType::class.java),
pb.readNullable { pb.readString().asExpressionLike() }
) }
soundEffect = buffer.readNullable { pb -> EventSoundEffect(pb.readIdentifier()) }
expression = buffer.readNullable { pb -> pb.readString().asExpressionLike() }
}
fun run(storm: ParticleStorm, particle: SnowstormParticle?) {
particleEffect?.let { effect ->
val bedrockParticleEffect = BedrockParticleEffectRepository.getEffect(effect.effect) ?: return@let
val rootMatrix = when (effect.type) {
EventParticleEffect.EventParticleType.EMITTER -> MatrixWrapper().updatePosition(storm.matrixWrapper.getOrigin())
EventParticleEffect.EventParticleType.EMITTER_BOUND -> storm.matrixWrapper
EventParticleEffect.EventParticleType.PARTICLE,
EventParticleEffect.EventParticleType.PARTICLE_WITH_VELOCITY -> (particle?.let { Vec3d(it.getX(), it.getY(), it.getZ()) } ?: Vec3d(storm.getX(), storm.getY(), storm.getZ())).let { MatrixWrapper().updatePosition(it) }
}
val sourceVelocity = when (effect.type) {
EventParticleEffect.EventParticleType.EMITTER -> storm.sourceVelocity().let { { it } }
EventParticleEffect.EventParticleType.EMITTER_BOUND -> storm.sourceVelocity
EventParticleEffect.EventParticleType.PARTICLE -> { { Vec3d.ZERO } }
EventParticleEffect.EventParticleType.PARTICLE_WITH_VELOCITY -> (particle?.let { Vec3d(it.getVelocityX(), it.getVelocityY(), it.getVelocityZ()) } ?: Vec3d.ZERO).let { { it } }
}
val newStorm = ParticleStorm(
effect = bedrockParticleEffect,
matrixWrapper = rootMatrix,
world = storm.world,
sourceVelocity = sourceVelocity,
sourceAlive = storm.sourceAlive,
sourceVisible = storm.sourceVisible,
onDespawn = {},
runtime = MoLangRuntime().setup().also { it.environment.structs["query"] = storm.runtime.environment.getQueryStruct() },
entity = storm.entity
)
effect.expression?.resolve(newStorm.runtime)
newStorm.spawn()
}
soundEffect?.let { effect ->
val position = particle?.let { Vec3d(it.getX(), it.getY(), it.getZ()) } ?: Vec3d(storm.getX(), storm.getY(), storm.getZ())
val world = storm.world
val soundEvent = SoundEvent.of(effect.sound)
world.playSound(position.x, position.y, position.z, soundEvent, SoundCategory.NEUTRAL, 1F, 1F, true)
}
expression?.resolve(storm.runtime)
}
}
/**
* A particle component of a [ParticleEvent]. It contains the effect to play, the spawning type, and a pre-run
* expression to run in the new storm's runtime.
*
* @author Hiroku
* @since March 2nd, 2024
*/
class EventParticleEffect(
val effect: Identifier,
val type: EventParticleType,
val expression: ExpressionLike? = null
) {
companion object {
val CODEC = RecordCodecBuilder.create<EventParticleEffect> { instance ->
instance.group(
Identifier.CODEC.fieldOf("effect").forGetter { it.effect },
PrimitiveCodec.STRING.fieldOf("type").forGetter { it.type.name },
PrimitiveCodec.STRING.optionalFieldOf("expression", null).forGetter { it.expression?.toString() }
).apply(instance) { effect, type, expression ->
EventParticleEffect(
effect,
EventParticleType.valueOf(type),
expression?.asExpressionLike()
)
}
}
}
enum class EventParticleType {
EMITTER,
EMITTER_BOUND,
PARTICLE,
PARTICLE_WITH_VELOCITY
}
}
/**
* A simple sound effect component of a [ParticleEvent]. Plays a sound at the particle location, or if run from an
* emitter event, at the emitter's location.
*
* @author Hiroku
* @since March 2nd, 2024
*/
class EventSoundEffect(
val sound: Identifier,
) {
companion object {
val CODEC = RecordCodecBuilder.create<EventSoundEffect> { instance ->
instance.group(
Identifier.CODEC.fieldOf("sound").forGetter { it.sound }
).apply(instance, ::EventSoundEffect)
}
}
}
@@ -0,0 +1,118 @@
/*
* 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.snowstorm
import com.cobblemon.mod.common.api.net.Decodable
import com.cobblemon.mod.common.api.net.Encodable
import com.cobblemon.mod.common.client.particle.ParticleStorm
import com.cobblemon.mod.common.client.render.SnowstormParticle
import com.mojang.serialization.codecs.PrimitiveCodec
import com.mojang.serialization.codecs.RecordCodecBuilder
import com.mojang.serialization.codecs.UnboundedMapCodec
import net.minecraft.network.PacketByteBuf
/*
* The different references to events that can be triggered by an effect.
*/
/**
* It really is simple.
*
* @author Hiroku
* @since March 2nd, 2024
*/
class SimpleEventTrigger(var event: String): Encodable, Decodable {
companion object {
val CODEC = RecordCodecBuilder.create<SimpleEventTrigger> { instance ->
instance.group(
PrimitiveCodec.STRING.fieldOf("event").forGetter { it.event }
).apply(instance, ::SimpleEventTrigger)
}
}
override fun encode(buffer: PacketByteBuf) {
buffer.writeString(event)
}
override fun decode(buffer: PacketByteBuf) {
event = buffer.readString()
}
fun trigger(storm: ParticleStorm, particle: SnowstormParticle?) {
val event = storm.effect.events[event] ?: return
event.run(storm, particle)
}
}
/**
* An event trigger that outlines specific times at which point groups of effects will play. This can be used
* on the basis of time but is also used in the case of travel distance.
*
* @author Hiroku
* @since March 2nd, 2024
*/
class EventTriggerTimeline(var map: MutableMap<Double, MutableList<String>>): Encodable, Decodable {
companion object {
val CODEC = RecordCodecBuilder.create<EventTriggerTimeline> { instance ->
instance.group(
UnboundedMapCodec(PrimitiveCodec.DOUBLE, PrimitiveCodec.STRING.listOf()).fieldOf("map").forGetter { it.map }
).apply(instance, ::EventTriggerTimeline)
}
}
override fun encode(buffer: PacketByteBuf) {
buffer.writeMap(map, { pb, k -> pb.writeDouble(k) }, { pb, v -> pb.writeCollection(v) { _, s -> pb.writeString(s) } })
}
override fun decode(buffer: PacketByteBuf) {
map = buffer.readMap({ pb -> pb.readDouble() }, { pb -> pb.readList { pb.readString() } })
}
fun check(storm: ParticleStorm, particle: SnowstormParticle?, previousTime: Double, newTime: Double) {
val events = map.entries.filter { it.key in previousTime..newTime }.flatMap { it.value }
events.forEach { event ->
val event = storm.effect.events[event] ?: return
event.run(storm, particle)
}
}
}
/**
* An event trigger that takes the total distance travelled by the emitter and modulos it across [distance] to know
* if we need to play it again.
*
* @author Hiroku
* @since March 2nd, 2024
*/
class LoopingTravelDistanceEventTrigger(var distance: Double, var events: MutableList<String>): Encodable, Decodable {
companion object {
val CODEC = RecordCodecBuilder.create<LoopingTravelDistanceEventTrigger> { instance ->
instance.group(
PrimitiveCodec.DOUBLE.fieldOf("distance").forGetter { it.distance },
PrimitiveCodec.STRING.listOf().fieldOf("events").forGetter { it.events }
).apply(instance, ::LoopingTravelDistanceEventTrigger)
}
}
override fun encode(buffer: PacketByteBuf) {
buffer.writeDouble(distance)
buffer.writeCollection(events) { _, s -> buffer.writeString(s) }
}
override fun decode(buffer: PacketByteBuf) {
distance = buffer.readDouble()
events = buffer.readList { buffer.readString() }
}
fun check(storm: ParticleStorm, particle: SnowstormParticle?, previousDistance: Double, currentDistance: Double) {
if (previousDistance < distance && currentDistance >= distance) {
events.mapNotNull { storm.effect.events[it] }.forEach { it.run(storm, particle) }
}
}
}
@@ -140,7 +140,7 @@ class InwardsMotionDirection : ParticleMotionDirection {
return if (particlePos == emitterPos) {
Vec3d(storm.world.random.nextDouble() - 0.5, storm.world.random.nextDouble() - 0.5, storm.world.random.nextDouble() - 0.5)
} else {
particlePos.subtract(emitterPos)
emitterPos.subtract(particlePos)
}.normalize()
}
override fun <T> encode(ops: DynamicOps<T>) = CODEC.encodeStart(ops, this)
@@ -30,9 +30,16 @@ abstract class SingleEntitySpawnAction<T : Entity>(
override fun run(): EntitySpawnResult? {
val e = createEntity() ?: return null
e.setPosition(ctx.position.toVec3d().add(0.5, 1.0, 0.5))
entity.emit(e)
CobblemonEvents.ENTITY_SPAWN.postThen(SpawnEvent(e, ctx), ifSucceeded = { ctx.world.spawnEntity(e) })
return EntitySpawnResult(listOf(e))
var shouldSpawn = false
CobblemonEvents.ENTITY_SPAWN.postThen(SpawnEvent(e, ctx), ifSucceeded = {
ctx.world.spawnEntity(e)
shouldSpawn = true
})
return if (shouldSpawn) {
this.entity.emit(e)
EntitySpawnResult(listOf(e))
} else null
}
@@ -8,6 +8,7 @@
package com.cobblemon.mod.common.api.storage.adapter.conversions
import com.cobblemon.mod.common.api.abilities.Abilities
import com.cobblemon.mod.common.api.moves.Moves
import com.cobblemon.mod.common.api.pokeball.PokeBalls
import com.cobblemon.mod.common.api.pokemon.Natures
@@ -98,8 +99,9 @@ class ReforgedConversion(val base: Path) : CobblemonConverter<NbtCompound> {
result.level = nbt.getInt("Level")
result.addExperience(SidemodExperienceSource("Reforged"), nbt.getInt("EXP"))
result.setFriendship(nbt.getInt("Friendship"))
result.ability = (result.form.abilities.find { it.template.name == nbt.getString("Ability") } ?: result.form.abilities.first())
.template.create()
Abilities.get(nbt.getString("Ability"))?.let { template ->
result.updateAbility(template.create(forced = result.form.abilities.none { it.template == template }))
}
result.nature = Natures.getNature(Identifier(ReforgedNatures.values()[nbt.getInt("Nature")].name.lowercase())) ?: Natures.getRandomNature()
result.mintedNature = Natures.getNature(Identifier(ReforgedNatures.values()[nbt.getInt("MintNature")].name.lowercase()))
result.currentHealth = nbt.getInt("Health")
@@ -10,6 +10,7 @@ package com.cobblemon.mod.common.api.storage.player
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonNetwork.sendPacket
import com.cobblemon.mod.common.api.tms.TechnicalMachine
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.net.messages.client.starter.SetClientPlayerDataPacket
import java.util.UUID
@@ -23,6 +24,7 @@ data class PlayerData(
var starterSelected: Boolean,
var starterUUID: UUID?,
var keyItems: MutableSet<Identifier>,
var tmSet: MutableSet<Identifier>,
var battleTheme: Identifier?,
val extraData: MutableMap<String, PlayerDataExtension>,
) {
@@ -42,6 +44,7 @@ data class PlayerData(
starterUUID = null,
keyItems = mutableSetOf(),
battleTheme = CobblemonSounds.PVP_BATTLE.id,
tmSet = mutableSetOf(),
extraData = mutableMapOf()
)
}
@@ -10,6 +10,8 @@ package com.cobblemon.mod.common.api.storage.player.adapter
import com.cobblemon.mod.common.api.storage.player.PlayerData
import com.cobblemon.mod.common.api.storage.player.PlayerDataExtension
import com.cobblemon.mod.common.api.tms.ObtainMethod
import com.cobblemon.mod.common.util.adapters.CobblemonObtainMethodAdapter
import com.cobblemon.mod.common.util.adapters.IdentifierAdapter
import com.cobblemon.mod.common.util.fromJson
import com.google.gson.GsonBuilder
@@ -31,6 +33,7 @@ class JsonPlayerData: PlayerDataStoreAdapter {
.setPrettyPrinting()
.disableHtmlEscaping()
.registerTypeAdapter(PlayerDataExtension::class.java, PlayerDataExtensionAdapter)
.registerTypeAdapter(ObtainMethod::class.java, CobblemonObtainMethodAdapter)
.registerTypeAdapter(Identifier::class.java, IdentifierAdapter)
.create()
}
@@ -144,6 +144,9 @@ object CobblemonItemTags {
@JvmField
val POTTERY_SHERDS = create("decorated_pot_sherds")
@JvmField
val ABILITY_CHANGERS = create("ability_changers")
private fun create(path: String) = TagKey.of(RegistryKeys.ITEM, cobblemonResource(path))
}
@@ -0,0 +1,26 @@
/*
* 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.server.network.ServerPlayerEntity
interface ObtainMethod {
/**
* Whether to check this [ObtainMethod] once every second.
*/
val passive: Boolean
/**
* Checks if the [ObtainMethod] condition is true.
*
* @param player The player to use when checking.
* @return Whether the check succeeded.
*/
fun matches(player: ServerPlayerEntity): Boolean
}
@@ -0,0 +1,23 @@
package com.cobblemon.mod.common.api.tms
import com.google.gson.JsonDeserializer
import com.google.gson.JsonSerializer
import net.minecraft.util.Identifier
import kotlin.reflect.KClass
/**
* A type adapter for [ObtainMethod]s.
* For the default implementation see [CobblemonObtainMethodAdapter].
*
* @author Apion
* @since November 22nd, 2023
*/
interface ObtainMethodAdapter : JsonDeserializer<ObtainMethod>, JsonSerializer<ObtainMethod> {
/**
* Register a [ObtainMethod] to be used by this adapter.
*
* @param type The [KClass] of the [ObtainMethod].
* @param identifier The expected [Identifier] in the parsed JSON.
*/
fun register(type: KClass<out ObtainMethod>, identifier: Identifier)
}
@@ -0,0 +1,119 @@
/*
* 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.Cobblemon
import com.cobblemon.mod.common.api.moves.Moves
import com.cobblemon.mod.common.api.types.ElementalType
import com.cobblemon.mod.common.api.types.ElementalTypes
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.tms.obtain.NoneObtainMethod
import com.cobblemon.mod.common.util.lang
import net.minecraft.item.Item
import net.minecraft.item.Items
import net.minecraft.registry.Registries
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.text.MutableText
import net.minecraft.text.Text
import net.minecraft.util.Identifier
class TechnicalMachine(
val moveName: String,
val recipe: TechnicalMachineRecipe?,
val obtainMethods: List<ObtainMethod> = emptyList(),
val type: String,
val primaryColor: Int?,
val secondaryColor: Int?
) {
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
* @author whatsy
*/
fun filterTms(search: String?, type: ElementalType?, pokemon: Pokemon?): 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()
val move = Moves.getByName(tm.moveName)
if (!pokemon.species.moves.tmLearnableMoves().contains(move)) {
iterator.remove()
}
}
}
search?.let {
val iterator = tms.iterator()
while (iterator.hasNext()) {
val tm = iterator.next()
if (!tm.translatedMoveName().toString().contains(search, true)) {
iterator.remove()
}
}
}
return tms
}
}
/**
* Gets the [Identifier] of this [TechnicalMachine]
*
* @return The [Identifier] of this [TechnicalMachine] (or null if not found)
* @author whatsy
*/
fun id(): Identifier? {
TechnicalMachines.tmMap.forEach {
if (TechnicalMachines.tmMap[it.key] == this) return it.key
}
return null
}
/**
* Unlocks this [TechnicalMachine] for the player.
*
* @param player The [ServerPlayerEntity] to give this [TechnicalMachine] to.
* @return Whether the player was successfully granted the [TechnicalMachine]
* @author whatsy
*/
fun unlock(player: ServerPlayerEntity): Boolean {
if (id() == null) return false
Cobblemon.playerData.get(player).tmSet.add(id()!!)
if (!obtainMethods.any { it is NoneObtainMethod }) player.sendMessage(lang("tms.unlock_tm", lang("move.$moveName")))
return true
}
/**
* Returns a [MutableText] of the translated move name of this [TechnicalMachine]
*
* @return This [TechnicalMachine]'s move name, translated
* @author whatsy
*/
fun translatedMoveName(): MutableText {
return lang("move." + this.moveName)
}
}
@@ -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.util.Identifier
/**
* Represents the ingredients necessary to craft a [TechnicalMachine] in the [TMBlock]
*/
data class TechnicalMachineRecipe(
val item: Identifier,
val count: Int
)
@@ -0,0 +1,76 @@
/*
* 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.Cobblemon
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.api.data.JsonDataRegistry
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.item.CobblemonItem
import com.cobblemon.mod.common.item.TechnicalMachineItem
import com.cobblemon.mod.common.registry.ItemTagCondition
import com.cobblemon.mod.common.util.adapters.CobblemonObtainMethodAdapter
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 net.minecraft.item.ItemStack
import net.minecraft.registry.Registries
import net.minecraft.resource.ResourceType
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.util.Identifier
object TechnicalMachines : JsonDataRegistry<TechnicalMachine> {
override val gson = GsonBuilder()
.registerTypeAdapter(Identifier::class.java, IdentifierAdapter)
.registerTypeAdapter(ObtainMethod::class.java, CobblemonObtainMethodAdapter)
.create()
override val typeToken = TypeToken.get(TechnicalMachine::class.java)
override val resourcePath = "tms"
override val id = cobblemonResource("technical_machines")
override val type = ResourceType.SERVER_DATA
override val observable = SimpleObservable<TechnicalMachines>()
val tmMap = mutableMapOf<Identifier, TechnicalMachine>()
val tagMap = mutableMapOf<ItemTagCondition, TechnicalMachine>()
val passiveTms = mutableMapOf<Identifier, TechnicalMachine>()
override fun reload(data: Map<Identifier, TechnicalMachine>) {
data.forEach {id, tm ->
tmMap[id] = tm
if (tm.obtainMethods.any { it.passive }) passiveTms.put(id, tm)
}
}
override fun sync(player: ServerPlayerEntity) { }
fun getTechnicalMachineFromStack(item: ItemStack?): TechnicalMachine? {
if (item == null) {
return null
}
return TechnicalMachineItem.getMoveNbt(item)
}
fun getStackFromTechnicalMachine(tm: TechnicalMachine): ItemStack {
val tmItem = CobblemonItems.TECHNICAL_MACHINE.defaultStack
return CobblemonItems.TECHNICAL_MACHINE.setNbt(tmItem,tm.id().toString())
}
fun isTechnicalMachine(item: ItemStack): Boolean {
return item.item is TechnicalMachineItem
}
fun checkPassives(player: ServerPlayerEntity) {
val playerTms = Cobblemon.playerData.get(player).tmSet
passiveTms.forEach { (id, tm) ->
if (tm.obtainMethods.all { it.matches(player) } && !playerTms.contains(id)) tm.unlock(player)
}
}
}
@@ -13,17 +13,23 @@ import net.minecraft.text.MutableText
import net.minecraft.util.Identifier
/**
* Class representing a type of a Pokemon or Move
* Class representing a type of Pokémon or Move
*
* @param name: The English name used to load / find it (spaces -> _)
* @param displayName: A Component used to display the name, normally a TranslatableText
* @param textureXMultiplier: The multiplier by which the TypeWidget shall move the display
* @param resourceLocation: The location of the resource used in the TypeWidget
* @param primaryColor: The primary color of this type used to color [TechnicalMachineItem].
* @param secondaryColor: The secondary color of this type used to color [TechnicalMachineItem]
* @param typeGem: The Type Gem item used to craft a [TechnicalMachineItem] of this type.
*/
class ElementalType(
val name: String,
val displayName: MutableText,
val hue: Int,
val textureXMultiplier: Int,
val resourceLocation: Identifier = Identifier(Cobblemon.MODID, "ui/types.png")
val resourceLocation: Identifier = Identifier(Cobblemon.MODID, "ui/types.png"),
val primaryColor: Int,
val secondaryColor: Int,
val typeGem: Identifier
)
@@ -8,6 +8,7 @@
package com.cobblemon.mod.common.api.types
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.text.MutableText
import net.minecraft.text.Text
@@ -22,134 +23,180 @@ object ElementalTypes {
name = "normal",
displayName = Text.translatable("cobblemon.type.normal"),
hue = 0xDDDDCF,
textureXMultiplier = 0
textureXMultiplier = 0,
primaryColor = 0xCCCCC1,
secondaryColor = 0xF9FFFC
)
val FIRE = register(
name = "fire",
displayName = Text.translatable("cobblemon.type.fire"),
hue = 0xE55C32,
textureXMultiplier = 1
textureXMultiplier = 1,
primaryColor = 0xFF8766,
secondaryColor = 0xFFD9A5
)
val WATER = register(
name = "water",
displayName = Text.translatable("cobblemon.type.water"),
hue = 0x4A9BE8,
textureXMultiplier = 2
textureXMultiplier = 2,
primaryColor = 0x7F9DFF,
secondaryColor = 0xA5EEFF
)
val GRASS = register(
name = "grass",
displayName = Text.translatable("cobblemon.type.grass"),
hue = 0x4DBC3C,
textureXMultiplier = 3
textureXMultiplier = 3,
primaryColor = 0x66CC99,
secondaryColor = 0xD6FFA5
)
val ELECTRIC = register(
name = "electric",
displayName = Text.translatable("cobblemon.type.electric"),
hue = 0xEFD128,
textureXMultiplier = 4
textureXMultiplier = 4,
primaryColor = 0xFFCF3F,
secondaryColor = 0xFFFFBF
)
val ICE = register(
name = "ice",
displayName = Text.translatable("cobblemon.type.ice"),
hue = 0x6BC3EF,
textureXMultiplier = 5
textureXMultiplier = 5,
primaryColor = 0x8CC7FF,
secondaryColor = 0xD6FFFF
)
val FIGHTING = register(
name = "fighting",
displayName = Text.translatable("cobblemon.type.fighting"),
hue = 0xC44C5C,
textureXMultiplier = 6
textureXMultiplier = 6,
primaryColor = 0xCC6673,
secondaryColor = 0xFFADA5
)
val POISON = register(
name = "poison",
displayName = Text.translatable("cobblemon.type.poison"),
hue = 0xA24BD8,
textureXMultiplier = 7
textureXMultiplier = 7,
primaryColor = 0x8D66CC,
secondaryColor = 0xEDA5FF
)
val GROUND = register(
name = "ground",
displayName = Text.translatable("cobblemon.type.ground"),
hue = 0xD89950,
textureXMultiplier = 8
textureXMultiplier = 8,
primaryColor = 0xE5A27E,
secondaryColor = 0xFFE1A5
)
val FLYING = register(
name = "flying",
displayName = Text.translatable("cobblemon.type.flying"),
hue = 0xBCC1FF,
textureXMultiplier = 9
textureXMultiplier = 9,
primaryColor = 0x8CB6FF,
secondaryColor = 0xE8DDFF
)
val PSYCHIC = register(
name = "psychic",
displayName = Text.translatable("cobblemon.type.psychic"),
hue = 0xD86AD6,
textureXMultiplier = 10
textureXMultiplier = 10,
primaryColor = 0xE48CFF,
secondaryColor = 0xFFCCF6
)
val BUG = register(
name = "bug",
displayName = Text.translatable("cobblemon.type.bug"),
hue = 0xA2C831,
textureXMultiplier = 11
textureXMultiplier = 11,
primaryColor = 0xAFCC66,
secondaryColor = 0xF6FFBF
)
val ROCK = register(
name = "rock",
displayName = Text.translatable("cobblemon.type.rock"),
hue = 0xAA9666,
textureXMultiplier = 12
textureXMultiplier = 12,
primaryColor = 0x997F4C,
secondaryColor = 0xDBC6A4
)
val GHOST = register(
name = "ghost",
displayName = Text.translatable("cobblemon.type.ghost"),
hue = 0x9572E5,
textureXMultiplier = 13
textureXMultiplier = 13,
primaryColor = 0x8E8CFF,
secondaryColor = 0xE8A5FF
)
val DRAGON = register(
name = "dragon",
displayName = Text.translatable("cobblemon.type.dragon"),
hue = 0x535DE8,
textureXMultiplier = 14
textureXMultiplier = 14,
primaryColor = 0xBA8CFF,
secondaryColor = 0xA5E5FF
)
val DARK = register(
name = "dark",
displayName = Text.translatable("cobblemon.type.dark"),
hue = 0x5C6CB2,
textureXMultiplier = 15
textureXMultiplier = 15,
primaryColor = 0x5966B2,
secondaryColor = 0xA5B1FF
)
val STEEL = register(
name = "steel",
displayName = Text.translatable("cobblemon.type.steel"),
hue = 0xC3CCE0,
textureXMultiplier = 16
textureXMultiplier = 16,
primaryColor = 0x858EB2,
secondaryColor = 0xF4FFFD
)
val FAIRY = register(
name = "fairy",
displayName = Text.translatable("cobblemon.type.fairy"),
hue = 0xEA727E,
textureXMultiplier = 17
textureXMultiplier = 17,
primaryColor = 0xFF8CB6,
secondaryColor = 0xFFD0CC
)
fun register(name: String, displayName: MutableText, hue: Int, textureXMultiplier: Int): ElementalType {
fun register(
name: String,
displayName: MutableText,
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,
typeGem = cobblemonResource("${name}_gem")
).also {
allTypes.add(it)
}
@@ -0,0 +1,111 @@
/*
* 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.DisplayCaseBlockEntity
import com.cobblemon.mod.common.item.PokeBallItem
import net.minecraft.block.*
import net.minecraft.block.HorizontalFacingBlock.*
import net.minecraft.entity.ai.pathing.NavigationType
import net.minecraft.entity.player.PlayerEntity
import net.minecraft.item.BlockItem
import net.minecraft.item.ItemPlacementContext
import net.minecraft.state.StateManager
import net.minecraft.util.ActionResult
import net.minecraft.util.Hand
import net.minecraft.util.ItemScatterer
import net.minecraft.util.hit.BlockHitResult
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction
import net.minecraft.world.BlockView
import net.minecraft.world.World
import net.minecraft.world.WorldAccess
@Suppress("OVERRIDE_DEPRECATION")
class DisplayCaseBlock(settings: Settings) : BlockWithEntity(settings) {
init {
this.defaultState = this.stateManager.defaultState
.with(FACING, Direction.NORTH)
}
override fun getPlacementState(ctx: ItemPlacementContext): BlockState? {
var blockState = defaultState
val worldView = ctx.world
val blockPos = ctx.blockPos
ctx.placementDirections.forEach { direction ->
if (direction.axis.isHorizontal) {
blockState = blockState.with(FACING, direction) as BlockState
if (blockState.canPlaceAt(worldView, blockPos)) {
return blockState
}
}
}
return null
}
override fun appendProperties(builder: StateManager.Builder<Block, BlockState>) {
builder.add(FACING)
}
override fun createBlockEntity(pos: BlockPos, state: BlockState): DisplayCaseBlockEntity {
return DisplayCaseBlockEntity(pos, state)
}
override fun getStateForNeighborUpdate(
state: BlockState,
direction: Direction,
neighborState: BlockState,
world: WorldAccess,
pos: BlockPos,
neighborPos: BlockPos
): BlockState {
return if (direction == state.get(FACING) && !state.canPlaceAt(world, pos)) Blocks.AIR.defaultState
else super.getStateForNeighborUpdate(state, direction, neighborState, world, pos, neighborPos)
}
override fun onUse(
state: BlockState,
world: World,
pos: BlockPos,
player: PlayerEntity,
hand: Hand,
hit: BlockHitResult
): ActionResult {
val entity = world.getBlockEntity(pos) as DisplayCaseBlockEntity
val result = entity.updateItem(player, hand)
if ((hit.side != Direction.UP && hit.side != Direction.DOWN) && result == ActionResult.SUCCESS) {
world.setBlockState(pos, state.with(FACING, hit.side.opposite))
}
return result
}
override fun onBreak(world: World, pos: BlockPos, state: BlockState, player: PlayerEntity) {
val entity = world.getBlockEntity(pos) as DisplayCaseBlockEntity
if (!entity.getStack().isEmpty && !player.isCreative) {
ItemScatterer.spawn(world, pos, entity.inv)
}
super.onBreak(world, pos, state, player)
}
override fun getRenderType(state: BlockState?) = BlockRenderType.MODEL
override fun getComparatorOutput(state: BlockState, world: World, pos: BlockPos): Int {
val stack = (world.getBlockEntity(pos) as DisplayCaseBlockEntity).getStack()
if (stack.isEmpty) return 0
if (stack.item is PokeBallItem) return 3
if (stack.item is BlockItem) return 2
return 1
}
override fun hasComparatorOutput(state: BlockState?) = true
override fun canPathfindThrough(state: BlockState, world: BlockView, pos: BlockPos, type: NavigationType) = false
}
@@ -0,0 +1,128 @@
package com.cobblemon.mod.common.block
import net.minecraft.block.AbstractBlock.Settings
import net.minecraft.block.Block
import net.minecraft.block.BlockState
import net.minecraft.block.Blocks
import net.minecraft.block.HorizontalFacingBlock
import net.minecraft.entity.LivingEntity
import net.minecraft.entity.ai.pathing.NavigationType
import net.minecraft.entity.player.PlayerEntity
import net.minecraft.fluid.Fluids
import net.minecraft.item.ItemPlacementContext
import net.minecraft.item.ItemStack
import net.minecraft.screen.NamedScreenHandlerFactory
import net.minecraft.state.StateManager
import net.minecraft.state.property.EnumProperty
import net.minecraft.util.*
import net.minecraft.util.hit.BlockHitResult
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction
import net.minecraft.world.BlockView
import net.minecraft.world.World
import net.minecraft.world.WorldAccess
import net.minecraft.world.WorldView
class ModStationBlock(settings: Settings) : Block(settings) {
companion object {
val PART = EnumProperty.of("part", ModStationPart::class.java)
}
enum class ModStationPart(private val label: String) : StringIdentifiable {
TOP("top"),
BOTTOM("bottom");
override fun asString() = label
}
init {
defaultState = this.stateManager.defaultState.with(
HorizontalFacingBlock.FACING, Direction.SOUTH
).with(PART, ModStationPart.BOTTOM)
}
override fun onPlaced(
world: World,
pos: BlockPos,
state: BlockState,
placer: LivingEntity?,
itemStack: ItemStack
) {
world.setBlockState(pos.up(), state
.with(PART, ModStationPart.TOP)
)
world.updateNeighbors(pos, Blocks.AIR)
state.updateNeighbors(world, pos, 3)
}
override fun getPlacementState(blockPlaceContext: ItemPlacementContext): BlockState? {
val abovePosition = blockPlaceContext.blockPos.up()
val world = blockPlaceContext.world
if (world.getBlockState(abovePosition).canReplace(blockPlaceContext) && !world.isOutOfHeightLimit(abovePosition)) {
return defaultState
.with(HorizontalFacingBlock.FACING, blockPlaceContext.horizontalPlayerFacing)
.with(PART, ModStationPart.BOTTOM)
}
return null
}
override fun canPlaceAt(state: BlockState, world: WorldView, pos: BlockPos): Boolean {
val blockPos = pos.down()
val blockState = world.getBlockState(blockPos)
return if (state.get(PART) == ModStationPart.BOTTOM) blockState.isSideSolidFullSquare(world, blockPos, Direction.UP) else blockState.isOf(this)
}
override fun canPathfindThrough(
state: BlockState?,
world: BlockView?,
pos: BlockPos?,
type: NavigationType?
) = false
override fun appendProperties(builder: StateManager.Builder<Block, BlockState>) {
builder.add(HorizontalFacingBlock.FACING)
builder.add(PART)
}
override fun rotate(blockState: BlockState, rotation: BlockRotation) = blockState.with(HorizontalFacingBlock.FACING, rotation.rotate(blockState.get(HorizontalFacingBlock.FACING)))
override fun mirror(blockState: BlockState, mirror: BlockMirror): BlockState {
return blockState.rotate(mirror.getRotation(blockState.get(HorizontalFacingBlock.FACING)))
}
override fun onStateReplaced(state: BlockState, world: World, pos: BlockPos?, newState: BlockState, moved: Boolean) {
if (!state.isOf(newState.block)) super.onStateReplaced(state, world, pos, newState, moved)
}
override fun getStateForNeighborUpdate(
state: BlockState,
direction: Direction,
neighborState: BlockState,
world: WorldAccess,
pos: BlockPos,
neighborPos: BlockPos
): BlockState {
val isStation = neighborState.isOf(this)
val part = state.get(PART)
if (!isStation && part == ModStationPart.TOP && neighborPos == pos.down()) {
return Blocks.AIR.defaultState
} else if (!isStation && part == ModStationPart.BOTTOM && neighborPos == pos.up()) {
return Blocks.AIR.defaultState
}
return state
}
override fun onUse(
state: BlockState,
world: World,
pos: BlockPos,
player: PlayerEntity,
hand: Hand,
hit: BlockHitResult
): ActionResult {
player.openHandledScreen(state.createScreenHandlerFactory(world, pos))
return ActionResult.CONSUME
}
}
@@ -0,0 +1,547 @@
/*
* 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.CobblemonSounds
import com.cobblemon.mod.common.api.tms.TechnicalMachine
import com.cobblemon.mod.common.api.tms.TechnicalMachines
import com.cobblemon.mod.common.api.types.ElementalTypes
import com.cobblemon.mod.common.block.entity.TMBlockEntity
import com.cobblemon.mod.common.gui.TMMScreenHandler
import com.cobblemon.mod.common.util.playSoundServer
import com.cobblemon.mod.common.util.toVec3d
import net.minecraft.block.*
import net.minecraft.block.entity.BlockEntity
import net.minecraft.block.entity.BrewingStandBlockEntity
import net.minecraft.entity.ItemEntity
import net.minecraft.entity.ai.pathing.NavigationType
import net.minecraft.entity.player.PlayerEntity
import net.minecraft.fluid.FluidState
import net.minecraft.fluid.Fluids
import net.minecraft.inventory.SidedInventory
import net.minecraft.item.ItemPlacementContext
import net.minecraft.item.ItemStack
import net.minecraft.item.Items
import net.minecraft.registry.Registries
import net.minecraft.screen.NamedScreenHandlerFactory
import net.minecraft.screen.SimpleNamedScreenHandlerFactory
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.server.world.ServerWorld
import net.minecraft.sound.SoundCategory
import net.minecraft.stat.Stats
import net.minecraft.state.StateManager
import net.minecraft.state.property.BooleanProperty
import net.minecraft.state.property.Properties
import net.minecraft.text.Text
import net.minecraft.util.ActionResult
import net.minecraft.util.BlockMirror
import net.minecraft.util.BlockRotation
import net.minecraft.util.Hand
import net.minecraft.util.hit.BlockHitResult
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction
import net.minecraft.util.shape.VoxelShape
import net.minecraft.util.shape.VoxelShapes
import net.minecraft.world.BlockView
import net.minecraft.world.World
import net.minecraft.world.WorldAccess
class TMBlock(properties: Settings): BlockWithEntity(properties), Waterloggable, InventoryProvider {
/*val FACING = Properties.FACING
val TRIGGERED = Properties.TRIGGERED
val WATERLOGGED = Properties.WATERLOGGED*/
//var filterTM: TechnicalMachine? = null
//var previousFilterTM: TechnicalMachine? = null
var loadedMaterials: MutableList<ItemStack> = mutableListOf()
//val inv = TMBlockInventory(this)
/*class TMBlockInventory(val tmBlock: TMBlock) : SidedInventory {
override fun clear() {
TODO("Not yet implemented")
}
override fun size(): Int {
TODO("Not yet implemented")
}
override fun isEmpty(): Boolean {
return false
}
override fun getStack(slot: Int): ItemStack {
return ItemStack.EMPTY
}
override fun removeStack(slot: Int, amount: Int): ItemStack {
return ItemStack.EMPTY
}
override fun removeStack(slot: Int): ItemStack {
TODO("Not yet implemented")
}
override fun setStack(slot: Int, stack: ItemStack?) {
TODO("Not yet implemented")
}
override fun markDirty() {
TODO("Not yet implemented")
}
override fun canPlayerUse(player: PlayerEntity?): Boolean {
return false
}
override fun getAvailableSlots(side: Direction?): IntArray {
return IntArray(1)
}
override fun canInsert(slot: Int, stack: ItemStack?, dir: Direction?): Boolean {
// todo only allow for hopper to insert materials if it has a filterTM in it
if (tmBlock.filterTM != null && stack != null) {
// if material is needed then load it
if (tmBlock.materialNeeded(tmBlock.filterTM!!, stack)) {
tmBlock.loadMaterial(stack)
return true
}
else
return false
}
return false
}
override fun canExtract(slot: Int, stack: ItemStack?, dir: Direction?): Boolean {
return false
}
}*/
companion object {
//val WATERLOGGED = BooleanProperty.of("waterlogged")
val ON = BooleanProperty.of("on")
val FACING = Properties.FACING
val TRIGGERED = Properties.TRIGGERED
val WATERLOGGED = Properties.WATERLOGGED
private var NORTH_OUTLINE = VoxelShapes.union(
VoxelShapes.cuboid(0.0, 0.0, 0.0, 1.0, 0.3125, 0.9375),
VoxelShapes.cuboid(0.0, 0.3125, 0.75, 1.0, 0.9375, 0.9375),
VoxelShapes.cuboid(0.0625, 0.3125, 0.0625, 0.9375, 0.875, 0.9375)
)
private var SOUTH_OUTLINE = VoxelShapes.union(
VoxelShapes.cuboid(0.0, 0.0, 0.0625, 1.0, 0.3125, 1.0),
VoxelShapes.cuboid(0.0, 0.3125, 0.0625, 1.0, 0.9375, 0.25),
VoxelShapes.cuboid(0.0625, 0.3125, 0.0625, 0.9375, 0.875, 0.9375)
)
private var WEST_OUTLINE = VoxelShapes.union(
VoxelShapes.cuboid(0.0, 0.0, 0.0, 0.9375, 0.3125, 1.0),
VoxelShapes.cuboid(0.75, 0.3125, 0.0, 0.9375, 0.9375, 1.0),
VoxelShapes.cuboid(0.0625, 0.3125, 0.0625, 0.9375, 0.875, 0.9375)
)
private var EAST_OUTLINE = VoxelShapes.union(
VoxelShapes.cuboid(0.0625, 0.0, 0.0, 1.0, 0.3125, 1.0),
VoxelShapes.cuboid(0.0625, 0.3125, 0.0, 0.25, 0.9375, 1.0),
VoxelShapes.cuboid(0.0625, 0.3125, 0.0625, 0.9375, 0.875, 0.9375)
)
}
init {
defaultState = this.stateManager.defaultState.with(FACING, Direction.NORTH)
.with(WATERLOGGED, false)
.with(ON, false)
.with(TRIGGERED, false)
}
override fun getPlacementState(blockPlaceContext: ItemPlacementContext): BlockState? {
return defaultState
.with(FACING, blockPlaceContext.horizontalPlayerFacing)
.with(WATERLOGGED, blockPlaceContext.world.getFluidState(blockPlaceContext.blockPos).fluid == Fluids.WATER)
.with(ON, false)
}
override fun onBreak(world: World, pos: BlockPos, state: BlockState, player: PlayerEntity?) {
super.onBreak(world, pos, state, player)
// todo dump materials and filterTM on the ground
}
@Deprecated("Deprecated in Java")
override fun neighborUpdate(state: BlockState, world: World, pos: BlockPos, sourceBlock: Block?, sourcePos: BlockPos?, notify: Boolean) {
val bl = world.isReceivingRedstonePower(pos) || world.isReceivingRedstonePower(pos.up())
val bl2 = state.get(TRIGGERED)
if (bl && !bl2) {
world.scheduleBlockTick(pos, this, 4)
world.setBlockState(pos, state.with(TRIGGERED, true) as BlockState, NO_REDRAW)
} else if (!bl && bl2) {
world.setBlockState(pos, state.with(TRIGGERED, false) as BlockState, NO_REDRAW)
}
}
@Deprecated("Deprecated in Java")
override fun scheduledTick(state: BlockState?, world: ServerWorld?, pos: BlockPos?, random: net.minecraft.util.math.random.Random?) {
val inventory = (world!!.getBlockEntity(pos) as TMBlockEntity).tmmInventory
val tmEntity = (world.getBlockEntity(pos) as TMBlockEntity)
if(world == null || pos == null) {
return
}
//val tankEntity = world.getBlockEntity(pos) as MultiblockEntity
if (tmEntity.blockState.get(ON)) {
//print("TMM is in use so scheduled Tick is disabled")
return
}
//this.onTriggerEvent(state, world, pos, random)
// todo if the output slot is empty then try to craft a TM to that slot
if (ItemStack.areItemsEqual(inventory.items!!.get(3), ItemStack.EMPTY)) {
if (state?.let { getInventory(it, world, pos) } != null) {
val itemStack: ItemStack
itemStack = if (inventory.filterTM != null)
inventory.filterTM!!.copy()
else
ItemStack(CobblemonItems.BLANK_TM, 1)
val tm = TechnicalMachines.getTechnicalMachineFromStack(itemStack)
// todo use isReadyToCraftTM to finalize the creation
if (((inventory.filterTM != null && tm != null) && isReadyToCraftTM(state, world, pos, tm)) || isReadyToCraftBlankTM(state, world, pos)) {
// create TM item to the output slot
inventory.items!!.set(3, itemStack)
tmEntity.markDirty()
// Play sound for TM creation
world.playSoundServer(tmEntity.blockPos.toVec3d(), CobblemonSounds.TMM_CRAFT, SoundCategory.BLOCKS)
}
}
}
// todo if there is an item in the output slot then spit it out
else {
if (state?.let { getInventory(it, world, pos) } != null) {
val itemStack = inventory.items!!.get(3)
// Get the direction the block is facing
val facingDirection = state.get(Properties.FACING)?.opposite ?: return
// Calculate the center position of the block
val frontOffset = 0.5 // Half block offset to the front
val spawnX = pos.x + 0.5 + facingDirection.offsetX * frontOffset
val spawnY = pos.y + 0.3 + facingDirection.offsetY * frontOffset
val spawnZ = pos.z + 0.5 + facingDirection.offsetZ * frontOffset
// Create the ItemEntity at the center of the block
val itemEntity = ItemEntity(world, spawnX, spawnY, spawnZ, itemStack).copy()
// Create the ItemEntity
itemEntity.setVelocity(0.0, 0.0, 0.0)
// Add the ItemEntity to the world
world.spawnEntity(itemEntity)
//clear inventory of SidedInventory
inventory.items?.clear()
tmEntity.markDirty()
//getInventory(state, world, pos).clear()
}
}
/*if (state?.let { getInventory(it, world, pos) } != null) {
val itemStack: ItemStack
itemStack = if (inventory.filterTM != null)
inventory.filterTM!!
else
ItemStack(CobblemonItems.BLANK_TM, 1)
val tm = TechnicalMachines.getTechnicalMachineFromStack(itemStack)
// todo use isReadyToCraftTM to finalize the creation
if ((inventory.filterTM != null && isReadyToCraftTM(state, world, pos, tm!!)) || isReadyToCraftBlankTM(state, world, pos)) {
// Get the direction the block is facing
val facingDirection = state.get(Properties.FACING)?.opposite ?: return
// Calculate the center position of the block
val frontOffset = 0.5 // Half block offset to the front
val spawnX = pos.x + 0.5 + facingDirection.offsetX * frontOffset
val spawnY = pos.y + 0.3 + facingDirection.offsetY * frontOffset
val spawnZ = pos.z + 0.5 + facingDirection.offsetZ * frontOffset
// Create the ItemEntity at the center of the block
val itemEntity = ItemEntity(world, spawnX, spawnY, spawnZ, itemStack).copy()
// Create the ItemEntity
itemEntity.setVelocity(0.0, 0.0, 0.0)
// Add the ItemEntity to the world
world.spawnEntity(itemEntity)
//clear inventory of SidedInventory
inventory.items?.clear()
//getInventory(state, world, pos).clear()
}
}*/
}
/*val currentTm = filterTM ?: return sendPacketToServer(CraftBlankTMPacket(handler.input.getStack(2)))
sendPacketToServer(
CraftTMPacket(
currentTm,
handler.input.getStack(0),
handler.input.getStack(1),
handler.input.getStack(2)
)
)*/
@Deprecated("Deprecated in Java")
override fun canPathfindThrough(blockState: BlockState, blockGetter: BlockView, blockPos: BlockPos, pathComputationType: NavigationType) = false
@Deprecated("Deprecated in Java")
override fun hasComparatorOutput(state: BlockState?): Boolean {
return true
}
@Deprecated("Deprecated in Java")
override fun getComparatorOutput(state: BlockState, world: World?, pos: BlockPos?): Int {
if(world == null || pos == null) {
return 0
}
val tmBlockEntity = world.getBlockEntity(pos) as TMBlockEntity
val tm = TechnicalMachines.getTechnicalMachineFromStack(tmBlockEntity.tmmInventory.filterTM)
// if TMM has already crafted something and something is in the output slot do not send signal out
if (!ItemStack.areItemsEqual(tmBlockEntity.tmmInventory.items?.get(3), ItemStack.EMPTY))
return 0
// if the TMM is ready to craft send signal of 15 out
if ((tm != null && isReadyToCraftTM(state, world, pos, tm)) || isReadyToCraftBlankTM(state, world, pos))
return 15
/*if (tmBlockEntity.automationDelay > 0)
tmBlockEntity.automationDelay--
if ((tmBlockEntity.tmmInventory.filterTM != null && isReadyToCraftTM(state, world, pos, tmBlockEntity.tmmInventory.filterTM!!)) || isReadyToCraftBlankTM(state, world, pos)) {
if (tmBlockEntity.automationDelay == 0) {
tmBlockEntity.resetAutomationDelay()
return 15
}
else
return 0
}*/
return 0
}
override fun getInventory(
state: BlockState,
world: WorldAccess,
pos: BlockPos
): SidedInventory {
val tmBlockEntity = world.getBlockEntity(pos) as TMBlockEntity
return tmBlockEntity.tmmInventory
}
override fun appendProperties(builder: StateManager.Builder<Block, BlockState>) {
builder.add(FACING)
builder.add(WATERLOGGED)
builder.add(ON)
builder.add(TRIGGERED)
}
override fun createBlockEntity(pos: BlockPos, state: BlockState): BlockEntity {
return TMBlockEntity(pos, state)
}
@Deprecated("Deprecated in Java")
override fun rotate(blockState: BlockState, rotation: BlockRotation) =
blockState.with(FACING, rotation.rotate(blockState.get(FACING)))
@Deprecated("Deprecated in Java")
override fun mirror(blockState: BlockState, mirror: BlockMirror): BlockState {
return blockState.rotate(mirror.getRotation(blockState.get(FACING)))
}
@Deprecated("Deprecated in Java")
override fun onStateReplaced(state: BlockState, world: World, pos: BlockPos?, newState: BlockState, moved: Boolean) {
if (!state.isOf(newState.block)) super.onStateReplaced(state, world, pos, newState, moved)
}
@Deprecated("Deprecated in Java")
override fun onUse(
blockState: BlockState,
world: World,
pos: BlockPos,
player: PlayerEntity,
interactionHand: Hand,
blockHitResult: BlockHitResult
): ActionResult {
// clear filter TM on client
if (world.isClient) {
val tmBlockEntity = world.getBlockEntity(pos)
val inventory = (tmBlockEntity as TMBlockEntity).tmmInventory
inventory.filterTM = null
return ActionResult.SUCCESS
}
val tmBlockEntity = world.getBlockEntity(pos)
if (tmBlockEntity is TMBlockEntity) {
// Set the block state to ON when the block is used
tmBlockEntity.stateManager.openContainer(player, world, pos, blockState)
player.openHandledScreen(tmBlockEntity as TMBlockEntity?)
}
val inventory = (tmBlockEntity as TMBlockEntity).tmmInventory // (world.getBlockEntity(pos) as TMBlockEntity).tmmInventory
// remove all machine from machine upon use
// inventory.items?.forEach {
// // Get the direction the block is facing
// val facingDirection = blockState.get(Properties.FACING).opposite
//
// // Calculate the center position of the block
// val frontOffset = 0.5 // Half block offset to the front
// val spawnX = pos.x + 0.5 + facingDirection.offsetX * frontOffset
// val spawnY = pos.y + 0.3 + facingDirection.offsetY * frontOffset
// val spawnZ = pos.z + 0.5 + facingDirection.offsetZ * frontOffset
//
// // Create the ItemEntity at the center of the block
// val itemEntity = ItemEntity(world, spawnX, spawnY, spawnZ, it)
//
// // Create the ItemEntity
// itemEntity.setVelocity(0.0, 0.0, 0.0)
//
// // Add the ItemEntity to the world
// world.spawnEntity(itemEntity)
// }
//
// inventory.items?.clear()
// spit out any disc filters
if (inventory.filterTM != null) {
// spit out the filter TM and set filterTM to null
val filterTMStack = inventory.filterTM!!
// Get the direction the block is facing
val facingDirection = blockState.get(Properties.FACING).opposite
// Calculate the center position of the block
val frontOffset = 0.5 // Half block offset to the front
val spawnX = pos.x + 0.5 + facingDirection.offsetX * frontOffset
val spawnY = pos.y + 0.3 + facingDirection.offsetY * frontOffset
val spawnZ = pos.z + 0.5 + facingDirection.offsetZ * frontOffset
// Create the ItemEntity at the center of the block
val itemEntity = ItemEntity(world, spawnX, spawnY, spawnZ, filterTMStack).copy()
// Create the ItemEntity
itemEntity.setVelocity(0.0, 0.0, 0.0)
// Add the ItemEntity to the world
world.spawnEntity(itemEntity)
inventory.filterTM = null
}
val blockEntity = world.getBlockEntity(pos)
if (blockEntity is TMBlockEntity) {
//player.openHandledScreen(blockEntity as TMBlockEntity?)
player.playSound(CobblemonSounds.TMM_ON, SoundCategory.BLOCKS, 1.0f, 1.0f)
// todo maybe uncomment this
//val serverPlayer = player as ServerPlayerEntity
//serverPlayer.openHandledScreen(blockState.createScreenHandlerFactory(world, pos))
}
return ActionResult.CONSUME
}
// override fun createScreenHandlerFactory(
// state: BlockState?,
// world: World?,
// pos: BlockPos?
// ): NamedScreenHandlerFactory {
// return SimpleNamedScreenHandlerFactory(::TMMScreenHandler, Text.of("TM Machine"))
// }
/*fun loadMaterial(itemStack: ItemStack) {
loadedMaterials.add(itemStack)
}*/
fun isReadyToCraftTM(state: BlockState?, world: WorldAccess, pos: BlockPos, tm: TechnicalMachine): Boolean {
val typeGem = Registries.ITEM.get(tm.let { ElementalTypes.get(it.type)?.typeGem }).defaultStack
val recipeItem = Registries.ITEM.get(tm.recipe?.item)
val inventory = state?.let { getInventory(it, world, pos) }
return (inventory!!.count(CobblemonItems.BLANK_TM) == 1
&& inventory.count(typeGem.item) == 1
&& ((inventory.count(recipeItem) == tm.recipe?.count) || (ItemStack.areItemsEqual(recipeItem.defaultStack, ItemStack.EMPTY))))
}
fun isReadyToCraftBlankTM(state: BlockState?, world: WorldAccess, pos: BlockPos): Boolean {
val inventory = state?.let { getInventory(it, world, pos) }
return (inventory!!.count(Items.AMETHYST_SHARD) == 1)
}
/*fun materialNeeded(tm: TechnicalMachine, itemStack: ItemStack): Boolean {
val typeGem = Registries.ITEM.get(ElementalTypes.get(tm.type)?.typeGem).defaultStack
val recipeItem = Registries.ITEM.get(tm.recipe?.item)
// if blank TM is needed still
if ((itemStack !in loadedMaterials && itemStack == CobblemonItems.BLANK_TM.defaultStack))
return true // load in blank TM
// if type gem is needed still
else if ((itemStack !in loadedMaterials && itemStack == typeGem))
return true // load in Type Gem
// if the itemStack is listed as needed in the recipe and the count is not currently met
else if (itemStack.item == recipeItem && loadedMaterials.count { it == itemStack } < tm.recipe?.count!!)
return true
else
return false
}*/
/*fun clearLoadedMaterials() {
loadedMaterials.clear()
}*/
@Deprecated("Deprecated in Java")
override fun getRenderType(blockState: BlockState): BlockRenderType {
return BlockRenderType.MODEL
}
override fun getFluidState(state: BlockState): FluidState? {
return if (state.get(WATERLOGGED)) {
Fluids.WATER.getStill(false)
} else super.getFluidState(state)
}
override fun getOutlineShape(
state: BlockState,
world: BlockView,
pos: BlockPos,
context: ShapeContext
): VoxelShape {
return when (state.get(FACING)) {
Direction.NORTH -> NORTH_OUTLINE
Direction.SOUTH -> SOUTH_OUTLINE
Direction.WEST -> WEST_OUTLINE
else -> EAST_OUTLINE
}
}
}
@@ -0,0 +1,20 @@
package com.cobblemon.mod.common.block
import net.minecraft.block.*
import net.minecraft.util.math.BlockPos
import net.minecraft.world.BlockView
class TypeGemBlock(
settings: Settings,
stage: Int,
height: Int,
xzOffset: Int,
nextStage: Block?,
val baseBlock: Block
) : GrowableStoneBlock(settings, stage, height, xzOffset, nextStage) {
override fun canGrow(pos: BlockPos, world: BlockView): Boolean {
val state = world.getBlockState(pos)
val dir = state.get(FACING).opposite
return (world.getBlockState(pos.offset(dir)).block == baseBlock)
}
}
@@ -0,0 +1,180 @@
/*
* 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.CobblemonSounds
import net.minecraft.block.Block
import net.minecraft.block.BlockState
import net.minecraft.block.entity.BlockEntity
import net.minecraft.entity.player.PlayerEntity
import net.minecraft.inventory.Inventories
import net.minecraft.inventory.SidedInventory
import net.minecraft.item.ItemStack
import net.minecraft.item.Items
import net.minecraft.nbt.NbtCompound
import net.minecraft.network.listener.ClientPlayPacketListener
import net.minecraft.network.packet.Packet
import net.minecraft.network.packet.s2c.play.BlockEntityUpdateS2CPacket
import net.minecraft.sound.SoundCategory
import net.minecraft.util.ActionResult
import net.minecraft.util.Hand
import net.minecraft.util.collection.DefaultedList
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction
import net.minecraft.world.World
class DisplayCaseBlockEntity(pos: BlockPos, state: BlockState) : BlockEntity(CobblemonBlockEntities.DISPLAY_CASE, pos, state), SidedInventory {
val inv: DefaultedList<ItemStack> = DefaultedList.ofSize(1, ItemStack.EMPTY)
/**
* Updates the [ItemStack] stored in this entity
*
* @param player The [PlayerEntity] that performed this interaction
* @param hand The [Hand] the player used to perform this interaction
* @author whatsy
*/
fun updateItem(player: PlayerEntity, hand: Hand): ActionResult {
val playerStack = player.getStackInHand(hand)
// Player and case item are the same - do nothing
if (playerStack.item == getStack().item) {
return if (playerStack.item != Items.AIR) ActionResult.SUCCESS else ActionResult.FAIL
}
// Player's hand is empty, case is not empty - give player the item in the case
if (playerStack.isEmpty && !getStack().isEmpty) {
if (!player.isCreative) player.setStackInHand(hand, getStack())
setCaseStack(ItemStack.EMPTY)
return ActionResult.success(true)
}
// Case is empty, player's hand is not - put playerStack in the case
if (getStack().isEmpty && !playerStack.isEmpty) {
setCaseStack(playerStack.copy())
if (!player.isCreative) playerStack.decrement(1)
return ActionResult.success(true)
}
// Player has item, case has item - swap items
if (!getStack().isEmpty && !playerStack.isEmpty) {
val oldCaseStack = getStack()
setCaseStack(playerStack.copy())
if (!player.isCreative) {
playerStack.decrement(1)
player.giveItemStack(oldCaseStack)
}
return ActionResult.SUCCESS
}
return ActionResult.FAIL
}
fun getStack(): ItemStack {
return inv[0]
}
private fun setCaseStack(newStack: ItemStack) {
if (world == null) return
val oldState = world!!.getBlockState(pos)
newStack.count = 1
inv[0] = newStack
if (newStack.isEmpty) {
world!!.playSound(null, pos, CobblemonSounds.DISPLAY_CASE_REMOVE_ITEM, SoundCategory.BLOCKS)
} else {
world!!.playSound(null, pos, CobblemonSounds.DISPLAY_CASE_ADD_ITEM, SoundCategory.BLOCKS)
}
world!!.updateListeners(pos, oldState, world!!.getBlockState(pos), Block.NOTIFY_LISTENERS)
world!!.updateComparators(pos, world!!.getBlockState(pos).block)
}
override fun writeNbt(nbt: NbtCompound) {
super.writeNbt(nbt)
Inventories.writeNbt(nbt, inv, true)
}
override fun readNbt(nbt: NbtCompound) {
super.readNbt(nbt)
inv.clear()
Inventories.readNbt(nbt, inv)
}
override fun toUpdatePacket(): Packet<ClientPlayPacketListener>? {
return BlockEntityUpdateS2CPacket.create(this)
}
override fun toInitialChunkDataNbt(): NbtCompound {
return this.createNbt()
}
private fun onItemUpdated(world: World, oldState: BlockState, newState: BlockState) {
markDirty()
world.updateListeners(pos, oldState, newState, Block.NOTIFY_LISTENERS)
world.updateComparators(pos, world.getBlockState(pos).block)
}
override fun clear() {
inv.clear()
}
override fun size(): Int {
return inv.size
}
override fun isEmpty(): Boolean {
return getStack().isEmpty
}
override fun getStack(slot: Int): ItemStack {
return getStack()
}
override fun removeStack(slot: Int, amount: Int): ItemStack {
val oldState = cachedState
val result = Inventories.splitStack(inv, slot, amount)
if (world != null) onItemUpdated(world!!, oldState, world!!.getBlockState(pos))
return result
}
override fun removeStack(slot: Int): ItemStack {
val oldState = cachedState
val result = Inventories.removeStack(inv, slot)
if (world != null) onItemUpdated(world!!, oldState, world!!.getBlockState(pos))
return result
}
override fun setStack(slot: Int, stack: ItemStack) {
val oldState = cachedState
inv[slot] = stack
if (stack.count > stack.maxCount) {
stack.count = stack.maxCount
}
if (world != null) onItemUpdated(world!!, oldState, world!!.getBlockState(pos))
}
override fun canPlayerUse(player: PlayerEntity?) = false
override fun getAvailableSlots(side: Direction?): IntArray {
val result = IntArray(inv.size)
for (i in result.indices) {
result[i] = i
}
return result
}
override fun getMaxCountPerStack() = 1
override fun canInsert(slot: Int, stack: ItemStack?, dir: Direction?): Boolean {
if(dir == Direction.DOWN) return false
return getStack().isEmpty
}
override fun canExtract(slot: Int, stack: ItemStack?, dir: Direction?): Boolean = (dir == Direction.DOWN)
}
@@ -0,0 +1,349 @@
/*
* 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.api.tms.TechnicalMachine
import com.cobblemon.mod.common.api.tms.TechnicalMachines
import com.cobblemon.mod.common.api.types.ElementalTypes
import com.cobblemon.mod.common.block.TMBlock
import com.cobblemon.mod.common.gui.TMMScreenHandler
import net.minecraft.block.Block
import net.minecraft.block.BlockState
import net.minecraft.block.entity.LockableContainerBlockEntity
import net.minecraft.block.entity.ViewerCountManager
import net.minecraft.entity.player.PlayerEntity
import net.minecraft.entity.player.PlayerInventory
import net.minecraft.inventory.Inventories
import net.minecraft.inventory.Inventory
import net.minecraft.inventory.SidedInventory
import net.minecraft.item.ItemStack
import net.minecraft.item.Items
import net.minecraft.nbt.NbtCompound
import net.minecraft.network.listener.ClientPlayPacketListener
import net.minecraft.network.packet.Packet
import net.minecraft.network.packet.s2c.play.BlockEntityUpdateS2CPacket
import net.minecraft.registry.Registries
import net.minecraft.screen.ScreenHandler
import net.minecraft.text.Text
import net.minecraft.util.collection.DefaultedList
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction
import net.minecraft.world.World
class TMBlockEntity(
val blockPos: BlockPos,
val blockState: BlockState
) : LockableContainerBlockEntity(CobblemonBlockEntities.TM_BLOCK, blockPos, blockState) {
var tmmInventory = TMBlockInventory(this)
var automationDelay: Int = AUTOMATION_DELAY
var partialTicks = 0.0f
val stateManager: ViewerCountManager = object : ViewerCountManager() {
override fun onContainerOpen(world: World, pos: BlockPos, state: BlockState) {
this@TMBlockEntity.setOpen(state, true)
}
override fun onContainerClose(world: World, pos: BlockPos, state: BlockState) {
this@TMBlockEntity.setOpen(state, false)
}
override fun onViewerCountUpdate(world: World, pos: BlockPos, state: BlockState, oldViewerCount: Int, newViewerCount: Int) {}
override fun isPlayerViewing(player: PlayerEntity): Boolean {
if (player.currentScreenHandler is TMMScreenHandler) {
val inventory = (player.currentScreenHandler as TMMScreenHandler).inventory
return inventory === this@TMBlockEntity
}
return false
}
}
companion object {
const val AUTOMATION_DELAY = 4
const val FILTER_TM_NBT = "FilterTM"
}
fun setOpen(state: BlockState, open: Boolean) {
world!!.setBlockState(getPos(), state.with(TMBlock.ON, open), Block.NOTIFY_ALL)
}
fun tick() {
if (!removed) {
stateManager.updateViewerCount(getWorld(), getPos(), cachedState)
}
}
//var filterTM: TechnicalMachine? = null
//private var inventory: DefaultedList<ItemStack> = DefaultedList.ofSize(size(), ItemStack.EMPTY)
/*override fun size(): Int { return 3 }
override fun getContainerName(): Text {
return Text.translatable("container.tmblock")
}
override fun getInvStackList(): DefaultedList<ItemStack> {
return inventory
}
override fun setInvStackList(list: DefaultedList<ItemStack>?) {
if (list != null) {
this.inventory = list
}
}*/
fun resetAutomationDelay() {
automationDelay = AUTOMATION_DELAY
}
fun materialNeeded(tm: TechnicalMachine?, slot: Int, itemStack: ItemStack): Boolean {
val typeGem = Registries.ITEM.get(tm?.type?.let { ElementalTypes.get(it)?.typeGem }).defaultStack
val recipeItem = Registries.ITEM.get(tm?.recipe?.item)
//val inventory = tmmInventory.inventory
val inventory = tmmInventory.items
//inventory.count { it.value == CobblemonItems.BLANK_TM.defaultStack }
//ItemStack.areEqual()
if (inventory != null) {
if (tmmInventory.filterTM != null) {
// if blank TM is needed still
if (slot == 0 && (inventory.none { it?.count == 1 && it.item == CobblemonItems.BLANK_TM }) && ItemStack.areEqual(itemStack, ItemStack(CobblemonItems.BLANK_TM, 1)))
return true // load in blank TM
// if type gem is needed still
else if (slot == 1 && inventory.none { ItemStack.areEqual(it, typeGem) } && ItemStack.areEqual(itemStack, typeGem))
return true // load in Type Gem
// if the itemStack is listed as needed in the recipe and the count is not currently met
else if (slot == 2 && inventory.none { it?.count == tm?.recipe?.count && ItemStack.areItemsEqual(it, ItemStack(recipeItem, 1)) } && ItemStack.areEqual(itemStack, ItemStack(recipeItem, 1)))
return true
else
return false
}
else if (tmmInventory.filterTM == null && ItemStack.areItemsEqual(itemStack,Items.AMETHYST_SHARD.defaultStack)) {
return (slot == 2 && inventory.none { it?.count == 1 && ItemStack.areItemsEqual(it, Items.AMETHYST_SHARD.defaultStack) })
}
}
return false
}
override fun toInitialChunkDataNbt(): NbtCompound {
val res = NbtCompound()
writeNbt(res)
return res
}
override fun clear() {
tmmInventory.clear()
}
override fun size(): Int {
return this.tmmInventory.size()
}
override fun isEmpty(): Boolean {
return this.tmmInventory.isEmpty()
}
override fun getStack(slot: Int): ItemStack {
return this.tmmInventory.getStack(slot)
}
override fun removeStack(slot: Int, amount: Int): ItemStack {
return this.tmmInventory.removeStack(slot, amount)
}
override fun removeStack(slot: Int): ItemStack {
return this.tmmInventory.removeStack(slot)
}
override fun setStack(slot: Int, stack: ItemStack?) {
return this.tmmInventory.setStack(slot, stack)
}
override fun canPlayerUse(player: PlayerEntity?): Boolean {
return this.tmmInventory.canPlayerUse(player)
}
override fun getContainerName(): Text {
return Text.translatable("container.brewing")
}
override fun createScreenHandler(syncId: Int, playerInventory: PlayerInventory?): ScreenHandler {
return TMMScreenHandler(syncId, playerInventory!!, this.tmmInventory, this)
}
override fun toUpdatePacket(): Packet<ClientPlayPacketListener>? {
return BlockEntityUpdateS2CPacket.create(this)
}
override fun writeNbt(nbt: NbtCompound) {
super.writeNbt(nbt)
Inventories.writeNbt(nbt, tmmInventory.items)
val filterTmCompound = tmmInventory.filterTM?.writeNbt(NbtCompound())
if (filterTmCompound != null)
nbt.put(FILTER_TM_NBT, filterTmCompound)
}
override fun readNbt(nbt: NbtCompound) {
super.readNbt(nbt)
Inventories.readNbt(nbt, tmmInventory.items)
if (nbt.contains(FILTER_TM_NBT)) {
val itemStack = CobblemonItems.TECHNICAL_MACHINE.defaultStack
tmmInventory.filterTM = ItemStack.fromNbt(nbt.getCompound(FILTER_TM_NBT))
val test = 2
}
}
override fun markDirty() {
super.markDirty()
if (this.world != null && !this.world!!.isClient) {
val currentState = world!!.getBlockState(pos)
world!!.setBlockState(pos, currentState, 3) // Flags: 2 | 1 = Block update | Render update
this.world!!.updateNeighborsAlways(this.pos, this.cachedState.block)
}
}
class TMBlockInventory(val tmBlockEntity: TMBlockEntity) : SidedInventory {
private val BLANK_DISC_SLOT_INDEX = 0
private val GEM_SLOT_INDEX = 1
private val MISC_SLOT_INDEX = 2
private val OUTPUT_SLOT_INDEX = 3
private val INPUT_SLOTS = intArrayOf(0, 1, 2, 3)
val entityPos = this.tmBlockEntity.blockPos
val entityState = this.tmBlockEntity.blockState
val entityWorld = this.tmBlockEntity.world
var filterTM: ItemStack? = null
//var inventory: MutableMap<Int, ItemStack?> = mutableMapOf(0 to ItemStack.EMPTY, 1 to ItemStack.EMPTY, 2 to ItemStack.EMPTY)
var items: DefaultedList<ItemStack?>? = DefaultedList.ofSize(4, ItemStack.EMPTY)
fun hasFilterTM(): Boolean {
return filterTM != null
}
override fun clear() {
tmBlockEntity.markDirty()
this.items?.clear()
}
override fun size(): Int {
return this.items?.size ?: 0;
}
/*fun getInvSize(): Int {
return size()
}*/
fun getInvStack(slot: Int): ItemStack {
return items?.get(slot) ?: ItemStack.EMPTY
}
override fun isEmpty(): Boolean {
for (i in 0 until size()) {
val stack: ItemStack = getInvStack(i)
if (!stack.isEmpty) {
return false
}
}
return true
}
/*override fun isEmpty(): Boolean {
return !inventory.any { it.value != ItemStack.EMPTY }
}*/
override fun getStack(slot: Int): ItemStack {
return items?.get(slot) ?: ItemStack.EMPTY
}
override fun getAvailableSlots(side: Direction?): IntArray? {
return if (side == Direction.DOWN) {
return listOf(this.OUTPUT_SLOT_INDEX).toIntArray()
}
else this.INPUT_SLOTS
// val result = this.items?.size?.let { IntArray(it) }
// if (result != null) {
// for (i in result.indices) {
// result[i] = i
// }
// }
// return result
}
override fun removeStack(slot: Int, amount: Int): ItemStack {
tmBlockEntity.markDirty()
return Inventories.splitStack(items, slot, amount)
}
override fun removeStack(slot: Int): ItemStack {
val slotStack = items?.get(slot)
items?.set(slot, ItemStack.EMPTY)
tmBlockEntity.markDirty()
return slotStack ?: ItemStack.EMPTY
}
override fun setStack(slot: Int, stack: ItemStack?) {
items?.set(slot, stack)
tmBlockEntity.markDirty()
}
override fun markDirty() {
if (tmBlockEntity.world != null)
tmBlockEntity.markDirty()
}
override fun canPlayerUse(player: PlayerEntity?): Boolean {
return Inventory.canPlayerUse(this.tmBlockEntity, player)
}
/*override fun getAvailableSlots(side: Direction?): IntArray {
return IntArray(3)
}*/
override fun canInsert(slot: Int, stack: ItemStack?, dir: Direction?): Boolean {
if (this.entityState.get(TMBlock.ON)) {
// print("TMM is in use so canInsert is disabled")
return false
}
if (stack != null && slot < this.INPUT_SLOTS.size) {
val tm = TechnicalMachines.getTechnicalMachineFromStack(tmBlockEntity.tmmInventory.filterTM)
// if material is needed then load it
if (tmBlockEntity.materialNeeded(tm, slot, stack) && (ItemStack.areEqual(items?.get(slot) ?: ItemStack.EMPTY, ItemStack.EMPTY) || ItemStack.areItemsEqual(items?.get(slot) ?: ItemStack.EMPTY, stack))) {
//tmBlockEntity.loadMaterial(stack)
return true
}
else
return false
}
return false
}
override fun canExtract(slot: Int, stack: ItemStack?, dir: Direction?): Boolean {
if (this.entityState.get(TMBlock.ON)) {
//print("TMM is in use so canExtract is disabled")
return false
}
return dir == Direction.DOWN && slot == this.OUTPUT_SLOT_INDEX
}
}
}
@@ -62,6 +62,7 @@ object CobblemonBakingOverrides {
cobblemonModel("restoration_tank_connector", "none")
)
fun registerOverride(modelLocation: Identifier, modelIdentifier: ModelIdentifier): BakingOverride {
val result = BakingOverride(modelLocation, modelIdentifier)
models.add(result)
@@ -18,16 +18,22 @@ import com.cobblemon.mod.common.CobblemonEntities
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.api.scheduling.ClientTaskTracker
import com.cobblemon.mod.common.api.text.gray
import com.cobblemon.mod.common.api.types.ElementalTypes
import com.cobblemon.mod.common.client.battle.ClientBattle
import com.cobblemon.mod.common.client.gui.PartyOverlay
import com.cobblemon.mod.common.client.gui.battle.BattleOverlay
import com.cobblemon.mod.common.client.gui.modstation.ModStationHandledScreen
import com.cobblemon.mod.common.client.gui.tm.TMMHandledScreen
import com.cobblemon.mod.common.client.particle.BedrockParticleEffectRepository
import com.cobblemon.mod.common.client.render.block.BerryBlockRenderer
import com.cobblemon.mod.common.client.render.block.FossilAnalyzerRenderer
import com.cobblemon.mod.common.client.render.block.RestorationTankRenderer
import com.cobblemon.mod.common.client.render.block.GildedChestBlockRenderer
import com.cobblemon.mod.common.client.render.block.HealingMachineRenderer
import com.cobblemon.mod.common.client.render.block.*
import com.cobblemon.mod.common.client.render.block.TMBlockRenderer
import com.cobblemon.mod.common.client.render.boat.CobblemonBoatRenderer
import com.cobblemon.mod.common.client.render.generic.GenericBedrockRenderer
import com.cobblemon.mod.common.client.render.item.CobblemonBuiltinItemRendererRegistry
import com.cobblemon.mod.common.client.render.item.PokemonItemRenderer
import com.cobblemon.mod.common.client.render.layer.PokemonOnShoulderRenderer
@@ -44,13 +50,19 @@ import com.cobblemon.mod.common.client.storage.ClientStorageManager
import com.cobblemon.mod.common.client.trade.ClientTrade
import com.cobblemon.mod.common.data.CobblemonDataProvider
import com.cobblemon.mod.common.entity.boat.CobblemonBoatType
import com.cobblemon.mod.common.gui.CobblemonScreenHandlers
import com.cobblemon.mod.common.item.PokeBallItem
import com.cobblemon.mod.common.item.TechnicalMachineItem
import com.cobblemon.mod.common.client.render.models.blockbench.repository.FossilModelRepository
import com.cobblemon.mod.common.client.render.models.blockbench.repository.GenericBedrockEntityModelRepository
import com.cobblemon.mod.common.client.render.models.blockbench.repository.MiscModelRepository
import com.cobblemon.mod.common.platform.events.PlatformEvents
import com.cobblemon.mod.common.util.DataKeys
import com.cobblemon.mod.common.util.asTranslated
import net.minecraft.client.color.block.BlockColorProvider
import net.minecraft.client.color.item.ItemColorProvider
import net.minecraft.client.gui.DrawContext
import net.minecraft.client.gui.screen.ingame.HandledScreens
import net.minecraft.client.render.RenderLayer
import net.minecraft.client.render.block.entity.HangingSignBlockEntityRenderer
import net.minecraft.client.render.block.entity.SignBlockEntityRenderer
@@ -104,9 +116,10 @@ object CobblemonClient {
this.registerBlockEntityRenderers()
registerBlockRenderTypes()
//registerColors()
registerColors()
registerFlywheelRenderers()
this.registerEntityRenderers()
registerHandledScreens()
Berries.observable.subscribe {
BerryModelRepository.patchModels()
}
@@ -117,7 +130,6 @@ object CobblemonClient {
PlatformEvents.CLIENT_ITEM_TOOLTIP.subscribe { event ->
val stack = event.stack
val lines = event.lines
val size = lines.size
@Suppress("DEPRECATION")
if (stack.item.registryEntry.key.isPresent && stack.item.registryEntry.key.get().value.namespace == Cobblemon.MODID) {
if (stack.nbt?.getBoolean(DataKeys.HIDE_TOOLTIP) == true) {
@@ -125,14 +137,14 @@ object CobblemonClient {
}
val language = Language.getInstance()
val key = this.baseLangKeyForItem(stack)
val offset = if (size > 1) 1 else 0
val offset = if (lines.size > 1) 1 else 0
if (language.hasTranslation(key)) {
lines.add(size - offset, key.asTranslated().gray())
lines.add(lines.size - offset, key.asTranslated().gray())
}
var i = 1
var listKey = "${key}_$i"
while(language.hasTranslation(listKey)) {
lines.add(size - offset, listKey.asTranslated().gray())
lines.add(lines.size - offset, listKey.asTranslated().gray())
listKey = "${key}_${++i}"
}
}
@@ -147,21 +159,24 @@ object CobblemonClient {
// .apply()
}
/*
fun registerColors() {
this.implementation.registerBlockColors(BlockColorProvider { _, _, _, _ ->
return@BlockColorProvider 0xE0A33A
}, CobblemonBlocks.APRICORN_LEAVES)
this.implementation.registerItemColors(ItemColorProvider { _, _ ->
this.implementation.registerItemColors(ItemColorProvider { stack, tint ->
return@ItemColorProvider 0xE0A33A
}, CobblemonItems.APRICORN_LEAVES)
}, CobblemonItems.TECHNICAL_MACHINE)
this.implementation.registerItemColors(TechnicalMachineItem::getItemColor, CobblemonItems.TECHNICAL_MACHINE)
}
*/
private fun registerBlockRenderTypes() {
this.implementation.registerBlockRenderType(RenderLayer.getCutoutMipped(), CobblemonBlocks.APRICORN_LEAVES)
this.implementation.registerBlockRenderType(RenderLayer.getTranslucent(), CobblemonBlocks.TM_MACHINE)
this.implementation.registerBlockRenderType(
RenderLayer.getCutout(),
CobblemonBlocks.GILDED_CHEST,
@@ -218,7 +233,8 @@ object CobblemonClient {
CobblemonBlocks.MEDIUM_BUDDING_SKY_TUMBLESTONE,
CobblemonBlocks.LARGE_BUDDING_SKY_TUMBLESTONE,
CobblemonBlocks.SKY_TUMBLESTONE_CLUSTER,
CobblemonBlocks.GIMMIGHOUL_CHEST
CobblemonBlocks.GIMMIGHOUL_CHEST,
CobblemonBlocks.DISPLAY_CASE
)
this.createBoatModelLayers()
@@ -248,7 +264,9 @@ object CobblemonClient {
this.implementation.registerBlockEntityRenderer(CobblemonBlockEntities.HANGING_SIGN, ::HangingSignBlockEntityRenderer)
this.implementation.registerBlockEntityRenderer(CobblemonBlockEntities.FOSSIL_ANALYZER, ::FossilAnalyzerRenderer)
this.implementation.registerBlockEntityRenderer(CobblemonBlockEntities.RESTORATION_TANK, ::RestorationTankRenderer)
this.implementation.registerBlockEntityRenderer(CobblemonBlockEntities.TM_BLOCK, ::TMBlockRenderer)
this.implementation.registerBlockEntityRenderer(CobblemonBlockEntities.GILDED_CHEST, ::GildedChestBlockRenderer)
this.implementation.registerBlockEntityRenderer(CobblemonBlockEntities.DISPLAY_CASE, ::DisplayCaseRenderer)
}
private fun registerEntityRenderers() {
@@ -260,6 +278,8 @@ object CobblemonClient {
this.implementation.registerEntityRenderer(CobblemonEntities.BOAT) { ctx -> CobblemonBoatRenderer(ctx, false) }
LOGGER.info("Registering Boat with Chest renderer")
this.implementation.registerEntityRenderer(CobblemonEntities.CHEST_BOAT) { ctx -> CobblemonBoatRenderer(ctx, true) }
LOGGER.info("Registering Generic Bedrock Entity renderer")
this.implementation.registerEntityRenderer(CobblemonEntities.GENERIC_BEDROCK_ENTITY, ::GenericBedrockRenderer)
}
fun reloadCodedAssets(resourceManager: ResourceManager) {
@@ -267,13 +287,14 @@ object CobblemonClient {
BedrockParticleEffectRepository.loadEffects(resourceManager)
BedrockAnimationRepository.loadAnimations(
resourceManager = resourceManager,
directories = PokemonModelRepository.animationDirectories + PokeBallModelRepository.animationDirectories + FossilModelRepository.animationDirectories + BlockEntityModelRepository.animationDirectories
directories = PokemonModelRepository.animationDirectories + PokeBallModelRepository.animationDirectories + FossilModelRepository.animationDirectories + BlockEntityModelRepository.animationDirectories + GenericBedrockEntityModelRepository.animationDirectories
)
PokemonModelRepository.reload(resourceManager)
PokeBallModelRepository.reload(resourceManager)
BerryModelRepository.reload(resourceManager)
FossilModelRepository.reload(resourceManager)
BlockEntityModelRepository.reload(resourceManager)
GenericBedrockEntityModelRepository.reload(resourceManager)
MiscModelRepository.reload(resourceManager)
LOGGER.info("Loaded assets")
}
@@ -299,4 +320,15 @@ object CobblemonClient {
}
}
private fun registerHandledScreens() {
HandledScreens.register(
CobblemonScreenHandlers.TMM_SCREEN,
::TMMHandledScreen
)
HandledScreens.register(
CobblemonScreenHandlers.MOD_STATION_SCREEN,
::ModStationHandledScreen
)
}
}
@@ -0,0 +1,48 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.gui
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.types.ElementalType
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.client.gui.DrawContext
import net.minecraft.client.util.math.MatrixStack
class TypeReturnIcon(
val x: Number,
val y: Number,
val centeredX: Boolean = false,
val small: Boolean = false,
val secondaryOffset: Float = 15F,
val doubleCenteredOffset: Float = 7.5F,
val opacity: Float = 1F
) {
companion object {
private const val TYPE_ICON_DIAMETER = 36
private const val SCALE = 0.5F
private val typeReturnResource = cobblemonResource("textures/gui/type_return.png")
private val smallTypeReturnResource = cobblemonResource("textures/gui/type_return_small.png")
}
fun render(context: DrawContext) {
val diameter = if (small) (TYPE_ICON_DIAMETER / 2) else TYPE_ICON_DIAMETER
blitk(
matrixStack = context.matrices,
texture = if (small) smallTypeReturnResource else typeReturnResource,
x = (x.toFloat()) / SCALE - 9,
y = y.toFloat() / SCALE,
height = diameter,
width = diameter,
alpha = opacity,
scale = SCALE
)
}
}
@@ -10,11 +10,12 @@ package com.cobblemon.mod.common.client.gui.battle.subscreen
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.battles.model.actor.ActorType
import com.cobblemon.mod.common.api.text.yellow
import com.cobblemon.mod.common.battles.BattleRegistry
import com.cobblemon.mod.common.client.CobblemonClient
import com.cobblemon.mod.common.client.battle.SingleActionRequest
import com.cobblemon.mod.common.client.gui.battle.BattleGUI
import com.cobblemon.mod.common.client.gui.battle.widgets.BattleOptionTile
import com.cobblemon.mod.common.net.messages.server.battle.FleeBattlePacket
import com.cobblemon.mod.common.util.battleLang
import net.minecraft.client.MinecraftClient
import net.minecraft.client.gui.DrawContext
@@ -61,8 +62,8 @@ class BattleGeneralActionSelection(
}
addOption(rank++, battleLang("ui.run"), BattleGUI.runResource) {
playDownSound(MinecraftClient.getInstance().soundManager)
FleeBattlePacket(battle.battleId).sendToServer()
MinecraftClient.getInstance().player?.sendMessage(battleLang("flee"))
}
}
}
@@ -0,0 +1,28 @@
package com.cobblemon.mod.common.client.gui.modstation
import com.cobblemon.mod.common.gui.ModStationScreenHandler
import net.minecraft.client.gui.DrawContext
import net.minecraft.client.gui.screen.ingame.HandledScreen
import net.minecraft.entity.player.PlayerInventory
import net.minecraft.item.ItemStack
import net.minecraft.screen.ScreenHandler
import net.minecraft.screen.ScreenHandlerListener
import net.minecraft.text.Text
class ModStationHandledScreen(
val handler: ModStationScreenHandler,
val inventory: PlayerInventory,
title: Text?
) : HandledScreen<ModStationScreenHandler>(handler, inventory, title), ScreenHandlerListener {
override fun drawBackground(context: DrawContext?, delta: Float, mouseX: Int, mouseY: Int) {
}
override fun onSlotUpdate(handler: ScreenHandler?, slotId: Int, stack: ItemStack?) {
}
override fun onPropertyUpdate(handler: ScreenHandler?, property: Int, value: Int) {
}
}
@@ -0,0 +1,37 @@
package com.cobblemon.mod.common.client.gui.tm
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.client.gui.DrawContext
import net.minecraft.client.gui.widget.ButtonWidget
import net.minecraft.client.sound.SoundManager
import net.minecraft.text.Text
class DiscButton(
pX: Int, pY: Int,
onPress: PressAction,
): ButtonWidget(pX, pY, WIDTH.toInt(), HEIGHT.toInt(), Text.literal("All Types"), onPress, DEFAULT_NARRATION_SUPPLIER) {
companion object {
private const val WIDTH = 53F
private const val HEIGHT = 53F
val DISC_BUTTON = cobblemonResource("textures/gui/tm/disc_button.png")
}
override fun renderButton(context: DrawContext, pMouseX: Int, pMouseY: Int, pPartialTicks: Float) {
blitk(
matrixStack = context.matrices,
texture = DISC_BUTTON,
x = x,
y = y,
width = WIDTH,
height = HEIGHT,
vOffset = if (isHovered(pMouseX.toDouble(), pMouseY.toDouble())) HEIGHT else 0,
textureHeight = HEIGHT * 2
)
}
override fun playDownSound(soundManager: SoundManager) {}
fun isHovered(mouseX: Double, mouseY: Double) = mouseX.toFloat() in (x.toFloat()..(x.toFloat() + WIDTH)) && mouseY.toFloat() in (y.toFloat()..(y.toFloat() + HEIGHT))
}
@@ -0,0 +1,54 @@
package com.cobblemon.mod.common.client.gui.tm
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.client.gui.DrawContext
import net.minecraft.client.gui.widget.ButtonWidget
import net.minecraft.client.sound.SoundManager
import net.minecraft.text.Text
class EjectButton(
pX: Int, pY: Int,
val small: Boolean = false,
onPress: PressAction
): ButtonWidget(
pX,
pY,
if (small) SMALL_WIDTH.toInt() else LARGE_WIDTH.toInt(),
if (small) SMALL_HEIGHT.toInt() else LARGE_HEIGHT.toInt(),
Text.literal("Eject"),
onPress,
DEFAULT_NARRATION_SUPPLIER
) {
companion object {
private const val LARGE_WIDTH = 54F
private const val LARGE_HEIGHT = 14F
private const val SMALL_WIDTH = 14F
private const val SMALL_HEIGHT = 14F
val SMALL_BUTTON = cobblemonResource("textures/gui/tm/eject_button_small.png")
val LARGE_BUTTON = cobblemonResource("textures/gui/tm/eject_button_large.png")
}
val height = if (small) SMALL_HEIGHT else LARGE_HEIGHT
val width = if (small) SMALL_WIDTH else LARGE_WIDTH
val texture = if (small) SMALL_BUTTON else LARGE_BUTTON
override fun renderButton(context: DrawContext, pMouseX: Int, pMouseY: Int, pPartialTicks: Float) {
blitk(
matrixStack = context.matrices,
texture = texture,
x = x,
y = y,
width = width,
height = height,
vOffset = if (isHovered(pMouseX.toDouble(), pMouseY.toDouble())) height else 0,
textureHeight = height * 2
)
}
override fun playDownSound(soundManager: SoundManager) {}
fun isHovered(mouseX: Double, mouseY: Double) = mouseX.toFloat() in (x.toFloat()..(x.toFloat() + width)) && mouseY.toFloat() in (y.toFloat()..(y.toFloat() + height))
}
@@ -0,0 +1,59 @@
package com.cobblemon.mod.common.client.gui.tm
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.tms.TechnicalMachine
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.client.render.renderScaledGuiItemIcon
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.client.gui.DrawContext
import net.minecraft.client.gui.widget.ButtonWidget
import net.minecraft.client.sound.SoundManager
import net.minecraft.text.Text
class TMListingButton(
pX: Int, pY: Int,
onPress: PressAction,
val tm: TechnicalMachine
): ButtonWidget(pX, pY, WIDTH.toInt(), HEIGHT.toInt(), Text.literal("All Types"), onPress, DEFAULT_NARRATION_SUPPLIER) {
companion object {
private const val HEIGHT = 20F
private const val WIDTH = 140F
val TM_LISTING_BUTTON = cobblemonResource("textures/gui/tm/tm_selection_listing.png")
}
override fun renderButton(context: DrawContext, pMouseX: Int, pMouseY: Int, pPartialTicks: Float) {
blitk(
matrixStack = context.matrices,
texture = TM_LISTING_BUTTON,
x = x,
y = y,
width = WIDTH,
height = HEIGHT,
vOffset = if (isHovered(pMouseX.toDouble(), pMouseY.toDouble())) HEIGHT else 0,
textureHeight = HEIGHT * 2
)
val item = CobblemonItems.TECHNICAL_MACHINE
renderScaledGuiItemIcon(
itemStack = item.setNbt(item.defaultStack, tm.id().toString()),
x = x.toDouble() + 2,
y = y.toDouble() + 2
)
drawScaledText(
context = context,
text = tm.translatedMoveName(),
x = x + 25,
y = y + 7.5,
scale = 0.75f
)
}
override fun playDownSound(soundManager: SoundManager) {}
fun isHovered(mouseX: Double, mouseY: Double) = mouseX.toFloat() in (x.toFloat()..(x.toFloat() + WIDTH)) && mouseY.toFloat() in (y.toFloat()..(y.toFloat() + HEIGHT))
}
@@ -0,0 +1,611 @@
package com.cobblemon.mod.common.client.gui.tm
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.CobblemonNetwork.sendPacketToServer
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.gui.ColourLibrary
import com.cobblemon.mod.common.api.gui.MultiLineLabelK
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.moves.Moves
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.api.tms.TechnicalMachineRecipe
import com.cobblemon.mod.common.api.types.ElementalType
import com.cobblemon.mod.common.api.types.ElementalTypes
import com.cobblemon.mod.common.block.TMBlock
import com.cobblemon.mod.common.block.entity.TMBlockEntity
import com.cobblemon.mod.common.client.CobblemonClient
import com.cobblemon.mod.common.client.gui.ExitButton
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.client.render.renderScaledGuiItemIcon
import com.cobblemon.mod.common.gui.TMMScreenHandler
import com.cobblemon.mod.common.net.messages.client.ui.CraftBlankTMPacket
import com.cobblemon.mod.common.net.messages.client.ui.CraftTMPacket
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.pokemon.activestate.PokemonState
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.lang
import net.minecraft.client.gui.DrawContext
import net.minecraft.client.gui.Drawable
import net.minecraft.client.gui.Element
import net.minecraft.client.gui.Selectable
import net.minecraft.client.gui.screen.ingame.HandledScreen
import net.minecraft.entity.player.PlayerInventory
import net.minecraft.item.ItemStack
import net.minecraft.item.Items
import net.minecraft.registry.Registries
import net.minecraft.screen.ScreenHandler
import net.minecraft.screen.ScreenHandlerListener
import net.minecraft.text.Text
import net.minecraft.util.Identifier
class TMMHandledScreen(
val handler: TMMScreenHandler,
val inventory: PlayerInventory,
title: Text?
) : HandledScreen<TMMScreenHandler>(handler, inventory, title), ScreenHandlerListener {
companion object {
val TEXTURE_HEIGHT = 222
val TEXTURE_WIDTH = 254
val TYPE_MENU_MODE = 1
val TM_BROWSING_MODE = 2
val TYPE_SELECTION_BASE = cobblemonResource("textures/gui/tm/type_selection_base.png")
val TM_SELECTION_BASE = cobblemonResource("textures/gui/tm/tm_selection_base.png")
val TM_SELECTION_BORDER = cobblemonResource("textures/gui/tm/tm_selection_border.png")
val TM_SELECTION_LISTING = cobblemonResource("textures/gui/tm/tm_selection_listing.png")
val EJECT_BUTTON_SMALL = cobblemonResource("textures/gui/tm/eject_button_small.png")
val EJECT_BUTTON_LARGE = cobblemonResource("textures/gui/tm/eject_button_large.png")
val PARTY_SLOT = cobblemonResource("textures/gui/tm/party_slot.png")
val TEACH_BUTTON = cobblemonResource("textures/gui/tm/teach_button.png")
}
// val selectedTM: TechnicalMachine? = TechnicalMachines.tmMap[cobblemonResource("thunderbolt")]!!
var selectedTM: TechnicalMachine? = null
var tmList: SettableObservable<MutableList<TechnicalMachine>> = SettableObservable(mutableListOf())
var output: ItemStack? = null
val children: MutableMap<String, Element> = mutableMapOf()
var mode: Int = TYPE_MENU_MODE
var sortType: ElementalType? = null
var selectedPokemon: Pokemon? = null
var scroll: TMScrollingList? = null
init {
scroll = TMScrollingList(
x = x + 132,
y = y + 45,
parent = this
)
}
// val selectedTM = TechnicalMachine("stompingtantrum", null, emptyList(), "ground", null, null)
/**
* Used instead of `addDrawableChild()` to avoid re-drawing children.
*/
private fun <T> addChild(drawableElement: T, identifier: String) where T : Element, T : Drawable, T : Selectable {
if (children.contains(identifier)) return
addDrawableChild(drawableElement)
children[identifier] = drawableElement
}
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
if (scroll?.isHovered(mouseX, mouseY) == true) {
scroll!!.mouseClicked(mouseX, mouseY, button)
}
return super.mouseClicked(mouseX, mouseY, button)
}
override fun mouseScrolled(mouseX: Double, mouseY: Double, amount: Double): Boolean {
if (scroll?.isHovered(mouseX, mouseY) == true) {
scroll!!.mouseScrolled(mouseX, mouseY, amount)
return true
}
return false
}
override fun mouseDragged(mouseX: Double, mouseY: Double, button: Int, deltaX: Double, deltaY: Double): Boolean {
if (mode != TYPE_MENU_MODE) scroll?.mouseDragged(
mouseX,
mouseY,
button,
deltaX,
deltaY
)
return super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)
}
fun drawBlankCraftingRecipe(context: DrawContext) {
val x = (width - TEXTURE_WIDTH) / 2
val y = (height - TEXTURE_HEIGHT) / 2
// ingredient
renderScaledGuiItemIcon(
itemStack = Items.AMETHYST_SHARD.defaultStack,
x = (x + 197 + 36).toDouble(),
y = (y + 127).toDouble(),
zTranslation = 1f
)
drawScaledText(
context = context,
text = lang("ui.tms.material_cost"),
x = (x + 223),
y = (y + 117.5),
scale = 0.5f,
centered = true
)
drawScaledText(
context = context,
text = Text.translatable(CobblemonItems.BLANK_TM.translationKey).bold(),
x = (x + 223),
y = (y + 12),
scale = 0.5f,
centered = true
)
drawScaledText(
context = context,
text = lang("ui.power"),
x = (x + 199),
y = (y + 25),
scale = 0.5f
)
drawScaledText(
context = context,
text = Text.literal("-"),
x = (x + 241),
y = (y + 25),
scale = 0.5f,
centered = true
)
drawScaledText(
context = context,
text = lang("ui.accuracy_short"),
x = (x + 199),
y = (y + 35),
scale = 0.5f
)
drawScaledText(
context = context,
text = Text.literal("-"),
x = (x + 241),
y = (y + 35),
scale = 0.5f,
centered = true
)
drawScaledText(
context = context,
text = lang("ui.pp"),
x = (x + 199),
y = (y + 45),
scale = 0.5f
)
drawScaledText(
context = context,
text = Text.literal("-"),
x = (x + 241),
y = (y + 45),
scale = 0.5f,
centered = true
)
drawScaledText(
context = context,
text = lang("ui.summary.title"),
x = (x + 199),
y = (y + 54.5),
scale = 0.5f
)
val scale = 0.5f
context.matrices.push()
context.matrices.scale(scale, scale, 1f)
MultiLineLabelK.create(
component = lang("ui.tms.blank_tm_desc"),
width = 49 / scale,
maxLines = 7
).renderLeftAligned(
context = context,
x = (x + 199) / scale,
y = (y + 62) / scale,
ySpacing = 5.5 / scale,
colour = ColourLibrary.WHITE,
shadow = true
)
context.matrices.pop()
}
fun drawMoveInfo(context: DrawContext, delta: Float, mouseX: Int, mouseY: Int) {
val currentTm = selectedTM
if (currentTm == null) {
return drawBlankCraftingRecipe(context)
}
val x = (width - TEXTURE_WIDTH) / 2
val y = (height - TEXTURE_HEIGHT) / 2
val move = Moves.getByNameOrDummy(currentTm.moveName)
// blank tm
renderScaledGuiItemIcon(
itemStack = CobblemonItems.BLANK_TM.defaultStack,
x = (x + 197).toDouble(),
y = (y + 127).toDouble(),
)
val typeGem = Registries.ITEM.get(ElementalTypes.get(currentTm.type)?.typeGem).defaultStack
// type gem
renderScaledGuiItemIcon(
itemStack = typeGem ?: ItemStack.EMPTY,
x = (x + 197 + 18).toDouble(),
y = (y + 127).toDouble(),
)
val recipe = currentTm.recipe ?: TechnicalMachineRecipe(Identifier.of("minecraft", "air")!!, 1)
// ingredient
renderScaledGuiItemIcon(
itemStack = Registries.ITEM.get(recipe.item).defaultStack,
x = (x + 197 + 36).toDouble(),
y = (y + 127).toDouble(),
zTranslation = 1f
)
context.matrices.push()
context.matrices.translate(0f, 0f, 2f)
if (recipe.count > 1) {
val xIncrease = if (recipe.count < 10) 47 else 41
drawScaledText(
context = context,
text = Text.literal(recipe.count.toString()),
x = (x + 197 + xIncrease).toDouble(),
y = (y + 136).toDouble(),
shadow = true
)
}
context.matrices.pop()
drawScaledText(
context = context,
text = lang("ui.tms.material_cost"),
x = (x + 223),
y = (y + 117.5),
scale = 0.5f,
centered = true
)
drawScaledText(
context = context,
text = currentTm.translatedMoveName().bold(),
x = (x + 223),
y = (y + 12),
scale = 0.5f,
centered = true
)
MoveCategoryIcon(
x = (x + 237.5),
y = (y + 52.5),
category = Moves.getByNameOrDummy(currentTm.moveName).damageCategory
).render(context)
drawScaledText(
context = context,
text = lang("ui.power"),
x = (x + 199),
y = (y + 25),
scale = 0.5f
)
drawScaledText(
context = context,
text = Text.literal(move.power.toInt().toString()),
x = (x + 241),
y = (y + 25),
scale = 0.5f,
centered = true
)
drawScaledText(
context = context,
text = lang("ui.accuracy_short"),
x = (x + 199),
y = (y + 35),
scale = 0.5f
)
drawScaledText(
context = context,
text = Text.literal(move.accuracy.toInt().toString() + "%"),
x = (x + 241),
y = (y + 35),
scale = 0.5f,
centered = true
)
drawScaledText(
context = context,
text = lang("ui.pp"),
x = (x + 199),
y = (y + 45),
scale = 0.5f
)
drawScaledText(
context = context,
text = Text.literal(move.pp.toString()),
x = (x + 241),
y = (y + 45),
scale = 0.5f,
centered = true
)
drawScaledText(
context = context,
text = lang("ui.summary.title"),
x = (x + 199),
y = (y + 54.5),
scale = 0.5f
)
val scale = 0.5f
context.matrices.push()
context.matrices.scale(scale, scale, 1f)
MultiLineLabelK.create(
component = lang("move.${currentTm.moveName}.desc"),
width = 49 / scale,
maxLines = 7
).renderLeftAligned(
context = context,
x = (x + 199) / scale,
y = (y + 62) / scale,
ySpacing = 5.5 / scale,
colour = ColourLibrary.WHITE,
shadow = true
)
context.matrices.pop()
}
fun drawTypeMenu(context: DrawContext, delta: Float, mouseX: Int, mouseY: Int) {
val x = (width - TEXTURE_WIDTH) / 2
val y = (height - TEXTURE_HEIGHT) / 2
blitk(
matrixStack = context.matrices,
texture = TYPE_SELECTION_BASE,
x = x, // horizontal placement of GUI
y = y, // vertical placement of GUI
width = TEXTURE_WIDTH, // scale of the GUI width
height = TEXTURE_HEIGHT // scale of the GUI height
)
fun drawTypeRow(drawX: Int, drawY: Int, types: List<ElementalType>) {
var xOffset = 0
for (type in types) {
addChild(TypeButton(
pX = drawX + 32 + xOffset,
pY = drawY + 16,
type = type,
onPress = {
inventory.player.playSound(CobblemonSounds.GUI_CLICK, 1f, 1f)
tmList.set(TechnicalMachine.filterTms(null, type, null).toMutableList())
sortType = type
mode = TM_BROWSING_MODE
clearGUI()
scroll?.scrollAmount = 0.0
}
), "${type.name}type")
xOffset += 24
}
}
drawTypeRow(x, y, listOf(
ElementalTypes.NORMAL,
ElementalTypes.FIRE,
ElementalTypes.WATER,
ElementalTypes.GRASS,
ElementalTypes.ELECTRIC,
ElementalTypes.ICE
))
drawTypeRow(x + 12, y + 24, listOf(
ElementalTypes.FIGHTING,
ElementalTypes.POISON,
ElementalTypes.GROUND,
ElementalTypes.FLYING,
))
drawTypeRow(x, y + 48, listOf(
ElementalTypes.PSYCHIC,
ElementalTypes.BUG,
ElementalTypes.ROCK,
ElementalTypes.GHOST,
))
drawTypeRow(x + 12, y + 72, listOf(
ElementalTypes.DRAGON,
ElementalTypes.DARK,
ElementalTypes.STEEL,
ElementalTypes.FAIRY,
))
addChild(
DiscButton(
pX = x + 134,
pY = y + 50,
onPress = {
inventory.player.playSound(CobblemonSounds.GUI_CLICK, 1f, 1f)
tmList.set(TechnicalMachine.filterTms(null, null, null).toMutableList())
mode = TM_BROWSING_MODE
sortType = null
scroll?.scrollAmount = 0.0
clearGUI()
}
), "disc"
)
}
fun drawTmSelectMenu(context: DrawContext, delta: Float, mouseX: Int, mouseY: Int) {
val x = (width - TEXTURE_WIDTH) / 2
val y = (height - TEXTURE_HEIGHT) / 2
blitk(
matrixStack = context.matrices,
texture = TM_SELECTION_BASE,
x = x, // horizontal placement of GUI
y = y, // vertical placement of GUI
width = TEXTURE_WIDTH, // scale of the GUI width
height = TEXTURE_HEIGHT // scale of the GUI height
)
blitk(
matrixStack = context.matrices,
texture = TM_SELECTION_BORDER,
x = x + 31, // horizontal placement of GUI
y = y + 14, // vertical placement of GUI
width = 157,
height = 101
)
val displayType = sortType
addChild(
TypeButton(
pX = x + 31,
pY = y + 16,
type = displayType,
onPress = {
mode = TYPE_MENU_MODE
inventory.player.playSound(CobblemonSounds.GUI_CLICK, 1f, 1f)
clearGUI()
}
), "returnTypeMenu"
)
scroll?.render(context, mouseX, mouseY, delta)
}
fun clearGUI() {
val iterator = children.iterator()
while (iterator.hasNext()) {
val (id, _) = iterator.next()
iterator.remove()
children.remove(id)
}
clearChildren()
}
override fun drawBackground(context: DrawContext, delta: Float, mouseX: Int, mouseY: Int) {
super.renderBackground(context)
val x = (width - TEXTURE_WIDTH) / 2
val y = (height - TEXTURE_HEIGHT) / 2
blitk(
matrixStack = context.matrices,
texture = TYPE_SELECTION_BASE,
x = x, // horizontal placement of GUI
y = y, // vertical placement of GUI
width = TEXTURE_WIDTH, // scale of the GUI width
height = TEXTURE_HEIGHT // scale of the GUI height
)
when (mode) {
TYPE_MENU_MODE -> drawTypeMenu(context, delta, mouseX, mouseY)
else -> drawTmSelectMenu(context, delta, mouseX, mouseY)
}
drawMoveInfo(context, delta, mouseX, mouseY)
inventory.markDirty()
addChild(
EjectButton(
pX = x + 196,
pY = y + 168,
small = false,
onPress = {
inventory.player.playSound(CobblemonSounds.GUI_CLICK, 1f, 1f)
val currentTm = selectedTM ?: return@EjectButton sendPacketToServer(CraftBlankTMPacket(handler.input.getStack(2)))
sendPacketToServer(
CraftTMPacket(
currentTm,
handler.input.getStack(0),
handler.input.getStack(1),
handler.input.getStack(2)
)
)
this.handler.syncState()
}
), "eject"
)
addChild(
ExitButton(
pX = x + 228,
pY = y + 185,
onPress = {
this.close()
}
), "exit"
)
val startY = y + 13
val offset = 28
var iterations = 0
CobblemonClient.storage.myParty.forEach { pokemon ->
if (pokemon != null) {
addChild(TMPartySlotWidget(
pX = x - 23,
pY = startY + (offset * iterations),
pokemon = pokemon,
onPress = {
selectedPokemon = if (selectedPokemon == pokemon) null else pokemon
}
), "partyslot_${pokemon.uuid}")
}
iterations++
}
}
override fun drawForeground(context: DrawContext?, mouseX: Int, mouseY: Int) {
//Text is usually drawn here, we dont want that
this.drawMouseoverTooltip(context, mouseX - 158, mouseY - 28)
//this.renderWithTooltip(context, mouseX, mouseY, 1F)
}
override fun onSlotUpdate(handler: ScreenHandler?, slotId: Int, stack: ItemStack?) {
}
override fun close() {
super.close()
val inventory = handler.inventory
if (inventory is TMBlockEntity.TMBlockInventory) {
inventory.tmBlockEntity.blockState.with(TMBlock.ON,false)
}
}
override fun onPropertyUpdate(handler: ScreenHandler?, property: Int, value: Int) {
}
}
@@ -0,0 +1,20 @@
package com.cobblemon.mod.common.client.gui.tm
import com.cobblemon.mod.common.gui.TMMScreenHandler
import com.cobblemon.mod.common.util.giveOrDropItemStack
import net.minecraft.entity.player.PlayerEntity
import net.minecraft.inventory.Inventory
import net.minecraft.item.ItemStack
import net.minecraft.item.Items
import net.minecraft.screen.slot.Slot
class TMMOutputSlot(val screen: TMMScreenHandler, index: Int, x: Int, y: Int) : Slot(screen.playerInventory, index, x, y) {
override fun canInsert(stack: ItemStack?) = false
override fun onTakeItem(player: PlayerEntity, stack: ItemStack) {
player.giveOrDropItemStack(stack, false)
this.stack = Items.AIR.defaultStack
}
}
@@ -0,0 +1,106 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.gui.tm
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.client.gui.drawProfilePokemon
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.math.fromEulerXYZDegrees
import net.minecraft.client.gui.DrawContext
import net.minecraft.client.gui.widget.ButtonWidget
import net.minecraft.text.Text
import org.joml.Quaternionf
import org.joml.Vector3f
class TMPartySlotWidget(
pX: Number,
pY: Number,
val pokemon: Pokemon?,
onPress: PressAction
) : ButtonWidget(pX.toInt(), pY.toInt(), WIDTH, HEIGHT, Text.literal("PartyMember"), onPress, NarrationSupplier { "".text() }) {
companion object {
const val WIDTH = 46
const val HEIGHT = 27
private const val PORTRAIT_DIAMETER = 25
private val slotResource = cobblemonResource("textures/gui/tm/party_slot.png")
val genderIconMale = cobblemonResource("textures/gui/party/party_gender_male.png")
val genderIconFemale = cobblemonResource("textures/gui/party/party_gender_female.png")
}
private fun getSlotVOffset(pokemon: Pokemon?, isHovered: Boolean, isSelected: Boolean): Int {
if (isHovered || isSelected) {
if (pokemon == null) {
return 0
}
return height
}
return 0
}
override fun renderButton(context: DrawContext, mouseX: Int, mouseY: Int, delta: Float) {
hovered = mouseX >= x && mouseY >= y && mouseX < x + width && mouseY < y + height;
val matrices = context.matrices
blitk(
matrixStack = matrices,
texture = slotResource,
x = x,
y = y,
width = width,
height = height,
vOffset = getSlotVOffset(pokemon, isHovered, isSelected),
textureHeight = height * 2,
)
if (pokemon != null) {
val halfScale = 0.5F
// Render Pokémon
matrices.push()
matrices.translate(x + (PORTRAIT_DIAMETER / 2.0) + 10, y - 1.0, 0.0)
matrices.scale(2.5F, 2.5F, 1F)
drawProfilePokemon(
species = pokemon.species.resourceIdentifier,
aspects = pokemon.aspects.toSet(),
matrixStack = matrices,
rotation = Quaternionf().fromEulerXYZDegrees(Vector3f(13F, 35F, 0F)),
state = null,
scale = 4.5F,
partialTicks = delta
)
matrices.pop()
// Draw Name
drawScaledText(
context = context,
text = pokemon.getDisplayName(),
x = x + 16,
y = y + 21.5,
scale = halfScale
)
val ballIcon = cobblemonResource("textures/gui/ball/" + pokemon.caughtBall.name.path + ".png")
val ballHeight = 22
blitk(
matrixStack = matrices,
texture = ballIcon,
x = (x - 2) / halfScale,
y = (y - 3) / halfScale,
height = ballHeight,
width = 18,
textureHeight = ballHeight * 2,
scale = halfScale
)
}
}
}
@@ -0,0 +1,158 @@
package com.cobblemon.mod.common.client.gui.tm
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.tms.TechnicalMachine
import net.minecraft.client.MinecraftClient
import net.minecraft.client.gui.DrawContext
import net.minecraft.client.gui.widget.AlwaysSelectedEntryListWidget
import net.minecraft.text.Text
class TMScrollingList(
val x: Int,
val y: Int,
val parent: TMMHandledScreen
) : AlwaysSelectedEntryListWidget<TMScrollingList.TMScrollingListEntry> (
MinecraftClient.getInstance(),
WIDTH, // width
HEIGHT, // height
0, // top
HEIGHT, // bottom
SLOT_HEIGHT// + SLOT_SPACING
) {
companion object {
const val WIDTH = 140
const val HEIGHT = 78
const val SLOT_WIDTH = 140
const val SLOT_HEIGHT = 20
const val SLOT_SPACING = 3
const val SCALE = 0.5f
}
private var scrolling = false
override fun getRowWidth() = SLOT_WIDTH
init {
correctSize()
setRenderHorizontalShadows(false)
setRenderBackground(false)
setRenderSelection(false)
parent.tmList.subscribeIncludingCurrent { tmList ->
val currentEntries = children()
val newTMs = tmList.filter { tm -> currentEntries.none { it.tm.id() == tm.id() } }
val removedTMs = currentEntries.filter { entry -> tmList.none { it.id() == entry.tm.id() } }
removedTMs.forEach(this::removeEntry)
newTMs.forEach { tm -> addEntry(TMScrollingListEntry(tm, parent)) }
}
}
override fun getScrollbarPositionX() = left + width - 3
private fun correctSize() {
updateSize(
WIDTH,
HEIGHT, y - 2, (y + 2) + (HEIGHT - 2))
setLeftPos(x)
}
public override fun addEntry(entry: TMScrollingListEntry) = super.addEntry(entry)
public override fun removeEntry(entry: TMScrollingListEntry) = super.removeEntry(entry)
override fun render(context: DrawContext, mouseX: Int, mouseY: Int, delta: Float) {
correctSize()
context.enableScissor(
left,
top + 5,
left + width,
top + 1 + height
)
super.render(context, mouseX, mouseY, delta)
context.disableScissor()
}
fun isHovered(mouseX: Double, mouseY: Double): Boolean {
println(mouseX.toFloat() in (x.toFloat()..(x.toFloat() + WIDTH)) && mouseY.toFloat() in (y.toFloat()..(y.toFloat() + HEIGHT)))
return mouseX.toFloat() in (x.toFloat()..(x.toFloat() + WIDTH)) && mouseY.toFloat() in (y.toFloat()..(y.toFloat() + HEIGHT))
}
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
updateScrollingState(mouseX, mouseY)
if (scrolling) {
focused = getEntryAtPosition(mouseX, mouseY)
isDragging = true
}
return super.mouseClicked(mouseX, mouseY, button)
}
override fun mouseDragged(mouseX: Double, mouseY: Double, button: Int, deltaX: Double, deltaY: Double): Boolean {
if (scrolling) {
if (mouseY < top) {
scrollAmount = 0.0
} else if (mouseY > bottom) {
scrollAmount = maxScroll.toDouble()
} else {
scrollAmount += deltaY
}
}
return super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)
}
private fun updateScrollingState(mouseX: Double, mouseY: Double) {
scrolling = mouseX >= this.scrollbarPositionX.toDouble()
&& mouseX < (this.scrollbarPositionX + 3).toDouble()
&& mouseY >= top
&& mouseY < bottom
}
class TMScrollingListEntry(val tm: TechnicalMachine, private val parent: TMMHandledScreen) : Entry<TMScrollingListEntry>() {
private val tmButton: TMListingButton = TMListingButton(
pX = 0,
pY = 0,
onPress = {
parent.selectedTM = tm
parent.inventory.player.playSound(CobblemonSounds.GUI_CLICK, 1f, 1f)
},
tm = tm
)
override fun render(
context: DrawContext,
index: Int,
rowTop: Int,
rowLeft: Int,
rowWidth: Int,
rowHeight: Int,
mouseX: Int,
mouseY: Int,
isHovered: Boolean,
tickDelta: Float
) {
val x = rowLeft
val y = rowTop
val matrixStack = context.matrices
// Render just this TMListingButton
tmButton.setPosition(x + 1, y)
tmButton.render(context, mouseX, mouseY, tickDelta)
}
override fun mouseClicked(mouseX: Double, mouseY: Double, delta: Int): Boolean {
if (tmButton.isHovered(mouseX, mouseY)) {
tmButton.onPress()
return true
}
return false
}
override fun getNarration(): Text {
TODO("Not yet implemented")
}
}
}
@@ -0,0 +1,69 @@
package com.cobblemon.mod.common.client.gui.tm
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.types.ElementalType
import com.cobblemon.mod.common.client.gui.TypeIcon
import com.cobblemon.mod.common.client.gui.TypeReturnIcon
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.client.gui.DrawContext
import net.minecraft.client.gui.widget.ButtonWidget
import net.minecraft.client.sound.SoundInstance
import net.minecraft.client.sound.SoundManager
class TypeButton(
pX: Int, pY: Int,
onPress: PressAction,
val type: ElementalType?
): ButtonWidget(pX, pY, WIDTH.toInt(), HEIGHT.toInt(), type?.displayName, onPress, DEFAULT_NARRATION_SUPPLIER) {
companion object {
private const val WIDTH = 24F
private const val HEIGHT = 24F
val TYPE_BUTTON = cobblemonResource("textures/gui/tm/type_button.png")
}
override fun renderButton(context: DrawContext, pMouseX: Int, pMouseY: Int, pPartialTicks: Float) {
blitk(
matrixStack = context.matrices,
texture = TYPE_BUTTON,
x = x,
y = y,
width = WIDTH,
height = HEIGHT,
vOffset = if (isHovered(pMouseX.toDouble(), pMouseY.toDouble())) HEIGHT else 0,
textureHeight = HEIGHT * 2
)
context.matrices.push()
context.matrices.scale(2f, 2f, 2f)
if (type != null) {
TypeIcon(
x = (x + 12.125) / 2,
y = (y + 3) / 2,
type = type,
centeredX = true,
small = true
).render(context)
}
else {
TypeReturnIcon(
x = (x + 12.125) / 2,
y = (y + 3) / 2,
centeredX = true,
small = true
).render(context)
}
context.matrices.pop()
}
override fun playDownSound(soundManager: SoundManager) {
}
fun isHovered(mouseX: Double, mouseY: Double) = mouseX.toFloat() in (x.toFloat()..(x.toFloat() + WIDTH)) && mouseY.toFloat() in (y.toFloat()..(y.toFloat() + HEIGHT))
}
@@ -40,7 +40,8 @@ object SpawnSnowstormEntityParticleHandler : ClientNetworkPacketHandler<SpawnSno
runtime = particleRuntime,
sourceVelocity = { entity.velocity },
sourceAlive = { !entity.isRemoved },
sourceVisible = { !entity.isInvisible }
sourceVisible = { !entity.isInvisible },
entity = entity
)
storm.spawn()
@@ -0,0 +1,30 @@
package com.cobblemon.mod.common.client.net.gui
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.net.ServerNetworkPacketHandler
import com.cobblemon.mod.common.gui.TMMScreenHandler
import com.cobblemon.mod.common.net.messages.client.ui.CraftBlankTMPacket
import com.cobblemon.mod.common.util.playSoundServer
import net.minecraft.item.Items
import net.minecraft.server.MinecraftServer
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.sound.SoundCategory
object CraftBlankTMPacketHandler : ServerNetworkPacketHandler<CraftBlankTMPacket> {
override fun handle(packet: CraftBlankTMPacket, server: MinecraftServer, player: ServerPlayerEntity) {
val screen = player.currentScreenHandler as TMMScreenHandler
val ingredientSlot = screen.input.getStack(2)
if (ingredientSlot.isOf(Items.AMETHYST_SHARD) && ingredientSlot.count >= 1) {
screen.input.removeStack(2, 1)
screen.result.setStack(0, CobblemonItems.BLANK_TM.defaultStack)
screen.input.markDirty()
screen.result.markDirty()
player.currentScreenHandler.syncState()
player.world.playSoundServer(player.pos, CobblemonSounds.TMM_CRAFT_BLANK, SoundCategory.BLOCKS)
}
}
}
@@ -0,0 +1,60 @@
package com.cobblemon.mod.common.client.net.gui
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.net.ServerNetworkPacketHandler
import com.cobblemon.mod.common.api.types.ElementalTypes
import com.cobblemon.mod.common.gui.TMMScreenHandler
import com.cobblemon.mod.common.item.TechnicalMachineItem
import com.cobblemon.mod.common.net.messages.client.ui.CraftTMPacket
import com.cobblemon.mod.common.util.playSoundServer
import net.minecraft.item.Items
import net.minecraft.registry.Registries
import net.minecraft.server.MinecraftServer
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.sound.SoundCategory
object CraftTMPacketHandler : ServerNetworkPacketHandler<CraftTMPacket> {
override fun handle(packet: CraftTMPacket, server: MinecraftServer, player: ServerPlayerEntity) {
val screen = player.currentScreenHandler as TMMScreenHandler
val discSlot = screen.inventory?.getStack(0)
val gemSlot = screen.inventory?.getStack(1)
val ingredientSlot = screen.inventory?.getStack(2)
val outputSlot = screen.result.getStack(0)
val typeGem = Registries.ITEM.get(ElementalTypes.get(packet.tm.type)?.typeGem)
print("test")
if (!outputSlot.isEmpty) return
if (discSlot != null) {
if (!discSlot.isOf(CobblemonItems.BLANK_TM)) return
}
if (gemSlot != null) {
if (!gemSlot.isOf(typeGem)) return
}
if (packet.tm.recipe != null) {
if (ingredientSlot != null) {
if (!ingredientSlot.isOf(Registries.ITEM.get(packet.tm.recipe.item))) return
}
if (ingredientSlot != null) {
if (ingredientSlot.count < packet.tm.recipe.count) return
}
}
val item = CobblemonItems.TECHNICAL_MACHINE.defaultStack
item.getOrCreateNbt().putString(TechnicalMachineItem.STORED_MOVE_KEY, packet.tm.id().toString())
screen.result.setStack(0, item)
screen.inventory?.removeStack(0, 1)
screen.inventory?.removeStack(1, 1)
if (packet.tm.recipe != null) {
screen.inventory?.removeStack(2, packet.tm.recipe.count)
}
screen.input.markDirty()
screen.result.markDirty()
player.currentScreenHandler.syncState()
player.world.playSoundServer(player.pos, CobblemonSounds.TMM_CRAFT, SoundCategory.BLOCKS)
}
}
@@ -0,0 +1,13 @@
package com.cobblemon.mod.common.client.net.gui
import com.cobblemon.mod.common.api.net.ClientNetworkPacketHandler
import com.cobblemon.mod.common.net.messages.client.ui.InteractPokemonUIPacket
import com.cobblemon.mod.common.net.messages.client.ui.OpenTMMPacket
import net.minecraft.client.MinecraftClient
object OpenTMMPacketHandler : ClientNetworkPacketHandler<OpenTMMPacket>
{
override fun handle(packet: OpenTMMPacket, client: MinecraftClient) {
//client.setScreen()
}
}
@@ -10,6 +10,7 @@ package com.cobblemon.mod.common.client.particle
import com.bedrockk.molang.runtime.MoLangRuntime
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.getQueryStruct
import com.cobblemon.mod.common.api.snowstorm.BedrockParticleEffect
import com.cobblemon.mod.common.api.snowstorm.ParticleEmitterAction
import com.cobblemon.mod.common.client.render.MatrixWrapper
@@ -42,6 +43,11 @@ class ParticleStorm(
): NoRenderParticle(world, matrixWrapper.getOrigin().x, matrixWrapper.getOrigin().y, matrixWrapper.getOrigin().z) {
fun spawn() {
if (entity != null) {
runtime.environment.getQueryStruct()
.addFunction("entity_width") { DoubleValue(entity.boundingBox.xLength) }
.addFunction("entity_height") { DoubleValue(entity.boundingBox.yLength) }
.addFunction("entity_size") { DoubleValue(entity.boundingBox.run { if (xLength > yLength) xLength else yLength }) }
.addFunction("entity_radius") { DoubleValue(entity.boundingBox.run { if (xLength > yLength) xLength else yLength } / 2) }
// TODO replace with a generified call to if (entity is MoLangEntity) entity.applyVariables(env) or w/e
runtime.environment.setSimpleVariable("entity_width", DoubleValue(entity.boundingBox.xLength))
runtime.environment.setSimpleVariable("entity_height", DoubleValue(entity.boundingBox.yLength))
@@ -67,6 +73,8 @@ class ParticleStorm(
// The idea is that some instantaneous particle effects could teeeechnically be over before they start.
var hasPlayedOnce = false
var distanceTravelled = 0F
companion object {
var contextStorm: ParticleStorm? = null
}
@@ -75,6 +83,7 @@ class ParticleStorm(
init {
runtime.execute(effect.emitter.startExpressions)
effect.emitter.creationEvents.forEach { it.trigger(this, null) }
}
override fun getMaxAge(): Int {
@@ -84,6 +93,7 @@ class ParticleStorm(
override fun markDead() {
super.markDead()
if (!despawned) {
effect.emitter.expirationEvents.forEach { it.trigger(this, null) }
despawned = true
onDespawn()
}
@@ -116,6 +126,12 @@ class ParticleStorm(
y = pos.y
z = pos.z
val oldDistanceTravelled = distanceTravelled
distanceTravelled += Vec3d(x - prevPosX, y - prevPosY, z - prevPosZ).length().toFloat()
effect.emitter.travelDistanceEvents.check(this, null, oldDistanceTravelled.toDouble(), distanceTravelled.toDouble())
effect.emitter.loopingTravelDistanceEvents.forEach { it.check(this, null, oldDistanceTravelled.toDouble(), distanceTravelled.toDouble()) }
effect.emitter.eventTimeline.check(this, null, (age - 1) / 20.0, age / 20.0)
runtime.environment.setSimpleVariable("emitter_random_1", DoubleValue(Random.Default.nextDouble()))
runtime.environment.setSimpleVariable("emitter_random_2", DoubleValue(Random.Default.nextDouble()))
@@ -137,18 +153,27 @@ class ParticleStorm(
}
}
fun spawnParticle() {
fun getNextParticleSpawnPosition(): Vec3d {
runtime.environment.setSimpleVariable("particle_random_1", DoubleValue(Random.Default.nextDouble()))
runtime.environment.setSimpleVariable("particle_random_2", DoubleValue(Random.Default.nextDouble()))
runtime.environment.setSimpleVariable("particle_random_3", DoubleValue(Random.Default.nextDouble()))
runtime.environment.setSimpleVariable("particle_random_4", DoubleValue(Random.Default.nextDouble()))
val center = transformPosition(effect.emitter.shape.getCenter(runtime, entity))
val newPosition = transformPosition(effect.emitter.shape.getNewParticlePosition(runtime, entity))
val initialVelocity = effect.particle.motion.getInitialVelocity(runtime, storm = this, particlePos = newPosition, emitterPos = center)
val velocity = initialVelocity
return newPosition
}
fun getNextParticleVelocity(nextParticlePosition: Vec3d): Vec3d {
val center = transformPosition(effect.emitter.shape.getCenter(runtime, entity))
val initialVelocity = effect.particle.motion.getInitialVelocity(runtime, storm = this, particlePos = nextParticlePosition, emitterPos = center)
return initialVelocity
.multiply(1 / 20.0)
.add(if (effect.space.localVelocity) sourceVelocity() else Vec3d.ZERO)
}
fun spawnParticle() {
val newPosition = getNextParticleSpawnPosition()
val velocity = getNextParticleVelocity(newPosition)
contextStorm = this
world.addParticle(particleEffect, newPosition.x, newPosition.y, newPosition.z, velocity.x, velocity.y, velocity.z)
@@ -89,6 +89,14 @@ class SnowstormParticle(
var viewDirection = Vec3d.ZERO
var originPos = Vec3d(storm.getX(), storm.getY(), storm.getZ())
fun getX() = x
fun getY() = y
fun getZ() = z
fun getVelocityX() = velocityX
fun getVelocityY() = velocityY
fun getVelocityZ() = velocityZ
fun getSpriteFromAtlas(): Sprite {
val atlas = MinecraftClient.getInstance().particleManager.particleAtlasTexture
@@ -118,6 +126,7 @@ class SnowstormParticle(
storm.particles.add(this)
gravityStrength = 0F
particleTextureSheet = if (invisible) NO_RENDER else PARTICLE_SHEET_TRANSLUCENT
storm.effect.particle.creationEvents.forEach { it.trigger(storm, this) }
// when (storm.effect.particle.material) {
// ParticleMaterial.ALPHA -> ParticleMaterials.ALPHA
// ParticleMaterial.OPAQUE -> ParticleMaterials.OPAQUE
@@ -245,6 +254,7 @@ class SnowstormParticle(
angularVelocity += storm.effect.particle.rotation.getAngularAcceleration(storm.runtime, angularVelocity) / 20
if (age > maxAge || storm.runtime.resolveBoolean(storm.effect.particle.killExpression)) {
storm.effect.particle.expirationEvents.forEach { it.trigger(storm, this)}
markDead()
return
} else {
@@ -285,6 +295,8 @@ class SnowstormParticle(
age++
this.move(velocityX, velocityY, velocityZ)
storm.effect.particle.timeline.check(storm, this, (age - 1) / 20.0, age / 20.0)
}
override fun move(dx: Double, dy: Double, dz: Double) {
@@ -0,0 +1,165 @@
/*
* 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.render.block
import com.cobblemon.mod.common.CobblemonBlocks
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.block.entity.DisplayCaseBlockEntity
import com.cobblemon.mod.common.client.render.models.blockbench.repository.PokemonModelRepository
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.item.PokeBallItem
import com.cobblemon.mod.common.item.PokemonItem
import net.minecraft.block.HorizontalFacingBlock
import net.minecraft.client.MinecraftClient
import net.minecraft.client.render.OverlayTexture
import net.minecraft.client.render.VertexConsumer
import net.minecraft.client.render.VertexConsumerProvider
import net.minecraft.client.render.block.entity.BlockEntityRenderer
import net.minecraft.client.render.block.entity.BlockEntityRendererFactory
import net.minecraft.client.render.model.json.ModelTransformationMode
import net.minecraft.client.util.math.MatrixStack
import net.minecraft.item.BannerItem
import net.minecraft.item.BedItem
import net.minecraft.item.Item
import net.minecraft.item.ItemStack
import net.minecraft.item.Items
import net.minecraft.nbt.NbtInt
import net.minecraft.util.math.Direction
import net.minecraft.util.math.RotationAxis
import net.minecraft.world.World
class DisplayCaseRenderer(ctx: BlockEntityRendererFactory.Context) : BlockEntityRenderer<DisplayCaseBlockEntity> {
val coinPouchStack: ItemStack by lazy { ItemStack(CobblemonItems.RELIC_COIN_POUCH).also { it.setSubNbt("CustomModelData", NbtInt.of(1)) } }
override fun render(
entity: DisplayCaseBlockEntity,
tickDelta: Float,
matrices: MatrixStack,
vertexConsumers: VertexConsumerProvider,
light: Int,
overlay: Int
) {
val stack: ItemStack = if (entity.getStack().isOf(CobblemonItems.RELIC_COIN_POUCH)) {
coinPouchStack
} else {
entity.getStack()
}
val world = entity.world ?: return
val posType = getPositioningType(stack, world)
val blockState = if (entity.world != null) entity.cachedState
else (CobblemonBlocks.DISPLAY_CASE.defaultState.with(HorizontalFacingBlock.FACING, Direction.NORTH))
val yRot = if (posType == PositioningType.ITEM_MODEL) blockState.get(HorizontalFacingBlock.FACING).opposite.asRotation()
else blockState.get(HorizontalFacingBlock.FACING).asRotation()
if (stack.item is PokemonItem) {
renderPokemon(
matrices,
vertexConsumers,
light,
stack,
yRot
)
return
}
matrices.push()
matrices.translate(0.5f, 0.4f, 0.5f)
matrices.scale(posType.scaleX, posType.scaleY, posType.scaleZ)
matrices.translate(posType.transX, posType.transY, posType.transZ)
matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(-yRot))
matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(posType.rotY))
MinecraftClient.getInstance().itemRenderer.renderItem(
stack,
ModelTransformationMode.GROUND,
light,
overlay,
matrices,
vertexConsumers,
entity.world,
0
)
matrices.pop()
}
private fun renderPokemon(
matrices: MatrixStack,
vertexConsumers: VertexConsumerProvider,
light: Int,
stack: ItemStack,
yRot: Float
) {
val item = stack.item as? PokemonItem ?: return
val pokemon = item.asPokemon(stack) ?: return
val model = PokemonModelRepository.getPoser(pokemon.species.resourceIdentifier, pokemon.aspects)
val renderLayer = model.getLayer(PokemonModelRepository.getTexture(pokemon.species.resourceIdentifier, pokemon.aspects, 0F))
val tint = item.tint(stack)
val vertexConsumer: VertexConsumer = vertexConsumers.getBuffer(renderLayer)
val scale = 0.25f
matrices.push()
matrices.scale(1f, -1f, -1f)
matrices.translate(0.5f, -0.69f, -0.5f)
matrices.scale(scale, scale, scale)
matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(yRot))
model.setupAnimStateless(PoseType.PROFILE)
model.withLayerContext(vertexConsumers, null, PokemonModelRepository.getLayers(pokemon.species.resourceIdentifier, pokemon.aspects)) {
model.render(matrices, vertexConsumer, light, OverlayTexture.DEFAULT_UV, tint.x, tint.y, tint.z, tint.w)
}
matrices.pop()
}
companion object {
private val mobHeads = listOf<Item>(
Items.SKELETON_SKULL,
Items.WITHER_SKELETON_SKULL,
Items.ZOMBIE_HEAD,
Items.PIGLIN_HEAD,
Items.PLAYER_HEAD,
Items.DRAGON_HEAD,
Items.CREEPER_HEAD
)
private fun getPositioningType(stack: ItemStack, world: World) = when {
mobHeads.contains(stack.item) -> PositioningType.MOB_HEAD
stack.item is BedItem -> PositioningType.BED
stack.item is BannerItem -> PositioningType.BANNER
stack.item is PokeBallItem -> PositioningType.POKE_BALL
stack.item == CobblemonItems.RELIC_COIN_POUCH -> PositioningType.COIN_POUCH
stack.item == CobblemonItems.PASTURE -> PositioningType.PASTURE
stack.item == CobblemonItems.POKEMON_MODEL -> PositioningType.ITEM_MODEL
stack.item == Items.SHIELD -> PositioningType.SHIELD
stack.item == Items.DECORATED_POT -> PositioningType.MOB_HEAD
MinecraftClient.getInstance().itemRenderer.getModel(stack, world, null, 0).hasDepth() -> PositioningType.BLOCK_MODEL
else -> PositioningType.ITEM_MODEL
}
}
private enum class PositioningType(
val scaleX: Float, val scaleY: Float, val scaleZ: Float,
val transX: Float, val transY: Float, val transZ: Float,
val rotY: Float = 0f
) {
POKE_BALL(1f, 1f, 1f, 0f, 0.04f, 0f),
BLOCK_MODEL(1f, 1f, 1f, 0f, -0.15f, 0f),
ITEM_MODEL(1f, 1f, 1f, 0f, 0.04f, 0f),
BED(1f, 1f, 1f, 0f, -0.02f, 0f),
BANNER(1f, 1f, 1f, 0f, -0.02f, 0f, 180f),
MOB_HEAD(1f, 1f, 1f, 0f, -0.025f, 0f, 180f),
SHIELD(1f, 1f, 1f, 0f, -0.045f, 0f, 180f),
PASTURE(1f, 1f, 1f, 0f, 0.0375f, 0f),
COIN_POUCH(1f, 1f, 1f, 0f, 0.415f, 0f)
}
}
@@ -19,8 +19,10 @@ import net.minecraft.client.render.block.entity.BlockEntityRenderer
import net.minecraft.client.render.block.entity.BlockEntityRendererFactory
import net.minecraft.client.render.model.json.ModelTransformationMode
import net.minecraft.client.util.math.MatrixStack
import net.minecraft.state.property.DirectionProperty
import net.minecraft.util.math.Direction
import net.minecraft.util.math.RotationAxis
import net.minecraft.util.math.Vec3d
class FossilAnalyzerRenderer(ctx: BlockEntityRendererFactory.Context) : BlockEntityRenderer<FossilAnalyzerBlockEntity> {
@@ -34,21 +36,29 @@ class FossilAnalyzerRenderer(ctx: BlockEntityRendererFactory.Context) : BlockEnt
) {
val blockState = if (entity.world != null) entity.cachedState
else (CobblemonBlocks.FOSSIL_ANALYZER.defaultState.with(HorizontalFacingBlock.FACING, Direction.SOUTH) as BlockState)
val yRot = blockState.get(HorizontalFacingBlock.FACING).asRotation()
//We shouldnt have to do any complex rendering when the block isn't a multiblock
// We shouldn't have to do any complex rendering when the block isn't a multiblock
if (entity.multiblockStructure == null) {
return
}
val direction = blockState.get(HorizontalFacingBlock.FACING)
val yRot = direction.asRotation() + if(direction == Direction.WEST || direction == Direction.EAST) 180F else 0F
val struct = entity.multiblockStructure as FossilMultiblockStructure
struct.fossilInventory.forEachIndexed { index, fossilStack ->
matrices.push()
matrices.translate(0.5, 0.4 + (index * 0.1F), 0.55)
matrices.scale(0.7F, 0.7F, 0.7F)
val dirOffset = when (direction) {
Direction.NORTH -> Vec3d(0.0, 0.0, 0.05)
Direction.SOUTH -> Vec3d(0.0, 0.0, -0.05)
Direction.EAST -> Vec3d(-0.05, 0.0, 0.0)
Direction.WEST -> Vec3d(0.05, 0.0, 0.0)
else -> Vec3d.ZERO
}
matrices.translate(0.5 + dirOffset.x,0.4 + (index * 0.1) + dirOffset.y, 0.5 + dirOffset.z)
matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(yRot))
matrices.multiply(RotationAxis.POSITIVE_Z.rotationDegrees(180F))
matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(-yRot))
matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees(90F))
matrices.scale(0.7F, 0.7F, 0.7F)
MinecraftClient.getInstance().itemRenderer.renderItem(fossilStack, ModelTransformationMode.NONE, light, overlay, matrices, vertexConsumers, entity.world, 0)
@@ -0,0 +1,78 @@
package com.cobblemon.mod.common.client.render.block
import com.cobblemon.mod.common.api.text.blue
import com.cobblemon.mod.common.api.text.green
import com.cobblemon.mod.common.api.text.red
import com.cobblemon.mod.common.api.tms.TechnicalMachines
import com.cobblemon.mod.common.api.types.ElementalTypes
import com.cobblemon.mod.common.block.TMBlock
import com.cobblemon.mod.common.block.entity.TMBlockEntity
import com.cobblemon.mod.common.client.CobblemonBakingOverrides
import com.cobblemon.mod.common.client.render.layer.CobblemonRenderLayers
import com.cobblemon.mod.common.client.render.models.blockbench.repository.MiscModelRepository
import com.cobblemon.mod.common.client.render.models.blockbench.setRotation
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.math.geometry.Axis
import net.minecraft.client.MinecraftClient
import net.minecraft.client.render.LightmapTextureManager
import net.minecraft.client.render.OverlayTexture
import net.minecraft.client.render.RenderLayer
import net.minecraft.client.render.VertexConsumerProvider
import net.minecraft.client.render.block.entity.BlockEntityRenderer
import net.minecraft.client.render.block.entity.BlockEntityRendererFactory
import net.minecraft.client.util.math.MatrixStack
import net.minecraft.util.math.Direction
import net.minecraft.util.math.RotationAxis
import java.awt.Color
import kotlin.math.sin
class TMBlockRenderer(context: BlockEntityRendererFactory.Context) : BlockEntityRenderer<TMBlockEntity> {
//TODO Do the render optimizations that we do for berries and fossils
override fun render(
entity: TMBlockEntity,
tickDelta: Float,
matrices: MatrixStack,
vertexConsumers: VertexConsumerProvider,
light: Int,
overlay: Int
) {
val diskModel = MiscModelRepository.modelOf(MODEL_ID)
matrices.push()
val tm = entity.tmmInventory.filterTM?.let {
TechnicalMachines.getTechnicalMachineFromStack(
it
)
}
entity.partialTicks += tickDelta
val color = tm?.let { Color(ElementalTypes.get(it.type)!!.hue) } ?: Color.WHITE
when (entity.cachedState.get(TMBlock.FACING)) {
Direction.SOUTH -> matrices.translate(15F / 16F, 5.5F / 16F, 1F / 16F)
Direction.WEST -> matrices.translate(14F / 16F, 5.5F / 16F, 0F)
Direction.EAST -> matrices.translate(1F, 5.5F / 16F, 0F)
else -> matrices.translate(15F / 16F, 5.5F / 16F, -1F / 16F)
}
matrices.translate(-7.0/16f, 0.0, 8.0/16f)
matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees( entity.partialTicks / 0.5f))
matrices.translate(7.0/16f, 0.0, -8.0/16f)
val renderLayer = RenderLayer.getEntityCutout(cobblemonResource("textures/block/tm_machine.png"))
diskModel?.render(
matrices,
vertexConsumers.getBuffer(renderLayer),
light,
overlay,
color.red.toFloat() / 255F,
color.green.toFloat() / 255F,
color.blue.toFloat() / 255F,
1.0F
)
matrices.pop()
}
companion object {
val MODEL_ID = cobblemonResource("tm_disk.geo")
}
}
@@ -40,6 +40,27 @@ object CobblemonRenderLayers {
multiPhaseParameters
)
}
val TM_BLOCK_LAYER = run {
val multiPhaseParameters = RenderLayer.MultiPhaseParameters.builder()
.lightmap(ENABLE_LIGHTMAP)
.program(CUTOUT_PROGRAM)
.texture(Texture(
cobblemonResource("textures/misc/tm_machine"),
false,
true
))
.cull(DISABLE_CULLING)
.build(true)
RenderLayer.of(
"tm_block",
VertexFormats.POSITION_COLOR_TEXTURE_LIGHT_NORMAL,
VertexFormat.DrawMode.QUADS,
512,
true,
false,
multiPhaseParameters
)
}
val ENTITY_TRANSLUCENT: BiFunction<Identifier, Boolean, RenderLayer> = Util.memoize { texture: Identifier, affectsOutline: kotlin.Boolean ->
var multiPhaseParameters: RenderLayer.MultiPhaseParameters =
@@ -268,7 +268,7 @@ abstract class PoseableEntityState<T : Entity> : Schedulable {
val pose = currentPose?.let(model::getPose)
allStatefulAnimations.forEach { it.applyEffects(entity, this, previousSeconds, currentSeconds) }
primaryAnimation?.animation?.applyEffects(entity, this, previousSeconds, currentSeconds)
pose?.idleAnimations?.filter { shouldIdleRun(it, 0.5F) }
pose?.idleAnimations?.filter { shouldIdleRun(it, 0.5F) }?.forEach { it.applyEffects(entity, this, previousSeconds, currentSeconds) }
}
}
@@ -10,6 +10,7 @@ package com.cobblemon.mod.common.command
import com.cobblemon.mod.common.CobblemonNetwork.sendPacket
import com.cobblemon.mod.common.api.permission.CobblemonPermissions
import com.cobblemon.mod.common.net.messages.client.effect.SpawnSnowstormEntityParticlePacket
import com.cobblemon.mod.common.net.messages.client.effect.SpawnSnowstormParticlePacket
import com.cobblemon.mod.common.util.alias
import com.cobblemon.mod.common.util.distanceTo
@@ -17,10 +18,12 @@ import com.cobblemon.mod.common.util.permission
import com.cobblemon.mod.common.util.toBlockPos
import com.mojang.brigadier.Command
import com.mojang.brigadier.CommandDispatcher
import com.mojang.brigadier.arguments.StringArgumentType
import net.minecraft.command.argument.DimensionArgumentType
import net.minecraft.command.argument.EntityArgumentType
import net.minecraft.command.argument.IdentifierArgumentType
import net.minecraft.command.argument.Vec3ArgumentType
import net.minecraft.entity.Entity
import net.minecraft.server.command.CommandManager
import net.minecraft.server.command.ServerCommandSource
import net.minecraft.server.world.ServerWorld
@@ -40,6 +43,16 @@ object BedrockParticleCommand {
val entities = EntityArgumentType.getEntities(it, "target")
return@executes entities.sumOf { entity -> execute(it.source, effectId, entity.world as ServerWorld, entity.pos) }
}
.then(
CommandManager.argument("locator", StringArgumentType.word())
.executes {
val effectId = IdentifierArgumentType.getIdentifier(it, "effect")
val entities = EntityArgumentType.getEntities(it, "target")
val locator = StringArgumentType.getString(it, "locator")
return@executes entities.sumOf { entity -> execute(it.source, effectId, entity.world as ServerWorld, entity, locator) }
}
)
)
.then(
CommandManager.argument("world", DimensionArgumentType.dimension())
@@ -60,9 +73,16 @@ object BedrockParticleCommand {
private fun execute(source: ServerCommandSource, effectId: Identifier, world: ServerWorld, target: Vec3d): Int {
val pos = target.toBlockPos()
val nearbyPlayers = world.getPlayers { it.distanceTo(pos) < 200 }
val nearbyPlayers = world.getPlayers { it.distanceTo(pos) < 1000 }
nearbyPlayers.forEach { player -> player.sendPacket(SpawnSnowstormParticlePacket(effectId, target)) }
return Command.SINGLE_SUCCESS
}
private fun execute(source: ServerCommandSource, effectId: Identifier, world: ServerWorld, target: Entity, locator: String): Int {
val pos = target.blockPos
val nearbyPlayers = world.getPlayers { it.distanceTo(pos) < 1000 }
nearbyPlayers.forEach { player -> player.sendPacket(SpawnSnowstormEntityParticlePacket(effectId, target.id, locator)) }
return Command.SINGLE_SUCCESS
}
}
@@ -11,21 +11,27 @@ package com.cobblemon.mod.common.command
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonEntities
import com.cobblemon.mod.common.CobblemonNetwork.sendPacket
import com.cobblemon.mod.common.api.Priority
import com.cobblemon.mod.common.api.abilities.Abilities
import com.cobblemon.mod.common.api.item.ability.AbilityChanger
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.scheduling.ServerTaskTracker
import com.cobblemon.mod.common.api.scheduling.taskBuilder
import com.cobblemon.mod.common.api.text.green
import com.cobblemon.mod.common.api.text.red
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.api.tms.TechnicalMachine
import com.cobblemon.mod.common.api.tms.TechnicalMachines
import com.cobblemon.mod.common.battles.BattleFormat
import com.cobblemon.mod.common.battles.BattleRegistry
import com.cobblemon.mod.common.battles.BattleSide
import com.cobblemon.mod.common.battles.actor.PlayerBattleActor
import com.cobblemon.mod.common.battles.actor.PokemonBattleActor
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.net.messages.client.effect.SpawnSnowstormParticlePacket
import com.cobblemon.mod.common.net.messages.client.trade.TradeStartedPacket
import com.cobblemon.mod.common.particle.SnowstormParticleReader
import com.cobblemon.mod.common.trade.ActiveTrade
import com.cobblemon.mod.common.trade.DummyTradeParticipant
import com.cobblemon.mod.common.trade.PlayerTradeParticipant
import com.cobblemon.mod.common.util.fromJson
import com.cobblemon.mod.common.util.party
import com.cobblemon.mod.common.util.toPokemon
import com.google.gson.GsonBuilder
@@ -57,7 +63,12 @@ object TestCommand {
return Command.SINGLE_SUCCESS
}
for (id in Cobblemon.playerData.get(context.source.playerOrThrow).tmSet) {
context.source.player?.sendMessage(TechnicalMachines.tmMap[id]?.translatedMoveName())
}
try {
filter(context)
// readBerryDataFromCSV()
// this.testClosestBattle(context)
@@ -105,6 +116,12 @@ object TestCommand {
var trade: ActiveTrade? = null
var lastDebugId = 0
private fun filter(context: CommandContext<ServerCommandSource>) {
val tms = TechnicalMachine.filterTms("fire", null, null)
println(context.source.playerOrThrow.party().get(1)?.getDisplayName())
tms.forEach { context.source.playerOrThrow.sendMessage(it.moveName.text()) }
}
private fun testClosestBattle(context: CommandContext<ServerCommandSource>) {
val player = context.source.playerOrThrow
val cloneTeam = player.party().toBattleTeam(true)
@@ -292,4 +309,82 @@ object TestCommand {
// }
// }
// }
private fun testAbilitiesBetweenEvolution(context: CommandContext<ServerCommandSource>) {
val results = Text.literal("Ability test results (Assumed default assets)")
.append(Text.literal("\n"))
.append(this.testHiddenAbilityThroughoutEvolutions())
.append(Text.literal("\n"))
.append(this.testMiddleStageSingleAbility())
.append(Text.literal("\n"))
.append(this.testForcedAbility())
.append(Text.literal("\n"))
.append(this.testIllegalAbilityNonForced())
.append(Text.literal("\n"))
.append(this.testAbilityCapsule())
.append(Text.literal("\n"))
.append(this.testAbilityPatch())
context.source.sendMessage(results)
}
private fun testHiddenAbilityThroughoutEvolutions(): Text {
// Hidden ability test, Dragonite HA differs from Dratini/Dragonair we need to ensure he keeps that ability until the end
// Skip Dratini cause same HA irrelevant for this test
val pokemon = PokemonProperties.parse("dragonair level=${Cobblemon.config.maxPokemonLevel} hiddenability=true").create()
val dragonite = pokemon.evolutions.firstOrNull() ?: return Text.literal("✖ Failed to find Dragonair » Dragonite evolution").red()
dragonite.evolutionMethod(pokemon)
val failed = pokemon.ability.index != 0 || pokemon.ability.priority != Priority.LOW || pokemon.ability.forced
val symbol = if (failed) "✖" else "✔"
val result = Text.literal(" $symbol Dratini line final Ability(name=${pokemon.ability.name}, priority=${pokemon.ability.priority}, index=${pokemon.ability.index}, forced=${pokemon.ability.forced})")
return if (failed) result.red() else result.green()
}
private fun testMiddleStageSingleAbility(): Text {
val pokemon = PokemonProperties.parse("scatterbug level=${Cobblemon.config.maxPokemonLevel} ability=compoundeyes").create()
val spewpa = pokemon.evolutions.firstOrNull() ?: return Text.literal("✖ Failed to find Scatterbug » Spewpa evolution").red()
spewpa.evolutionMethod(pokemon)
val vivillon = pokemon.evolutions.firstOrNull() ?: return Text.literal("✖ Failed to find Spewpa » Vivillon evolution").red()
vivillon.evolutionMethod(pokemon)
val failed = pokemon.ability.index != 1 || pokemon.ability.priority != Priority.LOWEST || pokemon.ability.forced
val symbol = if (failed) "✖" else "✔"
val result = Text.literal(" $symbol Scatterbug line final Ability(name=${pokemon.ability.name}, priority=${pokemon.ability.priority}, index=${pokemon.ability.index}, forced=${pokemon.ability.forced})")
return if (failed) result.red() else result.green()
}
private fun testForcedAbility(): Text {
val pokemon = PokemonProperties.parse("magikarp level=${Cobblemon.config.maxPokemonLevel} ability=adaptability").create()
val gyarados = pokemon.evolutions.firstOrNull() ?: return Text.literal("✖ Failed to find Magikarp » Gyarados evolution").red()
gyarados.evolutionMethod(pokemon)
val failed = !pokemon.ability.forced || pokemon.ability.template.name != "adaptability"
val symbol = if (failed) "✖" else "✔"
val result = Text.literal(" $symbol Magikarp line forced Ability(name=${pokemon.ability.name}, priority=${pokemon.ability.priority}, index=${pokemon.ability.index}, forced=${pokemon.ability.forced})")
return if (failed) result.red() else result.green()
}
private fun testIllegalAbilityNonForced(): Text {
val pokemon = PokemonProperties.parse("rattata").create()
pokemon.updateAbility(Abilities.getOrException("adaptability").create(false))
val failed = !pokemon.ability.forced
val symbol = if (failed) "✖" else "✔"
val result = Text.literal(" $symbol Rattata illegal non-forced (name=${pokemon.ability.name}, priority=${pokemon.ability.priority}, index=${pokemon.ability.index}, forced=${pokemon.ability.forced})")
return if (failed) result.red() else result.green()
}
private fun testAbilityCapsule(): Text {
val pokemon = PokemonProperties.parse("rattata").create()
val failed = !AbilityChanger.COMMON_ABILITY.performChange(pokemon)
val symbol = if (failed) "✖" else "✔"
val result = Text.literal(" $symbol Rattata capsule Ability(name=${pokemon.ability.name}, priority=${pokemon.ability.priority}, index=${pokemon.ability.index}, forced=${pokemon.ability.forced})")
return if (failed) result.red() else result.green()
}
private fun testAbilityPatch(): Text {
val pokemon = PokemonProperties.parse("magikarp ha=true").create()
// It shouldn't change
val failed = AbilityChanger.HIDDEN_ABILITY.performChange(pokemon)
val symbol = if (failed) "✖" else "✔"
val result = Text.literal(" $symbol Magikarp patch Ability(name=${pokemon.ability.name}, priority=${pokemon.ability.priority}, index=${pokemon.ability.index}, forced=${pokemon.ability.forced})")
return if (failed) result.red() else result.green()
}
}
@@ -86,9 +86,6 @@ class CobblemonConfig {
@NodeCategory(Category.Spawning)
var maxNearbyBlocksVerticalRange = 2
@NodeCategory(Category.Spawning)
var maxHorizontalSpace = 6
@NodeCategory(Category.Spawning)
var maxVerticalSpace = 8
@@ -29,6 +29,7 @@ import com.cobblemon.mod.common.api.pokemon.feature.SpeciesFeatures
import com.cobblemon.mod.common.api.spawning.CobblemonSpawnPools
import com.cobblemon.mod.common.api.spawning.CobblemonSpawnRules
import com.cobblemon.mod.common.api.spawning.SpawnDetailPresets
import com.cobblemon.mod.common.api.tms.TechnicalMachines
import com.cobblemon.mod.common.battles.BagItems
import com.cobblemon.mod.common.net.messages.client.data.UnlockReloadPacket
import com.cobblemon.mod.common.platform.events.PlatformEvents
@@ -75,6 +76,7 @@ object CobblemonDataProvider : DataProvider {
CobblemonSpawnPools.load()
this.register(Berries)
this.register(TechnicalMachines)
PlatformEvents.SERVER_PLAYER_LOGOUT.subscribe {
synchronizedPlayerIds.remove(it.player.uuid)
@@ -10,7 +10,6 @@ package com.cobblemon.mod.common.entity.pokemon
import com.cobblemon.mod.common.*
import com.cobblemon.mod.common.CobblemonNetwork.sendPacket
import com.cobblemon.mod.common.CobblemonNetwork.sendPacketToServer
import com.cobblemon.mod.common.api.drop.DropTable
import com.cobblemon.mod.common.api.entity.Despawner
import com.cobblemon.mod.common.api.events.CobblemonEvents
@@ -31,9 +30,9 @@ import com.cobblemon.mod.common.api.storage.InvalidSpeciesException
import com.cobblemon.mod.common.api.types.ElementalTypes
import com.cobblemon.mod.common.api.types.ElementalTypes.FIRE
import com.cobblemon.mod.common.battles.BagItems
import com.cobblemon.mod.common.battles.BattleBuilder
import com.cobblemon.mod.common.battles.BattleRegistry
import com.cobblemon.mod.common.block.entity.PokemonPastureBlockEntity
import com.cobblemon.mod.common.client.CobblemonClient
import com.cobblemon.mod.common.client.entity.PokemonClientDelegate
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.Poseable
@@ -46,7 +45,6 @@ import com.cobblemon.mod.common.net.messages.client.animation.PlayPoseableAnimat
import com.cobblemon.mod.common.net.messages.client.sound.UnvalidatedPlaySoundS2CPacket
import com.cobblemon.mod.common.net.messages.client.spawn.SpawnPokemonPacket
import com.cobblemon.mod.common.net.messages.client.ui.InteractPokemonUIPacket
import com.cobblemon.mod.common.net.messages.server.BattleChallengePacket
import com.cobblemon.mod.common.net.serverhandling.storage.SendOutPokemonHandler.SEND_OUT_DURATION
import com.cobblemon.mod.common.pokemon.FormData
import com.cobblemon.mod.common.pokemon.Pokemon
@@ -1046,13 +1044,13 @@ open class PokemonEntity(
* @param player The player to attempt a battle with.
* @return Whether the battle was successfully started.
*/
fun forceBattle(player: PlayerEntity): Boolean {
if (!canBattle(player)) return false
if (CobblemonClient.storage.myParty.isEmpty()) return false
val selectedPokemon = CobblemonClient.storage.myParty.get(CobblemonClient.storage.selectedSlot) ?: return false
if (selectedPokemon.isFainted()) return false
sendPacketToServer(BattleChallengePacket(this.id, selectedPokemon.uuid))
return true
fun forceBattle(player: ServerPlayerEntity): Boolean {
if (!canBattle(player)) {
return false
}
var isSuccessful = false
BattleBuilder.pve(player, this).ifSuccessful { isSuccessful = true }
return isSuccessful
}
/**
@@ -9,6 +9,7 @@
package com.cobblemon.mod.common.events
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.advancement.CobblemonCriteria
import com.cobblemon.mod.common.advancement.criterion.*
import com.cobblemon.mod.common.api.battles.model.actor.ActorType
@@ -17,6 +18,9 @@ import com.cobblemon.mod.common.api.events.pokemon.LevelUpEvent
import com.cobblemon.mod.common.api.events.pokemon.PokemonCapturedEvent
import com.cobblemon.mod.common.api.events.pokemon.TradeCompletedEvent
import com.cobblemon.mod.common.api.events.pokemon.evolution.EvolutionCompleteEvent
import com.cobblemon.mod.common.block.TumblestoneBlock
import com.cobblemon.mod.common.item.TumblestoneItem
import com.cobblemon.mod.common.platform.events.ServerPlayerEvent
import com.cobblemon.mod.common.util.getPlayer
import java.util.*
@@ -138,4 +142,20 @@ object AdvancementHandler {
Cobblemon.playerData.saveSingle(playerData)
}
}
/**
* Triggers the advancement for placing a tumblestone
*
* Checks if the item in the player's hand is a tumblestone.
* Then gets the block as a [TumblestoneBlock] so we can call the canGrow method.
* Finally, triggers the advancement.
*
* @param event the event to trigger the advancement from
*/
fun onTumbleStonePlaced(event: ServerPlayerEvent.RightClickBlock) {
if (event.player.getStackInHand(event.hand).item == CobblemonItems.TUMBLESTONE.asItem()) {
val block = ((event.player.getStackInHand(event.hand).item as TumblestoneItem).block as TumblestoneBlock)
CobblemonCriteria.PLANT_TUMBLESTONE.trigger(event.player, PlantTumblestoneContext(event.pos, block))
}
}
}
@@ -9,7 +9,9 @@
package com.cobblemon.mod.common.events
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.tms.TechnicalMachines
import com.cobblemon.mod.common.battles.BattleRegistry
import com.cobblemon.mod.common.item.TechnicalMachineItem
import com.cobblemon.mod.common.util.party
import net.minecraft.server.MinecraftServer
@@ -25,9 +27,21 @@ object ServerTickHandler {
if (secondsTick == 20) {
secondsTick = 0
// Party tick
// Code to run every in-game second
for (player in server.playerManager.playerList) {
player.party().onSecondPassed(player)
TechnicalMachines.checkPassives(player)
// Give player tm unlocks for tms they don't already have
repeat(player.inventory.size()) {
val stack = player.inventory.getStack(it)
if (stack.item is TechnicalMachineItem) {
val tm = TechnicalMachineItem.getMoveNbt(stack) ?: return@repeat
if (!Cobblemon.playerData.get(player).tmSet.contains(tm.id())) {
tm.unlock(player)
}
}
}
}
}
}
@@ -0,0 +1,25 @@
package com.cobblemon.mod.common.gui
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.mixin.invoker.ScreenHandlerTypeInvoker
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.registry.Registries
import net.minecraft.resource.featuretoggle.FeatureFlags
import net.minecraft.screen.ScreenHandlerType.Factory
import net.minecraft.resource.featuretoggle.FeatureSet
import net.minecraft.screen.ScreenHandler
import net.minecraft.screen.ScreenHandlerType
import net.minecraft.util.Identifier
object CobblemonScreenHandlers {
val TMM_SCREEN = register(cobblemonResource("tmm_screen"), ::TMMScreenHandler)
val MOD_STATION_SCREEN = register(cobblemonResource("mod_station_screen"), ::ModStationScreenHandler)
fun <T : ScreenHandler> register(identifier: Identifier, factory: Factory<T>): ScreenHandlerType<T> {
val result = ScreenHandlerTypeInvoker.`cobblemon$create`(factory, FeatureFlags.VANILLA_FEATURES)
Cobblemon.implementation.registerScreenHandlerType(identifier, result)
return result
}
}
@@ -0,0 +1,60 @@
package com.cobblemon.mod.common.gui
import net.minecraft.entity.player.PlayerEntity
import net.minecraft.entity.player.PlayerInventory
import net.minecraft.inventory.*
import net.minecraft.item.ItemStack
import net.minecraft.screen.ScreenHandler
import net.minecraft.screen.slot.CraftingResultSlot
import net.minecraft.screen.slot.Slot
class ModStationScreenHandler(syncId: Int) : ScreenHandler(CobblemonScreenHandlers.MOD_STATION_SCREEN, syncId) {
var playerInventory: PlayerInventory? = null
var input: RecipeInputInventory = CraftingInventory(this, 2, 1)
val result: CraftingResultInventory = CraftingResultInventory()
var inventory: Inventory? = null
constructor(syncId: Int, playerInventory: PlayerInventory) : this(syncId, playerInventory, SimpleInventory(3))
constructor(syncId: Int, playerInventory: PlayerInventory, inventory: Inventory) : this(syncId) {
val startX = 0 - 9
val startY = 112
val xLen = 18 // 18 is standard
val yLen = 18 // 18 is standard
this.playerInventory = playerInventory
this.inventory = inventory
for (row in 0..2) {
for (column in 0..8) {
this.addSlot(Slot(playerInventory, 9 + (row * 9) + column, startX + (xLen * column), startY + (yLen * row)))
}
}
for (column in 0..8) {
this.addSlot(Slot(playerInventory, column, startX + (xLen * column), startY + 58))
}
addSlot(CraftingResultSlot(
playerInventory.player,
input,
result,
0,
startX,
startY
))
this.addSlot(Slot(this.inventory, 0, startX, startY - 20))
this.addSlot(Slot(this.inventory, 1, startX, startY - 40))
}
override fun quickMove(player: PlayerEntity?, slot: Int): ItemStack {
TODO("Not yet implemented")
}
override fun canUse(player: PlayerEntity?): Boolean {
return inventory?.canPlayerUse(player) ?: false
}
override fun syncState() {
super.syncState()
}
}
@@ -0,0 +1,139 @@
package com.cobblemon.mod.common.gui
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.block.entity.TMBlockEntity
import net.minecraft.block.BlockState
import net.minecraft.entity.player.PlayerEntity
import net.minecraft.entity.player.PlayerInventory
import net.minecraft.inventory.*
import net.minecraft.item.ItemStack
import net.minecraft.screen.ScreenHandler
import net.minecraft.screen.slot.CraftingResultSlot
import net.minecraft.screen.slot.Slot
import net.minecraft.screen.slot.SlotActionType
import net.minecraft.util.math.BlockPos
import net.minecraft.world.World
class TMMScreenHandler(syncId: Int) : ScreenHandler(CobblemonScreenHandlers.TMM_SCREEN, syncId) {
var playerInventory: PlayerInventory? = null
var input: RecipeInputInventory = CraftingInventory(this, 3, 1)
val result: CraftingResultInventory = CraftingResultInventory()
var tmmEntity: TMBlockEntity? = null
var inventory: Inventory? = null
constructor(syncId: Int, playerInventory: PlayerInventory) : this(syncId, playerInventory, SimpleInventory(4), null) {
}
constructor(syncId: Int, playerInventory: PlayerInventory, inventory: Inventory, blockEntity: TMBlockEntity?) : this(syncId) {
val startX = 0 - 9
val startY = 112
val xLen = 18 // 18 is standard
val yLen = 18 // 18 is standard
this.playerInventory = playerInventory
this.inventory = inventory // as TMBlockEntity.TMBlockInventory) // this does grab the right inventory slots
tmmEntity = blockEntity
if (inventory is TMBlockEntity.TMBlockInventory) {
//inventory.tmBlockEntity
this.syncState()
/*this.inventory = inventory
this.tmmEntity = (inventory as TMBlockEntity.TMBlockInventory).tmBlockEntity
// sync the input slots with the TMM slots
input.setStack(0, inventory.items!!.get(0))
input.setStack(1, inventory.items!!.get(1))
input.setStack(2, inventory.items!!.get(2))
//inventory.items!![3] = result.getStack(0) // sync output of TMM with the crafted TM
result.setStack(0, inventory.items!!.get(3))*/
}
for (row in 0..2) {
for (column in 0..8) {
this.addSlot(Slot(playerInventory, 9 + (row * 9) + column, startX + (xLen * column), startY + (yLen * row)))
}
}
for (column in 0..8) {
this.addSlot(Slot(playerInventory, column, startX + (xLen * column), startY + 58))
}
this.addSlot(CraftingResultSlot(
playerInventory.player,
input,
result,
0,
startX + 123,
startY - 22
))
this.addSlot(Slot(this.inventory, 0, startX + 167, startY + 9)) // material input slot 1
this.addSlot(Slot(this.inventory, 1, startX + 185, startY + 9)) // material input slot 2
this.addSlot(Slot(this.inventory, 2, startX + 203, startY + 9)) // material input slot 3
}
override fun quickMove(player: PlayerEntity, slot: Int): ItemStack {
return if (insertItem(getSlot(slot).stack, 0, SLOT_COUNT, true)) getSlot(slot).stack else ItemStack.EMPTY
}
override fun canUse(player: PlayerEntity?): Boolean {
return inventory?.canPlayerUse(player) ?: false
}
// override fun onClosed(player: PlayerEntity) {
//// for (i in 0..2) {
//// if (!slots[i].stack.isEmpty) player.giveOrDropItemStack(slots[i].stack, false)
//// }
// super.onClosed(player)
// }
override fun onSlotClick(slotIndex: Int, button: Int, actionType: SlotActionType?, player: PlayerEntity) {
val type = if (actionType == SlotActionType.THROW) SlotActionType.PICKUP else actionType // todo issue might be inventory is not being clicked, but it is index 36 still
// if slot is the output slot then clear the TMM output slot
if (slotIndex == 36) {
this.inventory?.setStack(3, ItemStack.EMPTY)
}
super.onSlotClick(slotIndex, button, type, player)
}
override fun onClosed(player: PlayerEntity?) {
super.onClosed(player)
tmmEntity?.stateManager?.closeContainer(player, tmmEntity!!.world, tmmEntity!!.blockPos, tmmEntity!!.blockState)
}
override fun syncState() {
if (inventory is TMBlockEntity.TMBlockInventory) {
//inventory.tmBlockEntity
// sync the input slots with the TMM slots
input.setStack(0, (inventory as TMBlockEntity.TMBlockInventory).items!!.get(0))
input.setStack(1, (inventory as TMBlockEntity.TMBlockInventory).items!!.get(1))
input.setStack(2, (inventory as TMBlockEntity.TMBlockInventory).items!!.get(2))
//inventory.items!![3] = result.getStack(0) // sync output of TMM with the crafted TM
// if there is something in the output slot of this.inventory then sync that with result
if ((inventory as TMBlockEntity.TMBlockInventory).items?.get(3) != ItemStack.EMPTY) {
if (ItemStack.areItemsEqual((inventory as TMBlockEntity.TMBlockInventory).items?.get(3), ItemStack(CobblemonItems.TECHNICAL_MACHINE, 1))
|| ItemStack.areItemsEqual((inventory as TMBlockEntity.TMBlockInventory).items?.get(3), ItemStack(CobblemonItems.BLANK_TM, 1)))
result.setStack(0, (inventory as TMBlockEntity.TMBlockInventory).items!!.get(3))
}
// if inventory is empty in the output slot then sync it to result
else
(inventory as TMBlockEntity.TMBlockInventory).items?.set(3, result.getStack(0))
}
super.syncState()
}
companion object {
val SLOT_COUNT = 3
}
}
@@ -0,0 +1,175 @@
/*
* 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.item
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.moves.BenchedMove
import com.cobblemon.mod.common.api.moves.Moves
import com.cobblemon.mod.common.api.text.gray
import com.cobblemon.mod.common.api.text.green
import com.cobblemon.mod.common.api.tms.TechnicalMachine
import com.cobblemon.mod.common.api.tms.TechnicalMachines
import com.cobblemon.mod.common.api.types.ElementalTypes
import com.cobblemon.mod.common.block.entity.TMBlockEntity
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.lang
import com.cobblemon.mod.common.util.toBlockPos
import net.minecraft.block.Block
import net.minecraft.client.item.TooltipContext
import net.minecraft.entity.LivingEntity
import net.minecraft.entity.player.PlayerEntity
import net.minecraft.item.ItemStack
import net.minecraft.item.ItemUsageContext
import net.minecraft.nbt.NbtString
import net.minecraft.sound.SoundCategory
import net.minecraft.text.MutableText
import net.minecraft.text.Text
import net.minecraft.util.ActionResult
import net.minecraft.util.Hand
import net.minecraft.util.Identifier
import net.minecraft.world.World
class TechnicalMachineItem(settings: Settings): CobblemonItem(settings) {
companion object {
val STORED_MOVE_KEY = "StoredMove"
fun getMoveNbt(stack: ItemStack): TechnicalMachine? {
val nbtCompound = stack.nbt ?: return null
return TechnicalMachines.tmMap[Identifier.tryParse(nbtCompound.getString(STORED_MOVE_KEY))]
}
fun getItemColor(stack: ItemStack, tint: Int): Int {
val nbt = getMoveNbt(stack) ?: return ElementalTypes.NORMAL.primaryColor
if (nbt.primaryColor != null && tint == 0) return nbt.primaryColor
if (nbt.secondaryColor != null && tint != 0) return nbt.secondaryColor
val type = ElementalTypes.getOrException(nbt.type)
if (tint == 0) return type.primaryColor
return type.secondaryColor
}
}
override fun hasGlint(stack: ItemStack?) = false
fun setNbt(stack: ItemStack, id: String): ItemStack {
stack.getOrCreateNbt().put(STORED_MOVE_KEY, NbtString.of(id))
return stack
}
override fun appendTooltip(
stack: ItemStack,
world: World?,
tooltip: MutableList<Text>,
context: TooltipContext?
) {
val nbt = getMoveNbt(stack)
val text: MutableText = if (nbt != null) {
lang("move." + nbt.moveName)
} else {
lang("tms.unknown_move")
}
tooltip.add(text.gray())
}
override fun useOnEntity(stack: ItemStack, user: PlayerEntity, entity: LivingEntity, hand: Hand): ActionResult {
if (user.world.isClient) return ActionResult.FAIL
if (entity !is PokemonEntity) return ActionResult.FAIL
val tm = getMoveNbt(stack) ?: return ActionResult.FAIL
val move = Moves.getByName(tm.moveName) ?: return ActionResult.FAIL
val pokemon = entity.pokemon
val tmLearnableMoves = pokemon.species.moves.tmLearnableMoves()
if (!tmLearnableMoves.contains(move)) {
user.sendMessage(lang("tms.cannot_learn", pokemon.getDisplayName(), tm.translatedMoveName()))
return ActionResult.FAIL
}
if (pokemon.allAccessibleMoves.contains(move)) {
user.sendMessage(lang("tms.already_known", pokemon.getDisplayName(), tm.translatedMoveName()))
return ActionResult.FAIL
}
if (!user.isCreative) stack.decrement(1)
if (pokemon.moveSet.hasSpace()) { pokemon.moveSet.add(move.create()) }
else { pokemon.benchedMoves.add(BenchedMove(move, 0)) }
user.sendMessage(lang("tms.teach_move", pokemon.getDisplayName(), tm.translatedMoveName()).green())
user.playSound(CobblemonSounds.TM_USE, SoundCategory.PLAYERS, 1.0f, 1.0f)
entity.cry()
return ActionResult.CONSUME
}
override fun useOnBlock(context: ItemUsageContext): ActionResult {
if (context.world.isClient) {
context.player?.swingHand(context.hand)
return ActionResult.FAIL
}
//val TMM = context.world.getBlockState(context.hitPos.toBlockPos()).block
val TMM = context.world.getBlockEntity(context.hitPos.toBlockPos())
if (TMM is TMBlockEntity
&& (TMM.tmmInventory.filterTM == null
|| TMM.tmmInventory.filterTM!!.nbt?.get("StoredMove") != context.stack.nbt?.get("StoredMove"))) {
context.player?.playSound(CobblemonSounds.TMM_ON, SoundCategory.BLOCKS, 1.0f, 1.0f)
context.player?.swingHand(context.hand)
if (TMM.tmmInventory.filterTM != null) {
if (!context.player?.isCreative!!) {
context.player!!.giveItemStack(TMM.tmmInventory.filterTM!!)
}
}
// set filterTM equal to the item it corresponds to
TMM.tmmInventory.filterTM = TechnicalMachines.getTechnicalMachineFromStack(context.stack)?.let { TechnicalMachines.getStackFromTechnicalMachine(it) }
/*// if materials are in the machine already
if (TMM.tmmInventory.items?.isNotEmpty() == true) {
TMM.tmmInventory.items?.forEach {
// Get the direction the block is facing
val facingDirection = TMM.blockState.get(Properties.FACING).opposite
// Calculate the center position of the block
val frontOffset = 0.5 // Half block offset to the front
val spawnX = TMM.blockPos.x + 0.5 + facingDirection.offsetX * frontOffset
val spawnY = TMM.blockPos.y + 0.3 + facingDirection.offsetY * frontOffset
val spawnZ = TMM.blockPos.z + 0.5 + facingDirection.offsetZ * frontOffset
// Create the ItemEntity at the center of the block
val itemEntity = ItemEntity( context.world, spawnX, spawnY, spawnZ, it)
// Create the ItemEntity
itemEntity.setVelocity(0.0, 0.0, 0.0)
// Add the ItemEntity to the world
context.world.spawnEntity(itemEntity)
}
TMM.tmmInventory.items?.clear()
}*/
// todo change the color of the disk in the TMM
// todo remove 1 from the stack in the player's hand if not in creative
if (!context.player?.isCreative!!) {
context.player?.getStackInHand(context.hand)?.decrement(1)
}
TMM.markDirty()
TMM.world?.updateListeners(TMM.blockPos, TMM.cachedState, TMM.cachedState, Block.NOTIFY_LISTENERS)
}
/*val type = ElementalTypes.getOrException(getMoveNbt(context.stack)!!.type)
context.player!!.giveItemStack(Registries.ITEM.get(type.typeGem).defaultStack)*/
return super.useOnBlock(context)
}
}
@@ -9,6 +9,7 @@
package com.cobblemon.mod.common.item.group
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.api.tms.TechnicalMachines
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.item.ItemConvertible
import net.minecraft.item.ItemGroup
@@ -35,6 +36,7 @@ object CobblemonItemGroups {
@JvmStatic val CONSUMABLES_KEY = this.create("consumables", this::consumableEntries) { ItemStack(CobblemonItems.ROASTED_LEEK) }
@JvmStatic val HELD_ITEMS_KEY = this.create("held_item", this::heldItemEntries) { ItemStack(CobblemonItems.EXP_SHARE) }
@JvmStatic val EVOLUTION_ITEMS_KEY = this.create("evolution_item", this::evolutionItemEntries) { ItemStack(CobblemonItems.BLACK_AUGURITE) }
@JvmStatic val TECHNICAL_MACHINES_KEY = this.create("technical_machines", this::technicalMachineEntries) { ItemStack(CobblemonItems.BLANK_TM) }
@JvmStatic val BLOCKS get() = Registries.ITEM_GROUP.get(BLOCKS_KEY)
@JvmStatic val POKEBALLS get() = Registries.ITEM_GROUP.get(POKEBALLS_KEY)
@@ -43,6 +45,7 @@ object CobblemonItemGroups {
@JvmStatic val CONSUMABLES get() = Registries.ITEM_GROUP.get(CONSUMABLES_KEY)
@JvmStatic val HELD_ITEMS get() = Registries.ITEM_GROUP.get(HELD_ITEMS_KEY)
@JvmStatic val EVOLUTION_ITEMS get() = Registries.ITEM_GROUP.get(EVOLUTION_ITEMS_KEY)
@JvmStatic val TECHNICAL_MACHINES get() = Registries.ITEM_GROUP.get(TECHNICAL_MACHINES_KEY)
@JvmStatic val FOOD_INJECTIONS = this.inject(RegistryKey.of(Registries.ITEM_GROUP.key, Identifier("food_and_drinks")), this::foodInjections)
@JvmStatic val TOOLS_AND_UTILITIES_INJECTIONS = this.inject(RegistryKey.of(Registries.ITEM_GROUP.key, Identifier("tools_and_utilities")), this::toolsAndUtilitiesInjections)
@@ -76,6 +79,16 @@ object CobblemonItemGroups {
return consumer
}
private fun technicalMachineEntries(displayContext: DisplayContext, entries: Entries) {
entries.add(CobblemonItems.BLANK_TM)
for (tm in TechnicalMachines.tmMap) {
val item = CobblemonItems.TECHNICAL_MACHINE.setNbt(
CobblemonItems.TECHNICAL_MACHINE.defaultStack,
tm.key.toString())
entries.add(item)
}
}
private fun agricultureEntries(displayContext: DisplayContext, entries: Entries) {
entries.add(CobblemonItems.MEDICINAL_LEEK)
entries.add(CobblemonItems.BIG_ROOT)
@@ -217,8 +230,10 @@ object CobblemonItemGroups {
entries.add(CobblemonItems.FOSSIL_ANALYZER)
entries.add(CobblemonItems.MONITOR)
entries.add(CobblemonItems.PC)
entries.add(CobblemonItems.TM_MACHINE)
entries.add(CobblemonItems.HEALING_MACHINE)
entries.add(CobblemonItems.PASTURE)
entries.add(CobblemonItems.MOD_STATION)
entries.add(CobblemonItems.GILDED_CHEST)
entries.add(CobblemonItems.YELLOW_GILDED_CHEST)
@@ -231,6 +246,7 @@ object CobblemonItemGroups {
entries.add(CobblemonItems.RELIC_COIN_POUCH)
entries.add(CobblemonItems.RELIC_COIN_SACK)
entries.add(CobblemonItems.DISPLAY_CASE)
entries.add(CobblemonItems.APRICORN_LOG)
entries.add(CobblemonItems.APRICORN_WOOD)
entries.add(CobblemonItems.STRIPPED_APRICORN_LOG)
@@ -363,6 +379,9 @@ object CobblemonItemGroups {
entries.add(CobblemonItems.JOLLY_MINT)
entries.add(CobblemonItems.NAIVE_MINT)
entries.add(CobblemonItems.SERIOUS_MINT)
entries.add(CobblemonItems.ABILITY_CAPSULE)
entries.add(CobblemonItems.ABILITY_PATCH)
}
private fun evolutionItemEntries(displayContext: DisplayContext, entries: Entries) {
@@ -0,0 +1,39 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.item.interactive.ability
import com.cobblemon.mod.common.api.abilities.PotentialAbility
import com.cobblemon.mod.common.api.interaction.PokemonEntityInteraction
import com.cobblemon.mod.common.api.item.ability.AbilityChanger
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.item.CobblemonItem
import com.cobblemon.mod.common.util.asTranslated
import com.cobblemon.mod.common.util.lang
import net.minecraft.item.ItemStack
import net.minecraft.server.network.ServerPlayerEntity
open class AbilityChangeItem<T : PotentialAbility>(val changer: AbilityChanger<T>) : CobblemonItem(Settings()), PokemonEntityInteraction {
override val accepted: Set<PokemonEntityInteraction.Ownership> = setOf(PokemonEntityInteraction.Ownership.OWNER)
override fun processInteraction(player: ServerPlayerEntity, entity: PokemonEntity, stack: ItemStack): Boolean {
if (this.changer.performChange(entity.pokemon)) {
this.consumeItem(player, stack)
val feedback = lang(
"ability_changer.changed",
entity.pokemon.getDisplayName(),
entity.pokemon.ability.displayName.asTranslated()
)
player.sendMessage(feedback)
return true
}
return false
}
}
@@ -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.item.interactive.ability
import com.cobblemon.mod.common.api.abilities.*
import com.cobblemon.mod.common.api.item.ability.AbilityChanger
import com.cobblemon.mod.common.pokemon.Pokemon
// TODO Polish me down the line when we actually release the item in the mod, we need a way to select the ability
open class AbilityTypeChanger<T : PotentialAbility>(
override val type: PotentialAbilityType<T>,
private val supportsChangingFrom: (other: PotentialAbilityType<*>?) -> Boolean
) : AbilityChanger<T> {
override fun queryPossible(pokemon: Pokemon): Set<AbilityTemplate> {
val ofType = pokemon.form.abilities.filter { it.type == this.type }
return ofType.filter { it.template != pokemon.ability.template }
.map { it.template }
.toSet()
}
override fun performChange(pokemon: Pokemon): Boolean {
val currentType = this.findCurrent(pokemon)
if (!this.canChangeFrom(currentType)) {
return false
}
val possible = this.queryPossible(pokemon)
val picked = possible.randomOrNull() ?: return false
val old = pokemon.ability.template
pokemon.updateAbility(picked.create(forced = false))
return pokemon.ability.template != old
}
override fun canChangeFrom(type: PotentialAbilityType<*>?): Boolean = this.supportsChangingFrom(type)
private fun findCurrent(pokemon: Pokemon): PotentialAbilityType<*>? {
if (pokemon.ability.forced) {
return null
}
return pokemon.form.abilities.firstOrNull { it.template == pokemon.ability.template }?.type
}
}
@@ -0,0 +1,35 @@
package com.cobblemon.mod.common.net.messages.client.ui
import com.cobblemon.mod.common.api.net.NetworkPacket
import com.cobblemon.mod.common.api.tms.TechnicalMachine
import com.cobblemon.mod.common.api.tms.TechnicalMachines
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.item.ItemStack
import net.minecraft.network.PacketByteBuf
import java.util.UUID
/**
* Tells the server to attempt crafting a Blank TM using the [TMBlock]
*
* Handled by [CraftBlankTMPacketHandler]
*
* @author whatsy
*/
class CraftBlankTMPacket(
val ingredient: ItemStack
): NetworkPacket<CraftBlankTMPacket> {
override val id = ID
override fun encode(buffer: PacketByteBuf) {
buffer.writeItemStack(ingredient)
}
companion object {
val ID = cobblemonResource("craft_blank_tm")
fun decode(buffer: PacketByteBuf) = CraftBlankTMPacket(
buffer.readItemStack()
)
}
}
@@ -0,0 +1,42 @@
package com.cobblemon.mod.common.net.messages.client.ui
import com.cobblemon.mod.common.api.net.NetworkPacket
import com.cobblemon.mod.common.api.tms.TechnicalMachine
import com.cobblemon.mod.common.api.tms.TechnicalMachines
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.item.ItemStack
import net.minecraft.network.PacketByteBuf
import java.util.UUID
/**
* Tells the server to attempt crafting a [TechnicalMachineItem] using the [TMBlock]
*
* Handled by [CraftTMPacketHandler]
*
* @author whatsy
*/
class CraftTMPacket(
val tm: TechnicalMachine,
val disc: ItemStack,
val gem: ItemStack,
val ingredient: ItemStack
): NetworkPacket<CraftTMPacket> {
override val id = ID
override fun encode(buffer: PacketByteBuf) {
buffer.writeIdentifier(tm.id())
buffer.writeItemStack(disc)
buffer.writeItemStack(gem)
buffer.writeItemStack(ingredient)
}
companion object {
val ID = cobblemonResource("craft_tm")
fun decode(buffer: PacketByteBuf) = CraftTMPacket(
TechnicalMachines.tmMap[buffer.readIdentifier()]!!,
buffer.readItemStack(), buffer.readItemStack(), buffer.readItemStack()
)
}
}
@@ -0,0 +1,15 @@
package com.cobblemon.mod.common.net.messages.client.ui
import com.cobblemon.mod.common.api.net.NetworkPacket
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.network.PacketByteBuf
import net.minecraft.util.Identifier
class OpenTMMPacket: NetworkPacket<OpenTMMPacket> {
override val id = cobblemonResource("open_tm")
override fun encode(buffer: PacketByteBuf) {
}
}
@@ -1,32 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.net.messages.server.battle
import com.cobblemon.mod.common.api.net.NetworkPacket
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.network.PacketByteBuf
import java.util.UUID
class FleeBattlePacket(val battleId: UUID) : NetworkPacket<FleeBattlePacket> {
override val id = ID
override fun encode(buffer: PacketByteBuf) {
buffer.writeUuid(battleId)
}
companion object {
val ID = cobblemonResource("flee_battle")
fun decode(buffer: PacketByteBuf): FleeBattlePacket {
return FleeBattlePacket(buffer.readUuid())
}
}
}
@@ -1,25 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.net.serverhandling.battle
import com.cobblemon.mod.common.api.net.ServerNetworkPacketHandler
import com.cobblemon.mod.common.battles.BattleRegistry
import com.cobblemon.mod.common.net.messages.server.battle.FleeBattlePacket
import com.cobblemon.mod.common.util.battleLang
import net.minecraft.server.MinecraftServer
import net.minecraft.server.network.ServerPlayerEntity
object FleeBattlePacketHandler : ServerNetworkPacketHandler<FleeBattlePacket> {
override fun handle(packet: FleeBattlePacket, server: MinecraftServer, player: ServerPlayerEntity) {
val battle = BattleRegistry.getBattle(packet.battleId) ?: return
player.sendMessage(battleLang("flee"))
battle.end()
}
}
@@ -12,7 +12,10 @@ import com.bedrockk.molang.Expression
import com.bedrockk.molang.ast.NumberExpression
import com.cobblemon.mod.common.api.snowstorm.*
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.asExpressionLike
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import com.cobblemon.mod.common.util.normalizeToArray
import com.cobblemon.mod.common.util.singularToPluralList
import com.google.gson.JsonArray
import com.google.gson.JsonObject
import com.google.gson.JsonPrimitive
@@ -38,6 +41,7 @@ object SnowstormParticleReader {
val emitterShapeDiscJson = componentsJson.get("minecraft:emitter_shape_disc")?.asJsonObject
val emitterShapeBoxJson = componentsJson.get("minecraft:emitter_shape_box")?.asJsonObject
val emitterShapeEntityBoundingBoxJson = componentsJson.get("minecraft:emitter_shape_entity_aabb")?.asJsonObject
val emitterLifetimeEventsJson = componentsJson.get("minecraft:emitter_lifetime_events")?.asJsonObject
val dynamicMotionJson = componentsJson.get("minecraft:particle_motion_dynamic")?.asJsonObject
val particleAppearanceJson = componentsJson.get("minecraft:particle_appearance_billboard").asJsonObject
val sizeJson = particleAppearanceJson.get("size")?.asJsonArray
@@ -50,6 +54,7 @@ object SnowstormParticleReader {
val colourJson = tintingJson?.get("color")
val collisionJson = componentsJson.get("minecraft:particle_motion_collision")?.asJsonObject
val spaceJson = componentsJson.get("minecraft:emitter_local_space")?.asJsonObject
val particleLifetimeEventsJson = componentsJson.get("minecraft:particle_lifetime_events")?.asJsonObject
val id = Identifier(descJson.get("identifier").asString)
val maxAge = particleLifetimeJson?.get("max_lifetime")?.asString?.asExpression() ?: 0.0.asExpression()
@@ -107,6 +112,24 @@ object SnowstormParticleReader {
else -> TODO("Unrecognized curve type was used")
}
}
val events = mutableMapOf<String, ParticleEvent>()
val eventJson = effectJson.get("events")?.asJsonObject
eventJson?.entrySet()?.forEach { (name, event) ->
val eventObj = event.asJsonObject
val particleEffect = eventObj.get("particle_effect")?.asJsonObject?.let {
val effect = it.get("effect").asString.asIdentifierDefaultingNamespace()
val type = it.get("type").asString
val preEffectExpression = it.get("pre_effect_expression")?.asString?.asExpressionLike()
val typeEnum = EventParticleEffect.EventParticleType.valueOf(type.uppercase())
EventParticleEffect(effect, typeEnum, preEffectExpression)
}
val soundEffect = eventObj.get("sound_effect")?.asJsonObject?.let {
val eventName = it.get("event_name").asString.asIdentifierDefaultingNamespace()
EventSoundEffect(eventName)
}
val expression = eventObj.get("expression")?.asString?.asExpressionLike()
events[name] = ParticleEvent(particleEffect, soundEffect, expression)
}
val emitterStartExpressions = (emitterInitializationJson["creation_expression"]?.asString ?: "").split(";").filter { it.isNotEmpty() }.map { it.asExpression() }
val emitterUpdateExpressions = (emitterInitializationJson["per_update_expression"]?.asString ?: "").split(";").filter { it.isNotEmpty() }.map { it.asExpression() }
val particleUpdateExpressions = (particleInitializationJson["per_update_expression"]?.asString ?: "").split(";").filter { it.isNotEmpty() }.map { it.asExpression() }
@@ -128,7 +151,7 @@ object SnowstormParticleReader {
} else if (emitterLifetimeLoopingJson != null) {
LoopingEmitterLifetime(
activeTime = (emitterLifetimeLoopingJson.get("active_time")?.asString ?: "").asExpression(),
sleepTime = (emitterLifetimeLoopingJson.get("sleep_time")?.asString ?: "").asExpression()
sleepTime = (emitterLifetimeLoopingJson.get("sleep_time")?.asString ?: "0.0").asExpression()
)
} else if (emitterLifetimeExpressionJson != null) {
ExpressionEmitterLifetime(
@@ -324,14 +347,48 @@ object SnowstormParticleReader {
)
} ?: ParticleSpace()
val particleEventSet = particleLifetimeEventsJson?.let {
val creationEvents = it.get("creation_event")?.normalizeToArray()?.map { SimpleEventTrigger(it.asString) }?.toMutableList() ?: mutableListOf()
val expirationEvents = it.get("expiration_event")?.normalizeToArray()?.map { SimpleEventTrigger(it.asString) }?.toMutableList() ?: mutableListOf()
val timeline = it.get("timeline")?.asJsonObject?.entrySet()?.map { (key, value) ->
key.toDouble() to value.normalizeToArray().map { it.asString }.toMutableList()
}?.toMap()?.toMutableMap() ?: mutableMapOf()
BedrockParticle.EventSet(
creationEvents = creationEvents,
expirationEvents = expirationEvents,
timeline = EventTriggerTimeline(timeline)
)
} ?: BedrockParticle.EventSet(mutableListOf(), mutableListOf(), EventTriggerTimeline(mutableMapOf()))
val emitterCreationEvents = emitterLifetimeEventsJson?.get("creation_event")?.normalizeToArray()?.map { SimpleEventTrigger(it.asString) }?.toMutableList() ?: mutableListOf()
val emitterExpirationEvents = emitterLifetimeEventsJson?.get("expiration_event")?.normalizeToArray()?.map { SimpleEventTrigger(it.asString) }?.toMutableList() ?: mutableListOf()
val emitterTravelDistanceEvents = emitterLifetimeEventsJson?.get("travel_distance_events")?.asJsonObject?.entrySet()?.map { (key, value) ->
key.toDouble() to value.normalizeToArray().map { it.asString }.toMutableList()
}?.toMap()?.toMutableMap() ?: mutableMapOf()
val emitterLoopingTravelDistanceEvents = emitterLifetimeEventsJson?.get("looping_travel_distance_events")?.asJsonArray?.map {
val obj = it.asJsonObject
val distance = obj.get("distance").asDouble
val events = obj.get("events").normalizeToArray().map { it.asString }.toMutableList()
LoopingTravelDistanceEventTrigger(distance, events)
}?.toMutableList() ?: mutableListOf()
val emitterEventTimeline = EventTriggerTimeline(emitterLifetimeEventsJson?.get("timeline")?.asJsonObject?.entrySet()?.map { (key, value) ->
key.toDouble() to value.normalizeToArray().map { it.asString }.toMutableList()
}?.toMap()?.toMutableMap() ?: mutableMapOf())
return BedrockParticleEffect(
id = id,
events = events,
emitter = BedrockParticleEmitter(
startExpressions = emitterStartExpressions.toMutableList(),
updateExpressions = emitterUpdateExpressions.toMutableList(),
rate = rate,
shape = shape,
lifetime = lifetime
lifetime = lifetime,
eventTimeline = emitterEventTimeline,
creationEvents = emitterCreationEvents,
expirationEvents = emitterExpirationEvents,
travelDistanceEvents = EventTriggerTimeline(emitterTravelDistanceEvents),
loopingTravelDistanceEvents = emitterLoopingTravelDistanceEvents
),
curves = curves.toMutableList(),
particle = BedrockParticle(
@@ -350,7 +407,10 @@ object SnowstormParticleReader {
cameraMode = cameraMode,
collision = collision,
environmentLighting = environmentLighting,
tinting = tinting
tinting = tinting,
creationEvents = particleEventSet.creationEvents,
expirationEvents = particleEventSet.expirationEvents,
timeline = particleEventSet.timeline
),
space = space
)
@@ -20,7 +20,7 @@ import net.minecraft.client.world.ClientWorld
import net.minecraft.particle.ParticleType
import net.minecraft.util.math.Vec3d
class SnowstormParticleType : ParticleType<SnowstormParticleEffect>(false, SnowstormParticleEffect.PARAMETERS_FACTORY) {
class SnowstormParticleType : ParticleType<SnowstormParticleEffect>(true, SnowstormParticleEffect.PARAMETERS_FACTORY) {
companion object {
val CODEC: Codec<SnowstormParticleEffect> = RecordCodecBuilder.create { instance ->
instance.group(
@@ -15,6 +15,7 @@ import com.cobblemon.mod.common.CobblemonNetwork.sendPacketToPlayers
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.abilities.Abilities
import com.cobblemon.mod.common.api.abilities.Ability
import com.cobblemon.mod.common.api.abilities.AbilityPool
import com.cobblemon.mod.common.api.data.ShowdownIdentifiable
import com.cobblemon.mod.common.api.events.CobblemonEvents
import com.cobblemon.mod.common.api.events.CobblemonEvents.FRIENDSHIP_UPDATED
@@ -122,8 +123,8 @@ open class Pokemon : ShowdownIdentifiable {
updateAspects()
updateForm()
checkGender()
checkAbility()
updateHP(quotient)
this.attemptAbilityUpdate()
_species.emit(value)
}
@@ -138,9 +139,9 @@ open class Pokemon : ShowdownIdentifiable {
// Evo proxy is already cleared on species update but the form may be changed by itself, this is fine and no unnecessary packets will be sent out
this.evolutionProxy.current().clear()
findAndLearnFormChangeMoves()
updateHP(quotient)
checkGender()
checkAbility()
updateHP(quotient)
this.attemptAbilityUpdate()
_form.emit(value)
}
@@ -375,8 +376,9 @@ open class Pokemon : ShowdownIdentifiable {
*/
val benchedMoves = BenchedMoves()
var ability: Ability = Abilities.DUMMY.create()
set(value) {
var ability: Ability = Abilities.DUMMY.create(false)
// Keep internal for DTO & sync packet
internal set(value) {
if (field != value) {
_ability.emit(value)
}
@@ -777,10 +779,8 @@ open class Pokemon : ShowdownIdentifiable {
evs.loadFromNBT(nbt.getCompound(DataKeys.POKEMON_EVS))
moveSet.loadFromNBT(nbt)
scaleModifier = nbt.getFloat(DataKeys.POKEMON_SCALE_MODIFIER)
val abilityNBT = nbt.getCompound(DataKeys.POKEMON_ABILITY) ?: NbtCompound()
val abilityName = abilityNBT.getString(DataKeys.POKEMON_ABILITY_NAME).takeIf { it.isNotEmpty() } ?: "runaway"
if (abilityName != "dummy") {
ability = Abilities.getOrException(abilityName).create(abilityNBT)
if (nbt.contains(DataKeys.POKEMON_ABILITY, COMPOUND_TYPE.toInt())) {
this.ability.loadFromNBT(nbt.getCompound(DataKeys.POKEMON_ABILITY))
}
shiny = nbt.getBoolean(DataKeys.POKEMON_SHINY)
if (nbt.contains(DataKeys.POKEMON_STATE)) {
@@ -813,7 +813,6 @@ open class Pokemon : ShowdownIdentifiable {
}
updateAspects()
updateForm() // If saved with an incorrect form, readjust on load
checkAbility()
nbt.get(DataKeys.POKEMON_EVOLUTIONS)?.let { tag -> this.evolutionProxy.loadFromNBT(tag) }
if (nbt.contains(DataKeys.HELD_ITEM)) {
this.heldItem = ItemStack.fromNbt(nbt.getCompound(DataKeys.HELD_ITEM))
@@ -906,10 +905,8 @@ open class Pokemon : ShowdownIdentifiable {
evs.loadFromJSON(json.getAsJsonObject(DataKeys.POKEMON_EVS))
moveSet.loadFromJSON(json.get(DataKeys.POKEMON_MOVESET).asJsonObject)
scaleModifier = json.get(DataKeys.POKEMON_SCALE_MODIFIER).asFloat
val abilityJSON = json.get(DataKeys.POKEMON_ABILITY)?.asJsonObject ?: JsonObject()
val abilityName = abilityJSON.get(DataKeys.POKEMON_ABILITY_NAME)?.asString
if (abilityName != "dummy" && abilityName != null) {
ability = Abilities.getOrException(abilityName).create(abilityJSON)
if (json.has(DataKeys.POKEMON_ABILITY) && json.get(DataKeys.POKEMON_ABILITY).isJsonObject) {
this.ability.loadFromJSON(json.getAsJsonObject(DataKeys.POKEMON_ABILITY))
}
shiny = json.get(DataKeys.POKEMON_SHINY).asBoolean
if (json.has(DataKeys.POKEMON_STATE)) {
@@ -942,7 +939,6 @@ open class Pokemon : ShowdownIdentifiable {
}
updateAspects()
updateForm() // If saved with an incorrect form, readjust on load
checkAbility()
json.get(DataKeys.POKEMON_EVOLUTIONS)?.let { this.evolutionProxy.loadFromJson(it) }
if (json.has(DataKeys.HELD_ITEM)) {
ItemStack.CODEC.decode(JsonOps.INSTANCE, json.get(DataKeys.HELD_ITEM)).result().ifPresent {
@@ -1154,7 +1150,6 @@ open class Pokemon : ShowdownIdentifiable {
// Force the setter to initialize it
species = species
checkGender()
checkAbility()
// This should only be a thing once we have moveset control in properties until then a creation should require a moveset init.
/*
if (pokemon.moveSet.none { it != null }) {
@@ -1191,63 +1186,104 @@ open class Pokemon : ShowdownIdentifiable {
}
}
fun checkAbility() {
if (isClient) {
/**
* Rolls an ability for this [Pokemon] using [AbilityPool.select]. This will override any current [Ability.forced].
*
* This operation does nothing on the client side.
*
* @return The newly assigned [Ability] or the existing one if called on the client.
*/
@Suppress("MemberVisibilityCanBePrivate")
open fun rollAbility(): Ability {
if (this.isClient) {
return this.ability
}
val (ability, _) = this.form.abilities.select(this.species, this.aspects)
return this.updateAbility(ability.template.create(false))
}
/**
* Assigns the given [ability] to this Pokémon.
* It will also handle all the coordinate storing for proper synchronization across evolutions or other [species]/[form] updates.
* This coordinate storage is skipped for [Ability.forced].
*
* This operation does nothing on the client side.
*
* @param ability The ability getting assigned
* @return The resulting [Ability].
*/
open fun updateAbility(ability: Ability): Ability {
if (this.isClient) {
return this.ability
}
this.ability = if (ability.forced) ability else this.attachAbilityCoordinate(ability)
return this.ability
}
/**
* Tries to update the [ability] on [species]/[form] change.
*
* This operation does nothing on the client side or [Ability.forced] is true.
*
* If the ability is [Abilities.DUMMY] it invokes [rollAbility] instead.
*/
protected open fun attemptAbilityUpdate() {
if (this.isClient || this.ability.forced) {
return
}
val hasForcedAbility = this.ability.forced
val hasLegalAbility = this.form.abilities.mapping.values.any { list ->
list.any { potential ->
potential.template == this.ability.template
if (this.ability.template == Abilities.DUMMY) {
this.rollAbility()
return
}
val potentials = this.form.abilities.mapping[this.ability.priority]
// Step 1 try to get the same exact index and priority
var potential = potentials?.getOrNull(this.ability.index)
// Step 2 Disclaimer I don't really know the best course of action here
// For default assets this just means "pick the only other regular ability" and hidden abilities are always just 1 so...
// Sorry 3rd party please don't make really weird stats :(
if (potential == null && potentials != null) {
for (i in this.ability.index.coerceAtLeast(0) downTo 0) {
val indexed = potentials.getOrNull(i)
if (indexed != null) {
potential = indexed
break
}
}
}
val isDummy = this.ability.template == Abilities.DUMMY
if (potential != null) {
// Keep our known index and priority, this was the original valid state after all
this.ability = potential.template.create(false).apply {
this.index = this@Pokemon.ability.index
this.priority = this@Pokemon.ability.priority
}
return
}
// End of the road kiddo it didn't have to go down like this...
this.rollAbility()
}
// EXPLANATION
// This is used to keep the same intended ability between evolution stages
// Between species updates if an original indexed data is attached it will be honored next time that it's possible
// This is still not a perfect system but it will now only break if players are constantly adding/removing data edits which at that point it's on them
if (isDummy || (!hasLegalAbility && !hasForcedAbility)) {
var needsSelection = true
var needsUpdate = true
if (this.ability.index == -1 && !isDummy) {
base@ for ((priority, list) in this.form.abilities.mapping) {
for ((index, potential) in list.withIndex()) {
if (potential.template == this.ability.template) {
this.ability.priority = priority
this.ability.index = index
needsUpdate = false
needsSelection = false
break@base
}
}
}
}
else if (this.ability.index >= 0) {
needsUpdate = false
val potentialAbility = this.form.abilities.mapping[this.ability.priority]?.getOrNull(this.ability.index)
if (potentialAbility != null) {
// Don't update index nor priority
val newAbility = potentialAbility.template.create()
newAbility.index = this.ability.index
newAbility.priority = this.ability.priority
this.ability = newAbility
needsSelection = false
}
}
if (needsSelection) {
val (ability, priority) = this.form.abilities.select(this.species, this.aspects)
ability.index = this.ability.index
ability.priority = this.ability.priority
this.ability = ability
if (needsUpdate) {
// This may sometimes happen when both species and form update as well as if AbilityPool#select throws a graceful exception, we return to prevent a crash.
val mapped = this.form.abilities.mapping[priority] ?: return
this.ability.index = mapped.indexOfFirst { potential -> potential.template == this.ability.template }
this.ability.priority = priority
}
}
/**
* Finds the position of the current ability in the Pokémon data and attaches the coordinates.
* This is done in an attempt to synchronize correctly between [species]/[form] changes.
*
* This operation does nothing on the client side, if [Ability.forced] is true or if the ability is [Abilities.DUMMY].
*
* @param ability The [Ability] being queried.
* @return It can return [ability] with the [Ability.priority] & [Ability.index] mapped, the [Ability.forced] set to true if illegal or itself if no operation is performed.
*/
protected open fun attachAbilityCoordinate(ability: Ability): Ability {
if (this.isClient || ability.forced || ability.template == Abilities.DUMMY) {
return ability
}
val found = this.form.abilities.firstOrNull { potential -> potential.template == ability.template }
?: return ability.apply { this.forced = true }
val index = this.form.abilities.mapping[found.priority]
?.indexOf(found)
?.takeIf { it != -1 }
?: return ability.apply { this.forced = true }
ability.priority = found.priority
ability.index = index
return ability
}
fun initializeMoveset(preferLatest: Boolean = true) {
@@ -1362,11 +1398,18 @@ open class Pokemon : ShowdownIdentifiable {
val newLevelUpMoves = form.moves.getLevelUpMovesUpTo(newLevel)
val differences = (newLevelUpMoves - previousLevelUpMoves).filter { this.moveSet.none { move -> move.template == it } }.toMutableSet()
differences.forEach {
if (moveSet.hasSpace()) {
moveSet.add(it.create())
}
}
// differences.forEach {
// TechnicalMachines.tmsToCheckOnLevelUp.entries.filter { entry ->
// entry.value.moveName == it.name
// }.forEach { entry ->
// val player = this.getOwnerPlayer() as PlayerEntity
// Cobblemon.playerData.get(player).tmSet.add(entry.key)
// player.sendMessage(lang("tms.unlock_tm", lang("move." + entry.value.moveName)))
// }
// if (moveSet.hasSpace()) {
// moveSet.add(it.create())
// }
// }
CobblemonEvents.EXPERIENCE_GAINED_EVENT_POST.post(
ExperienceGainedPostEvent(this, source, appliedXP, oldLevel, newLevel, differences),
@@ -30,10 +30,10 @@ object HiddenAbilityPropertyType : CustomPokemonPropertyType<HiddenAbilityProper
class HiddenAbilityProperty : CustomPokemonProperty {
override fun asString() = "hiddenability"
override fun apply(pokemon: Pokemon) {
val hiddenAbilities = pokemon.form.abilities.mapping.flatMap { it.value }.filter { it.type == HiddenAbilityType }
if (hiddenAbilities.isNotEmpty()) {
pokemon.ability = hiddenAbilities.random().template.create()
}
val possible = pokemon.form.abilities.mapping.flatMap { it.value }
.filter { it.type == HiddenAbilityType }
val picked = possible.randomOrNull() ?: return
pokemon.updateAbility(picked.template.create(false))
}
override fun matches(pokemon: Pokemon) = pokemon.form.abilities.mapping
@@ -0,0 +1,22 @@
package com.cobblemon.mod.common.tms.obtain
import com.cobblemon.mod.common.api.tms.ObtainMethod
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.server.network.ServerPlayerEntity
/**
* An [ObtainMethod] that never triggers.
* Useful since the obtainMethods field in TM JSONs cannot be blank.
*
* @author whatsy
*/
class ImpossibleObtainMethod : ObtainMethod {
override val passive = false
companion object {
val ID = cobblemonResource("impossible")
}
override fun matches(player: ServerPlayerEntity) = false
}
@@ -0,0 +1,24 @@
package com.cobblemon.mod.common.tms.obtain
import com.cobblemon.mod.common.api.tms.ObtainMethod
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.server.network.ServerPlayerEntity
/**
* An [ObtainMethod] that unlocks immediately.
* Used for TMs that aren't unlocked and are available immediately.
* TMs with this [ObtainMethod] will not display an unlock text.
*
* @author whatsy
*/
class NoneObtainMethod : ObtainMethod {
override val passive = true
override fun matches(player: ServerPlayerEntity) = true
companion object {
val ID = cobblemonResource("none")
}
}
@@ -0,0 +1,29 @@
package com.cobblemon.mod.common.tms.obtain
import com.cobblemon.mod.common.api.tms.ObtainMethod
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.util.Identifier
/**
* An [ObtainMethod] that triggers when the player has an advancement.
*/
class PlayerHasAdvancementObtainMethod : ObtainMethod {
companion object {
val ID = cobblemonResource("advancement")
}
override val passive = true
val advancement: Identifier? = null
override fun matches(player: ServerPlayerEntity): Boolean {
if (advancement == null) return false
for (entry in player.advancementTracker.progress) {
if (entry.key.id == advancement && entry.value.isDone) return true
}
return false
}
}
@@ -0,0 +1,30 @@
package com.cobblemon.mod.common.tms.obtain
import com.cobblemon.mod.common.api.tms.ObtainMethod
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.server.network.ServerPlayerEntity
/**
* An [ObtainMethod] that checks the player's Y level.
*
* @param yLevel The Y level to check for.
* @param operator The operator to use. Can be "equals", "less", or "greater".
* @author whatsy
*/
class PlayerYObtainMethod(val yLevel: Int, val operator: String) : ObtainMethod {
override val passive = true
override fun matches(player: ServerPlayerEntity): Boolean {
when (operator) {
"equals" -> return (player.y.toInt() == yLevel)
"less" -> return (yLevel < player.y)
"greater" -> return (yLevel > player.y)
}
return false
}
companion object {
val ID = cobblemonResource("y_level")
}
}
@@ -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.tms.obtain
import com.cobblemon.mod.common.api.moves.Moves
import com.cobblemon.mod.common.api.tms.ObtainMethod
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.party
import com.google.gson.annotations.SerializedName
import net.minecraft.server.network.ServerPlayerEntity
/**
* An [ObtainMethod] that triggers when a [Pokemon] in the player's party knows a certain [Move]
*
* @author whatsy
*/
class PokemonHasMoveObtainMethod : ObtainMethod {
companion object {
val ID = cobblemonResource("pokemon_knows")
}
@SerializedName("move")
val moveId: String = "splash"
override val passive = true
override fun matches(player: ServerPlayerEntity): Boolean {
player.party().forEach {
it.allAccessibleMoves.forEach {
if (it.equals(Moves.getByName(moveId))) return true
}
}
return false
}
}
@@ -32,4 +32,14 @@ fun JsonObject.singularToPluralList(rootName: String, pluralName: String = "${ro
get(pluralName).asJsonArray.add(get(rootName))
remove(rootName)
}
}
fun JsonElement.normalizeToArray(): JsonArray {
if (this is JsonArray) {
return this
} else {
val array = JsonArray()
array.add(this)
return array
}
}
@@ -0,0 +1,46 @@
package com.cobblemon.mod.common.util.adapters
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.tms.ObtainMethod
import com.cobblemon.mod.common.api.tms.ObtainMethodAdapter
import com.cobblemon.mod.common.tms.obtain.*
import com.google.gson.JsonDeserializationContext
import com.google.gson.JsonElement
import com.google.gson.JsonSerializationContext
import net.minecraft.util.Identifier
import java.lang.reflect.Type
import kotlin.reflect.KClass
object CobblemonObtainMethodAdapter : ObtainMethodAdapter {
private const val VARIANT = "variant"
private val types = hashMapOf<String, KClass<out ObtainMethod>>()
init {
this.register(PokemonHasMoveObtainMethod::class, PokemonHasMoveObtainMethod.ID)
this.register(ImpossibleObtainMethod::class, ImpossibleObtainMethod.ID)
this.register(PlayerHasAdvancementObtainMethod::class, PlayerHasAdvancementObtainMethod.ID)
this.register(NoneObtainMethod::class, NoneObtainMethod.ID)
this.register(PlayerYObtainMethod::class, PlayerYObtainMethod.ID)
}
override fun register(type: KClass<out ObtainMethod>, identifier: Identifier) {
val existing = this.types.put(identifier.toString(), type)
if (existing != null) {
Cobblemon.LOGGER.debug("Replaced {} under ID {} with {} in the {}", existing::class.qualifiedName, identifier.toString(), type.qualifiedName, this::class.qualifiedName)
}
}
override fun deserialize(jElement: JsonElement, type: Type, context: JsonDeserializationContext): ObtainMethod {
val json = jElement.asJsonObject
val variant = json.get(VARIANT).asString.lowercase()
val registeredType = this.types[variant] ?: throw IllegalArgumentException("Cannot resolve type for variant $variant")
return context.deserialize(json, registeredType.java)
}
override fun serialize(method: ObtainMethod, type: Type, context: JsonSerializationContext): JsonElement {
val json = context.serialize(method).asJsonObject
val variant = this.types.entries.find { it.value == method::class }?.key ?: throw IllegalArgumentException("Cannot resolve variant for type ${method::class.qualifiedName}")
json.addProperty(VARIANT, variant)
return json
}
}

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