Compare commits

...
Author SHA1 Message Date
Landon Wills 95840fa30a Reduce jitter 2025-04-12 19:40:10 -04:00
Landon Wills 75953eda5f Locator rotation testing for JPAK 2025-04-12 19:34:55 -04:00
Landon 1c13ab24e8 Merge branch 'even-more-riding-tweaks' into 'main'
Even more riding tweaks

See merge request cable-mc/cobblemon!1597
2025-04-12 03:07:36 +00:00
Paul 380cf0ea71 You? You're meant to be dead! 2025-04-12 12:29:44 +10:00
Paul 8ded7334bb Refactor forcedTick handling in PosableEntityModel 2025-04-12 12:23:17 +10:00
Polymeta 0ac34bf1a4 Merge branch 'locator-rotation-player-rendering' into 'main'
Lock player model to seat locator's rotation when riding

See merge request cable-mc/cobblemon!1598
2025-04-11 19:41:47 +00:00
Landon Wills 9716ea0806 Fix rotation of player when using custom camera 2025-04-11 06:13:47 -04:00
Landon Wills ffea94f3b2 Rotate player depending on seat locator's rotation 2025-04-11 03:39:49 -04:00
Landon Wills 3061ba69e2 Riding momentum is maintained when window is not active 2025-04-11 03:17:26 -04:00
Landon Wills 741e38d606 Fix issue where same-species pokemon will sync animations on multiplayer environments 2025-04-11 02:01:28 -04:00
Landon Wills 02f2c072c9 Fix issues with pre-animation logic 2025-04-11 01:29:49 -04:00
Polymeta c54849f415 Merge branch 'implement_pokemon' into 'main'
Few mons added for testing

See merge request cable-mc/cobblemon!1595
2025-04-10 13:46:35 +00:00
Frank The FarmerandPolymeta d44e625e9d Few mons added for testing 2025-04-10 13:46:35 +00:00
Polymeta 6a74d5982a Merge branch 'more-riding-tweaks' into 'main'
Riding tweaks / fixes

See merge request cable-mc/cobblemon!1594
2025-04-10 09:13:32 +00:00
Landon Wills 03905c77d2 Add license 2025-04-10 03:12:53 -04:00
Landon Wills da9558f5f9 Fix camera riding momentum not occurring when in chat or screens 2025-04-10 01:40:44 -04:00
Landon Wills 5c65dffac8 Improve rendering of ridden pokemon substantially on low or inconsistent FPS 2025-04-09 23:36:21 -04:00
Landon Wills b67b37c99e Fix some issues with merge 2025-04-09 22:54:23 -04:00
Landon Wills fbb7ee31c5 Merge branch 'refs/heads/main' into water-mount-behaviours 2025-04-09 22:38:45 -04:00
Polymeta d3ef26976b Merge branch 'composite-behaviour-refactor' into 'main'
Composite behaviour refactor

See merge request cable-mc/cobblemon!1584
2025-04-09 15:58:55 +00:00
Landon Wills dac01a3730 Fix issue with crashing on packet decode before player is initialized 2025-04-09 03:30:55 -04:00
Landon Wills ddb05768e4 Remove redundant riding behaviour, rename ride style option from misc to composite 2025-04-09 03:17:31 -04:00
Polymeta 5374940d24 Merge branch 'spicy-molang' into 'main'
Added `spawn_pokemon` and `attempt_wild_battle` as available MoLang functions

Closes #1237

See merge request cable-mc/cobblemon!1586
2025-04-09 07:13:00 +00:00
SpicyandPolymeta 1fd6a660dd Added spawn_pokemon and attempt_wild_battle as available MoLang functions 2025-04-09 07:13:00 +00:00
Landon Wills fd45934775 Fix issue with canRide condition, remove duplicate riding config in aerodactyl 2025-04-09 03:09:04 -04:00
Polymeta 5e0ffd8bd3 Merge branch 'josh/item-molang-fixes' into 'main'
Small fixes for molang functions related to item rendering.

See merge request cable-mc/cobblemon!1590
2025-04-08 16:02:48 +00:00
JoshandPolymeta b4b315360d Small fixes for molang functions related to item rendering. 2025-04-08 16:02:48 +00:00
Polymeta a44c70648b actually return held item to player again
Signed-off-by: Polymeta <40173118+Polymeta@users.noreply.github.com>
2025-04-08 16:07:20 +01:00
Polymeta c41b0cdca2 Merge branch 'negative-config-values' into 'main'
Allow negative numbers in config GUI

See merge request cable-mc/cobblemon!1588
2025-04-08 12:01:45 +00:00
lionandPolymeta 88f743ee48 Allow negative numbers in config GUI 2025-04-08 12:01:44 +00:00
Polymeta 15b9afacf8 remove client logic from common code MoLang functions
Signed-off-by: Polymeta <40173118+Polymeta@users.noreply.github.com>
2025-04-08 12:59:36 +01:00
Polymeta babcf7430b don't error when heldItem nbt is not present
Closes #1505

Signed-off-by: Polymeta <40173118+Polymeta@users.noreply.github.com>
2025-04-08 11:38:05 +01:00
Polymeta 3439ca2011 [skip-ci] fix tag docs formatting
Signed-off-by: Polymeta <40173118+Polymeta@users.noreply.github.com>
2025-04-08 10:55:48 +01:00
Polymeta 6539f3dea1 Merge branch 'josh/3d-wearable-items' into 'main'
Start of wearable item implementation.

See merge request cable-mc/cobblemon!1579
2025-04-08 09:16:24 +00:00
Landon Wills 1677598ec0 Merge branch 'refs/heads/main' into water-mount-behaviours
# Conflicts:
#	common/src/main/java/com/cobblemon/mod/common/mixin/client/CameraMixin.java
2025-04-08 01:36:01 -04:00
Landon fcde1eb171 Merge branch 'hitbox-and-hit' into 'main'
Position hitbox on client to be accurate to the locator and make looking at...

See merge request cable-mc/cobblemon!1589
2025-04-08 03:26:26 +00:00
Josh a30a5644df Okay, this is it this time for real 2025-04-07 23:03:30 -04:00
JPAK a5672a38b9 Switch to using delegates to position riders. 2025-04-07 21:37:42 -05:00
JPAK 3b14ea319b Position hitbox on client to be accurate to the locator and make looking at stuff align to the camera. 2025-04-07 21:12:01 -05:00
Landon Wills 57e2765f90 Clean up some method signatures in CameraMixin 2025-04-07 21:57:20 -04:00
Josh a090c90831 Merge branch 'main' into josh/3d-wearable-items 2025-04-07 20:55:41 -04:00
Josh 5dcc9ba46d Edit CHANGELOG.md 2025-04-07 21:21:05 +00:00
Josh f8e4ea70df Removed debug models. 2025-04-07 17:09:15 -04:00
Josh 5dc1c749f3 Replaced WearableItemModel enum with WearableItem class. Instances of this class should have a corresponding 3D model in models/item/wearable/ 2025-04-07 16:35:46 -04:00
Polymeta 3accfa29b7 Merge branch 'josh/joint-transform-command' into 'main'
Changed command "changejointscale" into "transformmodelpart". Allows you to easily add model transformations in-game.

See merge request cable-mc/cobblemon!1587
2025-04-07 17:18:56 +00:00
Josh 1af1569f63 Updated CHANGELOG.md 2025-04-07 11:13:27 -04:00
Polymeta dbd2f5d340 Merge branch 'implement_pokemon' into 'main'
Implemented many pokemon

See merge request cable-mc/cobblemon!1585
2025-04-07 09:35:47 +00:00
Frank The FarmerandPolymeta 86bf413f00 Implemented many pokemon 2025-04-07 09:35:46 +00:00
Landon f13e7d03a1 Merge branch 'camera-changes-third-person' into 'main'
Riding Third Person Camera

See merge request cable-mc/cobblemon!1582
2025-04-07 02:51:07 +00:00
JordanandLandon 4a025ac65e Riding Third Person Camera 2025-04-07 02:51:07 +00:00
Josh 8a0c98c9ab Renamed command to transformmodelpart 2025-04-06 18:58:13 -04:00
Josh 36c5fe2419 Properly display name of model part. 2025-04-06 18:52:15 -04:00
Josh 0c79d102cf Changed changejointscale command into transformpartcommand with can cahnge the position rotation and scale of any joint on a model. 2025-04-06 18:31:39 -04:00
Polymeta b14b25ae19 Merge branch 'SaferSendouts' into 'main'
Safer sendouts

See merge request cable-mc/cobblemon!1458
2025-04-06 20:58:09 +00:00
Josh bd8f644b9a Add scaling as an option when creating transformedParts 2025-04-06 10:56:10 -04:00
Josh 7c84cdb05d transformedParts must add to the model part rather than replacing it with set() 2025-04-06 10:54:00 -04:00
Landon Wills 0293c0e811 Update licenses 2025-04-06 04:02:10 -04:00
Landon 125975e25f Merge branch 'item-with-components-swap' into 'main'
Fixed not being able to swap held item of the same type, but have different components.

See merge request cable-mc/cobblemon!1580
2025-04-06 05:42:42 +00:00
Landon Wills c7223ae898 Fix some issues with the new composite system, move assets to new system 2025-04-06 01:35:46 -04:00
Landon baf766298a Merge branch 'riding-ground-render-revert' into 'main'
revert broken ground rendering code. Correctly set stale yrot value on return...

See merge request cable-mc/cobblemon!1583
2025-04-06 05:11:30 +00:00
Jackson BowesandLandon 83580e7e8b revert broken ground rendering code. Correctly set stale yrot value on return... 2025-04-06 05:11:30 +00:00
Landon Wills 38f5c12dce Remove some redundant casting 2025-04-05 23:59:47 -04:00
Landon Wills e6a9a27692 Forgot to remove some code I refactored 2025-04-05 23:59:21 -04:00
Landon Wills 7ff413c401 Add support for custom behaviour settings for different composite strategies 2025-04-05 23:57:30 -04:00
Test 90335ccb2d hide mark scroll when unused 2025-04-05 17:26:09 -04:00
Landon Wills 603f99e5f3 Refactor composite behaviours 2025-04-05 04:24:19 -04:00
Landon Wills 1b53a127af Add lapras assets 2025-04-05 01:39:02 -04:00
Josh 9679929694 Merge branch 'main' into 'item-with-components-swap'
# Conflicts:
#   common/src/main/kotlin/com/cobblemon/mod/common/entity/pokemon/PokemonEntity.kt
2025-04-05 02:01:27 +00:00
Landon fd6fb5db47 Merge branch 'riding-friction-change' into 'main'
Decouple minecraft friction logic for better feeling controllers. Limit speed...

See merge request cable-mc/cobblemon!1581
2025-04-05 00:21:27 +00:00
Jackson BowesandLandon a80ada8590 Decouple minecraft friction logic for better feeling controllers. Limit speed... 2025-04-05 00:21:27 +00:00
Landon c4a4b9cb86 Merge branch 'multiplayer-riding' into 'main'
Multiplayer support for riding

See merge request cable-mc/cobblemon!1578
2025-04-04 21:15:55 +00:00
Josh 5af96e75eb Fixed message when swapping items.
Used `displayName` rather than `hoverName` for more dynamic text in message.
2025-04-04 13:35:09 -04:00
Josh 023a01befd Can now swap pokemon's held items if the components are different. 2025-04-04 13:03:49 -04:00
Polymeta 4912a89e6e pokemon are allowed to turn their heads when they are not ridden
Signed-off-by: Polymeta <40173118+Polymeta@users.noreply.github.com>
2025-04-04 13:12:16 +01:00
Polymeta f2e10d1b01 Merge branch 'refs/heads/main' into multiplayer-riding
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonNetwork.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/entity/pokemon/PokemonEntity.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/net/messages/client/spawn/SpawnPokemonPacket.kt
2025-04-04 12:54:10 +01:00
Hiroku 25eb197491 Merge branch 'ribs' into 'main'
🎀

See merge request cable-mc/cobblemon!1566
2025-04-04 10:13:58 +00:00
TestandHiroku 8de233b572 🎀 2025-04-04 10:13:58 +00:00
Landon Wills 83972ca9e6 Hook up a couple more pokemon for bird controller 2025-04-04 03:50:34 -04:00
Landon Wills 35e2190cae Reset orientation properly on landing 2025-04-04 02:33:23 -04:00
Landon Wills b966cd2db0 Fix issue with orientation tracking for rendering 2025-04-04 02:06:06 -04:00
Landon Wills 3e492541cc Fix issue with registry sync on riding properties 2025-04-04 01:40:27 -04:00
JazzMcNade cc2022ed59 Merge branch 'main' into SaferSendouts 2025-04-03 20:30:24 -05:00
Josh 298fdf45d5 Adjusted default position and scale values for wearables. Any additional transformations for a specific pokemon should be done in its posers. 2025-04-03 20:47:09 -04:00
Polymeta 4e9541d7f5 licenses
Signed-off-by: Polymeta <40173118+Polymeta@users.noreply.github.com>
2025-04-03 23:24:11 +01:00
Josh 1dab5b8c7c Added debug models to help with aligning wearable items on pokemon. 2025-04-03 17:03:26 -04:00
Landon Wills 2875e9efa9 Temporary fix to crashing issue due to pokemon sticking around when they shouldnt be 2025-04-03 16:27:44 -04:00
Landon Wills 2b42fff66b Fix issue with players sometimes launching at odd angles when riding 2025-04-03 16:26:54 -04:00
Landon Wills 643a33a643 Prevent crouching moving the player when riding pokemon 2025-04-03 16:26:34 -04:00
Landon Wills e348c48eff Fix reversed third person camera 2025-04-03 14:25:03 -04:00
Landon Wills 473f58cab9 No more random render flickers yippee 2025-04-03 13:49:18 -04:00
Landon Wills 987b8619a3 Specify side for riding state management, add multiplayer wing flap for charizard 2025-04-03 05:11:33 -04:00
Landon Wills 87632941e8 Fix issue with player not rotating on mount 2025-04-03 02:25:11 -04:00
Landon Wills 041acab7ca Optimize change detection on riding sync 2025-04-03 02:06:23 -04:00
Landon Wills b3de7d1c1a Synchronize riding state to clients 2025-04-03 01:25:30 -04:00
Landon Wills afcb2dd77c Use render orientation instead of raw orientation for local players 2025-04-03 00:12:36 -04:00
Landon Wills a7bd6d2bf4 Update Garchomp, change translation in mounted player rendering 2025-04-02 23:54:22 -04:00
Landon Wills b096cb60a3 Use more precise orientation for posable model if player is local 2025-04-02 23:16:34 -04:00
JPAK 7f1c0c3d4c Fixed culling. It really was that easy xd 2025-04-02 07:23:26 -05:00
Landon Wills a4f0bf9d2f Fix issue with the world ocasionally playing hide and seek when rolling in first person 2025-04-02 01:57:40 -04:00
JacksonB f46c748e9f Add forgotten bird logic 2025-04-02 00:07:37 -05:00
Landon Wills 065479c58c Make rendering smooth for rolling players & entities 2025-04-02 00:16:05 -04:00
JacksonB eee3d46a2d Keep a render orientation that interpolates behind the last received orientation and damp the slerp by a static amount frame to frame. 2025-04-01 21:07:21 -05:00
Hiroku d7b43fcbfc Merge branch 'spicy-molang' into 'main'
Added `pokemon` as an available Molang function for the `battleActor` functions

See merge request cable-mc/cobblemon!1577
2025-04-01 20:21:18 +00:00
Landon Wills c82e011f73 Pull together riding branches, trying to fix render stuttering 2025-04-01 13:18:50 -04:00
Spicy bf1b996962 Added pokemon as an available Molang function for the battleActor functions 2025-04-01 09:12:16 -05:00
Landon Wills fc46cdcda0 Merge remote-tracking branch 'refs/remotes/origin/camera-changes' into test
# Conflicts:
#	common/src/main/java/com/cobblemon/mod/common/mixin/client/CameraMixin.java
#	common/src/main/kotlin/com/cobblemon/mod/common/client/render/player/MountedPlayerRenderer.kt
2025-04-01 09:24:11 -04:00
Landon Wills 4e7b029afb Merge remote-tracking branch 'refs/remotes/origin/multiplayer-roll-sync' into test 2025-04-01 09:22:19 -04:00
Landon Wills 5430f02770 Add much more context to pose provider, optimize velocity tracking, add sync to server 2025-04-01 09:20:51 -04:00
Landon Wills 6a51abe3ee Axe more of the old system, fix some issues with deserialization 2025-04-01 06:48:21 -04:00
Landon Wills 008652b442 Forgot a change 2025-04-01 06:32:11 -04:00
Landon Wills c1e088eb33 Hook new riding infrastructure up to serialization, axe the old controllers 2025-04-01 06:31:33 -04:00
Landon Wills 1669a2f428 Make all riding behaviour settings encodable 2025-04-01 05:44:18 -04:00
Landon Wills 2ba63f4641 Redirect PokemonEntity and mixins to new riding system 2025-04-01 05:29:17 -04:00
Landon Wills 41b9e9001d Refactor riding system 2025-04-01 03:48:45 -04:00
Landon Wills f058d6e635 More work on controllers. Poses borked again 2025-03-31 20:46:11 -04:00
Landon Wills f9377b7489 Some progress on data transfer for ride controllers (currently not functional) 2025-03-31 17:55:59 -04:00
Polymeta e59b5094ad correctly warp world as holder inside spawnfilters
Signed-off-by: Polymeta <40173118+Polymeta@users.noreply.github.com>
2025-03-31 11:50:54 +01:00
Josh a2fef9f1be Moved WearableItemModel functions out of HeldItemRenderer.kt 2025-03-30 21:24:13 -04:00
Josh 3d4df2c098 Fixed item rendering not falling back to the item locator if other locators are not present. 2025-03-30 21:11:05 -04:00
Josh 7557cab03d Fixed item visibility button not showing the right icon when opening summary or switching pokemon. 2025-03-30 20:55:28 -04:00
Josh f1018b1da9 Simplified HeldItemRenderer to use only one render function that renders an Item on a PosableModel. And fixed the item lighting in the summary menu. 2025-03-30 20:03:07 -04:00
Josh 5af4e3bddb Added placeholder models for wearable items and logic to register the models. 2025-03-30 02:03:44 -04:00
Josh 1f519cae50 Fixed rotation of items rending in PokemonOnShoulderRenderer and ModelWidget 2025-03-29 20:01:04 -04:00
JazzMcNade 91864b13d0 Moved GetSendOutPosition into ActiveBattlePokemon.kt 2025-03-06 01:45:58 -06:00
JazzMcNade adc8e9921a Updated Changelog 2025-03-04 21:54:23 -06:00
JazzMcNade 5e5473acae Muted timing console logging 2025-03-04 21:50:28 -06:00
JazzMcNade 3509944f1a Comment, fixed indentation 2025-03-03 20:53:42 -06:00
JazzMcNade 8a5489f805 Merge branch 'main' into SaferSendouts 2025-03-03 20:44:26 -06:00
JPAK 922ee95fd2 Camera changes for riding. Need to find a better system for being "unlocked" (in third person only) to make the animations feel better. 2025-03-03 16:22:13 -06:00
JazzMcNade 3b7d56ff45 Merge branch 'main' into SaferSendouts 2025-02-16 20:53:08 -06:00
JazzMcNade 50c567d264 Clean up, scale look radius with size 2025-01-04 18:35:14 -06:00
JazzMcNade d4964e41d3 Clean up, fixed fluid handling 2025-01-03 22:04:23 -06:00
JazzMcNade 41b1c169c3 Merge branch 'main' into SaferSendouts 2025-01-03 20:49:13 -06:00
JazzMcNade 49e2001004 clean up 2025-01-03 20:48:28 -06:00
JazzMcNade 66393f9293 Merge branch 'main' into SaferSendouts 2025-01-02 21:11:07 -06:00
JazzMcNade 7b3b6146a1 Single raycast to avoid spawning in walls, setPositionSafely can no longer path into a wall 2024-12-30 20:44:32 -06:00
JazzMcNade e1452244c2 Merge branch 'main' into SaferSendouts 2024-12-30 12:50:24 -06:00
JazzMcNade 5358414a0c Merge branch 'main' into SaferSendouts 2024-12-24 15:27:25 -06:00
JazzMcNade d691373919 Capped collision box size. Added trace to help prevent attempts to send outs going into walls 2024-12-15 20:14:30 -06:00
JazzMcNade 038f13e6fa Merge branch 'main' into SaferSendouts 2024-12-15 00:52:31 -06:00
JazzMcNade dd288a3b0c SetPositionSafely now implements BFS WIP 2024-12-08 21:39:17 -06:00
1017 changed files with 79600 additions and 17331 deletions
+12
View File
@@ -0,0 +1,12 @@
### Additions
- Added functionality for marks.
- Added data for all marks and all title ribbons from Pokémon.
- Added marks screen within the summary UI.
- Added `/givemark <player> <partySlot> <mark>` command to give a mark to a Pokémon.
- Added `/takemark <player> <partySlot> <mark>` command to remove a mark from a Pokémon.
- Added `/giveallmarks <player> <partySlot>` command to give all available marks to a Pokémon.
### MoLang & Datapacks
- Added Flows for `bobber_spawn_pokemon_post`, `pokemon_entity_spawn`, and `fossil_revived`.
- Added `marks` datapack folder.
- Added MoLang functions `entity.is_in_rain`, `pokemon.add_marks(Mark...)`, `pokemon.add_potential_marks(Mark...)`, `pokemon.add_marks_with_chance(Mark...)`, `pokemon.apply_potential_marks`, and `world.is_snowing_at(x, y, z)`.
+100 -1
View File
@@ -11,7 +11,94 @@
- Added cosmetic item functionality for Pokémon. Certain cosmetic items can be given to applicable Pokémon via the interact menu.
- Added the various log blocks as cosmetic items for Timburr and Komala.
- Added `visibility/hidden`, `visibility/hat` and `visibility/face` tags to control where and how certain items are rendered.
- Added Pokémon markings, toggleable within the summary.
- Added `/boxcount` command to change PC boxes amount
- Added cosmetics for Gurdurr, Conkeldurr, Squirtle Line, Sneasler, Sandile line, Treecko line, Braixen, Delphox, and Dragonite.
- Added cosmetics for Gurdurr, Conkeldurr, Squirtle Line, Sneasler, Sandle line, Treecko line, Braixen, Delphox, and Dragonite.
- Added `/transformmodelpart (position|rotation|scale) <modelPart> <transform: x y z>` command that can add transformations to a pokemon's model part.
- The player executing the command must be facing the target pokemon entity. Transformations are not persistent and will revert when resources are reloaded.
### Pokémon Added
#### Gen 2
- Dunsparce
#### Gen 3
- Spoink
- Grumpig
- Snorunt
- Glalie
#### Gen 4
- Bronzor
- Bronzong
- Croagunk
- Froslass
#### Gen 5
- Pansage
- Simisage
- Pansear
- Simisear
- Panpour
- Simipour
- Munna
- Musharna
- Blitzle
- Zebstrika
- Trubbish
- Garbodor
#### Gen 6
- Noibat
- Noivern
#### Gen 7
- Drampa
#### Gen 8
- Silicobra
- Sandaconda
#### Gen 9
- Smoliv
- Dolliv
- Arboliva
- Orthworm
- Dudunsparce
### Animation updates for the following Pokémon
- Garchomp
- Tropius
- Torpius
- Nosepass
- Probopass
- Sneasler
- Braixen
- Delphox
### Model updates for the following Pokémon
- Gyarados
- Dragonite
- Eevee
- Vaporeon
- Jolteon
- Flareon
- Espeon
- Umbreon
- Leafeon
- Glaceon
- Sylveon
- Treecko
- Grovyle
- Sceptile
- Honchkrow
- Garchomp
- Pidgeot
- Nosepass
- Probopass
- Magnezone
- Metagross
### Changes
- Renamed `chargeGainedPerTick` config to `secondsToChargeHealingMachine`.
@@ -20,6 +107,8 @@
- When using the `cobblemon` or `generation_9` capture calculators a critical capture with a single shake will always play for successful captures when you've already registered the Pokémon as caught in your Pokédex.
- Improved the performance of saving Pokédex and player data.
- Pokémon hitbox now scales with entity attribute `generic.scale`.
- Removed Shulker aspect and replaced it with cosmetic_item-shulker_shell.
- Shulker shell Forretress is now a cosmetic rather than a special evo and thus all shulker Forretress will revert back to normal until a shulker shell is put in their cosmetic slot.
### Fixes
- Fixed Particles sometimes facing the wrong direction (looking at you, Swords Dance)
@@ -42,6 +131,7 @@
- Fixed field name in evolution requirements for Spewpa Pokeball.
- Fixed LevelUpCriterion logic to correctly check that the Pokémon is a preEvo.
- Fixed `hide_additional_tooltip` vanilla flag not properly hiding tooltips on pokerod and bait items
- Removed a number of scenarios in which a Pokémon battle may send out a Pokémon into collision geometry.
- Fixed NPCs using Pokémon outside of their pool when a Pokémon name had a typo.
- Fixed an issue with datapacked species features not being applied properly when relogging.
- Fixed Pokémon marked as silent still playing shiny sounds and effects.
@@ -64,6 +154,7 @@
- Updated NPCEntity beam positioning to properly account for the baseScale property.
- Updated NPCEntity pokeball throw positioning to properly account for the baseScale property.
- Fixed `[Pokemon].copyFrom` error causing forms, IVs, and EVs to not be applied properly when using `[Pokemon].loadFromJSON` or `[Pokemon].loadFromNBT`
- Added new item class, `WearableItem`. Instances of this class should have a corresponding 3D model. These models render when the items display context is `HEAD`.
### MoLang & Datapacks
- The following usages for item predicates can now use item conditions like advancements do, you can learn about them in the [Minecraft wiki](https://minecraft.wiki/w/Advancement_definition#minecraft:filled_bucket)
@@ -74,7 +165,14 @@
- Added `interpolate` boolean property to animated textures to allow gradual colour changes between frames.
- Fixed species additions not being capable of changing implemented status.
- Added support for action effects that are triggered by `|-activate|` Showdown instructions. `activate_{effect_id}` is the syntax.
- Added Molang functions for rendering items `render_item(item_id, locator_name)` and `clear_items()`.
- Added MoLang functions for rendering items `render_item(item_id, locator_name)` and `clear_items()`.
- Fixed location spawn filter components causing crashes
- Added `pokemon` as an available MoLang function for the `battleActor` functions.
- Added `spawn_pokemon` as an available MoLang function for the `worldHolder` functions.
- Added `attempt_wild_battle` as an available MoLang function.
- Added `pokemon` as an available Molang function for the `battleActor` functions.
- Fixed `heldItem` property inside spawn files not working and causing crashes
- Fixed `spawn_bedrock_particles` MoLang causing crashes when used in a server environment
## [1.6.1 (January 26th, 2025)](#1-6-1)
@@ -180,6 +278,7 @@
- Geodude
- Graveler
- Golem
- Magnezone
### Cry updates for the following Pokémon
- Sceptile
@@ -23,16 +23,15 @@ public abstract class LocalPlayerMixin {
@Inject(method = "getJumpRidingScale", at = @At("HEAD"), cancellable = true)
public void modifyJumpRidingScale(CallbackInfoReturnable<Float> cir) {
LocalPlayer player = (LocalPlayer) (Object) this;
if (player.isPassenger() && player.getVehicle() instanceof PokemonEntity) {
PokemonEntity ride = (PokemonEntity) player.getVehicle();
//Use custom jump bar logic if the current ride does not jump using it.
if( !ride.getRiding().canJump(ride, player))
{
cir.setReturnValue(ride.setRideBar());
if (player.isPassenger() && player.getVehicle() instanceof PokemonEntity pokemonEntity) {
var rideValue = pokemonEntity.<Float>ifRidingAvailableSupply(null, (behaviour, settings, state) -> {
if (behaviour.canJump(settings, state, pokemonEntity, player)) return null;
return behaviour.setRideBar(settings, state, pokemonEntity, player);
});
if (rideValue != null) {
cir.setReturnValue(rideValue);
}
}
}
}
}
@@ -15,10 +15,12 @@ import com.cobblemon.mod.common.api.events.CobblemonEvents;
import com.cobblemon.mod.common.api.events.item.LeftoversCreatedEvent;
import com.cobblemon.mod.common.api.orientation.OrientationController;
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies;
import com.cobblemon.mod.common.api.riding.Rideable;
import com.cobblemon.mod.common.api.riding.Seat;
import com.cobblemon.mod.common.api.storage.party.PartyStore;
import com.cobblemon.mod.common.api.storage.party.PlayerPartyStore;
import com.cobblemon.mod.common.api.tags.CobblemonItemTags;
import com.cobblemon.mod.common.client.entity.PokemonClientDelegate;
import com.cobblemon.mod.common.client.render.MatrixWrapper;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import com.cobblemon.mod.common.pokedex.scanner.PokedexEntityData;
import com.cobblemon.mod.common.pokedex.scanner.ScannableEntity;
@@ -38,15 +40,20 @@ import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.*;
import net.minecraft.world.entity.item.ItemEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.food.FoodProperties;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.ClipContext;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.HitResult;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.Nullable;
import org.joml.Matrix3f;
import org.joml.Vector3f;
import org.objectweb.asm.Opcodes;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
@@ -55,10 +62,7 @@ import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.*;
import java.util.stream.Collectors;
@Mixin(Player.class)
@@ -80,7 +84,10 @@ public abstract class PlayerMixin extends LivingEntity implements ScannableEntit
@Shadow public abstract void displayClientMessage(Component message, boolean overlay);
@Unique private OrientationController cobblemon$orientationController = new OrientationController(this);
@Shadow @Final private static Map<Pose, EntityDimensions> POSES;
@Shadow @Final public static EntityDimensions STANDING_DIMENSIONS;
@Unique
private final OrientationController cobblemon$orientationController = new OrientationController(this);
protected PlayerMixin(EntityType<? extends LivingEntity> p_20966_, Level p_20967_) {
super(p_20966_, p_20967_);
@@ -108,6 +115,19 @@ public abstract class PlayerMixin extends LivingEntity implements ScannableEntit
}
}
@Inject(method = "tick", at = @At("HEAD"))
private void cobblemon$updateRenderOrientation(CallbackInfo ci) {
this.cobblemon$orientationController.tick();
}
@Override
public EntityDimensions getDefaultDimensions(Pose pose) {
if (this.getVehicle() instanceof PokemonEntity) {
return STANDING_DIMENSIONS;
}
return POSES.getOrDefault(pose, STANDING_DIMENSIONS);
}
@Inject(
method = "removeEntitiesOnShoulder",
at = @At(
@@ -252,8 +272,10 @@ public abstract class PlayerMixin extends LivingEntity implements ScannableEntit
at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;wantsToStopRiding()Z")
)
public boolean delegateDismountToController(Player instance, Operation<Boolean> original) {
if (this.getVehicle() instanceof Rideable vehicle) {
return vehicle.getRiding().dismountOnShift() && original.call(instance);
if (this.getVehicle() instanceof PokemonEntity pokemonEntity) {
return pokemonEntity.ifRidingAvailableSupply(false, (behaviour, settings, state) -> {
return behaviour.dismountOnShift(settings, state, pokemonEntity) && original.call(instance);
});
}
return original.call(instance);
}
@@ -262,4 +284,41 @@ public abstract class PlayerMixin extends LivingEntity implements ScannableEntit
public OrientationController getOrientationController() {
return cobblemon$orientationController;
}
@Override
public HitResult pick(double hitDistance, float partialTicks, boolean hitFluids) {
Entity vehicle = this.getVehicle();
if (vehicle instanceof PokemonEntity pokemonEntity) {
int seatIndex = pokemonEntity.getPassengers().indexOf(this);
Seat seat = pokemonEntity.getSeats().get(seatIndex);
PokemonClientDelegate delegate = (PokemonClientDelegate) pokemonEntity.getDelegate();
MatrixWrapper locator = delegate.getLocatorStates().get(seat.getLocator());
if (locator == null) {
super.pick(hitDistance, partialTicks, hitFluids);
}
Vec3 locatorOffset = new Vec3(locator.getMatrix().getTranslation(new Vector3f()));
OrientationController controller = this.getOrientationController();
float currEyeHeight = this.getEyeHeight();
Matrix3f orientation = controller.isActive() && controller.getOrientation() != null ? controller.getOrientation() : new Matrix3f();
Vec3 rotatedEyeHeight = new Vec3(orientation.transform(new Vector3f(0f, currEyeHeight - (this.getBbHeight() / 2), 0f)));
Vec3 eyePosition = locatorOffset.add(pokemonEntity.position()).add(rotatedEyeHeight);
Vec3 viewVector = this.getViewVector(partialTicks);
Vec3 viewDistanceVector = eyePosition.add(viewVector.x * hitDistance, viewVector.y * hitDistance, viewVector.z * hitDistance);
return this.level()
.clip(
new ClipContext(
eyePosition, viewDistanceVector, ClipContext.Block.OUTLINE, hitFluids ? net.minecraft.world.level.ClipContext.Fluid.ANY : net.minecraft.world.level.ClipContext.Fluid.NONE, this
)
);
}
return super.pick(hitDistance, partialTicks, hitFluids);
}
}
@@ -10,10 +10,13 @@ package com.cobblemon.mod.common.mixin;
import com.cobblemon.mod.common.CobblemonNetwork;
import com.cobblemon.mod.common.OrientationControllable;
import com.cobblemon.mod.common.api.net.NetworkPacket;
import com.cobblemon.mod.common.api.orientation.OrientationController;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import com.cobblemon.mod.common.mixin.accessor.ChunkMapAccessor;
import com.cobblemon.mod.common.mixin.accessor.TrackedEntityAccessor;
import com.cobblemon.mod.common.net.messages.client.orientation.S2CUpdateOrientationPacket;
import com.cobblemon.mod.common.net.messages.client.orientation.ClientboundUpdateOrientationPacket;
import com.cobblemon.mod.common.net.messages.client.pokemon.update.ClientboundUpdateRidingStatePacket;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import net.minecraft.network.protocol.Packet;
import net.minecraft.server.level.ChunkMap;
@@ -37,28 +40,50 @@ import java.util.function.Consumer;
@Mixin(ServerEntity.class)
public abstract class ServerEntityMixin {
@Shadow @Final private Consumer<Packet<?>> broadcast;
@Shadow @Final private Entity entity;
@Shadow
@Final
private Consumer<Packet<?>> broadcast;
@Shadow
@Final
private Entity entity;
@Unique
private Matrix3f cobblemon$lastSentOrientation;
@Unique
private boolean cobblemon$lastSentActive;
@Inject(method = "sendChanges", at = @At("TAIL"))
private void cobblemon$sendOrientationChanges(CallbackInfo ci) {
private void cobblemon$sendChanges(CallbackInfo ci) {
cobblemon$sendOrientationChanges();
cobblemon$sendRidingStateChanges();
}
private void cobblemon$sendRidingStateChanges() {
if (!(this.entity instanceof PokemonEntity pokemonEntity)) return;
if (pokemonEntity.getRidingState() == null) return;
if (pokemonEntity.getRiding() == null) return;
var ridingBehaviour = pokemonEntity.getRiding();
var ridingState = pokemonEntity.getRidingState();
var previousRidingState = pokemonEntity.getPreviousRidingState();
if (previousRidingState != null && !ridingState.shouldSync(previousRidingState)) return;
cobblemon$broadcast(new ClientboundUpdateRidingStatePacket(pokemonEntity.getId(), ridingBehaviour.getKey(), pokemonEntity.getRidingState(), null));
}
private void cobblemon$sendOrientationChanges() {
if (!(this.entity instanceof OrientationControllable controllable)) return;
OrientationController controller = controllable.getOrientationController();
Matrix3f currOrientation = controller.getOrientation();
boolean currActive = controller.getActive();
if (currOrientation == null || currOrientation.equals(cobblemon$lastSentOrientation) && (currActive == cobblemon$lastSentActive)) return;
if (currOrientation == null || currOrientation.equals(cobblemon$lastSentOrientation) && (currActive == cobblemon$lastSentActive))
return;
cobblemon$lastSentOrientation = new Matrix3f(currOrientation);
cobblemon$lastSentActive = currActive;
cobblemon$broadcast(new ClientboundUpdateOrientationPacket(currOrientation, currActive, entity.getId()));
}
private void cobblemon$broadcast(NetworkPacket<?> packet) {
if (!(entity.level() instanceof ServerLevel level)) return;
ChunkMap chunkMap = level.getChunkSource().chunkMap;
Int2ObjectMap<?> entityMap = ((ChunkMapAccessor) chunkMap).getEntityMap();
Object tracked = entityMap.get(entity.getId());
@@ -68,35 +93,8 @@ public abstract class ServerEntityMixin {
for (ServerPlayerConnection conn : seenBy) {
ServerPlayer player = conn.getPlayer();
if (player == entity) continue;
CobblemonNetwork.INSTANCE.sendPacketToPlayer(player, new S2CUpdateOrientationPacket(currOrientation, currActive, entity.getId()));
CobblemonNetwork.INSTANCE.sendPacketToPlayer(player, packet);
}
/*
OrientationController controller = controllable.getOrientationController();
Matrix3f currOrientation = controller.getOrientation();
if(currOrientation != cobblemon$lastSentOrientation) {
cobblemon$lastSentOrientation = new Matrix3f(currOrientation);
//this.broadcast.accept(new S2CUpdateOrientationPacket(currOrientation,this.entity.getId()))
if (!(entity.level() instanceof ServerLevel level)) return;
ChunkMap chunkMap = level.getChunkSource().chunkMap;
Long2ObjectMap<ChunkMap.TrackedEntity> entityMap = ((ChunkMapAccessor) chunkMap).getEntityMap();
TrackedEntity trackedEntity = entityMap.get(entity.getId());
if (trackedEntity == null) return;
Set<ServerPlayerConnection> seenBy = ((TrackedEntityAccessor) trackedEntity).getSeenBy();
for (ServerPlayerConnection conn : seenBy) {
ServerPlayer player = conn.getPlayer();
if (player == entity) continue; // optional safety filter
CobblemonNetwork.sendPacketToPlayer(player, new S2CUpdateOrientationPacket(orientation, entity.getId()));
}
*/
}
@@ -32,7 +32,9 @@ public abstract class ServerPlayerMixin {
var playerVehicle = player.getVehicle();
if (playerVehicle == null) return false;
if (!(playerVehicle instanceof PokemonEntity pokemonEntity)) return false;
return pokemonEntity.getRiding().shouldRoll(pokemonEntity);
return pokemonEntity.ifRidingAvailableSupply(false, (behaviour, settings, state) -> {
return behaviour.shouldRoll(settings, state, pokemonEntity);
});
}
@Inject(method = "stopRiding", at = @At("HEAD"))
@@ -41,4 +43,4 @@ public abstract class ServerPlayerMixin {
controllable.getOrientationController().setActive(false);
}
}
}
@@ -8,10 +8,30 @@
package com.cobblemon.mod.common.mixin.client;
import com.cobblemon.mod.common.Cobblemon;
import com.cobblemon.mod.common.OrientationControllable;
import com.cobblemon.mod.common.api.orientation.OrientationController;
import com.cobblemon.mod.common.api.riding.Rideable;
import com.cobblemon.mod.common.api.riding.Seat;
import com.cobblemon.mod.common.client.MountedPokemonAnimationRenderController;
import com.cobblemon.mod.common.client.entity.PokemonClientDelegate;
import com.cobblemon.mod.common.client.render.MatrixWrapper;
import com.cobblemon.mod.common.client.render.models.blockbench.FloatingState;
import com.cobblemon.mod.common.client.render.models.blockbench.PosableModel;
import com.cobblemon.mod.common.client.render.models.blockbench.repository.VaryingModelRepository;
import com.cobblemon.mod.common.config.CobblemonConfig;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import com.llamalad7.mixinextras.sugar.Local;
import net.minecraft.client.Camera;
import net.minecraft.client.Minecraft;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.ClipContext;
import net.minecraft.world.phys.HitResult;
import net.minecraft.world.phys.Vec3;
import org.joml.Matrix3f;
import org.joml.Quaternionf;
import org.joml.Vector3f;
@@ -23,10 +43,10 @@ import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.Map;
@Mixin(Camera.class)
public abstract class CameraMixin {
@Shadow @Final private static float DEFAULT_CAMERA_DISTANCE;
@Shadow private Entity entity;
@Shadow @Final private Quaternionf rotation;
@Shadow @Final private Vector3f forwards;
@@ -35,6 +55,15 @@ public abstract class CameraMixin {
@Shadow private float xRot;
@Shadow private float yRot;
@Shadow private float eyeHeight;
@Shadow private float eyeHeightOld;
@Shadow protected abstract void setPosition(Vec3 pos);
@Shadow
private BlockGetter level;
@Shadow private float partialTickTime;
@Unique private float returnTimer = 0;
@Unique private float rollAngleStart = 0;
@Unique Minecraft minecraft = Minecraft.getInstance();
@@ -42,7 +71,7 @@ public abstract class CameraMixin {
@Unique boolean disableRollableCameraDebug = false;
@Inject(method = "setRotation", at = @At("HEAD"), cancellable = true)
public void open_camera$setRotation(float f, float g, CallbackInfo ci) {
public void cobblemon$setRotation(float f, float g, CallbackInfo ci) {
if (!(this.entity instanceof OrientationControllable controllable) || disableRollableCameraDebug) return;
var controller = controllable.getOrientationController();
if (!controller.isActive() && controller.getOrientation() != null) {
@@ -60,7 +89,7 @@ public abstract class CameraMixin {
return;
}
controller.getOrientation().rotateZ((float) Math.toRadians(-rollAngleStart*(1-returnTimer)));
applyRotation();
cobblemon$applyRotation();
this.returnTimer += .05F;
ci.cancel();
} else {
@@ -69,27 +98,163 @@ public abstract class CameraMixin {
}
return;
}
if (controller.getOrientation() == null) return;
applyRotation();
if (controller.getOrientation() == null && !Cobblemon.config.getThirdPartyViewBobbing()) return;
cobblemon$applyRotation();
this.returnTimer = 0;
this.rollAngleStart = controller.getRoll();
ci.cancel();
}
@Inject(method = "setup", at = @At("HEAD"))
private void cobblemon$prepAnimation(BlockGetter level, Entity entity, boolean detached, boolean thirdPersonReverse, float partialTick, CallbackInfo ci) {
Entity vehicle = entity.getVehicle();
MountedPokemonAnimationRenderController.INSTANCE.reset();
if (vehicle instanceof PokemonEntity pokemon) {
MountedPokemonAnimationRenderController.INSTANCE.setup(pokemon, partialTickTime);
}
}
//If you want to move this to a delagate you need an AW for position
@WrapOperation(method = "setup", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Camera;setPosition(DDD)V"))
public void cobblemon$positionCamera(Camera instance, double x, double y, double z, Operation<Void> original, @Local(ordinal = 1, argsOnly = true) boolean thirdPersonReverse) {
Entity entity = instance.getEntity();
Entity vehicle = entity.getVehicle();
if(vehicle instanceof Rideable){
//RidingCameraDelagate.INSTANCE.positionCamera(instance, x, y, z, original);
if (!(vehicle instanceof PokemonEntity pokemon)) {
original.call(instance, x, y, z);
return;
}
PokemonClientDelegate delegate = (PokemonClientDelegate) pokemon.getDelegate();
Vec3 entityPos = new Vec3(
Mth.lerp(instance.getPartialTickTime(), pokemon.xOld, pokemon.getX()),
Mth.lerp(instance.getPartialTickTime(), pokemon.yOld, pokemon.getY()),
Mth.lerp(instance.getPartialTickTime(), pokemon.zOld, pokemon.getZ())
);
int seatIndex = pokemon.getPassengers().indexOf(entity);
Seat seat = pokemon.getSeats().get(seatIndex);
OrientationControllable rollable = (OrientationControllable) entity;
OrientationController controller = rollable.getOrientationController();
if (!instance.isDetached() || Cobblemon.config.getThirdPartyViewBobbing()) {
MatrixWrapper locator = delegate.getLocatorStates().get(seat.getLocator());
if (locator == null) {
original.call(instance, x, y, z);
return;
}
Vec3 locatorOffset = new Vec3(locator.getMatrix().getTranslation(new Vector3f()));
float currEyeHeight = Mth.lerp(instance.getPartialTickTime(), eyeHeightOld, eyeHeight);
var rotatedEyeHeight = new Vector3f(0f, currEyeHeight - (entity.getBbHeight() / 2), 0f);
if (controller.isActive()) {
rotatedEyeHeight = controller.getRenderOrientation(partialTickTime).transform(rotatedEyeHeight);
}
Vec3 position = locatorOffset.add(entityPos).add(new Vec3(rotatedEyeHeight));
setPosition(position);
} else {
Vector3f pos = entityPos.add(new Vec3(0, pokemon.getBbHeight() / 2, 0)).toVector3f();
PosableModel model = VaryingModelRepository.INSTANCE.getPoser(pokemon.getPokemon().getSpecies().getResourceIdentifier(), new FloatingState());
Map<String, Vec3> cameraOffsets = model.getSeatToCameraOffset();
Vec3 cameraOffset;
if (thirdPersonReverse && cameraOffsets.containsKey(seat.getLocator() + "_reverse")) {
cameraOffset = cameraOffsets.get(seat.getLocator() + "_reverse");
} else if (cameraOffsets.containsKey(seat.getLocator())) {
cameraOffset = cameraOffsets.get(seat.getLocator());
} else {
cameraOffset = new Vec3(0f, 2f, 4f);
}
Vector3f offset = cameraOffset.toVector3f();
if (thirdPersonReverse) offset.z *= -1;
float xRot = (float) (-1 * Math.toRadians(instance.getXRot()));
Matrix3f orientation = controller.isActive() && controller.getOrientation() != null ? controller.getOrientation() : new Matrix3f().rotateY((float) Math.toRadians(180f - instance.getYRot())).rotateX(xRot);
Vector3f rotatedOffset = orientation.transform(offset);
float offsetDistance = rotatedOffset.length();
Vector3f offsetDirection = rotatedOffset.mul(1 / offsetDistance);
float maxZoom = cobblemon$getMaxZoom(offsetDistance, offsetDirection, pos);
setPosition(new Vec3(offsetDirection.mul(maxZoom).add(pos)));
}
} else {
original.call(instance, x, y, z);
}
}
@Unique
private void applyRotation(){
private void cobblemon$applyRotation(){
if (!(this.entity instanceof OrientationControllable controllable) || controllable.getOrientationController().getOrientation() == null) return;
var controller = controllable.getOrientationController();
var newRotation = controller.getOrientation().normal(new Matrix3f()).getNormalizedRotation(new Quaternionf());
if (this.minecraft.options.getCameraType().isMirrored()) {
newRotation.rotateY((float)Math.toRadians(180));
}
var vehicle = entity.getVehicle();
if (vehicle instanceof PokemonEntity pokemon) {
var delegate = (PokemonClientDelegate) pokemon.getDelegate();
int seatIndex = pokemon.getPassengers().indexOf(entity);
Seat seat = pokemon.getSeats().get(seatIndex);
MatrixWrapper locator = delegate.getLocatorStates().get(seat.getLocator());
if (locator != null) {
var locatorRotation = locator.getMatrix().getUnnormalizedRotation(new Quaternionf());
newRotation.mul(locatorRotation);
}
}
this.rotation.set(newRotation);
this.xRot = controller.getPitch();
this.yRot = controller.getYaw();
this.forwards.set(controller.getForwardVector());
this.up.set(controller.getUpVector());
this.left.set(controller.getLeftVector());
var forwardVector = newRotation.transform(OrientationController.Companion.getFORWARDS(), new Vector3f());
var upVector = newRotation.transform(OrientationController.Companion.getUP(), new Vector3f());
var leftVector = newRotation.transform(OrientationController.Companion.getLEFT(), new Vector3f());
this.xRot = (float)Mth.wrapDegrees(-Math.toDegrees(Math.asin(forwardVector.y)));
this.yRot = Mth.wrapDegrees((float)-Math.toDegrees(OrientationController.Companion.getFORWARDS().angleSigned(forwardVector, OrientationController.Companion.getUP())) + 180);
this.forwards.set(forwardVector);
this.up.set(upVector);
this.left.set(leftVector);
}
@WrapOperation(method = "setup", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Camera;move(FFF)V", ordinal = 0))
public void stopMoveIfRolling(Camera instance, float zoom, float dy, float dx, Operation<Void> original) {
Entity entity = instance.getEntity();
Entity vehicle = entity.getVehicle();
if (!(vehicle instanceof PokemonEntity) || Cobblemon.config.getThirdPartyViewBobbing()) {
original.call(instance, zoom, dy, dx);
}
}
//Modified vanilla code
@Unique
private float cobblemon$getMaxZoom(float maxZoom, Vector3f directionVector, Vector3f positionVector) {
Vec3 pos = new Vec3(positionVector);
for (int i = 0; i < 8; i++) {
float g = (float) ((i & 1) * 2 - 1);
float h = (float) ((i >> 1 & 1) * 2 - 1);
float j = (float) ((i >> 2 & 1) * 2 - 1);
Vec3 vec3 = pos.add(g * 0.1F, h * 0.1F, j * 0.1F);
Vec3 vec32 = vec3.add(new Vec3(directionVector).scale(maxZoom));
HitResult hitResult = this.level.clip(new ClipContext(vec3, vec32, ClipContext.Block.VISUAL, ClipContext.Fluid.NONE, this.entity));
if (hitResult.getType() != HitResult.Type.MISS) {
float k = (float) hitResult.getLocation().distanceToSqr(pos);
if (k < Mth.square(maxZoom)) {
maxZoom = Mth.sqrt(k);
}
}
}
return maxZoom;
}
}
@@ -12,6 +12,7 @@ import com.cobblemon.mod.common.Cobblemon;
import com.cobblemon.mod.common.ModAPI;
import com.cobblemon.mod.common.item.PokeBallItem;
import com.cobblemon.mod.common.item.PokedexItem;
import com.cobblemon.mod.common.item.WearableItem;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.renderer.ItemModelShaper;
@@ -54,17 +55,18 @@ public abstract class ItemRendererMixin {
)
private void cobblemon$overrideItemModel(ItemStack stack, ItemDisplayContext renderMode, boolean leftHanded, PoseStack matrices, MultiBufferSource multiBufferSource, int light, int overlay, BakedModel model, CallbackInfo ci) {
boolean shouldBe2d = renderMode == ItemDisplayContext.GUI || renderMode == ItemDisplayContext.FIXED;
ResourceLocation resourceLocation = null;
if (shouldBe2d) {
ResourceLocation resourceLocation = null;
if (stack.getItem() instanceof PokeBallItem pokeBallItem) resourceLocation = pokeBallItem.getPokeBall().getModel2d();
else if (stack.getItem() instanceof PokedexItem pokedexItem) resourceLocation = pokedexItem.getType().getItemSpritePath();
}
if (renderMode != ItemDisplayContext.HEAD && stack.getItem() instanceof WearableItem wearableItem) resourceLocation = wearableItem.getModel2d();
if (resourceLocation != null) {
BakedModel replacementModel = this.itemModelShaper.getModelManager().getModel(new ModelResourceLocation(resourceLocation, "inventory"));
if (!cobblemon$isSameModel(model, replacementModel)) {
ci.cancel();
render(stack, renderMode, leftHanded, matrices, multiBufferSource, light, overlay, replacementModel);
}
if (resourceLocation != null) {
BakedModel replacementModel = this.itemModelShaper.getModelManager().getModel(new ModelResourceLocation(resourceLocation, "inventory"));
if (!cobblemon$isSameModel(model, replacementModel)) {
ci.cancel();
render(stack, renderMode, leftHanded, matrices, multiBufferSource, light, overlay, replacementModel);
}
}
}
@@ -78,6 +80,7 @@ public abstract class ItemRendererMixin {
boolean canOpenScreen = entity != null && ((entity.getOffhandItem() == stack && !(entity.getMainHandItem().getItem() instanceof PokedexItem)) || entity.getMainHandItem() == stack);
resourceLocation = pokedexItem.getType().getItemModelPath(isScanModel ? "scanning" : (canOpenScreen ? null : "off"));
}
else if (stack.getItem() instanceof WearableItem wearableItem) resourceLocation = wearableItem.getModel3d();
if (resourceLocation != null) {
BakedModel model = this.itemModelShaper.getModelManager().getModel(new ModelResourceLocation(resourceLocation, MODEL_PATH));
@@ -11,10 +11,9 @@ package com.cobblemon.mod.common.mixin.client;
import com.cobblemon.mod.common.CobblemonNetwork;
import com.cobblemon.mod.common.OrientationControllable;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import com.cobblemon.mod.common.net.messages.server.orientation.C2SUpdateOrientationPacket;
import com.cobblemon.mod.common.net.messages.server.orientation.ServerboundUpdateOrientationPacket;
import net.minecraft.client.Minecraft;
import net.minecraft.client.player.LocalPlayer;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.level.Level;
@@ -34,14 +33,14 @@ public abstract class LocalPlayerMixin extends LivingEntity {
super(entityType, level);
}
@Inject(method = "sendPosition", at = @At("TAIL"))
private void cobblemon$updateRotationMatrix(CallbackInfo ci) {
if (!(this instanceof OrientationControllable controllable)) return;
var controller = controllable.getOrientationController();
if (!controller.isActive() || controller.getOrientation() == cobblemon$lastOrientation) return;
cobblemon$lastOrientation = controller.getOrientation() != null ? new Matrix3f(controller.getOrientation()) : null;
CobblemonNetwork.INSTANCE.sendToServer(new C2SUpdateOrientationPacket(controller.getOrientation()));
}
// @Inject(method = "sendPosition", at = @At("TAIL"))
// private void cobblemon$updateRotationMatrix(CallbackInfo ci) {
// if (!(this instanceof OrientationControllable controllable)) return;
// var controller = controllable.getOrientationController();
// if (!controller.isActive() || controller.getOrientation() == cobblemon$lastOrientation) return;
// cobblemon$lastOrientation = controller.getOrientation() != null ? new Matrix3f(controller.getOrientation()) : null;
// CobblemonNetwork.INSTANCE.sendToServer(new ServerboundUpdateOrientationPacket(controller.getOrientation()));
// }
@Inject(method = "tick", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/multiplayer/ClientPacketListener;send(Lnet/minecraft/network/protocol/Packet;)V"))
private void cobblemon$updateRotationMatrixPassenger(CallbackInfo ci) {
@@ -49,7 +48,7 @@ public abstract class LocalPlayerMixin extends LivingEntity {
var controller = controllable.getOrientationController();
if (!controller.isActive() || controller.getOrientation() == cobblemon$lastOrientation) return;
cobblemon$lastOrientation = controller.getOrientation() != null ? new Matrix3f(controller.getOrientation()) : null;
CobblemonNetwork.INSTANCE.sendToServer(new C2SUpdateOrientationPacket(controller.getOrientation()));
CobblemonNetwork.INSTANCE.sendToServer(new ServerboundUpdateOrientationPacket(controller.getOrientation()));
}
@Inject(method = "rideTick", at = @At("HEAD"))
@@ -65,7 +64,9 @@ public abstract class LocalPlayerMixin extends LivingEntity {
var playerVehicle = player.getVehicle();
if (playerVehicle == null) return false;
if (!(playerVehicle instanceof PokemonEntity pokemonEntity)) return false;
return pokemonEntity.getRiding().shouldRoll(pokemonEntity);
return pokemonEntity.ifRidingAvailableSupply(false, (behaviour, settings, state) -> {
return behaviour.shouldRoll(settings, state, pokemonEntity);
});
}
@Override
@@ -15,6 +15,7 @@ import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import com.cobblemon.mod.common.pokedex.scanner.PokedexUsageContext;
import com.llamalad7.mixinextras.injector.v2.WrapWithCondition;
import com.llamalad7.mixinextras.sugar.Local;
import com.mojang.blaze3d.Blaze3D;
import net.minecraft.client.Minecraft;
import net.minecraft.client.MouseHandler;
import net.minecraft.client.player.LocalPlayer;
@@ -29,9 +30,10 @@ import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
@Mixin(MouseHandler.class)
public class MouseHandlerMixin {
public abstract class MouseHandlerMixin {
@Shadow private double accumulatedScrollY;
@Shadow @Final private Minecraft minecraft;
@@ -49,6 +51,14 @@ public class MouseHandlerMixin {
@Shadow private double accumulatedDY;
@Shadow private double lastHandleMovementTime;
@Shadow public abstract boolean isMouseGrabbed();
@Shadow protected abstract void turnPlayer(double movementTime);
@Unique private double cobblemon$timeDelta;
@Inject(
method = "onScroll",
at = @At(
@@ -161,16 +171,21 @@ public class MouseHandlerMixin {
return false;
}
@Inject(method = "handleAccumulatedMovement", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/MouseHandler;isMouseGrabbed()Z", ordinal = 0))
private void cobblemon$maintainMovementWhenInScreens(CallbackInfo ci, @Local(ordinal = 1) double e) {
@Inject(method = "handleAccumulatedMovement", at = @At("HEAD"))
private void cobblemon$maintainMovementWhenInScreens2(CallbackInfo ci) {
double time = Blaze3D.getTime();
cobblemon$timeDelta = time - this.lastHandleMovementTime;
}
@Inject(method = "handleAccumulatedMovement", at = @At("TAIL"))
private void cobblemon$maintainMovementWhenInScreens(CallbackInfo ci) {
if (minecraft.player == null) return;
if (!(minecraft.player instanceof OrientationControllable controllable)) return;
if (!controllable.getOrientationController().isActive()) return;
if (minecraft.isPaused()) return;
if (isMouseGrabbed()) return;
var pitch = pitchSmoother.getNewDeltaValue(0, e);
var roll = rollSmoother.getNewDeltaValue(0, e);
controllable.getOrientationController().rotate(0.0F, (float)pitch, (float)roll);
this.turnPlayer(cobblemon$timeDelta);
}
@Unique
@@ -182,8 +197,9 @@ public class MouseHandlerMixin {
var playerVehicle = player.getVehicle();
if (playerVehicle == null) return Vec3.ZERO;
if (!(playerVehicle instanceof PokemonEntity pokemonEntity)) return Vec3.ZERO;
return pokemonEntity.getRiding().angRollVel(pokemonEntity, player, deltaTime);
return pokemonEntity.ifRidingAvailableSupply(Vec3.ZERO, (behaviour, settings, state) -> {
return behaviour.angRollVel(settings, state, pokemonEntity, player, deltaTime);
});
}
@Unique
@@ -192,20 +208,23 @@ public class MouseHandlerMixin {
if (player == null) return Vec3.ZERO;
var vehicle = player.getVehicle();
if (vehicle == null) return Vec3.ZERO;
if (!(vehicle instanceof PokemonEntity pokemon)) return Vec3.ZERO;
if (!(vehicle instanceof PokemonEntity pokemonEntity)) return Vec3.ZERO;
var sensitivity = cobblemon$getRidingSensitivity();
return pokemon.getRiding().rotationOnMouseXY(
pokemon,
player,
mouseY,
mouseX,
yMouseSmoother,
xMouseSmoother,
sensitivity,
deltaTime
);
return pokemonEntity.ifRidingAvailableSupply(Vec3.ZERO, (behaviour, settings, state) -> {
return behaviour.rotationOnMouseXY(
settings,
state,
pokemonEntity,
player,
mouseY,
mouseX,
yMouseSmoother,
xMouseSmoother,
sensitivity,
deltaTime
);
});
}
@Unique
@@ -222,8 +241,9 @@ public class MouseHandlerMixin {
var playerVehicle = player.getVehicle();
if (playerVehicle == null) return true;
if (!(playerVehicle instanceof PokemonEntity pokemonEntity)) return true;
return pokemonEntity.getRiding().useAngVelSmoothing(pokemonEntity);
return pokemonEntity.ifRidingAvailableSupply(true, (behaviour, settings, state) -> {
return behaviour.useAngVelSmoothing(settings, state, pokemonEntity);
});
}
}
@@ -7,8 +7,7 @@
*/
package com.cobblemon.mod.common.mixin.client;
import com.cobblemon.mod.common.api.riding.Rideable;
import com.cobblemon.mod.common.api.riding.RidingManager;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import net.minecraft.client.model.PlayerModel;
import net.minecraft.world.entity.Entity;
@@ -22,13 +21,16 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(PlayerModel.class)
public class PlayerModelMixin {
@Inject(method = "setupAnim(Lnet/minecraft/world/entity/LivingEntity;FFFFF)V", at = @At(value = "HEAD"))
private void cobblemon$setHeadRotation(LivingEntity entity, float limbSwing, float limbSwingAmount, float ageInTicks, float netHeadYaw, float headPitch, CallbackInfo ci){
private void cobblemon$setHeadRotation(LivingEntity entity, float limbSwing, float limbSwingAmount, float ageInTicks, float netHeadYaw, float headPitch, CallbackInfo ci) {
if (!(entity instanceof Player)) return;
Entity vehicle = entity.getVehicle();
if (!(vehicle instanceof PokemonEntity)) return;
RidingManager ridingManager = ((Rideable) vehicle).getRiding();
if (ridingManager.shouldRotatePlayerHead((PokemonEntity) vehicle)) return;
if (!(vehicle instanceof PokemonEntity pokemonEntity)) return;
var shouldRotatePlayerHead = pokemonEntity.ifRidingAvailableSupply(false, (behaviour, settings, state) -> {
return behaviour.shouldRotatePlayerHead(settings, state, pokemonEntity);
});
if (shouldRotatePlayerHead) return;
netHeadYaw = 0f;
headPitch = 0f;
@@ -11,9 +11,15 @@ package com.cobblemon.mod.common.mixin.client;
import com.cobblemon.mod.common.client.render.ClientPlayerIcon;
import com.cobblemon.mod.common.client.render.player.MountedPlayerRenderer;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.model.EntityModel;
import net.minecraft.client.model.PlayerModel;
import net.minecraft.client.player.AbstractClientPlayer;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.entity.EntityRendererProvider;
import net.minecraft.client.renderer.entity.LivingEntityRenderer;
import net.minecraft.client.renderer.entity.player.PlayerRenderer;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
@@ -23,12 +29,33 @@ import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(PlayerRenderer.class)
public class PlayerRendererMixin {
public abstract class PlayerRendererMixin extends LivingEntityRenderer<AbstractClientPlayer, PlayerModel<AbstractClientPlayer>> {
public PlayerRendererMixin(EntityRendererProvider.Context context, PlayerModel<AbstractClientPlayer> model, float shadowRadius) {
super(context, model, shadowRadius);
}
@Inject(method = "render(Lnet/minecraft/world/entity/LivingEntity;FFLcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/MultiBufferSource;I)V", at = @At(value = "TAIL"))
public void cobblemon$clientPlayerIcon(LivingEntity entity, float entityYaw, float partialTicks, PoseStack poseStack, MultiBufferSource buffer, int packedLight, CallbackInfo ci) {
ClientPlayerIcon.Companion.onRenderPlayer((Player) entity);
}
@WrapOperation(method = "getRenderOffset(Lnet/minecraft/client/player/AbstractClientPlayer;F)Lnet/minecraft/world/phys/Vec3;", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/player/AbstractClientPlayer;isCrouching()Z"))
private boolean cobblemon$disableCrouchOffset(AbstractClientPlayer instance, Operation<Boolean> original) {
if (instance.isPassenger() && instance.getVehicle() instanceof PokemonEntity) {
return false;
}
return original.call(instance);
}
@Inject(method = "setModelProperties", at = @At("TAIL"))
private void cobblemon$setModelProperties(AbstractClientPlayer player, CallbackInfo ci) {
if (player.isPassenger() && player.getVehicle() instanceof PokemonEntity) {
PlayerModel<AbstractClientPlayer> playerModel = this.getModel();
playerModel.crouching = false;
}
}
@Inject(
method = "setupRotations(Lnet/minecraft/client/player/AbstractClientPlayer;Lcom/mojang/blaze3d/vertex/PoseStack;FFFF)V",
at = @At("HEAD")
@@ -562,6 +562,9 @@ object Cobblemon {
this.implementation.registerCommandArgument(cobblemonResource("dex"), DexArgumentType::class, SingletonArgumentInfo.contextFree (DexArgumentType::dex))
this.implementation.registerCommandArgument(cobblemonResource("npc_class"), NPCClassArgumentType::class, SingletonArgumentInfo.contextFree(NPCClassArgumentType::npcClass))
this.implementation.registerCommandArgument(cobblemonResource("wallpaper"), UnlockablePCBoxWallpaperArgumentType::class, SingletonArgumentInfo.contextFree(UnlockablePCBoxWallpaperArgumentType::wallpaper))
this.implementation.registerCommandArgument(cobblemonResource("mark"), MarkArgumentType::class, SingletonArgumentInfo.contextFree(MarkArgumentType::mark))
this.implementation.registerCommandArgument(cobblemonResource("transform_type"), TransformTypeArgumentType::class, SingletonArgumentInfo.contextFree(TransformTypeArgumentType::transformType))
this.implementation.registerCommandArgument(cobblemonResource("model_part"), ModelPartArgumentType::class, SingletonArgumentInfo.contextFree(ModelPartArgumentType::modelPart))
}
}
@@ -23,7 +23,7 @@ object CobblemonCommands {
ChangeEyeHeight.register(dispatcher)
ChangeScaleAndSize.register(dispatcher)
ChangeWalkSpeed.register(dispatcher)
ChangeJointScaleCommand.register(dispatcher)
TransformModelPartCommand.register(dispatcher)
TestCommand.register(dispatcher)
ReloadShowdownCommand.register(dispatcher)
ClickTextCommand.register(dispatcher)
@@ -64,6 +64,9 @@ object CobblemonCommands {
CobblemonConfigCommand.register(dispatcher)
UnlockPCBoxWallpaperCommand.register(dispatcher)
ChangePCBoxesCommand.register(dispatcher)
MarkGiveCommand.register(dispatcher)
MarkTakeCommand.register(dispatcher)
MarkGiveAllCommand.register(dispatcher)
// Possibly lock down registration if and only if under dev environment or running in an environment
// with a certain system environment variable set
@@ -281,7 +281,6 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
// Evolution items
@JvmField val LINK_CABLE = create("link_cable", LinkCableItem())
@JvmField val DRAGON_SCALE = noSettingsItem("dragon_scale")
@JvmField val KINGS_ROCK = noSettingsItem("kings_rock")
@JvmField val METAL_COAT = noSettingsItem("metal_coat")
@JvmField val UPGRADE = noSettingsItem("upgrade")
@JvmField val DUBIOUS_DISC = noSettingsItem("dubious_disc")
@@ -660,6 +659,19 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val WATER_STONE = noSettingsItem("water_stone")
val wearables = mutableListOf<WearableItem>()
//Wearable items (these items should have a corresponding 3D model)
@JvmField val BLACK_GLASSES = wearableItem("black_glasses")
@JvmField val CHOICE_BAND = wearableItem("choice_band")
@JvmField val CHOICE_SPECS = wearableItem("choice_specs")
@JvmField val EXP_SHARE = wearableItem("exp_share")
@JvmField val FOCUS_BAND = wearableItem("focus_band")
@JvmField val KINGS_ROCK = wearableItem("kings_rock")
@JvmField val MUSCLE_BAND = wearableItem("muscle_band")
@JvmField val ROCKY_HELMET = wearableItem("rocky_helmet")
@JvmField val SAFETY_GOGGLES = wearableItem("safety_goggles")
@JvmField val WISE_GLASSES = wearableItem("wise_glasses")
// Held Items
@JvmField
val ABILITY_SHIELD = heldItem("ability_shield")
@@ -676,8 +688,6 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val BLACK_BELT = heldItem("black_belt")
@JvmField
val BLACK_GLASSES = heldItem("black_glasses")
@JvmField
val BLACK_SLUDGE = heldItem("black_sludge")
@JvmField
val BLUNDER_POLICY = heldItem("blunder_policy")
@@ -686,12 +696,8 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val CHARCOAL = heldItem("charcoal_stick", remappedName = "charcoal")
@JvmField
val CHOICE_BAND = heldItem("choice_band")
@JvmField
val CHOICE_SCARF = heldItem("choice_scarf")
@JvmField
val CHOICE_SPECS = heldItem("choice_specs")
@JvmField
val CLEANSE_TAG = heldItem("cleanse_tag")
// @JvmField
// val CLEAR_AMULET = heldItem("clear_amulet")
@@ -712,8 +718,6 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val EVIOLITE = heldItem("eviolite")
@JvmField
val EXP_SHARE = heldItem("exp_share")
@JvmField
val EXPERT_BELT = heldItem("expert_belt")
@JvmField
val FAIRY_FEATHER = heldItem("fairy_feather")
@@ -722,8 +726,6 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val FLOAT_STONE = heldItem("float_stone")
@JvmField
val FOCUS_BAND = heldItem("focus_band")
@JvmField
val FOCUS_SASH = heldItem("focus_sash")
@JvmField
val GRASSY_SEED = heldItem("grassy_seed")
@@ -758,8 +760,6 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val MIRACLE_SEED = heldItem("miracle_seed")
@JvmField
val MUSCLE_BAND = heldItem("muscle_band")
@JvmField
val MYSTIC_WATER = heldItem("mystic_water")
@JvmField
val NEVER_MELT_ICE = heldItem("never_melt_ice")
@@ -790,12 +790,8 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val RING_TARGET = heldItem("ring_target")
@JvmField
val ROCKY_HELMET = heldItem("rocky_helmet")
@JvmField
val ROOM_SERVICE = heldItem("room_service")
@JvmField
val SAFETY_GOGGLES = heldItem("safety_goggles")
@JvmField
val SCOPE_LENS = heldItem("scope_lens")
@JvmField
val SHARP_BEAK = heldItem("sharp_beak")
@@ -832,8 +828,6 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val WIDE_LENS = heldItem("wide_lens")
@JvmField
val WISE_GLASSES = heldItem("wise_glasses")
@JvmField
val ZOOM_LENS = heldItem("zoom_lens")
@JvmField
val MENTAL_HERB = compostableHeldItem("mental_herb", null, 1F)
@@ -1263,6 +1257,16 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
return item
}
private fun wearableItem(name: String, heldItemRemappedName: String? = null): CobblemonItem = create(
name,
WearableItem(name).also {
wearables.add(it)
if (heldItemRemappedName != null) {
CobblemonHeldItemManager.registerRemap(it, heldItemRemappedName)
}
}
)
private fun heldItem(name: String, remappedName: String? = null): CobblemonItem = create(
name,
CobblemonItem(Item.Properties()).also {
@@ -35,6 +35,7 @@ import com.cobblemon.mod.common.client.net.pasture.OpenPastureHandler
import com.cobblemon.mod.common.client.net.pasture.PokemonPasturedHandler
import com.cobblemon.mod.common.client.net.pasture.PokemonUnpasturedHandler
import com.cobblemon.mod.common.client.net.pokedex.ServerConfirmedRegisterHandler
import com.cobblemon.mod.common.client.net.pokemon.update.ClientboundUpdateRidingStateHandler
import com.cobblemon.mod.common.client.net.pokemon.update.PokemonUpdatePacketHandler
import com.cobblemon.mod.common.client.net.settings.OpenCobblemonConfigEditorHandler
import com.cobblemon.mod.common.client.net.settings.ServerSettingsPacketHandler
@@ -74,7 +75,7 @@ import com.cobblemon.mod.common.net.messages.client.effect.SpawnSnowstormEntityP
import com.cobblemon.mod.common.net.messages.client.effect.SpawnSnowstormParticlePacket
import com.cobblemon.mod.common.net.messages.client.npc.CloseNPCEditorPacket
import com.cobblemon.mod.common.net.messages.client.npc.OpenNPCEditorPacket
import com.cobblemon.mod.common.net.messages.client.orientation.S2CUpdateOrientationPacket
import com.cobblemon.mod.common.net.messages.client.orientation.ClientboundUpdateOrientationPacket
import com.cobblemon.mod.common.net.messages.client.pasture.ClosePasturePacket
import com.cobblemon.mod.common.net.messages.client.pasture.OpenPasturePacket
import com.cobblemon.mod.common.net.messages.client.pasture.PokemonPasturedPacket
@@ -144,7 +145,7 @@ import com.cobblemon.mod.common.net.messages.server.callback.partymove.PartyPoke
import com.cobblemon.mod.common.net.messages.server.dialogue.EscapeDialoguePacket
import com.cobblemon.mod.common.net.messages.server.dialogue.InputToDialoguePacket
import com.cobblemon.mod.common.net.messages.server.npc.SaveNPCPacket
import com.cobblemon.mod.common.net.messages.server.orientation.C2SUpdateOrientationPacket
import com.cobblemon.mod.common.net.messages.server.orientation.ServerboundUpdateOrientationPacket
import com.cobblemon.mod.common.net.messages.server.pasture.PasturePokemonPacket
import com.cobblemon.mod.common.net.messages.server.pasture.UnpastureAllPokemonPacket
import com.cobblemon.mod.common.net.messages.server.pasture.UnpasturePokemonPacket
@@ -153,6 +154,9 @@ import com.cobblemon.mod.common.net.messages.server.pokedex.scanner.StartScannin
import com.cobblemon.mod.common.net.messages.server.pokemon.interact.InteractPokemonPacket
import com.cobblemon.mod.common.net.messages.server.pokemon.update.SetItemHiddenPacket
import com.cobblemon.mod.common.net.messages.server.pokemon.update.SetNicknamePacket
import com.cobblemon.mod.common.net.messages.server.pokemon.update.SetActiveMarkPacket
import com.cobblemon.mod.common.net.messages.server.pokemon.update.SetMarkingsPacket
import com.cobblemon.mod.common.net.messages.server.pokemon.update.ServerboundUpdateRidingStatePacket
import com.cobblemon.mod.common.net.messages.server.pokemon.update.evolution.AcceptEvolutionPacket
import com.cobblemon.mod.common.net.messages.server.starter.RequestStarterScreenPacket
import com.cobblemon.mod.common.net.messages.server.storage.SwapPCPartyPokemonPacket
@@ -195,6 +199,9 @@ import com.cobblemon.mod.common.net.serverhandling.pokedex.scanner.StartScanning
import com.cobblemon.mod.common.net.serverhandling.pokemon.interact.InteractPokemonHandler
import com.cobblemon.mod.common.net.serverhandling.pokemon.update.SetItemHiddenHandler
import com.cobblemon.mod.common.net.serverhandling.pokemon.update.SetNicknameHandler
import com.cobblemon.mod.common.net.serverhandling.pokemon.update.SetActiveMarkHandler
import com.cobblemon.mod.common.net.serverhandling.pokemon.update.SetMarkingsHandler
import com.cobblemon.mod.common.net.serverhandling.pokemon.update.ServerboundUpdateRidingStateHandler
import com.cobblemon.mod.common.net.serverhandling.starter.RequestStarterScreenHandler
import com.cobblemon.mod.common.net.serverhandling.starter.SelectStarterPacketHandler
import com.cobblemon.mod.common.net.serverhandling.storage.BenchMoveHandler
@@ -267,6 +274,13 @@ object CobblemonNetwork {
list.add(PacketRegisterInfo(DmaxLevelUpdatePacket.ID, DmaxLevelUpdatePacket::decode, PokemonUpdatePacketHandler()))
list.add(PacketRegisterInfo(GmaxFactorUpdatePacket.ID, GmaxFactorUpdatePacket::decode, PokemonUpdatePacketHandler()))
list.add(PacketRegisterInfo(CosmeticItemUpdatePacket.ID, CosmeticItemUpdatePacket::decode, PokemonUpdatePacketHandler()))
list.add(PacketRegisterInfo(ActiveMarkUpdatePacket.ID, ActiveMarkUpdatePacket::decode, PokemonUpdatePacketHandler()))
list.add(PacketRegisterInfo(MarkAddUpdatePacket.ID, MarkAddUpdatePacket::decode, PokemonUpdatePacketHandler()))
list.add(PacketRegisterInfo(MarkRemoveUpdatePacket.ID, MarkRemoveUpdatePacket::decode, PokemonUpdatePacketHandler()))
list.add(PacketRegisterInfo(MarkPotentialAddUpdatePacket.ID, MarkPotentialAddUpdatePacket::decode, PokemonUpdatePacketHandler()))
list.add(PacketRegisterInfo(MarksUpdatePacket.ID, MarksUpdatePacket::decode, PokemonUpdatePacketHandler()))
list.add(PacketRegisterInfo(MarksPotentialUpdatePacket.ID, MarksPotentialUpdatePacket::decode, PokemonUpdatePacketHandler()))
list.add(PacketRegisterInfo(MarkingsUpdatePacket.ID, MarkingsUpdatePacket::decode, PokemonUpdatePacketHandler()))
// Evolution start
list.add(PacketRegisterInfo(AddEvolutionPacket.ID, AddEvolutionPacket::decode, PokemonUpdatePacketHandler()))
@@ -359,6 +373,7 @@ object CobblemonNetwork {
list.add(PacketRegisterInfo(FishingBaitRegistrySyncPacket.ID, FishingBaitRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(FlowRegistrySyncPacket.ID, FlowRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(CosmeticItemAssignmentSyncPacket.ID, CosmeticItemAssignmentSyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(MarkRegistrySyncPacket.ID, MarkRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
// Effects
list.add(PacketRegisterInfo(SpawnSnowstormParticlePacket.ID, SpawnSnowstormParticlePacket::decode, SpawnSnowstormParticleHandler))
@@ -390,7 +405,7 @@ object CobblemonNetwork {
list.add(PacketRegisterInfo(PokemonUnpasturedPacket.ID, PokemonUnpasturedPacket::decode, PokemonUnpasturedHandler))
// Orientation
list.add(PacketRegisterInfo(S2CUpdateOrientationPacket.ID, S2CUpdateOrientationPacket::decode, S2CUpdateOrientationHandler))
list.add(PacketRegisterInfo(ClientboundUpdateOrientationPacket.ID, ClientboundUpdateOrientationPacket::decode, S2CUpdateOrientationHandler))
// Behaviours
list.add(PacketRegisterInfo(PlayPosableAnimationPacket.ID, PlayPosableAnimationPacket::decode, PlayPosableAnimationHandler))
@@ -418,6 +433,9 @@ object CobblemonNetwork {
// Debug / cheats
list.add(PacketRegisterInfo(CalculateSeatPositionsPacket.ID, CalculateSeatPositionsPacket::decode, CalculateSeatPositionsHandler))
// Riding
list.add(PacketRegisterInfo(ClientboundUpdateRidingStatePacket.ID, ClientboundUpdateRidingStatePacket::decode, ClientboundUpdateRidingStateHandler))
return list
}
@@ -426,6 +444,8 @@ object CobblemonNetwork {
// Pokemon Update Packets
list.add(PacketRegisterInfo(SetNicknamePacket.ID, SetNicknamePacket::decode, SetNicknameHandler))
list.add(PacketRegisterInfo(SetItemHiddenPacket.ID, SetItemHiddenPacket::decode, SetItemHiddenHandler))
list.add(PacketRegisterInfo(SetActiveMarkPacket.ID, SetActiveMarkPacket::decode, SetActiveMarkHandler))
list.add(PacketRegisterInfo(SetMarkingsPacket.ID, SetMarkingsPacket::decode, SetMarkingsHandler))
// Evolution Packets
list.add(PacketRegisterInfo(AcceptEvolutionPacket.ID, AcceptEvolutionPacket::decode, AcceptEvolutionHandler))
@@ -513,8 +533,9 @@ object CobblemonNetwork {
// NPC packets
list.add(PacketRegisterInfo(SaveNPCPacket.ID, SaveNPCPacket::decode, SaveNPCHandler))
// Orientation packet(s)
list.add(PacketRegisterInfo(C2SUpdateOrientationPacket.ID, C2SUpdateOrientationPacket::decode, OrientationPacketHandler))
// Riding packet(s)
list.add(PacketRegisterInfo(ServerboundUpdateOrientationPacket.ID, ServerboundUpdateOrientationPacket::decode, OrientationPacketHandler))
list.add(PacketRegisterInfo(ServerboundUpdateRidingStatePacket.ID, ServerboundUpdateRidingStatePacket::decode, ServerboundUpdateRidingStateHandler))
return list
}
@@ -20,4 +20,4 @@ interface EntitySideDelegate<T : Entity> {
fun tick(entity: T) {}
fun onSyncedDataUpdated(data: EntityDataAccessor<*>) {}
fun addToStruct(struct: QueryStruct) {}
}
}
@@ -21,4 +21,6 @@ interface PokemonSideDelegate : EntitySideDelegate<PokemonEntity> {
fun updatePostDeath() {}
fun handleStatus(status: Byte) {}
fun spawnShinyParticle(player: Player) {}
}
fun positionRider(passenger: Entity, positionUpdater: MoveFunction)
fun updateAge(age: Int) {}
}
@@ -8,10 +8,16 @@
package com.cobblemon.mod.common.api.events.fishing
import com.bedrockk.molang.runtime.value.DoubleValue
import com.bedrockk.molang.runtime.value.MoValue
import com.cobblemon.mod.common.api.events.Cancelable
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.api.molang.ObjectValue
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.entity.fishing.PokeRodFishingBobberEntity
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.itemRegistry
import net.minecraft.core.registries.Registries
import net.minecraft.world.item.ItemStack
/**
@@ -55,5 +61,11 @@ interface BobberSpawnPokemonEvent {
val chosenBucket: SpawnBucket,
val bait: ItemStack,
val pokemon: PokemonEntity
) : BobberSpawnPokemonEvent
) : BobberSpawnPokemonEvent {
val context = mutableMapOf<String, MoValue>(
"chosen_bucket" to ObjectValue(chosenBucket, { it.name }),
"bait" to pokemon.level().itemRegistry.wrapAsHolder(bait.item).asMoLangValue(Registries.ITEM),
"pokemon_entity" to pokemon.asMoLangValue()
)
}
}
@@ -8,10 +8,18 @@
package com.cobblemon.mod.common.api.events.pokemon
import com.bedrockk.molang.runtime.value.DoubleValue
import com.bedrockk.molang.runtime.value.MoValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.pokemon.Pokemon
import net.minecraft.server.level.ServerPlayer
data class FossilRevivedEvent (
val pokemon: Pokemon,
val player: ServerPlayer?
)
) {
val context = mutableMapOf<String, MoValue>(
"pokemon" to pokemon.struct,
"player" to (player?.asMoLangValue() ?: DoubleValue.ZERO)
)
}
@@ -0,0 +1,87 @@
/*
* 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.mark
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.util.*
import com.google.gson.annotations.SerializedName
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.network.chat.MutableComponent
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.StringRepresentable
class Mark(
identifier: ResourceLocation,
val name: String,
val description: String,
val title: String?,
@SerializedName(value = "titleColor", alternate = ["titleColour"])
val titleColour: String?,
val texture: ResourceLocation,
val replace: List<ResourceLocation>?,
val group: String?,
val chance: Float = 0F,
val indexNumber: Int?
): StringRepresentable {
companion object {
internal fun decode(buffer: RegistryFriendlyByteBuf): Mark {
return Mark(
buffer.readIdentifier(),
buffer.readString(),
buffer.readString(),
buffer.readNullable { buffer.readString() },
buffer.readNullable { buffer.readString() },
buffer.readResourceLocation(),
buffer.readNullable { buffer.readList { buffer.readResourceLocation() } },
buffer.readNullable { buffer.readString() },
buffer.readFloat(),
buffer.readNullable { buffer.readInt() }
)
}
}
@Transient
var identifier: ResourceLocation = identifier
internal set
override fun getSerializedName(): String = identifier.toString()
fun getName(): MutableComponent = name.asTranslated()
fun getDescription(): MutableComponent = description.asTranslated()
fun getTitle(name: MutableComponent): MutableComponent {
title?.let {
try {
return if (titleColour != null) it.asTranslated(name.withColor(-1)).withColor(titleColour.toInt(16))
else it.asTranslated(name)
} catch (e: Exception) {
Cobblemon.LOGGER.error("Error parsing titleColor hexadecimal color code for {}", identifier, e)
return it.asTranslated(name)
}
}
return name
}
fun getChanceGroup(): Pair<String, Float> = Pair(group ?: chance.toString(), chance)
internal fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeIdentifier(identifier)
buffer.writeString(name)
buffer.writeString(description)
buffer.writeNullable(title) { _, v -> buffer.writeString(v) }
buffer.writeNullable(titleColour) { _, v -> buffer.writeString(v) }
buffer.writeResourceLocation(texture)
buffer.writeNullable(replace) { _, v -> buffer.writeCollection(v) { _, resource -> buffer.writeResourceLocation(resource) } }
buffer.writeNullable(group) { _, v -> buffer.writeString(v) }
buffer.writeFloat(chance)
buffer.writeNullable(indexNumber) { _, v -> buffer.writeInt(v) }
}
}
@@ -0,0 +1,68 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.mark
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.data.JsonDataRegistry
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.net.messages.client.data.MarkRegistrySyncPacket
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.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
import net.minecraft.server.packs.PackType
object Marks: JsonDataRegistry<Mark> {
override val id: ResourceLocation = cobblemonResource("marks")
override val type: PackType = PackType.SERVER_DATA
override val observable = SimpleObservable<Marks>()
override val typeToken: TypeToken<Mark> = TypeToken.get(Mark::class.java)
override val resourcePath: String = "marks"
override val gson = GsonBuilder()
.setPrettyPrinting()
.registerTypeAdapter(ResourceLocation::class.java, IdentifierAdapter)
.create()
private val marks = hashMapOf<ResourceLocation, Mark>()
override fun reload(data: Map<ResourceLocation, Mark>) {
this.marks.clear()
data.forEach { (identifier, mark) ->
try {
mark.identifier = identifier
this.marks[identifier] = mark
} catch (e: Exception) {
Cobblemon.LOGGER.error("Skipped loading the {} mark", identifier, e)
}
}
Cobblemon.LOGGER.info("Loaded {} marks", this.marks.size)
this.observable.emit(this)
}
override fun sync(player: ServerPlayer) {
MarkRegistrySyncPacket(this.all()).sendToPlayer(player)
}
/**
* Gets all loaded [Mark]s.
*/
fun all() = this.marks.values.toList()
fun identifiers(): Collection<String> = marks.keys.toSet().map { it.toString() }
/**
* Gets a [Mark] by its [ResourceLocation].
* @param identifier The identifier of the mark.
* @return The [Mark] if loaded, otherwise null.
*/
fun getByIdentifier(identifier: ResourceLocation): Mark? = this.marks[identifier]
}
@@ -17,31 +17,20 @@ import com.bedrockk.molang.runtime.struct.VariableStruct
import com.bedrockk.molang.runtime.value.DoubleValue
import com.bedrockk.molang.runtime.value.MoValue
import com.bedrockk.molang.runtime.value.StringValue
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonActivities
import com.cobblemon.mod.common.CobblemonBlockEntities
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.CobblemonUnlockableWallpapers
import com.cobblemon.mod.common.*
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.battles.model.actor.ActorType
import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
import com.cobblemon.mod.common.api.dialogue.PlayerDialogueFaceProvider
import com.cobblemon.mod.common.api.dialogue.ReferenceDialogueFaceProvider
import com.cobblemon.mod.common.api.mark.Marks
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
import com.cobblemon.mod.common.api.moves.animations.ActionEffects
import com.cobblemon.mod.common.api.moves.animations.NPCProvider
import com.cobblemon.mod.common.api.pokedex.AbstractPokedexManager
import com.cobblemon.mod.common.api.pokedex.CaughtCount
import com.cobblemon.mod.common.api.pokedex.CaughtPercent
import com.cobblemon.mod.common.api.pokedex.PokedexEntryProgress
import com.cobblemon.mod.common.api.pokedex.PokedexManager
import com.cobblemon.mod.common.api.pokedex.SeenCount
import com.cobblemon.mod.common.api.pokedex.SeenPercent
import com.cobblemon.mod.common.api.pokedex.*
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.pokemon.evolution.Evolution
import com.cobblemon.mod.common.api.pokemon.stats.Stats
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.api.scripting.CobblemonScripts
import com.cobblemon.mod.common.api.spawning.context.SpawningContext
import com.cobblemon.mod.common.api.storage.PokemonStore
@@ -49,11 +38,11 @@ import com.cobblemon.mod.common.api.storage.party.PartyStore
import com.cobblemon.mod.common.api.storage.party.PlayerPartyStore
import com.cobblemon.mod.common.api.storage.pc.PCPosition
import com.cobblemon.mod.common.api.storage.pc.PCStore
import com.cobblemon.mod.common.api.tags.CobblemonItemTags
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.battles.BattleRegistry
import com.cobblemon.mod.common.battles.actor.PlayerBattleActor
import com.cobblemon.mod.common.client.particle.BedrockParticleOptionsRepository
import com.cobblemon.mod.common.client.particle.ParticleStorm
import com.cobblemon.mod.common.battles.actor.PokemonBattleActor
import com.cobblemon.mod.common.client.render.models.blockbench.wavefunction.WaveFunctions
import com.cobblemon.mod.common.entity.PosableEntity
import com.cobblemon.mod.common.entity.npc.NPCBattleActor
@@ -71,13 +60,8 @@ import com.cobblemon.mod.common.pokemon.Species
import com.cobblemon.mod.common.pokemon.evolution.variants.ItemInteractionEvolution
import com.cobblemon.mod.common.pokemon.evolution.variants.LevelUpEvolution
import com.cobblemon.mod.common.pokemon.evolution.variants.TradeEvolution
import com.cobblemon.mod.common.pokemon.riding.controllers.BirdAirController
import com.cobblemon.mod.common.pokemon.riding.states.BirdAirState
import com.cobblemon.mod.common.util.*
import com.mojang.datafixers.util.Either
import java.util.UUID
import kotlin.math.sqrt
import net.minecraft.client.multiplayer.ClientLevel
import net.minecraft.commands.arguments.EntityAnchorArgument
import net.minecraft.core.BlockPos
import net.minecraft.core.Holder
@@ -85,11 +69,7 @@ import net.minecraft.core.Registry
import net.minecraft.core.RegistryAccess
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.core.registries.Registries
import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.DoubleTag
import net.minecraft.nbt.ListTag
import net.minecraft.nbt.StringTag
import net.minecraft.nbt.Tag
import net.minecraft.nbt.*
import net.minecraft.resources.ResourceKey
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.MinecraftServer
@@ -98,11 +78,7 @@ import net.minecraft.server.level.ServerPlayer
import net.minecraft.tags.TagKey
import net.minecraft.world.damagesource.DamageSource
import net.minecraft.world.damagesource.DamageTypes
import net.minecraft.world.entity.Entity
import net.minecraft.world.entity.EntityType
import net.minecraft.world.entity.LightningBolt
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.PathfinderMob
import net.minecraft.world.entity.*
import net.minecraft.world.entity.ai.behavior.BlockPosTracker
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.entity.ai.memory.MemoryStatus
@@ -114,8 +90,12 @@ import net.minecraft.world.level.Level.ExplosionInteraction
import net.minecraft.world.level.biome.Biome
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.dimension.DimensionType
import net.minecraft.world.level.levelgen.Heightmap
import net.minecraft.world.phys.AABB
import net.minecraft.world.phys.Vec3
import java.util.*
import kotlin.math.sqrt
import kotlin.random.Random
/**
* Holds a bunch of useful MoLang trickery that can be used or extended in API
@@ -236,6 +216,22 @@ object MoLangFunctions {
val z = params.getInt(2)
return@put DoubleValue(world.isRainingAt(BlockPos(x, y, z)))
}
map.put("is_snowing_at") { params ->
val x = params.getInt(0)
val y = params.getInt(1)
val z = params.getInt(2)
val blockPos = BlockPos(x, y, z)
if (!world.isRaining()) {
return@put DoubleValue.ZERO
} else if (!world.canSeeSky(blockPos)) {
return@put DoubleValue.ZERO
} else if (world.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, blockPos).getY() > blockPos.getY()) {
return@put DoubleValue.ZERO
} else {
val biome = world.getBiome(blockPos).value() as Biome
return@put DoubleValue(biome.getPrecipitationAt(blockPos) == Biome.Precipitation.SNOW)
}
}
map.put("is_chunk_loaded_at") { params ->
val x = params.getInt(0)
val y = params.getInt(1)
@@ -291,16 +287,32 @@ object MoLangFunctions {
} as? ServerPlayer
val pos = Vec3(x, y, z)
if (world !is ClientLevel) {
val packet = SpawnSnowstormParticlePacket(particle, pos)
if (player != null) {
packet.sendToPlayer(player)
} else {
packet.sendToPlayersAround(x, y, z, 64.0, world.dimension())
}
val packet = SpawnSnowstormParticlePacket(particle, pos)
if (player != null) {
packet.sendToPlayer(player)
} else {
val effect = BedrockParticleOptionsRepository.getEffect(particle) ?: return@put DoubleValue.ZERO
ParticleStorm.createAtPosition(world, effect, pos).spawn()
packet.sendToPlayersAround(x, y, z, 64.0, world.dimension())
}
}
map.put("spawn_pokemon") { params ->
val x = params.getInt(0)
val y = params.getInt(1)
val z = params.getInt(2)
val props = params.getString(3).toProperties()
val pos = BlockPos(x, y, z)
if (!Level.isInSpawnableBounds(pos)) {
return@put DoubleValue.ZERO
}
val pokemon = props.createEntity(world)
pokemon.moveTo(pos, pokemon.yRot, pokemon.xRot)
if (world.addFreshEntity(pokemon)) {
return@put pokemon.struct
} else {
return@put DoubleValue.ZERO
}
}
map.put("get_entities_around") { params ->
@@ -469,6 +481,7 @@ object MoLangFunctions {
map.put("is_sneaking") { _ -> DoubleValue(entity.isShiftKeyDown) }
map.put("is_sprinting") { _ -> DoubleValue(entity.isSprinting) }
map.put("is_in_water") { _ -> DoubleValue(entity.isUnderWater) }
map.put("is_in_rain") { _ -> DoubleValue(entity.isInWaterOrRain && !entity.isInWater) }
map.put("is_touching_water_or_rain") { _ -> DoubleValue(entity.isInWaterRainOrBubble) }
map.put("is_touching_water") { _ -> DoubleValue(entity.isInWater) }
map.put("is_underwater") { DoubleValue(entity.getIsSubmerged()) }
@@ -740,6 +753,8 @@ object MoLangFunctions {
map.put("npc") { battleActor.entity.struct }
} else if (battleActor is PlayerBattleActor) {
map.put("player") { battleActor.entity?.asMoLangValue() ?: DoubleValue.ZERO }
} else if (battleActor is PokemonBattleActor) {
map.put("pokemon") { battleActor.entity?.asMoLangValue() ?: DoubleValue.ZERO }
}
return@mutableListOf map
}
@@ -778,6 +793,34 @@ object MoLangFunctions {
DoubleValue.ONE
}
map.put("owner") { pokemon.getOwnerPlayer()?.asMoLangValue() ?: DoubleValue.ZERO }
map.put("add_marks") { params ->
for (param in params.params) {
val identifier = param.asString().asIdentifierDefaultingNamespace()
val mark = Marks.getByIdentifier(identifier)
if (mark != null) pokemon.exchangeMark(mark, true)
}
}
map.put("add_marks_with_chance") { params ->
for (param in params.params) {
val identifier = param.asString().asIdentifierDefaultingNamespace()
val mark = Marks.getByIdentifier(identifier)
mark?.let {
val probability = it.chance.coerceIn(0F, 1F) * 100
val randomValue = Random.nextDouble(0.0, 100.0)
if (randomValue < probability) pokemon.exchangeMark(it, true)
}
}
}
map.put("add_potential_marks") { params ->
for (param in params.params) {
val identifier = param.asString().asIdentifierDefaultingNamespace()
val mark = Marks.getByIdentifier(identifier)
if (mark != null) pokemon.addPotentialMark(mark)
}
}
map.put("apply_potential_marks") {
return@put DoubleValue(pokemon.applyPotentialMarks())
}
map
}
)
@@ -791,7 +834,11 @@ object MoLangFunctions {
map.put("is_gliding") { DoubleValue(pokemonEntity.useRidingAltPose()) }
map.put("has_aspect") { DoubleValue(it.getString(0) in pokemonEntity.aspects) }
map.put("is_pokemon") { DoubleValue.ONE }
map.put("is_holding_item") { DoubleValue(!pokemonEntity.entityData.get(PokemonEntity.SHOWN_HELD_ITEM).isEmpty) }
map.put("is_holding_item") { DoubleValue(!pokemonEntity.entityData.get(PokemonEntity.SHOWN_HELD_ITEM).let {
it.isEmpty || it.`is`(CobblemonItemTags.WEARABLE_HAT_ITEMS) || it.`is`(CobblemonItemTags.WEARABLE_FACE_ITEMS)
}) }
map.put("is_wearing_hat") { DoubleValue(pokemonEntity.entityData.get(PokemonEntity.SHOWN_HELD_ITEM).`is`(CobblemonItemTags.WEARABLE_HAT_ITEMS)) }
map.put("is_wearing_face") { DoubleValue(pokemonEntity.entityData.get(PokemonEntity.SHOWN_HELD_ITEM).`is`(CobblemonItemTags.WEARABLE_FACE_ITEMS)) }
map
}
)
@@ -1438,4 +1485,4 @@ object MoLangFunctions {
}
}
fun Either<ResourceLocation, ExpressionLike>.runScript(runtime: MoLangRuntime) = map({ CobblemonScripts.run(it, runtime) }, { it.resolve(runtime) })
fun Either<ResourceLocation, ExpressionLike>.runScript(runtime: MoLangRuntime) = map({ CobblemonScripts.run(it, runtime) }, { it.resolve(runtime) })
@@ -19,4 +19,4 @@ interface Decodable {
*/
fun decode(buffer: RegistryFriendlyByteBuf)
}
}
@@ -25,4 +25,4 @@ interface Encodable {
*/
fun encode(buffer: RegistryFriendlyByteBuf)
}
}
@@ -6,10 +6,10 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.pokemon.riding.states
package com.cobblemon.mod.common.api.net.serializers
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
object RidingStateSerializer {
class RunUpToFlightCompositeState(pokemonEntity: PokemonEntity) : CompositeState(pokemonEntity) {
var currSpeed: Double = 0.0
}
}
@@ -1,10 +1,20 @@
/*
* 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.orientation
import com.cobblemon.mod.common.util.math.geometry.toDegrees
import com.cobblemon.mod.common.util.math.geometry.toRadians
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.Mth
import net.minecraft.world.entity.LivingEntity
import org.joml.Matrix3f
import org.joml.Quaternionf
import org.joml.Vector3f
import kotlin.math.asin
@@ -21,6 +31,9 @@ open class OrientationController(val entity: LivingEntity) {
var orientation: Matrix3f? = null
private set
private var renderOrientationO: Matrix3f? = null
private var renderOrientation: Matrix3f? = null
/** Adding this simply because it irritates me seeing `getActive` when most of this is Java. */
fun isActive() = active
@@ -37,6 +50,8 @@ open class OrientationController(val entity: LivingEntity) {
fun reset() {
orientation = null
renderOrientationO = null
renderOrientation = null
active = false
}
@@ -46,6 +61,30 @@ open class OrientationController(val entity: LivingEntity) {
rotateRoll(roll)
}
fun getRenderOrientation(delta: Float): Quaternionf {
val old = renderOrientationO ?: renderOrientation ?: orientation
val new = renderOrientation ?: old
val oldQuat = Quaternionf().setFromUnnormalized(old)
val newQuat = Quaternionf().setFromUnnormalized(new)
oldQuat.slerp(newQuat, delta)
return oldQuat
}
fun tick() {
if (!active || orientation == null) return
renderOrientationO = renderOrientation
val current = orientation
val renderMatrix = this.renderOrientation ?: current
val renderQuat = Quaternionf().setFromUnnormalized(renderMatrix)
val targetQuat = Quaternionf().setFromUnnormalized(current)
val dampingFactor = 0.66f // We can change this factor for faster transitions
renderQuat.slerp(targetQuat, dampingFactor)
val newRenderOrientation = Matrix3f()
renderQuat.get(newRenderOrientation)
renderOrientation = newRenderOrientation
}
val forwardVector: Vector3f
get() = orientation?.transform(FORWARDS, Vector3f()) ?: Vector3f(FORWARDS)
@@ -20,7 +20,7 @@ object CobblemonPermissions {
@JvmStatic
val CHANGE_WALK_SPEED = this.create("${COMMAND_PREFIX}changewalkspeed", PermissionLevel.ALL_COMMANDS)
@JvmStatic
val CHANGE_JOINT_SCALE = this.create("${COMMAND_PREFIX}changejointscale", PermissionLevel.ALL_COMMANDS)
val TRANSFORM_MODEL_PART = this.create("${COMMAND_PREFIX}transformmodelpart", PermissionLevel.ALL_COMMANDS)
@JvmStatic
val CHECKSPAWNS = this.create("${COMMAND_PREFIX}checkspawns", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
@JvmStatic
@@ -90,6 +90,9 @@ object CobblemonPermissions {
@JvmStatic
val HELD_ITEM = this.create("${COMMAND_PREFIX}helditem", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
@JvmStatic
val CHANGE_MARK = this.create("${COMMAND_PREFIX}changemark", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
@JvmStatic
val PC = this.create("${COMMAND_PREFIX}pc", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
@JvmStatic
@@ -33,7 +33,7 @@ import com.cobblemon.mod.common.api.pokemon.moves.Learnset
import com.cobblemon.mod.common.api.pokemon.stats.Stat
import com.cobblemon.mod.common.api.pokemon.stats.Stats
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.api.riding.controller.RideController
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourSettings
import com.cobblemon.mod.common.api.riding.stats.RidingStatDefinition
import com.cobblemon.mod.common.api.spawning.TimeRange
import com.cobblemon.mod.common.api.types.ElementalType
@@ -48,7 +48,7 @@ import com.cobblemon.mod.common.pokemon.evolution.adapters.CobblemonRequirementA
import com.cobblemon.mod.common.pokemon.evolution.adapters.LegacyItemConditionWrapperAdapter
import com.cobblemon.mod.common.pokemon.helditem.CobblemonHeldItemManager
import com.cobblemon.mod.common.util.adapters.*
import com.cobblemon.mod.common.util.adapters.RideControllerAdapter
import com.cobblemon.mod.common.util.adapters.RidingBehaviourSettingsAdapter
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.ifClient
import com.google.common.collect.HashBasedTable
@@ -107,7 +107,7 @@ object PokemonSpecies : JsonDataRegistry<Species> {
.registerTypeAdapter(EggGroup::class.java, EggGroupAdapter)
.registerTypeAdapter(MobEffect::class.java, RegistryElementAdapter<MobEffect>(BuiltInRegistries::MOB_EFFECT))
.registerTypeAdapter(ItemPredicate::class.java, LegacyItemConditionWrapperAdapter)
.registerTypeAdapter(RideController::class.java, RideControllerAdapter)
.registerTypeAdapter(RidingBehaviourSettings::class.java, RidingBehaviourSettingsAdapter)
.registerTypeAdapter(RidingStatDefinition::class.java, RidingStatDefinitionAdapter)
.registerTypeAdapter(Expression::class.java, ExpressionAdapter)
.disableHtmlEscaping()
@@ -8,6 +8,9 @@
package com.cobblemon.mod.common.api.riding
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourSettings
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourState
import net.minecraft.world.entity.PlayerRideableJumping
/**
@@ -20,6 +23,6 @@ interface Rideable : PlayerRideableJumping {
/**
* Denotes the manager responsible for handling any instance of riding taking place on an entity
*/
val riding: RidingManager
val riding: RidingBehaviour<RidingBehaviourSettings, RidingBehaviourState>?
}
}
@@ -6,7 +6,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.riding.controller
package com.cobblemon.mod.common.api.riding
import com.cobblemon.mod.common.util.readString
import com.cobblemon.mod.common.util.writeString
@@ -29,4 +29,4 @@ class RidingAnimation(
)
}
}
}
}
@@ -1,210 +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.api.riding
import com.bedrockk.molang.runtime.MoLangRuntime
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.setup
import com.cobblemon.mod.common.api.riding.controller.RideController
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.withQueryValue
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
data class RidingManager(val entity: PokemonEntity) {
var lastSpeed = 0F
var states: MutableMap<ResourceLocation, RidingState> = mutableMapOf()
var deltaRotation = Vec2.ZERO
val runtime: MoLangRuntime by lazy {
MoLangRuntime()
.setup()
.withQueryValue("entity", entity.struct)
.also {
it.environment.query.addFunction("passenger_count") { DoubleValue(entity.passengers.size.toDouble()) }
it.environment.query.addFunction("get_ride_stats") { params ->
val rideStat = RidingStat.valueOf(params.getString(0).uppercase())
val rideStyle = RidingStyle.valueOf(params.getString(1).uppercase())
val maxVal = params.getDouble(2)
val minVal = params.getDouble(3)
//TODO: Use the mons actual boost once implemented
val normalizedStat = entity.rideProp.calculate(rideStat, rideStyle, 0) / 100.0f
val trueStatVal = (normalizedStat * (maxVal - minVal)) + minVal
DoubleValue(trueStatVal)
}
}
}
fun <T : RidingState> getState(id: ResourceLocation, constructor: (PokemonEntity) -> T): T {
val storedState = states[id]
if (storedState == null) {
val newState = constructor(entity)
states[id] = newState
return newState
}
return storedState as T
}
fun getController(entity: PokemonEntity): RideController? {
return entity.pokemon.riding.controller?.takeIf { it.condition.invoke(entity) }
}
/**
* Responsible for handling riding conditions and transitions amongst controllers. This will tick
* whenever the entity receives a tickControlled interaction.
*/
fun tick(entity: PokemonEntity, driver: Player, input: Vec3) {
val controller = getController(entity) ?: return
controller.tick(entity, driver, input)
if (!entity.level().isClientSide) {
val pose = controller.pose(entity)
entity.entityData.set(PokemonEntity.POSE_TYPE, pose)
}
}
fun speed(entity: PokemonEntity, driver: Player): Float {
val controller = getController(entity) ?: return 0.05F
this.lastSpeed = controller.speed(entity, driver)
return this.lastSpeed
}
fun controlledRotation(entity: PokemonEntity, driver: Player): Vec2 {
val controller = getController(entity) ?: return entity.rotationVector
val previousRotation = entity.rotationVector
val rotation = controller.rotation(entity, driver)
this.deltaRotation = Vec2(rotation.x - previousRotation.x, rotation.y - previousRotation.y)
return rotation
}
fun clampPassengerRotation(entity: PokemonEntity, driver: LivingEntity) {
val controller = getController(entity) ?: return
return controller.clampPassengerRotation(entity, driver)
}
fun updatePassengerRotation(entity: PokemonEntity, driver: LivingEntity) {
val controller = getController(entity) ?: return
return controller.updatePassengerRotation(entity, driver)
}
fun velocity(entity: PokemonEntity, driver: Player, input: Vec3): Vec3 {
val controller = getController(entity) ?: return Vec3.ZERO
return controller.velocity(entity, driver, input)
}
fun setRideBar(entity: PokemonEntity, driver: Player): Float {
val controller = getController(entity) ?: return 0.0f
return controller.setRideBar(entity, driver)
}
fun rideFovMult(entity: PokemonEntity, driver: Player): Float {
val controller = getController(entity) ?: return 1.0f
return controller.rideFovMult(entity, driver)
}
fun canJump(entity: PokemonEntity, driver: Player): Boolean {
val controller = getController(entity) ?: return false
return controller.canJump(entity, driver)
}
fun jumpVelocity(entity: PokemonEntity, driver: Player, jumpStrength: Int): Vec3 {
val controller = getController(entity) ?: return Vec3.ZERO
return controller.jumpForce(entity, driver, jumpStrength)
}
fun useRidingAltPose(entity: PokemonEntity, driver: Player): Boolean {
val controller = getController(entity) ?: return false
return controller.useRidingAltPose(entity, driver)
}
/**
* Used to supply the delta for the lerp() between velocity
* vectors. Called inertia for now as the way it feels when
* affecting the ride is somewhat akin to inertia.
*/
fun inertia(entity: PokemonEntity): Double {
val controller = getController(entity) ?: return 0.5
return controller.inertia(entity)
}
fun gravity(entity: PokemonEntity, regularGravity: Double): Double? {
val controller = getController(entity) ?: return null
return controller.gravity(entity, regularGravity)
}
//Maybe should be changed to be a function that conveys:
// "turn on use our camera system function"
fun shouldRoll(entity: PokemonEntity): Boolean {
val controller = getController(entity) ?: return false
return controller.shouldRoll(entity)
}
fun shouldRotatePlayerHead(entity: PokemonEntity): Boolean {
val controller = getController(entity) ?: return false
return controller.shouldRotatePlayerHead()
}
fun shouldRotatePokemonHead(entity: PokemonEntity): Boolean {
val controller = getController(entity) ?: return false
return controller.shouldRotatePokemonHead()
}
fun rotationOnMouseXY(
entity: PokemonEntity,
driver: Player,
yMouse: Double,
xMouse: Double,
yMouseSmoother: SmoothDouble,
xMouseSmoother: SmoothDouble,
sensitivity: Double,
deltaTime: Double
): Vec3 {
val controller = getController(entity) ?: return Vec3.ZERO
return controller.rotationOnMouseXY(
entity,
driver,
yMouse,
xMouse,
yMouseSmoother,
xMouseSmoother,
sensitivity,
deltaTime
) }
/**
* Determines whether mouse input will affect the rotation of
* the rollable object. Need to remove this as it's not used anymore
*/
//I think this might better serve as a "use smoothing on angular velocity"
fun useAngVelSmoothing(entity: PokemonEntity): Boolean {
val controller = getController(entity) ?: return true
return controller.useAngVelSmoothing(entity)
}
fun turnOffOnGround(entity: PokemonEntity): Boolean {
val controller = getController(entity) ?: return false
return controller.turnOffOnGround(entity)
}
fun angRollVel(entity: PokemonEntity, driver: Player, deltaTime: Double): Vec3 {
val controller = getController(entity) ?: return Vec3(0.0, 0.0, 0.0)
return controller.angRollVel(entity, driver, deltaTime)
}
fun dismountOnShift(): Boolean {
val controller = getController(entity) ?: return false
return controller.dismountOnShift(entity)
}
}
@@ -9,45 +9,38 @@
package com.cobblemon.mod.common.api.riding
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.api.riding.controller.RideController
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourSettings
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.api.riding.stats.RidingStatDefinition
import com.cobblemon.mod.common.util.adapters.RideControllerAdapter
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.getString
import com.cobblemon.mod.common.util.readIdentifier
import com.cobblemon.mod.common.util.readString
import com.cobblemon.mod.common.util.writeString
import com.cobblemon.mod.common.util.*
import com.cobblemon.mod.common.util.adapters.RidingBehaviourSettingsAdapter
import net.minecraft.network.RegistryFriendlyByteBuf
class RidingProperties(
val stats: Map<RidingStat, RidingStatDefinition> = mapOf(),
val seats: List<Seat> = listOf(),
val conditions: List<Expression> = listOf(),
val controller: RideController? = null
val behaviour: RidingBehaviourSettings? = null
) {
companion object {
fun decode(buffer: RegistryFriendlyByteBuf): RidingProperties {
val stats: Map<RidingStat, RidingStatDefinition> = buffer.readMap(
{ buffer.readEnum<RidingStat>(RidingStat::class.java) },
{ buffer.readEnum(RidingStat::class.java) },
{ RidingStatDefinition.decode(buffer) }
)
val seats: List<Seat> = buffer.readList { _ -> Seat.decode(buffer) }
val conditions = buffer.readList { buffer.readString().asExpression() }
val controller = buffer.readNullable { _ ->
val key = buffer.readIdentifier()
val controller = RideControllerAdapter.types[key]?.getConstructor()?.newInstance() ?: error("Unknown controller key: $key")
controller.decode(buffer)
return@readNullable controller
val behaviour = buffer.readNullable { _ ->
val key = buffer.readResourceLocation()
val settings = RidingBehaviourSettingsAdapter.types[key]?.getConstructor()?.newInstance() ?: error("Unknown controller key: $key")
settings.decode(buffer)
return@readNullable settings
}
return RidingProperties(stats = stats, seats = seats, conditions = conditions, controller = controller)
return RidingProperties(stats = stats, seats = seats, conditions = conditions, behaviour = behaviour)
}
}
val canRide: Boolean
get() = seats.isNotEmpty() && controller != null
fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeMap(
stats,
@@ -56,7 +49,9 @@ class RidingProperties(
)
buffer.writeCollection(seats) { _, seat -> seat.encode(buffer) }
buffer.writeCollection(conditions) { _, condition -> buffer.writeString(condition.getString()) }
buffer.writeNullable(controller) { _, controller -> controller.encode(buffer) }
buffer.writeNullable(behaviour) { _, behaviour ->
behaviour.encode(buffer)
}
}
fun calculate(stat: RidingStat, style: RidingStyle, boosts: Int): Float {
@@ -1,18 +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.api.riding
/**
* Honestly having a base interface is probably unnecessary I'm just considering whether there's going to be some
* kind of shared state later.
*
* @author Hiroku
* @since October 6th, 2024
*/
interface RidingState
@@ -18,5 +18,6 @@ package com.cobblemon.mod.common.api.riding
enum class RidingStyle {
LAND,
LIQUID,
AIR
}
AIR,
COMPOSITE
}
@@ -9,7 +9,6 @@
package com.cobblemon.mod.common.api.riding
import com.cobblemon.mod.common.api.net.Encodable
import com.cobblemon.mod.common.api.riding.controller.RidingAnimation
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.util.readString
import com.cobblemon.mod.common.util.writeString
@@ -0,0 +1,92 @@
/*
* 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.riding.behaviour
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
/**
* Represents the behaviour of a Pokemon when being ridden.
* This is intended to contain the logic for how a Pokemon should behave when being ridden.
*
* This class should remain stateless as it is intended to be shared between multiple instances of Pokemon.
*
* @author landonjw
*/
interface RidingBehaviour<Settings : RidingBehaviourSettings, State : RidingBehaviourState> {
val key: ResourceLocation
val style: RidingStyle
fun isActive(settings: Settings, state: State, vehicle: PokemonEntity): Boolean
fun tick(settings: Settings, state: State, vehicle: PokemonEntity, driver: Player, input: Vec3) {}
fun pose(settings: Settings, state: State, vehicle: PokemonEntity): PoseType
fun speed(settings: Settings, state: State, vehicle: PokemonEntity, driver: Player): Float
fun clampPassengerRotation(settings: Settings, state: State, entity: PokemonEntity, driver: LivingEntity) {}
fun updatePassengerRotation(settings: Settings, state: State, entity: PokemonEntity, driver: LivingEntity) {}
fun rotation(settings: Settings, state: State, vehicle: PokemonEntity, driver: LivingEntity): Vec2
fun velocity(settings: Settings, state: State, vehicle: PokemonEntity, driver: Player, input: Vec3): Vec3
fun angRollVel(settings: Settings, state: State, vehicle: PokemonEntity, driver: Player, deltaTime: Double): Vec3
fun rotationOnMouseXY(
settings: Settings,
state: State,
vehicle: PokemonEntity,
driver: Player,
mouseY: Double,
mouseX: Double,
mouseYSmoother: SmoothDouble,
mouseXSmoother: SmoothDouble,
sensitivity: Double,
deltaTime: Double
): Vec3
fun canJump(settings: Settings, state: State, vehicle: PokemonEntity, driver: Player): Boolean
fun setRideBar(settings: Settings, state: State, vehicle: PokemonEntity, driver: Player): Float
fun jumpForce(settings: Settings, state: State, vehicle: PokemonEntity, driver: Player, jumpStrength: Int): Vec3
fun gravity(settings: Settings, state: State, vehicle: PokemonEntity, regularGravity: Double): Double
fun rideFovMultiplier(settings: Settings, state: State, vehicle: PokemonEntity, driver: Player): Float
fun useAngVelSmoothing(settings: Settings, state: State, vehicle: PokemonEntity): Boolean
fun useRidingAltPose(settings: Settings, state: State, vehicle: PokemonEntity, driver: Player): Boolean
fun inertia(settings: Settings, state: State, vehicle: PokemonEntity): Double
fun shouldRoll(settings: Settings, state: State, vehicle: PokemonEntity): Boolean
fun turnOffOnGround(settings: Settings, state: State, vehicle: PokemonEntity): Boolean
fun dismountOnShift(settings: Settings, state: State, vehicle: PokemonEntity): Boolean
fun shouldRotatePokemonHead(settings: Settings, state: State, vehicle: PokemonEntity): Boolean
fun shouldRotatePlayerHead(settings: Settings, state: State, vehicle: PokemonEntity): Boolean
fun createDefaultState(settings: Settings): State
}
@@ -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.riding.behaviour
import com.cobblemon.mod.common.api.net.Decodable
import com.cobblemon.mod.common.api.net.Encodable
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.phys.Vec3
/**
* Represents static settings of a riding behaviour.
* Values in this class are intended to be constant and not change during the riding process.
* Typically this will be initialized for each pokemon form during deserialization
* to determine how they should ride.
*
* @author landonjw
*/
interface RidingBehaviourSettings: Encodable, Decodable {
val key: ResourceLocation
}
@@ -0,0 +1,95 @@
/*
* 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.riding.behaviour
import com.cobblemon.mod.common.util.ifClient
import com.cobblemon.mod.common.util.ifServer
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.world.phys.Vec3
/**
* Represents the state of a Pokemon when being ridden.
* This is intended to contain mutable state that is passed between the client and server
* or temporary state that is used during the riding process.
*
* This will be destroyed when the a user dismounts the Pokemon.
*
* @author landonjw
*/
open class RidingBehaviourState {
open val rideVelocity: SidedRidingState<Vec3> = ridingState(Vec3.ZERO, Side.BOTH)
open val stamina: SidedRidingState<Float> = ridingState(0F, Side.BOTH)
open fun reset() {
rideVelocity.set(Vec3.ZERO, true)
stamina.set(0F, true)
}
open fun copy(): RidingBehaviourState {
val copy = RidingBehaviourState()
copy.rideVelocity.set(rideVelocity.get(), true)
copy.stamina.set(stamina.get(), true)
return copy
}
open fun shouldSync(previous: RidingBehaviourState): Boolean {
if (previous.rideVelocity.get() != rideVelocity.get()) return true
if (previous.stamina.get() != stamina.get()) return true
return false
}
open fun encode(buffer: FriendlyByteBuf) {
buffer.writeVec3(rideVelocity.get())
buffer.writeFloat(stamina.get())
}
open fun decode(buffer: FriendlyByteBuf) {
rideVelocity.set(buffer.readVec3(), true)
stamina.set(buffer.readFloat(), true)
}
}
fun <T> ridingState(value: T, side: Side) = SidedRidingState(value, side)
class SidedRidingState<T>(private var value: T, val side: Side) {
fun get() = value
fun set(value: T, forced: Boolean = false) {
if (forced) {
this.value = value
}
else {
when (side) {
Side.BOTH -> { this.value = value }
Side.CLIENT -> { ifClient { this.value = value } }
Side.SERVER -> { ifServer { this.value = value } }
}
}
}
override fun equals(other: Any?): Boolean {
if (other is SidedRidingState<*>) {
return value == other.value
}
return super.equals(other)
}
override fun hashCode(): Int {
return value.hashCode()
}
}
enum class Side {
SERVER, CLIENT, BOTH
}
enum class DriverSide {
LOCAL, REMOTE, BOTH
}
@@ -0,0 +1,40 @@
/*
* 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.riding.behaviour
import com.cobblemon.mod.common.api.riding.behaviour.types.*
import com.cobblemon.mod.common.api.riding.behaviour.types.composite.CompositeBehaviour
import net.minecraft.resources.ResourceLocation
object RidingBehaviours {
val behaviours = mutableMapOf<ResourceLocation, RidingBehaviour<RidingBehaviourSettings, RidingBehaviourState>>()
init {
register(BirdAirBehaviour.KEY, BirdAirBehaviour())
register(DolphinBehaviour.KEY, DolphinBehaviour())
register(GenericLandBehaviour.KEY, GenericLandBehaviour())
register(GenericSwimBehaviour.KEY, GenericSwimBehaviour())
register(GliderAirBehaviour.KEY, GliderAirBehaviour())
register(HelicopterBehaviour.KEY, HelicopterBehaviour())
register(JetAirBehaviour.KEY, JetAirBehaviour())
register(SwimDashBehaviour.KEY, SwimDashBehaviour())
register(VehicleLandBehaviour.KEY, VehicleLandBehaviour())
register(CompositeBehaviour.KEY, CompositeBehaviour())
}
fun register(key: ResourceLocation, behaviour: RidingBehaviour<out RidingBehaviourSettings, out RidingBehaviourState>) {
if (behaviours.contains(key)) error("Behaviour already registered to key $key")
behaviours[key] = RidingController(behaviour) as RidingBehaviour<RidingBehaviourSettings, RidingBehaviourState>
}
fun get(key: ResourceLocation): RidingBehaviour<RidingBehaviourSettings, RidingBehaviourState> {
if (!behaviours.contains(key)) error("Behaviour not registered to key $key")
return behaviours[key]!!
}
}
@@ -0,0 +1,173 @@
/*
* 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.riding.behaviour
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
/**
* Small wrapper around a RidingBehaviour to provide sane defaults in the event that the behaviour is not active.
*
* @author landonjw
*/
class RidingController<Settings : RidingBehaviourSettings, State : RidingBehaviourState>(val behaviour: RidingBehaviour<Settings, State>) : RidingBehaviour<Settings, State> {
override val key = behaviour.key
override val style = behaviour.style
override fun isActive(settings: Settings, state: State, vehicle: PokemonEntity) =
behaviour.isActive(settings, state, vehicle)
override fun tick(settings: Settings, state: State, vehicle: PokemonEntity, driver: Player, input: Vec3) =
behaviour.tick(settings, state, vehicle, driver, input)
override fun pose(settings: Settings, state: State, vehicle: PokemonEntity) =
behaviour.pose(settings, state, vehicle)
override fun speed(settings: Settings, state: State, vehicle: PokemonEntity, driver: Player): Float {
if (!isActive(settings, state, vehicle)) return 0.0F
return behaviour.speed(settings, state, vehicle, driver)
}
override fun rotation(settings: Settings, state: State, vehicle: PokemonEntity, driver: LivingEntity): Vec2 {
if (!isActive(settings, state, vehicle)) return driver.rotationVector
return behaviour.rotation(settings, state, vehicle, driver)
}
override fun velocity(
settings: Settings,
state: State,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
if (!isActive(settings, state, vehicle)) return Vec3.ZERO
return behaviour.velocity(settings, state, vehicle, driver, input)
}
override fun angRollVel(
settings: Settings,
state: State,
vehicle: PokemonEntity,
driver: Player,
deltaTime: Double
): Vec3 {
if (!isActive(settings, state, vehicle)) return Vec3.ZERO
return behaviour.angRollVel(settings, state, vehicle, driver, deltaTime)
}
override fun rotationOnMouseXY(
settings: Settings,
state: State,
vehicle: PokemonEntity,
driver: Player,
mouseY: Double,
mouseX: Double,
mouseYSmoother: SmoothDouble,
mouseXSmoother: SmoothDouble,
sensitivity: Double,
deltaTime: Double
): Vec3 {
if (!isActive(settings, state, vehicle)) return Vec3.ZERO
return behaviour.rotationOnMouseXY(
settings,
state,
vehicle,
driver,
mouseY,
mouseX,
mouseYSmoother,
mouseXSmoother,
sensitivity,
deltaTime
)
}
override fun canJump(settings: Settings, state: State, vehicle: PokemonEntity, driver: Player): Boolean {
if (!isActive(settings, state, vehicle)) return false
return behaviour.canJump(settings, state, vehicle, driver)
}
override fun shouldRotatePlayerHead(settings: Settings, state: State, vehicle: PokemonEntity): Boolean {
if (!isActive(settings, state, vehicle)) return false
return behaviour.shouldRotatePlayerHead(settings, state, vehicle)
}
override fun shouldRotatePokemonHead(settings: Settings, state: State, vehicle: PokemonEntity): Boolean {
if (!isActive(settings, state, vehicle)) return false
return behaviour.shouldRotatePokemonHead(settings, state, vehicle)
}
override fun dismountOnShift(settings: Settings, state: State, vehicle: PokemonEntity): Boolean {
if (!isActive(settings, state, vehicle)) return false
return behaviour.dismountOnShift(settings, state, vehicle)
}
override fun turnOffOnGround(settings: Settings, state: State, vehicle: PokemonEntity): Boolean {
if (!isActive(settings, state, vehicle)) return false
return behaviour.turnOffOnGround(settings, state, vehicle)
}
override fun shouldRoll(settings: Settings, state: State, vehicle: PokemonEntity): Boolean {
if (!isActive(settings, state, vehicle)) return false
return behaviour.shouldRoll(settings, state, vehicle)
}
override fun inertia(settings: Settings, state: State, vehicle: PokemonEntity): Double {
if (!isActive(settings, state, vehicle)) return 0.5
return behaviour.inertia(settings, state, vehicle)
}
override fun useRidingAltPose(settings: Settings, state: State, vehicle: PokemonEntity, driver: Player): Boolean {
if (!isActive(settings, state, vehicle)) return false
return behaviour.useRidingAltPose(settings, state, vehicle, driver)
}
override fun useAngVelSmoothing(settings: Settings, state: State, vehicle: PokemonEntity): Boolean {
if (!isActive(settings, state, vehicle)) return true
return behaviour.useAngVelSmoothing(settings, state, vehicle)
}
override fun rideFovMultiplier(settings: Settings, state: State, vehicle: PokemonEntity, driver: Player): Float {
if (!isActive(settings, state, vehicle)) return 1.0F
return behaviour.rideFovMultiplier(settings, state, vehicle, driver)
}
override fun gravity(settings: Settings, state: State, vehicle: PokemonEntity, regularGravity: Double): Double {
if (!isActive(settings, state, vehicle)) return regularGravity
return behaviour.gravity(settings, state, vehicle, regularGravity)
}
override fun jumpForce(
settings: Settings,
state: State,
vehicle: PokemonEntity,
driver: Player,
jumpStrength: Int
): Vec3 {
if (!isActive(settings, state, vehicle)) return Vec3.ZERO
return behaviour.jumpForce(settings, state, vehicle, driver, jumpStrength)
}
override fun setRideBar(settings: Settings, state: State, vehicle: PokemonEntity, driver: Player): Float {
if (!isActive(settings, state, vehicle)) return 0.0F
return behaviour.setRideBar(settings, state, vehicle, driver)
}
override fun createDefaultState(settings: Settings): State {
return behaviour.createDefaultState(settings)
}
}
@@ -0,0 +1,523 @@
/*
* 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.riding.behaviour.types
import com.bedrockk.molang.Expression
import com.bedrockk.molang.runtime.MoLangMath.lerp
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.*
import com.cobblemon.mod.common.api.riding.posing.PoseOption
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.*
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
import kotlin.math.*
class BirdAirBehaviour : RidingBehaviour<BirdAirSettings, BirdAirState> {
companion object {
val KEY = cobblemonResource("air/bird")
}
override val key: ResourceLocation = KEY
override val style = RidingStyle.AIR
val poseProvider = PoseProvider<BirdAirSettings, BirdAirState>(PoseType.HOVER)
.with(PoseOption(PoseType.FLY) { _, state, _ -> state.rideVelocity.get().z > 0.2 })
override fun isActive(settings: BirdAirSettings, state: BirdAirState, vehicle: PokemonEntity): Boolean {
return true
}
override fun pose(settings: BirdAirSettings, state: BirdAirState, vehicle: PokemonEntity): PoseType {
return poseProvider.select(settings, state, vehicle)
}
override fun speed(
settings: BirdAirSettings,
state: BirdAirState,
vehicle: PokemonEntity,
driver: Player
): Float {
return state.rideVelocity.get().length().toFloat()
}
override fun rotation(
settings: BirdAirSettings,
state: BirdAirState,
vehicle: PokemonEntity,
driver: LivingEntity
): Vec2 {
return Vec2(driver.xRot * 0.5f, driver.yRot)
}
override fun velocity(
settings: BirdAirSettings,
state: BirdAirState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
var yVel = 0.0
var leftForce = 0.0
var upForce = 0.0
var forwardForce = 0.0
//Perform ride velocity update
calculateRideSpaceVel(settings, state, vehicle, driver)
//Translate ride space velocity to world space velocity.
val controller = (driver as? OrientationControllable)?.orientationController
if (controller != null) {
//Need to deadzone this when straight up or down
upForce += -1.0 * sin(Math.toRadians(controller.pitch.toDouble())) * state.rideVelocity.get().z
forwardForce += cos(Math.toRadians(controller.pitch.toDouble())) * state.rideVelocity.get().z
upForce += cos(Math.toRadians(controller.pitch.toDouble())) * state.rideVelocity.get().y
forwardForce += sin(Math.toRadians(controller.pitch.toDouble())) * state.rideVelocity.get().y
}
//Bring the ride out of the sky when stamina is depleted.
if (state.stamina.get() <= 0.0) {
upForce -= 0.3
}
val altitudeLimit = vehicle.runtime.resolveDouble(settings.jumpExpr)
//Only limit altitude if altitude is not infinite
if (!vehicle.runtime.resolveBoolean(settings.infiniteAltitude)) {
//Provide a hard limit on altitude
upForce = if (vehicle.y >= altitudeLimit && upForce > 0) 0.0 else upForce
}
val velocity = Vec3(state.rideVelocity.get().x , upForce, forwardForce)
return velocity
}
/*
* Calculates the change in the ride space vector due to player input and ride state
*/
fun calculateRideSpaceVel(settings: BirdAirSettings, state: BirdAirState, vehicle: PokemonEntity, driver: Player) {
//retrieve stats
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
val glideTopSpeed = vehicle.runtime.resolveDouble(settings.glidespeedExpr)
val accel = vehicle.runtime.resolveDouble(settings.accelerationExpr)
val staminaStat = vehicle.runtime.resolveDouble(settings.staminaExpr)
var glideSpeedChange = 0.0
val currSpeed = state.rideVelocity.get().length()
//Flag for determining if player is actively inputting
var activeInput = false
var newVelocity = Vec3(state.rideVelocity.get().x, state.rideVelocity.get().y, state.rideVelocity.get().z)
//speed up and slow down based on input
if (driver.zza != 0.0f && state.stamina.get() > 0.0) {
//make sure it can't exceed top speed
val forwardInput = when {
driver.zza > 0 && newVelocity.z > topSpeed -> 0.0
driver.zza < 0 && newVelocity.z < (-topSpeed / 3.0) -> 0.0
else -> driver.zza.sign
}
newVelocity = Vec3(
newVelocity.x,
newVelocity.y,
(newVelocity.z + (accel * forwardInput.toDouble())))
activeInput = true
}
val controller = (driver as? OrientationControllable)?.orientationController
if (controller != null) {
//Base glide speed change on current pitch of the ride.
glideSpeedChange = sin(Math.toRadians(controller.pitch.toDouble()))
glideSpeedChange = glideSpeedChange.pow(3) * 0.5
//TODO: Possibly create a deadzone around parallel where glide doesn't affect speed?
if (glideSpeedChange <= 0.0) {
//Ensures that a propelling force is still able to be applied when
//climbing in height
if (driver.zza <= 0) {
//speed decrease should be 2x speed increase?
//state.currSpeed = max(state.currSpeed + (0.0166) * glideSpeedChange, 0.0 )
newVelocity = Vec3(
newVelocity.x,
newVelocity.y,
lerp( newVelocity.z, 0.0,glideSpeedChange * -0.0166 * 2 )
)
}
} else {
// only add to the speed if it hasn't exceeded the current
//glide angles maximum amount of speed that it can give.
//state.currSpeed = min(state.currSpeed + ((0.0166 * 2) * glideSpeedChange), maxGlideSpeed)
newVelocity = Vec3(
newVelocity.x,
newVelocity.y,
min(newVelocity.z + ((0.0166 * 2) * glideSpeedChange), glideTopSpeed)
)
}
}
//Lateral movement based on driver input.
val latTopSpeed = topSpeed / 2.0
if (driver.xxa != 0.0f && state.stamina.get() > 0.0) {
newVelocity = Vec3(
(newVelocity.x + (accel * driver.xxa)).coerceIn(-latTopSpeed, latTopSpeed),
newVelocity.y,
newVelocity.z)
activeInput = true
}
else {
newVelocity = Vec3(
lerp(newVelocity.x, 0.0, latTopSpeed / 20.0),
newVelocity.y,
newVelocity.z)
}
//Vertical movement based on driver input.
val vertTopSpeed = topSpeed / 2.0
val vertInput = when {
driver.jumping -> 1.0
driver.isShiftKeyDown -> -1.0
else -> 0.0
}
if (vertInput != 0.0 && state.stamina.get() > 0.0) {
newVelocity = Vec3(
newVelocity.x,
(newVelocity.y + (accel * vertInput)).coerceIn(-vertTopSpeed, vertTopSpeed),
newVelocity.z)
activeInput = true
}
else {
newVelocity = Vec3(
newVelocity.x,
lerp(newVelocity.y, 0.0, vertTopSpeed / 20.0),
newVelocity.z)
}
//Check if the ride should be gliding
if (driver.isLocalPlayer) {
if (activeInput && state.stamina.get() > 0.0) {
state.gliding.set(false)
} else {
state.gliding.set(true)
}
}
//Only perform stamina logic if the ride does not have infinite stamina
if (!vehicle.runtime.resolveBoolean(settings.infiniteStamina)) {
if (activeInput) {
state.stamina.set(state.stamina.get() - (0.05 / staminaStat).toFloat())
}
//Lose a base amount of stamina just for being airborne
state.stamina.set(state.stamina.get() - (0.01 / staminaStat).toFloat())
}
else
{
state.stamina.set(1.0f)
}
//air resistance
if( abs(newVelocity.z) > 0) {
newVelocity = newVelocity.subtract(0.0, 0.0, min(0.003 * newVelocity.z.sign, newVelocity.z))
}
state.rideVelocity.set(newVelocity)
}
override fun angRollVel(
settings: BirdAirSettings,
state: BirdAirState,
vehicle: PokemonEntity,
driver: Player,
deltaTime: Double
): Vec3 {
if (driver !is OrientationControllable) return Vec3.ZERO
val controller = (driver as OrientationControllable).orientationController
//TODO: Tie into handling
val handling = vehicle.runtime.resolveDouble(settings.handlingExpr)
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
val rotationChangeRate = 10.0
var yawForce = rotationChangeRate * sin(Math.toRadians(controller.roll.toDouble()))
//for a bit of correction on the rolls limit it to a quarter the amount
var pitchForce = -0.35 * rotationChangeRate * abs(sin(Math.toRadians(controller.roll.toDouble())))
//limit rotation modulation when pitched up heavily or pitched down heavily
yawForce *= abs(cos(Math.toRadians(controller.pitch.toDouble())))
pitchForce *= abs(cos(Math.toRadians(controller.pitch.toDouble()))) * 1.5
return Vec3(yawForce, pitchForce, 0.0)
}
override fun rotationOnMouseXY(
settings: BirdAirSettings,
state: BirdAirState,
vehicle: PokemonEntity,
driver: Player,
mouseY: Double,
mouseX: Double,
mouseYSmoother: SmoothDouble,
mouseXSmoother: SmoothDouble,
sensitivity: Double,
deltaTime: Double
): Vec3 {
if (driver !is OrientationControllable) return Vec3.ZERO
val controller = (driver as OrientationControllable).orientationController
val handling = vehicle.runtime.resolveDouble(settings.handlingExpr)
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
//Smooth out mouse input.
val smoothingSpeed = 4.0
val invertRoll = if (Cobblemon.config.invertRoll) -1 else 1
val invertPitch = if (Cobblemon.config.invertPitch) -1 else 1
val mouseXc = (mouseX).coerceIn(-60.0, 60.0)
val mouseYc = (mouseY).coerceIn(-60.0, 60.0)
val xInput = mouseXSmoother.getNewDeltaValue(mouseXc * 0.1 * invertRoll, deltaTime * smoothingSpeed);
val yInput = mouseYSmoother.getNewDeltaValue(mouseYc * 0.1 * invertPitch, deltaTime * smoothingSpeed);
//limit rolling based on handling and current speed.
//modulated by speed so that when flapping idle in air you are ont wobbling around to look around
val rotMin = 15.0
var rollForce = xInput
val rotLimit = max(handling * normalizeVal(state.rideVelocity.get().length(), 0.0, topSpeed).pow(3), rotMin)
//Limit roll by non linearly decreasing inputs towards
// a rotation limit based on the current distance from
// that rotation limit
if (abs(controller.roll + rollForce) < rotLimit) {
if (sign(rollForce) == sign(controller.roll).toDouble()) {
val d = abs(abs(controller.roll) - rotLimit)
rollForce *= (d.pow(2)) / (rotLimit.pow(2))
}
} else if (sign(rollForce) == sign(controller.roll).toDouble()) {
rollForce = 0.0
}
//Give the ability to yaw with x mouse input when at low speeds.
val yawForce = xInput * ( 1.0 - normalizeVal(state.rideVelocity.get().length(), 0.0, topSpeed).pow(3))
//yaw, pitch, roll
return Vec3(yawForce, yInput, rollForce)
}
/*
* Normalizes the given value between a min and a max.
* The result is clamped between 0.0 and 1.0, where 0.0 represents x is at or below min
* and 1.0 represents x is at or above it.
*/
private fun normalizeVal(x: Double, min: Double, max: Double): Double {
require(max > min) { "max must be greater than min" }
return ((x - min) / (max - min)).coerceIn(0.0, 1.0)
}
override fun canJump(
settings: BirdAirSettings,
state: BirdAirState,
vehicle: PokemonEntity,
driver: Player
): Boolean {
return false
}
override fun setRideBar(
settings: BirdAirSettings,
state: BirdAirState,
vehicle: PokemonEntity,
driver: Player
): Float {
return (state.stamina.get() / 1.0f)
}
override fun jumpForce(
settings: BirdAirSettings,
state: BirdAirState,
vehicle: PokemonEntity,
driver: Player,
jumpStrength: Int
): Vec3 {
return Vec3.ZERO
}
override fun gravity(
settings: BirdAirSettings,
state: BirdAirState,
vehicle: PokemonEntity,
regularGravity: Double
): Double {
return 0.0
}
override fun rideFovMultiplier(
settings: BirdAirSettings,
state: BirdAirState,
vehicle: PokemonEntity,
driver: Player
): Float {
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
val glideTopSpeed = vehicle.runtime.resolveDouble(settings.glidespeedExpr)
//Must I ensure that topspeed is greater than minimum?
val normalizedGlideSpeed = normalizeVal(state.rideVelocity.get().length(), topSpeed, glideTopSpeed)
//Only ever want the fov change to be a max of 0.2 and for it to have non linear scaling.
return 1.0f + normalizedGlideSpeed.pow(2).toFloat() * 0.2f
}
override fun useAngVelSmoothing(
settings: BirdAirSettings,
state: BirdAirState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun useRidingAltPose(
settings: BirdAirSettings,
state: BirdAirState,
vehicle: PokemonEntity,
driver: Player
): Boolean {
return state.gliding.get()
}
override fun inertia(settings: BirdAirSettings, state: BirdAirState, vehicle: PokemonEntity): Double {
return 0.5
}
override fun shouldRoll(settings: BirdAirSettings, state: BirdAirState, vehicle: PokemonEntity): Boolean {
return true
}
override fun turnOffOnGround(settings: BirdAirSettings, state: BirdAirState, vehicle: PokemonEntity): Boolean {
return false
}
override fun dismountOnShift(settings: BirdAirSettings, state: BirdAirState, vehicle: PokemonEntity): Boolean {
return false
}
override fun shouldRotatePokemonHead(
settings: BirdAirSettings,
state: BirdAirState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun shouldRotatePlayerHead(
settings: BirdAirSettings,
state: BirdAirState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun createDefaultState(settings: BirdAirSettings) = BirdAirState()
}
class BirdAirSettings : RidingBehaviourSettings {
override val key = BirdAirBehaviour.KEY
var infiniteAltitude: Expression = "false".asExpression()
private set
var infiniteStamina: Expression = "false".asExpression()
private set
//max y level for the ride
var jumpExpr: Expression = "q.get_ride_stats('JUMP', 'AIR', 200.0, 128.0)".asExpression()
var handlingExpr: Expression = "q.get_ride_stats('SKILL', 'AIR', 135.0, 45.0)".asExpression()
var speedExpr: Expression = "q.get_ride_stats('SPEED', 'AIR', 1.0, 0.35)".asExpression()
var accelerationExpr: Expression = "q.get_ride_stats('ACCELERATION', 'AIR', (1.0 / (20.0 * 3.0)), (1.0 / (20.0 * 8.0)))".asExpression()
var staminaExpr: Expression = "q.get_ride_stats('STAMINA', 'AIR', 120.0, 20.0)".asExpression()
var glidespeedExpr: Expression = "q.get_ride_stats('SPEED', 'AIR', 2.0, 1.0)".asExpression()
private set
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeExpression(infiniteAltitude)
buffer.writeExpression(infiniteStamina)
buffer.writeExpression(glidespeedExpr)
buffer.writeExpression(jumpExpr)
buffer.writeExpression(handlingExpr)
buffer.writeExpression(speedExpr)
buffer.writeExpression(accelerationExpr)
buffer.writeExpression(staminaExpr)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
infiniteAltitude = buffer.readExpression()
infiniteStamina = buffer.readExpression()
glidespeedExpr = buffer.readExpression()
jumpExpr = buffer.readExpression()
handlingExpr = buffer.readExpression()
speedExpr = buffer.readExpression()
accelerationExpr = buffer.readExpression()
staminaExpr = buffer.readExpression()
}
}
class BirdAirState : RidingBehaviourState() {
var gliding = ridingState(false, Side.CLIENT)
var lastGlide = ridingState(-100L, Side.CLIENT)
override fun encode(buffer: FriendlyByteBuf) {
super.encode(buffer)
buffer.writeFloat(stamina.get())
buffer.writeBoolean(gliding.get())
}
override fun decode(buffer: FriendlyByteBuf) {
super.decode(buffer)
gliding.set(buffer.readBoolean(), forced = true)
}
override fun reset() {
super.reset()
gliding.set(false, forced = true)
lastGlide.set(-100L, forced = true)
}
override fun toString(): String {
return "BirdAirState(rideVelocity=${rideVelocity.get()}, stamina=${stamina.get()}, gliding=${gliding.get()})"
}
override fun copy() = BirdAirState().also {
it.rideVelocity.set(this.rideVelocity.get(), forced = true)
it.stamina.set(this.stamina.get(), forced = true)
it.gliding.set(this.gliding.get(), forced = true)
it.lastGlide.set(this.lastGlide.get(), forced = true)
}
override fun shouldSync(previous: RidingBehaviourState): Boolean {
if (previous !is BirdAirState) return false
if (previous.gliding != gliding) return true
return super.shouldSync(previous)
}
}
@@ -0,0 +1,328 @@
/*
* 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.riding.behaviour.types
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.*
import com.cobblemon.mod.common.api.riding.posing.PoseOption
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.*
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
import net.minecraft.world.phys.shapes.Shapes
import kotlin.math.abs
import kotlin.math.cos
import kotlin.math.sin
class DolphinBehaviour : RidingBehaviour<DolphinSettings, DolphinState> {
companion object {
val KEY = cobblemonResource("swim/dolphin")
}
override val key = KEY
override val style = RidingStyle.LIQUID
val poseProvider = PoseProvider<DolphinSettings, DolphinState>(PoseType.FLOAT)
.with(PoseOption(PoseType.SWIM) { _, _, entity -> entity.entityData.get(PokemonEntity.MOVING) })
override fun isActive(settings: DolphinSettings, state: DolphinState, vehicle: PokemonEntity): Boolean {
return Shapes.create(vehicle.boundingBox).blockPositionsAsListRounded().any {
if (it.y.toDouble() == (vehicle.position().y)) {
val blockState = vehicle.level().getBlockState(it.below())
return@any (blockState.isAir || !blockState.fluidState.isEmpty ) ||
(vehicle.isInWater || vehicle.isUnderWater)
}
true
}
}
override fun pose(settings: DolphinSettings, state: DolphinState, vehicle: PokemonEntity): PoseType {
return poseProvider.select(settings, state, vehicle)
}
override fun speed(settings: DolphinSettings, state: DolphinState, vehicle: PokemonEntity, driver: Player): Float {
return vehicle.runtime.resolveFloat(settings.speed)
}
override fun rotation(
settings: DolphinSettings,
state: DolphinState,
vehicle: PokemonEntity,
driver: LivingEntity
): Vec2 {
return Vec2(driver.xRot * 0.5f, driver.yRot)
}
override fun velocity(
settings: DolphinSettings,
state: DolphinState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
val runtime = vehicle.runtime
if (!vehicle.isInWater && !vehicle.isUnderWater)
{
state.lastVelocity.set(Vec3(state.lastVelocity.get().x, state.lastVelocity.get().y - 0.035, state.lastVelocity.get().z))
return state.lastVelocity.get()
}
val driveFactor = runtime.resolveFloat(settings.driveFactor)
val strafeFactor = runtime.resolveFloat(settings.strafeFactor)
val f = driver.xxa * strafeFactor
var g = driver.zza * driveFactor
if (g <= 0.0f) {
g *= runtime.resolveFloat(settings.reverseDriveFactor)
}
var yComp = 0.0
//Get roll to add a left and right strafe during roll
//Not sure if this is desired? Will need to mess around with this
//a bit more
var zComp = 0.0
val controller = (driver as? OrientationControllable)?.orientationController
if (controller != null)
{
//Can be used again if I better figure out how to deadzone it near the tops?
zComp = -1.0 * g.toDouble() * sin(Math.toRadians(controller.roll.toDouble()))
yComp = -1.0 * g.toDouble() * sin(Math.toRadians(controller.pitch.toDouble()))
}
val currVelocity = Vec3(0.0, yComp , g.toDouble())
state.lastVelocity.set(currVelocity)
return currVelocity
}
override fun angRollVel(
settings: DolphinSettings,
state: DolphinState,
vehicle: PokemonEntity,
driver: Player,
deltaTime: Double
): Vec3 {
if (!vehicle.isInWater && !vehicle.isUnderWater)
{
return Vec3(0.0, 0.0, 0.0)
}
val controller = (driver as? OrientationControllable)?.orientationController
//this should be changed to be speed maybe?
val movingForce = driver.zza
if (controller != null) {
var yawAngVel = 3 * sin(Math.toRadians(controller.roll.toDouble())).toFloat()
var pitchAngVel = -1 * Math.abs(sin(Math.toRadians(controller.roll.toDouble())).toFloat())
//limit rotation modulation when pitched up heavily or pitched down heavily
yawAngVel *= (abs(cos(Math.toRadians(controller.pitch.toDouble())))).toFloat()
pitchAngVel *= (abs(cos(Math.toRadians(controller.pitch.toDouble())))).toFloat()
//if you are not pressing forward then don't turn
//yawAngVel *= movingForce
//Ignore x,y,z its angular velocity:
//yaw, pitch, roll
return Vec3(yawAngVel.toDouble(), pitchAngVel.toDouble(), 0.0)
}
return Vec3(0.0, 0.0, 0.0)
}
override fun rotationOnMouseXY(
settings: DolphinSettings,
state: DolphinState,
vehicle: PokemonEntity,
driver: Player,
mouseY: Double,
mouseX: Double,
mouseYSmoother: SmoothDouble,
mouseXSmoother: SmoothDouble,
sensitivity: Double,
deltaTime: Double
): Vec3 {
if (driver !is OrientationControllable) return Vec3.ZERO
//Might need to add the smoothing here for default.
val invertRoll = if (Cobblemon.config.invertRoll) -1 else 1
val invertPitch = if (Cobblemon.config.invertPitch) -1 else 1
return Vec3(0.0, mouseY * invertPitch, mouseX * invertRoll)
}
override fun canJump(
settings: DolphinSettings,
state: DolphinState,
vehicle: PokemonEntity,
driver: Player
): Boolean {
return vehicle.runtime.resolveBoolean(settings.canJump)
}
override fun setRideBar(
settings: DolphinSettings,
state: DolphinState,
vehicle: PokemonEntity,
driver: Player
): Float {
return 0.0f
}
override fun jumpForce(
settings: DolphinSettings,
state: DolphinState,
vehicle: PokemonEntity,
driver: Player,
jumpStrength: Int
): Vec3 {
val runtime = vehicle.runtime
runtime.environment.query.addFunction("jump_strength") { DoubleValue(jumpStrength.toDouble()) }
val jumpVector = settings.jumpVector.map { runtime.resolveFloat(it) }
return Vec3(jumpVector[0].toDouble(), jumpVector[1].toDouble(), jumpVector[2].toDouble())
}
override fun gravity(
settings: DolphinSettings,
state: DolphinState,
vehicle: PokemonEntity,
regularGravity: Double
): Double {
return 0.0
}
override fun rideFovMultiplier(
settings: DolphinSettings,
state: DolphinState,
vehicle: PokemonEntity,
driver: Player
): Float {
return 1.0f
}
override fun useAngVelSmoothing(settings: DolphinSettings, state: DolphinState, vehicle: PokemonEntity): Boolean {
return false
}
override fun useRidingAltPose(
settings: DolphinSettings,
state: DolphinState,
vehicle: PokemonEntity,
driver: Player
): Boolean {
return false
}
override fun inertia(settings: DolphinSettings, state: DolphinState, vehicle: PokemonEntity): Double {
return 0.05
}
override fun shouldRoll(settings: DolphinSettings, state: DolphinState, vehicle: PokemonEntity): Boolean {
return true
}
override fun turnOffOnGround(settings: DolphinSettings, state: DolphinState, vehicle: PokemonEntity): Boolean {
return true
}
override fun dismountOnShift(settings: DolphinSettings, state: DolphinState, vehicle: PokemonEntity): Boolean {
return false
}
override fun shouldRotatePokemonHead(
settings: DolphinSettings,
state: DolphinState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun shouldRotatePlayerHead(
settings: DolphinSettings,
state: DolphinState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun createDefaultState(settings: DolphinSettings) = DolphinState()
}
class DolphinSettings : RidingBehaviourSettings {
override val key = DolphinBehaviour.KEY
var canJump = "true".asExpression()
private set
var jumpVector = listOf("0".asExpression(), "0.3".asExpression(), "0".asExpression())
private set
var speed = "1.0".asExpression()
private set
var driveFactor = "1.0".asExpression()
private set
var reverseDriveFactor = "0.25".asExpression()
private set
var strafeFactor = "0.2".asExpression()
private set
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeExpression(canJump)
buffer.writeExpression(jumpVector[0])
buffer.writeExpression(jumpVector[1])
buffer.writeExpression(jumpVector[2])
buffer.writeExpression(speed)
buffer.writeExpression(driveFactor)
buffer.writeExpression(reverseDriveFactor)
buffer.writeExpression(strafeFactor)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
canJump = buffer.readExpression()
jumpVector = listOf(
buffer.readExpression(),
buffer.readExpression(),
buffer.readExpression()
)
speed = buffer.readExpression()
driveFactor = buffer.readExpression()
reverseDriveFactor = buffer.readExpression()
strafeFactor = buffer.readExpression()
}
}
class DolphinState : RidingBehaviourState() {
var lastVelocity = ridingState(Vec3.ZERO, Side.BOTH)
override fun reset() {
super.reset()
lastVelocity.set(Vec3.ZERO, forced = true)
}
override fun toString(): String {
return "DolphinState(lastVelocity=${lastVelocity.get()})"
}
override fun copy() = DolphinState().also {
it.stamina.set(stamina.get(), forced = true)
it.rideVelocity.set(rideVelocity.get(), forced = true)
it.lastVelocity.set(lastVelocity.get(), forced = true)
}
}
@@ -0,0 +1,404 @@
/*
* 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.riding.behaviour.types
import com.bedrockk.molang.Expression
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.*
import com.cobblemon.mod.common.api.riding.posing.PoseOption
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.*
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
import net.minecraft.world.phys.shapes.Shapes
import kotlin.math.max
import kotlin.math.min
import kotlin.math.pow
class GenericLandBehaviour : RidingBehaviour<GenericLandSettings, GenericLandState> {
companion object {
val KEY = cobblemonResource("land/generic")
}
override val key = KEY
override val style = RidingStyle.LAND
val poseProvider = PoseProvider<GenericLandSettings, GenericLandState>(PoseType.STAND)
.with(PoseOption(PoseType.WALK) { _, state, _ ->
return@PoseOption state.rideVelocity.get().z > 0.1
})
override fun isActive(
settings: GenericLandSettings,
state: GenericLandState,
vehicle: PokemonEntity
): Boolean {
return Shapes.create(vehicle.boundingBox).blockPositionsAsListRounded().any {
//Need to check other fluids
if (vehicle.isInWater || vehicle.isUnderWater) {
return@any false
}
//This might not actually work, depending on what the yPos actually is. yPos of the middle of the entity? the feet?
if (it.y.toDouble() == (vehicle.position().y)) {
val blockState = vehicle.level().getBlockState(it.below())
return@any !blockState.isAir && blockState.fluidState.isEmpty
}
true
}
}
override fun pose(
settings: GenericLandSettings,
state: GenericLandState,
vehicle: PokemonEntity
): PoseType {
return this.poseProvider.select(settings, state, vehicle)
}
override fun speed(
settings: GenericLandSettings,
state: GenericLandState,
vehicle: PokemonEntity,
driver: Player
): Float {
return state.rideVelocity.get().length().toFloat()
}
override fun rotation(
settings: GenericLandSettings,
state: GenericLandState,
vehicle: PokemonEntity,
driver: LivingEntity
): Vec2 {
return Vec2(driver.xRot * 0.5f, driver.yRot)
}
override fun velocity(
settings: GenericLandSettings,
state: GenericLandState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
val runtime = vehicle.runtime
val driveFactor = runtime.resolveFloat(settings.driveFactor)
val strafeFactor = runtime.resolveFloat(settings.strafeFactor)
val f = driver.xxa * strafeFactor
var g = driver.zza * driveFactor
if (g <= 0.0f) {
g *= runtime.resolveFloat(settings.reverseDriveFactor)
}
val gravity = -1.0
val newVelocity = calculateRideSpaceVel(settings, state, vehicle, driver)
//Jump the thang!
if (driver.jumping && vehicle.onGround()) {
state.rideVelocity.set(Vec3(newVelocity.x, 2.0, newVelocity.z))
} else {
state.rideVelocity.set(newVelocity)
}
//This is cheap.
//Also make it stop quicker the slower it is.
val maxSpeed = 1.0
//state.currVel = state.currVel.lerp( Vec3(0.0, state.currVel.y, 0.0), 1.0/20.0 )
//entity.deltaMovement = entity.deltaMovement.lerp(velocity, 1.0)
//state.currVel = state.currVel.add( f.toDouble() / 20.0, gravity / 20.0 , g.toDouble() / 20.0)
return state.rideVelocity.get()
}
private fun calculateRideSpaceVel(
settings: GenericLandSettings,
state: GenericLandState,
vehicle: PokemonEntity,
driver: Player
): Vec3 {
val topSpeed = vehicle.runtime.resolveDouble(settings.topSpeedExpr)
val accel = vehicle.runtime.resolveDouble(settings.accelExpr)
val speed = state.rideVelocity.get().length()
val minSpeed = 0.0
var newVelocity = Vec3(state.rideVelocity.get().x, state.rideVelocity.get().y, state.rideVelocity.get().z)
//speed up and slow down based on input
if (driver.zza > 0.0 && speed <= topSpeed && state.stamina.get() > 0.0f) {
//modify acceleration to be slower when at closer speeds to top speed
val accelMod = max((normalizeSpeed(speed, minSpeed, topSpeed) - 1).pow(2), 0.1)
newVelocity = Vec3(newVelocity.x, newVelocity.y, min(newVelocity.z + (accel * accelMod), topSpeed))
} else if (driver.zza >= 0.0 && state.stamina.get() == 0.0f || speed > topSpeed) {
newVelocity = Vec3(newVelocity.x, newVelocity.y, max(newVelocity.z - ((accel) / 4), minSpeed))
} else if (driver.zza < 0.0 && speed > minSpeed) {
//modify deccel to be slower when at closer speeds to minimum speed
val deccelMod = max(-(normalizeSpeed(speed, minSpeed, topSpeed)) + 1, 0.0)
//Decelerate currently always a constant half of max acceleration.
newVelocity =
Vec3(newVelocity.x, newVelocity.y, max(newVelocity.z - ((accel * deccelMod) / 2), minSpeed))
}
if (vehicle.onGround()) {
newVelocity = Vec3(newVelocity.x, 0.0, newVelocity.z)
} else {
newVelocity = Vec3(newVelocity.x, max(newVelocity.y - 0.2, -1.0), newVelocity.z)
}
return newVelocity
}
/*
* Normalizes the current speed between minSpeed and maxSpeed.
* The result is clamped between 0.0 and 1.0, where 0.0 represents minSpeed and 1.0 represents maxSpeed.
*/
private fun normalizeSpeed(currSpeed: Double, minSpeed: Double, maxSpeed: Double): Double {
require(maxSpeed > minSpeed) { "maxSpeed must be greater than minSpeed" }
return ((currSpeed - minSpeed) / (maxSpeed - minSpeed)).coerceIn(0.0, 1.0)
}
override fun angRollVel(
settings: GenericLandSettings,
state: GenericLandState,
vehicle: PokemonEntity,
driver: Player,
deltaTime: Double
): Vec3 {
return Vec3.ZERO
}
override fun rotationOnMouseXY(
settings: GenericLandSettings,
state: GenericLandState,
vehicle: PokemonEntity,
driver: Player,
mouseY: Double,
mouseX: Double,
mouseYSmoother: SmoothDouble,
mouseXSmoother: SmoothDouble,
sensitivity: Double,
deltaTime: Double
): Vec3 {
//Smooth out mouse input.
val smoothingSpeed = 4
val xInput = mouseXSmoother.getNewDeltaValue(mouseX * 0.1, deltaTime * smoothingSpeed);
val yInput = mouseYSmoother.getNewDeltaValue(mouseY * 0.1, deltaTime * smoothingSpeed);
//yaw, pitch, roll
return Vec3(0.0, yInput, xInput)
}
override fun canJump(
settings: GenericLandSettings,
state: GenericLandState,
vehicle: PokemonEntity,
driver: Player
): Boolean {
return false
}
override fun setRideBar(
settings: GenericLandSettings,
state: GenericLandState,
vehicle: PokemonEntity,
driver: Player
): Float {
//Retrieve stamina from state and tick up at a rate of 0.1 a second
val staminaGain = (1.0 / 20.0) * 0.1
state.stamina.set(min(state.stamina.get() + staminaGain, 1.0).toFloat())
return (state.stamina.get() / 1.0f)
}
override fun jumpForce(
settings: GenericLandSettings,
state: GenericLandState,
vehicle: PokemonEntity,
driver: Player,
jumpStrength: Int
): Vec3 {
val runtime = vehicle.runtime
runtime.environment.query.addFunction("jump_strength") { DoubleValue(jumpStrength.toDouble()) }
val jumpVector = settings.jumpVector.map { runtime.resolveFloat(it) }
return Vec3(jumpVector[0].toDouble(), jumpVector[1].toDouble(), jumpVector[2].toDouble())
}
override fun gravity(
settings: GenericLandSettings,
state: GenericLandState,
vehicle: PokemonEntity,
regularGravity: Double
): Double {
return 0.0
}
override fun rideFovMultiplier(
settings: GenericLandSettings,
state: GenericLandState,
vehicle: PokemonEntity,
driver: Player
): Float {
return 1.0f
}
override fun useAngVelSmoothing(
settings: GenericLandSettings,
state: GenericLandState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun useRidingAltPose(
settings: GenericLandSettings,
state: GenericLandState,
vehicle: PokemonEntity,
driver: Player
): Boolean {
return false
}
override fun inertia(
settings: GenericLandSettings,
state: GenericLandState,
vehicle: PokemonEntity
): Double {
return 0.5
}
override fun shouldRoll(
settings: GenericLandSettings,
state: GenericLandState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun turnOffOnGround(
settings: GenericLandSettings,
state: GenericLandState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun dismountOnShift(
settings: GenericLandSettings,
state: GenericLandState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun shouldRotatePokemonHead(
settings: GenericLandSettings,
state: GenericLandState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun shouldRotatePlayerHead(
settings: GenericLandSettings,
state: GenericLandState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun createDefaultState(settings: GenericLandSettings) = GenericLandState()
}
class GenericLandSettings : RidingBehaviourSettings {
override val key = GenericLandBehaviour.KEY
var driveFactor = "1.0".asExpression()
private set
var strafeFactor = "0.2".asExpression()
private set
var reverseDriveFactor = "0.25".asExpression()
private set
var topSpeedExpr: Expression = "q.get_ride_stats('SPEED', 'LAND', 1.0, 0.3)".asExpression()
private set
// Max accel is a whole 1.0 in 1 second. The conversion in the function below is to convert seconds to ticks
var accelExpr: Expression =
"q.get_ride_stats('ACCELERATION', 'LAND', (1.0 / (20.0 * 0.5)), (1.0 / (20.0 * 5.0)))".asExpression()
private set
var jumpVector = listOf("0".asExpression(), "0.3".asExpression(), "0".asExpression())
private set
// Between 30 seconds and 10 seconds at the lowest when at full speed.
var staminaExpr: Expression = "q.get_ride_stats('STAMINA', 'LAND', 30.0, 10.0)".asExpression()
private set
//Between a one block jump and a ten block jump
var jumpExpr: Expression = "q.get_ride_stats('JUMP', 'LAND', 10.0, 1.0)".asExpression()
private set
var handlingExpr: Expression = "q.get_ride_stats('SKILL', 'LAND', 140.0, 20.0)".asExpression()
private set
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeExpression(driveFactor)
buffer.writeExpression(strafeFactor)
buffer.writeExpression(reverseDriveFactor)
buffer.writeExpression(topSpeedExpr)
buffer.writeExpression(accelExpr)
buffer.writeExpression(jumpVector[0])
buffer.writeExpression(jumpVector[1])
buffer.writeExpression(jumpVector[2])
buffer.writeExpression(staminaExpr)
buffer.writeExpression(jumpExpr)
buffer.writeExpression(handlingExpr)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
driveFactor = buffer.readExpression()
strafeFactor = buffer.readExpression()
reverseDriveFactor = buffer.readExpression()
topSpeedExpr = buffer.readExpression()
accelExpr = buffer.readExpression()
jumpVector = listOf(
buffer.readExpression(),
buffer.readExpression(),
buffer.readExpression()
)
staminaExpr = buffer.readExpression()
jumpExpr = buffer.readExpression()
handlingExpr = buffer.readExpression()
}
}
class GenericLandState : RidingBehaviourState() {
var currSpeed = ridingState(0.0, Side.BOTH)
override fun copy() = GenericLandState().also {
it.rideVelocity.set(this.rideVelocity.get(), forced = true)
it.stamina.set(this.stamina.get(), forced = true)
it.currSpeed.set(this.currSpeed.get(), forced = true)
}
override fun shouldSync(previous: RidingBehaviourState): Boolean {
if (previous !is GenericLandState) return false
return super.shouldSync(previous)
}
}
@@ -0,0 +1,264 @@
/*
* 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.riding.behaviour.types
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourState
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourSettings
import com.cobblemon.mod.common.api.riding.posing.PoseOption
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.*
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
import net.minecraft.world.phys.shapes.Shapes
class GenericSwimBehaviour : RidingBehaviour<GenericSwimSettings, RidingBehaviourState> {
companion object {
val KEY = cobblemonResource("swim/generic")
}
override val key = KEY
override val style = RidingStyle.LIQUID
val poseProvider = PoseProvider<GenericSwimSettings, RidingBehaviourState>(PoseType.FLOAT)
.with(PoseOption(PoseType.SWIM) { _, _, entity -> entity.isSwimming && entity.entityData.get(PokemonEntity.MOVING) })
override fun isActive(settings: GenericSwimSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Boolean {
return Shapes.create(vehicle.boundingBox).blockPositionsAsListRounded().any {
if (vehicle.isInWater || vehicle.isUnderWater) {
return@any true
}
val blockState = vehicle.level().getBlockState(it)
return@any !blockState.fluidState.isEmpty
}
}
override fun pose(settings: GenericSwimSettings, state: RidingBehaviourState, vehicle: PokemonEntity): PoseType {
return poseProvider.select(settings, state, vehicle)
}
override fun speed(settings: GenericSwimSettings, state: RidingBehaviourState, vehicle: PokemonEntity, driver: Player): Float {
return vehicle.runtime.resolveFloat(settings.speed)
}
override fun rotation(
settings: GenericSwimSettings,
state: RidingBehaviourState,
vehicle: PokemonEntity,
driver: LivingEntity
): Vec2 {
return Vec2(driver.xRot * 0.5f, driver.yRot)
}
override fun velocity(
settings: GenericSwimSettings,
state: RidingBehaviourState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
val runtime = vehicle.runtime
val driveFactor = runtime.resolveFloat(settings.driveFactor)
val strafeFactor = runtime.resolveFloat(settings.strafeFactor)
val f = driver.xxa * strafeFactor
var g = driver.zza * driveFactor
if (g <= 0.0f) {
g *= runtime.resolveFloat(settings.reverseDriveFactor)
}
val velocity = Vec3(f.toDouble(), 0.0, g.toDouble())
return velocity
}
override fun angRollVel(
settings: GenericSwimSettings,
state: RidingBehaviourState,
vehicle: PokemonEntity,
driver: Player,
deltaTime: Double
): Vec3 {
return Vec3.ZERO
}
override fun rotationOnMouseXY(
settings: GenericSwimSettings,
state: RidingBehaviourState,
vehicle: PokemonEntity,
driver: Player,
mouseY: Double,
mouseX: Double,
mouseYSmoother: SmoothDouble,
mouseXSmoother: SmoothDouble,
sensitivity: Double,
deltaTime: Double
): Vec3 {
if (driver !is OrientationControllable) return Vec3.ZERO
//Might need to add the smoothing here for default.
val invertRoll = if (Cobblemon.config.invertRoll) -1 else 1
val invertPitch = if (Cobblemon.config.invertPitch) -1 else 1
return Vec3(0.0, mouseY * invertPitch, mouseX * invertRoll)
}
override fun canJump(
settings: GenericSwimSettings,
state: RidingBehaviourState,
vehicle: PokemonEntity,
driver: Player
): Boolean {
return vehicle.runtime.resolveBoolean(settings.canJump)
}
override fun setRideBar(
settings: GenericSwimSettings,
state: RidingBehaviourState,
vehicle: PokemonEntity,
driver: Player
): Float {
return 0.0f
}
override fun jumpForce(
settings: GenericSwimSettings,
state: RidingBehaviourState,
vehicle: PokemonEntity,
driver: Player,
jumpStrength: Int
): Vec3 {
val runtime = vehicle.runtime
runtime.environment.query.addFunction("jump_strength") { DoubleValue(jumpStrength.toDouble()) }
val jumpVector = settings.jumpVector.map { runtime.resolveFloat(it) }
return Vec3(jumpVector[0].toDouble(), jumpVector[1].toDouble(), jumpVector[2].toDouble())
}
override fun gravity(
settings: GenericSwimSettings,
state: RidingBehaviourState,
vehicle: PokemonEntity,
regularGravity: Double
): Double {
return regularGravity
}
override fun rideFovMultiplier(
settings: GenericSwimSettings,
state: RidingBehaviourState,
vehicle: PokemonEntity,
driver: Player
): Float {
return 1.0f
}
override fun useAngVelSmoothing(settings: GenericSwimSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Boolean {
return false
}
override fun useRidingAltPose(
settings: GenericSwimSettings,
state: RidingBehaviourState,
vehicle: PokemonEntity,
driver: Player
): Boolean {
return false
}
override fun inertia(settings: GenericSwimSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Double {
return 0.5
}
override fun shouldRoll(settings: GenericSwimSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Boolean {
return false
}
override fun turnOffOnGround(settings: GenericSwimSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Boolean {
return false
}
override fun dismountOnShift(settings: GenericSwimSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Boolean {
return false
}
override fun shouldRotatePokemonHead(
settings: GenericSwimSettings,
state: RidingBehaviourState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun shouldRotatePlayerHead(
settings: GenericSwimSettings,
state: RidingBehaviourState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun createDefaultState(settings: GenericSwimSettings) = RidingBehaviourState()
}
class GenericSwimSettings : RidingBehaviourSettings {
override val key = GenericSwimBehaviour.KEY
var canJump = "true".asExpression()
private set
var jumpVector = listOf("0".asExpression(), "0.3".asExpression(), "0".asExpression())
private set
var speed = "1.0".asExpression()
private set
var driveFactor = "1.0".asExpression()
private set
var reverseDriveFactor = "0.25".asExpression()
private set
var strafeFactor = "0.2".asExpression()
private set
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeExpression(canJump)
buffer.writeExpression(jumpVector[0])
buffer.writeExpression(jumpVector[1])
buffer.writeExpression(jumpVector[2])
buffer.writeExpression(speed)
buffer.writeExpression(driveFactor)
buffer.writeExpression(reverseDriveFactor)
buffer.writeExpression(strafeFactor)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
canJump = buffer.readExpression()
jumpVector = listOf(
buffer.readExpression(),
buffer.readExpression(),
buffer.readExpression()
)
speed = buffer.readExpression()
driveFactor = buffer.readExpression()
reverseDriveFactor = buffer.readExpression()
strafeFactor = buffer.readExpression()
}
}
@@ -0,0 +1,214 @@
/*
* 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.riding.behaviour.types
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourState
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourSettings
import com.cobblemon.mod.common.api.riding.posing.PoseOption
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.*
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
class GliderAirBehaviour : RidingBehaviour<GliderAirSettings, RidingBehaviourState> {
companion object {
val KEY = cobblemonResource("air/glider")
}
override val key = KEY
override val style = RidingStyle.AIR
val poseProvider = PoseProvider<GliderAirSettings, RidingBehaviourState>(PoseType.HOVER)
.with(PoseOption(PoseType.FLY) { _, _, entity -> entity.entityData.get(PokemonEntity.MOVING) })
override fun isActive(settings: GliderAirSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Boolean {
return true
}
override fun pose(settings: GliderAirSettings, state: RidingBehaviourState, vehicle: PokemonEntity): PoseType {
return poseProvider.select(settings, state, vehicle)
}
override fun speed(settings: GliderAirSettings, state: RidingBehaviourState, vehicle: PokemonEntity, driver: Player): Float {
return vehicle.runtime.resolveFloat(settings.speed)
}
override fun rotation(
settings: GliderAirSettings,
state: RidingBehaviourState,
vehicle: PokemonEntity,
driver: LivingEntity
): Vec2 {
return Vec2(driver.xRot, driver.yRot)
}
override fun velocity(
settings: GliderAirSettings,
state: RidingBehaviourState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
val xVector = if (vehicle.runtime.resolveBoolean(settings.canStrafe)) driver.xxa.toDouble() else 0.0
val yVector = -vehicle.runtime.resolveDouble(settings.glideSpeed)
val zVector = driver.zza.toDouble()
val statSpeed = vehicle.rideProp.calculate(RidingStat.SPEED, RidingStyle.AIR, 0 )
return Vec3(xVector, yVector, zVector)
}
override fun angRollVel(
settings: GliderAirSettings,
state: RidingBehaviourState,
vehicle: PokemonEntity,
driver: Player,
deltaTime: Double
): Vec3 {
return Vec3.ZERO
}
override fun rotationOnMouseXY(
settings: GliderAirSettings,
state: RidingBehaviourState,
vehicle: PokemonEntity,
driver: Player,
mouseY: Double,
mouseX: Double,
mouseYSmoother: SmoothDouble,
mouseXSmoother: SmoothDouble,
sensitivity: Double,
deltaTime: Double
): Vec3 {
if (driver !is OrientationControllable) return Vec3.ZERO
//Might need to add the smoothing here for default.
val invertRoll = if (Cobblemon.config.invertRoll) -1 else 1
val invertPitch = if (Cobblemon.config.invertPitch) -1 else 1
return Vec3(0.0, mouseY * invertPitch, mouseX * invertRoll)
}
override fun canJump(settings: GliderAirSettings, state: RidingBehaviourState, vehicle: PokemonEntity, driver: Player): Boolean {
return false
}
override fun setRideBar(
settings: GliderAirSettings,
state: RidingBehaviourState,
vehicle: PokemonEntity,
driver: Player
): Float {
return 0.0f
}
override fun jumpForce(
settings: GliderAirSettings,
state: RidingBehaviourState,
vehicle: PokemonEntity,
driver: Player,
jumpStrength: Int
): Vec3 {
return Vec3.ZERO
}
override fun gravity(
settings: GliderAirSettings,
state: RidingBehaviourState,
vehicle: PokemonEntity,
regularGravity: Double
): Double {
return 0.0
}
override fun rideFovMultiplier(
settings: GliderAirSettings,
state: RidingBehaviourState,
vehicle: PokemonEntity,
driver: Player
): Float {
return 1.0f
}
override fun useAngVelSmoothing(settings: GliderAirSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Boolean {
return false
}
override fun useRidingAltPose(
settings: GliderAirSettings,
state: RidingBehaviourState,
vehicle: PokemonEntity,
driver: Player
): Boolean {
return false
}
override fun inertia(settings: GliderAirSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Double {
return 0.5
}
override fun shouldRoll(settings: GliderAirSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Boolean {
return false
}
override fun turnOffOnGround(settings: GliderAirSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Boolean {
return false
}
override fun dismountOnShift(settings: GliderAirSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Boolean {
return false
}
override fun shouldRotatePokemonHead(settings: GliderAirSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Boolean {
return false
}
override fun shouldRotatePlayerHead(settings: GliderAirSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Boolean {
return false
}
override fun createDefaultState(settings: GliderAirSettings) = RidingBehaviourState()
}
class GliderAirSettings : RidingBehaviourSettings {
override val key = GliderAirBehaviour.KEY
var glideSpeed: Expression = "0.1".asExpression()
private set
var speed: Expression = "1.0".asExpression()
private set
var canStrafe: Expression = "false".asExpression()
private set
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeExpression(glideSpeed)
buffer.writeExpression(speed)
buffer.writeExpression(canStrafe)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
glideSpeed = buffer.readExpression()
speed = buffer.readExpression()
canStrafe = buffer.readExpression()
}
}
@@ -6,18 +6,22 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.pokemon.riding.controllers
package com.cobblemon.mod.common.api.riding.behaviour.types
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.api.riding.controller.RideController
import com.cobblemon.mod.common.api.riding.controller.posing.PoseOption
import com.cobblemon.mod.common.api.riding.controller.posing.PoseProvider
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourState
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourSettings
import com.cobblemon.mod.common.api.riding.posing.PoseOption
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.*
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
@@ -28,25 +32,25 @@ import kotlin.math.abs
import kotlin.math.pow
import kotlin.math.sin
/**
* Allows for flight like a bird controller, except you don't have to do any "flapping" motion,
* you can just go up and down like creative
*
* @author Apion, Jackowes
* @since January 1, 2025
*/
class HelicopterAirController : RideController {
override val key = KEY
override val poseProvider = PoseProvider(PoseType.HOVER)
.with(PoseOption(PoseType.FLY) { it.entityData.get(PokemonEntity.MOVING) })
class HelicopterBehaviour : RidingBehaviour<HelicopterSettings, RidingBehaviourState> {
companion object {
val KEY = cobblemonResource("air/helicopter")
val ROTATION_LIMIT = 30.0f
}
//If there are only fluid blocks or air block below the ride
//then activate the controller. If it is in water the ride will
//dismount accordingly
override val condition: (PokemonEntity) -> Boolean = { entity ->
Shapes.create(entity.boundingBox).blockPositionsAsListRounded().any {
if (it.y.toDouble() == (entity.position().y)) {
val blockState = entity.level().getBlockState(it.below())
override val key = KEY
override val style = RidingStyle.AIR
val poseProvider = PoseProvider<HelicopterSettings, RidingBehaviourState>(PoseType.HOVER)
.with(PoseOption(PoseType.FLY) { _, _, entity -> entity.entityData.get(PokemonEntity.MOVING) })
override fun isActive(settings: HelicopterSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Boolean {
//If there are only fluid blocks or air block below the ride
//then activate the controller. If it is in water the ride will
//dismount accordingly
return Shapes.create(vehicle.boundingBox).blockPositionsAsListRounded().any {
if (it.y.toDouble() == (vehicle.position().y)) {
val blockState = vehicle.level().getBlockState(it.below())
return@any (blockState.isAir || !blockState.fluidState.isEmpty)
}
true
@@ -54,33 +58,38 @@ class HelicopterAirController : RideController {
}
}
var gravity: Expression = "1.0".asExpression()
private set
var horizontalAcceleration: Expression = "0.1".asExpression()
private set
var verticalVelocity: Expression = "1.0".asExpression()
private set
var speed: Expression = "1.0".asExpression()
private set
override fun pose(settings: HelicopterSettings, state: RidingBehaviourState, vehicle: PokemonEntity): PoseType {
return poseProvider.select(settings, state, vehicle)
}
override fun speed(entity: PokemonEntity, driver: Player): Float {
override fun speed(settings: HelicopterSettings, state: RidingBehaviourState, vehicle: PokemonEntity, driver: Player): Float {
//Increased max speed to exaggerate movement.
//This likely just needs to be a static number if it really is just
//the scalar for the velocity vector
return 2.0f
}
override fun rotation(entity: PokemonEntity, driver: LivingEntity): Vec2 {
override fun rotation(
settings: HelicopterSettings,
state: RidingBehaviourState,
vehicle: PokemonEntity,
driver: LivingEntity
): Vec2 {
return Vec2(driver.xRot * 0.5f, driver.yRot)
}
override fun velocity(entity: PokemonEntity, driver: Player, input: Vec3): Vec3 {
val runtime = getRuntime(entity)
override fun velocity(
settings: HelicopterSettings,
state: RidingBehaviourState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
val runtime = vehicle.runtime
//If the player is not modulating height then hover
var yVel = if (driver.jumping) runtime.resolveDouble(verticalVelocity)
else if (driver.isShiftKeyDown) -runtime.resolveDouble(verticalVelocity)
var yVel = if (driver.jumping) runtime.resolveDouble(settings.verticalVelocity)
else if (driver.isShiftKeyDown) -runtime.resolveDouble(settings.verticalVelocity)
else (0.0)
yVel *= 0.25
@@ -98,9 +107,13 @@ class HelicopterAirController : RideController {
return Vec3(xVel, yVel, zVel)
}
override fun angRollVel(entity: PokemonEntity, driver: Player, deltaTime: Double): Vec3 {
override fun angRollVel(
settings: HelicopterSettings,
state: RidingBehaviourState,
vehicle: PokemonEntity,
driver: Player,
deltaTime: Double
): Vec3 {
val controller = (driver as? OrientationControllable)?.orientationController
//In degrees per second? It was supposed to be I think but
@@ -149,35 +162,15 @@ class HelicopterAirController : RideController {
return Vec3(0.0, 0.0, 0.0)
}
override fun useAngVelSmoothing(entity: PokemonEntity): Boolean = true
override fun inertia(entity: PokemonEntity): Double = 0.1
override fun canJump(
entity: PokemonEntity,
driver: Player
): Boolean {
return false
}
override fun jumpForce(
entity: PokemonEntity,
driver: Player,
jumpStrength: Int
): Vec3 {
return Vec3.ZERO
}
override fun shouldRoll(entity: PokemonEntity): Boolean = true
override fun rotationOnMouseXY(
entity: PokemonEntity,
settings: HelicopterSettings,
state: RidingBehaviourState,
vehicle: PokemonEntity,
driver: Player,
yMouse: Double,
xMouse: Double,
yMouseSmoother: SmoothDouble,
xMouseSmoother: SmoothDouble,
mouseY: Double,
mouseX: Double,
mouseYSmoother: SmoothDouble,
mouseXSmoother: SmoothDouble,
sensitivity: Double,
deltaTime: Double
): Vec3 {
@@ -186,18 +179,117 @@ class HelicopterAirController : RideController {
val controller = (driver as OrientationControllable).orientationController
val invertYaw = if (Cobblemon.config.invertYaw) -1 else 1
//yaw, pitch, roll
return Vec3(xMouse * invertYaw, 0.0, 0.0)
return Vec3(mouseX * invertYaw, 0.0, 0.0)
}
override fun gravity(entity: PokemonEntity, regularGravity: Double): Double {
override fun canJump(
settings: HelicopterSettings,
state: RidingBehaviourState,
vehicle: PokemonEntity,
driver: Player
): Boolean {
return false
}
override fun setRideBar(
settings: HelicopterSettings,
state: RidingBehaviourState,
vehicle: PokemonEntity,
driver: Player
): Float {
return 0.0f
}
override fun jumpForce(
settings: HelicopterSettings,
state: RidingBehaviourState,
vehicle: PokemonEntity,
driver: Player,
jumpStrength: Int
): Vec3 {
return Vec3.ZERO
}
override fun gravity(
settings: HelicopterSettings,
state: RidingBehaviourState,
vehicle: PokemonEntity,
regularGravity: Double
): Double {
return 0.0
}
//Need to ensure that crouching lowers altitude and doesn't eject the player
override fun dismountOnShift(entity: PokemonEntity): Boolean = false
override fun rideFovMultiplier(
settings: HelicopterSettings,
state: RidingBehaviourState,
vehicle: PokemonEntity,
driver: Player
): Float {
return 1.0f
}
override fun useAngVelSmoothing(settings: HelicopterSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Boolean {
return true
}
override fun useRidingAltPose(
settings: HelicopterSettings,
state: RidingBehaviourState,
vehicle: PokemonEntity,
driver: Player
): Boolean {
return false
}
override fun inertia(settings: HelicopterSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Double {
return 0.1
}
override fun shouldRoll(settings: HelicopterSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Boolean {
return true
}
override fun turnOffOnGround(settings: HelicopterSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Boolean {
return false
}
override fun dismountOnShift(settings: HelicopterSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Boolean {
return false
}
override fun shouldRotatePokemonHead(
settings: HelicopterSettings,
state: RidingBehaviourState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun shouldRotatePlayerHead(settings: HelicopterSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Boolean {
return false
}
override fun createDefaultState(settings: HelicopterSettings) = RidingBehaviourState()
}
class HelicopterSettings : RidingBehaviourSettings {
override val key = HelicopterBehaviour.KEY
var gravity: Expression = "1.0".asExpression()
private set
var horizontalAcceleration: Expression = "0.1".asExpression()
private set
var verticalVelocity: Expression = "1.0".asExpression()
private set
var speed: Expression = "1.0".asExpression()
private set
override fun encode(buffer: RegistryFriendlyByteBuf) {
super.encode(buffer)
buffer.writeResourceLocation(key)
buffer.writeExpression(gravity)
buffer.writeExpression(horizontalAcceleration)
buffer.writeExpression(verticalVelocity)
@@ -210,10 +302,4 @@ class HelicopterAirController : RideController {
verticalVelocity = buffer.readExpression()
speed = buffer.readExpression()
}
companion object {
val KEY = cobblemonResource("air/helicopter")
val ROTATION_LIMIT = 30.0f
}
}
@@ -0,0 +1,436 @@
/*
* 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.riding.behaviour.types
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.*
import com.cobblemon.mod.common.api.riding.posing.PoseOption
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.*
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
import kotlin.math.*
class JetAirBehaviour : RidingBehaviour<JetAirSettings, JetAirState> {
companion object {
val KEY = cobblemonResource("air/jet")
}
override val key = KEY
override val style = RidingStyle.AIR
val poseProvider = PoseProvider<JetAirSettings, JetAirState>(PoseType.HOVER)
.with(PoseOption(PoseType.FLY) { _, state, _ -> state.rideVelocity.get().z > 0.1 })
override fun isActive(settings: JetAirSettings, state: JetAirState, vehicle: PokemonEntity): Boolean {
return true
}
override fun pose(settings: JetAirSettings, state: JetAirState, vehicle: PokemonEntity): PoseType {
return poseProvider.select(settings, state, vehicle)
}
override fun speed(settings: JetAirSettings, state: JetAirState, vehicle: PokemonEntity, driver: Player): Float {
//retrieve stats
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
val staminaStat = vehicle.runtime.resolveDouble(settings.staminaExpr)
//retrieve minSpeed
val minSpeed = vehicle.runtime.resolveDouble(settings.minSpeed)
//Reduce stamina unless stamina is infinite
if (!vehicle.runtime.resolveBoolean(settings.infiniteStamina)) {
//Calculate stamina loss due to speed
//At max speed it will tick down 0.1 a second so the stamina will last ten seconds
//There has got to be a better way to express this equation. It interpolates between 0.5 and 1.0
var staminaRate = (normalizeSpeed(state.rideVelocity.get().length(), minSpeed, topSpeed))
//interpolate between 0.25 and 1.0 so that you always have at least a min of 0.25 stam loss
staminaRate = 0.25 + (0.75 * staminaRate.pow(3))
//Calculate stamina loss in seconds achievable at top speed
val staminaLoss = staminaRate * (1.0 / (20.0 * staminaStat))
state.stamina.set(max(state.stamina.get() - staminaLoss, 0.0).toFloat())
} else {
state.stamina.set(1.0f)
}
return state.rideVelocity.get().length().toFloat()
}
//TODO: Move these functions to a riding util class.
/*
* Normalizes the current speed between minSpeed and maxSpeed.
* The result is clamped between 0.0 and 1.0, where 0.0 represents minSpeed and 1.0 represents maxSpeed.
*/
private fun normalizeSpeed(currSpeed: Double, minSpeed: Double, maxSpeed: Double): Double {
require(maxSpeed > minSpeed) { "maxSpeed must be greater than minSpeed" }
return ((currSpeed - minSpeed) / (maxSpeed - minSpeed)).coerceIn(0.0, 1.0)
}
override fun rotation(
settings: JetAirSettings,
state: JetAirState,
vehicle: PokemonEntity,
driver: LivingEntity
): Vec2 {
return Vec2(driver.xRot * 0.5f, driver.yRot)
}
override fun velocity(
settings: JetAirSettings,
state: JetAirState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
var upForce = 0.0
var forwardForce = 0.0
val controller = (driver as? OrientationControllable)?.orientationController
//Calculate ride space velocity
calculateRideSpaceVel(settings, state, vehicle, driver)
//Translate ride space velocity to world space velocity.
if (controller != null) {
upForce = -1.0 * sin(Math.toRadians(controller.pitch.toDouble())) * state.rideVelocity.get().z
forwardForce = cos(Math.toRadians(controller.pitch.toDouble())) * state.rideVelocity.get().z
}
//If stamina has run out then initiate forced glide down.
upForce = if (state.stamina.get() > 0.0) upForce else -0.7
val altitudeLimit = vehicle.runtime.resolveDouble(settings.jumpExpr)
//Only limit altitude if altitude is not infinite
if (!vehicle.runtime.resolveBoolean(settings.infiniteAltitude)) {
//Provide a hard limit on altitude
upForce = if (vehicle.y >= altitudeLimit && upForce > 0) 0.0 else upForce
}
val velocity = Vec3(0.0, upForce, forwardForce)
return velocity
}
/*
* Calculates the change in the ride space vector due to player input and ride state
*/
private fun calculateRideSpaceVel(
settings: JetAirSettings,
state: JetAirState,
vehicle: PokemonEntity,
driver: Player
) {
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
val accel = vehicle.runtime.resolveDouble(settings.accelerationExpr)
val altitudeLimit = vehicle.runtime.resolveDouble(settings.jumpExpr)
val minSpeed = vehicle.runtime.resolveDouble(settings.minSpeed)
val speed = state.rideVelocity.get().length()
//Give no altitude limit if at max jump stat.
val pushingHeightLimit = if (vehicle.runtime.resolveBoolean(settings.infiniteStamina)) false
else (vehicle.y >= altitudeLimit && vehicle.xRot <= 0)
//speed up and slow down based on input
if (driver.zza > 0.0 && speed < topSpeed && state.stamina.get() > 0.0f && !pushingHeightLimit) {
//modify acceleration to be slower when at closer speeds to top speed
val accelMod = max(-(normalizeSpeed(speed, minSpeed, topSpeed)) + 1, 0.0)
state.rideVelocity.set(Vec3(state.rideVelocity.get().x, state.rideVelocity.get().y, min(state.rideVelocity.get().z + (accel * accelMod), topSpeed)))
} else if (driver.zza >= 0.0 && (state.stamina.get() == 0.0f || pushingHeightLimit)) {
state.rideVelocity.set(Vec3(state.rideVelocity.get().x, state.rideVelocity.get().y, max(state.rideVelocity.get().z - ((accel) / 4), minSpeed)))
} else if (driver.zza < 0.0 && speed > minSpeed) {
//modify deccel to be slower when at closer speeds to minimum speed
val deccelMod = max((normalizeSpeed(speed, minSpeed, topSpeed) - 1).pow(2), 0.1)
//Decelerate currently always a constant half of max acceleration.
state.rideVelocity.set(Vec3(state.rideVelocity.get().x, state.rideVelocity.get().y, max(state.rideVelocity.get().z - ((accel * deccelMod) / 2), minSpeed)))
}
}
override fun angRollVel(
settings: JetAirSettings,
state: JetAirState,
vehicle: PokemonEntity,
driver: Player,
deltaTime: Double
): Vec3 {
//Cap at a rate of 5fps so frame skips dont lead to huge jumps
val cappedDeltaTime = min(deltaTime, 0.2)
//Get handling in degrees per second
val yawRotRate = vehicle.runtime.resolveDouble(settings.handlingYawExpr)
//Base the change off of deltatime.
var handlingYaw = yawRotRate * (cappedDeltaTime)
//apply stamina debuff if applicable
handlingYaw *= if (state.stamina.get() > 0.0) 1.0 else 0.5
//A+D to yaw
val yawForce = driver.xxa * handlingYaw * -1
return Vec3(yawForce, 0.0, 0.0)
}
override fun rotationOnMouseXY(
settings: JetAirSettings,
state: JetAirState,
vehicle: PokemonEntity,
driver: Player,
mouseY: Double,
mouseX: Double,
mouseYSmoother: SmoothDouble,
mouseXSmoother: SmoothDouble,
sensitivity: Double,
deltaTime: Double
): Vec3 {
if (driver !is OrientationControllable) return Vec3.ZERO
//TODO: figure out a cleaner solution to this issue of large jumps when skipping frames or lagging
//Cap at a rate of 5fps so frame skips dont lead to huge jumps
val cappedDeltaTime = min(deltaTime, 0.2)
val invertRoll = if (Cobblemon.config.invertRoll) -1 else 1
val invertPitch = if (Cobblemon.config.invertPitch) -1 else 1
// Accumulate the mouse input
state.currMouseXForce.set((state.currMouseXForce.get() + (0.0015 * mouseX * invertRoll)).coerceIn(-1.0, 1.0))
state.currMouseYForce.set((state.currMouseYForce.get() + (0.0015 * mouseY * invertPitch)).coerceIn(-1.0, 1.0))
//Get handling in degrees per second
var handling = vehicle.runtime.resolveDouble(settings.handlingExpr)
//convert it to delta time
handling *= (cappedDeltaTime)
//apply stamina debuff if applicable
handling *= if (state.stamina.get() > 0.0) 1.0 else 0.5
val poke = driver.vehicle as? PokemonEntity
//TODO: reevaluate if deadzones are needed and if they are still causing issues.
//create deadzones for the constant input values.
//val xInput = remapWithDeadzone(state.currMouseXForce, 0.025, 1.0)
//val yInput = remapWithDeadzone(state.currMouseYForce, 0.025, 1.0)
val pitchRot = handling * state.currMouseYForce.get()
//Roll is 1.5 times as fast as pitch
val rollRot = handling * 1.5 * state.currMouseXForce.get()
//yaw, pitch, roll
return Vec3(0.0, pitchRot, rollRot)
}
override fun canJump(
settings: JetAirSettings,
state: JetAirState,
vehicle: PokemonEntity,
driver: Player
): Boolean {
return false
}
override fun setRideBar(
settings: JetAirSettings,
state: JetAirState,
vehicle: PokemonEntity,
driver: Player
): Float {
return (state.stamina.get() / 1.0f)
}
override fun jumpForce(
settings: JetAirSettings,
state: JetAirState,
vehicle: PokemonEntity,
driver: Player,
jumpStrength: Int
): Vec3 {
return Vec3.ZERO
}
override fun gravity(
settings: JetAirSettings,
state: JetAirState,
vehicle: PokemonEntity,
regularGravity: Double
): Double {
return 0.0
}
override fun rideFovMultiplier(
settings: JetAirSettings,
state: JetAirState,
vehicle: PokemonEntity,
driver: Player
): Float {
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
val minSpeed = vehicle.runtime.resolveDouble(settings.minSpeed)
//Must I ensure that topspeed is greater than minimum?
val normalizedSpeed = normalizeSpeed(state.rideVelocity.get().length(), minSpeed, topSpeed)
//TODO: Determine if this should be based on max possible speed instead of top speed.
//Only ever want the fov change to be a max of 0.2 and for it to have non linear scaling.
return 1.0f + normalizedSpeed.pow(2).toFloat() * 0.2f
}
override fun useAngVelSmoothing(settings: JetAirSettings, state: JetAirState, vehicle: PokemonEntity): Boolean {
return true
}
override fun useRidingAltPose(
settings: JetAirSettings,
state: JetAirState,
vehicle: PokemonEntity,
driver: Player
): Boolean {
return false
}
override fun inertia(settings: JetAirSettings, state: JetAirState, vehicle: PokemonEntity): Double {
return 0.5
}
override fun shouldRoll(settings: JetAirSettings, state: JetAirState, vehicle: PokemonEntity): Boolean {
return true
}
override fun turnOffOnGround(settings: JetAirSettings, state: JetAirState, vehicle: PokemonEntity): Boolean {
return false
}
override fun dismountOnShift(settings: JetAirSettings, state: JetAirState, vehicle: PokemonEntity): Boolean {
return false
}
override fun shouldRotatePokemonHead(
settings: JetAirSettings,
state: JetAirState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun shouldRotatePlayerHead(settings: JetAirSettings, state: JetAirState, vehicle: PokemonEntity): Boolean {
return false
}
override fun createDefaultState(settings: JetAirSettings) = JetAirState()
}
class JetAirSettings : RidingBehaviourSettings {
override val key = JetAirBehaviour.KEY
var gravity: Expression = "0".asExpression()
private set
var minSpeed: Expression = "1.2".asExpression()
private set
var handlingYawExpr: Expression = "q.get_ride_stats('SKILL', 'AIR', 25.0, 8.0)".asExpression()
private set
// Make configurable by json
var infiniteStamina: Expression = "false".asExpression()
private set
var infiniteAltitude: Expression = "false".asExpression()
private set
var jumpExpr: Expression = "q.get_ride_stats('JUMP', 'AIR', 300.0, 128.0)".asExpression()
private set
var handlingExpr: Expression = "q.get_ride_stats('SKILL', 'AIR', 140.0, 20.0)".asExpression()
private set
var speedExpr: Expression = "q.get_ride_stats('SPEED', 'AIR', 2.5, 1.0)".asExpression()
private set
var accelerationExpr: Expression = "q.get_ride_stats('ACCELERATION', 'AIR', (1.0 / (20.0 * 1.0)), (1.0 / (20.0 * 5.0)))".asExpression()
private set
var staminaExpr: Expression = "q.get_ride_stats('STAMINA', 'AIR', 60.0, 10.0)".asExpression()
private set
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeExpression(gravity)
buffer.writeExpression(minSpeed)
buffer.writeExpression(handlingYawExpr)
buffer.writeExpression(infiniteStamina)
buffer.writeExpression(infiniteAltitude)
buffer.writeExpression(jumpExpr)
buffer.writeExpression(handlingExpr)
buffer.writeExpression(speedExpr)
buffer.writeExpression(accelerationExpr)
buffer.writeExpression(staminaExpr)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
gravity = buffer.readExpression()
minSpeed = buffer.readExpression()
handlingYawExpr = buffer.readExpression()
infiniteStamina = buffer.readExpression()
infiniteAltitude = buffer.readExpression()
jumpExpr = buffer.readExpression()
handlingExpr = buffer.readExpression()
speedExpr = buffer.readExpression()
accelerationExpr = buffer.readExpression()
staminaExpr = buffer.readExpression()
}
}
class JetAirState : RidingBehaviourState() {
var currSpeed = ridingState(0.0, Side.CLIENT)
var currMouseXForce = ridingState(0.0, Side.BOTH)
var currMouseYForce = ridingState(0.0, Side.BOTH)
override fun encode(buffer: FriendlyByteBuf) {
super.encode(buffer)
buffer.writeDouble(currSpeed.get())
buffer.writeFloat(stamina.get())
}
override fun decode(buffer: FriendlyByteBuf) {
super.decode(buffer)
currSpeed.set(buffer.readDouble(), forced = true)
}
override fun reset() {
super.reset()
currSpeed.set(0.0, forced = true)
currMouseXForce.set(0.0, forced = true)
currMouseYForce.set(0.0, forced = true)
}
override fun copy() = JetAirState().also {
it.currSpeed.set(currSpeed.get(), forced = true)
it.stamina.set(stamina.get(), forced = true)
it.rideVelocity.set(rideVelocity.get(), forced = true)
it.currMouseXForce.set(currMouseXForce.get(), forced = true)
it.currMouseYForce.set(currMouseYForce.get(), forced = true)
}
override fun shouldSync(previous: RidingBehaviourState): Boolean {
if (previous !is JetAirState) return false
if (previous.currSpeed != currSpeed) return true
return super.shouldSync(previous)
}
}
@@ -0,0 +1,261 @@
/*
* 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.riding.behaviour.types
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.*
import com.cobblemon.mod.common.api.riding.posing.PoseOption
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.blockPositionsAsListRounded
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
import net.minecraft.world.phys.shapes.Shapes
class SwimDashBehaviour : RidingBehaviour<SwimDashSettings, SwimDashState> {
companion object {
val KEY = cobblemonResource("swim/dash")
const val DASH_TICKS: Int = 60
}
override val key = KEY
override val style = RidingStyle.LIQUID
val poseProvider = PoseProvider<SwimDashSettings, SwimDashState>(PoseType.FLOAT)
.with(PoseOption(PoseType.SWIM) { _, _, entity -> entity.isSwimming && entity.entityData.get(PokemonEntity.MOVING) })
override fun isActive(settings: SwimDashSettings, state: SwimDashState, vehicle: PokemonEntity): Boolean {
//This could be kinda weird... what if the top of the mon is in a fluid but the bottom isnt?
return Shapes.create(vehicle.boundingBox).blockPositionsAsListRounded().any {
if (vehicle.isInWater || vehicle.isUnderWater) {
return@any true
}
val blockState = vehicle.level().getBlockState(it)
return@any !blockState.fluidState.isEmpty
}
}
override fun pose(settings: SwimDashSettings, state: SwimDashState, vehicle: PokemonEntity): PoseType {
return poseProvider.select(settings, state, vehicle)
}
override fun speed(
settings: SwimDashSettings,
state: SwimDashState,
vehicle: PokemonEntity,
driver: Player
): Float {
if(state.dashing.get()) {
state.ticks.set(state.ticks.get() + 1)
if(state.ticks.get() >= DASH_TICKS) {
state.dashing.set(false)
}
return 0.0F
}
state.dashing.set(true)
return settings.dashSpeed
}
override fun rotation(
settings: SwimDashSettings,
state: SwimDashState,
vehicle: PokemonEntity,
driver: LivingEntity
): Vec2 {
return Vec2(driver.xRot * 0.5f, driver.yRot)
}
override fun velocity(
settings: SwimDashSettings,
state: SwimDashState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
val f = driver.xxa * 0.05f
var g = driver.zza * 0.6f
if (g <= 0.0f) {
g *= 0.25f
}
return Vec3(f.toDouble(), 0.0, g.toDouble())
}
override fun angRollVel(
settings: SwimDashSettings,
state: SwimDashState,
vehicle: PokemonEntity,
driver: Player,
deltaTime: Double
): Vec3 {
return Vec3.ZERO
}
override fun rotationOnMouseXY(
settings: SwimDashSettings,
state: SwimDashState,
vehicle: PokemonEntity,
driver: Player,
mouseY: Double,
mouseX: Double,
mouseYSmoother: SmoothDouble,
mouseXSmoother: SmoothDouble,
sensitivity: Double,
deltaTime: Double
): Vec3 {
if (driver !is OrientationControllable) return Vec3.ZERO
//Might need to add the smoothing here for default.
val invertRoll = if (Cobblemon.config.invertRoll) -1 else 1
val invertPitch = if (Cobblemon.config.invertPitch) -1 else 1
return Vec3(0.0, mouseX * invertPitch, mouseY * invertRoll)
}
override fun canJump(
settings: SwimDashSettings,
state: SwimDashState,
vehicle: PokemonEntity,
driver: Player
): Boolean {
TODO("Not yet implemented")
}
override fun setRideBar(
settings: SwimDashSettings,
state: SwimDashState,
vehicle: PokemonEntity,
driver: Player
): Float {
return 0.0f
}
override fun jumpForce(
settings: SwimDashSettings,
state: SwimDashState,
vehicle: PokemonEntity,
driver: Player,
jumpStrength: Int
): Vec3 {
TODO("Not yet implemented")
}
override fun gravity(
settings: SwimDashSettings,
state: SwimDashState,
vehicle: PokemonEntity,
regularGravity: Double
): Double {
return regularGravity
}
override fun rideFovMultiplier(
settings: SwimDashSettings,
state: SwimDashState,
vehicle: PokemonEntity,
driver: Player
): Float {
return 1.0f
}
override fun useAngVelSmoothing(settings: SwimDashSettings, state: SwimDashState, vehicle: PokemonEntity): Boolean {
return false
}
override fun useRidingAltPose(
settings: SwimDashSettings,
state: SwimDashState,
vehicle: PokemonEntity,
driver: Player
): Boolean {
return false
}
override fun inertia(settings: SwimDashSettings, state: SwimDashState, vehicle: PokemonEntity): Double {
return 0.5
}
override fun shouldRoll(settings: SwimDashSettings, state: SwimDashState, vehicle: PokemonEntity): Boolean {
return false
}
override fun turnOffOnGround(settings: SwimDashSettings, state: SwimDashState, vehicle: PokemonEntity): Boolean {
return false
}
override fun dismountOnShift(settings: SwimDashSettings, state: SwimDashState, vehicle: PokemonEntity): Boolean {
return false
}
override fun shouldRotatePokemonHead(
settings: SwimDashSettings,
state: SwimDashState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun shouldRotatePlayerHead(
settings: SwimDashSettings,
state: SwimDashState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun createDefaultState(settings: SwimDashSettings) = SwimDashState()
}
class SwimDashSettings : RidingBehaviourSettings {
override val key = SwimDashBehaviour.KEY
var dashSpeed = 1F
private set
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeFloat(dashSpeed)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
dashSpeed = buffer.readFloat()
}
}
class SwimDashState : RidingBehaviourState() {
var dashing = ridingState(false, Side.BOTH)
var ticks = ridingState(0, Side.BOTH)
override fun reset() {
super.reset()
dashing.set(false, forced = true)
ticks.set(0, forced = true)
}
override fun toString(): String {
return "SwimDashState(dashing=${dashing.get()}, ticks=${ticks.get()})"
}
override fun copy() = SwimDashState().also {
it.rideVelocity.set(rideVelocity.get(), forced = true)
it.stamina.set(stamina.get(), forced = true)
it.dashing.set(dashing.get(), forced = true)
it.ticks.set(ticks. get(), forced = true)
}
}
@@ -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.api.riding.behaviour.types
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.*
import com.cobblemon.mod.common.api.riding.posing.PoseOption
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.*
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
import net.minecraft.world.phys.shapes.Shapes
import kotlin.math.absoluteValue
import kotlin.math.max
import kotlin.math.min
class VehicleLandBehaviour : RidingBehaviour<VehicleLandSettings, VehicleLandState> {
companion object {
val KEY = cobblemonResource("land/vehicle")
val MAX_TOP_SPEED = 1.0 // 20 bl/s
val MIN_TOP_SPEED = 0.35 // 7 bl/s
//val MIN_SPEED = 0.25 // 5 bl/s
//Accel will lie between 1.0 second and 5.0 seconds
val MAX_ACCEL = (MAX_TOP_SPEED) / (20*3) //3.0 second to max speed
val MIN_ACCEL = (MAX_TOP_SPEED) / (20*8) // 8 seconds to max speed
//Can rotate 90 degrees
val MAX_HANDLING = 90.0
val MIN_HANDLING = 30.0
val MAX_YAW_HANDLING = 16.0
val MIN_YAW_HANDLING = 8.0
}
override val key = KEY
override val style = RidingStyle.LAND
val poseProvider = PoseProvider<VehicleLandSettings, VehicleLandState>(PoseType.STAND)
.with(PoseOption(PoseType.WALK) { _, _, entity -> entity.entityData.get(PokemonEntity.MOVING) })
override fun isActive(settings: VehicleLandSettings, state: VehicleLandState, vehicle: PokemonEntity): Boolean {
return Shapes.create(vehicle.boundingBox).blockPositionsAsListRounded().any {
//Need to check other fluids
if (vehicle.isInWater || vehicle.isUnderWater) {
return@any false
}
//This might not actually work, depending on what the yPos actually is. yPos of the middle of the entity? the feet?
if (it.y.toDouble() == (vehicle.position().y)) {
val blockState = vehicle.level().getBlockState(it.below())
return@any !(!blockState.isAir && blockState.fluidState.isEmpty)
}
true
}
}
override fun pose(settings: VehicleLandSettings, state: VehicleLandState, vehicle: PokemonEntity): PoseType {
return poseProvider.select(settings, state, vehicle)
}
override fun speed(
settings: VehicleLandSettings,
state: VehicleLandState,
vehicle: PokemonEntity,
driver: Player
): Float {
val topSpeed = vehicle.getRideStat(RidingStat.SPEED, RidingStyle.AIR, MIN_TOP_SPEED, MAX_TOP_SPEED)
val accel = vehicle.getRideStat(RidingStat.ACCELERATION, RidingStyle.AIR, MIN_ACCEL, MAX_ACCEL)
//speed up and slow down based on input
if (driver.zza > 0.0 && state.currSpeed.get() < topSpeed) {
state.currSpeed.set(min(state.currSpeed.get() + accel , topSpeed))
} else if (driver.zza < 0.0 && state.currSpeed.get() > 0.0) {
//Decelerate is now always a constant half of max acceleration.
state.currSpeed.set(max(state.currSpeed.get() - (MAX_ACCEL / 2), 0.0))
}
return state.currSpeed.get().toFloat()
}
override fun rotation(
settings: VehicleLandSettings,
state: VehicleLandState,
vehicle: PokemonEntity,
driver: LivingEntity
): Vec2 {
val rotationDegrees = driver.xxa * vehicle.runtime.resolveFloat(settings.rotationSpeed)
val rotation = Vec2(driver.xRot, vehicle.yRot - rotationDegrees)
state.deltaRotation.set(Vec2(rotation.x - driver.rotationVector.x, rotation.y - vehicle.rotationVector.y))
return rotation
}
override fun velocity(
settings: VehicleLandSettings,
state: VehicleLandState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
val runtime = vehicle.runtime
val driveFactor = runtime.resolveFloat(settings.driveFactor)
var g = driver.zza * driveFactor
if (g <= 0.0f) {
g *= runtime.resolveFloat(settings.reverseDriveFactor)
}
if (driver.xxa != 0F && g.absoluteValue < runtime.resolveFloat(settings.minimumSpeedToTurn)) {
driver.xxa = 0F
}
val velocity = Vec3(0.0 /* Maybe do drifting here later */, 0.0, g.toDouble() * state.currSpeed.get())
return velocity
}
override fun angRollVel(
settings: VehicleLandSettings,
state: VehicleLandState,
vehicle: PokemonEntity,
driver: Player,
deltaTime: Double
): Vec3 {
return Vec3.ZERO
}
override fun rotationOnMouseXY(
settings: VehicleLandSettings,
state: VehicleLandState,
vehicle: PokemonEntity,
driver: Player,
mouseY: Double,
mouseX: Double,
mouseYSmoother: SmoothDouble,
mouseXSmoother: SmoothDouble,
sensitivity: Double,
deltaTime: Double
): Vec3 {
if (driver !is OrientationControllable) return Vec3.ZERO
//Might need to add the smoothing here for default.
val invertRoll = if (Cobblemon.config.invertRoll) -1 else 1
val invertPitch = if (Cobblemon.config.invertPitch) -1 else 1
return Vec3(0.0, mouseY * invertPitch, mouseX * invertRoll)
}
override fun canJump(
settings: VehicleLandSettings,
state: VehicleLandState,
vehicle: PokemonEntity,
driver: Player
): Boolean {
return vehicle.runtime.resolveBoolean(settings.canJump)
}
override fun setRideBar(
settings: VehicleLandSettings,
state: VehicleLandState,
vehicle: PokemonEntity,
driver: Player
): Float {
return 0.0f
}
override fun jumpForce(
settings: VehicleLandSettings,
state: VehicleLandState,
vehicle: PokemonEntity,
driver: Player,
jumpStrength: Int
): Vec3 {
val runtime = vehicle.runtime
runtime.environment.query.addFunction("jump_strength") { DoubleValue(jumpStrength.toDouble()) }
val jumpVector = settings.jumpVector.map { runtime.resolveFloat(it) }
return Vec3(jumpVector[0].toDouble(), jumpVector[1].toDouble(), jumpVector[2].toDouble())
}
override fun gravity(
settings: VehicleLandSettings,
state: VehicleLandState,
vehicle: PokemonEntity,
regularGravity: Double
): Double {
return regularGravity
}
override fun rideFovMultiplier(
settings: VehicleLandSettings,
state: VehicleLandState,
vehicle: PokemonEntity,
driver: Player
): Float {
return 1.0f
}
override fun useAngVelSmoothing(
settings: VehicleLandSettings,
state: VehicleLandState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun useRidingAltPose(
settings: VehicleLandSettings,
state: VehicleLandState,
vehicle: PokemonEntity,
driver: Player
): Boolean {
return false
}
override fun inertia(settings: VehicleLandSettings, state: VehicleLandState, vehicle: PokemonEntity): Double {
return 0.5
}
override fun shouldRoll(settings: VehicleLandSettings, state: VehicleLandState, vehicle: PokemonEntity): Boolean {
return false
}
override fun turnOffOnGround(
settings: VehicleLandSettings,
state: VehicleLandState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun dismountOnShift(
settings: VehicleLandSettings,
state: VehicleLandState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun shouldRotatePokemonHead(
settings: VehicleLandSettings,
state: VehicleLandState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun shouldRotatePlayerHead(
settings: VehicleLandSettings,
state: VehicleLandState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun createDefaultState(settings: VehicleLandSettings) = VehicleLandState()
}
class VehicleLandSettings : RidingBehaviourSettings {
override val key = VehicleLandBehaviour.KEY
var canJump = "true".asExpression()
private set
var jumpVector = listOf("0".asExpression(), "0.3".asExpression(), "0".asExpression())
private set
var speed = "0.3".asExpression()
private set
var driveFactor = "1.0".asExpression()
private set
var reverseDriveFactor = "0.25".asExpression()
private set
var minimumSpeedToTurn = "0.1".asExpression()
private set
var rotationSpeed = "45/20".asExpression()
private set
var lookYawLimit = "101".asExpression()
private set
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeExpression(canJump)
buffer.writeExpression(jumpVector[0])
buffer.writeExpression(jumpVector[1])
buffer.writeExpression(jumpVector[2])
buffer.writeExpression(speed)
buffer.writeExpression(driveFactor)
buffer.writeExpression(reverseDriveFactor)
buffer.writeExpression(minimumSpeedToTurn)
buffer.writeExpression(rotationSpeed)
buffer.writeExpression(lookYawLimit)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
canJump = buffer.readExpression()
jumpVector = listOf(
buffer.readExpression(),
buffer.readExpression(),
buffer.readExpression()
)
speed = buffer.readExpression()
driveFactor = buffer.readExpression()
reverseDriveFactor = buffer.readExpression()
minimumSpeedToTurn = buffer.readExpression()
rotationSpeed = buffer.readExpression()
lookYawLimit = buffer.readExpression()
}
}
class VehicleLandState : RidingBehaviourState() {
var currSpeed = ridingState(0.0, Side.BOTH)
var deltaRotation = ridingState(Vec2.ZERO, Side.BOTH)
override fun reset() {
super.reset()
currSpeed.set(0.0, forced = true)
deltaRotation.set(Vec2.ZERO, forced = true)
}
override fun toString(): String {
return "VehicleLandState(currSpeed=${currSpeed.get()}, deltaRotation=${deltaRotation.get()})"
}
override fun copy() = VehicleLandState().also {
it.rideVelocity.set(rideVelocity.get(), forced = true)
it.stamina.set(stamina.get(), forced = true)
it.currSpeed.set(currSpeed.get(), forced = true)
it.deltaRotation.set(deltaRotation.get(), forced = true)
}
}
@@ -0,0 +1,418 @@
/*
* 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.riding.behaviour.types.composite
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.*
import com.cobblemon.mod.common.api.riding.behaviour.types.composite.strategies.CompositeRidingStrategies
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.adapters.RidingBehaviourSettingsAdapter
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
class CompositeBehaviour : RidingBehaviour<CompositeSettings, CompositeState> {
companion object {
val KEY = cobblemonResource("composite")
}
override val key = KEY
override val style = RidingStyle.COMPOSITE
override fun createDefaultState(settings: CompositeSettings): CompositeState {
val defaultBehaviour = RidingBehaviours.get(settings.defaultBehaviour.key)
val defaultState = defaultBehaviour.createDefaultState(settings.defaultBehaviour)
val alternateBehaviour = RidingBehaviours.get(settings.alternateBehaviour.key)
val alternativeState = alternateBehaviour.createDefaultState(settings.alternateBehaviour)
return CompositeState(
defaultBehaviour = settings.defaultBehaviour.key,
defaultBehaviourState = defaultState,
alternateBehaviourState = alternativeState
)
}
fun <T> chooseBehaviour(
settings: CompositeSettings,
state: CompositeState,
action: (behaviour: RidingBehaviour<RidingBehaviourSettings, RidingBehaviourState>, settings: RidingBehaviourSettings, state: RidingBehaviourState) -> T
): T {
val defaultBehaviour = RidingBehaviours.get(settings.defaultBehaviour.key)
val alternativeBehaviour = RidingBehaviours.get(settings.alternateBehaviour.key)
return when (state.activeController.get()) {
settings.defaultBehaviour.key -> action(
defaultBehaviour,
settings.defaultBehaviour,
state.defaultBehaviourState
)
settings.alternateBehaviour.key -> action(
alternativeBehaviour,
settings.alternateBehaviour,
state.alternateBehaviourState
)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun tick(
settings: CompositeSettings,
state: CompositeState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
) {
val strategy = CompositeRidingStrategies.get(settings.transitionStrategy)
strategy.tick(settings, state, state.defaultBehaviourState, state.alternateBehaviourState, vehicle, driver, input)
}
override fun clampPassengerRotation(
settings: CompositeSettings,
state: CompositeState,
entity: PokemonEntity,
driver: LivingEntity
) {
return chooseBehaviour(settings, state) { behaviour, behaviourSettings, behaviourState ->
behaviour.clampPassengerRotation(behaviourSettings, behaviourState, entity, driver)
}
}
override fun updatePassengerRotation(
settings: CompositeSettings,
state: CompositeState,
entity: PokemonEntity,
driver: LivingEntity
) {
return chooseBehaviour(settings, state) { behaviour, behaviourSettings, behaviourState ->
behaviour.updatePassengerRotation(behaviourSettings, behaviourState, entity, driver)
}
}
override fun shouldRotatePlayerHead(
settings: CompositeSettings,
state: CompositeState,
vehicle: PokemonEntity
): Boolean {
return chooseBehaviour(settings, state) { behaviour, behaviourSettings, behaviourState ->
behaviour.shouldRotatePlayerHead(behaviourSettings, behaviourState, vehicle)
}
}
override fun shouldRotatePokemonHead(
settings: CompositeSettings,
state: CompositeState,
vehicle: PokemonEntity
): Boolean {
return chooseBehaviour(settings, state) { behaviour, behaviourSettings, behaviourState ->
behaviour.shouldRotatePokemonHead(behaviourSettings, behaviourState, vehicle)
}
}
override fun dismountOnShift(
settings: CompositeSettings,
state: CompositeState,
vehicle: PokemonEntity
): Boolean {
return chooseBehaviour(settings, state) { behaviour, behaviourSettings, behaviourState ->
behaviour.dismountOnShift(behaviourSettings, behaviourState, vehicle)
}
}
override fun turnOffOnGround(
settings: CompositeSettings,
state: CompositeState,
vehicle: PokemonEntity
): Boolean {
return chooseBehaviour(settings, state) { behaviour, behaviourSettings, behaviourState ->
behaviour.turnOffOnGround(behaviourSettings, behaviourState, vehicle)
}
}
override fun shouldRoll(
settings: CompositeSettings,
state: CompositeState,
vehicle: PokemonEntity
): Boolean {
return chooseBehaviour(settings, state) { behaviour, behaviourSettings, behaviourState ->
behaviour.shouldRoll(behaviourSettings, behaviourState, vehicle)
}
}
override fun inertia(
settings: CompositeSettings,
state: CompositeState,
vehicle: PokemonEntity
): Double {
return chooseBehaviour(settings, state) { behaviour, behaviourSettings, behaviourState ->
behaviour.inertia(behaviourSettings, behaviourState, vehicle)
}
}
override fun useRidingAltPose(
settings: CompositeSettings,
state: CompositeState,
vehicle: PokemonEntity,
driver: Player
): Boolean {
return chooseBehaviour(settings, state) { behaviour, behaviourSettings, behaviourState ->
behaviour.useRidingAltPose(behaviourSettings, behaviourState, vehicle, driver)
}
}
override fun useAngVelSmoothing(
settings: CompositeSettings,
state: CompositeState,
vehicle: PokemonEntity
): Boolean {
return chooseBehaviour(settings, state) { behaviour, behaviourSettings, behaviourState ->
behaviour.useAngVelSmoothing(behaviourSettings, behaviourState, vehicle)
}
}
override fun rideFovMultiplier(
settings: CompositeSettings,
state: CompositeState,
vehicle: PokemonEntity,
driver: Player
): Float {
return chooseBehaviour(settings, state) { behaviour, behaviourSettings, behaviourState ->
behaviour.rideFovMultiplier(behaviourSettings, behaviourState, vehicle, driver)
}
}
override fun gravity(
settings: CompositeSettings,
state: CompositeState,
vehicle: PokemonEntity,
regularGravity: Double
): Double {
return chooseBehaviour(settings, state) { behaviour, behaviourSettings, behaviourState ->
behaviour.gravity(behaviourSettings, behaviourState, vehicle, regularGravity)
}
}
override fun jumpForce(
settings: CompositeSettings,
state: CompositeState,
vehicle: PokemonEntity,
driver: Player,
jumpStrength: Int
): Vec3 {
return chooseBehaviour(settings, state) { behaviour, behaviourSettings, behaviourState ->
behaviour.jumpForce(behaviourSettings, behaviourState, vehicle, driver, jumpStrength)
}
}
override fun setRideBar(
settings: CompositeSettings,
state: CompositeState,
vehicle: PokemonEntity,
driver: Player
): Float {
return chooseBehaviour(settings, state) { behaviour, behaviourSettings, behaviourState ->
behaviour.setRideBar(behaviourSettings, behaviourState, vehicle, driver)
}
}
override fun canJump(
settings: CompositeSettings,
state: CompositeState,
vehicle: PokemonEntity,
driver: Player
): Boolean {
return chooseBehaviour(settings, state) { behaviour, behaviourSettings, behaviourState ->
behaviour.canJump(behaviourSettings, behaviourState, vehicle, driver)
}
}
override fun rotationOnMouseXY(
settings: CompositeSettings,
state: CompositeState,
vehicle: PokemonEntity,
driver: Player,
mouseY: Double,
mouseX: Double,
mouseYSmoother: SmoothDouble,
mouseXSmoother: SmoothDouble,
sensitivity: Double,
deltaTime: Double
): Vec3 {
return chooseBehaviour(settings, state) { behaviour, behaviourSettings, behaviourState ->
behaviour.rotationOnMouseXY(
behaviourSettings,
behaviourState,
vehicle,
driver,
mouseY,
mouseX,
mouseYSmoother,
mouseXSmoother,
sensitivity,
deltaTime
)
}
}
override fun angRollVel(
settings: CompositeSettings,
state: CompositeState,
vehicle: PokemonEntity,
driver: Player,
deltaTime: Double
): Vec3 {
return chooseBehaviour(settings, state) { behaviour, behaviourSettings, behaviourState ->
behaviour.angRollVel(behaviourSettings, behaviourState, vehicle, driver, deltaTime)
}
}
override fun velocity(
settings: CompositeSettings,
state: CompositeState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
return chooseBehaviour(settings, state) { behaviour, behaviourSettings, behaviourState ->
behaviour.velocity(behaviourSettings, behaviourState, vehicle, driver, input)
}
}
override fun rotation(
settings: CompositeSettings,
state: CompositeState,
vehicle: PokemonEntity,
driver: LivingEntity
): Vec2 {
return chooseBehaviour(settings, state) { behaviour, behaviourSettings, behaviourState ->
behaviour.rotation(behaviourSettings, behaviourState, vehicle, driver)
}
}
override fun speed(
settings: CompositeSettings,
state: CompositeState,
vehicle: PokemonEntity,
driver: Player
): Float {
return chooseBehaviour(settings, state) { behaviour, behaviourSettings, behaviourState ->
behaviour.speed(behaviourSettings, behaviourState, vehicle, driver)
}
}
override fun pose(
settings: CompositeSettings,
state: CompositeState,
vehicle: PokemonEntity
): PoseType {
return chooseBehaviour(settings, state) { behaviour, behaviourSettings, behaviourState ->
behaviour.pose(behaviourSettings, behaviourState, vehicle)
}
}
override fun isActive(
settings: CompositeSettings,
state: CompositeState,
vehicle: PokemonEntity
): Boolean {
return chooseBehaviour(settings, state) { behaviour, behaviourSettings, behaviourState ->
behaviour.isActive(behaviourSettings, behaviourState, vehicle)
}
}
}
open class CompositeSettings : RidingBehaviourSettings {
lateinit var transitionStrategy: ResourceLocation
private set
lateinit var defaultBehaviour: RidingBehaviourSettings
private set
lateinit var alternateBehaviour: RidingBehaviourSettings
private set
override val key: ResourceLocation = CompositeBehaviour.KEY
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeResourceLocation(transitionStrategy)
defaultBehaviour.encode(buffer)
alternateBehaviour.encode(buffer)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
transitionStrategy = buffer.readResourceLocation()
val defaultBehaviourKey = buffer.readResourceLocation()
defaultBehaviour = RidingBehaviourSettingsAdapter.types[defaultBehaviourKey]?.getConstructor()?.newInstance()
?: error("Unknown controller key: $key")
defaultBehaviour.decode(buffer)
val alternativeBehaviourKey = buffer.readResourceLocation()
alternateBehaviour =
RidingBehaviourSettingsAdapter.types[alternativeBehaviourKey]?.getConstructor()?.newInstance()
?: error("Unknown controller key: $key")
alternateBehaviour.decode(buffer)
}
}
class CompositeState(
private val defaultBehaviour: ResourceLocation,
val defaultBehaviourState: RidingBehaviourState,
val alternateBehaviourState: RidingBehaviourState
) : RidingBehaviourState() {
var activeController = ridingState(defaultBehaviour, Side.CLIENT)
var lastTransition = ridingState(-100L, Side.BOTH)
override val rideVelocity: SidedRidingState<Vec3>
get() = when (activeController.get()) {
defaultBehaviour -> defaultBehaviourState.rideVelocity
else -> alternateBehaviourState.rideVelocity
}
override val stamina: SidedRidingState<Float>
get() = when (activeController.get()) {
defaultBehaviour -> defaultBehaviourState.stamina
else -> alternateBehaviourState.stamina
}
override fun reset() {
super.reset()
activeController.set(defaultBehaviour, forced = true)
lastTransition.set(-100L, forced = true)
}
override fun copy(): CompositeState {
val state =
CompositeState(defaultBehaviour, defaultBehaviourState.copy(), alternateBehaviourState.copy())
state.activeController.set(activeController.get(), forced = true)
state.lastTransition.set(lastTransition.get(), forced = true)
state.rideVelocity.set(rideVelocity.get(), forced = true)
state.stamina.set(stamina.get(), forced = true)
return state
}
override fun shouldSync(previous: RidingBehaviourState): Boolean {
if (previous !is CompositeState) return false
if (previous.activeController.get() != activeController.get()) return true
return super.shouldSync(previous)
}
override fun encode(buffer: FriendlyByteBuf) {
super.encode(buffer)
buffer.writeResourceLocation(activeController.get())
}
override fun decode(buffer: FriendlyByteBuf) {
super.decode(buffer)
activeController.set(buffer.readResourceLocation(), forced = true)
}
}
@@ -0,0 +1,32 @@
/*
* 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.riding.behaviour.types.composite.strategies
import com.cobblemon.mod.common.api.riding.behaviour.types.composite.CompositeSettings
import net.minecraft.resources.ResourceLocation
object CompositeRidingStrategies {
val strategies = mutableMapOf<ResourceLocation, CompositeRidingStrategy<out CompositeSettings>>()
init {
register(RunStrategy.key, RunStrategy)
register(JumpStrategy.key, JumpStrategy)
register(FallStrategy.key, FallStrategy)
}
fun register(key: ResourceLocation, strategy: CompositeRidingStrategy<out CompositeSettings>) {
if (strategies.contains(key)) error("Strategy already registered to key $key")
strategies[key] = strategy
}
fun get(key: ResourceLocation): CompositeRidingStrategy<CompositeSettings> {
if (!strategies.contains(key)) error("Strategy not registered to key $key")
return strategies[key]!! as CompositeRidingStrategy<CompositeSettings>
}
}
@@ -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.api.riding.behaviour.types.composite.strategies
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourSettings
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourState
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviours
import com.cobblemon.mod.common.api.riding.behaviour.types.composite.CompositeSettings
import com.cobblemon.mod.common.api.riding.behaviour.types.composite.CompositeState
import com.cobblemon.mod.common.entity.pokemon.PokemonBehaviourFlag
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec3
interface CompositeRidingStrategy<T : CompositeSettings> {
val key: ResourceLocation
fun tick(
settings: T,
state: CompositeState,
defaultState: RidingBehaviourState,
alternateState: RidingBehaviourState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
)
fun transition(
vehicle: PokemonEntity,
settings: CompositeSettings,
state: CompositeState,
fromState: RidingBehaviourState,
toState: RidingBehaviourState,
toSettings: RidingBehaviourSettings
) {
toState.stamina.set(fromState.stamina.get())
toState.rideVelocity.set(fromState.rideVelocity.get())
state.activeController.set(toSettings.key)
state.lastTransition.set(vehicle.level().gameTime)
val behaviour = RidingBehaviours.get(toSettings.key)
vehicle.setBehaviourFlag(PokemonBehaviourFlag.FLYING, behaviour.style == RidingStyle.AIR)
}
}
@@ -0,0 +1,99 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.riding.behaviour.types.composite.strategies
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourSettings
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourState
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviours
import com.cobblemon.mod.common.api.riding.behaviour.types.composite.CompositeSettings
import com.cobblemon.mod.common.api.riding.behaviour.types.composite.CompositeState
import com.cobblemon.mod.common.entity.pokemon.PokemonBehaviourFlag
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.*
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec3
object FallStrategy : CompositeRidingStrategy<FallCompositeSettings> {
override val key = cobblemonResource("strategy/fall")
override fun tick(
settings: FallCompositeSettings,
state: CompositeState,
defaultState: RidingBehaviourState,
alternateState: RidingBehaviourState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
) {
if (shouldTransitionToDefault(state, settings.defaultBehaviour, vehicle)) {
transition(vehicle, settings, state, alternateState, defaultState, settings.defaultBehaviour)
} else if (shouldTransitionToAlternate(settings, state, settings.alternateBehaviour, vehicle, driver)) {
transition(vehicle, settings, state, defaultState, alternateState, settings.alternateBehaviour)
}
}
private fun shouldTransitionToDefault(
state: CompositeState,
defaultSettings: RidingBehaviourSettings,
vehicle: PokemonEntity
): Boolean {
if (state.activeController.get() == defaultSettings.key) return false
if (state.lastTransition.get() + 20 >= vehicle.level().gameTime) return false
if (!vehicle.onGround()) return false
val defaultBehaviour = RidingBehaviours.get(defaultSettings.key)
return defaultBehaviour.isActive(defaultSettings, state.defaultBehaviourState, vehicle)
}
private fun shouldTransitionToAlternate(
settings: FallCompositeSettings,
state: CompositeState,
alternateSettings: RidingBehaviourSettings,
vehicle: PokemonEntity,
driver: Player
): Boolean {
if (state.activeController.get() == alternateSettings.key) return false
if (!driver.jumping) return false
if (state.lastTransition.get() + 20 >= vehicle.level().gameTime) return false
val runtime = vehicle.runtime
val minFallingSpeed = runtime.resolveFloat(settings.minimumFallSpeed)
val minForwardSpeed = runtime.resolveFloat(settings.minimumForwardSpeed)
if (vehicle.deltaMovement.y < -minFallingSpeed) return false
if (vehicle.deltaMovement.horizontalDistance() < minForwardSpeed) return false
val alternativeBehaviour = RidingBehaviours.get(alternateSettings.key)
return alternativeBehaviour.isActive(alternateSettings, state.alternateBehaviourState, vehicle)
}
}
class FallCompositeSettings : CompositeSettings() {
override val key = FallStrategy.key
var minimumForwardSpeed: Expression = "0.0".asExpression()
private set
var minimumFallSpeed: Expression = "0.5".asExpression()
private set
override fun encode(buffer: RegistryFriendlyByteBuf) {
super.encode(buffer)
buffer.writeExpression(minimumForwardSpeed)
buffer.writeExpression(minimumFallSpeed)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
super.decode(buffer)
minimumForwardSpeed = buffer.readExpression()
minimumFallSpeed = buffer.readExpression()
}
}
@@ -0,0 +1,68 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.riding.behaviour.types.composite.strategies
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourSettings
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourState
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviours
import com.cobblemon.mod.common.api.riding.behaviour.types.composite.CompositeSettings
import com.cobblemon.mod.common.api.riding.behaviour.types.composite.CompositeState
import com.cobblemon.mod.common.entity.pokemon.PokemonBehaviourFlag
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec3
object JumpStrategy : CompositeRidingStrategy<CompositeSettings> {
override val key = cobblemonResource("strategy/jump")
override fun tick(
settings: CompositeSettings,
state: CompositeState,
defaultState: RidingBehaviourState,
alternateState: RidingBehaviourState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
) {
if (shouldTransitionToDefault(state, settings.defaultBehaviour, vehicle)) {
transition(vehicle, settings, state, alternateState, defaultState, settings.defaultBehaviour)
} else if (shouldTransitionToAlternative(state, settings.alternateBehaviour, vehicle, driver)) {
transition(vehicle, settings, state, defaultState, alternateState, settings.alternateBehaviour)
}
}
private fun shouldTransitionToDefault(
state: CompositeState,
defaultSettings: RidingBehaviourSettings,
vehicle: PokemonEntity
): Boolean {
if (state.activeController.get() == defaultSettings.key) return false
if (!vehicle.onGround()) return false
if (state.lastTransition.get() + 20 >= vehicle.level().gameTime) return false
val defaultBehaviour = RidingBehaviours.get(defaultSettings.key)
return defaultBehaviour.isActive(defaultSettings, state.defaultBehaviourState, vehicle)
}
private fun shouldTransitionToAlternative(
state: CompositeState,
alternativeSettings: RidingBehaviourSettings,
vehicle: PokemonEntity,
driver: Player
): Boolean {
if (state.activeController.get() == alternativeSettings.key) return false
if (!driver.jumping) return false
if (state.lastTransition.get() + 20 >= vehicle.level().gameTime) return false
val alternativeBehaviour = RidingBehaviours.get(alternativeSettings.key)
return alternativeBehaviour.isActive(alternativeSettings, state.alternateBehaviourState, vehicle)
}
}
@@ -0,0 +1,67 @@
/*
* 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.riding.behaviour.types.composite.strategies
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourSettings
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourState
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviours
import com.cobblemon.mod.common.api.riding.behaviour.types.composite.CompositeSettings
import com.cobblemon.mod.common.api.riding.behaviour.types.composite.CompositeState
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec3
object RunStrategy : CompositeRidingStrategy<CompositeSettings> {
override val key = cobblemonResource("strategy/run")
override fun tick(
settings: CompositeSettings,
state: CompositeState,
defaultState: RidingBehaviourState,
alternateState: RidingBehaviourState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
) {
if (shouldTransitionToDefault(state, settings.defaultBehaviour, vehicle, driver)) {
transition(vehicle, settings, state, alternateState, defaultState, settings.defaultBehaviour)
}
else if (shouldTransitionToAlternate(state, settings.alternateBehaviour, vehicle, driver, input)) {
transition(vehicle, settings, state, defaultState, alternateState, settings.alternateBehaviour)
}
}
private fun shouldTransitionToDefault(
state: CompositeState,
defaultSettings: RidingBehaviourSettings,
entity: PokemonEntity,
driver: Player
): Boolean {
if (state.activeController.get() == defaultSettings.key) return false
if (state.lastTransition.get() + 20 >= entity.level().gameTime) return false
val defaultBehaviour = RidingBehaviours.get(defaultSettings.key)
return !driver.isSprinting || defaultBehaviour.isActive(defaultSettings, state.defaultBehaviourState, entity)
}
private fun shouldTransitionToAlternate(
state: CompositeState,
alternativeSettings: RidingBehaviourSettings,
entity: PokemonEntity,
driver: Player,
input: Vec3
): Boolean {
if (state.activeController.get() == alternativeSettings.key) return false
if (state.lastTransition.get() + 20 >= entity.level().gameTime) return false
val alternativeBehaviour = RidingBehaviours.get(alternativeSettings.key)
return driver.isSprinting && input.z > 0.5 && alternativeBehaviour.isActive(alternativeSettings, state.alternateBehaviourState, entity)
}
}
@@ -1,152 +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.api.riding.controller
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.api.net.Decodable
import com.cobblemon.mod.common.api.net.Encodable
import com.cobblemon.mod.common.api.riding.RidingState
import com.cobblemon.mod.common.api.riding.controller.posing.PoseProvider
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.writeIdentifier
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
/**
* A riding controller is the internal control mechanism for determining how a pokemon
* handles during riding. Controllers come into play once the pokemon being ridden has
* established the path it's capable of. For instance, a land based controller could have
* the pokemon experience drifting after movement, or generic movement. It's up to the
* controller to decide how the pokemon behaves.
*
* @since 1.7.0
*/
interface RideController : Encodable, Decodable {
/** A reference key used to denote the individual controller */
val key: ResourceLocation
/**
* Specifies the provider of poses which helps control updating the current pose on the pokemon
* being ridden. Controllers are expected to define the pose types responsible for posing the entity
* through all expected conditions. It should define a default pose, that will be used in all cases
* where a pose does not meet the current conditions for the current mount. Typically, this will be
* an idle pose.
*/
val poseProvider: PoseProvider
/**
* Represents a condition that must be met for this controller to be active. For instance, this can be used
* to evaluate things such as current velocity or block state of the entity.
*/
val condition: (PokemonEntity) -> Boolean
fun tick(entity: PokemonEntity, driver: Player, input: Vec3) {}
/**
* Specifies the pose the ridden mount should be positioned in. This queries the pose provider using
* the entity as a means of determining the conditions the entity is currently under. This allows for
* dynamic pose updates.
*/
fun pose(entity: PokemonEntity): PoseType {
return this.poseProvider.select(entity)
}
/**
* Calculates the current speed of the mount.
*/
fun speed(entity: PokemonEntity, driver: Player): Float
/**
* Sets the rotation of the mount. This is typically based on the controlling driver and is manipulated as
* necessary.
*/
fun rotation(entity: PokemonEntity, driver: LivingEntity): Vec2
/**
* Sets the change current change in rotation for that tick in three
* dimensions
*/
fun angRollVel(entity: PokemonEntity, driver: Player, deltaTime: Double): Vec3 {
return Vec3(0.0, 0.0, 0.0)
}
fun clampPassengerRotation(entity: PokemonEntity, driver: LivingEntity) {}
fun updatePassengerRotation(entity: PokemonEntity, driver: LivingEntity) {}
/**
* Manipulates the movement input as necessary by the controller. This is primarily used to apply limits
* to the types of movements. For instance, we can apply limits to any sort of sideways movement input, so that
* it would otherwise be slower than normal forward movement.
*/
fun velocity(entity: PokemonEntity, driver: Player, input: Vec3): Vec3
fun setRideBar(entity: PokemonEntity, driver: Player): Float = 0.0f
fun rideFovMult(entity: PokemonEntity, driver: Player): Float = 1.0f
fun canJump(entity: PokemonEntity, driver: Player): Boolean
fun jumpForce(entity: PokemonEntity, driver: Player, jumpStrength: Int): Vec3
fun gravity(entity: PokemonEntity, regularGravity: Double): Double? = null
fun inertia(entity: PokemonEntity): Double = 0.5
fun shouldRoll(entity: PokemonEntity): Boolean = false
fun useAngVelSmoothing(entity: PokemonEntity): Boolean = false
fun useRidingAltPose(entity: PokemonEntity, driver: Player): Boolean = false
//If function is not overwritten by controllers then just perform the defauit
//rolling function which is roll on mousex and pitch on mousey
fun rotationOnMouseXY(
entity: PokemonEntity,
driver: Player,
yMouse: Double,
xMouse: Double,
yMouseSmoother: SmoothDouble,
xMouseSmoother: SmoothDouble,
sensitivity: Double,
deltaTime: Double
): Vec3 {
if (driver !is OrientationControllable) return Vec3.ZERO
//Might need to add the smoothing here for default.
val invertRoll = if (Cobblemon.config.invertRoll) -1 else 1
val invertPitch = if (Cobblemon.config.invertPitch) -1 else 1
return Vec3(0.0, yMouse * invertPitch, xMouse * invertRoll)
}
fun turnOffOnGround(entity: PokemonEntity): Boolean = false
fun dismountOnShift(entity: PokemonEntity): Boolean = false
fun getRuntime(entity: PokemonEntity) = entity.riding.runtime
fun shouldRotatePokemonHead(): Boolean = false
fun shouldRotatePlayerHead(): Boolean = false
fun <T : RidingState> getState(entity: PokemonEntity, constructor: (PokemonEntity) -> T): T {
return entity.riding.getState(key, constructor)
}
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeIdentifier(this.key)
}
}
@@ -1,15 +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.api.riding.controller.posing
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import java.util.function.Predicate
data class PoseOption(val pose: PoseType, val condition: Predicate<PokemonEntity>)
@@ -1,31 +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.api.riding.controller.posing
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
class PoseProvider(private val fallback: PoseType) {
private val options = mutableListOf<PoseOption>()
fun select(entity: PokemonEntity) : PoseType {
return this.options.stream()
.filter { it.condition.test(entity) }
.map { it.pose }
.findFirst()
.orElse(this.fallback)
}
fun with(option: PoseOption) : PoseProvider {
this.options.add(option)
return this
}
}
@@ -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.riding.posing
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourSettings
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourState
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
data class PoseOption<Settings : RidingBehaviourSettings, State : RidingBehaviourState>(
val pose: PoseType,
val condition: (Settings, State, PokemonEntity) -> Boolean
)
@@ -0,0 +1,33 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.riding.posing
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourSettings
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourState
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
class PoseProvider<Settings : RidingBehaviourSettings, State : RidingBehaviourState>(private val fallback: PoseType) {
private val options = mutableListOf<PoseOption<Settings, State>>()
fun select(settings: Settings, state: State, entity: PokemonEntity) : PoseType {
return this.options.stream()
.filter { it.condition(settings, state, entity) }
.map { it.pose }
.findFirst()
.orElse(this.fallback)
}
fun with(option: PoseOption<Settings, State>) : PoseProvider<Settings, State> {
this.options.add(option)
return this
}
}
@@ -61,9 +61,9 @@ class LocationRuleCalculator : SpawnRuleComponent {
reusableContext.value = contextCalculator.name
if (!this::reusableWorldValue.isInitialized) {
reusableWorldValue = world.registryAccess().registryOrThrow(Registries.DIMENSION).getHolder(world.dimension()).get().asWorldMoLangValue()
reusableWorldValue = world.registryAccess().registryOrThrow(Registries.DIMENSION).wrapAsHolder(world).asWorldMoLangValue()
} else {
reusableWorldValue.obj = world.registryAccess().registryOrThrow(Registries.DIMENSION).getHolder(world.dimension()).get()
reusableWorldValue.obj = world.registryAccess().registryOrThrow(Registries.DIMENSION).wrapAsHolder(world)
}
if (!this::reusableDimensionTypeValue.isInitialized) {
@@ -8,9 +8,13 @@
package com.cobblemon.mod.common.battles
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
import com.cobblemon.mod.common.api.battles.model.actor.EntityBackedBattleActor
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.level.ClipContext
import net.minecraft.world.phys.HitResult
import net.minecraft.world.phys.Vec3
class ActiveBattlePokemon(val actor: BattleActor, var battlePokemon: BattlePokemon? = null): Targetable {
val battle = actor.battle
@@ -28,4 +32,115 @@ class ActiveBattlePokemon(val actor: BattleActor, var battlePokemon: BattlePokem
override fun hasPokemon() = battlePokemon != null
fun isGone() = battlePokemon?.gone ?: true
fun isAlive() = (battlePokemon?.health ?: 0) > 0
fun getSendOutPosition(): Vec3? {
val pnx = getPNX()
val actorEntityPosList = actor.getSide().actors.mapNotNull { if (it is EntityBackedBattleActor<*>) it.initialPos else null }
val actorEntityPos = if (actorEntityPosList.size == 1)
actorEntityPosList[0]
else if (actorEntityPosList.size > 1)
actorEntityPosList.fold(Vec3(0.0, 0.0, 0.0)) { acc, vec3 -> acc.add(vec3.scale(1.0 / actorEntityPosList.size)) }
else
null
val opposingActorEntityList = actor.getSide().getOppositeSide().actors.mapNotNull { if (it is EntityBackedBattleActor<*>) it.initialPos else null }
val opposingEntityPos = if (opposingActorEntityList.size == 1)
opposingActorEntityList[0]
else if (opposingActorEntityList.size > 1) {
// If multiple actors per side, avg their position
opposingActorEntityList.fold(Vec3(0.0, 0.0, 0.0)) { acc, vec3 -> acc.add(vec3.scale(1.0 / opposingActorEntityList.size)) }
}
else null
var actorOffset = actorEntityPos?.let { opposingEntityPos?.subtract(it) }
var result = actorEntityPos
if (actorOffset != null) {
var widthSum = 4.0 // Leave a constant to allow double/triples alignment to be consistent
var pokemonWidth = battlePokemon?.originalPokemon?.form?.hitbox?.width ?: 0F
val pokemonBaseScale = battlePokemon?.originalPokemon?.form?.baseScale ?: 0F
pokemonWidth *= pokemonBaseScale
if (battle.format.battleType.pokemonPerSide == 1) {
battlePokemon?.let { battlePokemon ->
// sum of the hitbox widths of both pokemon
val opposingActivePokemon = (getOppositeOpponent() as ActiveBattlePokemon)
val opposingPokemonWidth = opposingActivePokemon.battlePokemon?.let {
it.originalPokemon.form.hitbox.width * it.originalPokemon.form.baseScale
} ?: pokemonWidth
widthSum = (pokemonWidth + opposingPokemonWidth) / 2.0 // Only care about the front half of each hitbox
}
}
val minDistance = 4.0 + widthSum
val actorDistance = actorOffset.length()
if (actorDistance < minDistance) {
val temp = actorOffset.scale(minDistance / actorDistance) ?: actorOffset
result = actorEntityPos?.subtract(temp.subtract(actorOffset))
actorOffset = temp
}
// orthogonalVector is the sideways displacement from the battle actors
var orthogonalVector = Vec3(actorOffset.x, 0.0, actorOffset.z).normalize()
orthogonalVector = orthogonalVector.cross(Vec3(0.0, 1.0, 0.0))
if (battle.format.battleType.pokemonPerSide == 1) { // Singles
result = result?.add(actorOffset.scale(if (battle.isPvW) 0.4 else 0.3))
battlePokemon?.let { battlePokemon ->
val hitbox = battlePokemon.originalPokemon.form.hitbox
val scale = battlePokemon.originalPokemon.form.baseScale
orthogonalVector = orthogonalVector.scale(-0.3 - hitbox.width * scale )
}
} else if (battle.format.battleType.pokemonPerSide == 2) { // Doubles/Multi
if (battle.actors.first() != battle.actors.last()) {
val offsetB = if (pnx[2] == 'a') orthogonalVector.scale(-1.0) else orthogonalVector
result = result?.add(actorOffset.scale(0.33))
orthogonalVector = offsetB.scale(2.5)
}
} else if (battle.format.battleType.pokemonPerSide == 3) { // Triples
if (battle.actors.first() != battle.actors.last()) {
result = when (pnx[2]) {
'a' -> {
orthogonalVector = orthogonalVector.scale(-3.5)
result?.add(actorOffset.scale(0.15))
}
'b' -> {
orthogonalVector = Vec3.ZERO
result?.add(actorOffset.scale(0.3))
}
'c' -> {
orthogonalVector = orthogonalVector.scale(3.5)
result?.add(actorOffset.scale(0.15))
}
else -> result
}
}
}
val fallbackPos = result
result = result?.add(orthogonalVector)
if (actor is EntityBackedBattleActor<*> && result != null) {
val entity = actor.entity ?: return result
// Do a single raycast to estimate if we're trying to throw a pokemon into a wall.
// If the collision is too close to the actor, try to find a more reasonable position.
// The motivation here is to give setPositionSafely something more reasonable to work with.
val collisionResult = entity.level().clip(
ClipContext(
actorEntityPos!!.add(Vec3(0.0,entity.eyeHeight.toDouble(),0.0)), // start position
Vec3(result.x, entity.y + entity.eyeHeight.toDouble(), result.z), // end position
ClipContext.Block.OUTLINE,
ClipContext.Fluid.NONE,
entity
)
)
if (collisionResult.type == HitResult.Type.BLOCK) {
// Collided with terrain
result = if (collisionResult.location.distanceToSqr(actorEntityPos) < pokemonWidth) {
// Fallback to in between the two actors, dropping the orthogonal vector
// Ideally this keeps a pokemon in between the actors in a narrow tunnel
fallbackPos
} else {
Vec3(collisionResult.location.x, result.y, collisionResult.location.z)
}
}
}
}
return result
}
}
@@ -22,6 +22,8 @@ import com.cobblemon.mod.common.battles.interpreter.instructions.*
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.util.battleLang
import com.cobblemon.mod.common.util.runOnServer
import net.minecraft.world.level.ClipContext
import net.minecraft.world.phys.HitResult
import net.minecraft.world.phys.Vec3
import java.util.UUID
import kotlin.collections.Iterator
@@ -34,6 +36,7 @@ import kotlin.collections.mutableMapOf
import kotlin.collections.set
import kotlin.collections.toMutableList
import kotlin.collections.toTypedArray
import kotlin.time.measureTime
@Suppress("KotlinPlaceholderCountMatchesArgumentCount", "UNUSED_PARAMETER")
object ShowdownInterpreter {
@@ -138,85 +141,6 @@ object ShowdownInterpreter {
// Note '-cureteam' is a legacy thing that is only used in generation 2 and 4 mods for heal bell and aromatherapy respectively as such we can just ignore that
}
/**
*
* Figures out the sendout position for a pokemon in a battle
*
*
*/
fun getSendoutPosition(battle: PokemonBattle, activePokemon: ActiveBattlePokemon, battleActor: BattleActor): Vec3? {
val pnx = activePokemon.getPNX()
val actorEntityPosList = battleActor.getSide().actors.mapNotNull { if (it is EntityBackedBattleActor<*>) it.initialPos else null }
val actorEntityPos = if (actorEntityPosList.size == 1)
actorEntityPosList[0]
else if (actorEntityPosList.size > 1)
actorEntityPosList.fold(Vec3(0.0, 0.0, 0.0)) { acc, vec3 -> acc.add(vec3.scale(1.0 / actorEntityPosList.size)) }
else
null
val opposingActorEntityList = battleActor.getSide().getOppositeSide().actors.mapNotNull { if (it is EntityBackedBattleActor<*>) it.initialPos else null }
val opposingEntityPos = if (opposingActorEntityList.size == 1)
opposingActorEntityList[0]
else if (opposingActorEntityList.size > 1) {
// If multiple actors per side, avg their position
opposingActorEntityList.fold(Vec3(0.0, 0.0, 0.0)) { acc, vec3 -> acc.add(vec3.scale(1.0 / opposingActorEntityList.size)) }
}
else null
var actorOffset = actorEntityPos?.let { opposingEntityPos?.subtract(it) }
var result = actorEntityPos
if (actorOffset != null) {
var widthSum = 4.0 // Leave a constant to allow double/triples alignment to be consistent
if (battle.format.battleType.pokemonPerSide == 1) {
activePokemon.battlePokemon?.let { battlePokemon ->
// sum of the hitbox widths of both pokemon
val opposingActivePokemon = (activePokemon.getOppositeOpponent() as ActiveBattlePokemon)
val pokemonWidth = battlePokemon.originalPokemon.form.hitbox.width * battlePokemon.originalPokemon.form.baseScale
val opposingPokemonWidth = opposingActivePokemon.battlePokemon?.let {
it.originalPokemon.form.hitbox.width * it.originalPokemon.form.baseScale
} ?: pokemonWidth
widthSum = (pokemonWidth + opposingPokemonWidth) / 2.0 // Only care about the front half of each hitbox
}
}
val minDistance = 4.0 + widthSum
val actorDistance = actorOffset.length()
if (actorDistance < minDistance) {
val temp = actorOffset.scale(minDistance / actorDistance) ?: actorOffset
result = actorEntityPos?.subtract(temp.subtract(actorOffset))
actorOffset = temp
}
var vector = Vec3(actorOffset.x, 0.0, actorOffset.z).normalize()
vector = vector.cross(Vec3(0.0, 1.0, 0.0))
if (battle.format.battleType.pokemonPerSide == 1) { // Singles
result = result?.add(actorOffset.scale(if (battle.isPvW) 0.4 else 0.3))
activePokemon.battlePokemon?.let { battlePokemon ->
val hitbox = battlePokemon.originalPokemon.form.hitbox
val scale = battlePokemon.originalPokemon.form.baseScale
activePokemon.getAdjacentOpponents()
result = result?.add(vector.scale(-0.3 - hitbox.width * scale ))
}
} else if (battle.format.battleType.pokemonPerSide == 2) { // Doubles/Multi
if (battle.actors.first() !== battle.actors.last()) {
val offsetB = if (pnx[2] == 'a') vector.scale(-1.0) else vector
result = result?.add(actorOffset.scale(0.33))?.add(offsetB.scale(2.5))
}
} else if (battle.format.battleType.pokemonPerSide == 3) { // Triples
if (battle.actors.first() !== battle.actors.last()) {
result = when (pnx[2]) {
'a' -> result?.add(actorOffset.scale(0.15))?.add(vector.scale(-3.5))
'b' -> result?.add(actorOffset.scale(0.3))
'c' -> result?.add(actorOffset.scale(0.15))?.add(vector.scale(3.5))
else -> result
}
}
}
}
return result
}
fun interpretMessage(battleId: UUID, message: String) {
// Check key map and use function if matching
@@ -13,7 +13,6 @@ import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.battles.ShowdownInterpreter
import com.cobblemon.mod.common.battles.dispatch.InstructionSet
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.entity.pokemon.effects.IllusionEffect
import com.cobblemon.mod.common.net.messages.client.battle.BattleSwapPokemonPacket
import com.cobblemon.mod.common.util.battleLang
import com.cobblemon.mod.common.util.setPositionSafely
@@ -42,12 +41,12 @@ class SwapInstruction(val message: BattleMessage, val instructionSet: Instructio
// Swap references of the 2 pokemon
actor.activePokemon.swap((pnxA[2] - 'a'), (pnxB[2] - 'a'))
val posA = ShowdownInterpreter.getSendoutPosition(battle, activePokemonA, battlePokemonA.actor) ?: activePokemonA.position?.second
val posA = activePokemonA.getSendOutPosition() ?: activePokemonA.position?.second
if (posA != null && battlePokemonA.entity != null) {
battlePokemonA.entity?.setPositionSafely(battlePokemonA.entity!!.getAjustedSendoutPosition(posA))
}
val posB = ShowdownInterpreter.getSendoutPosition(battle, activePokemonB, battlePokemonA.actor) ?: activePokemonB.position?.second
val posB = activePokemonB.getSendOutPosition() ?: activePokemonB.position?.second
val battlePokemonB = activePokemonB.battlePokemon
if (posB != null && battlePokemonB?.entity != null) {
activePokemonB.battlePokemon?.entity?.setPositionSafely(battlePokemonB.entity!!.getAjustedSendoutPosition(posB))
@@ -70,7 +70,7 @@ class SwitchInstruction(val instructionSet: InstructionSet, val battleActor: Bat
else if (pokemonEntity == null && entity != null) {
activePokemon.battlePokemon = pokemon
activePokemon.illusion = illusion
val targetPos = ShowdownInterpreter.getSendoutPosition(battle, activePokemon, battleActor)
val targetPos = activePokemon.getSendOutPosition()
if (targetPos != null) {
val battleSendoutCount = activePokemon.getActorShowdownId()[1].digitToInt() - 1 + actor.stillSendingOutCount
actor.stillSendingOutCount++
@@ -180,7 +180,7 @@ class SwitchInstruction(val instructionSet: InstructionSet, val battleActor: Bat
}
} else {
// For Singles, we modify the sendout position based on the pokemon's hitbox size
val pos = (if (battle.format.battleType.pokemonPerSide == 1) ShowdownInterpreter.getSendoutPosition(battle, activePokemon, actor)
val pos = (if (battle.format.battleType.pokemonPerSide == 1) activePokemon.getSendOutPosition()
else activePokemon.position?.second) ?: entity.position()
// Send out at previous Pokémon's location if it is known, otherwise actor location
val world = entity.level() as ServerLevel
@@ -213,8 +213,8 @@ class SwitchInstruction(val instructionSet: InstructionSet, val battleActor: Bat
val publicPokemon = (illusion ?: newPokemon).effectedPokemon
val publicLang = publicPokemon.nickname?.let { nickname ->
battleLang("switch.other.nickname", actor.getName(), nickname, publicPokemon.species.translatedName)
} ?: battleLang("switch.other", actor.getName(), publicPokemon.getDisplayName())
actor.sendMessage(battleLang("switch.self", publicPokemon.getDisplayName()))
} ?: battleLang("switch.other", actor.getName(), publicPokemon.getDisplayName(true))
actor.sendMessage(battleLang("switch.self", publicPokemon.getDisplayName(true)))
battle.actors.filter { it != actor }.forEach {
it.sendMessage(publicLang)
}
@@ -0,0 +1,77 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client
import com.cobblemon.mod.common.client.entity.PokemonClientDelegate
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import net.minecraft.util.Mth
import net.minecraft.world.phys.Vec3
/**
* This class is responsible for setting up the animation state of a Pokemon entity before it begins to render.
*
* Sometimes we depend on the animation state of the Pokemon to be set up before we begin rendering it, so this class
* allows us to do that. It also caches which Pokemon have been pre-animated to avoid doing it multiple times.
*
* @author landonjw
*/
object MountedPokemonAnimationRenderController {
private val forcedDeltas = mutableMapOf<Int, Float>()
fun setup(pokemon: PokemonEntity, partialTickTime: Float) {
if (forcedDeltas.contains(pokemon.id)) return
val delegate = pokemon.delegate as? PokemonClientDelegate ?: return
val model = delegate.currentModel ?: return
if (!model.isReadyForAnimation()) return
val entityPos = Vec3(
Mth.lerp(partialTickTime.toDouble(), pokemon.xOld, pokemon.x),
Mth.lerp(partialTickTime.toDouble(), pokemon.yOld, pokemon.y),
Mth.lerp(partialTickTime.toDouble(), pokemon.zOld, pokemon.z)
)
val f = Mth.rotLerp(partialTickTime, pokemon.yBodyRotO, pokemon.yBodyRot)
val g = Mth.rotLerp(partialTickTime, pokemon.yHeadRotO, pokemon.yHeadRot)
var h = g - f
val j = Mth.lerp(partialTickTime, pokemon.xRotO, pokemon.xRot)
h = Mth.wrapDegrees(h)
var l = 0.0f
var m = 0.0f
if (!pokemon.isPassenger && pokemon.isAlive) {
l = pokemon.walkAnimation.speed(partialTickTime)
m = pokemon.walkAnimation.position(partialTickTime)
if (pokemon.isBaby) {
m *= 3.0f
}
if (l > 1.0f) {
l = 1.0f
}
}
delegate.updatePartialTicks(partialTickTime)
delegate.updateLocatorPosition(entityPos)
delegate.currentModel?.applyAnimations(pokemon, delegate, l, m, partialTickTime, j, h)
forcedDeltas[pokemon.id] = partialTickTime
}
fun getPartialTick(pokemon: PokemonEntity) : Float? {
return forcedDeltas[pokemon.id]
}
/**
* Resets the prerendered list. This should be called in the render loop before we begin setting up any pokemon with `setup`.
*/
fun reset() {
forcedDeltas.clear()
}
}
@@ -12,6 +12,7 @@ import com.bedrockk.molang.runtime.struct.QueryStruct
import com.bedrockk.molang.runtime.value.DoubleValue
import com.bedrockk.molang.runtime.value.StringValue
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonNetwork
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.entity.PokemonSideDelegate
import com.cobblemon.mod.common.api.molang.MoLangFunctions.addFunctions
@@ -31,6 +32,7 @@ import com.cobblemon.mod.common.client.render.models.blockbench.animation.Primar
import com.cobblemon.mod.common.client.render.models.blockbench.repository.VaryingModelRepository
import com.cobblemon.mod.common.client.render.pokemon.PokemonRenderer.Companion.ease
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.net.messages.server.pokemon.update.ServerboundUpdateRidingStatePacket
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.*
import com.mojang.blaze3d.vertex.PoseStack
@@ -41,8 +43,10 @@ import net.minecraft.sounds.SoundEvent
import net.minecraft.sounds.SoundSource
import net.minecraft.world.InteractionHand
import net.minecraft.world.entity.Entity
import net.minecraft.world.entity.Entity.MoveFunction
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec3
import org.joml.Vector3f
class PokemonClientDelegate : PosableState(), PokemonSideDelegate {
companion object {
@@ -99,6 +103,7 @@ class PokemonClientDelegate : PosableState(), PokemonSideDelegate {
currentEntity.pokemon.species = PokemonSpecies.getByIdentifier(identifier)!! // TODO exception handling
// force a model update - handles edge case where the PosableState's tracked PosableModel isn't updated until the LivingEntityRenderer render is run
currentModel = VaryingModelRepository.getPoser(identifier, this)
currentEntity.refreshRiding()
} else if (data == PokemonEntity.ASPECTS) {
currentAspects = currentEntity.entityData.get(PokemonEntity.ASPECTS)
currentEntity.pokemon.shiny = currentAspects.contains("shiny")
@@ -340,9 +345,12 @@ class PokemonClientDelegate : PosableState(), PokemonSideDelegate {
runtime.environment.query = struct
}
override fun updateAge(age: Int) {
this.age = age
}
override fun initialize(entity: PokemonEntity) {
this.currentEntity = entity
this.age = entity.tickCount
this.runtime.environment.query.addFunctions(mapOf(
"in_battle" to java.util.function.Function {
@@ -372,6 +380,18 @@ class PokemonClientDelegate : PosableState(), PokemonSideDelegate {
override fun tick(entity: PokemonEntity) {
incrementAge(entity)
playWildShinySounds()
sendRidingChanges(entity)
}
private fun sendRidingChanges(entity: PokemonEntity) {
val player = Minecraft.getInstance().player ?: return
if (entity.controllingPassenger != player) return
entity.ifRidingAvailable { behaviour, _, state ->
if (entity.previousRidingState != null && !state.shouldSync(entity.previousRidingState!!)) {
return@ifRidingAvailable
}
CobblemonNetwork.sendToServer(ServerboundUpdateRidingStatePacket(entity.id, behaviour.key, state))
}
}
fun playWildShinySounds() {
@@ -436,4 +456,30 @@ class PokemonClientDelegate : PosableState(), PokemonSideDelegate {
}
cryAnimation = animation
}
override fun positionRider(passenger: Entity, positionUpdater: MoveFunction) {
val index =
this.getEntity().passengers.indexOf(passenger).takeIf { it >= 0 && it < this.getEntity().seats.size }
?: return
val seat = this.getEntity().seats[index]
val locator = this.locatorStates[seat.locator]
if (locator == null) return
val offset = locator.matrix.getTranslation(Vector3f())
.sub(
Vector3f(
0f,
passenger.eyeHeight - (passenger.bbHeight / 2),
0f
)
) // This is close but not exact
positionUpdater.accept(
passenger,
this.getEntity().x + offset.x,
this.getEntity().y + offset.y,
this.getEntity().z + offset.z
)
}
}
@@ -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.common
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.mark.Mark
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.GuiGraphics
class MarkIcon(
val x: Number,
val y: Number,
val mark: Mark,
val showTooltip: Boolean = true,
val opacity: Float = 1F
) {
companion object {
const val SIZE = 32
const val SCALE = 0.5F
}
fun render(context: GuiGraphics, mouseX: Int, mouseY: Int) {
blitk(
matrixStack = context.pose(),
texture = mark.texture,
x = x.toFloat() / SCALE,
y = y.toFloat() / SCALE,
height = SIZE,
width = SIZE,
alpha = opacity,
scale = SCALE
)
if (showTooltip && isHovered(mouseX, mouseY)) {
context.renderTooltip(Minecraft.getInstance().font, mark.getName(), mouseX, mouseY)
}
}
fun isHovered(mouseX: Int, mouseY: Int): Boolean =
mouseX.toFloat() in (x.toFloat()..(x.toFloat() + (SIZE * SCALE)))
&& mouseY.toFloat() in (y.toFloat()..(y.toFloat() + (SIZE * SCALE)))
}
@@ -98,13 +98,13 @@ class CobblemonConfigIntVariable(
override fun getConfigValueAsString(): String = (config.getter.call(parent.parent.clonedConfig) as Int?).toString()
init {
editBox.setFilter { value -> value.toIntOrNull() != null || value.isBlank() }
editBox.setFilter { value -> value.toIntOrNull() != null || value.isBlank() || value == "-" }
editBox.setResponder { value ->
if (value.isBlank()) {
return@setResponder
}
val intValue = value.toInt()
val intValue = value.toIntOrNull() ?: return@setResponder
setConfigValue(intValue, false)
if (intConstraintAnnotation != null) {
@@ -129,7 +129,7 @@ class CobblemonConfigFloatVariable(
override fun getConfigValueAsString(): String = (config.getter.call(parent.parent.clonedConfig) as Float?).toString()
init {
editBox.setFilter { value -> value.toFloatOrNull() != null || value.isBlank() }
editBox.setFilter { value -> value.toFloatOrNull() != null || value.isBlank() || value == "." || value == "-" }
editBox.setResponder { value ->
if (value.toFloatOrNull() != null) {
setConfigValue(value.toFloat(), false)
@@ -145,7 +145,7 @@ class CobblemonConfigDoubleVariable(
override fun getConfigValueAsString(): String = (config.getter.call(parent.parent.clonedConfig) as Double?).toString()
init {
editBox.setFilter { value -> value.toDoubleOrNull() != null || value.isBlank() }
editBox.setFilter { value -> value.toDoubleOrNull() != null || value.isBlank() || value == "." || value == "-" }
editBox.setResponder { value ->
if (value.toDoubleOrNull() != null) {
setConfigValue(value.toDouble(), false)
@@ -19,6 +19,7 @@ import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import com.cobblemon.mod.common.client.gui.ExitButton
import com.cobblemon.mod.common.client.gui.TypeIcon
import com.cobblemon.mod.common.client.gui.summary.Summary
import com.cobblemon.mod.common.client.gui.summary.widgets.MarkingsWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.ModelWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.common.reformatNatureTextIfMinted
import com.cobblemon.mod.common.client.render.drawScaledText
@@ -81,9 +82,10 @@ class PCGUI(
private lateinit var boxNameWidget: BoxNameWidget
private lateinit var filterWidget: FilterWidget
private lateinit var wallpaperWidget: WallpapersScrollingWidget
private lateinit var markingsWidget: MarkingsWidget
private var modelWidget: ModelWidget? = null
internal var previewPokemon: Pokemon? = null
var isPreviewInParty: Boolean? = null
private val optionButtons: MutableList<IconButton> = mutableListOf()
@@ -110,6 +112,7 @@ class PCGUI(
for (button in optionButtons) button.highlighted = false
playSound(CobblemonSounds.PC_CLICK)
} else {
saveMarkings(isPreviewInParty ?: false)
configuration.exitFunction(this)
}
}
@@ -239,6 +242,8 @@ class PCGUI(
}
}
this.markingsWidget = MarkingsWidget(x + 29, y + 96.5, null, false)
this.addRenderableWidget(markingsWidget)
this.setPreviewPokemon(null)
super.init()
@@ -575,12 +580,15 @@ class PCGUI(
val filterFocused = this::filterWidget.isInitialized && filterWidget.isFocused
if (isInventoryKeyPressed(minecraft, keyCode, scanCode) && !boxNameFocused && !filterFocused) {
saveMarkings(isPreviewInParty ?: false)
playSound(CobblemonSounds.PC_OFF)
UnlinkPlayerFromPCPacket().sendToServer()
Minecraft.getInstance().setScreen(null)
return true
}
if (keyCode == InputConstants.KEY_ESCAPE) saveMarkings(isPreviewInParty ?: false)
if (!filterFocused && !boxNameFocused) {
when (keyCode) {
InputConstants.KEY_ESCAPE -> {
@@ -628,8 +636,13 @@ class PCGUI(
Minecraft.getInstance().soundManager.play(SimpleSoundInstance.forUI(soundEvent, 1.0F))
}
fun setPreviewPokemon(pokemon: Pokemon?) {
private fun saveMarkings(isParty: Boolean = false) {
if (::markingsWidget.isInitialized) markingsWidget.saveMarkingsToPokemon(isParty)
}
fun setPreviewPokemon(pokemon: Pokemon?, isParty: Boolean = false) {
if (pokemon != null) {
saveMarkings(isParty)
previewPokemon = pokemon
val x = (width - BASE_WIDTH) / 2
@@ -644,9 +657,11 @@ class PCGUI(
rotationY = 325F,
offsetY = -10.0
)
markingsWidget.setActivePokemon(previewPokemon)
} else {
previewPokemon = null
modelWidget = null
markingsWidget.setActivePokemon(null)
}
}
}
@@ -321,7 +321,11 @@ class StorageWidget(
slot.render(context, mouseX, mouseY, delta)
val pokemon = slot.getPokemon()
if (grabbedSlot == null && slot.isHovered(mouseX, mouseY)
&& pokemon != null && pokemon != pcGui.previewPokemon) pcGui.setPreviewPokemon(pokemon)
&& pokemon != null && pokemon != pcGui.previewPokemon
) {
pcGui.setPreviewPokemon(pokemon, pcGui.isPreviewInParty ?: false)
pcGui.isPreviewInParty = false
}
}
} else {
if (pcGui.ticksElapsed >= 10) screenLoaded = true
@@ -334,7 +338,10 @@ class StorageWidget(
val pokemon = slot.getPokemon()
if (grabbedSlot == null && slot.isHovered(mouseX, mouseY)
&& pokemon != null && pokemon != pcGui.previewPokemon
) pcGui.setPreviewPokemon(pokemon)
) {
pcGui.setPreviewPokemon(pokemon, pcGui.isPreviewInParty ?: true)
pcGui.isPreviewInParty = true
} // TODO: Hovering from party to party or pc not saving marks
}
}
@@ -427,7 +434,7 @@ class StorageWidget(
}
this.selectedPosition = clickedPosition
this.pcGui.setPreviewPokemon(clickedPokemon)
this.pcGui.setPreviewPokemon(clickedPokemon, pcGui.isPreviewInParty ?: false)
grabbedSlot = GrabbedStorageSlot(
x = button.x,
y = button.y,
@@ -23,12 +23,10 @@ import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import com.cobblemon.mod.common.client.gui.ExitButton
import com.cobblemon.mod.common.client.gui.TypeIcon
import com.cobblemon.mod.common.client.gui.summary.widgets.EvolutionSelectScreen
import com.cobblemon.mod.common.client.gui.summary.widgets.ModelWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.NicknameEntryWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.PartyWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.*
import com.cobblemon.mod.common.client.gui.summary.widgets.screens.SummaryTab
import com.cobblemon.mod.common.client.gui.summary.widgets.screens.info.InfoWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.screens.marks.MarksWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.screens.moves.MoveSwapScreen
import com.cobblemon.mod.common.client.gui.summary.widgets.screens.moves.MovesWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.screens.stats.StatWidget
@@ -75,6 +73,7 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
private const val INFO = 0
private const val MOVES = 1
private const val STATS = 2
private const val MARKS = 3
// Side Screen Index
const val PARTY = 0
@@ -93,6 +92,7 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
private val tabIconInfo = cobblemonResource("textures/gui/summary/summary_tab_icon_info.png")
private val tabIconMoves = cobblemonResource("textures/gui/summary/summary_tab_icon_moves.png")
private val tabIconStats = cobblemonResource("textures/gui/summary/summary_tab_icon_stats.png")
private val tabIconMarks = cobblemonResource("textures/gui/summary/summary_tab_icon_marks.png")
val iconShinyResource = cobblemonResource("textures/gui/summary/icon_shiny.png")
val iconHeldItemResource = cobblemonResource("textures/gui/summary/icon_item_held.png")
val iconCosmeticItemResource = cobblemonResource("textures/gui/summary/icon_item_cosmetic.png")
@@ -122,8 +122,10 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
lateinit var sideScreen: GuiEventListener
private lateinit var modelWidget: ModelWidget
private lateinit var nicknameEntryWidget: NicknameEntryWidget
private lateinit var markingsWidget: MarkingsWidget
private val summaryTabs = mutableListOf<SummaryTab>()
private var showCosmeticItem = false
private lateinit var heldItemVisibilityButton: SummaryButton
private var mainScreenIndex = INFO
var sideScreenIndex = PARTY
private val party = ArrayList(party)
@@ -155,6 +157,13 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
displayMainScreen(mainScreenIndex)
displaySideScreen(PARTY)
markingsWidget = MarkingsWidget(
x + 29,
y + 102,
selectedPokemon
)
addRenderableWidget(markingsWidget)
// Evolve Button
addRenderableWidget(
SummaryButton(
@@ -173,7 +182,7 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
)
//Item Visibility Button
val HeldItemVisibilityButton = SummaryButton(
heldItemVisibilityButton = SummaryButton(
buttonX = x + 3F,
buttonY = y + 104F,
buttonWidth = 32,
@@ -183,7 +192,7 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
activeResource = itemHiddenResource,
clickAction = {
selectedPokemon.heldItemVisible = !selectedPokemon.heldItemVisible
(it as SummaryButton).buttonActive = !selectedPokemon.heldItemVisible
heldItemVisibilityButton.buttonActive = !selectedPokemon.heldItemVisible
modelWidget.heldItem = if (selectedPokemon.heldItemVisible) selectedPokemon.heldItem else null
// Send item visibility update to server
sendToServer(SetItemHiddenPacket(selectedPokemon.uuid, selectedPokemon.heldItemVisible))
@@ -191,8 +200,8 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
renderRequirement = { !selectedPokemon.heldItemNoCopy().isEmpty && !showCosmeticItem },
clickRequirement = { !selectedPokemon.heldItemNoCopy().isEmpty && !showCosmeticItem }
)
addRenderableWidget(HeldItemVisibilityButton)
heldItemVisibilityButton.buttonActive = !selectedPokemon.heldItemVisible
addRenderableWidget(heldItemVisibilityButton)
// Held/Cosmetic Item Button
addRenderableWidget(
@@ -215,28 +224,25 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
summaryTabs.clear()
summaryTabs.add(
SummaryTab(pX = x + 78, pY = y - 1, icon = tabIconInfo) {
if (mainScreenIndex != INFO) {
displayMainScreen(INFO)
playSound(CobblemonSounds.GUI_CLICK)
}
if (mainScreenIndex != INFO) displayMainScreen(INFO)
}
)
summaryTabs.add(
SummaryTab(pX = x + 119, pY = y - 1, icon = tabIconMoves) {
if (mainScreenIndex != MOVES) {
displayMainScreen(MOVES)
playSound(CobblemonSounds.GUI_CLICK)
}
SummaryTab(pX = x + 109, pY = y - 1, icon = tabIconMoves) {
if (mainScreenIndex != MOVES) displayMainScreen(MOVES)
}
)
summaryTabs.add(
SummaryTab(pX = x + 160, pY = y - 1, icon = tabIconStats) {
if (mainScreenIndex != STATS) {
displayMainScreen(STATS)
playSound(CobblemonSounds.GUI_CLICK)
}
SummaryTab(pX = x + 140, pY = y - 1, icon = tabIconStats) {
if (mainScreenIndex != STATS) displayMainScreen(STATS)
}
)
summaryTabs.add(
SummaryTab(pX = x + 171, pY = y - 1, icon = tabIconMarks) {
if (mainScreenIndex != MARKS) displayMainScreen(MARKS)
}
)
@@ -250,6 +256,8 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
if (sideScreenIndex != PARTY) {
displaySideScreen(PARTY)
} else {
saveActiveMark()
saveMarkings()
Minecraft.getInstance().setScreen(null)
}
}
@@ -314,6 +322,7 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
* Switches the selected PKM
*/
fun switchSelection(newSelection: Int) {
saveMarkings()
this.selectedPokemon.moveSet.changeFunction = {}
this.party.getOrNull(newSelection)?.let { this.selectedPokemon = it }
listenToMoveSet()
@@ -323,11 +332,12 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
if (::modelWidget.isInitialized) {
modelWidget.pokemon = selectedPokemon.asRenderablePokemon()
modelWidget.heldItem = if (selectedPokemon.heldItemVisible) selectedPokemon.heldItem else null
heldItemVisibilityButton.buttonActive = !selectedPokemon.heldItemVisible
}
if (this::nicknameEntryWidget.isInitialized) {
this.nicknameEntryWidget.setSelectedPokemon(selectedPokemon)
}
if (::nicknameEntryWidget.isInitialized) nicknameEntryWidget.setSelectedPokemon(selectedPokemon)
if (::markingsWidget.isInitialized) markingsWidget.setActivePokemon(selectedPokemon)
}
/**
@@ -341,6 +351,14 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
}
}
private fun saveMarkings() {
if (::markingsWidget.isInitialized) markingsWidget.saveMarkingsToPokemon()
}
private fun saveActiveMark() {
if (mainScreenIndex == MARKS && mainScreen is MarksWidget) (mainScreen as MarksWidget).saveActiveMarkToPokemon()
}
/**
* Returns if this Screen is open or not
*/
@@ -350,6 +368,9 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
* Switch center screen
*/
private fun displayMainScreen(screen: Int) {
// Save mark to Pokémon if any changes when switching out of marks screen
saveActiveMark()
// Get stat tab index if currently displaying stat screen
val subIndex = if (mainScreenIndex == STATS && mainScreen is StatWidget) (mainScreen as StatWidget).statTabIndex else 0
@@ -389,6 +410,14 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
tabIndex = subIndex
)
}
MARKS -> {
mainScreen = MarksWidget(
pX = x + 77,
pY = y + 12,
pokemon = this.selectedPokemon
)
}
}
addRenderableWidget(mainScreen)
}
@@ -438,6 +467,8 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
}
EVOLVE -> {
saveActiveMark()
saveMarkings()
sideScreen = EvolutionSelectScreen(
x + 216,
y + 23,
@@ -651,7 +682,15 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
override fun keyPressed(keyCode: Int, scanCode: Int, modifiers: Int): Boolean {
val nicknameSelected = this::nicknameEntryWidget.isInitialized && nicknameEntryWidget.isFocused
if (keyCode == InputConstants.KEY_ESCAPE) {
saveActiveMark()
saveMarkings()
}
if (isInventoryKeyPressed(minecraft, keyCode, scanCode) && !nicknameSelected) {
saveActiveMark()
saveMarkings()
Minecraft.getInstance().setScreen(null)
return true
}
@@ -0,0 +1,60 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.gui.summary.widgets
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.components.Button
import net.minecraft.client.resources.sounds.SimpleSoundInstance
import net.minecraft.client.sounds.SoundManager
import net.minecraft.resources.ResourceLocation
class MarkingButton(
val buttonX: Number,
val buttonY: Number,
var buttonState: Int,
var canEdit: Boolean = true,
private val resource: ResourceLocation,
val clickAction: OnPress
): Button(buttonX.toInt(), buttonY.toInt(), (SIZE / 2), (SIZE / 2), "".text(), clickAction, DEFAULT_NARRATION), CobblemonRenderable {
companion object {
const val SIZE = 12
const val SCALE = 0.5F
}
fun renderButton(context: GuiGraphics, mouseX: Int, mouseY: Int, partialTicks: Float) {
val matrices = context.pose()
blitk(
matrixStack = matrices,
texture = resource,
x = buttonX.toFloat() / SCALE,
y = buttonY.toFloat() / SCALE,
width = SIZE,
height = SIZE,
vOffset = buttonState * SIZE,
uOffset = if (canEdit && isMouseOver(mouseX.toDouble(), mouseY.toDouble())) SIZE else 0,
textureHeight = SIZE * 3,
textureWidth = SIZE * 2,
scale = SCALE
)
}
override fun playDownSound(soundManager: SoundManager) {
if (canEdit) soundManager.play(SimpleSoundInstance.forUI(CobblemonSounds.GUI_CLICK, 1.0F))
}
override fun onClick(mouseX: Double, mouseY: Double) {
if (canEdit) super.onClick(mouseX, mouseY)
}
}
@@ -0,0 +1,68 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.gui.summary.widgets
import com.cobblemon.mod.common.CobblemonNetwork.sendToServer
import com.cobblemon.mod.common.net.messages.server.pokemon.update.SetMarkingsPacket
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.network.chat.Component
class MarkingsWidget(
val pX: Number,
val pY: Number,
var pokemon: Pokemon?,
var canEdit: Boolean = true,
): SoundlessWidget(pX.toInt(), pY.toInt(), WIDTH, HEIGHT, Component.literal("MarkingsWidget")) {
companion object {
private const val WIDTH = 82
private const val HEIGHT = 12
}
var markingStates: MutableList<Int> = mutableListOf(0, 0, 0, 0, 0, 0)
var markings: MutableList<MarkingButton> = mutableListOf()
init { setActivePokemon(pokemon) }
fun incrementState(state: Int) : Int = (state + 1) % 3
fun setActivePokemon(newPokemon: Pokemon?) {
pokemon = newPokemon
children.clear()
pokemon?.let {
markingStates = it.markings.toMutableList()
markingStates.forEachIndexed { index, state ->
addWidget(MarkingButton(
buttonX = pX.toFloat() + (index * ((MarkingButton.SIZE / 2) + 1)),
buttonY = pY,
buttonState = state,
canEdit = canEdit,
resource = cobblemonResource("textures/gui/summary/icon_marking_${index}.png")
) {
val nextState = incrementState(markingStates.get(index))
markingStates.set(index, nextState)
(it as MarkingButton).buttonState = nextState
})
}
}
}
fun saveMarkingsToPokemon(isParty: Boolean = true) {
pokemon?.let {
val currentStates = markingStates.toList()
if (it.markings != currentStates) sendToServer(SetMarkingsPacket(it.uuid, currentStates, isParty))
}
}
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, partialTicks: Float) {
children.forEach { (it as MarkingButton).renderButton(context, mouseX, mouseY, partialTicks) }
}
}
@@ -18,7 +18,6 @@ import com.cobblemon.mod.common.util.math.fromEulerXYZDegrees
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.network.chat.Component
import net.minecraft.world.item.ItemStack
import net.minecraft.world.phys.Vec3
import org.joml.Quaternionf
import org.joml.Vector3f
@@ -114,12 +113,13 @@ class ModelWidget(
)
heldItemRenderer.renderOnModel(
state.currentModel!!,
heldItem?: ItemStack.EMPTY,
state.currentModel!!,
state,
matrices,
context.bufferSource(),
Vec3(0.0,-90.0,-90.0)
light = 0xF000F0,
true
)
matrices.popPose()
@@ -8,14 +8,14 @@
package com.cobblemon.mod.common.client.gui.summary.widgets.screens
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.text.bold
import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.components.Button
import net.minecraft.client.resources.sounds.SimpleSoundInstance
import net.minecraft.client.sounds.SoundManager
import net.minecraft.network.chat.MutableComponent
import net.minecraft.resources.ResourceLocation
@@ -25,7 +25,15 @@ class SummaryTab(
val label: MutableComponent? = null,
val icon: ResourceLocation? = null,
onPress: OnPress
): Button(pX, pY, 50, 13, label, onPress, DEFAULT_NARRATION), CobblemonRenderable {
): Button(pX, pY, WIDTH, HEIGHT, label, onPress, DEFAULT_NARRATION), CobblemonRenderable {
companion object {
private const val WIDTH = 39
private const val HEIGHT = 13
private const val SCALE = 0.5F
private val tabResource = cobblemonResource("textures/gui/summary/summary_tab.png")
}
private var isActive = false
override fun renderWidget(context: GuiGraphics, pMouseX: Int, pMouseY: Int, pPartialTicks: Float) {
@@ -33,7 +41,7 @@ class SummaryTab(
if (isActive) {
blitk(
matrixStack = matrices,
texture = cobblemonResource("textures/gui/summary/summary_tab.png"),
texture = tabResource,
x = x,
y = y,
width = width,
@@ -45,31 +53,24 @@ class SummaryTab(
blitk(
matrixStack = matrices,
texture = icon,
x = (x + 21) / 0.5F,
y = (y + 3.5) / 0.5F,
x = (x + 15.5) / SCALE,
y = (y + 3.5) / SCALE,
width = 17,
height = 17,
scale = 0.5F
scale = SCALE
)
}
if (label !== null) {
drawScaledText(
context = context,
font = CobblemonResources.DEFAULT_LARGE,
text = label.bold(),
x = x + 25,
y = y + 3,
centered = true,
shadow = true
)
if (label !== null && isMouseOver(pMouseX.toDouble(), pMouseY.toDouble())) {
context.renderTooltip(Minecraft.getInstance().font, label, pMouseX, pMouseY)
}
}
override fun playDownSound(soundManager: SoundManager) {
Minecraft.getInstance().soundManager.play(SimpleSoundInstance.forUI(CobblemonSounds.GUI_CLICK, 1.0F))
}
fun toggleTab(state: Boolean = true) {
isActive = state
}
}
}
@@ -0,0 +1,186 @@
/*
* 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.summary.widgets.screens.marks
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.mark.Mark
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.client.gui.ScrollingWidget
import com.cobblemon.mod.common.client.gui.common.MarkIcon
import com.cobblemon.mod.common.client.gui.summary.widgets.screens.marks.MarksScrollingWidget.ScrollSlotRow
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.network.chat.Component
import net.minecraft.util.FastColor
import net.minecraft.util.Mth
class MarksScrollingWidget(val pX: Int, val pY: Int, val setSelectedMark: () -> (Unit), val setHoveredMark: (Mark?) -> (Unit)): ScrollingWidget<ScrollSlotRow>(
width = WIDTH,
height = HEIGHT,
left = pX,
top = pY - HEIGHT,
slotHeight = SLOT_SIZE + SLOT_SPACING
) {
companion object {
const val WIDTH = 119
const val HEIGHT = 98
const val SLOT_SIZE = 16
const val SLOT_SPACING = 3
}
fun createEntries(marks: List<Mark?>) {
marks.chunked(6).forEachIndexed { index, listChunk ->
addEntry(ScrollSlotRow(this, listChunk, setSelectedMark, setHoveredMark))
}
}
override fun getRowLeft(): Int = if (children().size > 5) (left + 1) else left
override fun getScrollbarPosition(): Int = rowLeft + width - 3
override fun renderScrollbar(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
// Do not render scrollbar if all rows are already visible
if (children().size > 5) {
val xLeft = this.scrollbarPosition
val xRight = xLeft + 3
val barHeight = this.bottom - this.y
var yBottom = ((barHeight * barHeight).toFloat() / this.maxPosition.toFloat()).toInt()
yBottom = Mth.clamp(yBottom, 32, barHeight - 8)
var yTop = scrollAmount.toInt() * (barHeight - yBottom) / this.maxScroll + this.y
if (yTop < this.y) {
yTop = this.y
}
context.fill(xLeft, this.y, xRight, this.bottom, FastColor.ARGB32.color(255, 75, 75, 75)) // background
context.fill(xLeft,yTop, xRight, yTop + yBottom, FastColor.ARGB32.color(255, 141, 141, 141)) // base
}
}
fun renderEntry(context: GuiGraphics, mouseX: Int, mouseY: Int, index: Int, x: Int, y: Int): Boolean {
val entry = this.getEntry(index)
entry.x = x
entry.y = y
return entry.renderRow( context, y, x, mouseX, mouseY)
}
override fun getEntry(index: Int): ScrollSlotRow = children()[index] as ScrollSlotRow
override fun renderListItems(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
val rowX = rowLeft
var anyHovered = false
for (index in 0 until this.itemCount) {
val rowY = this.getRowTop(index)
val o = this.getRowBottom(index)
if (o >= this.y && rowY <= this.bottom) {
if (this.renderEntry(context!!, mouseX, mouseY, index, rowX, rowY)) anyHovered = true
}
}
if (!anyHovered) setHoveredMark(null)
}
override fun mouseDragged(mouseX: Double, mouseY: Double, button: Int, dragX: Double, dragY: Double): Boolean {
// Prevent scrollbar drag if all rows are already visible
return if (children().size > 5) super.mouseDragged(mouseX, mouseY, button, dragX, dragY) else true
}
override fun mouseScrolled(mouseX: Double, mouseY: Double, scrollX: Double, scrollY: Double): Boolean {
// Prevent scroll if all rows are already visible
return if (children().size > 5) super.mouseScrolled(mouseX, mouseY, scrollX, scrollY) else true
}
class ScrollSlotRow(
val parent: MarksScrollingWidget,
val markList: List<Mark?>,
val setSelectedMark : () -> Unit,
val setHoveredMark: (Mark?) -> Unit
): Slot<ScrollSlotRow>() {
companion object {
private val slotResource = cobblemonResource("textures/gui/summary/summary_mark_slot.png")
}
var x: Int = 0
var y: Int = 0
fun renderRow(context: GuiGraphics, y: Int, x: Int, mouseX: Int, mouseY: Int): Boolean {
var anyHovered = false
val horizontalSpacing = if (isScrollVisible()) SLOT_SPACING else (SLOT_SPACING + 1)
markList.forEachIndexed { index, mark ->
val matrices = context.pose()
val startPosX = x + ((horizontalSpacing + SLOT_SIZE) * index)
val startPosY = y + SLOT_SPACING
val slotHovered = mark != null && getHoveredSlotIndex(mouseX, mouseY) == index
if (slotHovered) {
setHoveredMark(markList[index])
anyHovered = true
}
blitk(
matrixStack = matrices,
texture = slotResource,
x = startPosX,
y = startPosY,
width = SLOT_SIZE,
height = SLOT_SIZE,
vOffset = if (slotHovered) SLOT_SIZE else 0,
textureHeight = SLOT_SIZE * 2
)
if (mark != null) MarkIcon(startPosX, startPosY, mark, false).render(context, mouseX, mouseY)
}
return anyHovered
}
override fun render(
context: GuiGraphics,
index: Int,
y: Int,
x: Int,
entryWidth: Int,
entryHeight: Int,
mouseX: Int,
mouseY: Int,
hovered: Boolean,
tickDelta: Float
) {}
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
setSelectedMark.invoke()
return true
}
private fun isScrollVisible(): Boolean = parent.children().size > 5
private fun getHoveredSlotIndex(mouseX: Int, mouseY: Int): Int {
val horizontalSpacing = if (isScrollVisible()) SLOT_SPACING else (SLOT_SPACING + 1)
markList.forEachIndexed { index, _ ->
val startPosX = x + ((horizontalSpacing + SLOT_SIZE) * index)
val startPosY = y + SLOT_SPACING + 1
if (mouseX in startPosX..(startPosX + SLOT_SIZE)
&& mouseY in startPosY..(startPosY + SLOT_SIZE)) {
return index
}
}
return -1
}
override fun getNarration(): Component {
if (markList.isNotEmpty()) {
return "${markList[0]}-${markList[markList.size - 1]}".text()
}
return "".text()
}
}
}
@@ -0,0 +1,138 @@
/*
* 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.summary.widgets.screens.marks
import com.cobblemon.mod.common.CobblemonNetwork.sendToServer
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.mark.Mark
import com.cobblemon.mod.common.client.gui.common.MarkIcon
import com.cobblemon.mod.common.client.gui.summary.widgets.SoundlessWidget
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.net.messages.server.pokemon.update.SetActiveMarkPacket
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.resources.sounds.SimpleSoundInstance
import net.minecraft.network.chat.Component
class MarksWidget(
pX: Int, pY: Int,
val pokemon: Pokemon
): SoundlessWidget(pX, pY, WIDTH, HEIGHT, Component.literal("MarksWidget")) {
companion object {
private const val WIDTH = 134
private const val HEIGHT = 148
const val SCALE = 0.5F
private val marksBaseResource = cobblemonResource("textures/gui/summary/summary_marks_base.png")
}
var activeMark: Mark? = null
var selectedMark: Mark? = null
var hoveredMark: Mark? = null
val marksScrollList = MarksScrollingWidget(
pX = pX + 9,
pY = pY + 45,
setSelectedMark = {
if (hoveredMark != null) {
selectedMark = hoveredMark
Minecraft.getInstance().soundManager.play(SimpleSoundInstance.forUI(CobblemonSounds.GUI_CLICK, 1.0F))
}
},
setHoveredMark = { hoveredMark = it }
)
init {
children.clear()
activeMark = pokemon.activeMark
selectedMark = pokemon.activeMark
val marksList: MutableList<Mark?> = pokemon.marks.sortedBy { it.identifier.toString() }.toMutableList()
while ((marksList.size < 30) || (marksList.size > 30 && marksList.size % 6 != 0)) marksList.add(null)
marksScrollList.createEntries(marksList)
addWidget(marksScrollList)
}
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
if (selectedMark !== null && isSelectedMarkHovered(mouseX.toInt(), mouseY.toInt())) {
selectedMark = null
Minecraft.getInstance().soundManager.play(SimpleSoundInstance.forUI(CobblemonSounds.GUI_CLICK, 1.0F))
}
return super.mouseClicked(mouseX, mouseY, button)
}
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, partialTicks: Float) {
val matrices = context.pose()
blitk(
matrixStack = matrices,
texture = marksBaseResource,
x = x,
y = y,
width = width,
height = height
)
if (selectedMark != null) {
MarkIcon(
x = x + 12,
y = y + 12,
selectedMark!!
).render(context, mouseX, mouseY)
matrices.pushPose()
matrices.scale(SCALE, SCALE, 1F)
MultiLineLabelK.create(
component = selectedMark!!.getDescription(),
width = 85 / SCALE,
maxLines = 4
).renderLeftAligned(
context = context,
x = (x + 38) / SCALE,
y = (y + 11) / SCALE,
ySpacing = 5 / SCALE,
colour = ColourLibrary.WHITE,
shadow = true
)
matrices.popPose()
}
// Title
(if (selectedMark?.title != null) selectedMark!!.getTitle(pokemon.getDisplayName()) else pokemon.getDisplayName())?.let {
drawScaledText(
context = context,
text = it,
x = x + (width / 2),
y = y + 38,
scale = SCALE,
centered = true
)
}
marksScrollList.render(context, mouseX, mouseY, partialTicks)
if (hoveredMark !== null && marksScrollList.isMouseOver(mouseX.toDouble(), mouseY.toDouble())) {
context.renderTooltip(Minecraft.getInstance().font, hoveredMark!!.getName(), mouseX, mouseY)
}
}
fun isSelectedMarkHovered(mouseX: Int, mouseY: Int): Boolean {
val markX = x + 12
val markY = y + 12
return mouseX in (markX..(markX + (MarkIcon.SIZE * MarkIcon.SCALE).toInt()))
&& mouseY in (markY..(markY + (MarkIcon.SIZE * MarkIcon.SCALE).toInt()))
}
fun saveActiveMarkToPokemon() {
if (selectedMark != activeMark) sendToServer(SetActiveMarkPacket(pokemon.uuid, selectedMark))
}
}
@@ -20,6 +20,7 @@ import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import com.cobblemon.mod.common.client.gui.ExitButton
import com.cobblemon.mod.common.client.gui.TypeIcon
import com.cobblemon.mod.common.client.gui.summary.Summary
import com.cobblemon.mod.common.client.gui.summary.widgets.MarkingsWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.common.reformatNatureTextIfMinted
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.client.render.drawScaledTextJustifiedRight
@@ -99,6 +100,9 @@ class TradeGUI(
private var offeredPokemonModel: ModelWidget? = null
private var opposingOfferedPokemonModel: ModelWidget? = null
private lateinit var offeredPokemonMarkings: MarkingsWidget
private lateinit var opposingOfferedPokemonMarkings: MarkingsWidget
var offeredPokemon: Pokemon? = null
var opposingOfferedPokemon: Pokemon? = null
@@ -247,6 +251,11 @@ class TradeGUI(
setOfferedPokemon(pokemon = offeredPokemon, isOpposing = false)
setOfferedPokemon(pokemon = opposingOfferedPokemon, isOpposing = true)
offeredPokemonMarkings = MarkingsWidget(x + 74.5, y + 21, offeredPokemon, false)
opposingOfferedPokemonMarkings = MarkingsWidget(x + 177.5, y + 21, opposingOfferedPokemon, false)
addRenderableWidget(offeredPokemonMarkings)
addRenderableWidget(opposingOfferedPokemonMarkings)
}
override fun render(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
@@ -522,6 +531,7 @@ class TradeGUI(
rotationY = 35F,
offsetY = PORTRAIT_MODEL_OFFSET_Y
) else null
if (::opposingOfferedPokemonMarkings.isInitialized) opposingOfferedPokemonMarkings.setActivePokemon(opposingOfferedPokemon)
trade.acceptedOppositeOffer = false
} else {
offeredPokemon = pokemon
@@ -535,6 +545,7 @@ class TradeGUI(
rotationY = 325F,
offsetY = PORTRAIT_MODEL_OFFSET_Y
) else null
if (::offeredPokemonMarkings.isInitialized) offeredPokemonMarkings.setActivePokemon(offeredPokemon)
}
}
@@ -0,0 +1,32 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.net.orientation
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.api.net.ClientNetworkPacketHandler
import com.cobblemon.mod.common.net.messages.client.orientation.ClientboundUpdateOrientationPacket
import net.minecraft.client.Minecraft
object S2CUpdateOrientationHandler : ClientNetworkPacketHandler<ClientboundUpdateOrientationPacket> {
override fun handle(packet: ClientboundUpdateOrientationPacket, client: Minecraft) {
client.executeIfPossible {
val level = client.level ?: return@executeIfPossible
val entity = level.getEntity(packet.entityId)
if (entity is OrientationControllable) {
packet.active?.let {
entity.orientationController.active = it
if (!it) {
entity.orientationController.reset()
}
}
entity.orientationController.updateOrientation { _ -> packet.orientation }
}
}
}
}
@@ -1,27 +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.client.net.orientation
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.api.net.ClientNetworkPacketHandler
import com.cobblemon.mod.common.net.messages.client.orientation.S2CUpdateOrientationPacket
import net.minecraft.client.Minecraft
object S2CUpdateOrientationHandler : ClientNetworkPacketHandler<S2CUpdateOrientationPacket> {
override fun handle(packet: S2CUpdateOrientationPacket, client: Minecraft) {
val level = client.level ?: return
val entity = level.getEntity(packet.entityId)
if (entity is OrientationControllable) {
entity.orientationController.updateOrientation { _ -> packet.orientation }
packet.active?.let {
entity.orientationController.active = it
}
}
}
}
@@ -0,0 +1,28 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.net.pokemon.update
import com.cobblemon.mod.common.api.net.ClientNetworkPacketHandler
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.net.messages.client.pokemon.update.ClientboundUpdateRidingStatePacket
import net.minecraft.client.Minecraft
object ClientboundUpdateRidingStateHandler : ClientNetworkPacketHandler<ClientboundUpdateRidingStatePacket> {
override fun handle(packet: ClientboundUpdateRidingStatePacket, client: Minecraft) {
client.executeIfPossible {
val player = client.player ?: return@executeIfPossible
val entity = player.level().getEntity(packet.entity) ?: return@executeIfPossible
if (entity !is PokemonEntity) return@executeIfPossible
if (entity.controllingPassenger == player) return@executeIfPossible
val buffer = packet.data ?: return@executeIfPossible
if (entity.riding?.key != packet.behaviour) return@executeIfPossible
entity.ridingState?.decode(buffer)
}
}
}
@@ -11,9 +11,12 @@ package com.cobblemon.mod.common.client.render.item
import com.cobblemon.mod.common.api.tags.CobblemonItemTags
import com.cobblemon.mod.common.api.tags.CobblemonItemTags.WEARABLE_FACE_ITEMS
import com.cobblemon.mod.common.api.tags.CobblemonItemTags.WEARABLE_HAT_ITEMS
import com.cobblemon.mod.common.client.render.MatrixWrapper
import com.cobblemon.mod.common.client.entity.NPCClientDelegate
import com.cobblemon.mod.common.client.entity.PokemonClientDelegate
import com.cobblemon.mod.common.client.render.models.blockbench.FloatingState
import com.cobblemon.mod.common.client.render.models.blockbench.PosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.PosableState
import com.mojang.blaze3d.systems.RenderSystem
import com.mojang.blaze3d.vertex.PoseStack
import com.mojang.math.Axis
import net.minecraft.client.Minecraft
@@ -24,7 +27,7 @@ import net.minecraft.client.renderer.texture.OverlayTexture
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.item.ItemDisplayContext
import net.minecraft.world.item.ItemStack
import net.minecraft.world.phys.Vec3
import org.joml.Vector3f
class HeldItemRenderer {
private val itemRenderer: ItemRenderer = Minecraft.getInstance().itemRenderer
@@ -34,133 +37,91 @@ class HeldItemRenderer {
const val ITEM_FACE = "item_face"
const val ITEM_HAT = "item_hat"
const val ITEM = "item"
}
fun render(
entity: LivingEntity?,
model: PosableModel,
item: ItemStack,
locators: Map<String, MatrixWrapper>,
poseStack: PoseStack,
buffer: MultiBufferSource,
light: Int,
seed: Int,
rotation: Vec3
) {
if (item.isEmpty) return
displayContext = ItemDisplayContext.FIXED
poseStack.pushPose()
when {
//item_face locator
(locators.containsKey(ITEM_FACE) && item.`is`(WEARABLE_FACE_ITEMS)) -> {
displayContext = model.getLocatorDisplayContext(ITEM_FACE) ?: ItemDisplayContext.HEAD
poseStack.mulPose(locators[ITEM_FACE]!!.matrix)
poseStack.translate(0f, 0f, .28f)
poseStack.scale(0.7f, 0.7f, 0.7f)
}
//item_hat locator
(locators.containsKey(ITEM_HAT) && item.`is`(WEARABLE_HAT_ITEMS)) -> {
displayContext = model.getLocatorDisplayContext(ITEM_HAT) ?: ItemDisplayContext.HEAD
poseStack.mulPose(locators[ITEM_HAT]!!.matrix)
poseStack.translate(0f, -0.26f, 0f)
poseStack.scale(.68f, .68f, .68f)
}
//item locator
(locators.containsKey(ITEM)) -> {
displayContext = model.getLocatorDisplayContext(ITEM) ?: ItemDisplayContext.FIXED
poseStack.mulPose(locators[ITEM]!!.matrix)
applyContextTransformation(poseStack, rotation)
}
// Don't render any item
else -> {
poseStack.popPose()
return
}
}
itemRenderer.renderStatic(entity, item, displayContext, (displayContext==ItemDisplayContext.THIRD_PERSON_LEFT_HAND), poseStack, buffer, null, light, OverlayTexture.NO_OVERLAY, seed)
poseStack.popPose()
}
private fun renderAnimationItem(
model: PosableModel,
state: PosableState,
locators: Map<String, MatrixWrapper>,
poseStack: PoseStack,
buffer: MultiBufferSource,
light: Int,
entity: LivingEntity?,
rotation: Vec3
) {
state.itemRenderingLocations.forEach { (locatorName, item) ->
poseStack.pushPose()
if (locators.containsKey(locatorName)) {
displayContext = model.getLocatorDisplayContext(locatorName)?: ItemDisplayContext.FIXED
poseStack.mulPose(locators[locatorName]!!.matrix)
applyContextTransformation(poseStack, rotation)
}
itemRenderer.renderStatic(entity, item, displayContext, (displayContext==ItemDisplayContext.THIRD_PERSON_LEFT_HAND), poseStack, buffer, null, light, OverlayTexture.NO_OVERLAY, 0)
poseStack.popPose()
private fun PoseStack.setOriginAndScale(x:Float,y:Float,z:Float,scale:Float=1f) {
this.translate(x*.0625*scale, y*.0625*scale, z*.0625*scale)
this.scale(scale, scale, scale)
}
}
fun renderOnModel(
model: PosableModel,
item: ItemStack,
model: PosableModel,
state: PosableState,
poseStack: PoseStack,
buffer: MultiBufferSource,
rotation: Vec3 = Vec3(0.0,0.0,0.0),
light: Int = LightTexture.pack(11, 7),
frontLight: Boolean = false,
entity: LivingEntity? = null
) {
val locators: Map<String, MatrixWrapper> = state.locatorStates
if (state.itemRenderingLocations.isNotEmpty()) {
renderAnimationItem(model, state, locators, poseStack, buffer, light, null, rotation)
}
else if (!item.`is`(CobblemonItemTags.HIDDEN_ITEMS)) {
render(null, model, item, locators, poseStack, buffer, light, 0, rotation)
}
if (!item.`is`(CobblemonItemTags.HIDDEN_ITEMS)) renderAtLocator(item, model, state, entity, poseStack, buffer, light, 0, frontLight)
state.animationItems.forEach { (targetLocator, item) -> renderAtLocator(item, model, state, entity, poseStack, buffer, light, 0, frontLight, targetLocator) }
}
fun renderOnEntity(
entity: LivingEntity,
private fun renderAtLocator(
item: ItemStack,
model: PosableModel,
state: PosableState,
entity: LivingEntity?,
poseStack: PoseStack,
buffer: MultiBufferSource,
light: Int,
rotation: Vec3 = Vec3(0.0,0.0,0.0)
seed: Int,
frontLight: Boolean = false,
targetLocator: String =
if (item.`is`(WEARABLE_FACE_ITEMS) && state.locatorStates.containsKey(ITEM_FACE)) ITEM_FACE
else if (item.`is`(WEARABLE_HAT_ITEMS) && state.locatorStates.containsKey(ITEM_HAT)) ITEM_HAT
else ITEM
) {
val locators: Map<String, MatrixWrapper> = state.locatorStates
if (state.itemRenderingLocations.isNotEmpty()) {
renderAnimationItem(model, state, locators, poseStack, buffer, light, entity, rotation)
}
else {
render(entity, model, item, locators, poseStack, buffer, light, 0, rotation)
}
}
if (item.isEmpty) return
private fun applyContextTransformation(poseStack: PoseStack, rotation: Vec3) {
when (displayContext) {
ItemDisplayContext.FIXED -> {
poseStack.mulPose(Axis.XP.rotationDegrees(90.0f))
poseStack.scale(.5f, .5f, .5f )
poseStack.translate(0f, 0.01666f, 0f)
if (state.locatorStates.containsKey(targetLocator)) {
poseStack.pushPose()
RenderSystem.applyModelViewMatrix()
displayContext = model.getLocatorDisplayContext(targetLocator)?:
if ((item.`is`(WEARABLE_FACE_ITEMS) && targetLocator==ITEM_FACE) || (item.`is`(WEARABLE_HAT_ITEMS) && targetLocator== ITEM_HAT)) ItemDisplayContext.HEAD
else ItemDisplayContext.FIXED
poseStack.mulPose(state.locatorStates[targetLocator]!!.matrix)
when (displayContext) {
ItemDisplayContext.FIXED -> {
poseStack.setOriginAndScale(0f,.5f,0f, .5f)
poseStack.mulPose(Axis.XP.rotationDegrees(90.0f))
}
ItemDisplayContext.THIRD_PERSON_RIGHT_HAND, ItemDisplayContext.THIRD_PERSON_LEFT_HAND -> {
//For Minecraft player models
if (state is NPCClientDelegate) {
poseStack.mulPose(Axis.XP.rotationDegrees(-90.0F))
}
//for T-Posing Models
else if (state is PokemonClientDelegate || state is FloatingState) {
poseStack.mulPose(Axis.YP.rotationDegrees(-90.0F))
poseStack.mulPose(Axis.ZP.rotationDegrees(90.0F))
}
poseStack.translate(0.025f * if (displayContext==ItemDisplayContext.THIRD_PERSON_LEFT_HAND) 1 else -1 , 0.0f, 0.0f)
}
ItemDisplayContext.HEAD -> {
if (state is NPCClientDelegate) {
if (item.`is`(WEARABLE_FACE_ITEMS)) poseStack.setOriginAndScale(0f,-2f,7.25f, 0.62f)
else if (item.`is`(WEARABLE_HAT_ITEMS)) poseStack.setOriginAndScale(0f,-10.25f,0f, 0.62f)
}
else {
if (item.`is`(WEARABLE_FACE_ITEMS)) poseStack.setOriginAndScale(0f,1f,(7.25f)-.5f, 0.62f)
else if (item.`is`(WEARABLE_HAT_ITEMS)) poseStack.setOriginAndScale(0f,(-7.25f)+.5f,0f, 0.62f)
}
}
else -> {}
}
ItemDisplayContext.THIRD_PERSON_RIGHT_HAND, ItemDisplayContext.THIRD_PERSON_LEFT_HAND -> {
poseStack.mulPose(Axis.XP.rotationDegrees((rotation.x).toFloat()))
poseStack.mulPose(Axis.YP.rotationDegrees((rotation.y).toFloat()))
poseStack.mulPose(Axis.ZP.rotationDegrees((rotation.z).toFloat()))
poseStack.translate(0.025f * if (displayContext==ItemDisplayContext.THIRD_PERSON_LEFT_HAND) 1 else -1 , 0.0f, 0.0f)
//Render front lighting for UI Models
if (frontLight) {
RenderSystem.setShaderLights(Vector3f(0F,0F,1F), Vector3f(0F,0F,1F))
}
ItemDisplayContext.HEAD -> {
poseStack.mulPose(Axis.XP.rotationDegrees(90.0f))
poseStack.scale(.6f, .6f, .6f)
poseStack.translate(0f, -0.6f, 0f)
}
else -> {}
itemRenderer.renderStatic(entity, item, displayContext, (displayContext==ItemDisplayContext.THIRD_PERSON_LEFT_HAND), poseStack, buffer, null, light, OverlayTexture.NO_OVERLAY, seed)
poseStack.popPose()
}
}
}
@@ -37,7 +37,6 @@ import net.minecraft.nbt.Tag
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.ItemStack
import net.minecraft.world.phys.Vec3
class PokemonOnShoulderRenderer<T : Player>(renderLayerParent: RenderLayerParent<T, PlayerModel<T>>) : RenderLayer<T, PlayerModel<T>>(renderLayerParent) {
@@ -162,12 +161,12 @@ class PokemonOnShoulderRenderer<T : Player>(renderLayerParent: RenderLayerParent
}
heldItemRenderer.renderOnModel(
model,
shoulderData.shownItem,
model,
state,
matrixStack,
buffer,
Vec3(0.0,-90.0,-90.0)
packedLight
)
model.setDefault()
@@ -78,8 +78,15 @@ class JsonPose(model: PosableModel, json: JsonObject) {
it[2].asDouble
)
} ?: Vec3.ZERO
val scale = it.get("scale")?.asJsonArray?.let {
Vec3(
it[0].asDouble,
it[1].asDouble,
it[2].asDouble
)
} ?: Vec3(1.0,1.0,1.0)
val isVisible = it.get("isVisible")?.asString?.asExpressionLike()
return@map part.withPosition(position.x, position.y, position.z).withRotationDegrees(rotation.x, rotation.y, rotation.z).also { if (isVisible != null) it.withVisibility(isVisible) }
return@map part.withPosition(position.x, position.y, position.z).withRotationDegrees(rotation.x, rotation.y, rotation.z).withScale(scale.x, scale.y, scale.z).also { if (isVisible != null) it.withVisibility(isVisible) }
}?.toTypedArray() ?: arrayOf()
val idleAnimations = (json.get("animations")?.asJsonArray ?: JsonArray()).asJsonArray.mapNotNull {
@@ -8,10 +8,8 @@
package com.cobblemon.mod.common.client.render.models.blockbench
import com.cobblemon.mod.common.client.entity.PokemonClientDelegate
import com.cobblemon.mod.common.client.render.models.blockbench.repository.RenderContext
import com.cobblemon.mod.common.entity.PosableEntity
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import net.minecraft.client.renderer.texture.OverlayTexture
import net.minecraft.client.renderer.RenderType
import com.mojang.blaze3d.vertex.VertexConsumer
@@ -36,7 +34,6 @@ abstract class PosableEntityModel<T : Entity>(
val context: RenderContext = RenderContext().also {
it.put(RenderContext.RENDER_STATE, RenderContext.RenderState.WORLD)
}
lateinit var posableModel: PosableModel
override fun renderToBuffer(
@@ -79,11 +76,25 @@ abstract class PosableEntityModel<T : Entity>(
) {
setupEntityTypeContext(entity)
if (entity is PosableEntity) {
val state = entity.delegate as PosableState
posableModel.applyAnimations(entity, state, limbSwing, limbSwingAmount, ageInTicks, headYaw, headPitch)
val ticks = getTicksForAnimation(entity, limbSwing, limbSwingAmount, ageInTicks, headYaw, headPitch)
posableModel.applyAnimations(entity, entity.delegate as PosableState, limbSwing, limbSwingAmount, ticks, headYaw, headPitch)
}
}
/**
* This is to support cases where the ageInTicks value that should go to the animator is not the same as the entity's
* normal value. This can happen in the case of riding where it has been rendered in a different part of the pipeline
* and we want to prevent stuttering / re-render it for a specific moment.
*/
open fun getTicksForAnimation(
entity: T,
limbSwing: Float,
limbSwingAmount: Float,
ageInTicks: Float,
headYaw: Float,
headPitch: Float
): Float = ageInTicks
open fun setupEntityTypeContext(entity: Entity?) {
entity?.let {
context.put(RenderContext.ENTITY, entity)
@@ -92,4 +103,4 @@ abstract class PosableEntityModel<T : Entity>(
}
}
}
}
}
@@ -48,6 +48,7 @@ import com.mojang.blaze3d.vertex.PoseStack
import com.mojang.blaze3d.vertex.VertexConsumer
import com.mojang.blaze3d.vertex.VertexFormat
import com.mojang.math.Axis
import net.minecraft.client.Minecraft
import net.minecraft.client.model.geom.ModelPart
import net.minecraft.client.renderer.MultiBufferSource
import net.minecraft.client.renderer.RenderStateShard
@@ -56,7 +57,6 @@ import net.minecraft.client.renderer.texture.DynamicTexture
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.Mth
import net.minecraft.world.entity.Entity
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.item.ItemDisplayContext
import net.minecraft.world.phys.Vec3
import org.joml.Matrix4f
@@ -93,6 +93,9 @@ open class PosableModel(@Transient override val rootPart: Bone) : ModelFrame {
open var profileScale = 1F
/** Used for third person riding camera */
open var seatToCameraOffset = mutableMapOf<String, Vec3>()
/**
* These are open-ended properties that can be used to store miscellaneous properties about the model.
*
@@ -529,7 +532,7 @@ open class PosableModel(@Transient override val rootPart: Bone) : ModelFrame {
/** Puts the model back to its original location and rotations. */
fun setDefault() {
defaultPositions.forEach { it.set() }
transformedParts.forEach { it.set() }
transformedParts.forEach { it.apply() }
}
/**
@@ -602,7 +605,11 @@ open class PosableModel(@Transient override val rootPart: Bone) : ModelFrame {
applyPose(state, pose, 1F)
val primaryAnimation = state.primaryAnimation
val shouldRotateHead = if(entity is PokemonEntity) entity.riding.shouldRotatePokemonHead(entity) else true
val shouldRotateHead = if (entity is PokemonEntity) {
entity.ifRidingAvailableSupply(true) { behaviour, settings, ridingState ->
behaviour.shouldRotatePokemonHead(settings, ridingState, entity)
}
} else true
// Quirks will run if there is no primary animation running and quirks are enabled for this context.
if (primaryAnimation == null && context.request(RenderContext.DO_QUIRKS) != false) {
@@ -717,12 +724,13 @@ open class PosableModel(@Transient override val rootPart: Bone) : ModelFrame {
// We could improve this to be generalized for other entities. First we'd have to figure out wtf is going on, though.
if (entity is PokemonEntity) {
if(entity.passengers.isNotEmpty() && entity.controllingPassenger is OrientationControllable
&& (entity.controllingPassenger as OrientationControllable).orientationController.orientation != null){
val controllingPassenger = entity.controllingPassenger
&& (entity.controllingPassenger as OrientationControllable).orientationController.active){
val controllingPassenger = entity.controllingPassenger as OrientationControllable
val controller = controllingPassenger.orientationController
val transformationMatrix = Matrix4f()
val center = Vector3f(0f, entity.bbHeight/2, 0f)
transformationMatrix.translate(center)
transformationMatrix.mul(Matrix4f((controllingPassenger as OrientationControllable).orientationController.orientation!!))
transformationMatrix.rotate(controller.getRenderOrientation(state.getPartialTicks()))
transformationMatrix.translate(center.negate())
matrixStack.mulPose(transformationMatrix)
} else {
@@ -819,4 +827,6 @@ open class PosableModel(@Transient override val rootPart: Bone) : ModelFrame {
condition = condition,
animations = { animations(it) }
)
fun isReadyForAnimation() = ::context.isInitialized
}
@@ -37,6 +37,7 @@ import com.cobblemon.mod.common.entity.PosableEntity
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import com.mojang.brigadier.StringReader
import com.mojang.brigadier.exceptions.CommandSyntaxException
import net.minecraft.client.Minecraft
import net.minecraft.client.multiplayer.ClientLevel
import net.minecraft.client.resources.sounds.SimpleSoundInstance
@@ -46,7 +47,6 @@ import net.minecraft.sounds.SoundSource
import net.minecraft.world.entity.Entity
import net.minecraft.world.item.ItemStack
import net.minecraft.world.phys.Vec3
import org.joml.Matrix4f
import java.util.*
import java.util.concurrent.ConcurrentLinkedQueue
@@ -128,9 +128,8 @@ abstract class PosableState : Schedulable {
/** This gets called 500 million times so use a mutable value for runtime */
private val reusableAnimTime = DoubleValue(0.0)
val itemRenderingLocations = mutableMapOf<String, ItemStack>()
// var itemRenderingLocation = ""
// var animationItem : ItemStack? = null
/** A list of items to be rendered on a PosableModel during an animation */
val animationItems = mutableMapOf<String, ItemStack>()
/** All of the MoLang functions that can be applied to something with this state. */
val functions = QueryStruct(hashMapOf())
@@ -159,21 +158,27 @@ abstract class PosableState : Schedulable {
.addFunction("render_item") { params ->
if (params.get<MoValue>(0) !is StringValue) return@addFunction Unit
val client = Minecraft.getInstance().connection ?: return@addFunction Unit
val result = ItemParser(client.registryAccess()).parse(StringReader(params.getString(0)))
val item = ItemStack(result.item)
item.applyComponents(result.components)
val item: ItemStack
try {
val client = Minecraft.getInstance().connection ?: return@addFunction Unit
val result = ItemParser(client.registryAccess()).parse(StringReader(params.getString(0)))
item = ItemStack(result.item)
item.applyComponents(result.components)
}
catch (_: CommandSyntaxException) {
return@addFunction Unit
}
val renderLocation = if (params.contains(1)) params.getString(1) else "item"
if (!item.isEmpty && locatorStates.containsKey(renderLocation)) {
itemRenderingLocations.put(renderLocation, item)
animationItems.put(renderLocation, item)
} else {
if (itemRenderingLocations.containsKey(renderLocation)) itemRenderingLocations.remove(renderLocation)
if (animationItems.containsKey(renderLocation)) animationItems.remove(renderLocation)
return@addFunction Unit
}
}
.addFunction("clear_items") { itemRenderingLocations.clear() }
.addFunction("clear_items") { animationItems.clear() }
.addFunction("play_animation") { params ->
val animationParameter = params.get<MoValue>(0)
val animation = if (animationParameter is ObjectValue<*>) {
@@ -77,6 +77,6 @@ class PoseTransitionAnimation(
it.apply(context, model, state, limbSwing, limbSwingAmount, ageInTicks, headYaw, headPitch, newIntensity)
}
return ratio < 1F
return ratio <= 1F
}
}
}
@@ -8,12 +8,16 @@
package com.cobblemon.mod.common.client.render.models.blockbench.pokemon
import com.cobblemon.mod.common.client.MountedPokemonAnimationRenderController
import com.cobblemon.mod.common.client.entity.PokemonClientDelegate
import com.cobblemon.mod.common.client.render.models.blockbench.PosableEntityModel
import com.cobblemon.mod.common.client.render.models.blockbench.PosableState
import com.cobblemon.mod.common.client.render.models.blockbench.repository.RenderContext
import com.cobblemon.mod.common.client.render.models.blockbench.repository.VaryingModelRepository
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import net.minecraft.util.Mth
import net.minecraft.world.entity.Entity
import net.minecraft.world.phys.Vec3
class PosablePokemonEntityModel : PosableEntityModel<PokemonEntity>() {
override fun setupEntityTypeContext(entity: Entity?) {
@@ -26,4 +30,28 @@ class PosablePokemonEntityModel : PosableEntityModel<PokemonEntity>() {
.let { texture -> context.put(RenderContext.TEXTURE, texture) }
}
}
// TODO: This is a bit of a hack, but it works for now. We should find a better way to do this.
override fun getTicksForAnimation(
entity: PokemonEntity,
limbSwing: Float,
limbSwingAmount: Float,
ageInTicks: Float,
headYaw: Float,
headPitch: Float
): Float {
val delegate = entity.delegate as? PosableState
val forcedTick = MountedPokemonAnimationRenderController.getPartialTick(entity)
if (entity.passengers.isEmpty() || delegate == null || forcedTick == null) {
return ageInTicks
}
delegate.updatePartialTicks(forcedTick)
val entityPos = Vec3(
Mth.lerp(forcedTick.toDouble(), entity.xOld, entity.x),
Mth.lerp(forcedTick.toDouble(), entity.yOld, entity.y),
Mth.lerp(forcedTick.toDouble(), entity.zOld, entity.z)
)
delegate.updateLocatorPosition(entityPos)
return forcedTick
}
}
@@ -1,98 +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.client.render.models.blockbench.pokemon.gen1
import com.cobblemon.mod.common.client.render.models.blockbench.createTransformation
import com.cobblemon.mod.common.client.render.models.blockbench.frame.HeadedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.frame.QuadrupedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.CryProvider
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.PokemonPosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.pose.ModelPartTransformation.Companion.X_AXIS
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Pose
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.PoseType.Companion.MOVING_POSES
import com.cobblemon.mod.common.entity.PoseType.Companion.STATIONARY_POSES
import com.cobblemon.mod.common.entity.PoseType.Companion.UI_POSES
import net.minecraft.client.model.geom.ModelPart
import net.minecraft.world.phys.Vec3
class EeveeModel(root: ModelPart) : PokemonPosableModel(root), HeadedFrame, QuadrupedFrame {
override val rootPart = root.registerChildWithAllChildren("eevee")
val body = getPart("body")
override val head = getPart("head")
override val hindRightLeg = getPart("leg_back_right")
override val hindLeftLeg = getPart("leg_back_left")
override val foreRightLeg = getPart("leg_front_right")
override val foreLeftLeg= getPart("leg_front_left")
override var portraitScale = 2.2F
override var portraitTranslation = Vec3(-0.4, -0.54, 0.0)
override var profileScale = 0.8F
override var profileTranslation = Vec3(0.0, 0.55, 0.0)
lateinit var stand: Pose
lateinit var walk: Pose
lateinit var shoulderLeft: Pose
lateinit var shoulderRight: Pose
override val cryAnimation = CryProvider { bedrockStateful("eevee", "cry") }
val shoulderOffset = 4
override fun registerPoses() {
val blink = quirk { bedrockStateful("eevee", "blink") }
stand = registerPose(
poseName = "standing",
poseTypes = STATIONARY_POSES + UI_POSES,
transformTicks = 10,
quirks = arrayOf(blink),
animations = arrayOf(
singleBoneLook(),
bedrock("eevee", "ground_idle")
)
)
walk = registerPose(
poseName = "walk",
poseTypes = MOVING_POSES,
transformTicks = 10,
quirks = arrayOf(blink),
animations = arrayOf(
singleBoneLook(),
bedrock("eevee", "ground_walk")
)
)
shoulderLeft = registerPose(
poseType = PoseType.SHOULDER_LEFT,
quirks = arrayOf(blink),
animations = arrayOf(
singleBoneLook(),
bedrock("eevee", "ground_idle")
),
transformedParts = arrayOf(
rootPart.createTransformation().addPosition(X_AXIS, shoulderOffset)
)
)
shoulderRight = registerPose(
poseType = PoseType.SHOULDER_RIGHT,
quirks = arrayOf(blink),
animations = arrayOf(
singleBoneLook(),
bedrock("eevee", "ground_idle")
),
transformedParts = arrayOf(
rootPart.createTransformation().addPosition(X_AXIS, -shoulderOffset)
)
)
}
}
@@ -1,62 +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.client.render.models.blockbench.pokemon.gen1
import com.cobblemon.mod.common.client.render.models.blockbench.frame.HeadedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.CryProvider
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.PokemonPosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Pose
import com.cobblemon.mod.common.entity.PoseType.Companion.MOVING_POSES
import com.cobblemon.mod.common.entity.PoseType.Companion.STATIONARY_POSES
import com.cobblemon.mod.common.entity.PoseType.Companion.UI_POSES
import net.minecraft.client.model.geom.ModelPart
import net.minecraft.world.phys.Vec3
class FlareonModel(root: ModelPart) : PokemonPosableModel(root), HeadedFrame {
override val rootPart = root.registerChildWithAllChildren("flareon")
override val head = getPart("head")
override var portraitScale = 2.2F
override var portraitTranslation = Vec3(-0.6, -0.45, 0.0)
override var profileScale = 0.9F
override var profileTranslation = Vec3(0.0, 0.36, 0.0)
lateinit var standing: Pose
lateinit var walk: Pose
override val cryAnimation = CryProvider { bedrockStateful("flareon", "cry") }
override fun registerPoses() {
standing = registerPose(
poseName = "standing",
poseTypes = STATIONARY_POSES + UI_POSES,
transformTicks = 10,
animations = arrayOf(
singleBoneLook(),
bedrock("flareon", "ground_idle")
)
)
walk = registerPose(
poseName = "walk",
poseTypes = MOVING_POSES,
transformTicks = 10,
animations = arrayOf(
singleBoneLook(),
bedrock("flareon", "ground_run")
)
)
}
// override fun getFaintAnimation(
// pokemonEntity: PokemonEntity,
// state: PosableState<PokemonEntity>
// ) = if (state.isPosedIn(standing, walk)) bedrockStateful("flareon", "faint") else null
}
@@ -1,62 +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.client.render.models.blockbench.pokemon.gen1
import com.cobblemon.mod.common.client.render.models.blockbench.frame.HeadedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.CryProvider
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.PokemonPosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Pose
import com.cobblemon.mod.common.entity.PoseType.Companion.MOVING_POSES
import com.cobblemon.mod.common.entity.PoseType.Companion.STATIONARY_POSES
import com.cobblemon.mod.common.entity.PoseType.Companion.UI_POSES
import net.minecraft.client.model.geom.ModelPart
import net.minecraft.world.phys.Vec3
class JolteonModel(root: ModelPart) : PokemonPosableModel(root), HeadedFrame {
override val rootPart = root.registerChildWithAllChildren("jolteon")
override val head = getPart("head")
override var portraitScale = 2.3F
override var portraitTranslation = Vec3(-0.55, -0.6, 0.0)
override var profileScale = 1.0F
override var profileTranslation = Vec3(0.0, 0.2, 0.0)
lateinit var standing: Pose
lateinit var walk: Pose
override val cryAnimation = CryProvider { bedrockStateful("jolteon", "cry") }
override fun registerPoses() {
standing = registerPose(
poseName = "standing",
poseTypes = STATIONARY_POSES + UI_POSES,
transformTicks = 10,
animations = arrayOf(
singleBoneLook(),
bedrock("jolteon", "ground_idle")
)
)
walk = registerPose(
poseName = "walk",
poseTypes = MOVING_POSES,
transformTicks = 10,
animations = arrayOf(
singleBoneLook(),
bedrock("jolteon", "ground_run")
)
)
}
// override fun getFaintAnimation(
// pokemonEntity: PokemonEntity,
// state: PosableState<PokemonEntity>
// ) = if (state.isPosedIn(standing, walk)) bedrockStateful("jolteon", "faint") else null
}
@@ -1,129 +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.client.render.models.blockbench.pokemon.gen1
import com.cobblemon.mod.common.client.render.models.blockbench.animation.BipedWalkAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.createTransformation
import com.cobblemon.mod.common.client.render.models.blockbench.frame.BipedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.frame.HeadedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.CryProvider
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.PokemonPosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.pose.CobblemonPose
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.PoseType.Companion.UI_POSES
import net.minecraft.client.model.geom.ModelPart
import net.minecraft.world.phys.Vec3
class PidgeotModel(root: ModelPart) : PokemonPosableModel(root), HeadedFrame, BipedFrame {
override val rootPart = root.registerChildWithAllChildren("pidgeot")
override val leftLeg = getPart("leg_left")
override val rightLeg = getPart("leg_right")
override val head = getPart("neck")
private val wingOpenRight = getPart("wing_open_right")
private val wingOpenLeft = getPart("wing_open_left")
private val wingClosedRight = getPart("wing_closed_right")
private val wingClosedLeft = getPart("wing_closed_left")
override var portraitScale = 2.2F
override var portraitTranslation = Vec3(-0.6, 0.15, 0.0)
override var profileScale = 0.9F
override var profileTranslation = Vec3(0.0, 0.4, 0.0)
// lateinit var sleep: CobblemonPose
lateinit var stand: CobblemonPose
lateinit var walk: CobblemonPose
lateinit var hover: CobblemonPose
lateinit var fly: CobblemonPose
override val cryAnimation = CryProvider { bedrockStateful("pidgeot", "cry") }
override fun registerPoses() {
val blink = quirk { bedrockStateful("pidgeot", "blink")}
val flyQuirk1 = quirk { bedrockStateful("pidgeot", "air_fly_quirk") }
val flyQuirk2 = quirk { bedrockStateful("pidgeot", "air_fly_quirk2") }
// sleep = registerPose(
// poseName = "sleeping",
// transformedParts = arrayOf(
// wingClosedLeft.createTransformation().withVisibility(visibility = true),
// wingClosedRight.createTransformation().withVisibility(visibility = true),
// wingOpenLeft.createTransformation().withVisibility(visibility = false),
// wingOpenRight.createTransformation().withVisibility(visibility = false)
// ),
// poseType = PoseType.SLEEP,
// animations = arrayOf(bedrock("pidgeot", "sleep"))
// )
stand = registerPose(
poseName = "stand",
transformedParts = arrayOf(
wingClosedLeft.createTransformation().withVisibility(visibility = true),
wingClosedRight.createTransformation().withVisibility(visibility = true),
wingOpenLeft.createTransformation().withVisibility(visibility = false),
wingOpenRight.createTransformation().withVisibility(visibility = false)
),
poseTypes = PoseType.STATIONARY_POSES - PoseType.HOVER - PoseType.FLOAT + UI_POSES,
quirks = arrayOf(blink),
animations = arrayOf(
singleBoneLook(),
bedrock("pidgeot", "ground_idle_PLACEHOLDER"),
)
)
walk = registerPose(
poseName = "walk",
transformedParts = arrayOf(
wingClosedLeft.createTransformation().withVisibility(visibility = true),
wingClosedRight.createTransformation().withVisibility(visibility = true),
wingOpenLeft.createTransformation().withVisibility(visibility = false),
wingOpenRight.createTransformation().withVisibility(visibility = false)
),
poseTypes = PoseType.MOVING_POSES - PoseType.FLY - PoseType.SWIM,
quirks = arrayOf(blink),
animations = arrayOf(
singleBoneLook(),
bedrock("pidgeot", "ground_idle_PLACEHOLDER"),
BipedWalkAnimation(this)
)
)
hover = registerPose(
poseName = "floating",
transformedParts = arrayOf(
wingClosedLeft.createTransformation().withVisibility(visibility = false),
wingClosedRight.createTransformation().withVisibility(visibility = false),
wingOpenLeft.createTransformation().withVisibility(visibility = true),
wingOpenRight.createTransformation().withVisibility(visibility = true)
),
poseTypes = setOf(PoseType.FLOAT, PoseType.HOVER),
quirks = arrayOf(blink),
animations = arrayOf(
singleBoneLook(),
bedrock("pidgeot", "air_idle")
)
)
fly = registerPose(
poseName = "flying",
transformedParts = arrayOf(
wingClosedLeft.createTransformation().withVisibility(visibility = false),
wingClosedRight.createTransformation().withVisibility(visibility = false),
wingOpenLeft.createTransformation().withVisibility(visibility = true),
wingOpenRight.createTransformation().withVisibility(visibility = true)
),
poseTypes = setOf(PoseType.FLY, PoseType.SWIM),
quirks = arrayOf(blink, flyQuirk1, flyQuirk2),
animations = arrayOf(
singleBoneLook(),
bedrock("pidgeot", "air_fly")
)
)
}
}
@@ -1,84 +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.client.render.models.blockbench.pokemon.gen1
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.PokemonPosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.frame.HeadedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.CryProvider
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Pose
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.PoseType.Companion.STATIONARY_POSES
import com.cobblemon.mod.common.entity.PoseType.Companion.UI_POSES
import net.minecraft.client.model.geom.ModelPart
import net.minecraft.world.phys.Vec3
class VaporeonModel(root: ModelPart) : PokemonPosableModel(root), HeadedFrame {
override val rootPart = root.registerChildWithAllChildren("vaporeon")
override val head = getPart("head")
override var portraitScale = 2.2F
override var portraitTranslation = Vec3(-0.6, -0.58, 0.0)
override var profileScale = 0.9F
override var profileTranslation = Vec3(0.0, 0.35, 0.0)
lateinit var standing: Pose
lateinit var walk: Pose
lateinit var swimIdle: Pose
lateinit var swimMove: Pose
override val cryAnimation = CryProvider { bedrockStateful("vaporeon", "cry") }
override fun registerPoses() {
standing = registerPose(
poseName = "standing",
poseTypes = STATIONARY_POSES + UI_POSES - PoseType.FLOAT,
transformTicks = 10,
animations = arrayOf(
singleBoneLook(),
bedrock("vaporeon", "ground_idle")
)
)
walk = registerPose(
poseName = "walk",
poseTypes = setOf(PoseType.WALK),
transformTicks = 10,
animations = arrayOf(
singleBoneLook(),
bedrock("vaporeon", "ground_run")
)
)
swimIdle = registerPose(
poseName = "float",
poseTypes = setOf(PoseType.FLOAT),
transformTicks = 10,
animations = arrayOf(
singleBoneLook(),
bedrock("vaporeon", "water_idle")
)
)
swimMove = registerPose(
poseName = "swim",
poseTypes = setOf(PoseType.SWIM),
transformTicks = 10,
animations = arrayOf(
singleBoneLook(),
bedrock("vaporeon", "water_swim")
)
)
}
// override fun getFaintAnimation(
// pokemonEntity: PokemonEntity,
// state: PosableState<PokemonEntity>
// ) = if (state.isPosedIn(standing, walk)) bedrockStateful("vaporeon", "faint") else null
}
@@ -1,65 +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.client.render.models.blockbench.pokemon.gen2
import com.cobblemon.mod.common.client.render.models.blockbench.frame.HeadedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.CryProvider
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.PokemonPosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Pose
import com.cobblemon.mod.common.entity.PoseType.Companion.MOVING_POSES
import com.cobblemon.mod.common.entity.PoseType.Companion.STATIONARY_POSES
import com.cobblemon.mod.common.entity.PoseType.Companion.UI_POSES
import net.minecraft.client.model.geom.ModelPart
import net.minecraft.world.phys.Vec3
class EspeonModel(root: ModelPart) : PokemonPosableModel(root), HeadedFrame {
override val rootPart = root.registerChildWithAllChildren("espeon")
override val head = getPart("head")
override var portraitScale = 2.15F
override var portraitTranslation = Vec3(-0.55, -0.43, 0.0)
override var profileScale = 0.81F
override var profileTranslation = Vec3(0.07, 0.59, 0.0)
lateinit var standing: Pose
lateinit var walk: Pose
override val cryAnimation = CryProvider { bedrockStateful("espeon", "cry") }
override fun registerPoses() {
val blink = quirk { bedrockStateful("espeon", "blink")}
standing = registerPose(
poseName = "standing",
poseTypes = STATIONARY_POSES + UI_POSES,
transformTicks = 10,
quirks = arrayOf(blink),
animations = arrayOf(
singleBoneLook(),
bedrock("espeon", "ground_idle")
)
)
walk = registerPose(
poseName = "walk",
poseTypes = MOVING_POSES,
transformTicks = 10,
quirks = arrayOf(blink),
animations = arrayOf(
singleBoneLook(),
bedrock("espeon", "ground_run")
)
)
}
// override fun getFaintAnimation(
// pokemonEntity: PokemonEntity,
// state: PosableState<PokemonEntity>
// ) = if (state.isPosedIn(standing, walk)) bedrockStateful("espeon", "faint") else null
}
@@ -1,65 +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.client.render.models.blockbench.pokemon.gen2
import com.cobblemon.mod.common.client.render.models.blockbench.frame.HeadedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.CryProvider
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.PokemonPosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Pose
import com.cobblemon.mod.common.entity.PoseType.Companion.MOVING_POSES
import com.cobblemon.mod.common.entity.PoseType.Companion.STATIONARY_POSES
import com.cobblemon.mod.common.entity.PoseType.Companion.UI_POSES
import net.minecraft.client.model.geom.ModelPart
import net.minecraft.world.phys.Vec3
class UmbreonModel(root: ModelPart) : PokemonPosableModel(root), HeadedFrame {
override val rootPart = root.registerChildWithAllChildren("umbreon")
override val head = getPart("head")
override var portraitScale = 2.2F
override var portraitTranslation = Vec3(-0.6, -0.45, 0.0)
override var profileScale = 0.9F
override var profileTranslation = Vec3(0.0, 0.35, 0.0)
lateinit var standing: Pose
lateinit var walk: Pose
override val cryAnimation = CryProvider { bedrockStateful("umbreon", "cry") }
override fun registerPoses() {
val blink = quirk { bedrockStateful("umbreon", "blink")}
standing = registerPose(
poseName = "standing",
poseTypes = STATIONARY_POSES + UI_POSES,
transformTicks = 10,
quirks = arrayOf(blink),
animations = arrayOf(
singleBoneLook(),
bedrock("umbreon", "ground_idle")
)
)
walk = registerPose(
poseName = "walk",
poseTypes = MOVING_POSES,
transformTicks = 10,
quirks = arrayOf(blink),
animations = arrayOf(
singleBoneLook(),
bedrock("umbreon", "ground_run")
)
)
}
// override fun getFaintAnimation(
// pokemonEntity: PokemonEntity,
// state: PosableState<PokemonEntity>
// ) = if (state.isPosedIn(standing, walk)) bedrockStateful("umbreon", "faint") else null
}
@@ -1,72 +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.client.render.models.blockbench.pokemon.gen3
import com.cobblemon.mod.common.client.render.models.blockbench.animation.BipedWalkAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.frame.BipedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.frame.HeadedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.CryProvider
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.PokemonPosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.pose.CobblemonPose
import com.cobblemon.mod.common.entity.PoseType
import net.minecraft.client.model.geom.ModelPart
import net.minecraft.world.phys.Vec3
class GrovyleModel (root: ModelPart) : PokemonPosableModel(root), HeadedFrame, BipedFrame {
override val rootPart = root.registerChildWithAllChildren("grovyle")
override val head = getPart("head")
override val leftLeg = getPart("left_upper_leg")
override val rightLeg = getPart("right_upper_leg")
override var portraitScale = 2.31F
override var portraitTranslation = Vec3(-0.24, 1.29, 0.0)
override var profileScale = 0.66F
override var profileTranslation = Vec3(0.01, 0.87, 0.0)
//lateinit var sleep: CobblemonPose
lateinit var standing: CobblemonPose
lateinit var walk: CobblemonPose
override val cryAnimation = CryProvider { bedrockStateful("grovyle", "cry") }
override fun registerPoses() {
//sleep = registerPose(
// poseType = PoseType.SLEEP,
// animations = arrayOf(bedrock("grovyle", "sleep"))
//)
val blink = quirk { bedrockStateful("grovyle", "blink") }
standing = registerPose(
poseName = "standing",
poseTypes = PoseType.STATIONARY_POSES + PoseType.UI_POSES,
quirks = arrayOf(blink),
animations = arrayOf(
singleBoneLook(),
bedrock("grovyle", "ground_idle")
)
)
walk = registerPose(
poseName = "walk",
poseTypes = PoseType.MOVING_POSES,
quirks = arrayOf(blink),
animations = arrayOf(
singleBoneLook(),
bedrock("grovyle", "ground_idle"),
BipedWalkAnimation(this, periodMultiplier = 0.6F, amplitudeMultiplier = 0.9F)
)
)
}
//override fun getFaintAnimation(
// pokemonEntity: PokemonEntity,
// state: PosableState<PokemonEntity>
//) = if (state.isNotPosedIn(sleep)) bedrockStateful("grovyle", "faint") else null
}
@@ -1,95 +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.client.render.models.blockbench.pokemon.gen3
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.PokemonPosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.CryProvider
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Pose
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.util.isBattling
import net.minecraft.client.model.geom.ModelPart
import net.minecraft.world.phys.Vec3
class MetagrossModel (root: ModelPart) : PokemonPosableModel(root) {
override val rootPart = root.registerChildWithAllChildren("metagross")
override var portraitScale = 1.1F
override var portraitTranslation = Vec3(-0.45, 0.5, 0.0)
override var profileScale = 0.4F
override var profileTranslation = Vec3(0.0, 1.0, 0.0)
lateinit var standing: Pose
lateinit var walk: Pose
lateinit var hover: Pose
lateinit var fly: Pose
lateinit var sleep: Pose
lateinit var battleidle: Pose
override val cryAnimation = CryProvider { bedrockStateful("metagross", "cry") }
override fun registerPoses() {
val blink = quirk { bedrockStateful("metagross", "blink")}
sleep = registerPose(
poseType = PoseType.SLEEP,
animations = arrayOf(bedrock("metagross", "sleep"))
)
standing = registerPose(
poseName = "stand",
poseTypes = PoseType.STATIONARY_POSES - PoseType.HOVER - PoseType.FLOAT + PoseType.UI_POSES,
condition = { !it.isBattling },
transformTicks = 10,
quirks = arrayOf(blink),
animations = arrayOf(
bedrock("metagross", "ground_idle")
)
)
hover = registerPose(
poseName = "hover",
poseTypes = setOf(PoseType.HOVER, PoseType.FLOAT),
quirks = arrayOf(blink),
animations = arrayOf(
bedrock("metagross", "air_idle")
)
)
fly = registerPose(
poseName = "fly",
poseTypes = setOf(PoseType.FLY, PoseType.SWIM),
quirks = arrayOf(blink),
animations = arrayOf(
bedrock("metagross", "air_fly")
)
)
walk = registerPose(
poseName = "walk",
poseTypes = PoseType.MOVING_POSES - PoseType.FLY - PoseType.SWIM,
transformTicks = 5,
quirks = arrayOf(blink),
animations = arrayOf(
bedrock("metagross", "ground_walk")
)
)
battleidle = registerPose(
poseName = "battle_idle",
poseTypes = PoseType.STATIONARY_POSES,
transformTicks = 10,
quirks = arrayOf(blink),
condition = { it.isBattling },
animations = arrayOf(
bedrock("metagross", "battle_idle")
)
)
}
// override fun getFaintAnimation(
// pokemonEntity: PokemonEntity,
// state: PosableState<PokemonEntity>
// ) = if (state.isPosedIn(standing, walk)) bedrockStateful("metagross", "faint") else null
}

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