Compare commits

...
Author SHA1 Message Date
JPAKx4 8c1d30c039 Charizard third person is now centered 2025-11-11 13:38:46 -06:00
Nick be466d1610 Merge branch 'aprijuice-neoforge-crash-fix' into 'main'
Fix Aprijuices crashing on Neoforge

See merge request cable-mc/cobblemon!1957
2025-11-11 11:18:42 -08:00
lionandNick d75faae1a0 Fix Aprijuices crashing on Neoforge 2025-11-11 11:18:42 -08:00
Polymeta 202bee5690 Merge branch 'do-you-get-the-joke' into 'main'
🍆

See merge request cable-mc/cobblemon!1777
2025-11-11 17:03:40 +00:00
Landon 730dc88ede Merge branch 'small-rides-sprint-issue' into 'main'
fix the stuttery walk on large mons when ridden

See merge request cable-mc/cobblemon!1953
2025-11-11 15:17:21 +00:00
Landon 4f0a667f23 Merge branch 'riding-air-transition-improvement' into 'main'
Improve air transition so you can consistently get off the ground

See merge request cable-mc/cobblemon!1954
2025-11-11 15:17:01 +00:00
Landon f1186f469c Merge branch 'motion-sick-camera-changes' into 'main'
Motion Sick Cam

See merge request cable-mc/cobblemon!1948
2025-11-11 15:11:45 +00:00
Jackson BowesandLandon 91eeecf6e4 Motion Sick Cam 2025-11-11 15:11:45 +00:00
Polymeta f6cae9d52e Edit eggantberry.js 2025-11-11 15:08:04 +00:00
Polymeta ed94d76e98 Merge branch '1.7-spawns' into 'main'
Added and updated spawns and tags. Edited berry data scraper.

See merge request cable-mc/cobblemon!1937
2025-11-11 15:04:46 +00:00
Polymeta 6e2b76d6a1 Merge branch 'its-not-bait' into 'main'
Bait Seasoning Correction

See merge request cable-mc/cobblemon!1955
2025-11-11 15:03:28 +00:00
Landon 2b70b222df Merge branch 'non-rolling-block-placement-fix' into 'main'
Fix non rollables arrow spawning and block picking. (building should be much better now)

See merge request cable-mc/cobblemon!1943
2025-11-11 14:51:18 +00:00
Landon e9f0ef6497 Merge branch 'camera-fixes' into 'main'
Camera fixes

See merge request cable-mc/cobblemon!1944
2025-11-11 14:41:24 +00:00
dhi_holo a4dd34235a Bait Seasonings are now also Baits
Added Apple, Gapple, EGapples, Golden Carrots, and Glistering Melon Slices to the bait types
2025-11-11 15:37:08 +10:00
dhi_holo 8501fd3b18 Delete nether_star.json
Dev tester or something?
2025-11-11 14:36:19 +10:00
dhi_holo 52896b208e Bait Seasoning Correction
You can no longer cook Nether Stars into food, but you can Apples!
2025-11-11 14:28:43 +10:00
jackowes 6ad6487dbe Gave a bit more time to double jump and now boost the y velocity to a minimum of 0.4 on transition. 2025-11-10 21:30:37 -06:00
jackowes 703ccb9d8e revert uneeded stuff 2025-11-10 19:34:53 -06:00
jackowes c33d4b591e fix the stuttery walk on large mons when ridden. The ridevelocity keeps going to zero at some point and I have no idea why. 2025-11-10 18:32:55 -06:00
Landon e31f2a2598 Merge branch 'small-rides-sprint-issue' into 'main'
Fix small mounts erratically stopping their sprints

See merge request cable-mc/cobblemon!1952
2025-11-10 23:37:03 +00:00
jackowes 513c958da1 Move sprint logic to custom tracking rather than using player sprint flag (odd interactions with short mounts) 2025-11-10 15:47:39 -06:00
Polymeta 9e1fde04ef Merge branch 'mopping-up-tags' into 'main'
Tags and Seasonings Improvement

Closes #1765 and #1764

See merge request cable-mc/cobblemon!1949
2025-11-10 09:30:10 +00:00
dhi_holo 6e4565d49b Update eggant_sprout.json
Missed a spot that didn't show up properly
2025-11-10 16:10:13 +10:00
dhi_holo 91f1523ef5 Update eggant_sprout.json
apparently the model didn't get updated properly
2025-11-10 15:59:09 +10:00
dhi_holo e949626f06 Update eggant_sprout.png
Mipmap Fix Compat
2025-11-10 15:26:32 +10:00
dhi_holo 9678fcc009 Merge branch 'main' into do-you-get-the-joke 2025-11-10 15:24:38 +10:00
dhi_holo 4fbe3e406d Merge branch 'main' into mopping-up-tags 2025-11-10 15:22:21 +10:00
dhi_holo 00a1e77a9f Update pitcher_plant.json
Buffed it
2025-11-10 15:20:28 +10:00
dhi_holo e055dede73 Number fix
Forgot it's in ticks not seconds.
2025-11-10 15:14:14 +10:00
Test 98cfa0ac91 Merge branch 'mip-map-fix' into 'main'
Mip map fix

See merge request cable-mc/cobblemon!1707
2025-11-10 03:47:48 +00:00
DeltricandTest f2441e7b27 Mip map fix 2025-11-10 03:47:48 +00:00
dhi_holo 46c247c742 Merge branch 'main' into mopping-up-tags 2025-11-10 13:28:49 +10:00
Paul 3d99db64b9 Double the spawning speed for poke snack blocks 2025-11-10 12:39:10 +10:00
Paul 4f07746355 Fix poor failure handling in behaviours on NPCs. Closes #1772 2025-11-10 11:58:00 +10:00
Hiroku 2f0a201cf1 Merge branch 'aprijuice-color-crash' into 'main'
Aprijuice Color Crash

See merge request cable-mc/cobblemon!1946
2025-11-09 22:46:23 +00:00
Gertimoshka 778f24acf1 Fixed a bug where if there is no flavours in aprijuice, it would crash when rendered 2025-11-09 22:32:07 +02:00
jackowes aa9c7b4f55 Fix non rollables arrow spawning and block picking. (building should be much better now) 2025-11-09 10:39:13 -06:00
JPAKx4 96e2e4a6fe Merge remote-tracking branch 'origin/camera-fixes' into pivot
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/client/render/camera/MountedCameraRenderer.kt
2025-11-09 06:58:34 -06:00
JPAKx4 20d903cffe Fix some camera code that got yeeted in the cleanup. 2025-11-09 06:57:33 -06:00
JPAKx4 d4383fa317 Fix some camera code that got yeeted in the cleanup. 2025-11-09 06:47:41 -06:00
Paul 1687fa12db Changelog [skip-ci] 2025-11-09 22:11:19 +10:00
Hiroku cf599de339 Merge branch 'YetMorePathingImprovement' into 'main'
YetMorePathingImprovements

Closes #1754

See merge request cable-mc/cobblemon!1942
2025-11-09 12:09:38 +00:00
Hiroku c5f92c1ad4 Merge branch 'party-ui-overhaul' into 'main'
Implementation of Party Overlay UI Animations for different Events

See merge request cable-mc/cobblemon!1939
2025-11-09 12:08:38 +00:00
Paul 1d99da78e0 Updated the behaviour scraper 2025-11-09 21:35:33 +10:00
dhi_holo 050a2a6a4b Seasoning Fixes
Resolves #1765
Partially Addresses #1766
2025-11-09 15:25:02 +10:00
dhi_holo c80e7f4a51 Update leaves.json
Corrects IDs
Resolves #1764
2025-11-09 15:11:51 +10:00
dhi_holo f4fbdf8735 Block Tag Improvements
Added some overlooked pieces to certain tags
2025-11-09 15:03:43 +10:00
dhi_holo f2aa4141c0 Merge branch 'main' into do-you-get-the-joke 2025-11-09 14:04:39 +10:00
Landon aeded69b25 Merge branch 'implement_pokemon' into 'main'
Merge species updates

See merge request cable-mc/cobblemon!1940
2025-11-09 02:59:41 +00:00
dhi_holo 16b25a2e6e Tag Updates
Now matches to main
2025-11-09 12:57:52 +10:00
dhi_holo e66ebfb7a8 Merge branch 'main' into do-you-get-the-joke 2025-11-09 12:56:25 +10:00
JazzMcNade 70e79e6fe4 Fix a case in which trimpath would result in a mon pathing into a solid block. Fixed NPCs getting stuck on specific upward diagonal formations 2025-11-08 20:20:47 -06:00
Hiroku 797e0704e9 Merge branch 'new-aprijuice-point-formula' into 'main'
Aprijuice formula rework and don't allow poke bait to stack otherwise fishing combines baits and that's bad

See merge request cable-mc/cobblemon!1938
2025-11-09 00:24:56 +00:00
Carmendar 0b184a722b Cutiefly and Ribombee scale adjustments. 2025-11-08 18:17:35 -06:00
Carmendar 1a34c2942f Cutiefly line remodels and Charizard zfighting fix. 2025-11-08 17:29:16 -06:00
Carmendar 2a20398b42 Merge remote-tracking branch 'origin/implement_pokemon' into implement_pokemon 2025-11-08 15:25:42 -06:00
Carmendar 3931295086 Merge branch 'main' into implement_pokemon 2025-11-08 14:18:46 -06:00
violet 550e579190 e 2025-11-08 06:03:40 -05:00
violet e7d710e0a3 implemented poltchageist & sinistcha remodels (+ tweaks to polteageist) 2025-11-08 06:02:08 -05:00
violet acb86b1935 implemented dratini, dragonair, and dragonite remodels 2025-11-08 05:50:42 -05:00
Carmendar fd8e9b49b5 Adjusted some ride stats. 2025-11-08 03:07:34 -06:00
Landon Wills 1fdbee91df Run scraper 2025-11-08 03:10:01 -05:00
Gertimoshka bfab1d7070 Actually Fix the Merge Conflicts 2025-11-07 16:41:39 +02:00
Gertimoshka e61e1390cb Fix Merge Conflicts 2025-11-07 14:26:09 +00:00
Gertimoshka 18151ff2f0 Implemented Levelup Sounds 2025-11-07 16:04:17 +02:00
Paul d5b1dfa9da no wait don't do that 2025-11-07 22:34:26 +10:00
Paul 9f0ddf4dd3 Put the bucket multiplication back onto fishing since I think I forgor to put this back on 2025-11-07 22:07:41 +10:00
Paul ef3ea62ae0 Aprijuice formula rework and don't allow poke bait to stack otherwise fishing combines baits and that's bad 2025-11-07 21:46:20 +10:00
Polymeta 6b43bf9f48 Merge branch 'main' into '1.7-spawns'
# Conflicts:
#   common/src/main/resources/data/cobblemon/spawn_pool_world/0194_wooper.json
#   common/src/main/resources/data/cobblemon/tags/worldgen/biome/is_mirage_island.json
#   utilityscripts/cobblemon_spawn_csv_to_json.py
2025-11-07 11:13:38 +00:00
Veraxiel a4012895f3 Added and updated spawns and tags. Edited berry data scraper. 2025-11-07 04:32:20 -05:00
FrankTheFarmer 7f6a3e8e87 Retweaked Alolan Golem's profile because the profile's respect aspects again. Yippee 2025-11-07 19:06:56 +10:00
FrankTheFarmer 7a84ea723f Merge branch 'main' into implement_pokemon 2025-11-07 18:53:53 +10:00
FrankTheFarmer 2aad33ded5 Merge remote-tracking branch 'origin/implement_pokemon' into implement_pokemon 2025-11-07 18:45:53 +10:00
Carmendar 254c95fa15 Rocket Expression changes. 2025-11-07 02:14:07 -06:00
Carmendar 5aab7f67e1 Ride anims for Lapras and Glide/Dive anims for Salamence. - surfacewater poser tweaks for some pokemon. - Tyrantrum crash fix. 2025-11-07 02:12:46 -06:00
Polymeta ae4decacc7 Merge branch 'bottle-o-knowledge' into 'main'
Added Unlock Condition

See merge request cable-mc/cobblemon!1936
2025-11-07 07:01:59 +00:00
dhi_holo ae5a9be626 Added Unlock Condition
Elixirs, Ethers, Potions, Super Potions, and Hyper Potions now unlock from having the berry or Medicinal Brew, rather than just the berry
2025-11-07 16:11:11 +10:00
Landon 05faea4ec8 Merge branch 'no-riding-suffocation' into 'main'
Prevent suffocation when riding a pokemon

See merge request cable-mc/cobblemon!1935
2025-11-07 05:09:05 +00:00
Landon Wills 05edd34db6 Prevent suffocation when riding a pokemon 2025-11-06 10:54:39 -05:00
Landon c198f8fccd Merge branch 'implement_pokemon' into 'main'
A more polished world...

See merge request cable-mc/cobblemon!1930
2025-11-06 14:37:53 +00:00
Landon Wills 521f9aaafa Scrape files 2025-11-06 08:52:29 -05:00
Hiroku 8d38505993 Merge branch 'SaccTreeFixes' into 'main'
SaccTreeFixes

See merge request cable-mc/cobblemon!1932
2025-11-06 12:20:44 +00:00
Carmendar 7c5fab02a8 Merge remote-tracking branch 'origin/implement_pokemon' into implement_pokemon 2025-11-06 01:53:24 -06:00
Carmendar 7e5a267c89 Dolphins & Submarines ready for business. - Mime Jr shoulder mount anims. - Various seats and poser tweaks. 2025-11-06 01:52:51 -06:00
Landon 5b23b0cfbe Merge branch 'pondering-my-orb-and-not-crashing' into 'main'
Fix a crash with Ponder and likely others in CameraMixin

See merge request cable-mc/cobblemon!1933
2025-11-05 13:52:17 +00:00
Nadja Reitzenstein 778ceff183 Fix a crash with Ponder and likely others in CameraMixin
Our setRotation mixin assumes the camera to be attached to an entity. It's not always, and it's not for Ponder Scenes specifically.
2025-11-05 13:57:36 +01:00
Paul f32050c8b5 Added setter to player spawner mixin and opened PlayerSpawner 2025-11-05 21:40:09 +10:00
Hiroku 3522bac72a Merge branch 'smeargle-paint-me-like-one-of-your-french-mooshtanks' into 'main'
Smeargle paint me like one of your french mooshtanks

See merge request cable-mc/cobblemon!1746
2025-11-05 11:24:27 +00:00
Paul ecf037666f Merge remote-tracking branch 'weblate/lang-updates' 2025-11-05 21:13:23 +10:00
Watuneni iteiteandWeblate 618d062ca9 Translated using Weblate (Japanese)
Currently translated at 93.5% (8334 of 8908 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ja/
2025-11-05 11:13:04 +00:00
Paul 49841b40ec Merge remote-tracking branch 'weblate/lang-updates' 2025-11-05 21:10:49 +10:00
Paul 87141e1137 Variable naming cleanup 2025-11-05 21:09:18 +10:00
Paul 1f962d664f Removed inaccurate comment 2025-11-05 21:08:50 +10:00
Paul 09797f25b1 Added characteristic_rainbow molang function to pokemon behaviour object 2025-11-05 21:08:31 +10:00
Paul 3911413c69 Removed unused lang function 2025-11-05 21:08:09 +10:00
violet e075e60231 tea time 2025-11-05 05:35:57 -05:00
violet acdd98573b Merge remote-tracking branch 'origin/implement_pokemon' into implement_pokemon 2025-11-05 05:27:35 -05:00
Paul 12f3e7b78b Fix party renders not respecting aspects 2025-11-05 19:01:24 +10:00
violet a462fe5efc added raboot.json poser 2025-11-05 03:43:26 -05:00
Carmendar 2390662fc8 A few submarines and poser/anim tweaks for Dusknoir, Bastiodon and Magnezone. Added surface water ride sounds for Aqua Tauros. 2025-11-05 02:33:26 -06:00
Carmendar 1bd5d2372f Merge branch 'refs/heads/main' into implement_pokemon
# Conflicts:
#	common/src/main/resources/data/cobblemon/species/generation4/garchomp.json
2025-11-05 01:20:44 -06:00
Landon Wills 12f6bb55c2 Fix goof 2025-11-05 02:05:32 -05:00
Landon fdb37ba5e8 Merge branch 'dolphin-sub-noPuke' into 'main'
submarine, dolphin rework, projectile support, view vector fix, and motion sick cam

See merge request cable-mc/cobblemon!1929
2025-11-05 06:56:07 +00:00
Jackson BowesandLandon 7a99845d0f submarine, dolphin rework, projectile support, view vector fix, and motion sick cam 2025-11-05 06:56:06 +00:00
JazzMcNade 54d183c8d3 Merge branch 'main' into SaccTreeFixes 2025-11-04 23:08:44 -06:00
JazzMcNade 29713bbe1f Update SaccharineTreeFeature.kt
natural sacc trees have 20% chance to generate a hive.
post generation sacc trees with nearby flowers have 5% chance to generate a hive.
Hives now always generate adjacent to a log block.
No longer records flower positions.
2025-11-04 22:56:43 -06:00
Landon 247052ee67 Merge branch 'moonrise-compat' into 'main'
Retain moonrise compatibility for Cobblemon 1.7

See merge request cable-mc/cobblemon!1926
2025-11-05 03:11:05 +00:00
FrankTheFarmer 14b7e93965 Tweaked Geodude's look range + poser tweaks 2025-11-05 09:24:32 +10:00
FrankTheFarmer 85a4793a1e formatting and cry fix for Graveler and Golem posers 2025-11-05 09:23:32 +10:00
Veraxiel 29f6524c49 Fix to formatting in TimeRange.kt 2025-11-04 17:50:24 -05:00
Veraxiel 1e617a684b Minor edit to a time range in TimeRange.kt 2025-11-04 17:49:42 -05:00
Paul c9a0352a7c Nerfed aprijuice stat point gain, tweak saccharine tree top leaf gen 2025-11-05 06:40:51 +10:00
Gertimoshka 1a5808e5a6 Update Licenses 2025-11-04 21:57:53 +02:00
Gertimoshka f14d242907 Fix a crash in code for party overlay 2025-11-04 21:52:50 +02:00
Sterrezo 4998ee2c5b Added levelup sounds 2025-11-04 20:25:19 +01:00
violet 015402d54b Merge remote-tracking branch 'origin/implement_pokemon' into implement_pokemon 2025-11-04 05:24:23 -05:00
violet 6f697d2b9a solosis line poser tweaks 2025-11-04 05:23:29 -05:00
violet f18752d484 haxorus model + poser tweaks 2025-11-04 04:53:53 -05:00
violet 5876c02d44 unown animations 2025-11-04 04:28:56 -05:00
violet c890f6910c a lot of poser alterations 2025-11-04 04:27:24 -05:00
Paul d157536e1e party pokemon walk again woops 2025-11-04 18:54:29 +10:00
Carmendar dac2d9e96e Mimikyu animations & ride style changes. - Updated changelog 2025-11-04 01:59:01 -06:00
Landon Wills 5c71680db9 Run scraper 2025-11-04 00:26:13 -05:00
Carmendar 5f5c036f8d Merge branch 'main' into implement_pokemon 2025-11-03 23:14:21 -06:00
Carmendar 1f16c2d542 Updated dewgong anim file. 2025-11-03 23:12:50 -06:00
JazzMcNade 2da9dc50f7 Fixed creative duped beehives creating combees with duplicate UUIDs 2025-11-03 21:30:57 -06:00
Carmendar 092f9ef9f9 Updated Gyarados fly pose. 2025-11-03 20:03:18 -06:00
Landon Wills 2f12b05d43 Run scraper 2025-11-03 19:48:32 -05:00
Sterrezo 4299aff1bf Added water ride loop sounds and updated the wind loop 2025-11-03 16:57:05 +01:00
Paul fb90ccb86e Fixed dismount not working for non-owner passengers if they have no Pokemon 2025-11-03 22:08:12 +10:00
Paul c112f9615c Merge remote-tracking branch 'origin/main' 2025-11-03 21:42:57 +10:00
Paul fc882190fe /ride no longer works on seatless pokemon, closes #1750 2025-11-03 21:39:48 +10:00
Paul 15d7cd815d Only handle mount transition camera logic for the actual client player. Probably doesn't fix anything? 2025-11-03 21:23:35 +10:00
violet 6b5969fff6 Merge remote-tracking branch 'origin/implement_pokemon' into implement_pokemon 2025-11-03 03:33:13 -05:00
Carmendar 5053c633b7 Grafaiai paint/saliva colors 2025-11-03 02:22:33 -06:00
violet c4294697e6 implemented remodels for scorbunny, raboot, and cinderace 2025-11-03 03:16:37 -05:00
Carmendar dfa1c5e369 Orthworm sand_idle fix 2025-11-03 01:59:46 -06:00
Carmendar 51f3b4357d Implemented Remodels for Steelix and Shroodle line. 2025-11-03 01:59:08 -06:00
Carmendar 220a9741ae Implemented Mime Jr animations. 2025-11-03 01:58:15 -06:00
Carmendar 48a9446f73 Implemented the Vanillite line. 2025-11-03 01:56:52 -06:00
violet f1edfc453e minor adjustments to shuppet's poser 2025-11-03 01:18:40 -05:00
FrankTheFarmer 8d612ec395 Changelog of hatred and anguish 2025-11-03 15:01:03 +10:00
FrankTheFarmer 6d9daf3cf6 Stupid fish's mama 🔴⚪ 2025-11-03 15:00:26 +10:00
FrankTheFarmer c7ecfc74de Stupid fish 🔴🔵⚪ 2025-11-03 14:26:02 +10:00
Hiroku fefdfd877b Merge branch 'let-me-/ride' into 'main'
Fix /ride

Closes #1751

See merge request cable-mc/cobblemon!1927
2025-11-03 02:35:18 +00:00
Landon Wills 35fb170225 Fix /ride 2025-11-02 20:50:39 -05:00
Paul d2e405131f Fixed speculative cast in pokedex FOV multiplier. Closes #1756 2025-11-03 11:37:00 +10:00
Paul b6ec0205ef Fixed NPC gibber sounds playing even when text was skipped. Closes #1755 2025-11-03 11:36:20 +10:00
Paul e57a42e755 Fixed immobile pokemon walking to follow the player. Closes #1757 2025-11-03 11:25:46 +10:00
Carmendar f34a03c8d6 Implemented Carvanha and Sharpedo animations - removed surfacewater swimming from Charizard and Arcanine. 2025-11-02 17:28:22 -06:00
Carmendar 8b1599705f Merge remote-tracking branch 'origin/implement_pokemon' into implement_pokemon 2025-11-02 13:35:26 -06:00
Carmendar e7f6fc32c4 Implemented Arcanine Animations! 2025-11-02 13:34:39 -06:00
Landon Wills 5119617f00 Check for moonrise and disable conflicting mixin if loaded 2025-11-02 09:39:33 -05:00
Paul 6e84efb53c No Packed Up backpacks either. Your move, Monocle 2025-11-02 22:41:11 +10:00
Paul de3191b768 Some english improvements on new NPC Behaviours 2025-11-02 22:06:27 +10:00
Paul 76ec4b4d5a Merge remote-tracking branch 'origin/main' 2025-11-02 21:34:42 +10:00
Hiroku 25702ade61 Merge branch 'main' into 'main'
dialogue selection interaction and name tag display - done properly this time

See merge request cable-mc/cobblemon!1925
2025-11-02 11:34:19 +00:00
Paul c6da88d0b2 Improvements to GUI pokemon animation config options, pokebait effects, setup some berry gen stuff for vera 2025-11-02 21:32:18 +10:00
violet c4acc09fbd Merge remote-tracking branch 'origin/implement_pokemon' into implement_pokemon 2025-11-02 03:49:32 -05:00
violet 53b434906b altered various starter pokemon portraits, profiles, and scales 2025-11-02 03:49:22 -05:00
Jolihan b22a31909d Update file en_us.json 2025-11-02 08:09:19 +00:00
Jolihan cbbbacc215 Edit name_tag_display.json 2025-11-02 07:01:40 +00:00
Jolihan566 d22d81209d dialogue selection interaction and name tag display - done properly this time 2025-11-02 16:59:21 +10:00
Carmendar 89b989c46c Updated Changelog 2025-11-02 01:42:05 -05:00
Carmendar 714d51b26e Implemented Tadbulb and Bellibolt animations 2025-11-02 01:35:27 -05:00
Hiroku 8571d78894 Merge branch 'FixGreedyHoneyEaters' into 'main'
Fixed Greedy Honey Eaters

Closes #1725

See merge request cable-mc/cobblemon!1920
2025-11-02 05:54:22 +00:00
JazzMcNade 72c57fde39 Sacc tree spawned beenests are now populated with either Minecraft bees or combees 2025-11-01 21:58:15 -05:00
Carmendar fc5c479f31 two small fixes 2025-11-01 21:48:58 -05:00
Carmendar d3c54924be no more blinky bronzong 2025-11-01 20:57:25 -05:00
Paul 65c5e95ddf Fixed possible crash from bees putting honey in saccarine leaves 2025-11-02 11:56:27 +10:00
Carmendar fc1dec6b4b Shellos & Gastrodon cries implemented 2025-11-01 20:53:45 -05:00
Carmendar 7604af9308 Shellos & Gastrodon cries 2025-11-01 20:48:16 -05:00
Carmendar ab1e138ace Koffing and Weezing animations. 2025-11-01 19:25:38 -05:00
Carmendar 62ee94c71d Bronzong and Camerupt animations - More poser tweaks. 2025-11-01 19:06:26 -05:00
Nadja Reitzenstein 0fae95aabc Reorder smeargle's tail colours to allow handing them cosmetic dyes
Our aspect system has the limitation that you can not easily have two separate systems add mutually exclusive aspects correctly. This means that for cosmetics to work without much duplication, the characteristic rainbow has to set a different aspect with some (but much more limited) duplication in the assignment of textures.
2025-11-01 19:55:55 +01:00
Nadja Reitzenstein bbfd864b33 Clean up Rainbow Aspect provider 2025-11-01 18:43:31 +01:00
Nadja Reitzenstein b107d76762 Cosmetic Item dye affects smeargle tails now 2025-11-01 17:37:04 +01:00
Nadja Reitzenstein 8d4f10349c Use the easier to understand maxBy call 2025-11-01 17:25:23 +01:00
Nadja Reitzenstein f36787e67f Clean up Characteristic calculation
Also ensures characteristics are recalculated on IV updates.
2025-11-01 16:42:59 +01:00
Nadja Reitzenstein fc198c599e Update CHANGELOG.md 2025-11-01 16:23:12 +01:00
Nadja Reitzenstein 26f753880c Implement rotating a feature on lightning hit more sensibly 2025-11-01 16:23:12 +01:00
Nadja Reitzenstein a9ebb0b6f2 Merge thunderHit method for Pokemon entity 2025-11-01 16:23:12 +01:00
Nadja Reitzenstein a7158ee57a Rainbow aspected Smeargle 2025-11-01 16:23:12 +01:00
Nadja Reitzenstein 06c3fd3f09 Make lightning damage only not apply if there was an actual aspect change. 2025-11-01 16:23:12 +01:00
Nadja Reitzenstein 4e196e8a29 Generalise switching aspect behaviour on lightning hit 2025-11-01 16:23:12 +01:00
Nadja Reitzenstein 871f33f442 Make mooshtank switch colour on lightning hit 2025-11-01 16:23:12 +01:00
Polymeta 21c1f912a7 Merge branch 'typo' into 'main'
Update crops.json

See merge request cable-mc/cobblemon!1924
2025-11-01 10:16:37 +00:00
Carmendar a0d9ce12a6 Implemented animations for Mr. Mime and Relicanth - poser/anim tweaks for Dragonite. 2025-11-01 03:59:05 -05:00
Carmendar c07173fc0f Merge branch 'main' into implement_pokemon 2025-11-01 01:54:43 -05:00
violet 3faf8ac302 Merge remote-tracking branch 'origin/implement_pokemon' into implement_pokemon 2025-11-01 01:21:23 -04:00
violet e85b3f3cc5 implemented shuppet and banette 2025-11-01 01:19:52 -04:00
RedGenesectNinja 251a09f78c Implemented Stunfisk and Galarian Stunfisk 2025-10-31 23:43:46 -05:00
dhi_holo 776fb5413f Update crops.json
Apparently missed this file while updating stuff? I swear I didn't but it must not've commited
2025-11-01 14:36:45 +10:00
Paul f645fd9b60 Added support for species-specific move action effects 2025-11-01 11:44:05 +10:00
Landon ac161ae5b4 Merge branch 'orientation-refactor' into 'main'
Refactor orientation to better allow for passengers on rolling rides.

See merge request cable-mc/cobblemon!1910
2025-11-01 01:19:29 +00:00
Jackson BowesandLandon fc3542e3eb Refactor orientation to better allow for passengers on rolling rides. 2025-11-01 01:19:28 +00:00
Gertimoshka 7c44dd204e Remove Old Notifications of Level Up, EXP Gained, and Moves Learned 2025-10-31 15:13:53 +02:00
Hiroku 88932c8c41 Merge branch 'poke-snack-spawner' into 'main'
Makes Poké Snacks act as spawners

See merge request cable-mc/cobblemon!1915
2025-10-31 12:10:36 +00:00
lionandHiroku 411f98d0ae Makes Poké Snacks act as spawners 2025-10-31 12:10:36 +00:00
Polymeta 02a1929ede Merge branch 'fix-pick-block' into 'main'
Fix picking blocks while riding mounts

Closes #1698

See merge request cable-mc/cobblemon!1922
2025-10-31 12:00:14 +00:00
JordanandPolymeta ff90059eec Fix picking blocks while riding mounts 2025-10-31 12:00:13 +00:00
Carmendar eca14f61ac Various mount style changes - prep work for Submarine, and removed mountability from 5 pokemon. 2025-10-31 02:35:15 -05:00
Carmendar 9ccf2bb9ad Heracross anims, poser fixes, removed land mount style from select pokemon. 2025-10-31 02:33:30 -05:00
Kenji_64 eacb0fe587 Forgot the changelog 2025-10-30 13:27:23 +01:00
Kenji_64 1752588cbc New scales for Dreepy and Drakloak 2025-10-30 13:22:48 +01:00
Kenji_64 9e09ac52fc Added animations + ride stats to Dragapult! 2025-10-30 13:22:25 +01:00
Kenji_64 845ade1aeb Added cries to Alolan Vulpix and Ninetales 2025-10-30 11:28:53 +01:00
Carmendar f6bd04f016 Implemented Darmanitan, Gastrodon, Orthworm & Salamence anims - Switched Tentacruel to json poser - fixed minor camera and poser issues. 2025-10-30 02:41:42 -05:00
Landon Wills afa1d9af8b Update riding scraper, scrape pokemon 2025-10-30 03:15:02 -04:00
Landon Wills bf6db8b745 Merge branch 'main' into implement_pokemon 2025-10-30 00:58:03 -04:00
Gertimoshka 9aef7b9c3b Party Notifications Will Override and Stack if triggered too fast (feeding exp candy as an example) and now pokemon level text will change during animation instead instantly 2025-10-29 16:28:00 +02:00
Carmendar c5bebb1abb Hydreigon geo fix
(I goofed up)
2025-10-29 03:03:12 -05:00
Carmendar 990fed2fe0 The great Camera polishing event of '25. 2025-10-29 02:51:02 -05:00
Polymeta 4a30bb3b04 Merge branch 'seasoning-filters' into 'main'
Cooking Pot Recipes now have filtered

See merge request cable-mc/cobblemon!1919
2025-10-29 07:45:59 +00:00
Carmendar fe77a3cd30 Implemented new Charizard, Staraptor, Dusknoir & Forretress animations, fixed Altaria not using some anims, adjusted floating seat locators.
Also fixed Will O' Wisp particle.
2025-10-29 02:34:02 -05:00
Gertimoshka de9e1949fe Do actually implement all of the animations for party overlay level up 2025-10-29 08:06:14 +02:00
Polymeta fac8c5f510 Merge branch 'party-overlay-config-fix' into 'main'
Party overlay config fix

See merge request cable-mc/cobblemon!1921
2025-10-28 14:41:22 +00:00
lionandPolymeta 2d077c607a Party overlay config fix 2025-10-28 14:41:21 +00:00
Nico MerenqandWeblate f75e3d11da Translated using Weblate (Russian)
Currently translated at 63.6% (5669 of 8908 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ru/
2025-10-28 08:50:20 +00:00
Gertimoshka f46968f878 Remove Unnecesary Comments 2025-10-28 07:20:54 +02:00
Nico MerenqandWeblate 3439aeff1e Translated using Weblate (Russian)
Currently translated at 63.5% (5662 of 8908 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ru/
2025-10-27 21:50:57 +00:00
Nico MerenqandWeblate 2ada7e0b68 Translated using Weblate (Russian)
Currently translated at 63.5% (5659 of 8908 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ru/
2025-10-26 06:57:00 +00:00
Polymeta b8f73f277d Fix PokeRodFishingBobberEntity baitId detection for ReelInPokemonCriterionCondition
Fixes #1744

Signed-off-by: Polymeta <40173118+Polymeta@users.noreply.github.com>
2025-10-25 21:40:17 +01:00
JazzMcNade 7f6197ab8b Honey slathered logs no longer activated multiple time on the same pokemon, and ignore pokemon that already have their hidden ability. 2025-10-25 14:52:51 -05:00
Polymeta 6df2b996e2 Merge branch 'smarter-scalable-brains' into 'main'
New tools for brain activity management

See merge request cable-mc/cobblemon!1916
2025-10-25 19:39:15 +00:00
Polymeta ddc4bd358d Merge branch 'niche-tags' into 'main'
Berry Tag Updates

See merge request cable-mc/cobblemon!1918
2025-10-25 19:34:59 +00:00
Gertimoshka e197287775 Fixed preview item still using seasoning items even if its invalid by tag 2025-10-25 22:04:47 +03:00
dhi_holo be019c79df Update poke_cake.json
Sadly, Poke Cakes do not get tinted by non-flavour colours, so despite my attempt to allow it, it just doesn't work
2025-10-25 20:45:47 +10:00
dhi_holo 73fe851c58 Merge branch 'seasoning-filters' of https://gitlab.com/cable-mc/cobblemon into seasoning-filters 2025-10-25 20:20:09 +10:00
dhi_holo 8544744530 Tags in Recipes 2025-10-25 20:19:37 +10:00
Gertimoshka 32d7bfcef3 Recipes no longer need to define seasoning tag and will assume it as cobblemon:empty 2025-10-25 12:53:25 +03:00
Gertimoshka 27263cd057 JEI is now filtering seasoning slots additionally based berries 2025-10-25 12:22:08 +03:00
Gertimoshka 55f0962205 Recipes will now not consume seasoning items that are not in seasoning filter of recipe and slots will highlight red if seasoning item does not apply to current recipe 2025-10-25 12:02:22 +03:00
Carmendar 63143832e0 Chinchilla implementation. 2025-10-25 02:53:59 -05:00
Carmendar 29215f5c0c Merge branch 'refs/heads/main' into implement_pokemon 2025-10-25 02:48:22 -05:00
dhi_holo 70bf3e7805 Merge branch 'main' into do-you-get-the-joke 2025-10-25 17:12:40 +10:00
dhi_holo 40c5df46ce Berry Tag Updates
Reformatted how the tag is handled to be a bit cleaner, and allow for more specific berry querying via searching, mod compat, etc
2025-10-25 17:08:06 +10:00
Gertimoshka b4d9d1b963 First working itteration of Popups and EXP Bar animations for level up, new moves and new evos 2025-10-25 09:42:26 +03:00
dhi_holo e753c75c31 Merge branch 'seasoning-filters' of https://gitlab.com/cable-mc/cobblemon into seasoning-filters 2025-10-25 15:52:35 +10:00
dhi_holo 4e96222a4d Tags
Renamed a tag and fixed errors it caused, as well as adding the current 6 Filter tags requested for system upgrades
2025-10-25 15:52:09 +10:00
Paul 4ee90d4cfb Removed unneeded comment 2025-10-25 15:22:29 +10:00
Paul 540cd612c1 Added seasoning tag filter to cooking pot recipes 2025-10-25 14:22:47 +10:00
Hiroku 1b1d22fdf3 Merge branch 'social-distancing' into 'main'
Adds random offset to follow herd leader task

See merge request cable-mc/cobblemon!1917
2025-10-25 01:49:48 +00:00
lionandHiroku 1ee1001774 Adds random offset to follow herd leader task 2025-10-25 01:49:48 +00:00
Paul 533272ef09 u got a loicense for that error? 2025-10-24 20:27:31 +10:00
Carmendar cf5e1bf142 ride stat adjustments and Blastoise anim implementation. 2025-10-24 02:20:43 -05:00
Nico MerenqandWeblate 4b8196685a Translated using Weblate (Russian)
Currently translated at 63.4% (5653 of 8908 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ru/
2025-10-24 04:12:32 +00:00
Carmendar d1053468bf Adjusted first person mount view for the paldean tauros variants. 2025-10-23 19:22:54 -05:00
Carmendar f0c41865db Adjusted first person mount view for most mons. 2025-10-23 19:19:36 -05:00
Carmendar 2b2264bdca Updated Boat expressions 2: Electric Boogaloo 2025-10-22 19:13:09 -05:00
Carmendar 1e32f7b80a Updated Boat expressions 2025-10-22 17:19:52 -05:00
Carmendar cfc8edfa9e Ride stats. 2025-10-22 01:40:12 -05:00
Carmendar 417b49beda Hover speed expression increase and updated Metagross Poser. 2025-10-22 01:21:41 -05:00
FrankTheFarmer a2f6a35227 Changelog 2025-10-22 11:11:02 +10:00
FrankTheFarmer b9f1cc5883 Added handmade placeholders for ❤Corphish💖 and 🤷‍♂️Crawdaunt🦞 2025-10-22 11:07:02 +10:00
FrankTheFarmer ad132fe729 Made Corphish and Crawdaunt sink and walk in water 2025-10-22 11:03:29 +10:00
FrankTheFarmer f33892fcd1 Merge branch 'main' into implement_pokemon 2025-10-22 11:01:54 +10:00
Landon 91300e4d03 Merge branch '1.7-spawns' into 'main'
Some JSON tweaks

See merge request cable-mc/cobblemon!1913
2025-10-21 23:38:34 +00:00
Carmendar 2a56709395 Merge branch 'main' into implement_pokemon 2025-10-21 17:57:11 -05:00
Polymeta fc135faf57 revert pokemon number range 2025-10-21 22:48:59 +00:00
Polymeta cdd72c664e fix some typos
Signed-off-by: Polymeta <40173118+Polymeta@users.noreply.github.com>
2025-10-21 23:23:43 +01:00
Polymeta 5f025d0f68 Merge branch 'moomoo-milk' into 'main'
Adds clear stat changes effect to Moomoo Milk

See merge request cable-mc/cobblemon!1911
2025-10-21 21:50:59 +00:00
lionandPolymeta 6af2c807ab Adds clear stat changes effect to Moomoo Milk 2025-10-21 21:50:58 +00:00
Polymeta c45c90c6c7 Merge branch 'main' into 1.7-spawns
# Conflicts:
#	common/src/main/resources/data/cobblemon/spawn_pool_world/0027_sandshrew.json
#	common/src/main/resources/data/cobblemon/spawn_pool_world/0028_sandslash.json
#	common/src/main/resources/data/cobblemon/spawn_pool_world/0079_slowpoke.json
#	common/src/main/resources/data/cobblemon/spawn_pool_world/0080_slowbro.json
#	common/src/main/resources/data/cobblemon/spawn_pool_world/0194_wooper.json
#	common/src/main/resources/data/cobblemon/spawn_pool_world/0199_slowking.json
#	common/src/main/resources/data/cobblemon/spawn_pool_world/0688_binacle.json
#	common/src/main/resources/data/cobblemon/spawn_pool_world/0689_barbaracle.json
#	common/src/main/resources/data/cobblemon/spawn_pool_world/0751_dewpider.json
#	common/src/main/resources/data/cobblemon/spawn_pool_world/0752_araquanid.json
#	common/src/main/resources/data/cobblemon/spawn_pool_world/0845_cramorant.json
#	common/src/main/resources/data/cobblemon/tags/worldgen/biome/is_overworld.json
#	utilityscripts/cobblemon_spawn_csv_to_json.py
2025-10-21 22:38:33 +01:00
Polymeta 1ec97c4c90 Merge branch 'riding-style-molang' into 'main'
Add molang function to get the current riding style of a pokemon

See merge request cable-mc/cobblemon!1912
2025-10-21 06:42:54 +00:00
Landon Wills f0bcd38ac7 Add molang function to get the current riding style of a pokemon 2025-10-21 01:55:19 -04:00
Test f4ba446c79 Merge branch 'restoration-tank-fixes' into 'main'
Small fixes

Closes #1735 and #1741

See merge request cable-mc/cobblemon!1908
2025-10-21 03:49:09 +00:00
Guilherme Pereira 60f98ebfce refactor poke snack 2025-10-21 00:04:20 -03:00
TeggyandWeblate 08c2a0d07b Translated using Weblate (Spanish)
Currently translated at 85.0% (7578 of 8908 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/es/
2025-10-20 20:24:26 +00:00
Nico MerenqandWeblate 1f68fdac8c Translated using Weblate (Russian)
Currently translated at 63.4% (5649 of 8908 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ru/
2025-10-20 17:26:47 +00:00
TeggyandWeblate e8d4f0fcf4 Translated using Weblate (Spanish)
Currently translated at 84.7% (7546 of 8908 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/es/
2025-10-20 17:26:45 +00:00
Guilherme Pereira 36a93bda5a refactor poke snack 2025-10-20 03:10:09 -03:00
阿鱼andWeblate 5d522bd608 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (8908 of 8908 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/zh_Hans/
2025-10-20 05:04:02 +00:00
Paul 8d60b07c54 Merge remote-tracking branch 'origin/main' 2025-10-20 14:45:26 +10:00
Paul 5f9cc86044 Added rouseChance and drowsyChance to RestBehaviour so pokemon don't insta sleep in the wild and may spontaneously wake up 2025-10-20 14:45:06 +10:00
Carmendar 35436c1d09 Dewott no longer sleeps in the void. 2025-10-19 23:26:07 -05:00
Landon a6b7e07cf5 Merge branch 'held-item-fix' into 'main'
Remove pvp check for held item logic

See merge request cable-mc/cobblemon!1907
2025-10-20 03:18:36 +00:00
Landon Wills 0ba6215b8d Elaborate on comment 2025-10-19 23:05:16 -04:00
Guilherme Pereira e2f6fae689 change willDefendSelf combat behaviour default to false 2025-10-19 23:36:52 -03:00
Guilherme Pereira 296aa01d0f remove println 2025-10-19 18:41:38 -03:00
Guilherme Pereira 12ab3f2d07 fix dolphin crash on servers 2025-10-19 18:18:31 -03:00
Guilherme Pereira 5a7a374bc9 Merge branch 'main' of gitlab.com:cable-mc/cobblemon into restoration-tank-fixes 2025-10-19 17:35:50 -03:00
Guilherme Pereira ceece45ad4 make poke snack spawn 4 mons per bite 2025-10-19 17:24:01 -03:00
Landon Wills b344ad66c5 Remove pvp check 2025-10-19 14:44:08 -04:00
Carmendar d66793d186 Merge branch 'main' into implement_pokemon 2025-10-18 16:03:03 -05:00
Carmendar cc00962752 Swadloon do be schmoovin (properly this time) 2025-10-18 15:58:41 -05:00
Paul dbc7af4ad4 Don't need to override the start conditions function for activity aware behavior 2025-10-18 21:40:43 +10:00
Paul 5f5bf01cb5 Added ActivityAwareBehavior and tagging for memories and activities 2025-10-18 21:30:47 +10:00
Polymeta 04f2af22f3 Merge branch 'hide-server-configs' into 'main'
Hide server configs when playing on a server

See merge request cable-mc/cobblemon!1905
2025-10-18 10:06:06 +00:00
lionandPolymeta ea916a0db8 Hide server configs when playing on a server 2025-10-18 10:06:06 +00:00
Guilherme Pereira 3f03afc7eb smooth campfire pot spinning animation 2025-10-18 06:53:20 -03:00
Paul e940483863 Typos corrected 2025-10-18 18:46:37 +10:00
Hiroku 9e3d3df3a9 Merge branch 'patch-1' into 'main'
Added Resource Texture Behaviour.

See merge request cable-mc/cobblemon!1894
2025-10-18 08:42:39 +00:00
Jolihan c18368d777 fixed? 2025-10-18 08:40:24 +00:00
Watuneni iteiteandWeblate 3894a99bc4 Translated using Weblate (Japanese)
Currently translated at 93.5% (8334 of 8908 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ja/
2025-10-17 18:40:49 +00:00
Hiroku aab5b55204 Merge branch 'Bee-Polite-Bee-Efficient' into 'main'
Pokemon bee behavior rework

See merge request cable-mc/cobblemon!1892
2025-10-17 10:24:14 +00:00
Paul 3e6e346eb9 Nectar aspect shouldn't be mixed case, Jazz!! 2025-10-17 19:39:41 +10:00
Carmendar 3a83ed424f Swadloon do be schmoovin. 2025-10-17 01:46:39 -05:00
Hiroku 7bef6db7b7 Merge branch 'riding-scraper-changes' into 'main'
Add lighting data to riding scraper

See merge request cable-mc/cobblemon!1904
2025-10-17 05:29:07 +00:00
lionandHiroku 71775328ef Add lighting data to riding scraper 2025-10-17 05:29:07 +00:00
Carmendar cbccc612a4 Spewpa learned how to walk. 2025-10-16 02:07:51 -05:00
Carmendar 63fd5d90c1 Merge branch 'main' into implement_pokemon 2025-10-15 23:42:59 -05:00
Carmendar 92336d6107 Implemented Aerodactyl remodel. 2025-10-15 23:37:00 -05:00
Landon 727785b0b3 Merge branch 'boat-stamina' into 'main'
Adjust stamina logic

See merge request cable-mc/cobblemon!1903
2025-10-16 03:45:39 +00:00
Landon Wills 762241420e Adjust stamina logic 2025-10-15 19:48:10 -04:00
Guilherme Pereira 93011f084b make riding fov effects proportional to minecraft fov effect scale setting 2025-10-15 19:23:51 -03:00
Guilherme Pereira 7c90a8425e fix restoration tank concurrent modification crash #1735 2025-10-15 15:01:41 -03:00
Jolihan 8674cbe788 Update 2 files
- /resource_texture.json
- /common/src/main/resources/data/cobblemon/behaviours/npc/resource_identifier.json
2025-10-15 07:06:20 +00:00
Jolihan 8075fe0bae Update 2 files
- /common/src/main/resources/assets/cobblemon/lang/en_us.json
- /resource_texture.json
2025-10-15 07:03:10 +00:00
Carmendar f3f29ca0ea sky protector remodel. 2025-10-15 02:02:01 -05:00
Polymeta 0152803192 Merge branch 'fix-hide-tooltips' into 'main'
Pulled up the Hide Tooltip check to instead run before any custom tooltip generators are performed.

Closes #1733

See merge request cable-mc/cobblemon!1902
2025-10-15 06:14:22 +00:00
Carmendar 2544cbc252 Cosmetics for Alola-bias Pichu and Pikachu, and cosmetics for Alolan Raichu. 2025-10-14 23:53:32 -05:00
Charzard4261 5e094241e3 Pulled up the Hide Tooltip check to instead run before any custom tooltip generators are performed. 2025-10-14 23:33:08 +01:00
Paul bd028c328d Fixed NPC class variables not being applied to NPCs 2025-10-14 22:43:06 +10:00
Polymeta 598cb1d964 Merge branch 'dead-bushes-bad-omen' into 'main'
Adds bad omen effect to dead bushes

See merge request cable-mc/cobblemon!1901
2025-10-14 08:30:41 +00:00
JazzMcNade 79d264fd94 Merge branch 'main' into Bee-Polite-Bee-Efficient 2025-10-14 00:34:09 -05:00
Guilherme Pereira 5f691d3f1c add bad omen effect to dead bushes 2025-10-13 22:26:29 -03:00
Landon 7be0163e76 Merge branch 'bird-stamina-changes' into 'main'
Bird stamina changes and bird fixes

See merge request cable-mc/cobblemon!1897
2025-10-13 21:51:47 +00:00
Jackson BowesandLandon c0b3068824 Bird stamina changes and bird fixes 2025-10-13 21:51:46 +00:00
阿鱼andWeblate 9036a2f317 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (8908 of 8908 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/zh_Hans/
2025-10-13 12:31:47 +00:00
Polymeta dd35adc05d Merge branch 'help-i-cannot-get-down' into 'main'
Fixed neoforge hover mounts not being able to descend

Closes #1717

See merge request cable-mc/cobblemon!1900
2025-10-13 10:58:40 +00:00
Paul 26b5707317 how about you license deez 2025-10-13 19:37:14 +10:00
Hiroku 75e39f5595 Merge branch 'neoforge-tag-annoyance' into 'main'
Plural Tags Please

See merge request cable-mc/cobblemon!1899
2025-10-13 06:47:59 +00:00
Paul 7fdeaeabef Fixed neoforge hover mounts not being able to descend 2025-10-13 16:45:32 +10:00
阿鱼andWeblate c75ab28002 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 99.3% (8853 of 8908 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/zh_Hans/
2025-10-13 06:00:42 +00:00
dhi_holo 809ffd8b90 Merge branch 'main' into neoforge-tag-annoyance 2025-10-13 14:50:58 +10:00
dhi_holo da7610951d Plural Tags Please
NeoForge uses singular form food tags for some reason. Also removed our berries as they're not technically a food and it would break balancing
2025-10-13 14:50:53 +10:00
Nico MerenqandWeblate 62a8320068 Translated using Weblate (Russian)
Currently translated at 63.2% (5635 of 8908 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ru/
2025-10-11 20:53:16 +00:00
Watuneni iteiteandWeblate f21b38f66a Translated using Weblate (Japanese)
Currently translated at 93.1% (8300 of 8908 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ja/
2025-10-11 20:53:15 +00:00
Paul 76fdd1fa98 No more tricky riding pokemon after battles start 2025-10-11 21:04:59 +10:00
阿鱼andWeblate 596765bb52 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 98.6% (8791 of 8908 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/zh_Hans/
2025-10-11 06:00:06 +00:00
Watuneni iteiteandWeblate 297aafba45 Translated using Weblate (Japanese)
Currently translated at 92.9% (8282 of 8908 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ja/
2025-10-11 06:00:04 +00:00
Polymeta 156983bf58 [skip-ci] changelog 2025-10-10 09:28:59 +00:00
Polymeta c69930b836 Merge branch 'SaferPokeballBreakFree' into 'main'
Avoid suffocation when a pokeball breaks near a wall

See merge request cable-mc/cobblemon!1898
2025-10-10 09:27:56 +00:00
JazzMcNade 6926932340 Merge branch 'main' into SaferPokeballBreakFree 2025-10-10 00:29:18 -05:00
Landon 221b3308fa Merge branch 'fix-boat-crash' into 'main'
Fix typo

See merge request cable-mc/cobblemon!1896
2025-10-09 22:39:05 +00:00
Landon Wills 66840670b6 Fix typo 2025-10-09 17:51:51 -04:00
Hiroku 28887d9116 Merge branch 'do-you-get-the-joke' into 'do-you-get-the-joke'
Eggant Effect Data Pack

See merge request cable-mc/cobblemon!1893
2025-10-08 13:02:28 +00:00
KyozennoandHiroku 6ef1bac1d7 Eggant Effect Data Pack 2025-10-08 13:02:27 +00:00
Jolihan 5d6a7807e5 Added Resource Texture Behaviour. This adds a behaviour that allows the user to change the resource identifier of the npc class without needing a new class. 2025-10-08 12:48:20 +00:00
JazzMcNade 20b7eb17f3 simplify 2025-10-07 00:52:36 -05:00
Watuneni iteiteandWeblate a0a682e224 Translated using Weblate (Japanese)
Currently translated at 92.3% (8227 of 8908 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ja/
2025-10-06 12:15:07 +00:00
JazzMcNade 103ac86345 Merge branch 'main' into SaferPokeballBreakFree 2025-10-05 02:00:56 -05:00
JazzMcNade 8f705a698d Merge branch 'main' into Bee-Polite-Bee-Efficient 2025-10-04 21:16:20 -05:00
JazzMcNade f4fb09947e Change log 2025-10-04 20:51:49 -05:00
JazzMcNade 97e31e341b Licenses! 2025-10-04 19:26:32 -05:00
JazzMcNade 813789235d Serializable hive/flow/leaf memories 2025-10-04 19:10:32 -05:00
Nico MerenqandWeblate d161b11892 Translated using Weblate (Russian)
Currently translated at 63.1% (5627 of 8908 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ru/
2025-10-04 22:39:35 +00:00
JazzMcNade 5a82e7e185 Clean up and refactor 2025-10-04 17:15:20 -05:00
JazzMcNade 3bc8d3ce75 Code cleanup 2025-10-04 16:50:23 -05:00
JazzMcNade d9e9fd0f4e Clean up wander and move to hive task 2025-10-04 04:09:11 -05:00
Polymeta fddb203db3 Merge branch 'fix-aprijuice-crash' into 'main'
Aprijuice feeding crash

Closes #1712

See merge request cable-mc/cobblemon!1887
2025-10-03 16:14:39 +00:00
lionandPolymeta 8a8cace5e8 Aprijuice feeding crash 2025-10-03 16:14:38 +00:00
Polymeta 5f5b4ee3c1 Merge branch 'implement_pokemon' into 'main'
Implement pokemon

See merge request cable-mc/cobblemon!1890
2025-10-03 08:19:57 +00:00
JazzMcNade aa6581ae39 WIP hive blacklisting 2025-10-03 02:18:32 -05:00
Paul 8aa9e0191f Shulker Boxes and Traveler's Backpacks cannot be held by Pokémon and make the PC file 5000000gb large anymore 2025-10-03 16:43:35 +10:00
JazzMcNade 8dc09796f8 corrected extra start conditions 2025-10-02 23:57:38 -05:00
JazzMcNade 8f93712edb Merge branch 'main' into Bee-Polite-Bee-Efficient 2025-10-02 23:40:46 -05:00
JazzMcNade b91d1b931e Bees can now give up on a unreachable hive 2025-10-02 23:27:51 -05:00
Test 9d84cbb3ac Merge branch 'main' of https://gitlab.com/cable-mc/cobblemon 2025-10-02 22:35:54 -04:00
Test 8e730f2b5c fix ride stat exceeding limit 2025-10-02 22:35:24 -04:00
Hiroku 7b5809fd55 Merge branch 'aprijuice-feeding-crash-fix' into 'main'
Aprijuice feeding crash fix

Closes #1712

See merge request cable-mc/cobblemon!1891
2025-10-03 01:22:09 +00:00
PieDuck bc682ddf62 Removes comments 2025-10-03 00:57:04 +01:00
PieDuck 990ead0544 Fixes ConcurrentModificationException crash when feeding pokemon aprijuice 2025-10-03 00:55:38 +01:00
Carmendar 4cf55dc95d Merge branch 'main' into implement_pokemon 2025-10-02 17:54:36 -05:00
Polymeta 27a9f3c704 Merge branch 'ride-settings-network-fixes-electric-boogaloo' into 'main'
Make RideSettingsSyncHandler be correctly sided

See merge request cable-mc/cobblemon!1889
2025-10-02 20:47:01 +00:00
YasitoandWeblate d70bb243b2 Translated using Weblate (French)
Currently translated at 100.0% (8908 of 8908 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2025-10-02 18:49:12 +00:00
YasitoandWeblate 301a3efb7c Translated using Weblate (French (Canada))
Currently translated at 100.0% (8908 of 8908 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr_CA/
2025-10-02 18:49:11 +00:00
YasitoandWeblate 05b7cbe20a Translated using Weblate (French)
Currently translated at 100.0% (8908 of 8908 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2025-10-02 18:38:58 +00:00
YasitoandWeblate 48cbbc1bef Translated using Weblate (French (Canada))
Currently translated at 100.0% (8908 of 8908 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr_CA/
2025-10-02 18:38:57 +00:00
YasitoandWeblate f8f7c28274 Translated using Weblate (French)
Currently translated at 99.8% (8895 of 8908 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2025-10-02 17:13:57 +00:00
YasitoandWeblate 0ebb53888a Translated using Weblate (French (Canada))
Currently translated at 99.8% (8891 of 8908 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr_CA/
2025-10-02 17:13:56 +00:00
Nadja Reitzenstein 83aba7e4a6 Make RideSettingsSyncHandler be correctly sided 2025-10-02 18:57:37 +02:00
Landon db10ae2183 Merge branch 'riding-behaviour-network-fixes' into 'main'
Make RidingBehaviourSettings::encode/decode symmetric

See merge request cable-mc/cobblemon!1888
2025-10-02 15:39:53 +00:00
YasitoandWeblate c0071c1525 Translated using Weblate (French)
Currently translated at 99.5% (8868 of 8908 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2025-10-02 14:53:46 +00:00
YasitoandWeblate 71f7b3b3d8 Translated using Weblate (French (Canada))
Currently translated at 99.2% (8841 of 8908 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr_CA/
2025-10-02 14:53:45 +00:00
Nadja Reitzenstein 548b8522a8 Make RidingBehaviourSettings::encode/decode symmetric
Having the encode/decode functionality symmetric is the expected behaviour and not doing so will introduce a lot of accidental bugs when people assume them to be. Case in point: `RideSettingsSyncPacket.kt` (which this commit notably *does not touch*) expected them to be and was thus entirely broken.
2025-10-02 16:02:24 +02:00
Polymeta 4791b146dc Merge branch 'hotfix-BufferUtils-NPE' into 'main'
Fix a logic error in write-/readNullable resulting in NPE on server joins.

See merge request cable-mc/cobblemon!1886
2025-10-02 13:43:18 +00:00
Nadja Reitzenstein b7a9931393 Fix a logic error in write-/readNullable resulting in NPE on server joins.
This error occurs because `writeNullable` will write `true` if the encoded value is `null` while `readNullable` will treat a `true` value as a value being present. Since it makes more sense to have a true value as a go-ahead this commit changes the behaviour of `writeNullable` to write `true` if a value is present and `false` if a value is `null`
2025-10-02 15:20:33 +02:00
Polymeta 9a35a11440 Merge branch 'the-case-of-disappearing-cubes' into 'main'
The Case of Disappearing Cubes

See merge request cable-mc/cobblemon!1883
2025-10-02 10:46:15 +00:00
Polymeta 6d4a50cee2 Merge branch 'cooking-pot-hopper-bugfix' into 'main'
Fixed bug with seasonings and hoppers in the campfire cooking pot

See merge request cable-mc/cobblemon!1885
2025-10-02 08:16:43 +00:00
PieDuck 818a637897 Merge branch cobblemon:main into cooking-pot-hopper-bugfix 2025-10-01 22:34:45 +01:00
Polymeta 4c96ab9b28 Merge branch 'Carm-Signfix' into 'main'
Added missing Saccharine sign loot table.

See merge request cable-mc/cobblemon!1884
2025-10-01 17:12:08 +00:00
PieDuck d5a316b9ae Fixed bug with seasonings and hoppers in the campfire cooking pot 2025-10-01 17:29:29 +01:00
Carmendar 87e32f0e9b Added missing Saccharine sign loot table. 2025-09-30 23:59:15 -05:00
Watuneni iteiteandWeblate bd6d7b7149 Translated using Weblate (Japanese)
Currently translated at 91.1% (8116 of 8908 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ja/
2025-10-01 00:25:05 +00:00
JazzMcNade 73275626a3 Persist bees that have a valid nectar deposit location 2025-09-30 00:51:29 -05:00
GenotypeXD-Dev 0686428138 Implemented Elgyem, and Beheeyem remodels 2025-09-29 23:47:04 -05:00
GenotypeXD-Dev f05e6008e5 Ho-oh Fixes 2025-09-29 13:33:00 -05:00
Alexis SalgueiroandWeblate 63de8fe44a Translated using Weblate (French)
Currently translated at 99.5% (8868 of 8908 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2025-09-29 18:00:45 +00:00
Gertimoshka 53372e5400 Fixed a very specific case of how a model was made causing dissapearing cubes ingame 2025-09-29 20:15:14 +03:00
Polymeta 2fbb1f354c Merge branch '#1568-npcdelete' into 'main'
changes to /npcdelete uuid prameter and console support

Closes #1568

See merge request cable-mc/cobblemon!1851
2025-09-29 13:13:46 +00:00
CyrusandPolymeta bf06476558 changes to /npcdelete uuid prameter and console support 2025-09-29 13:13:46 +00:00
Paul 2e3a742477 Merge remote-tracking branch 'origin/lang-updates' 2025-09-29 19:45:46 +10:00
Watuneni iteiteandWeblate 94c7542d2c Translated using Weblate (Japanese)
Currently translated at 86.7% (7693 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ja/
2025-09-29 09:27:58 +00:00
Polymeta fa2b6b5996 Merge branch 'regional-food-use-fix' into 'main'
Small fixes

Closes #1708, #1689, #1696, #1710, and #1711

See merge request cable-mc/cobblemon!1877
2025-09-29 08:13:44 +00:00
Watuneni iteiteandWeblate 8e77dc70f6 Translated using Weblate (Japanese)
Currently translated at 86.7% (7692 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ja/
2025-09-29 07:04:26 +00:00
Hiroku 51e3748adb Merge branch 'NoMoreAwkwardSurfaceSwimmingPlz' into 'main'
Fix Awkward water swimming

See merge request cable-mc/cobblemon!1882
2025-09-29 05:50:14 +00:00
Hiroku d8cf2701c7 Merge branch 'sterr/sigh-need-a-new-branch-for-this' into 'main'
Remove an unused sound from sounds.json

See merge request cable-mc/cobblemon!1881
2025-09-29 05:07:36 +00:00
Carmendar 4450164d9c Garchomp can swim in liquid water now. 2025-09-28 23:56:10 -05:00
JazzMcNade 31b7c83369 Merge branch 'main' into NoMoreAwkwardSurfaceSwimmingPlz 2025-09-28 22:50:07 -05:00
JazzMcNade a170123e2c Enforce minimum depth requirement for swim wander, fix mistake in omnipathnav 2025-09-28 22:43:15 -05:00
Carmendar b99899353c Merge branch 'main' into implement_pokemon 2025-09-28 22:39:22 -05:00
Hiroku c3c17a228e Merge branch 'riding-work-hiro' into 'main'
Riding setting and stamina changes

See merge request cable-mc/cobblemon!1880
2025-09-29 02:13:24 +00:00
Paul 41d607ad56 Remember the last camera type used for a mount type and switch to it if the config is enabled 2025-09-29 11:05:41 +10:00
Hiroku 58b3596948 Wait that wasn't meant to still be in there 2025-09-28 11:12:33 +00:00
Paul ed4456d756 API and config and controller options for infinite stamina 2025-09-28 15:47:17 +10:00
violet 608b711833 minor fix to exeggutor's animations 🥚 2025-09-28 00:59:44 -04:00
violet 3adc1b9f30 altered posers for staryu, starmie, and corsola 🫧 2025-09-28 00:35:52 -04:00
violet 478cbc2489 updated models & animations for litwick, lampent, and chandelure 🕯️ 2025-09-27 23:49:26 -04:00
Test 481734e6b1 Merge branch 'main' of https://gitlab.com/cable-mc/cobblemon 2025-09-27 20:44:47 -04:00
Test 636792fa1f rm unused function 2025-09-27 20:44:40 -04:00
Test ad65002b19 fix stat color 2025-09-27 20:43:44 -04:00
Sterrezo 903f3b6d5a Removed an unused sound from sounds.json 2025-09-27 12:15:22 +02:00
Paul 4456743e97 Merge remote-tracking branch 'origin/main' into riding-work-hiro
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonNetwork.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/data/CobblemonDataProvider.kt
2025-09-27 18:41:27 +10:00
Paul d0989e5ece Migrated all the other controllers to support the global config 2025-09-27 18:40:57 +10:00
Carmendar 03edb5a385 Merge branch 'main' into implement_pokemon 2025-09-27 02:47:44 -05:00
Paul 180229db61 Merge remote-tracking branch 'origin/main' into riding-work-hiro 2025-09-27 16:18:31 +10:00
Carmendar cf77713860 No more Hydreigon quirks while flying. 2025-09-27 01:08:41 -05:00
Paul b6e10c016e Migrate global ride controller settings to use a different solution 2025-09-27 16:06:13 +10:00
Carmendar 0494193c9b fixed paldean tauros falling too. 2025-09-26 22:50:30 -05:00
Carmendar 7bee34170d removed non-existant quirk from crobat poser & fixed tauros falling in battle. 2025-09-26 22:47:49 -05:00
Paul ce09236dab Added global controller settings for riding, moved jet over to it 2025-09-27 12:39:17 +10:00
Guilherme Pereira 0bb968dd73 add pokémon fullness to pokémon properties 2025-09-26 11:42:58 -03:00
Guilherme Pereira f3d4cb34e0 fix poke puff tooltip and mild multiplier #1711 #1710 2025-09-26 10:41:17 -03:00
JazzMcNade 3a22e387b6 Clean up flower sensor 2025-09-26 00:46:27 -05:00
JazzMcNade 956b3bfcbd removed hive sensor from non-wild bees, clean up 2025-09-26 00:28:43 -05:00
Paul 3b6e1bd429 Persist stamina between dismount and mount 2025-09-26 11:24:44 +10:00
Guilherme Pereira 1eb7354fc0 update CHANGELOG.md 2025-09-25 16:49:59 -03:00
Guilherme Pereira 52ad6a3250 fix mochi tooltips #1689 2025-09-25 16:24:15 -03:00
Guilherme Pereira aa57bbbf34 fix glint on enchanted pokémon item models #1696 2025-09-25 16:20:17 -03:00
Guilherme Pereira 26278eeb46 fix regional food items not usable on pokémon #1708 2025-09-25 14:55:41 -03:00
JazzMcNade a9ceddd94a Clean up, disabled hive storage for non-wild bees 2025-09-25 00:50:04 -05:00
JazzMcNade e99c55e812 Merge branch 'main' into Bee-Polite-Bee-Efficient 2025-09-24 01:10:00 -05:00
JazzMcNade 5053d559bc Refactor, tweak start conditions, stop channeled behaviours when exiting the bee activity, allow bees to patrol sacc leaves if no hive is availiable. 2025-09-24 01:09:21 -05:00
Jerzykv 1a30109f1c Galarian Farfetch'd can no longer fly 2025-09-23 15:03:21 +02:00
Nico MerenqandWeblate efd4a72287 Translated using Weblate (Russian)
Currently translated at 63.1% (5598 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ru/
2025-09-21 23:09:39 +00:00
violet 5088497575 MANECTRIC. 2025-09-21 18:55:04 -04:00
Watuneni iteiteandWeblate bba8a12a2a Translated using Weblate (Japanese)
Currently translated at 85.7% (7608 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ja/
2025-09-21 10:00:32 +00:00
JazzMcNade 31129ef14d Merge branch 'main' into Bee-Polite-Bee-Efficient 2025-09-20 19:44:59 -05:00
阿鱼andWeblate 1cdf664dce Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 99.5% (8833 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/zh_Hans/
2025-09-20 07:30:00 +00:00
Watuneni iteiteandWeblate 2ea5eb2823 Translated using Weblate (Japanese)
Currently translated at 85.7% (7602 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ja/
2025-09-16 18:48:56 +00:00
Nico MerenqandWeblate e109b0fc9f Translated using Weblate (Russian)
Currently translated at 63.0% (5595 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ru/
2025-09-14 14:39:57 +00:00
Watuneni iteiteandWeblate 5de7f96fef Translated using Weblate (Japanese)
Currently translated at 85.6% (7595 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ja/
2025-09-14 14:39:56 +00:00
Nico MerenqandWeblate 1f62f447b5 Translated using Weblate (Russian)
Currently translated at 62.8% (5572 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ru/
2025-09-13 18:04:21 +00:00
Nico MerenqandWeblate 674e1df96d Translated using Weblate (Russian)
Currently translated at 62.8% (5571 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ru/
2025-09-11 17:08:21 +00:00
Watuneni iteiteandWeblate a4a2ae6133 Translated using Weblate (Japanese)
Currently translated at 85.6% (7595 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ja/
2025-09-11 17:08:20 +00:00
Veraxiel 17c71efb17 Added spawns for 1.7 Pokémon. Updated spawns. Updated spawn scraper. 2025-09-08 02:04:58 -04:00
JazzMcNade e80eb91e69 Refactored Combees to only pass through Saccharine Leaves 2025-09-08 01:03:31 -05:00
Veraxiel a5b6a954fd Added "Mansion Dining" preset tag. Updated is_overworld tag. 2025-09-07 20:42:53 -04:00
Nico MerenqandWeblate a5c3d2f800 Translated using Weblate (Russian)
Currently translated at 62.5% (5549 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ru/
2025-09-07 20:08:56 +00:00
TeggyandWeblate 00d2bbbe42 Translated using Weblate (Spanish)
Currently translated at 85.5% (7586 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/es/
2025-09-07 20:08:55 +00:00
JazzMcNade 2a295a8796 Merge branch 'main' into Bee-Polite-Bee-Efficient 2025-09-07 03:55:50 -05:00
TeggyandWeblate 40b351a303 Translated using Weblate (Spanish)
Currently translated at 85.5% (7586 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/es/
2025-09-06 12:01:45 +00:00
Not telling you thatandWeblate dc32bd1845 Translated using Weblate (Spanish)
Currently translated at 85.5% (7586 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/es/
2025-09-06 12:01:45 +00:00
Nico MerenqandWeblate a096485155 Translated using Weblate (Russian)
Currently translated at 62.0% (5508 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ru/
2025-09-06 11:58:11 +00:00
TeggyandWeblate f1ae7c4476 Translated using Weblate (Spanish)
Currently translated at 85.4% (7579 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/es/
2025-09-06 11:58:10 +00:00
Not telling you thatandWeblate bcfac2f68a Translated using Weblate (Spanish)
Currently translated at 85.4% (7579 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/es/
2025-09-06 11:58:10 +00:00
Nico MerenqandWeblate ff1902770c Translated using Weblate (Russian)
Currently translated at 62.0% (5503 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ru/
2025-09-06 11:52:11 +00:00
Not telling you thatandWeblate ebb01d4fcc Translated using Weblate (Spanish)
Currently translated at 85.3% (7571 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/es/
2025-09-06 11:52:11 +00:00
阿鱼andWeblate 0b392c66eb Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 99.5% (8833 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/zh_Hans/
2025-09-04 12:47:12 +00:00
Nico MerenqandWeblate 5733074bd8 Translated using Weblate (Russian)
Currently translated at 61.5% (5460 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ru/
2025-09-04 12:47:11 +00:00
阿鱼andWeblate 88ff4e37b8 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 99.5% (8833 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/zh_Hans/
2025-09-03 03:48:57 +00:00
JazzMcNade 245226f4f6 Refresh/delete hive memory on exiting a hive 2025-09-02 20:31:13 -05:00
Nico MerenqandWeblate 35b7a80501 Translated using Weblate (Russian)
Currently translated at 61.4% (5455 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ru/
2025-09-02 12:29:43 +00:00
Watuneni iteiteandWeblate 91949414cc Translated using Weblate (Japanese)
Currently translated at 85.6% (7595 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ja/
2025-09-02 12:29:42 +00:00
JazzMcNade d3e1c2c79d Merge branch 'main' into Bee-Polite-Bee-Efficient 2025-09-02 01:34:58 -05:00
washashu0905andWeblate 10bfe3b29d Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 96.8% (8593 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/zh_Hant/
2025-08-30 11:18:38 +00:00
阿鱼andWeblate ff88a3c756 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 99.5% (8832 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/zh_Hans/
2025-08-30 11:18:38 +00:00
washashu0905andWeblate 4d47cb8385 Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 96.7% (8582 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/zh_Hant/
2025-08-30 00:42:40 +00:00
washashu0905andWeblate 8aba2bc279 Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 96.1% (8530 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/zh_Hant/
2025-08-29 23:50:13 +00:00
Nico MerenqandWeblate fe91e912d0 Translated using Weblate (Russian)
Currently translated at 59.5% (5284 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ru/
2025-08-29 13:16:35 +00:00
Alexis SalgueiroandWeblate 680bc1d205 Translated using Weblate (French)
Currently translated at 100.0% (8870 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2025-08-28 20:17:51 +00:00
JazzMcNade e04e82c6de Added combee aggression for defending hives, more refactoring 2025-08-27 00:45:12 -05:00
阿鱼andWeblate d0c4bd5a16 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 99.5% (8832 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/zh_Hans/
2025-08-26 20:41:46 +00:00
Watuneni iteiteandWeblate 500069e5d8 Translated using Weblate (Japanese)
Currently translated at 85.2% (7561 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ja/
2025-08-26 20:41:45 +00:00
Nico MerenqandWeblate 13ae174c66 Translated using Weblate (Russian)
Currently translated at 58.0% (5148 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ru/
2025-08-25 21:25:28 +00:00
JazzMcNade 95589dd48b Added hasNectar aspect particle effect. Removed programmer textures, removed unused file. 2025-08-23 19:34:02 -05:00
JazzMcNade 4cb17135bd Cancel honey deposit if the leaves are removed 2025-08-23 18:09:53 -05:00
JazzMcNade 8dc23160fc Added hasNectar aspect. Refactored POLLINATED memory to HAS_NECTAR 2025-08-23 17:22:58 -05:00
JazzMcNade 4a96a41c8e Merge branch 'main' into Bee-Polite-Bee-Efficient 2025-08-23 14:40:51 -05:00
Nico MerenqandWeblate 3bf777c2a9 Translated using Weblate (Russian)
Currently translated at 58.0% (5147 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ru/
2025-08-23 10:45:45 +00:00
Nico MerenqandWeblate b5174686b4 Translated using Weblate (Russian)
Currently translated at 57.9% (5138 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ru/
2025-08-22 06:04:58 +00:00
Watuneni iteiteandWeblate 26a7bc29b3 Translated using Weblate (Japanese)
Currently translated at 85.1% (7555 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ja/
2025-08-22 06:04:57 +00:00
Nico MerenqandWeblate 05cd37eb38 Translated using Weblate (Russian)
Currently translated at 57.9% (5136 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ru/
2025-08-20 23:46:22 +00:00
JazzMcNade 3f18e7c0bf Combees may pass through leaves to deposit honey in saccharine leaves. 2025-08-19 23:37:24 -05:00
Nico MerenqandWeblate 3ac3fcbcda Translated using Weblate (Russian)
Currently translated at 57.4% (5092 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ru/
2025-08-18 18:30:18 +00:00
Weblate 27b3884f27 Update translation files
Updated by "Remove blank strings" add-on in Weblate.

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/
2025-08-16 21:52:57 +00:00
dhi_holoandWeblate 15e8f46702 Translated using Weblate (English (en_PT))
Currently translated at 24.6% (2189 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/en_PT/
2025-08-16 21:52:53 +00:00
dhi_holoandWeblate e91b1061d3 Translated using Weblate (English (United Kingdom))
Currently translated at 0.8% (71 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/en_GB/
2025-08-16 21:52:51 +00:00
JazzMcNade 3c3a23a397 Merge branch 'LionsFoxesAndWolvesOhMy' into Bee-Polite-Bee-Efficient 2025-08-16 14:38:57 -05:00
JazzMcNade 438de6ed6d Merge branch 'main' into Bee-Polite-Bee-Efficient 2025-08-16 14:36:53 -05:00
dhi_holo 95399a32a8 Tweaks
Fixed the advancements breaking (whoops) and lowered the berry in soil
2025-08-16 18:10:11 +10:00
dhi_holo 7089ff68d4 🍆
That's it, that's the addition

Things that need finishing:
- Bait/Cooking Effects
- Battle Effects (Eggant Berry doesn't exist within our Showdown repo, which it seems to need)
2025-08-16 17:09:02 +10:00
JazzMcNade c754d820a5 Combee no longer gets stuck if it tries to enter a full hive, Will now always attempt to return to a hive in night or rain. 2025-08-15 00:35:38 -05:00
JazzMcNade 81c819ddd3 Pollination more closely reflects vanilla bees. Combee now stay closer to the ground. Combee now correctly update their rotation upon leaving a hive. 2025-08-14 22:26:38 -05:00
阿鱼andWeblate f042d167d1 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 99.5% (8832 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/zh_Hans/
2025-08-14 07:35:19 +00:00
Watuneni iteiteandWeblate eff3bd91d7 Translated using Weblate (Japanese)
Currently translated at 84.9% (7538 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ja/
2025-08-13 11:19:51 +00:00
JazzMcNade ec3e0bfd04 More beelike hive logic. Nectar is applied more closely to how minecraft bees function 2025-08-11 00:38:42 -05:00
Nico MerenqandWeblate 1391bfee01 Translated using Weblate (Russian)
Currently translated at 56.9% (5054 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ru/
2025-08-10 10:56:14 +00:00
JazzMcNade b61d739e46 Wild Combees can store themselves in beehives, partial rework of hive sensor 2025-08-09 20:34:43 -05:00
JazzMcNade 7a050d5374 Merge branch 'main' into Bee-Polite-Bee-Efficient 2025-08-09 13:58:48 -05:00
JazzMcNade 52076de76a Reduced performance cost of flower sensor 2025-08-09 13:57:43 -05:00
Watuneni iteiteandWeblate e8b6bee139 Translated using Weblate (Japanese)
Currently translated at 84.9% (7538 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ja/
2025-08-09 09:35:43 +00:00
Nico MerenqandWeblate a98bc80005 Translated using Weblate (Russian)
Currently translated at 56.9% (5052 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ru/
2025-08-08 15:26:11 +00:00
Zack the GhastandWeblate 06d25a043c Translated using Weblate (Portuguese (Portugal))
Currently translated at 68.7% (6097 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/pt_PT/
2025-08-08 02:52:08 +00:00
Zack the GhastandWeblate 6aa3b8b92f Translated using Weblate (Portuguese (Portugal))
Currently translated at 68.5% (6082 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/pt_PT/
2025-08-08 00:40:47 +00:00
Nico MerenqandWeblate 7381b47830 Translated using Weblate (Russian)
Currently translated at 56.1% (4981 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ru/
2025-08-07 13:28:54 +00:00
Watuneni iteiteandWeblate b2a704fa12 Translated using Weblate (Japanese)
Currently translated at 84.8% (7528 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ja/
2025-08-07 13:28:52 +00:00
Zack the GhastandWeblate 41ed476c00 Translated using Weblate (Portuguese (Portugal))
Currently translated at 68.4% (6072 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/pt_PT/
2025-08-07 04:26:31 +00:00
Nico MerenqandWeblate 44ddf097d6 Translated using Weblate (Russian)
Currently translated at 55.7% (4948 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ru/
2025-08-06 20:04:57 +00:00
Zack the GhastandWeblate cb1386fdb3 Translated using Weblate (Portuguese (Portugal))
Currently translated at 68.2% (6051 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/pt_PT/
2025-08-06 20:04:56 +00:00
Nico MerenqandWeblate 0dafe79032 Translated using Weblate (Russian)
Currently translated at 55.3% (4911 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ru/
2025-08-06 14:49:22 +00:00
Nico MerenqandWeblate 51cd929d1c Translated using Weblate (Russian)
Currently translated at 55.3% (4911 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ru/
2025-08-06 12:06:36 +00:00
Nico MerenqandWeblate 5468d5e888 Translated using Weblate (Russian)
Currently translated at 54.6% (4851 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ru/
2025-08-06 11:23:20 +00:00
Nico MerenqandWeblate 0174b8bc67 Translated using Weblate (Russian)
Currently translated at 54.5% (4840 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ru/
2025-08-06 11:18:06 +00:00
Watuneni iteiteandWeblate e3b8fbb973 Translated using Weblate (Japanese)
Currently translated at 84.8% (7527 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ja/
2025-08-06 11:18:05 +00:00
Nico MerenqandWeblate 7103df0116 Translated using Weblate (Russian)
Currently translated at 53.5% (4752 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ru/
2025-08-06 10:25:46 +00:00
Zack the GhastandWeblate 42fba85241 Translated using Weblate (Portuguese (Portugal))
Currently translated at 68.2% (6050 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/pt_PT/
2025-08-06 10:25:45 +00:00
Watuneni iteiteandWeblate f9f11c7137 Translated using Weblate (Japanese)
Currently translated at 84.0% (7452 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ja/
2025-08-06 10:25:45 +00:00
Zack the GhastandWeblate a51acead21 Translated using Weblate (Portuguese (Portugal))
Currently translated at 68.1% (6048 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/pt_PT/
2025-08-06 08:53:38 +00:00
Watuneni iteiteandWeblate 7a3ab17e57 Translated using Weblate (Japanese)
Currently translated at 82.0% (7280 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ja/
2025-08-06 08:53:37 +00:00
Watuneni iteiteandWeblate d4e741c614 Translated using Weblate (Japanese)
Currently translated at 81.7% (7248 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ja/
2025-08-06 08:11:59 +00:00
Watuneni iteiteandWeblate bee1797ff5 Translated using Weblate (Japanese)
Currently translated at 81.7% (7247 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ja/
2025-08-06 08:06:41 +00:00
Watuneni iteiteandWeblate 72fa7d42d5 Translated using Weblate (Japanese)
Currently translated at 81.6% (7243 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ja/
2025-08-06 07:47:36 +00:00
Zack the GhastandWeblate 7a4bf4cc5d Translated using Weblate (Portuguese (Portugal))
Currently translated at 68.1% (6046 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/pt_PT/
2025-08-06 01:23:25 +00:00
Zack the GhastandWeblate ab66e13c8c Translated using Weblate (Portuguese (Portugal))
Currently translated at 68.0% (6038 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/pt_PT/
2025-08-06 00:45:38 +00:00
Zack the GhastandWeblate eaeea493a1 Translated using Weblate (Portuguese (Portugal))
Currently translated at 67.8% (6022 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/pt_PT/
2025-08-05 23:49:36 +00:00
Nico MerenqandWeblate 0bfaedbf6e Translated using Weblate (Russian)
Currently translated at 53.5% (4752 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ru/
2025-08-05 20:57:37 +00:00
Zack the GhastandWeblate 52f9f45608 Translated using Weblate (Portuguese (Portugal))
Currently translated at 67.8% (6016 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/pt_PT/
2025-08-05 20:57:36 +00:00
Zack the GhastandWeblate dfa9dc1e2b Translated using Weblate (Portuguese (Portugal))
Currently translated at 67.5% (5992 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/pt_PT/
2025-08-05 12:37:13 +00:00
Nico MerenqandWeblate c8f5538ae8 Translated using Weblate (Russian)
Currently translated at 53.3% (4728 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ru/
2025-08-05 12:12:59 +00:00
Nico MerenqandWeblate 5af5a59ff1 Translated using Weblate (Russian)
Currently translated at 52.7% (4679 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ru/
2025-08-05 10:18:42 +00:00
Zack the GhastandWeblate eb37ecbe56 Translated using Weblate (Portuguese (Portugal))
Currently translated at 67.5% (5991 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/pt_PT/
2025-08-05 03:22:34 +00:00
Zack the GhastandWeblate d7b528aef1 Translated using Weblate (Portuguese (Portugal))
Currently translated at 67.4% (5981 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/pt_PT/
2025-08-05 02:27:49 +00:00
Nico MerenqandWeblate 2908ca3a89 Translated using Weblate (Russian)
Currently translated at 52.5% (4660 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ru/
2025-08-04 23:50:47 +00:00
Nico MerenqandWeblate aa04dc077b Translated using Weblate (Russian)
Currently translated at 52.5% (4657 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ru/
2025-08-04 21:31:48 +00:00
Nico MerenqandWeblate c1c064e657 Translated using Weblate (Russian)
Currently translated at 52.4% (4653 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ru/
2025-08-04 20:35:53 +00:00
washashu0905andWeblate c0a509820f Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 95.8% (8498 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/zh_Hant/
2025-08-04 20:30:32 +00:00
Nico MerenqandWeblate aa0b71aad7 Translated using Weblate (Russian)
Currently translated at 52.4% (4651 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ru/
2025-08-04 20:30:31 +00:00
Nico MerenqandWeblate 55c5dfddc4 Translated using Weblate (Russian)
Currently translated at 51.9% (4610 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ru/
2025-08-04 16:16:22 +00:00
Nico MerenqandWeblate b40c69a1d9 Translated using Weblate (Russian)
Currently translated at 51.9% (4610 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ru/
2025-08-04 16:15:06 +00:00
Watuneni iteiteandWeblate ca20f432b3 Translated using Weblate (Japanese)
Currently translated at 81.6% (7238 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ja/
2025-08-04 16:15:05 +00:00
Watuneni iteiteandWeblate 63fe61b913 Translated using Weblate (Japanese)
Currently translated at 80.9% (7177 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ja/
2025-08-04 15:53:50 +00:00
Nico MerenqandWeblate 7605b526c8 Translated using Weblate (Russian)
Currently translated at 51.9% (4606 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ru/
2025-08-04 15:53:34 +00:00
Watuneni iteiteandWeblate 189f39f1c1 Translated using Weblate (Japanese)
Currently translated at 80.9% (7176 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ja/
2025-08-04 15:53:33 +00:00
Nico MerenqandWeblate 8c718069c7 Translated using Weblate (Russian)
Currently translated at 51.7% (4586 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ru/
2025-08-04 14:15:27 +00:00
Nico MerenqandWeblate c2facdf5ce Translated using Weblate (Russian)
Currently translated at 51.1% (4539 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ru/
2025-08-04 10:23:03 +00:00
Watuneni iteiteandWeblate 0c5dcb26f6 Translated using Weblate (Japanese)
Currently translated at 79.8% (7084 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ja/
2025-08-04 10:23:02 +00:00
Watuneni iteiteandWeblate b88634691b Translated using Weblate (Japanese)
Currently translated at 79.8% (7082 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ja/
2025-08-04 08:28:46 +00:00
Watuneni iteiteandWeblate df9dcdd622 Translated using Weblate (Japanese)
Currently translated at 79.7% (7070 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ja/
2025-08-04 07:51:11 +00:00
Watuneni iteiteandWeblate 15943fae47 Translated using Weblate (Japanese)
Currently translated at 79.6% (7069 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ja/
2025-08-04 07:50:58 +00:00
Watuneni iteiteandWeblate 78a71ba252 Translated using Weblate (Japanese)
Currently translated at 79.6% (7064 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ja/
2025-08-04 07:46:32 +00:00
Watuneni iteiteandWeblate 7f1acd16b5 Translated using Weblate (Japanese)
Currently translated at 79.5% (7060 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ja/
2025-08-04 07:43:04 +00:00
Watuneni iteiteandWeblate be038d2348 Translated using Weblate (Japanese)
Currently translated at 78.9% (7004 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ja/
2025-08-04 06:54:08 +00:00
阿鱼andWeblate b3b11012f4 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 99.5% (8832 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/zh_Hans/
2025-08-04 03:55:56 +00:00
Watuneni iteiteandWeblate 095c466b89 Translated using Weblate (Japanese)
Currently translated at 78.8% (6992 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ja/
2025-08-03 19:29:01 +00:00
Watuneni iteiteandWeblate 4c3cd6fdc4 Translated using Weblate (Japanese)
Currently translated at 78.2% (6943 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ja/
2025-08-03 17:05:25 +00:00
Watuneni iteiteandWeblate 2379b22135 Translated using Weblate (Japanese)
Currently translated at 77.7% (6893 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ja/
2025-08-03 16:18:02 +00:00
Watuneni iteiteandWeblate f36fa861da Translated using Weblate (Japanese)
Currently translated at 77.5% (6876 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ja/
2025-08-03 16:12:31 +00:00
Watuneni iteiteandWeblate 3b4f9d0fba Translated using Weblate (Japanese)
Currently translated at 77.0% (6838 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ja/
2025-08-03 15:47:39 +00:00
Watuneni iteiteandWeblate 42dabbf16a Translated using Weblate (Japanese)
Currently translated at 75.6% (6714 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ja/
2025-08-03 14:46:18 +00:00
Watuneni iteiteandWeblate 07abff7583 Translated using Weblate (Japanese)
Currently translated at 75.1% (6665 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ja/
2025-08-03 13:05:07 +00:00
Nico MerenqandWeblate dbd9bfacc5 Translated using Weblate (Russian)
Currently translated at 50.5% (4486 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ru/
2025-08-03 11:59:16 +00:00
Nico MerenqandWeblate 6ed1ea0cad Translated using Weblate (Russian)
Currently translated at 50.5% (4486 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ru/
2025-08-03 11:33:17 +00:00
YasitoandWeblate 5384fec57e Translated using Weblate (French)
Currently translated at 100.0% (8870 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2025-08-03 04:33:05 +00:00
YasitoandWeblate 1868bcf4d7 Translated using Weblate (French (Canada))
Currently translated at 100.0% (8870 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr_CA/
2025-08-03 04:33:04 +00:00
YasitoandWeblate 42059b9931 Translated using Weblate (French)
Currently translated at 100.0% (8870 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2025-08-03 04:02:52 +00:00
YasitoandWeblate 24890225a2 Translated using Weblate (French (Canada))
Currently translated at 100.0% (8870 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr_CA/
2025-08-03 04:02:52 +00:00
YasitoandWeblate e4e32769dc Translated using Weblate (French)
Currently translated at 100.0% (8870 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2025-08-03 03:50:45 +00:00
YasitoandWeblate 3420422a4b Translated using Weblate (French (Canada))
Currently translated at 100.0% (8870 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr_CA/
2025-08-03 03:50:44 +00:00
YasitoandWeblate 18564baec7 Translated using Weblate (French)
Currently translated at 100.0% (8870 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2025-08-03 03:43:40 +00:00
YasitoandWeblate b0c6d83913 Translated using Weblate (French (Canada))
Currently translated at 100.0% (8870 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr_CA/
2025-08-03 03:43:39 +00:00
YasitoandWeblate 5016b2b227 Translated using Weblate (French)
Currently translated at 99.8% (8860 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2025-08-03 03:38:25 +00:00
YasitoandWeblate 4727321d47 Translated using Weblate (French)
Currently translated at 99.8% (8857 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2025-08-03 03:37:46 +00:00
YasitoandWeblate 6e96e92d33 Translated using Weblate (French (Canada))
Currently translated at 100.0% (8870 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr_CA/
2025-08-03 03:37:45 +00:00
YasitoandWeblate a8c92ce641 Translated using Weblate (French)
Currently translated at 99.8% (8854 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2025-08-03 03:32:47 +00:00
YasitoandWeblate 032d2e5c95 Translated using Weblate (French (Canada))
Currently translated at 100.0% (8870 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr_CA/
2025-08-03 03:32:46 +00:00
YasitoandWeblate ec9d7d9dae Translated using Weblate (French)
Currently translated at 99.8% (8853 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2025-08-03 03:26:48 +00:00
YasitoandWeblate 217dab4dea Translated using Weblate (French (Canada))
Currently translated at 100.0% (8870 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr_CA/
2025-08-03 03:26:47 +00:00
YasitoandWeblate 452a92daa9 Translated using Weblate (French (Canada))
Currently translated at 99.9% (8862 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr_CA/
2025-08-03 03:12:08 +00:00
YasitoandWeblate fb364deff9 Translated using Weblate (French)
Currently translated at 99.5% (8831 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2025-08-03 03:06:55 +00:00
YasitoandWeblate ac824ce39c Translated using Weblate (French (Canada))
Currently translated at 99.8% (8859 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr_CA/
2025-08-03 03:06:54 +00:00
Nico MerenqandWeblate fa74a1c45a Translated using Weblate (Russian)
Currently translated at 50.5% (4480 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ru/
2025-08-02 13:19:58 +00:00
Nico MerenqandWeblate 1cce1d1a44 Translated using Weblate (Russian)
Currently translated at 50.1% (4451 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ru/
2025-08-02 12:39:24 +00:00
YasitoandWeblate b7f537e34c Translated using Weblate (French)
Currently translated at 99.5% (8830 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2025-07-31 04:25:44 +00:00
YasitoandWeblate 0311a8a4ef Translated using Weblate (French (Canada))
Currently translated at 99.5% (8830 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr_CA/
2025-07-31 04:25:43 +00:00
YasitoandWeblate 14ecc13cb3 Translated using Weblate (French)
Currently translated at 99.5% (8830 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2025-07-31 03:53:38 +00:00
YasitoandWeblate 5baca56d6d Translated using Weblate (French (Canada))
Currently translated at 99.5% (8830 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr_CA/
2025-07-31 03:53:37 +00:00
YasitoandWeblate 91c9692db1 Translated using Weblate (French)
Currently translated at 99.5% (8830 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2025-07-31 03:52:50 +00:00
YasitoandWeblate bb7b1a8dee Translated using Weblate (French (Canada))
Currently translated at 99.5% (8830 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr_CA/
2025-07-31 03:52:49 +00:00
YasitoandWeblate e6322e5fb7 Translated using Weblate (French)
Currently translated at 99.1% (8798 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2025-07-31 03:42:06 +00:00
YasitoandWeblate 3c5f04b52a Translated using Weblate (French (Canada))
Currently translated at 99.4% (8825 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr_CA/
2025-07-31 03:42:05 +00:00
YasitoandWeblate 5d78a5b573 Translated using Weblate (French)
Currently translated at 99.1% (8796 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2025-07-31 03:39:37 +00:00
YasitoandWeblate 4f58615065 Translated using Weblate (French (Canada))
Currently translated at 99.4% (8823 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr_CA/
2025-07-31 03:39:36 +00:00
YasitoandWeblate 32a4161a69 Translated using Weblate (French)
Currently translated at 99.1% (8793 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2025-07-30 16:09:48 +00:00
YasitoandWeblate c5708145cd Translated using Weblate (French (Canada))
Currently translated at 99.1% (8793 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr_CA/
2025-07-30 16:09:47 +00:00
KakunoandWeblate 118f207adb Translated using Weblate (French)
Currently translated at 98.7% (8762 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2025-07-30 03:59:57 +00:00
YasitoandWeblate a5826028b3 Translated using Weblate (French)
Currently translated at 98.7% (8762 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2025-07-30 03:59:57 +00:00
YasitoandWeblate fa3f6053c3 Translated using Weblate (French (Canada))
Currently translated at 98.7% (8762 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr_CA/
2025-07-30 03:59:56 +00:00
KakunoandWeblate bc60fe58b1 Translated using Weblate (French)
Currently translated at 98.7% (8756 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2025-07-30 03:51:13 +00:00
YasitoandWeblate f9a5e9a9e4 Translated using Weblate (French)
Currently translated at 98.7% (8756 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2025-07-30 03:51:13 +00:00
YasitoandWeblate 6f6ee6f160 Translated using Weblate (French (Canada))
Currently translated at 98.7% (8755 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr_CA/
2025-07-30 03:51:12 +00:00
KakunoandWeblate 8a3dbd5e4a Translated using Weblate (French)
Currently translated at 98.6% (8754 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2025-07-30 03:48:18 +00:00
YasitoandWeblate e052ad2281 Translated using Weblate (French)
Currently translated at 98.6% (8754 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2025-07-30 03:48:17 +00:00
YasitoandWeblate 8938ea13a4 Translated using Weblate (French (Canada))
Currently translated at 98.6% (8753 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr_CA/
2025-07-30 03:48:17 +00:00
YasitoandWeblate 907aed4c63 Translated using Weblate (French)
Currently translated at 98.6% (8752 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2025-07-30 03:45:17 +00:00
KakunoandWeblate 60798aae03 Translated using Weblate (French)
Currently translated at 98.6% (8752 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2025-07-30 03:45:16 +00:00
YasitoandWeblate 6f09957fbf Translated using Weblate (French (Canada))
Currently translated at 98.6% (8751 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr_CA/
2025-07-30 03:45:16 +00:00
KakunoandWeblate 6fc2344473 Translated using Weblate (French)
Currently translated at 98.5% (8741 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2025-07-30 03:38:53 +00:00
YasitoandWeblate c82059ae72 Translated using Weblate (French)
Currently translated at 98.5% (8741 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2025-07-30 03:38:53 +00:00
YasitoandWeblate a080db690f Translated using Weblate (French (Canada))
Currently translated at 98.5% (8740 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr_CA/
2025-07-30 03:38:53 +00:00
KakunoandWeblate d82849ddd3 Translated using Weblate (French)
Currently translated at 98.5% (8740 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2025-07-30 03:36:11 +00:00
YasitoandWeblate 71a3fe6e48 Translated using Weblate (French)
Currently translated at 98.5% (8740 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2025-07-30 03:36:11 +00:00
YasitoandWeblate 5e934ac7a6 Translated using Weblate (French (Canada))
Currently translated at 98.5% (8739 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr_CA/
2025-07-30 03:36:10 +00:00
Natchakot ChuchokjatupornandWeblate 6aa5bde424 Translated using Weblate (Thai)
Currently translated at 54.3% (4818 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/th/
2025-07-30 03:22:26 +00:00
KakunoandWeblate fee7488090 Translated using Weblate (French)
Currently translated at 98.3% (8726 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2025-07-30 03:22:25 +00:00
YasitoandWeblate f5e5e3411e Translated using Weblate (French (Canada))
Currently translated at 98.3% (8725 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr_CA/
2025-07-30 03:22:24 +00:00
Natchakot ChuchokjatupornandWeblate 818f9365ec Translated using Weblate (Thai)
Currently translated at 52.6% (4669 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/th/
2025-07-29 03:34:54 +00:00
KakunoandWeblate f81db9df3d Translated using Weblate (French)
Currently translated at 98.3% (8722 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2025-07-29 03:34:52 +00:00
YasitoandWeblate 8162e3a30d Translated using Weblate (French)
Currently translated at 98.3% (8722 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2025-07-29 03:34:52 +00:00
YasitoandWeblate 5a00bbf02d Translated using Weblate (French (Canada))
Currently translated at 98.3% (8722 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr_CA/
2025-07-29 03:34:50 +00:00
Natchakot ChuchokjatupornandWeblate ff7fff52ec Translated using Weblate (Thai)
Currently translated at 52.0% (4620 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/th/
2025-07-29 03:23:48 +00:00
KakunoandWeblate 087fab4065 Translated using Weblate (French)
Currently translated at 98.2% (8719 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2025-07-29 03:23:48 +00:00
YasitoandWeblate bed2897950 Translated using Weblate (French)
Currently translated at 98.2% (8719 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2025-07-29 03:23:47 +00:00
YasitoandWeblate d51f0711b7 Translated using Weblate (French (Canada))
Currently translated at 98.2% (8719 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr_CA/
2025-07-29 03:23:46 +00:00
Felipe Bermudes OrmeñoandWeblate 728860332d Translated using Weblate (Spanish (Mexico))
Currently translated at 26.9% (2390 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/es_MX/
2025-07-29 03:23:45 +00:00
Felipe Bermudes OrmeñoandWeblate fe0ca14b3b Translated using Weblate (Spanish (Mexico))
Currently translated at 26.2% (2330 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/es_MX/
2025-07-28 23:07:55 +00:00
YasitoandWeblate 7e57c3c862 Translated using Weblate (French)
Currently translated at 96.7% (8586 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2025-07-28 17:41:18 +00:00
YasitoandWeblate 0f4fdfcedd Translated using Weblate (French (Canada))
Currently translated at 96.7% (8586 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr_CA/
2025-07-28 17:41:17 +00:00
Felipe Bermudes OrmeñoandWeblate 1cb77f35b5 Translated using Weblate (Spanish (Mexico))
Currently translated at 25.3% (2247 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/es_MX/
2025-07-28 17:41:16 +00:00
阿鱼andWeblate 9ac23be59b Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 99.5% (8832 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/zh_Hans/
2025-07-28 01:58:42 +00:00
KakunoandWeblate e469409a22 Translated using Weblate (French)
Currently translated at 96.6% (8572 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2025-07-28 01:58:41 +00:00
YasitoandWeblate 28db8e98d7 Translated using Weblate (French)
Currently translated at 96.6% (8572 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2025-07-28 01:58:41 +00:00
YasitoandWeblate 08bf3d4f8f Translated using Weblate (French (Canada))
Currently translated at 96.6% (8572 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr_CA/
2025-07-28 01:58:39 +00:00
Felipe Bermudes OrmeñoandWeblate 88380bfb76 Translated using Weblate (Spanish (Mexico))
Currently translated at 25.2% (2240 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/es_MX/
2025-07-28 01:58:38 +00:00
KakunoandWeblate 83d7cbbefa Translated using Weblate (French)
Currently translated at 96.0% (8524 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2025-07-27 23:23:04 +00:00
YasitoandWeblate 426d7c8e5e Translated using Weblate (French)
Currently translated at 96.0% (8524 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2025-07-27 23:23:04 +00:00
YasitoandWeblate 602d88db5e Translated using Weblate (French (Canada))
Currently translated at 95.8% (8506 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr_CA/
2025-07-27 23:23:03 +00:00
Felipe Bermudes OrmeñoandWeblate 751d3be872 Translated using Weblate (Spanish (Mexico))
Currently translated at 25.0% (2220 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/es_MX/
2025-07-27 23:23:00 +00:00
KakunoandWeblate 358a62d5b1 Translated using Weblate (French)
Currently translated at 96.0% (8521 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2025-07-27 22:45:12 +00:00
YasitoandWeblate 409814823b Translated using Weblate (French (Canada))
Currently translated at 95.7% (8490 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr_CA/
2025-07-27 22:45:11 +00:00
KakunoandWeblate 5d8f45b058 Translated using Weblate (French)
Currently translated at 96.0% (8520 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2025-07-27 22:42:03 +00:00
YasitoandWeblate 3001e8bf84 Translated using Weblate (French (Canada))
Currently translated at 95.6% (8488 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr_CA/
2025-07-27 22:42:02 +00:00
阿鱼andWeblate 80de268987 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 99.5% (8832 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/zh_Hans/
2025-07-27 22:40:38 +00:00
KakunoandWeblate b08004b6c6 Translated using Weblate (French)
Currently translated at 96.0% (8519 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2025-07-27 22:40:37 +00:00
YasitoandWeblate 2783a52495 Translated using Weblate (French)
Currently translated at 96.0% (8519 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2025-07-27 22:40:36 +00:00
YasitoandWeblate 3ae3f0db95 Translated using Weblate (French (Canada))
Currently translated at 95.6% (8486 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr_CA/
2025-07-27 22:40:35 +00:00
阿鱼andWeblate a15f026739 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 99.5% (8832 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/zh_Hans/
2025-07-27 18:38:05 +00:00
阿鱼andWeblate bc8ed8bb38 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 98.9% (8776 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/zh_Hans/
2025-07-27 16:54:12 +00:00
阿鱼andWeblate efa246272b Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 98.7% (8758 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/zh_Hans/
2025-07-27 05:55:51 +00:00
阿鱼andWeblate e29d84a71a Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 97.1% (8617 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/zh_Hans/
2025-07-26 18:52:35 +00:00
阿鱼andWeblate 6c671b5e08 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 96.9% (8598 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/zh_Hans/
2025-07-25 22:35:43 +00:00
Weblate 3ffd9ecc28 Update translation files
Updated by "Remove blank strings" add-on in Weblate.

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/
2025-07-25 07:41:41 +00:00
阿鱼andWeblate 281131f5b3 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 96.3% (8543 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/zh_Hans/
2025-07-25 07:41:37 +00:00
Nico MerenqandWeblate 3c67cd2a59 Translated using Weblate (Russian)
Currently translated at 49.7% (4411 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ru/
2025-07-25 07:41:36 +00:00
阿鱼andWeblate 74f5c69bb5 Translated using Weblate (French)
Currently translated at 95.9% (8513 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2025-07-25 07:41:35 +00:00
TeggyandWeblate 8f82e1d960 Translated using Weblate (Spanish)
Currently translated at 85.3% (7567 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/es/
2025-07-25 07:41:34 +00:00
Nico MerenqandWeblate c5497cfb2b Translated using Weblate (Russian)
Currently translated at 49.6% (4406 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ru/
2025-07-22 22:10:58 +00:00
Nico MerenqandWeblate 94b6b3b948 Translated using Weblate (Russian)
Currently translated at 49.6% (4405 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ru/
2025-07-22 21:58:42 +00:00
KakunoandWeblate 8855c5ad7f Translated using Weblate (French)
Currently translated at 95.9% (8513 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2025-07-22 15:14:45 +00:00
Alexis SalgueiroandWeblate 5015a0a56c Translated using Weblate (French)
Currently translated at 95.9% (8513 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2025-07-22 15:14:44 +00:00
KakunoandWeblate ec51fc0701 Translated using Weblate (French)
Currently translated at 95.9% (8512 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2025-07-22 14:33:22 +00:00
Alexis SalgueiroandWeblate 13287e6f76 Translated using Weblate (French)
Currently translated at 95.9% (8512 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2025-07-22 14:33:22 +00:00
KakunoandWeblate a65dfcd3cd Translated using Weblate (French)
Currently translated at 95.9% (8509 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2025-07-22 14:28:56 +00:00
KakunoandWeblate 18183dd649 Translated using Weblate (French)
Currently translated at 95.9% (8508 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2025-07-22 14:26:11 +00:00
Alexis SalgueiroandWeblate 40c1f074bb Translated using Weblate (French)
Currently translated at 95.9% (8508 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2025-07-22 14:26:10 +00:00
KakunoandWeblate 5d1219e467 Translated using Weblate (French)
Currently translated at 95.6% (8482 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2025-07-22 14:18:30 +00:00
KakunoandWeblate 568773f1a8 Translated using Weblate (French)
Currently translated at 95.6% (8481 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2025-07-22 14:18:08 +00:00
Alexis SalgueiroandWeblate f0c3cde213 Translated using Weblate (French)
Currently translated at 95.6% (8481 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/fr/
2025-07-22 14:18:08 +00:00
Tobias (Targ)andWeblate def5d09903 Translated using Weblate (German)
Currently translated at 58.8% (5218 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/de/
2025-07-18 13:37:42 +00:00
Watuneni iteiteandWeblate ad5e2ddc41 Translated using Weblate (Japanese)
Currently translated at 75.0% (6661 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ja/
2025-07-16 08:59:20 +00:00
TeggyandWeblate 2d2f92c7a7 Translated using Weblate (Spanish)
Currently translated at 84.2% (7470 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/es/
2025-07-15 18:19:05 +00:00
Watuneni iteiteandWeblate 2bd4d1fb08 Translated using Weblate (Japanese)
Currently translated at 74.0% (6565 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ja/
2025-07-13 18:01:49 +00:00
TeggyandWeblate 4c342c2047 Translated using Weblate (Spanish)
Currently translated at 83.1% (7373 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/es/
2025-07-13 18:01:48 +00:00
Watuneni iteiteandWeblate 24cee748d5 Translated using Weblate (Japanese)
Currently translated at 73.8% (6548 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ja/
2025-07-10 18:47:55 +00:00
TeggyandWeblate fbec6f03e7 Translated using Weblate (Spanish)
Currently translated at 81.1% (7199 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/es/
2025-07-10 18:47:52 +00:00
TeggyandWeblate 8ab0288ab2 Translated using Weblate (Spanish)
Currently translated at 79.7% (7072 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/es/
2025-07-10 06:04:53 +00:00
Nico MerenqandWeblate cc426a5ca9 Translated using Weblate (Russian)
Currently translated at 49.6% (4403 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ru/
2025-07-08 22:17:00 +00:00
TeggyandWeblate e4f04b605a Translated using Weblate (Spanish)
Currently translated at 79.1% (7025 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/es/
2025-07-08 22:16:58 +00:00
TeggyandWeblate 48441b1232 Translated using Weblate (Spanish)
Currently translated at 76.0% (6748 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/es/
2025-07-06 09:38:57 +00:00
Watuneni iteiteandWeblate 589d3614b8 Translated using Weblate (Japanese)
Currently translated at 68.7% (6099 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ja/
2025-07-05 04:12:04 +00:00
TeggyandWeblate fe386095de Translated using Weblate (Spanish)
Currently translated at 75.3% (6687 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/es/
2025-07-04 22:04:28 +00:00
Watuneni iteiteandWeblate 5640177c99 Translated using Weblate (Japanese)
Currently translated at 68.7% (6097 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ja/
2025-07-04 11:45:00 +00:00
Zack the GhastandWeblate ff8c8b1bfa Translated using Weblate (Portuguese (Portugal))
Currently translated at 67.3% (5973 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/pt_PT/
2025-07-04 05:53:12 +00:00
Watuneni iteiteandWeblate 4bccd9b5aa Translated using Weblate (Japanese)
Currently translated at 64.2% (5702 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ja/
2025-07-04 05:53:11 +00:00
阿鱼andWeblate 8c1c5ac957 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 95.8% (8498 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/zh_Hans/
2025-07-03 05:30:56 +00:00
Watuneni iteiteandWeblate a20ec4d1c5 Translated using Weblate (Japanese)
Currently translated at 64.1% (5694 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ja/
2025-07-03 05:30:55 +00:00
TeggyandWeblate 12b4e58dea Translated using Weblate (Spanish)
Currently translated at 75.1% (6669 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/es/
2025-07-03 05:30:53 +00:00
JazzMcNade be8dfd7fa2 Utilize setPositionSafely in breakFree() 2025-07-01 22:04:20 -05:00
Zack the GhastandWeblate 940ec82182 Translated using Weblate (Portuguese (Portugal))
Currently translated at 67.1% (5952 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/pt_PT/
2025-07-02 02:26:55 +00:00
KyoandWeblate 67c01ce5f2 Translated using Weblate (Vietnamese)
Currently translated at 65.8% (5842 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/vi/
2025-07-01 12:38:56 +00:00
washashu0905andWeblate e603325eef Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 94.6% (8398 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/zh_Hant/
2025-07-01 12:38:54 +00:00
阿鱼andWeblate afd09e93eb Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 95.7% (8497 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/zh_Hans/
2025-07-01 12:38:53 +00:00
Zack the GhastandWeblate 92775a4b8a Translated using Weblate (Portuguese (Portugal))
Currently translated at 66.2% (5877 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/pt_PT/
2025-07-01 12:38:52 +00:00
Watuneni iteiteandWeblate 6a2668171e Translated using Weblate (Japanese)
Currently translated at 63.2% (5607 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/ja/
2025-07-01 12:38:50 +00:00
TeggyandWeblate 5b7dc9f8f4 Translated using Weblate (Spanish)
Currently translated at 74.2% (6583 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/es/
2025-07-01 12:38:47 +00:00
washashu0905andWeblate 75dc76020f Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 92.5% (8209 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/zh_Hant/
2025-06-29 06:11:55 +00:00
阿鱼andWeblate 56289ce5bf Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 95.7% (8496 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/zh_Hans/
2025-06-29 06:11:53 +00:00
Zack the GhastandWeblate e4ec7b0ae7 Translated using Weblate (Portuguese (Portugal))
Currently translated at 60.7% (5387 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/pt_PT/
2025-06-29 06:11:52 +00:00
Anonymous f38ef0b6b9 Translated using Weblate (Vietnamese)
Currently translated at 63.0% (5592 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/vi/
2025-06-29 01:50:06 +00:00
阿鱼andWeblate d5bb3e2d97 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 95.3% (8458 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/zh_Hans/
2025-06-29 01:50:04 +00:00
Zack the GhastandWeblate 66f6555711 Translated using Weblate (Portuguese (Portugal))
Currently translated at 60.4% (5362 of 8870 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/pt_PT/
2025-06-29 01:50:00 +00:00
Zack the GhastandWeblate 472ca9c748 Translated using Weblate (Portuguese (Portugal))
Currently translated at 63.4% (5398 of 8505 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/pt_PT/
2025-06-29 00:30:29 +00:00
阿鱼andWeblate 054e4dffdc Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 98.2% (8358 of 8505 strings)

Translation: Cobblemon/Mod
Translate-URL: http://lang.cobblemon.com/projects/cobblemon/mod/zh_Hans/
2025-06-29 00:30:29 +00:00
1953 changed files with 214322 additions and 48315 deletions
+2 -1
View File
@@ -12,7 +12,8 @@ out
*.ipr
*.iws
*.iml
.idea
.idea/*
!.idea/icon.png
# gradle
build
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

+9 -1
View File
@@ -12,6 +12,14 @@
- SpawningContext is now SpawnablePosition
- WorldSlice is SpawningZone
- SpawningProspector is now SpawningZoneGenerator
- Majorly refactored the hierarchy of Spawner
- The base Spawner interface provides more functions to allow single-point and area spawning given appropriate inputs.
- TickingSpawner is removed in favour of outside code handling ticking logic.
- AreaSpawner is removed.
- BasicSpawner is the first implementation of Spawner which can be used for any purpose.
- PlayerSpawners are now mixin'd into ServerPlayer and ticked from ServerPlayer#tick.
- A hierarchy diagram can be found at `./docs/spawner-hierarchy.png` in the mod repository.
- The SpawnerManager class has been removed as its functionality is all now handled elsewhere.
- Renamed things in Spawn Rules to go with the other renames:
- contextSelector is now spawnablePositionSelector
- contextSelector is now spawnablePositionSelector
- context is now spawnable_position
+78 -1
View File
@@ -2,6 +2,7 @@
## [1.7.0 (Month xth, 2025)](#1-7-0)
### Additions
- Added `/npcdelete` uuid parameter support and usuable to console
- Added `/spectateBattle <player>` command to spectate battles without having to manually walk up to the target.
- Added an in-game configuration screen, allowing all settings from `main.json` to be edited directly in-game.
- Added `/cobblemonconfig reload` command to reload `main.json` configuration. **Note:** Some settings require a server restart to take effect; use this command cautiously.
@@ -33,6 +34,7 @@
- Added Hyper Training items (IV Modification) as well as some additional candy items to do so (Health Candy, Sickly Candy)
- Added Galarica Nut Bushes
- Many Pokémon (mostly cats) are now feared by phantoms
- Combees are now capable of gathering nectar from flowers and delivering it to either Saccharine leaves or hives. (Hive interactions are restricted to wild Combees)
- Lightning is now affected by a Pokemon's ability/typing
- Pokémon with the ability Lightning Rod draw in lightning similar to a lightning rod block albeit with a lower priority and range, gain immunity to lightning damage, and receive a temporary damage buff.
- Pokémon with the ability Motor Drive are immune to lightning damage and receive a temporary speed buff when struck by lightning
@@ -47,11 +49,15 @@
- Berries can now be smelted into their respective dyes.
- Added Syrupy Apples.
- Added `/runmolang <molang> [<npc>|<player>|<pokemon>]` command that executes a MoLang expression with the provided options as environment variables, as well as the entity (as `q.entity`) that executed the command.
- Added new 69th berry, Eggant.
- Added bubble quirk to Kingler; like Krabby, Kingler will blows bubbles during dusk.
- Added `.Pre` and `.Post` to the following events:
- `PokemonRecallEvent`
- `TradeEvent`
- Added a new gamerule, 'healerHealsPC', when set to true a successful use of a healer will also heal all of the Pokemon in that player's PC.
- Mooshtanks will switch between their red and brown variant when hit by lightning
- Smeargle spawns with differing tail colour depending on its [Characteristic](https://bulbapedia.bulbagarden.net/wiki/Characteristic) stat
- Added smooth level-up animations to the party overlay to replace chat messages.
### Pokémon Added
@@ -83,6 +89,8 @@
- Altaria
- Snorunt
- Glalie
- Shuppet
- Banette
- Latias
- Latios
@@ -131,6 +139,11 @@
- Swadloon
- Leavanny
- Druddigon
- Minccino
- Cinccino
- Vanillite
- Vanillish
- Vanilluxe
#### Gen 6
- Skiddo
@@ -292,6 +305,24 @@
- Tauros - Paldea Blaze
- Tauros - Paldea Combat
- Dewgong
- Exeggutor - Kanto
- Corphish (updated placeholders for crustaceous tripod gait legs)
- Crawdaunt (updated placeholders for crustaceous bipedal legs)
- Dragapult
- Camerupt
- Relicanth
- Heracross
- Salamence
- Staraptor
- Dusknoir
- Arcanine
- Carvanha
- Sharpedo
- Mimikyu
- Dewgong
- Mime Jr
- Gyarados
- Lapras
### Model updates for the following Pokémon
- Cleffa
@@ -319,6 +350,8 @@
- Nosepass
- Probopass
- Kangaskhan
- Scorbunny
- Raboot
- Cinderace
- Magnemite
- Magneton
@@ -402,6 +435,21 @@
- Revavroom
- Nickit
- Thievul
- Litwick
- Lampent
- Chandelure
- Rayquaza
- Aerodactyl
- Basculin
- Basculegion
- Shroodle
- Grafaiai
- Steelix
- Dratini
- Dragonair
- Dragonite
- Cutiefly
- Ribombee
### Changes
- Changed pokemon caught and seen count to update based on the current pokedex being looked
@@ -415,6 +463,8 @@
- Updated some item tags to better integrate behaviours between Cobblemon, Vanilla Minecraft, and other mods
- Removed Cooked Meat, Raw Meat, Protein Ingredients, and Zinc Ingredients, the first two are now using `c` namespace tags, the latter have better integrated use of tags within their recipes which removes need for custom tags.
- Added our seeds tag into `#c:seeds`, which is now made use of for the Miracle Seed recipe.
- Updated the `#cobblemon:berries` tag to use subtags located in `data/cobblemon/tags/item/berries`, such as `#cobblemon:berries/hp_recovery` for sorting purposes
- Moved the tag `#cobblemon:mutated_berries` to `#cobblemon:berries/mutation_result`
- Added herbs and snowballs to the consumable in PvE and Wild battle tags
- Tweaked the Natural Materials Vanilla file to fit with the changes to tags
- Substantially optimised spawning checks mainly by front-loading biome filtering.
@@ -466,6 +516,8 @@
- Updated `HatchEggEvent.Post` to include the Pokemon that hatched.
- Roseli Berry Trees now naturally generate in their preferred biomes.
- Refactored dynamic lights compat to be more future proof. The dynamic light support is only tested with LambDynamicLights, on NeoForged use that mod in combination with Sinytra Connector
- Shulker Boxes and Traveler's Backpacks AND Packed Up backpacks can no longer be held by Pokémon. Thanks Monocle ;) You could re-enable this with our datapack (but you'd be crazy).
- Moomoo Milk now clears Pokémon stat changes when used in battle.
### Fixes
- Fixed game crashing when removing national pokedex using datapacks
@@ -549,6 +601,11 @@
- Fixed busted abilities and moves in Pokémon data due to removed datapacks etc. causing storage corruption. It now just rerolls their ability / uses Tackle.
- Fixed singular Pokémon corruption causing entire storage corruption. Storages will now skip corrupted Pokémon and print an error to console.
- Fixed species additions not being able to properly mark a species as implemented.
- Fixed Pokémon item models not showing a glint when enchanted.
- Fixed some specific bag items not being dropped when used in battle.
- The Corphish line will now sink in water.
- Fixed the "use all berry bait" achievement not being progressed
- Fixed bobber hook and berry sprouts texture sizes causing mipmap issues.
### Developer
- A finished battle now has winners and losers set inside of `PokemonBattle` instead of them always being empty.
@@ -586,10 +643,25 @@
- Added a `hoverText` option to PartySelectCallback, to display a tooltip on hovering over a Pokémon in the selection screen.
- `PokemonEntity` instances spawned into the world now appropriately finalize the spawn for mod compatibility.
- Added PokedexManager.obtain as a replacement for .catch which is not a friendly function name in Java.
- Added `Pokemon#hyperTrainIV()` and `IVs#setHyperTrainedIV(Stat, Int)`
- `ElementalType` now implements `ShowdownIdentifiable` to ensure the communcation with showdown stays consistent (also in regards to TeraTypes)
- Pokemon no longer have a change observable
- Pokémon can now have a behaviour changing the value of a species feature on lightning hit:
```JSON
{
"behaviour": {
"lightningHit": {
"rotateFeatures": [
{
"key": "mooshtank",
"chain": ["red", "brown"]
}
]
}
}
}
```
This will change the value of `mooshtank` from `red` to `brown` and vice versa when a lightning strikes this Pokémon. Only chains of which a value is the current value will be considered, so multiple entries for the same key with different chains can be added.
### 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)
@@ -693,6 +765,10 @@
- Mechanics
- Unlockable Wallpapers
- Starter Data
- Fixed `cobblemon:reel_in_pokemon` criteria not working when used together with a `baitId`
- Also changed the default from `cobblemon:empty_bait` to `any`
- The previous default is still available by using the above as baitId
- Added support for species-specific move action effects, using the format `{move_id}_{species}.json`.
### Particles
Added new/updated particles for the following moves:
@@ -988,6 +1064,7 @@ Added new/updated particles for the following moves:
- Parametric particle motion now works.
- Event-spawned particles now work.
- Particles can now have independent coordinate spaces.
- Fixed suffocation of Pokémon when a pokeball breaks near a wall
### Developer
- Updated the Pokédex data updated events to always include a `Pokemon` instance, and optionally a `DisguiseData` instance.
@@ -0,0 +1,23 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common;
import com.cobblemon.mod.common.api.spawning.spawner.PlayerSpawner;
/**
* Used for mixin shenanigans. ServerPlayers now store their Cobblemon spawner
* inside, so this interface helps execute that.
*
* @author Hiroku
* @since October 27th, 2025
*/
public interface PlayerSpawnerAccessor {
PlayerSpawner getPlayerSpawner();
void setPlayerSpawner(PlayerSpawner spawner);
}
@@ -0,0 +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.duck;
import net.minecraft.world.phys.Vec3;
public interface RidePassenger {
void cobblemon$setRideXRot(float rideXRot);
float cobblemon$getRideXRot();
void cobblemon$setRideYRot(float rideYRot);
float cobblemon$getRideYRot();
void cobblemon$setRideEyePos(Vec3 rideEyePos);
Vec3 cobblemon$getRideEyePos();
}
@@ -0,0 +1,51 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.mixin;
import com.cobblemon.mod.common.OrientationControllable;
import com.cobblemon.mod.common.duck.RidePassenger;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.projectile.AbstractArrow;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(AbstractArrow.class)
public abstract class AbstractArrowMixin {
@Inject(
method = "<init>(Lnet/minecraft/world/entity/EntityType;Lnet/minecraft/world/entity/LivingEntity;Lnet/minecraft/world/level/Level;Lnet/minecraft/world/item/ItemStack;Lnet/minecraft/world/item/ItemStack;)V",
at = @At("TAIL")
)
private void cobblemon$adjustSpawnPosition(
EntityType<? extends AbstractArrow> entityType,
LivingEntity owner,
Level level,
ItemStack pickupItemStack,
@Nullable ItemStack firedFromWeapon,
CallbackInfo ci
) {
AbstractArrow arrow = (AbstractArrow)(Object)this;
if(!(owner.getVehicle() instanceof OrientationControllable vehicle)) return;
if(!(vehicle instanceof PokemonEntity)) return;
var vehicleController = vehicle.getOrientationController();
// If this is a pokemon ride passenger then use the rider eye position to set the arrow spawn position.
if (owner instanceof RidePassenger ridePassenger && vehicleController != null ) {
var eyePos = ridePassenger.cobblemon$getRideEyePos(); //.add(0.0, -2.0, 0.0);
arrow.setPos(eyePos);
}
}
}
@@ -0,0 +1,107 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.mixin;
import com.cobblemon.mod.common.CobblemonMemories;
import com.cobblemon.mod.common.api.ai.config.task.PathToBeeHiveTaskConfig;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.tags.BlockTags;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.item.component.CustomData;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.BeehiveBlock;
import net.minecraft.world.level.block.HorizontalDirectionalBlock;
import net.minecraft.world.level.block.entity.BeehiveBlockEntity;
import net.minecraft.world.phys.Vec3;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
@Mixin(BeehiveBlockEntity.Occupant.class)
public abstract class BeeOccupantMixin {
@Inject(method = "createEntity", at = @At("RETURN"), cancellable = true)
private void cobblemon$createEntity(Level level, BlockPos pos, CallbackInfoReturnable<Entity> cir) {
// Needing to replicate all the post op stuff on the hive isn't great
if (cir.getReturnValue() == null) {
final BeehiveBlockEntity.Occupant occupant = (BeehiveBlockEntity.Occupant) (Object) this;
CompoundTag compoundTag = occupant.entityData().copyTag();
Entity entity = EntityType.loadEntityRecursive(compoundTag, level, (entityx) -> entityx);
if (entity instanceof PokemonEntity pokemonEntity) {
var state = level.getBlockState(pos);
var newPos = Vec3.ZERO;
var newYaw = 0f;
var brain = pokemonEntity.getBrain();
if (state.is(BlockTags.BEEHIVES)) {
// position in front of the entrance, face away from the block
var facing = state.getValue(HorizontalDirectionalBlock.FACING);
newPos = pos.relative(facing).getCenter();
newYaw = facing.toYRot();
brain.setMemory(CobblemonMemories.INSTANCE.getHIVE_LOCATION(), pos); // This needs to be set in the case that the hive was picked up and moved.
} else {
// Block has likely been destroyed
var center = pos.getCenter();
newPos = new Vec3(
center.x + level.random.nextFloat() * 0.6 - 0.3,
center.y + level.random.nextFloat() * 0.6 - 0.3,
center.z + level.random.nextFloat() * 0.6 - 0.3
);
newYaw = level.random.nextFloat() * 360F;
brain.eraseMemory(CobblemonMemories.INSTANCE.getHIVE_LOCATION());
}
entity.yRotO = newYaw;
entity.setPos(newPos);
// Do honey logic
var hasNectar = brain.getMemory(CobblemonMemories.INSTANCE.getHAS_NECTAR()).orElse(false);
if (hasNectar) {
// Remove nectar and reset got to hive cooldown
brain.eraseMemory(CobblemonMemories.INSTANCE.getHAS_NECTAR());
brain.setMemoryWithExpiry(CobblemonMemories.INSTANCE.getHIVE_COOLDOWN(), true, PathToBeeHiveTaskConfig.STAY_OUT_OF_HIVE_COOLDOWN);
// Increment honey level of the hive
if (state.is(BlockTags.BEEHIVES, (blockStateBase) -> blockStateBase.hasProperty(BeehiveBlock.HONEY_LEVEL))) {
int i = state.getValue(BeehiveBlock.HONEY_LEVEL);
if (i < 5) {
int j = level.random.nextInt(100) == 0 ? 2 : 1;
if (i + j > 5) {
--j;
}
level.setBlockAndUpdate(pos, state.setValue(BeehiveBlock.HONEY_LEVEL, i + j));
}
}
}
cir.setReturnValue(entity);
}
}
}
@Inject(method = "of", at = @At("HEAD"), cancellable = true)
private static void cobblemon$of(Entity entity, CallbackInfoReturnable<BeehiveBlockEntity.Occupant> cir) {
if (entity instanceof PokemonEntity pokemonEntity) {
List<String> IGNORED_BEE_TAGS = Arrays.asList("Air", "ArmorDropChances", "ArmorItems", "Brain", "CanPickUpLoot", "DeathTime", "FallDistance", "FallFlying", "Fire", "HandDropChances", "HandItems", "HurtByTimestamp", "HurtTime", "LeftHanded", "Motion", "NoGravity", "OnGround", "PortalCooldown", "Pos", "Rotation", "Passengers", "leash", "UUID");
CompoundTag compoundTag = new CompoundTag();
entity.save(compoundTag);
Objects.requireNonNull(compoundTag);
IGNORED_BEE_TAGS.forEach(compoundTag::remove);
Boolean hasNectar = pokemonEntity.getBrain().getMemory(CobblemonMemories.INSTANCE.getHAS_NECTAR()).orElse(false);
cir.setReturnValue(new BeehiveBlockEntity.Occupant(CustomData.of(compoundTag), 0, hasNectar ? 2400 : 600));
cir.cancel();
}
}
}
@@ -0,0 +1,102 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.mixin;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import com.cobblemon.mod.common.mixin.accessor.BeeDataAccessor;
import com.cobblemon.mod.common.mixin.accessor.BeehiveBlockEntityAccessor;
import com.cobblemon.mod.common.mixin.accessor.BlockEntityAccessor;
import com.google.common.collect.Lists;
import net.minecraft.core.BlockPos;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.ai.memory.MemoryModuleType;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.BeehiveBlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.List;
@Mixin(BeehiveBlockEntity.class)
public abstract class BeehiveBlockEntityMixin {
// Accessor for the private 'stored' field in BeehiveBlockEntity
@Accessor("stored")
public abstract List<BeeDataAccessor> getStored();
@Inject(method = "emptyAllLivingFromHive", at = @At("HEAD"))
private void cobblemon$emptyAllLivingFromHive(@Nullable Player player, BlockState state, BeehiveBlockEntity.BeeReleaseStatus releaseStatus, CallbackInfo ci) {
if (player != null) {
final BeehiveBlockEntity blockEntity = (BeehiveBlockEntity) (Object) this;
// if the block is sedated or the player is too far away, no aggro should be applied
if (blockEntity.isSedated() || player.position().distanceToSqr(blockEntity.getBlockPos().getCenter()) > 16.0)
return;
// from this point need to access private members of the block entity
final BeehiveBlockEntityAccessor blockEntityAccessor = (BeehiveBlockEntityAccessor) this;
List<Object> storedList = blockEntityAccessor.getStored();
List<Entity> list = Lists.newArrayList();
storedList.removeIf((beeData) -> {
BeehiveBlockEntity.Occupant occupant = ((BeeDataAccessor) beeData).invokeToOccupant();
// Checking the for the tag allows us to make sure it's one of our pokemon before trying to rehydrate it into an entity
boolean isPokemon = false;
if (occupant.entityData().contains("id")) {
isPokemon = "cobblemon:pokemon".equals(occupant.entityData().getUnsafe().getString("id"));
}
if (isPokemon) {
return BeehiveBlockEntityAccessor.releaseOccupant(
((BlockEntityAccessor) blockEntityAccessor).getLevel(),
((BlockEntityAccessor) blockEntityAccessor).getWorldPosition(),
state,
occupant,
list,
releaseStatus,
blockEntityAccessor.savedFlowerPos());
} else {
return false;
}
}
);
for (Entity entity : list) {
if (entity instanceof PokemonEntity) {
((PokemonEntity) entity).getBrain().setMemory(MemoryModuleType.ATTACK_TARGET, player);
}
}
}
}
@Redirect(
method = "releaseOccupant",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/world/level/Level;addFreshEntity(Lnet/minecraft/world/entity/Entity;)Z"
)
)
private static boolean cobblemon$captureReleasedEntity(
Level level, Entity entity,
Level lvl, BlockPos pos, BlockState state,
BeehiveBlockEntity.Occupant occupant, @Nullable List<Entity> bees,
BeehiveBlockEntity.BeeReleaseStatus releaseStatus, @Nullable BlockPos flowerPos) {
if (bees != null && !bees.contains(entity)) {
// Avoid duplicates if vanilla also adds bees to the list on your version
bees.add(entity);
}
return level.addFreshEntity(entity);
}
}
@@ -7,6 +7,7 @@
*/
package com.cobblemon.mod.common.mixin;
import com.cobblemon.mod.common.duck.ChannelDuck;
import com.mojang.blaze3d.audio.Channel;
import org.lwjgl.openal.AL10;
@@ -8,18 +8,25 @@
package com.cobblemon.mod.common.mixin;
import com.cobblemon.mod.common.CobblemonNetwork;
import com.cobblemon.mod.common.OrientationControllable;
import com.cobblemon.mod.common.api.orientation.OrientationController;
import com.cobblemon.mod.common.api.pokemon.effect.ShoulderEffectRegistry;
import com.cobblemon.mod.common.api.riding.RidingStyle;
import com.cobblemon.mod.common.api.riding.Seat;
import com.cobblemon.mod.common.api.riding.behaviour.types.liquid.SubmarineBehaviour;
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.net.messages.server.orientation.ServerboundUpdateOrientationPacket;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.effect.MobEffectInstance;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.ClipContext;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.HitResult;
@@ -32,6 +39,7 @@ 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.CallbackInfoReturnable;
@Mixin(LivingEntity.class)
public abstract class LivingEntityMixin extends Entity implements OrientationControllable {
@@ -55,6 +63,14 @@ public abstract class LivingEntityMixin extends Entity implements OrientationCon
}
}
@WrapOperation(method = "baseTick", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;isInWall()Z"))
public boolean cobblemon$preventMountSuffocation(LivingEntity instance, Operation<Boolean> original) {
if (instance.getVehicle() instanceof PokemonEntity) {
return false;
}
return original.call(instance);
}
@Override
public @NotNull HitResult pick(double hitDistance, float partialTicks, boolean hitFluids) {
Entity vehicle = this.getVehicle();
@@ -116,4 +132,31 @@ public abstract class LivingEntityMixin extends Entity implements OrientationCon
}
}
@Inject(method = "decreaseAirSupply", at = @At("HEAD"), cancellable = true)
private void cobblemon$conditionalWaterBreathing(
int currentAir,
CallbackInfoReturnable<Integer> cir
) {
var entity = (LivingEntity) (Object) this;
if (!(entity instanceof Player player)) return;
if (!(player.getVehicle() instanceof PokemonEntity vehicle)) return;
var ridingController = vehicle.getRidingController();
if (ridingController == null) return;
var activeContext = ridingController.getContext();
if (activeContext == null) return;
// If the rider is currently on a submarine and the ride still has stamina.
var stamina = activeContext.getState().getStamina().get();
if (
activeContext.getStyle() == RidingStyle.LIQUID &&
activeContext.getBehaviour().equals(SubmarineBehaviour.Companion.getKEY()) &&
stamina > 0.0f
) {
cir.setReturnValue(currentAir);
cir.cancel();
}
}
}
@@ -19,6 +19,7 @@ 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.duck.PlayerDuck;
import com.cobblemon.mod.common.duck.RidePassenger;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import com.cobblemon.mod.common.pokedex.scanner.PokedexEntityData;
import com.cobblemon.mod.common.pokedex.scanner.ScannableEntity;
@@ -47,6 +48,7 @@ import net.minecraft.world.entity.player.Player;
import net.minecraft.world.food.FoodProperties;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.Nullable;
import org.joml.Vector3f;
import org.objectweb.asm.Opcodes;
@@ -63,7 +65,7 @@ import java.util.*;
import java.util.stream.Collectors;
@Mixin(Player.class)
public abstract class PlayerMixin extends LivingEntity implements ScannableEntity, OrientationControllable, PlayerDuck {
public abstract class PlayerMixin extends LivingEntity implements ScannableEntity, OrientationControllable, PlayerDuck, RidePassenger {
@Unique
private Vector3f cobblemon$driverInput;
@@ -91,6 +93,12 @@ public abstract class PlayerMixin extends LivingEntity implements ScannableEntit
@Shadow protected int jumpTriggerTime;
@Unique private final OrientationController cobblemon$orientationController = new OrientationController(this);
@Unique private float cobblemon$rideXRot = 0.0f;
@Unique private float cobblemon$rideYRot = 0.0f;
@Unique private Vec3 cobblemon$rideEyePos = Vec3.ZERO;
protected PlayerMixin(EntityType<? extends LivingEntity> p_20966_, Level p_20967_) {
super(p_20966_, p_20967_);
}
@@ -117,11 +125,6 @@ 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) {
@@ -307,4 +310,34 @@ public abstract class PlayerMixin extends LivingEntity implements ScannableEntit
return cobblemon$lastSentDriverInput;
}
@Override
public float cobblemon$getRideXRot() {
return this.cobblemon$rideXRot;
}
@Override
public void cobblemon$setRideXRot(float rideXRot) {
this.cobblemon$rideXRot = rideXRot;
}
@Override
public float cobblemon$getRideYRot() {
return this.cobblemon$rideYRot;
}
@Override
public void cobblemon$setRideYRot(float rideYRot) {
this.cobblemon$rideYRot = rideYRot;
}
@Override
public Vec3 cobblemon$getRideEyePos() {
return this.cobblemon$rideEyePos;
}
@Override
public void cobblemon$setRideEyePos(Vec3 rideEyePos) {
this.cobblemon$rideEyePos = rideEyePos;
}
}
@@ -8,33 +8,70 @@
package com.cobblemon.mod.common.mixin;
import com.cobblemon.mod.common.Cobblemon;
import com.cobblemon.mod.common.OrientationControllable;
import com.cobblemon.mod.common.PlayerSpawnerAccessor;
import com.cobblemon.mod.common.api.spawning.spawner.PlayerSpawner;
import com.cobblemon.mod.common.api.spawning.spawner.PlayerSpawnerFactory;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import com.cobblemon.mod.common.world.gamerules.CobblemonGameRules;
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
import com.llamalad7.mixinextras.sugar.Local;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.Entity;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
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;
@Mixin(ServerPlayer.class)
public abstract class ServerPlayerMixin {
public abstract class ServerPlayerMixin implements PlayerSpawnerAccessor {
@Shadow
public abstract ServerLevel serverLevel();
@Unique
public PlayerSpawner cobblemon$spawner;
@Override
public PlayerSpawner getPlayerSpawner() {
ServerPlayer player = (ServerPlayer)(Object)this;
if (cobblemon$spawner == null) {
cobblemon$spawner = PlayerSpawnerFactory.INSTANCE.create(player);
}
return cobblemon$spawner;
}
@Override
public void setPlayerSpawner(PlayerSpawner spawner) {
this.cobblemon$spawner = spawner;
}
@Inject(method = "tick", at = @At("TAIL"))
private void cobblemon$tickSpawner(CallbackInfo ci) {
if (!Cobblemon.config.getEnableSpawning()) {
return;
} else if (!serverLevel().getGameRules().getBoolean(CobblemonGameRules.DO_POKEMON_SPAWNING)) {
return;
}
getPlayerSpawner().tick();
}
@Inject(method = "rideTick", at = @At("HEAD"))
private void cobblemon$updateOrientationControllerRideTick(CallbackInfo ci) {
if (!(this instanceof OrientationControllable controllable)) return;
var shouldUseCustomOrientation = cobblemon$shouldUseCustomOrientation((ServerPlayer)(Object)this);
controllable.getOrientationController().setActive(shouldUseCustomOrientation);
ServerPlayer player = (ServerPlayer)(Object)this;
Entity vehicle = player.getVehicle();
if (!(vehicle instanceof OrientationControllable controllableVehicle)) return;
var shouldUseCustomOrientation = cobblemon$shouldUseCustomOrientation(vehicle);
controllableVehicle.getOrientationController().setActive(shouldUseCustomOrientation);
}
@Unique
private boolean cobblemon$shouldUseCustomOrientation(ServerPlayer player) {
var playerVehicle = player.getVehicle();
if (playerVehicle == null) return false;
if (!(playerVehicle instanceof PokemonEntity pokemonEntity)) return false;
private boolean cobblemon$shouldUseCustomOrientation(Entity entity) {
if (entity == null) return false;
if (!(entity instanceof PokemonEntity pokemonEntity)) return false;
return pokemonEntity.ifRidingAvailableSupply(false, (behaviour, settings, state) -> {
return behaviour.shouldRoll(settings, state, pokemonEntity);
});
@@ -42,8 +79,9 @@ public abstract class ServerPlayerMixin {
@Inject(method = "stopRiding", at = @At("HEAD"))
public void cobblemon$resetOrientationOnDismount(CallbackInfo ci) {
if (!(this instanceof OrientationControllable controllable)) return;
controllable.getOrientationController().setActive(false);
ServerPlayer player = (ServerPlayer)(Object)this;
if (!(player.getVehicle() instanceof OrientationControllable controllableVehicle)) return;
controllableVehicle.getOrientationController().setActive(false);
}
//TODO: Switch to sending out on load instead of preventing saving
@@ -51,5 +89,4 @@ public abstract class ServerPlayerMixin {
private boolean cobblemon$cancelSavingPokemonMounts(boolean original, @Local(ordinal=0) Entity entity) {
return original && !(entity instanceof PokemonEntity);
}
}
@@ -0,0 +1,21 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.mixin.accessor;
import net.minecraft.world.level.block.entity.BeehiveBlockEntity;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Invoker;
@Mixin(targets = "net.minecraft.world.level.block.entity.BeehiveBlockEntity$BeeData")
public interface BeeDataAccessor {
@Invoker("toOccupant")
BeehiveBlockEntity.Occupant invokeToOccupant();
}
@@ -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.mixin.accessor;
import net.minecraft.core.BlockPos;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.BeehiveBlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
import org.spongepowered.asm.mixin.gen.Invoker;
import java.util.List;
@Mixin(BeehiveBlockEntity.class)
public interface BeehiveBlockEntityAccessor {
@Accessor("stored")
List<Object> getStored();
@Accessor("savedFlowerPos")
BlockPos savedFlowerPos();
/**
* Access the private static method:
* private static void releaseOccupant(Level level, BlockPos pos, BeeData beeData, ...)
*/
@Invoker("releaseOccupant")
static boolean releaseOccupant(Level level, BlockPos pos, BlockState state, BeehiveBlockEntity.Occupant occupant, @Nullable List<Entity> storedInHives, BeehiveBlockEntity.BeeReleaseStatus releaseStatus, @Nullable BlockPos storedFlowerPos) {
throw new AssertionError(); // Mixin will overwrite
}
}
@@ -0,0 +1,21 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.mixin.accessor;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.BlockEntity;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
@Mixin(BlockEntity.class)
public interface BlockEntityAccessor {
@Accessor("level") Level getLevel();
@Accessor("worldPosition") BlockPos getWorldPosition();
}
@@ -34,7 +34,8 @@ public class AbstractClientPlayerMixin {
} else {
// Only modify fov through riding if the pokedex is not open
LocalPlayer player = (LocalPlayer) (Object) this;
if (player != null && player.isPassenger() && player.getVehicle() instanceof PokemonEntity) {
// Only modify fov for the driver and not the passengers
if (player != null && player.isPassenger() && player.getVehicle() instanceof PokemonEntity && player.getVehicle().getControllingPassenger() == player) {
PokemonEntity ride = (PokemonEntity) player.getVehicle();
// Return custom fov mult for riding
@@ -14,14 +14,18 @@ import com.cobblemon.mod.common.api.orientation.OrientationController;
import com.cobblemon.mod.common.api.riding.Rideable;
import com.cobblemon.mod.common.client.MountedPokemonAnimationRenderController;
import com.cobblemon.mod.common.client.render.camera.MountedCameraRenderer;
import com.cobblemon.mod.common.duck.RidePassenger;
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 com.mojang.blaze3d.Blaze3D;
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.phys.Vec3;
import org.jetbrains.annotations.Nullable;
import org.joml.Matrix3f;
import org.joml.Quaternionf;
import org.joml.Vector3f;
@@ -48,45 +52,51 @@ public abstract class CameraMixin {
@Shadow protected abstract void setPosition(Vec3 pos);
@Unique private float returnTimer = 0;
@Unique private float rollAngleStart = 0;
@Unique Minecraft minecraft = Minecraft.getInstance();
@Shadow private float partialTickTime;
// Used to help in camera rotation rendering when transitioning
// from rolled to unrolled.
@Unique private float cobblemon$returnTimer = 0;
@Unique private float cobblemon$rollAngleStart = 0;
@Unique private double cobblemon$lastHandledRotationTime = Double.MIN_VALUE;
@Unique private double cobblemon$frameTime = 0.0;
// Used to as the render rotation for the motion sick camera.
@Unique @Nullable private Quaternionf cobblemon$smoothRotation = null;
@Inject(method = "setRotation", at = @At("HEAD"), cancellable = true)
public void cobblemon$setRotation(float f, float g, CallbackInfo ci) {
if (!(this.entity instanceof OrientationControllable controllable)) return;
var controller = controllable.getOrientationController();
if (!controller.isActive() && controller.getOrientation() != null) {
if(this.returnTimer < 1) {
//Rotation is taken from entity since we no longer handle mouse ourselves
//Stops a period of time when you can't input anything.
controller.getOrientation().set(
new Matrix3f()
.rotateY((float) Math.toRadians(180 - this.entity.getYRot()))
.rotateX((float) Math.toRadians(-this.entity.getXRot()))
);
if(rollAngleStart == 0){
this.returnTimer = 1;
controller.reset();
return;
}
controller.getOrientation().rotateZ((float) Math.toRadians(-rollAngleStart*(1-returnTimer)));
cobblemon$applyRotation();
this.returnTimer += .05F;
ci.cancel();
} else {
this.returnTimer = 1;
controller.reset();
}
return;
}
if (controller.getOrientation() == null) return;
cobblemon$applyRotation();
// Calculate the current frametime
double d = Blaze3D.getTime();
this.cobblemon$frameTime = (this.cobblemon$lastHandledRotationTime != Double.MIN_VALUE) ? d - this.cobblemon$lastHandledRotationTime : 0.0;
this.cobblemon$lastHandledRotationTime = d;
this.returnTimer = 0;
this.rollAngleStart = controller.getRoll();
ci.cancel();
// Do no camera rotations if the game is paused
if(Minecraft.getInstance().isPaused()) return;
// Don't assume the camera to be attached to an entity. Ponder Scenes from create et.al. for example aren't.
if (this.entity == null) return;
var vehicle = this.entity.getVehicle();
if (!(vehicle instanceof OrientationControllable controllableVehicle)) return;
var instance = (Camera)(Object)this;
// If the current vehicle has no orientation then return and perform
// vanilla camera rotations
var vehicleController = controllableVehicle.getOrientationController();
if (vehicleController.getOrientation() == null) return;
// If the controller has been deactivated but the orientation isn't null yet
// then perform transition from rolled ride/camera to vanilla
if (!vehicleController.isActive() && vehicleController.getOrientation() != null) {
cobblemon$applyTransitionRotation(vehicleController, ci);
} else { // Otherwise perform cobblemon specific camera rotation and set transition helper variables
cobblemon$applyRotation();
this.cobblemon$returnTimer = 0;
this.cobblemon$rollAngleStart = instance.rotation().getEulerAnglesYXZ(new Vector3f()).z();
ci.cancel();
}
}
@WrapOperation(method = "setup", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Camera;setPosition(DDD)V"))
@@ -114,25 +124,153 @@ public abstract class CameraMixin {
}
}
// reset the rotation values if it is now not being ridden.
cobblemon$smoothRotation = null;
original.call(instance, x, y, z);
}
@Unique
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()) {
var driver = this.entity;
if (!(driver.getVehicle() instanceof OrientationControllable controllableVehicle) ||
controllableVehicle.getOrientationController().getOrientation() == null ||
!controllableVehicle.getOrientationController().getActive()
) return;
// Grab the driver and vehicle orientation controllers.
var vehicleController = controllableVehicle.getOrientationController();
var isDriving = driver.getControlledVehicle() != null;
// Grab the ride orientation
var newRotation = new Quaternionf();
// If the player is driving then use the current orientation since the driver sets it.
// If not then use the smoothed render orientation for passengers otherwise the vehicle rotation will be jerky
if (isDriving) {
newRotation = vehicleController.getOrientation().normal(new Matrix3f()).getNormalizedRotation(new Quaternionf());
} else {
newRotation = vehicleController.getRenderOrientation(this.partialTickTime);
}
// Unroll the current rotation if no roll is requested and not currently freelooking.
if (Cobblemon.config.getDisableRoll()) {
// Init the smooth rotation if it has not been set yet.
if (cobblemon$smoothRotation == null) {
cobblemon$smoothRotation = new Quaternionf().set(this.rotation);
}
var rideAngs = newRotation.getEulerAnglesYXZ(new Vector3f());
var cameraAngs = cobblemon$smoothRotation.getEulerAnglesYXZ(new Vector3f());
// var vehicle = (PokemonEntity)driver.getVehicle();
// var vel = vehicle.getRidingAnimationData().getVelocitySpring().getInterpolated(this.partialTickTime, 0);
// // Grab the target yaw as the current rides yaw. If the horizontal velocity is high enough to be used to
// // derive a yaw then use it.
// var targetYaw = (double) rideAngs.y();
// if ( vel.horizontalDistance() > 0.01) {
// // lerp the new rotation towards this y rot at a rate proportional to the horizontal movement speed?
// targetYaw = Mth.wrapDegrees(Math.toDegrees(Mth.atan2(vel.x(), vel.z())) + 180.0f);
// }
var degDiff = Mth.degreesDifference((float)Math.toDegrees(cameraAngs.y()), (float)Math.toDegrees(rideAngs.y));
var pitchDeadzoneDeg = 15.0; // x degrees of deadzone about +-90 degrees of pitch.
var pitchDeg = vehicleController.getPitch();
// ConsiderPitch to be already 90 if within the deadzone around it.
var cutoffPitch = Math.min(1.0,(Mth.abs(pitchDeg) / (90.0f - pitchDeadzoneDeg))) * 90.0f * Math.signum(pitchDeg);
var lerpRateMod = Mth.cos((float)Math.toRadians(cutoffPitch));
// Apply smoothing from the cameras current yaw to the yaw of the velocity vector of the ride.
var k = 10.0f; // Some smoothing constant k
var smoothingFactor = lerpRateMod * cobblemon$frameTime * k;
var newYaw = cameraAngs.y() + (Math.toRadians(degDiff) * smoothingFactor);
var newPitch = (float)Mth.lerp(cobblemon$frameTime * k, cameraAngs.x(), rideAngs.x());
//var newYaw = (float)Mth.lerp(smoothingFactor, cameraAngs.y(), rideAngs.y());
var maxRoll = Math.toRadians(15.0f);
var newRoll = (float)Mth.lerp(cobblemon$frameTime * k / 2.0, cameraAngs.z(), Math.abs(lerpRateMod) * Math.sin(rideAngs.z()) * maxRoll);
// Set rotations
var smoothedRot = new Quaternionf().rotateYXZ((float)newYaw, newPitch, newRoll);
cobblemon$smoothRotation.set(smoothedRot);
newRotation.set(smoothedRot);
}
var rotationOffset = 0;
if (Minecraft.getInstance().options.getCameraType().isMirrored()) {
newRotation.rotateY((float)Math.toRadians(180));
rotationOffset = 180;
}
// Get the drivers local look angle in relation to the ride and apply it.
if (vehicleController.getActive()) {
var vehicleMatrix = new Matrix3f().set(newRotation);
var driverMatrix = new Matrix3f();// Base the driver
var playerRotater = (RidePassenger)driver;
newRotation = vehicleMatrix.mul(
driverMatrix.rotateYXZ(
-Mth.DEG_TO_RAD * (playerRotater.cobblemon$getRideYRot()),
-Mth.DEG_TO_RAD * playerRotater.cobblemon$getRideXRot(),
0.0f)
).normal(new Matrix3f()).getNormalizedRotation(new Quaternionf());
}
this.rotation.set(newRotation);
this.xRot = controller.getPitch();
this.yRot = controller.getYaw();
var eulerAngs = newRotation.getEulerAnglesXYZ(new Vector3f());
this.xRot = eulerAngs.x() * Mth.RAD_TO_DEG;
this.yRot = eulerAngs.y() * Mth.RAD_TO_DEG;
// Set the drivers rotations to match
var driverEulerAngs = new Quaternionf(this.rotation).getEulerAnglesYXZ(new Vector3f());
driver.setXRot(-driverEulerAngs.x() * Mth.RAD_TO_DEG + rotationOffset);
driver.setYRot(180.0f - (driverEulerAngs.y() * Mth.RAD_TO_DEG));
OrientationController.Companion.getFORWARDS().rotate(this.rotation, this.forwards);
OrientationController.Companion.getUP().rotate(this.rotation, this.up);
OrientationController.Companion.getLEFT().rotate(this.rotation, this.left);
}
@Unique
private void cobblemon$applyTransitionRotation(
OrientationController vehicleController,
CallbackInfo ci
) {
// If the transition has just started then
if (this.cobblemon$returnTimer == 0.0) {
MountedCameraRenderer.INSTANCE.resetDriverRotations((Camera)(Object)this, this.entity);
}
if(this.cobblemon$returnTimer < 1) {
//Rotation is taken from entity since we no longer handle mouse ourselves
//Stops a period of time when you can't input anything.
float interpolatedRoll = Mth.lerp(this.cobblemon$returnTimer, this.cobblemon$rollAngleStart, 0.0f);
float pitch = (float) Math.toRadians(-this.entity.getXRot());
float yaw = (float) Math.toRadians(180 - this.entity.getYRot());
Quaternionf interRot = new Quaternionf();
interRot.rotationYXZ(yaw, pitch, interpolatedRoll);
this.rotation.set(interRot);
var eulerAngs = interRot.getEulerAnglesXYZ(new Vector3f());
this.xRot = eulerAngs.x() * Mth.RAD_TO_DEG;
this.yRot = eulerAngs.y() * Mth.RAD_TO_DEG;
OrientationController.Companion.getFORWARDS().rotate(this.rotation, this.forwards);
OrientationController.Companion.getUP().rotate(this.rotation, this.up);
OrientationController.Companion.getLEFT().rotate(this.rotation, this.left);
if(cobblemon$rollAngleStart == 0){
this.cobblemon$returnTimer = 1;
vehicleController.reset();
MountedCameraRenderer.INSTANCE.resetDriverRotations((Camera)(Object)this, this.entity);
return;
}
this.cobblemon$returnTimer += (partialTickTime * (1.0f/20.0f));
ci.cancel();
} else {
this.cobblemon$returnTimer = 1;
vehicleController.reset();
MountedCameraRenderer.INSTANCE.resetDriverRotations((Camera)(Object)this, this.entity);
}
}
@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();
@@ -9,15 +9,21 @@
package com.cobblemon.mod.common.mixin.client;
import com.cobblemon.mod.common.api.scheduling.ClientTaskTracker;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import com.llamalad7.mixinextras.sugar.Local;
import net.minecraft.client.DeltaTracker;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.GameRenderer;
import net.minecraft.world.entity.Entity;
import org.spongepowered.asm.mixin.Mixin;
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.ModifyArg;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.function.Predicate;
@Mixin(GameRenderer.class)
public class GameRendererMixin {
@Unique
@@ -42,4 +48,16 @@ public class GameRendererMixin {
lastTime = newTime;
}
@ModifyArg(
method = "pick(Lnet/minecraft/world/entity/Entity;DDF)Lnet/minecraft/world/phys/HitResult;",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/world/entity/projectile/ProjectileUtil;getEntityHitResult(Lnet/minecraft/world/entity/Entity;Lnet/minecraft/world/phys/Vec3;Lnet/minecraft/world/phys/Vec3;Lnet/minecraft/world/phys/AABB;Ljava/util/function/Predicate;D)Lnet/minecraft/world/phys/EntityHitResult;"
),
index = 4
)
private Predicate<Entity> filterPokemonVehicle(Predicate<Entity> original, @Local(ordinal = 0, argsOnly = true) Entity entity) {
return (entityX) -> !(entity.getVehicle() == entityX && entityX instanceof PokemonEntity) && original.test(entityX);
}
}
@@ -10,6 +10,7 @@ package com.cobblemon.mod.common.mixin.client;
import com.cobblemon.mod.common.OrientationControllable;
import com.cobblemon.mod.common.client.render.player.MountedPlayerRenderer;
import com.cobblemon.mod.common.duck.RidePassenger;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import net.minecraft.client.model.HumanoidModel;
import net.minecraft.client.model.geom.ModelPart;
@@ -68,6 +69,13 @@ public class HumanoidModelMixin {
behaviour.shouldRotateRiderHead(settings, state, pokemonEntity)
);
if (entity instanceof RidePassenger playerRotater) {
//this.head.yRot = playerRotater.cobblemon$getRideYRot();
this.head.xRot = playerRotater.cobblemon$getRideXRot();
headPitch = playerRotater.cobblemon$getRideXRot();
}
if (!shouldRotatePlayerHead) {
netHeadYaw = 0f;
headPitch = 0f;
@@ -19,9 +19,11 @@ 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.duck.PlayerDuck;
import com.cobblemon.mod.common.duck.RidePassenger;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import com.cobblemon.mod.common.net.messages.server.orientation.ServerboundUpdateOrientationPacket;
import com.cobblemon.mod.common.net.messages.server.riding.ServerboundUpdateDriverInputPacket;
import com.cobblemon.mod.common.net.messages.server.riding.ServerboundUpdateRiderRotationPacket;
import com.mojang.authlib.GameProfile;
import net.minecraft.client.CameraType;
import net.minecraft.client.Minecraft;
@@ -29,12 +31,10 @@ import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.player.AbstractClientPlayer;
import net.minecraft.client.player.Input;
import net.minecraft.client.player.LocalPlayer;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
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.NotNull;
@@ -55,6 +55,10 @@ public abstract class LocalPlayerMixin extends AbstractClientPlayer implements O
@Unique Matrix3f cobblemon$lastOrientation;
@Unique float cobblemon$lastRideXRot;
@Unique float cobblemon$lastRideYRot;
@Unique Vec3 cobblemon$lastRideEyePos;
@Unique boolean cobblemon$isDoubleJumping = false;
@Shadow
@@ -72,30 +76,35 @@ public abstract class LocalPlayerMixin extends AbstractClientPlayer implements O
super(clientLevel, gameProfile);
}
// @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) {
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()));
if (!(this.getVehicle() instanceof OrientationControllable controllableVehicle)) return;
var vehicleController = controllableVehicle.getOrientationController();
if (!vehicleController.isActive() || vehicleController.getOrientation() == cobblemon$lastOrientation) return;
cobblemon$lastOrientation = vehicleController.getOrientation() != null ? new Matrix3f(vehicleController.getOrientation()) : null;
CobblemonNetwork.INSTANCE.sendToServer(new ServerboundUpdateOrientationPacket(this.getVehicle().getId(), vehicleController.getOrientation()));
}
@Inject(method = "rideTick", at = @At("HEAD"))
private void cobblemon$updateOrientationControllerRideTick(CallbackInfo ci) {
if (Minecraft.getInstance().player != (Object)this) return;
if (!(this instanceof OrientationControllable controllable)) return;
var driver = (LocalPlayer)(Object)this;
var vehicle = driver.getVehicle();
if (!(this.getVehicle() instanceof OrientationControllable controllableVehicle)) return;
var shouldUseCustomOrientation = cobblemon$shouldUseCustomOrientation((LocalPlayer)(Object)this);
controllable.getOrientationController().setActive(shouldUseCustomOrientation);
var vehicleController = controllableVehicle.getOrientationController();
// If the player has just switched to riding a custom orientation ride then set their
// x and y rots local to the vehicle rots(if the controller wasn't active and will now be active)
// This ensures that on transition your camera stays in the same spot
if (!vehicleController.isActive() && shouldUseCustomOrientation) {
// Set local to the vehicle x and yrot so
var playerRotater = (RidePassenger)driver;
playerRotater.cobblemon$setRideXRot(Mth.wrapDegrees(driver.getXRot() - vehicle.getXRot()));
playerRotater.cobblemon$setRideYRot(Mth.wrapDegrees(driver.getYRot() - vehicle.getYRot()));
}
vehicleController.setActive(shouldUseCustomOrientation);
}
@Inject(method = "rideTick", at = @At("HEAD"))
@@ -115,6 +124,36 @@ public abstract class LocalPlayerMixin extends AbstractClientPlayer implements O
CobblemonNetwork.INSTANCE.sendToServer(new ServerboundUpdateDriverInputPacket(driverInput));
}
@Inject(method = "rideTick", at = @At("HEAD"))
private void cobblemon$updateRiderRotationsRideTick(CallbackInfo ci) {
if (Minecraft.getInstance().player != (Object)this) return;
if (!(this.getVehicle() instanceof PokemonEntity pokemonEntity)) return;
var passenger = (LocalPlayer)(Object)this;
var playerRotater = (RidePassenger)passenger;
// Gather Rider Rotation info
var rideXRot = playerRotater.cobblemon$getRideXRot();
var rideYRot = playerRotater.cobblemon$getRideYRot();
var rideEyePos = playerRotater.cobblemon$getRideEyePos();
// Check for a change in the values before sending them.
if (cobblemon$lastRideXRot != rideXRot ||
cobblemon$lastRideYRot != rideYRot ||
cobblemon$lastRideEyePos != rideEyePos
) {
// Update 'last' values
cobblemon$lastRideXRot = rideXRot;
cobblemon$lastRideYRot = rideYRot;
cobblemon$lastRideEyePos = rideEyePos;
CobblemonNetwork.INSTANCE.sendToServer(new ServerboundUpdateRiderRotationPacket(rideXRot, rideYRot, rideEyePos));
}
}
@Unique
private boolean cobblemon$shouldUseCustomOrientation(LocalPlayer player) {
var playerVehicle = player.getVehicle();
@@ -129,8 +168,8 @@ public abstract class LocalPlayerMixin extends AbstractClientPlayer implements O
public void stopRiding() {
super.stopRiding();
if (Minecraft.getInstance().player != (Object)this) return;
if (!(this instanceof OrientationControllable controllable)) return;
controllable.getOrientationController().setActive(false);
if (!(this instanceof OrientationControllable controllableVehicle)) return;
controllableVehicle.getOrientationController().setActive(false);
}
@Inject(method = "getJumpRidingScale", at = @At("HEAD"), cancellable = true)
@@ -161,7 +200,7 @@ public abstract class LocalPlayerMixin extends AbstractClientPlayer implements O
this.cobblemon$survivalJumpTriggerTime = 0;
}
else {
this.cobblemon$survivalJumpTriggerTime = 7;
this.cobblemon$survivalJumpTriggerTime = 12;
}
}
else if (this.cobblemon$survivalJumpTriggerTime > 0) {
@@ -188,33 +227,17 @@ public abstract class LocalPlayerMixin extends AbstractClientPlayer implements O
public HitResult pick(double hitDistance, float partialTicks, boolean hitFluids) {
Entity vehicle = this.getVehicle();
if (vehicle instanceof PokemonEntity pokemonEntity) {
PokemonClientDelegate delegate = (PokemonClientDelegate) pokemonEntity.getDelegate();
String locatorName = delegate.getSeatLocator(this);
MatrixWrapper locator = delegate.getLocatorStates().get(locatorName);
if (locator == null) {
return super.pick(hitDistance, partialTicks, hitFluids);
}
// Get player object
var passenger = (LocalPlayer)(Object)this;
var playerRotater = (RidePassenger)passenger;
Vec3 locatorOffset = locator != null ? new Vec3(locator.getMatrix().getTranslation(new Vector3f())) : Vec3.ZERO;
OrientationController controller = this.getOrientationController();
float currEyeHeight = this.getEyeHeight();
Vector3f offset = new Vector3f(new Vector3f(0f, currEyeHeight - (this.getBbHeight() / 2), 0f));
if (Minecraft.getInstance().options.getCameraType() == CameraType.FIRST_PERSON) {
PosableModel model = VaryingModelRepository.INSTANCE.getPoser(pokemonEntity.getPokemon().getSpecies().getResourceIdentifier(), new FloatingState());
offset.add(cobblemon$getFirstPersonOffset(model, locatorName));
}
Matrix3f orientation = controller.isActive() && controller.getOrientation() != null ? controller.getOrientation() : new Matrix3f();
Vec3 rotatedEyeHeight = new Vec3(orientation.transform(offset));
Vec3 eyePosition = locatorOffset.add(pokemonEntity.position()).add(rotatedEyeHeight);
// Gather eye position from stored 1st person camera position calculation
var eyePosition = playerRotater.cobblemon$getRideEyePos();
Vec3 viewVector = this.getViewVector(partialTicks);
Vec3 viewDistanceVector = eyePosition.add(viewVector.x * hitDistance, viewVector.y * hitDistance, viewVector.z * hitDistance);
return this.level()
.clip(
new ClipContext(
@@ -10,13 +10,16 @@ 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.client.CobblemonClient;
import com.cobblemon.mod.common.client.keybind.keybinds.PartySendBinding;
import com.cobblemon.mod.common.duck.RidePassenger;
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 kotlin.Pair;
import net.minecraft.client.Minecraft;
import net.minecraft.client.MouseHandler;
import net.minecraft.client.player.LocalPlayer;
@@ -32,6 +35,9 @@ import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import static com.google.common.primitives.Floats.min;
import static net.minecraft.util.Mth.lerp;
@Mixin(MouseHandler.class)
public abstract class MouseHandlerMixin {
@Shadow private double accumulatedScrollY;
@@ -117,8 +123,6 @@ public abstract class MouseHandlerMixin {
@Local(argsOnly = true) double d,
@Local(argsOnly = true) double movementTime
) {
boolean returnValue = true;
PokedexUsageContext usageContext = CobblemonClient.INSTANCE.getPokedexUsageContext();
if (usageContext.getScanningGuiOpen()) {
this.smoothTurnY.reset();
@@ -126,34 +130,78 @@ public abstract class MouseHandlerMixin {
var defaultSensitivity = this.minecraft.options.sensitivity().get() * 0.6 + 0.2;
var spyglassSensitivity = Math.pow(defaultSensitivity, 3);
var lookSensitivity = spyglassSensitivity * 8.0;
var sensitivity = Mth.lerp(usageContext.getFovMultiplier(), spyglassSensitivity, lookSensitivity);
var sensitivity = lerp(usageContext.getFovMultiplier(), spyglassSensitivity, lookSensitivity);
var yRotationFlip = this.minecraft.options.invertYMouse().get() ? -1 : 1;
player.turn(this.accumulatedDX * sensitivity, (this.accumulatedDY * sensitivity * yRotationFlip));
returnValue = false;
}
var vehicle = player.getVehicle();
if (!(vehicle instanceof PokemonEntity)) return true;
// Clamp player rotation if riding and the vehicle demands it
if (player.isPassenger() && player.getVehicle() instanceof PokemonEntity vehicle) {
vehicle.clampPassengerRotation(player);
if (player.isPassenger() && vehicle instanceof PokemonEntity pokeVehicle) {
pokeVehicle.clampPassengerRotation(player);
}
if (!(player instanceof OrientationControllable controllable)) return true;
if (!controllable.getOrientationController().isActive()) {
// If the ride is not orientation controllable then return and perform the normal turnPlayer logic
if (!(vehicle instanceof OrientationControllable controllableVehicle) || !controllableVehicle.getOrientationController().isActive()) {
xMouseSmoother.reset();
yMouseSmoother.reset();
pitchSmoother.reset();
rollSmoother.reset();
yawSmoother.reset();
return returnValue;
return true;
}
// Send mouse input to be interpreted into rotation
// deltas by the ride controller
var vehicleController = controllableVehicle.getOrientationController();
var lookaroundButtonPressed = Minecraft.getInstance().mouseHandler.isMiddlePressed();
Pair<Boolean, Boolean> mouseShouldModifyDriver = ((PokemonEntity) vehicle).ifRidingAvailableSupply(new Pair(false, false), (behaviour, settings, state) -> {
return behaviour.mouseModifiesDriverRotation(
settings,
state,
(PokemonEntity) vehicle
);
});
//-------------------------------------------------------------------------
// If looking around then modify the players orientation instead and zero the mouse deltas so the mouse doesn't
// affect the vehicle orientation when looking around.
// If the client is a passenger it will modify these rotations and return
//-------------------------------------------------------------------------
if (lookaroundButtonPressed || player.getControlledVehicle() == null) {
// Modify driver rotations
cobblemon$setDriverRotations(player, cursorDeltaX, cursorDeltaY);
// If the player/passenger is not the driver then return and ignore all vehicle rotation logic
if (player.getControlledVehicle() == null) return false;
// Set to zero so that vehicle rotations don't also get affected by movement.
cursorDeltaX = 0.0;
cursorDeltaY = 0.0;
} else if (mouseShouldModifyDriver.getFirst()) { // If not freelooking then check modification per mouse input dimension
cobblemon$setDriverRotations(player, cursorDeltaX, 0.0);
cursorDeltaX = 0.0;
} else if (mouseShouldModifyDriver.getSecond()) {
cobblemon$setDriverRotations(player, 0.0, cursorDeltaY);
cursorDeltaY = 0.0;
}
//-------------------------------------------------------------------------
// Smoothly return driver rotations to zero when needed
//-------------------------------------------------------------------------
var playerRotater = (RidePassenger)player;
if( !lookaroundButtonPressed && !mouseShouldModifyDriver.getFirst()){
playerRotater.cobblemon$setRideYRot(lerp(min(5.0f * (float)movementTime, 1.0f), Mth.wrapDegrees(playerRotater.cobblemon$getRideYRot()), 0.0f));
}
if( !lookaroundButtonPressed && !mouseShouldModifyDriver.getSecond()) {
playerRotater.cobblemon$setRideXRot(lerp(min(5.0f * (float)movementTime, 1.0f), Mth.wrapDegrees(playerRotater.cobblemon$getRideXRot()), 0.0f));
}
// Send mouse input to be interpreted into rotation deltas by the ride controller
Vec3 angVecMouse = cobblemon$getRideMouseRotation(cursorDeltaX, cursorDeltaY, movementTime);
// Perform Rotation using mouse influenced rotation deltas.
controllable.getOrientationController().rotate(
vehicleController.rotate(
(float) angVecMouse.x,
(float) angVecMouse.y,
(float) angVecMouse.z
@@ -163,17 +211,16 @@ public abstract class MouseHandlerMixin {
var angRot = cobblemon$getAngularVelocity(movementTime);
// Apply smoothing if requested by the controller.
// This Might be best if done by the controller itself?
if(cobblemon$shouldUseAngVelSmoothing()) {
var yaw = yawSmoother.getNewDeltaValue(angRot.x * 0.5f, d);
var pitch = pitchSmoother.getNewDeltaValue(angRot.y * 0.5f, d);
var roll = rollSmoother.getNewDeltaValue(angRot.z * 0.5f, d);
controllable.getOrientationController().rotate((float) yaw, (float) pitch, (float) roll);
vehicleController.rotate((float) yaw, (float) pitch, (float) roll);
}
// Otherwise simply apply the smoothing
else {
controllable.getOrientationController().rotate((float) (angRot.x * 10 * d), (float) (angRot.y * 10 * d), (float) (angRot.z * 10 * d));
vehicleController.rotate((float) (angRot.x * 10 * d), (float) (angRot.y * 10 * d), (float) (angRot.z * 10 * d));
}
return false;
}
@@ -186,10 +233,11 @@ public abstract class MouseHandlerMixin {
@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.player.getVehicle() instanceof OrientationControllable vehicleControllable)) return;
if (!(vehicleControllable.getOrientationController() instanceof OrientationController vehicleController)) return;
if (vehicleController.getOrientation() == null) return;
if (minecraft.isPaused()) return;
if (isMouseGrabbed()) return;
if (this.isMouseGrabbed()) return; // return if turnPlayer() has already run this pass
this.turnPlayer(cobblemon$timeDelta);
}
@@ -251,6 +299,20 @@ public abstract class MouseHandlerMixin {
return Math.pow(sensitivity, 3);
}
@Unique
private void cobblemon$setDriverRotations(LocalPlayer player, double cursorDeltaX, double cursorDeltaY) {
// the vanilla turn() method redone so that it uses rideRotations.
float f = (float)cursorDeltaY * 0.15F; // Note: turn() uses xRot for pitch (up/down)
float g = (float)cursorDeltaX * 0.15F; // Note: turn() uses yRot for yaw (left/right)
var playerRotater = (RidePassenger)player;
playerRotater.cobblemon$setRideXRot(playerRotater.cobblemon$getRideXRot() + f);
playerRotater.cobblemon$setRideYRot(playerRotater.cobblemon$getRideYRot() + g);
playerRotater.cobblemon$setRideXRot(Mth.clamp(playerRotater.cobblemon$getRideXRot(), -90.0F, 90.0F));
if (player.getVehicle() != null) {
player.getVehicle().onPassengerTurned(player);
}
}
@Unique
private boolean cobblemon$shouldUseAngVelSmoothing() {
var player = minecraft.player;
@@ -0,0 +1,45 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.mixin.client;
import com.cobblemon.mod.common.Cobblemon;
import com.cobblemon.mod.common.OrientationControllable;
import net.minecraft.client.CameraType;
import net.minecraft.client.Minecraft;
import net.minecraft.client.Options;
import net.minecraft.world.entity.player.Player;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(Options.class)
public abstract class OptionsMixin {
/**
* Only allow third person on rollables when motion sick cam is enabled
*/
@Inject(
method = "getCameraType",
at = @At("HEAD"),
cancellable = true
)
private void cobblemon$forceThirdPerson(CallbackInfoReturnable<CameraType> cir) {
var mc = Minecraft.getInstance();
if(!(mc.player instanceof Player player)) return;
if(!(player.getVehicle() instanceof OrientationControllable vehicle)) return;
if(vehicle.getOrientationController() == null) return;
// Force third person if on an active rollable vehicle and roll is disabled.
if (Cobblemon.config.getDisableRoll() && vehicle.getOrientationController().isActive()) {
cir.setReturnValue(CameraType.THIRD_PERSON_BACK);
cir.cancel();
}
}
}
@@ -10,8 +10,7 @@ package com.cobblemon.mod.common.mixin.client.snapshots;
import com.cobblemon.mod.common.CobblemonBuildDetails;
import com.cobblemon.mod.common.client.gui.snapshots.SnapshotWarningScreen;
import com.cobblemon.mod.common.client.snapshots.SnapshotAcknowledgementData;
import com.cobblemon.mod.common.client.snapshots.SnapshotAcknowledgementReader;
import com.cobblemon.mod.common.client.persisted.ClientPersistedData;
import kotlin.Unit;
import net.minecraft.SharedConstants;
import net.minecraft.client.Minecraft;
@@ -42,7 +41,7 @@ public final class SnapshotWarningMixin {
return Unit.INSTANCE;
}
SnapshotAcknowledgementReader.INSTANCE.write(new SnapshotAcknowledgementData(CobblemonBuildDetails.VERSION, dsa));
ClientPersistedData.INSTANCE.writeSnapshotsData(new ClientPersistedData.SnapshotAcknowledgementData(CobblemonBuildDetails.VERSION, dsa));
runnable.run();
return Unit.INSTANCE;
@@ -54,7 +53,7 @@ public final class SnapshotWarningMixin {
@Unique
@SuppressWarnings("ResultOfMethodCallIgnored")
private boolean cobblemon$isAcknowledged() {
SnapshotAcknowledgementData data = SnapshotAcknowledgementReader.INSTANCE.read();
ClientPersistedData.SnapshotAcknowledgementData data = ClientPersistedData.INSTANCE.readSnapshotsData();
if (data == null) {
return false;
}
@@ -8,20 +8,24 @@
package com.cobblemon.mod.common.mixin.entity;
import com.cobblemon.mod.common.OrientationControllable;
import com.cobblemon.mod.common.duck.RidePassenger;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(Entity.class)
public abstract class EntityMixin {
@@ -57,5 +61,42 @@ public abstract class EntityMixin {
return original.call(entity);
}
@Inject(
method = "getEyePosition(F)Lnet/minecraft/world/phys/Vec3;",
at = @At("HEAD"),
cancellable = true
)
private void cobblemon$modifyEyePosition_partial(float partialTicks, CallbackInfoReturnable<Vec3> cir) {
var entity = (Entity)(Object)this;
if (!(entity instanceof Player player)) return;
if (!(player instanceof RidePassenger ridePassenger)) return;
if (!(player.getVehicle() instanceof OrientationControllable vehicle)) return;
var vehicleController = vehicle.getOrientationController();
if (vehicleController == null) return;
// TODO: Determine if it needs partialtick involved.
Vec3 customEyePos = ridePassenger.cobblemon$getRideEyePos();
cir.setReturnValue(customEyePos);
}
@Inject(
method = "getEyePosition()Lnet/minecraft/world/phys/Vec3;",
at = @At("HEAD"),
cancellable = true
)
private void cobblemon$modifyEyePosition_noPartial(CallbackInfoReturnable<Vec3> cir) {
var entity = (Entity)(Object)this;
if (!(entity instanceof Player player)) return;
if (!(player instanceof RidePassenger ridePassenger)) return;
if (!(player.getVehicle() instanceof OrientationControllable vehicle)) return;
var vehicleController = vehicle.getOrientationController();
if (vehicleController == null) return;
// TODO: Determine if it needs partialtick involved.
Vec3 customEyePos = ridePassenger.cobblemon$getRideEyePos();
cir.setReturnValue(customEyePos);
}
}
@@ -83,6 +83,7 @@ import com.cobblemon.mod.common.net.messages.client.settings.ServerSettingsPacke
import com.cobblemon.mod.common.permission.LaxPermissionValidator
import com.cobblemon.mod.common.platform.events.PlatformEvents
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.pokemon.aspects.CHARACTERISTIC_RAINBOW_ASPECT
import com.cobblemon.mod.common.pokemon.aspects.COSMETIC_SLOT_ASPECT
import com.cobblemon.mod.common.pokemon.aspects.GENDER_ASPECT
import com.cobblemon.mod.common.pokemon.aspects.SHINY_ASPECT
@@ -293,6 +294,7 @@ object Cobblemon {
SHINY_ASPECT.register()
GENDER_ASPECT.register()
COSMETIC_SLOT_ASPECT.register()
CHARACTERISTIC_RAINBOW_ASPECT.register()
SpeciesFeatures.types["choice"] = ChoiceSpeciesFeatureProvider::class.java
SpeciesFeatures.types["flag"] = FlagSpeciesFeatureProvider::class.java
@@ -213,7 +213,7 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
@JvmField
val SACCHARINE_SIGN = this.create("saccharine_sign", CobblemonSignBlock(BlockBehaviour.Properties.ofFullCopy(Blocks.OAK_SIGN), SACCHARINE_WOOD_TYPE))
@JvmField
val SACCHARINE_WALL_SIGN = this.create("saccharine_wall_sign", CobblemonWallSignBlock(BlockBehaviour.Properties.ofFullCopy(Blocks.OAK_WALL_SIGN), SACCHARINE_WOOD_TYPE))
val SACCHARINE_WALL_SIGN = this.create("saccharine_wall_sign", CobblemonWallSignBlock(BlockBehaviour.Properties.ofFullCopy(Blocks.OAK_SIGN), SACCHARINE_WOOD_TYPE))
@JvmField
val SACCHARINE_HANGING_SIGN = this.create("saccharine_hanging_sign", CobblemonHangingSignBlock(BlockBehaviour.Properties.ofFullCopy(Blocks.OAK_WALL_HANGING_SIGN), SACCHARINE_WOOD_TYPE))
@JvmField
@@ -593,6 +593,7 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
val CORNN_BERRY = this.berryBlock("cornn")
val CUSTAP_BERRY = this.berryBlock("custap")
val DURIN_BERRY = this.berryBlock("durin")
val EGGANT_BERRY = this.berryBlock("eggant")
val ENIGMA_BERRY = this.berryBlock("enigma")
val FIGY_BERRY = this.berryBlock("figy")
val GANLON_BERRY = this.berryBlock("ganlon")
@@ -51,6 +51,11 @@ object CobblemonItemComponents : PlatformRegistry<Registry<DataComponentType<*>>
.networkSynchronized(FlavourComponent.PACKET_CODEC)
.build())
val RIDE_BOOST: DataComponentType<RideBoostsComponent> = create("ride_boosts", DataComponentType.builder<RideBoostsComponent>()
.persistent(RideBoostsComponent.CODEC)
.networkSynchronized(RideBoostsComponent.PACKET_CODEC)
.build())
val FOOD_COLOUR: DataComponentType<FoodColourComponent> = create("food_colour", DataComponentType.builder<FoodColourComponent>()
.persistent(FoodColourComponent.CODEC)
.networkSynchronized(FoodColourComponent.PACKET_CODEC)
@@ -82,6 +87,7 @@ object CobblemonItemComponents : PlatformRegistry<Registry<DataComponentType<*>>
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:cooking_pot_item"), POT_DATA)
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:bait_effects"), BAIT_EFFECTS)
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:flavour"), FLAVOUR)
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:ride_boosts"), RIDE_BOOST)
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:food_colour"), FOOD_COLOUR)
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:ingredient"), INGREDIENT)
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:food"), FOOD)
@@ -36,7 +36,7 @@ import com.cobblemon.mod.common.item.berry.PPRestoringBerryItem
import com.cobblemon.mod.common.item.berry.PortionHealingBerryItem
import com.cobblemon.mod.common.item.berry.StatusCuringBerryItem
import com.cobblemon.mod.common.item.berry.VolatileCuringBerryItem
import com.cobblemon.mod.common.item.food.PonigiriItem
import com.cobblemon.mod.common.item.interactive.PonigiriItem
import com.cobblemon.mod.common.item.food.SinisterTeaItem
import com.cobblemon.mod.common.item.interactive.*
import com.cobblemon.mod.common.item.interactive.ability.AbilityChangeItem
@@ -517,6 +517,7 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField val RAWST_BERRY = berryItem("rawst", StatusCuringBerryItem(CobblemonBlocks.RAWST_BERRY, Statuses.BURN))
@JvmField val ASPEAR_BERRY = berryItem("aspear", StatusCuringBerryItem(CobblemonBlocks.ASPEAR_BERRY, Statuses.FROZEN))
@JvmField val PERSIM_BERRY = berryItem("persim", VolatileCuringBerryItem(CobblemonBlocks.PERSIM_BERRY, "confusion"))
@JvmField val EGGANT_BERRY = berryItem("eggant", VolatileCuringBerryItem(CobblemonBlocks.EGGANT_BERRY, "attract"))
@JvmField val RAZZ_BERRY = berryItem("razz", CobblemonBlocks.RAZZ_BERRY)
@JvmField val BLUK_BERRY = berryItem("bluk", CobblemonBlocks.BLUK_BERRY)
@JvmField val NANAB_BERRY = berryItem("nanab", CobblemonBlocks.NANAB_BERRY)
@@ -696,7 +697,7 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val SUPERB_REMEDY = compostableItem("superb_remedy", RemedyItem(RemedyItem.SUPERB), 0.85f)
@JvmField
val MOOMOO_MILK = create("moomoo_milk", CobblemonItem(Item.Properties().craftRemainder(Items.GLASS_BOTTLE).stacksTo((16))))
val MOOMOO_MILK = create("moomoo_milk", MoomooMilk(Item.Properties().craftRemainder(Items.GLASS_BOTTLE).stacksTo((16))))
@JvmField
val POTION = create("potion", PotionItem(PotionType.POTION))
@@ -12,6 +12,7 @@ import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.api.data.DataRegistry
import com.cobblemon.mod.common.api.molang.ExpressionLike
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.mechanics.AprijuicesMechanic
import com.cobblemon.mod.common.mechanics.BerriesMechanic
import com.cobblemon.mod.common.mechanics.PotionsMechanic
import com.cobblemon.mod.common.mechanics.RemediesMechanic
@@ -37,12 +38,15 @@ object CobblemonMechanics : DataRegistry {
var remedies = RemediesMechanic()
var berries = BerriesMechanic()
var potions = PotionsMechanic()
var aprijuices = AprijuicesMechanic()
override fun sync(player: ServerPlayer) {}
override fun reload(manager: ResourceManager) {
remedies = loadMechanic(manager, "remedies", RemediesMechanic::class.java)
berries = loadMechanic(manager, "berries", BerriesMechanic::class.java)
potions = loadMechanic(manager, "potions", PotionsMechanic::class.java)
aprijuices = loadMechanic(manager, "aprijuices", AprijuicesMechanic::class.java)
}
private fun <T> loadMechanic(manager: ResourceManager, name: String, clazz: Class<T>): T {
@@ -15,12 +15,12 @@ import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
import com.mojang.serialization.Codec
import com.mojang.serialization.codecs.ListCodec
import com.mojang.serialization.codecs.PrimitiveCodec
import net.minecraft.core.UUIDUtil
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import java.util.Optional
import java.util.UUID
import net.minecraft.core.BlockPos
import net.minecraft.core.GlobalPos
import net.minecraft.core.UUIDUtil
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import java.util.*
object CobblemonMemories {
val memories = mutableMapOf<String, MemoryModuleType<*>>()
@@ -48,10 +48,13 @@ object CobblemonMemories {
val TIME_TRYING_TO_REACH_BERRY_BUSH = register<Int>("time_trying_to_reach_berry_bush")
val IS_CONSUMING_ITEM = register<Boolean>("is_consuming_item")
val RECENTLY_ATE_GRASS = register<Boolean>("recently_ate_grass")
val HIVE_LOCATION = register<BlockPos>("hive_location")
val HIVE_LOCATION = register<BlockPos>("hive_location", BlockPos.CODEC)
val HIVE_BLACKLIST = register<List<BlockPos>>("hive_blacklist")
val HIVE_COOLDOWN = register<Boolean>("hive_cooldown")
val NEARBY_FLOWERS = register<List<BlockPos>>("nearby_flower")
val POLLINATED = register<Boolean>("pollinated", PrimitiveCodec.BOOL)
val NEARBY_FLOWER = register<BlockPos>("nearby_flower", BlockPos.CODEC)
val PATH_TO_NEARBY_FLOWER_COOLDOWN = register<Boolean>("path_to_nearby_flower_cooldown")
val HAS_NECTAR = register<Boolean>("has_nectar", PrimitiveCodec.BOOL)
val NEARBY_SACC_LEAVES = register<BlockPos>("nearby_sacc_leaves", BlockPos.CODEC)
/** who am i following rn? */
val HERD_LEADER = register<String>("herd_leader", PrimitiveCodec.STRING)
/** how many are following me rn? */
@@ -20,6 +20,7 @@ import com.cobblemon.mod.common.client.net.callback.party.OpenPartyCallbackHandl
import com.cobblemon.mod.common.client.net.callback.partymove.OpenPartyMoveCallbackHandler
import com.cobblemon.mod.common.client.net.cooking.ToggleCookingPotLidHandler
import com.cobblemon.mod.common.client.net.data.DataRegistrySyncPacketHandler
import com.cobblemon.mod.common.client.net.data.RideSettingsSyncHandler
import com.cobblemon.mod.common.client.net.dialogue.DialogueClosedHandler
import com.cobblemon.mod.common.client.net.dialogue.DialogueOpenedHandler
import com.cobblemon.mod.common.client.net.effect.PokeSnackBlockParticlesHandler
@@ -27,12 +28,13 @@ import com.cobblemon.mod.common.client.net.effect.RunPosableMoLangHandler
import com.cobblemon.mod.common.client.net.effect.SaccharineLogBlockParticlesHandler
import com.cobblemon.mod.common.client.net.effect.SpawnSnowstormEntityParticleHandler
import com.cobblemon.mod.common.client.net.effect.SpawnSnowstormParticleHandler
import com.cobblemon.mod.common.client.net.gui.ExpGainedDataPacketHandler
import com.cobblemon.mod.common.client.net.gui.InteractPokemonUIPacketHandler
import com.cobblemon.mod.common.client.net.gui.PokedexUIPacketHandler
import com.cobblemon.mod.common.client.net.gui.SummaryUIPacketHandler
import com.cobblemon.mod.common.client.net.npc.CloseNPCEditorHandler
import com.cobblemon.mod.common.client.net.npc.OpenNPCEditorHandler
import com.cobblemon.mod.common.client.net.orientation.S2CUpdateOrientationHandler
import com.cobblemon.mod.common.client.net.orientation.ClientboundUpdateOrientationHandler
import com.cobblemon.mod.common.client.net.pasture.ClosePastureHandler
import com.cobblemon.mod.common.client.net.pasture.OpenPastureHandler
import com.cobblemon.mod.common.client.net.pasture.PokemonPasturedHandler
@@ -131,6 +133,7 @@ import com.cobblemon.mod.common.net.messages.client.trade.TradeOfferNotification
import com.cobblemon.mod.common.net.messages.client.trade.TradeProcessStartedPacket
import com.cobblemon.mod.common.net.messages.client.trade.TradeStartedPacket
import com.cobblemon.mod.common.net.messages.client.trade.TradeUpdatedPacket
import com.cobblemon.mod.common.net.messages.client.ui.ExpGainedDataPacket
import com.cobblemon.mod.common.net.messages.client.ui.InteractPokemonUIPacket
import com.cobblemon.mod.common.net.messages.client.ui.PokedexUIPacket
import com.cobblemon.mod.common.net.messages.client.ui.SummaryUIPacket
@@ -178,6 +181,7 @@ import com.cobblemon.mod.common.net.messages.server.pokemon.update.SetNicknamePa
import com.cobblemon.mod.common.net.messages.server.pokemon.update.evolution.AcceptEvolutionPacket
import com.cobblemon.mod.common.net.messages.server.riding.DismountPokemonPacket
import com.cobblemon.mod.common.net.messages.server.riding.ServerboundUpdateDriverInputPacket
import com.cobblemon.mod.common.net.messages.server.riding.ServerboundUpdateRiderRotationPacket
import com.cobblemon.mod.common.net.messages.server.starter.RequestStarterScreenPacket
import com.cobblemon.mod.common.net.messages.server.storage.SwapPCPartyPokemonPacket
import com.cobblemon.mod.common.net.messages.server.storage.party.MovePartyPokemonPacket
@@ -215,7 +219,7 @@ import com.cobblemon.mod.common.net.serverhandling.dialogue.EscapeDialogueHandle
import com.cobblemon.mod.common.net.serverhandling.dialogue.InputToDialogueHandler
import com.cobblemon.mod.common.net.serverhandling.evolution.AcceptEvolutionHandler
import com.cobblemon.mod.common.net.serverhandling.npc.SaveNPCHandler
import com.cobblemon.mod.common.net.serverhandling.orientation.OrientationPacketHandler
import com.cobblemon.mod.common.net.serverhandling.orientation.ServerboundUpdateOrientationHandler
import com.cobblemon.mod.common.net.serverhandling.pasture.PasturePokemonHandler
import com.cobblemon.mod.common.net.serverhandling.pasture.SetPastureConflictHandler
import com.cobblemon.mod.common.net.serverhandling.pasture.UnpastureAllPokemonHandler
@@ -230,6 +234,7 @@ import com.cobblemon.mod.common.net.serverhandling.pokemon.update.SetMarkingsHan
import com.cobblemon.mod.common.net.serverhandling.pokemon.update.SetNicknameHandler
import com.cobblemon.mod.common.net.serverhandling.riding.DismountPokemonPacketHandler
import com.cobblemon.mod.common.net.serverhandling.riding.DriverInputPacketHandler
import com.cobblemon.mod.common.net.serverhandling.riding.ServerboundUpdateRiderRotationHandler
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
@@ -311,6 +316,7 @@ object CobblemonNetwork {
list.add(PacketRegisterInfo(MarksPotentialUpdatePacket.ID, MarksPotentialUpdatePacket::decode, PokemonUpdatePacketHandler()))
list.add(PacketRegisterInfo(MarkingsUpdatePacket.ID, MarkingsUpdatePacket::decode, PokemonUpdatePacketHandler()))
list.add(PacketRegisterInfo(RideBoostsUpdatePacket.ID, RideBoostsUpdatePacket::decode, PokemonUpdatePacketHandler()))
list.add(PacketRegisterInfo(RideStaminaUpdatePacket.ID, RideStaminaUpdatePacket::decode, PokemonUpdatePacketHandler()))
// Evolution start
list.add(PacketRegisterInfo(AddEvolutionPacket.ID, AddEvolutionPacket::decode, PokemonUpdatePacketHandler()))
@@ -343,6 +349,7 @@ object CobblemonNetwork {
list.add(PacketRegisterInfo(InteractPokemonUIPacket.ID, InteractPokemonUIPacket::decode, InteractPokemonUIPacketHandler))
list.add(PacketRegisterInfo(PlayerInteractOptionsPacket.ID, PlayerInteractOptionsPacket::decode, PlayerInteractOptionsHandler))
list.add(PacketRegisterInfo(PokedexUIPacket.ID, PokedexUIPacket::decode, PokedexUIPacketHandler))
list.add(PacketRegisterInfo(ExpGainedDataPacket.ID, ExpGainedDataPacket::decode, ExpGainedDataPacketHandler))
// Starter packets
list.add(PacketRegisterInfo(OpenStarterUIPacket.ID, OpenStarterUIPacket::decode, StarterUIPacketHandler))
@@ -405,6 +412,7 @@ object CobblemonNetwork {
list.add(PacketRegisterInfo(CosmeticItemAssignmentSyncPacket.ID, CosmeticItemAssignmentSyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(BehaviourSyncPacket.ID, BehaviourSyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(MarkRegistrySyncPacket.ID, MarkRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(RideSettingsSyncPacket.ID, RideSettingsSyncPacket::decode, RideSettingsSyncHandler))
// Effects
list.add(PacketRegisterInfo(SpawnSnowstormParticlePacket.ID, SpawnSnowstormParticlePacket::decode, SpawnSnowstormParticleHandler))
@@ -439,7 +447,7 @@ object CobblemonNetwork {
list.add(PacketRegisterInfo(UpdatePastureConflictFlagPacket.ID, UpdatePastureConflictFlagPacket::decode, UpdatePastureConflictFlagHandler))
// Orientation
list.add(PacketRegisterInfo(ClientboundUpdateOrientationPacket.ID, ClientboundUpdateOrientationPacket::decode, S2CUpdateOrientationHandler))
list.add(PacketRegisterInfo(ClientboundUpdateOrientationPacket.ID, ClientboundUpdateOrientationPacket::decode, ClientboundUpdateOrientationHandler))
// Behaviours
list.add(PacketRegisterInfo(PlayPosableAnimationPacket.ID, PlayPosableAnimationPacket::decode, PlayPosableAnimationHandler))
@@ -574,13 +582,14 @@ object CobblemonNetwork {
list.add(PacketRegisterInfo(SaveNPCPacket.ID, SaveNPCPacket::decode, SaveNPCHandler))
// Riding packet(s)
list.add(PacketRegisterInfo(ServerboundUpdateOrientationPacket.ID, ServerboundUpdateOrientationPacket::decode, OrientationPacketHandler))
list.add(PacketRegisterInfo(ServerboundUpdateOrientationPacket.ID, ServerboundUpdateOrientationPacket::decode, ServerboundUpdateOrientationHandler))
list.add(PacketRegisterInfo(ServerboundUpdateRidingStatePacket.ID, ServerboundUpdateRidingStatePacket::decode, ServerboundUpdateRidingStateHandler))
list.add(PacketRegisterInfo(ServerboundUpdateRidingStatsPacket.ID, ServerboundUpdateRidingStatsPacket::decode, ServerboundUpdateRidingStatsHandler))
list.add(PacketRegisterInfo(ServerboundUpdateRidingStatRangePacket.ID, ServerboundUpdateRidingStatRangePacket::decode, ServerboundUpdateRidingStatRangeHandler))
list.add(PacketRegisterInfo(ServerboundUpdateRidingSettingsPacket.ID, ServerboundUpdateRidingSettingsPacket::decode, ServerboundUpdateRidingSettingsHandler))
list.add(PacketRegisterInfo(DismountPokemonPacket.ID, DismountPokemonPacket::decode, DismountPokemonPacketHandler))
list.add(PacketRegisterInfo(ServerboundUpdateDriverInputPacket.ID, ServerboundUpdateDriverInputPacket::decode, DriverInputPacketHandler))
list.add(PacketRegisterInfo(ServerboundUpdateRiderRotationPacket.ID, ServerboundUpdateRiderRotationPacket::decode, ServerboundUpdateRiderRotationHandler))
// Cooking
list.add(PacketRegisterInfo(ToggleCookingPotLidPacket.ID, ToggleCookingPotLidPacket::decode, ToggleCookingPotLidHandler))
@@ -26,8 +26,6 @@ object CobblemonPoiTypes: PlatformRegistry<Registry<PoiType>, ResourceKey<Regist
@JvmField
val NURSE_KEY = createKey("nurse", CobblemonBlocks.HEALING_MACHINE, 1, 1)
@JvmField
val POKE_SNACK_KEY = createKey("poke_snack", CobblemonBlocks.POKE_SNACK, 0, 1)
@JvmField
val SACCHARINE_LOG_SLATHERED_KEY = createKey("saccharine_log_slathered", CobblemonBlocks.SACCHARINE_LOG_SLATHERED, 0, 1)
// TODO after 1.7
// @JvmField
@@ -0,0 +1,110 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.CobblemonNetwork.sendPacket
import com.cobblemon.mod.common.api.data.DataRegistry
import com.cobblemon.mod.common.api.molang.ExpressionLike
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourSettings
import com.cobblemon.mod.common.api.riding.behaviour.types.air.BirdSettings
import com.cobblemon.mod.common.api.riding.behaviour.types.air.GliderSettings
import com.cobblemon.mod.common.api.riding.behaviour.types.air.HelicopterSettings
import com.cobblemon.mod.common.api.riding.behaviour.types.air.HoverSettings
import com.cobblemon.mod.common.api.riding.behaviour.types.air.JetSettings
import com.cobblemon.mod.common.api.riding.behaviour.types.air.RocketSettings
import com.cobblemon.mod.common.api.riding.behaviour.types.land.HorseSettings
import com.cobblemon.mod.common.api.riding.behaviour.types.land.MinekartSettings
import com.cobblemon.mod.common.api.riding.behaviour.types.land.VehicleSettings
import com.cobblemon.mod.common.api.riding.behaviour.types.liquid.BoatSettings
import com.cobblemon.mod.common.api.riding.behaviour.types.liquid.BurstSettings
import com.cobblemon.mod.common.api.riding.behaviour.types.liquid.DolphinSettings
import com.cobblemon.mod.common.api.riding.behaviour.types.liquid.SubmarineSettings
import com.cobblemon.mod.common.net.messages.client.data.RideSettingsSyncPacket
import com.cobblemon.mod.common.util.adapters.ExpressionAdapter
import com.cobblemon.mod.common.util.adapters.ExpressionLikeAdapter
import com.cobblemon.mod.common.util.adapters.FloatNumberRangeAdapter
import com.cobblemon.mod.common.util.cobblemonResource
import com.google.gson.GsonBuilder
import net.minecraft.advancements.critereon.MinMaxBounds
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
import net.minecraft.server.packs.PackType
import net.minecraft.server.packs.resources.ResourceManager
object CobblemonRideSettings : DataRegistry {
override val id: ResourceLocation = cobblemonResource("ride_settings")
override val type = PackType.SERVER_DATA
override val observable = SimpleObservable<CobblemonRideSettings>()
val gson = GsonBuilder()
.setPrettyPrinting()
.registerTypeAdapter(Expression::class.java, ExpressionAdapter)
.registerTypeAdapter(ExpressionLike::class.java, ExpressionLikeAdapter)
.registerTypeAdapter(MinMaxBounds.Doubles::class.java, FloatNumberRangeAdapter)
.create()
var bird = BirdSettings()
var glider = GliderSettings()
var helicopter = HelicopterSettings()
var hover = HoverSettings()
var jet = JetSettings()
var rocket = RocketSettings()
var horse = HorseSettings()
var minekart = MinekartSettings()
var vehicle = VehicleSettings()
var boat = BoatSettings()
var burst = BurstSettings()
var dolphin = DolphinSettings()
var submarine = SubmarineSettings()
override fun sync(player: ServerPlayer) {
player.sendPacket(
RideSettingsSyncPacket(
bird = bird,
glider = glider,
helicopter = helicopter,
hover = hover,
jet = jet,
rocket = rocket,
horse = horse,
minekart = minekart,
vehicle = vehicle,
boat = boat,
burst = burst,
dolphin = dolphin,
submarine = submarine
)
)
}
override fun reload(manager: ResourceManager) {
bird = loadStyle(manager, "bird", BirdSettings::class.java)
glider = loadStyle(manager, "glider", GliderSettings::class.java)
helicopter = loadStyle(manager, "helicopter", HelicopterSettings::class.java)
hover = loadStyle(manager, "hover", HoverSettings::class.java)
jet = loadStyle(manager, "jet", JetSettings::class.java)
rocket = loadStyle(manager, "rocket", RocketSettings::class.java)
horse = loadStyle(manager, "horse", HorseSettings::class.java)
minekart = loadStyle(manager, "minekart", MinekartSettings::class.java)
vehicle = loadStyle(manager, "vehicle", VehicleSettings::class.java)
boat = loadStyle(manager, "boat", BoatSettings::class.java)
burst = loadStyle(manager, "burst", BurstSettings::class.java)
dolphin = loadStyle(manager, "dolphin", DolphinSettings::class.java)
submarine = loadStyle(manager, "submarine", SubmarineSettings::class.java)
}
private fun <T : RidingBehaviourSettings> loadStyle(manager: ResourceManager, name: String, clazz: Class<T>): T {
manager.getResourceOrThrow(cobblemonResource("ride_settings/$name.json")).open().use {
return gson.fromJson(it.reader(), clazz)
}
}
}
@@ -41,6 +41,8 @@ object CobblemonSensors {
val POKEMON_NEARBY_WANTED_ITEM = register("pokemon_nearby_wanted_item", ::PokemonItemSensor)
val NEARBY_SACC_LEAVES = register("nearby_sacc_leaves", ::SacLeavesSensor)
fun <E : Entity, U : Sensor<E>> register(id: String, supplier: Supplier<U>): SensorType<U> {
val sensor = SensorType(supplier)
sensors[id] = sensor
@@ -103,6 +103,11 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, ResourceKey<Regi
@JvmField
val EVOLUTION = this.create("evolution.full")
@JvmField
val LEVELUP_START = this.create("gui.levelup_start")
@JvmField
val LEVELUP = this.create("gui.levelup")
@JvmField
val PVN_BATTLE = this.create("battle.pvn.default")
@JvmField
@@ -22,18 +22,19 @@ class ReelInPokemonContext(val species : ResourceLocation, val baitId: ResourceL
class ReelInPokemonCriterionCondition(
playerCtx: Optional<ContextAwarePredicate>,
val species: String,
val baitId: String
val baitId: String // also allows "cobblemon:empty_bait" to indicate that no bait is allowed for this criterion to match
) : SimpleCriterionCondition<ReelInPokemonContext>(playerCtx) {
companion object {
val CODEC: Codec<ReelInPokemonCriterionCondition> = RecordCodecBuilder.create { it.group(
EntityPredicate.ADVANCEMENT_CODEC.optionalFieldOf("player").forGetter { it.playerCtx },
Codec.STRING.optionalFieldOf("species", "any").forGetter { it.species },
Codec.STRING.optionalFieldOf("baitId", "empty_bait").forGetter { it.baitId }
).apply(it, { playerCtx, pokemonId, baitId -> ReelInPokemonCriterionCondition(playerCtx, pokemonId, baitId.ifEmpty { "empty_bait" }) }) }
Codec.STRING.optionalFieldOf("baitId", "any").forGetter { it.baitId }
).apply(it, { playerCtx, pokemonId, baitId -> ReelInPokemonCriterionCondition(playerCtx, pokemonId, baitId) }) }
}
override fun matches(player: ServerPlayer, context: ReelInPokemonContext): Boolean {
return (context.species == this.species.asIdentifierDefaultingNamespace() || this.species == "any") && (context.baitId == this.baitId.asIdentifierDefaultingNamespace() || this.baitId == "empty_bait")
return (context.species == this.species.asIdentifierDefaultingNamespace() || this.species == "any")
&& (context.baitId == this.baitId.asIdentifierDefaultingNamespace() || this.baitId == "any")
}
}
@@ -0,0 +1,49 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.ai
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.behavior.Behavior
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.entity.ai.memory.MemoryStatus
import net.minecraft.world.entity.schedule.Activity
/**
* A [Behavior] subclass that represents tasks that check that the entity is still in one
* of the activities that this task is part of. If the entity is no longer doing any of those
* activities, the task will be interrupted and you can handle any cleanup in [onInterrupted].
*
* @author Hiroku
* @since October 17th, 2025
*/
abstract class ActivityAwareBehavior<E : LivingEntity> : Behavior<E> {
constructor(entryCondition: Map<MemoryModuleType<*>, MemoryStatus>) : super(entryCondition)
constructor(entryCondition: Map<MemoryModuleType<*>, MemoryStatus>, duration: Int) : super(entryCondition, duration)
constructor(entryCondition: Map<MemoryModuleType<*>, MemoryStatus>, minDuration: Int, maxDuration: Int) : super(entryCondition, minDuration, maxDuration)
val activities: MutableSet<Activity> = mutableSetOf()
fun isStillDoingActivity(entity: E): Boolean {
return activities.any(entity.brain::isActive)
}
override fun canStillUse(level: ServerLevel, entity: E, gameTime: Long) = isStillDoingActivity(entity)
open fun onInterrupted(level: ServerLevel, entity: E, gameTime: Long) {
// No-op
}
override fun stop(level: ServerLevel, entity: E, gameTime: Long) {
super.stop(level, entity, gameTime)
if (!isStillDoingActivity(entity)) {
onInterrupted(level, entity, gameTime)
}
}
}
@@ -11,7 +11,6 @@ package com.cobblemon.mod.common.api.ai
import com.google.common.collect.ImmutableList
import com.mojang.datafixers.util.Pair
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.Mob
import net.minecraft.world.entity.ai.behavior.BehaviorControl
import net.minecraft.world.entity.schedule.Activity
@@ -23,6 +22,13 @@ class ActivityConfigurationContext(val activity: Activity) {
}
fun apply(entity: LivingEntity) {
tasks.forEach {
val activityAwareBehavior = it.second as? ActivityAwareBehavior<*>
?: (it.second as? WrapperLivingEntityTask<*>)?.task as? ActivityAwareBehavior
?: return@forEach
activityAwareBehavior.activities += activity
}
entity.brain.addActivity(activity, ImmutableList.copyOf(tasks))
}
}
@@ -47,12 +47,23 @@ class ApplyBehaviours : BehaviourConfig {
override fun configure(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext) {
if (!checkCondition(behaviourConfigurationContext, condition)) return
val configurations = behaviours.map { CobblemonBehaviours.behaviours[it]?.takeIf { it.canBeApplied(entity) } ?: return Cobblemon.LOGGER.warn("Behaviour $it not found") }
val applicableBehaviours = behaviours.mapNotNull {
val resolvedBehaviour = CobblemonBehaviours.behaviours[it]
if (resolvedBehaviour == null) {
Cobblemon.LOGGER.warn("Behaviour $it not found while configuring entity of type ${entity.type}")
} else if (!resolvedBehaviour.canBeApplied(entity)) {
Cobblemon.LOGGER.warn("Behaviour $it cannot be applied to entity ${entity.id}")
} else {
return@mapNotNull resolvedBehaviour
}
null
}
// Why not just add the presets to the context directly?
// Nested preset application is a thing, and I only want to track the top level presets.
// i.e. if a preset applies another preset, I don't want to track the inner preset, since it's redundant if we track the top one.
val originalContextBehaviours = behaviourConfigurationContext.appliedBehaviours.toMutableSet()
configurations.forEach {
applicableBehaviours.forEach {
it.configure(entity, behaviourConfigurationContext)
}
originalContextBehaviours.addAll(behaviours)
@@ -23,7 +23,7 @@ import net.minecraft.world.entity.schedule.Activity
class ActivityChangeTaskConfig : SingleTaskConfig {
val activity: Activity = Activity.IDLE
val condition: ExpressionLike = "false".asExpressionLike()
val condition: ExpressionLike = "true".asExpressionLike()
override fun getVariables(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): List<MoLangConfigVariable> = emptyList()
@@ -17,6 +17,7 @@ import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.entity.npc.NPCEntity
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.getMemorySafely
import com.cobblemon.mod.common.util.resolveString
import com.mojang.datafixers.util.Either
import net.minecraft.server.level.ServerLevel
@@ -62,7 +63,7 @@ class MemoryAspectTaskConfig : SingleTaskConfig {
override fun canStillUse(level: ServerLevel, entity: LivingEntity, gameTime: Long) = true
override fun tick(level: ServerLevel, owner: LivingEntity, gameTime: Long) {
val hasMemory = !owner.brain.hasMemoryValue(memory)
val hasMemory = owner.brain.hasMemoryValue(memory)
if (owner is PokemonEntity) {
if (!hasMemory && aspect in owner.pokemon.forcedAspects) {
owner.pokemon.forcedAspects -= aspect
@@ -38,7 +38,7 @@ class MoveToOwnerTaskConfig : SingleTaskConfig {
override fun createTask(
entity: LivingEntity,
behaviourConfigurationContext: BehaviourConfigurationContext
): BehaviorControl<in LivingEntity>? {
): BehaviorControl<in LivingEntity> {
behaviourConfigurationContext.addMemories(
MemoryModuleType.WALK_TARGET,
MemoryModuleType.LOOK_TARGET
@@ -8,16 +8,31 @@
package com.cobblemon.mod.common.api.ai.config.task
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.CobblemonSensors
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.entity.pokemon.ai.tasks.PathToBeeHiveTask
import com.cobblemon.mod.common.entity.pokemon.ai.tasks.PlaceHoneyInHiveTask
import com.cobblemon.mod.common.util.distanceTo
import com.cobblemon.mod.common.util.getMemorySafely
import net.minecraft.core.BlockPos
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.PathfinderMob
import net.minecraft.world.entity.ai.behavior.Behavior
import net.minecraft.world.entity.ai.behavior.BehaviorControl
import net.minecraft.world.entity.ai.behavior.BlockPosTracker
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.entity.ai.memory.MemoryStatus
import net.minecraft.world.entity.ai.memory.WalkTarget
import net.minecraft.world.phys.Vec3
class PathToBeeHiveTaskConfig : SingleTaskConfig {
companion object {
const val HONEY = "honey"
const val STAY_OUT_OF_HIVE_COOLDOWN = 400
}
val condition = booleanVariable(HONEY, "can_add_honey", true).asExpressible()
@@ -36,6 +51,107 @@ class PathToBeeHiveTaskConfig : SingleTaskConfig {
if (!checkCondition(behaviourConfigurationContext.runtime, condition)) {
return null
}
return PathToBeeHiveTask.create()
behaviourConfigurationContext.addSensors(CobblemonSensors.NEARBY_BEE_HIVE)
return object : Behavior<LivingEntity>(
mapOf(
MemoryModuleType.LOOK_TARGET to MemoryStatus.REGISTERED,
MemoryModuleType.WALK_TARGET to MemoryStatus.VALUE_ABSENT,
CobblemonMemories.HAS_NECTAR to MemoryStatus.REGISTERED,
CobblemonMemories.HIVE_LOCATION to MemoryStatus.VALUE_PRESENT,
CobblemonMemories.HIVE_COOLDOWN to MemoryStatus.VALUE_ABSENT,
CobblemonMemories.HIVE_BLACKLIST to MemoryStatus.REGISTERED,
),
400,
400
) {
val maxTicks = 400
var traveledTicks = 0
override fun checkExtraStartConditions(level: ServerLevel, owner: LivingEntity): Boolean {
return (owner is PathfinderMob && owner.isAlive && (owner is PokemonEntity && PlaceHoneyInHiveTask.wantsToEnterHive(owner))) && owner.brain.checkMemory(
MemoryModuleType.WALK_TARGET, MemoryStatus.VALUE_ABSENT)
}
override fun canStillUse(level: ServerLevel, entity: LivingEntity, gameTime: Long): Boolean {
if (maxTicks >= traveledTicks) return false
val pos = entity.brain.getMemory(CobblemonMemories.HIVE_LOCATION)
return !pos.isEmpty && entity.distanceTo(pos.get()) > 1.0
}
override fun start(level: ServerLevel, entity: LivingEntity, gameTime: Long) {
super.start(level, entity, gameTime)
val hiveLocation = entity.brain.getMemorySafely(CobblemonMemories.HIVE_LOCATION).orElse(null)
val world = entity.level()
val sidesByClosest = listOf(
hiveLocation.north(),
hiveLocation.south(),
hiveLocation.east(),
hiveLocation.west()
).sortedBy { it.distSqr(entity.blockPosition()) }
var openSide = BlockPos.ZERO
for (side in sidesByClosest) {
if (world.getBlockState(side).isAir) {
openSide = side
break
}
}
if (openSide == BlockPos.ZERO) {
if (world.getBlockState(hiveLocation.above()).isAir) {
// If the above position is also air, we can use it
openSide = hiveLocation.above() // Fallback to above if no open sides found
} else if (world.getBlockState(hiveLocation.below()).isAir) {
// If the below position is also air, we can use it
openSide = hiveLocation.below() // Fallback to below if no open sides found
} else {
// If no open sides or above/below positions are found, we cannot proceed
return
}
}
val targetVec = Vec3.atCenterOf(openSide)
// Set path target toward hive
if (pathfindDirectlyTowards(openSide, entity)) {
entity.brain.setMemory(MemoryModuleType.WALK_TARGET,WalkTarget(targetVec, 0.35F, 0))
entity.brain.setMemory(MemoryModuleType.LOOK_TARGET,BlockPosTracker(targetVec.add(0.0, entity.eyeHeight.toDouble(), 0.0)))
traveledTicks = 0
} else {
dropAndBlackListHive(entity)
}
}
override fun tick(level: ServerLevel, owner: LivingEntity, gameTime: Long) {
traveledTicks++
}
override fun stop(level: ServerLevel, entity: LivingEntity, gameTime: Long) {
if (maxTicks < traveledTicks) {
dropAndBlackListHive(entity)
entity.brain.setMemoryWithExpiry(CobblemonMemories.HIVE_COOLDOWN, true, 100)
}
}
fun dropAndBlackListHive(entity: LivingEntity) {
val hivePos = entity.brain.getMemorySafely(CobblemonMemories.HIVE_LOCATION).orElse(null)
if (hivePos == null) return
val blackList = entity.brain.getMemorySafely(CobblemonMemories.HIVE_BLACKLIST).orElse(emptyList()).toMutableList()
blackList.add(hivePos)
if (blackList.count() > 3) {
blackList.removeFirst()
}
entity.brain.eraseMemory(CobblemonMemories.HIVE_LOCATION)
entity.brain.setMemory(CobblemonMemories.HIVE_BLACKLIST, blackList)
}
private fun pathfindDirectlyTowards(pos: BlockPos, entity: LivingEntity): Boolean {
if (entity !is PathfinderMob) return false
val nav = entity.navigation
val path = nav. createPath(pos, 0)
return path != null && path.canReach()
}
}
}
}
@@ -8,7 +8,6 @@
package com.cobblemon.mod.common.api.ai.config.task
import com.bedrockk.molang.runtime.MoLangRuntime
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.CobblemonSensors
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
@@ -41,12 +40,14 @@ class PathToFlowerTaskConfig : SingleTaskConfig {
return null
}
behaviourConfigurationContext.addMemories(
CobblemonMemories.NEARBY_FLOWERS,
CobblemonMemories.NEARBY_FLOWER,
CobblemonMemories.PATH_TO_NEARBY_FLOWER_COOLDOWN,
CobblemonMemories.HIVE_COOLDOWN,
CobblemonMemories.HIVE_LOCATION,
CobblemonMemories.POLLINATED
CobblemonMemories.HIVE_BLACKLIST,
CobblemonMemories.HAS_NECTAR,
)
behaviourConfigurationContext.addSensors(CobblemonSensors.NEARBY_BEE_HIVE, CobblemonSensors.NEARBY_FLOWER)
behaviourConfigurationContext.addSensors(CobblemonSensors.NEARBY_FLOWER)
return PathToFlowerTask.create()
}
}
@@ -0,0 +1,49 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.ai.config.task
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.CobblemonSensors
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.entity.pokemon.ai.tasks.PathToSacLeafTask
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.behavior.BehaviorControl
class PathToSaccLeavesTaskConfig : SingleTaskConfig {
companion object {
const val POLLINATE = "pollinate"
}
val condition = booleanVariable(POLLINATE, "can_pollinate", true).asExpressible()
override fun getVariables(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext) = listOf(
condition
).asVariables()
override fun createTask(
entity: LivingEntity,
behaviourConfigurationContext: BehaviourConfigurationContext
): BehaviorControl<in LivingEntity>? {
if (entity !is PokemonEntity) {
return null
}
if (!checkCondition(behaviourConfigurationContext.runtime, condition)) {
return null
}
behaviourConfigurationContext.addMemories(
CobblemonMemories.NEARBY_SACC_LEAVES,
CobblemonMemories.HAS_NECTAR
)
behaviourConfigurationContext.addSensors(CobblemonSensors.NEARBY_SACC_LEAVES, CobblemonSensors.NEARBY_FLOWER)
return PathToSacLeafTask.create()
}
}
@@ -8,8 +8,8 @@
package com.cobblemon.mod.common.api.ai.config.task
import com.bedrockk.molang.runtime.MoLangRuntime
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.WrapperLivingEntityTask
import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.entity.pokemon.ai.tasks.PlaceHoneyInHiveTask
@@ -37,6 +37,9 @@ class PlaceHoneyInHiveTaskConfig : SingleTaskConfig {
if (!checkCondition(behaviourConfigurationContext.runtime, condition)) {
return null
}
return PlaceHoneyInHiveTask.create()
return WrapperLivingEntityTask(
PlaceHoneyInHiveTask.create(),
PokemonEntity::class.java
)
}
}
@@ -0,0 +1,130 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.ai.config.task
import com.cobblemon.mod.common.CobblemonActivities
import com.cobblemon.mod.common.CobblemonBlocks
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.block.SaccharineLeafBlock
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.getMemorySafely
import net.minecraft.server.level.ServerLevel
import net.minecraft.sounds.SoundEvents
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.behavior.Behavior
import net.minecraft.world.entity.ai.behavior.BehaviorControl
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.entity.ai.memory.MemoryStatus
import net.minecraft.world.phys.Vec3
class PlaceHoneyInSaccLeavesTaskConfig : SingleTaskConfig {
companion object {
const val POLLINATE = "pollinate"
const val MIN_DURATION : Int = 300
const val MAX_DURATION : Int = 400
const val REQUIRED_SUCCESSFUL_POLLINATION_TICKS = 250
}
val condition = booleanVariable(POLLINATE, "can_pollinate", true).asExpressible()
override fun getVariables(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext) = listOf(
condition
).asVariables()
override fun createTask(
entity: LivingEntity,
behaviourConfigurationContext: BehaviourConfigurationContext
): BehaviorControl<in LivingEntity>? {
if (entity !is PokemonEntity) {
return null
}
if (!checkCondition(behaviourConfigurationContext.runtime, condition)) {
return null
}
return object : Behavior<LivingEntity>(
mapOf(
CobblemonMemories.NEARBY_SACC_LEAVES to MemoryStatus.VALUE_PRESENT,
MemoryModuleType.WALK_TARGET to MemoryStatus.VALUE_ABSENT,
CobblemonMemories.HAS_NECTAR to MemoryStatus.VALUE_PRESENT,
),
MIN_DURATION,
MAX_DURATION
) {
var successfulPollinationTicks = 0
var lastSoundPlayedTick = 0
var hoverPos: Vec3? = null
override fun checkExtraStartConditions(level: ServerLevel, owner: LivingEntity): Boolean {
if (level.isNight || level.isRaining || !(entity.brain.getMemorySafely(CobblemonMemories.HAS_NECTAR).orElse(false))) return false
val optionalBlockPos = entity.brain.getMemorySafely(CobblemonMemories.NEARBY_SACC_LEAVES)
if (!optionalBlockPos.isPresent || level.getBlockState(optionalBlockPos.get()).block != CobblemonBlocks.SACCHARINE_LEAVES) return false
return Vec3.atCenterOf(optionalBlockPos.get()).distanceTo(entity.position()) <= 0.6
}
override fun canStillUse(level: ServerLevel, entity: LivingEntity, gameTime: Long): Boolean {
return checkExtraStartConditions(level, entity) && entity.brain.activeActivities.indexOf(
CobblemonActivities.POKEMON_POLLINATION) != -1
}
override fun start(level: ServerLevel, entity: LivingEntity, gameTime: Long) {
super.start(level, entity, gameTime)
hoverPos = null
}
override fun tick(level: ServerLevel, owner: LivingEntity, gameTime: Long) {
++successfulPollinationTicks
if (entity.random.nextFloat() < 0.05f && successfulPollinationTicks > this.lastSoundPlayedTick + 60) {
this.lastSoundPlayedTick = successfulPollinationTicks
entity.playSound(SoundEvents.BEEHIVE_DRIP, 1.0f, 1.0f)
}
val leavesPos = entity.brain.getMemorySafely(CobblemonMemories.NEARBY_SACC_LEAVES).orElse(null)
val bl = hoverPos == null || entity.position().distanceTo(hoverPos!!) <= 0.1
val bl3 = entity.random.nextInt(40) == 0
if (bl && bl3) {
// mimics the wriggling over a flower the vanilla bees do
if (hoverPos == null) {
hoverPos = leavesPos.bottomCenter.add(0.0, 0.4, 0.0)
}
if (leavesPos != null) {
hoverPos = leavesPos.bottomCenter.add(Vec3((entity.random.nextDouble() * 2.0 - 1.0) * (1.0/5.0), 0.3 + (entity.random.nextDouble() * 2.0 - 1.0) * (1.0/7.0), (entity.random.nextDouble() * 2.0 - 1.0) * (1.0/5.0)))
entity.getMoveControl()
.setWantedPosition(hoverPos!!.x(), hoverPos!!.y(), hoverPos!!.z(), 0.35)
entity.lookControl.setLookAt(leavesPos.bottomCenter.add(0.0,0.5,0.0))
}
} else if (!bl) {
entity.getMoveControl()
.setWantedPosition(hoverPos!!.x(), hoverPos!!.y(), hoverPos!!.z(), 0.35)
}
}
override fun stop(level: ServerLevel, entity: LivingEntity, gameTime: Long) {
if (successfulPollinationTicks > REQUIRED_SUCCESSFUL_POLLINATION_TICKS) {
val blockPos = entity.brain.getMemorySafely(CobblemonMemories.NEARBY_SACC_LEAVES).orElse(null)
blockPos?.let {
val blockState = level.getBlockState(blockPos)
if (blockState.block == CobblemonBlocks.SACCHARINE_LEAVES) {
val saccAge = blockState.getValue( SaccharineLeafBlock.AGE)
if (saccAge < SaccharineLeafBlock.MAX_AGE) {
entity.brain.eraseMemory(CobblemonMemories.HAS_NECTAR)
val newBlockState = blockState.setValue(SaccharineLeafBlock.AGE, saccAge + 1)
level.setBlock(blockPos, newBlockState, 3)
}
}
}
}
successfulPollinationTicks = 0
entity.brain.eraseMemory(MemoryModuleType.WALK_TARGET)
}
}
}
}
@@ -8,17 +8,14 @@
package com.cobblemon.mod.common.api.ai.config.task
import com.cobblemon.mod.common.CobblemonActivities
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.getMemorySafely
import com.cobblemon.mod.common.util.sendParticlesServer
import com.mojang.datafixers.util.Either
import net.minecraft.core.particles.ParticleTypes
import net.minecraft.server.level.ServerLevel
import net.minecraft.sounds.SoundEvents
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.behavior.Behavior
import net.minecraft.world.entity.ai.behavior.BehaviorControl
@@ -29,10 +26,11 @@ import net.minecraft.world.phys.Vec3
class PollinateFlowerTaskConfig : SingleTaskConfig {
companion object {
const val POLLINATE = "pollinate"
const val PATH_TO_FLOWER_COOLDOWN_TICKS : Long = 200
const val HAS_NECTAR_ASPECT = "has_nectar"
}
val condition = booleanVariable(POLLINATE, "can_pollinate", true).asExpressible()
val durationTicks: ExpressionOrEntityVariable = Either.left("30".asExpression())
override fun getVariables(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext) = listOf(
condition
@@ -51,22 +49,28 @@ class PollinateFlowerTaskConfig : SingleTaskConfig {
return object : Behavior<LivingEntity>(
mapOf(
CobblemonMemories.NEARBY_FLOWERS to MemoryStatus.VALUE_PRESENT,
CobblemonMemories.NEARBY_FLOWER to MemoryStatus.VALUE_PRESENT,
MemoryModuleType.WALK_TARGET to MemoryStatus.VALUE_ABSENT,
CobblemonMemories.POLLINATED to MemoryStatus.VALUE_ABSENT,
CobblemonMemories.HAS_NECTAR to MemoryStatus.REGISTERED,
CobblemonMemories.HIVE_COOLDOWN to MemoryStatus.VALUE_ABSENT,
),
15,
45
600,
625
) {
val REQUIRED_SUCCESSFUL_POLLINATION_TICKS = 400
var successfulPollinationTicks = 0
var lastSoundPlayedTick = 0
var hoverPos: Vec3? = null
override fun checkExtraStartConditions(level: ServerLevel, owner: LivingEntity): Boolean {
return entity.brain.getMemorySafely(CobblemonMemories.NEARBY_FLOWERS).orElse(emptyList()).any {
Vec3.atCenterOf(it).distanceTo(entity.position()) <= 1.0
}
if (level.isNight || level.isRaining || entity.brain.getMemorySafely(CobblemonMemories.HAS_NECTAR).orElse(false)) return false
val optionalBlockPos = entity.brain.getMemorySafely(CobblemonMemories.NEARBY_FLOWER)
return optionalBlockPos.isPresent && Vec3.atCenterOf(optionalBlockPos.get()).distanceTo(entity.position()) <= 1.0
}
override fun canStillUse(level: ServerLevel, entity: LivingEntity, gameTime: Long): Boolean {
return checkExtraStartConditions(level, entity)
return checkExtraStartConditions(level, entity) && entity.brain.activeActivities.indexOf(
CobblemonActivities.POKEMON_POLLINATION) != -1
}
override fun start(level: ServerLevel, entity: LivingEntity, gameTime: Long) {
@@ -74,20 +78,44 @@ class PollinateFlowerTaskConfig : SingleTaskConfig {
}
override fun tick(level: ServerLevel, owner: LivingEntity, gameTime: Long) {
level.sendParticlesServer(
particleType = ParticleTypes.DRIPPING_HONEY,
position = owner.eyePosition,
particles = 3,
offset = Vec3.ZERO,
speed = 0.0
)
++successfulPollinationTicks
if (entity.random.nextFloat() < 0.05f && successfulPollinationTicks > this.lastSoundPlayedTick + 60) {
this.lastSoundPlayedTick = successfulPollinationTicks
entity.playSound(SoundEvents.BEE_POLLINATE, 1.0f, 1.0f)
}
val flowerPos = entity.brain.getMemorySafely(CobblemonMemories.NEARBY_FLOWER).orElse(null)
val bl = hoverPos == null || entity.position().distanceTo(hoverPos!!) <= 0.1
val bl3 = entity.random.nextInt(40) == 0
if (bl && bl3) {
// mimics the wriggling over a flower the vanilla bees do
if (hoverPos == null) {
hoverPos = flowerPos.bottomCenter.add(0.0, 0.4, 0.0)
}
if (flowerPos != null) {
hoverPos = flowerPos.bottomCenter.add(Vec3((entity.random.nextDouble() * 2.0 - 1.0) * (1.0/5.0), 0.3 + (entity.random.nextDouble() * 2.0 - 1.0) * (1.0/7.0), (entity.random.nextDouble() * 2.0 - 1.0) * (1.0/5.0)))
entity.getMoveControl()
.setWantedPosition(hoverPos!!.x(), hoverPos!!.y(), hoverPos!!.z(), 0.35)
entity.lookControl.setLookAt(flowerPos.bottomCenter.add(0.0,0.5,0.0))
}
} else if (!bl) {
entity.getMoveControl()
.setWantedPosition(hoverPos!!.x(), hoverPos!!.y(), hoverPos!!.z(), 0.35)
}
entity.lookControl.setLookAt(flowerPos.bottomCenter.add(0.0,0.5,0.0))
}
override fun stop(level: ServerLevel, entity: LivingEntity, gameTime: Long) {
val flowerStillThere = canStillUse(level, entity, gameTime)
if (flowerStillThere) {
entity.brain.setMemory(CobblemonMemories.POLLINATED, true)
if (successfulPollinationTicks > REQUIRED_SUCCESSFUL_POLLINATION_TICKS) {
entity.brain.setMemory(CobblemonMemories.HAS_NECTAR, true)
}
successfulPollinationTicks = 0
entity.brain.setMemoryWithExpiry(CobblemonMemories.PATH_TO_NEARBY_FLOWER_COOLDOWN, true, PATH_TO_FLOWER_COOLDOWN_TICKS)
}
}
}
@@ -97,7 +97,9 @@ interface TaskConfig {
cobblemonResource("go_to_land") to GoToLandTaskConfig::class.java,
cobblemonResource("path_to_hive") to PathToBeeHiveTaskConfig::class.java,
cobblemonResource("place_honey_in_hive") to PlaceHoneyInHiveTaskConfig::class.java,
cobblemonResource("place_honey_in_sacc_leaves") to PlaceHoneyInSaccLeavesTaskConfig::class.java,
cobblemonResource("path_to_flower") to PathToFlowerTaskConfig::class.java,
cobblemonResource("path_to_sacc_leaves") to PathToSaccLeavesTaskConfig::class.java,
cobblemonResource("pollinate_flower") to PollinateFlowerTaskConfig::class.java,
cobblemonResource("go_to_land") to GoToLandTaskConfig::class.java,
cobblemonResource("manage_flight_in_battle") to ManageFlightInBattleTaskConfig::class.java,
@@ -31,8 +31,10 @@ import net.minecraft.world.entity.ai.behavior.BlockPosTracker
import net.minecraft.world.entity.ai.behavior.declarative.BehaviorBuilder
import net.minecraft.world.entity.ai.behavior.declarative.Trigger
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.level.material.Fluids
import net.minecraft.world.level.pathfinder.PathType
import net.minecraft.world.phys.Vec3
import kotlin.math.ceil
class WaterWanderTaskConfig : SingleTaskConfig {
val condition = booleanVariable(WANDER, "water_wanders", true).asExpressible()
@@ -100,6 +102,33 @@ class WaterWanderTaskConfig : SingleTaskConfig {
return@Trigger false
}
// Move target pos down so that we don't move out of water too much
val requiredDepth = ceil(0.5 + entity.eyeHeight)
// Get the current depth of the block
var depth = 1
var testPos = pos.above()
var blockState = entity.level().getBlockState(testPos)
while (blockState.fluidState.type == Fluids.WATER) {
depth++
testPos = testPos!!.above()
blockState = entity.level().getBlockState(testPos)
}
if (depth < requiredDepth) {
// Iterate down until we reach the desired min depth or we reach a non water block
pos = pos.below()
blockState = entity.level().getBlockState(pos)
while (blockState.fluidState.type == Fluids.WATER && depth < requiredDepth) {
depth ++
pos = pos!!.below()
blockState = entity.level().getBlockState(pos)
}
}
if (depth < requiredDepth) {
return@Trigger false
}
walkTarget.set(
CobblemonWalkTarget(
pos = pos,
@@ -10,7 +10,6 @@ package com.cobblemon.mod.common.api.apricorn
import com.cobblemon.mod.common.CobblemonBlocks
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.api.cooking.Flavour
import com.cobblemon.mod.common.block.ApricornBlock
import com.cobblemon.mod.common.block.ApricornSaplingBlock
import com.cobblemon.mod.common.item.ApricornItem
@@ -28,43 +27,14 @@ import net.minecraft.world.level.material.MapColor
* @author Licious
* @since October 29th, 2022
*/
enum class Apricorn(
/** A value of true means it buffs that flavour, a value of false means it weakens that flavour. */
val flavourStrength: Map<Flavour, Boolean> = emptyMap()
) : StringRepresentable {
enum class Apricorn : StringRepresentable {
BLACK,
BLUE(
mapOf(
Flavour.DRY to true,
Flavour.BITTER to false
)
),
GREEN(
mapOf(
Flavour.BITTER to true,
Flavour.SWEET to false
)
),
PINK(
mapOf(
Flavour.SWEET to true,
Flavour.SPICY to false
)
),
RED(
mapOf(
Flavour.SPICY to true,
Flavour.SOUR to false
)
),
BLUE,
GREEN,
PINK,
RED,
WHITE,
YELLOW(
mapOf(
Flavour.SOUR to true,
Flavour.DRY to false
)
);
YELLOW;
fun item(): ApricornItem = when(this) {
BLACK -> CobblemonItems.BLACK_APRICORN
@@ -131,5 +101,4 @@ enum class Apricorn(
companion object {
val CODEC: Codec<Apricorn> = StringRepresentable.fromValues(::values)
}
}
@@ -0,0 +1,29 @@
/*
* 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.berry.spawncondition
import com.cobblemon.mod.common.api.berry.Berry
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.core.Holder
import net.minecraft.tags.TagKey
import net.minecraft.util.RandomSource
import net.minecraft.world.level.biome.Biome
class SpecificBiomeCondition : BerrySpawnCondition {
lateinit var biome: TagKey<Biome>
val minGroveSize: Int = 1
val maxGroveSize: Int = 1
override fun canSpawn(berry: Berry, biome: Holder<Biome>) = biome.`is`(this.biome)
override fun getGroveSize(random: RandomSource) = random.nextIntBetweenInclusive(minGroveSize, maxGroveSize)
companion object {
val ID = cobblemonResource("specific_biome")
}
}
@@ -8,6 +8,7 @@
package com.cobblemon.mod.common.api.cooking
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import net.minecraft.util.FastColor
import net.minecraft.world.item.ItemStack
@@ -65,6 +66,10 @@ fun getColourMixFromFlavours(dominantFlavours: List<Flavour>, forBubbles: Boolea
)
}
fun getColourMixFromRideStatBoosts(dominantBoosts: Iterable<RidingStat>, forBubbles: Boolean = false): Int? {
return getColourMixFromFlavours(dominantBoosts.map { it.flavour }, forBubbles)
}
fun getColourMixFromColors(colors: List<Int>): Int? {
if (colors.isEmpty()) return null
@@ -9,13 +9,11 @@
package com.cobblemon.mod.common.api.cooking
import com.cobblemon.mod.common.CobblemonItemComponents
import com.cobblemon.mod.common.item.PokePuffItem
import com.cobblemon.mod.common.item.PokePuffItem.Companion.DISLIKED_FLAVOR_MULTIPLIER
import com.cobblemon.mod.common.item.PokePuffItem.Companion.LIKED_FLAVOR_MULTIPLIER
import com.cobblemon.mod.common.pokemon.Nature
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.item.ItemStack
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.world.item.ItemStack
object PokePuffUtils {
@@ -25,11 +23,12 @@ object PokePuffUtils {
val (baseFriendship) = calculateBaseFriendship(stack)
return when {
dominantFlavour == nature.favouriteFlavour -> (baseFriendship / LIKED_FLAVOR_MULTIPLIER).toInt()
dominantFlavour == Flavour.MILD && isNeutralNature -> (baseFriendship / 2.0).coerceAtLeast(1.0).toInt()
else -> (-baseFriendship / DISLIKED_FLAVOR_MULTIPLIER).toInt()
}
val multiplier = if (
dominantFlavour == nature.favouriteFlavour ||
(dominantFlavour == Flavour.MILD && isNeutralNature)
) LIKED_FLAVOR_MULTIPLIER else -DISLIKED_FLAVOR_MULTIPLIER
return (baseFriendship / multiplier).toInt()
}
fun calculateBaseFriendship(stack: ItemStack): Pair<Double, Double> {
@@ -77,8 +76,8 @@ object PokePuffUtils {
Flavour.SPICY -> "attack"
Flavour.DRY -> "special_attack"
Flavour.SWEET -> "speed"
Flavour.BITTER -> "special_defense"
Flavour.SOUR -> "defense"
Flavour.BITTER -> "special_defence"
Flavour.SOUR -> "defence"
Flavour.MILD -> "neutral"
}
}
@@ -22,6 +22,7 @@ interface PokemonSideDelegate : EntitySideDelegate<PokemonEntity> {
fun handleStatus(status: Byte) {}
fun spawnShinyParticle(player: Player) {}
fun spawnAspectParticle() {}
fun applyRenderRotation(partialTick: Float) {}
fun positionRider(passenger: Entity, positionUpdater: MoveFunction)
fun updateAge(age: Int) {}
}
@@ -152,6 +152,9 @@ object CobblemonEvents {
@JvmField
val RIDE_EVENT_PRE = CancelableObservable<RidePokemonEvent.Pre>()
@JvmField
val RIDE_EVENT_APPLY_STAMINA = EventObservable<RidePokemonEvent.ApplyStamina>()
@JvmField
val RIDE_EVENT_POST = EventObservable<RidePokemonEvent.Post>()
@@ -8,9 +8,7 @@
package com.cobblemon.mod.common.api.events.pokemon
import com.cobblemon.mod.common.util.server
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.MoLangFunctions.moLangFunctionMap
@@ -49,4 +47,36 @@ interface RidePokemonEvent {
"pokemon" to pokemon.struct
)
}
/**
* Event published when stamina is being applied. At this point you can modify the stamina that will get used on the
* mount or you can make the stamina infinite. Handy hey.
*/
class ApplyStamina(
override val player: ServerPlayer,
override val pokemon: PokemonEntity,
var rideStamina: Float
) : RidePokemonEvent {
val context = mutableMapOf(
"player" to player.asMoLangValue(),
"pokemon" to pokemon.struct,
"ride_stamina" to DoubleValue(rideStamina)
)
val functions = moLangFunctionMap(
"set_ride_stamina" to { params ->
rideStamina = params.getDouble(0).toFloat().coerceIn(0F, 1F)
DoubleValue.ONE
},
"set_infinite_stamina" to { _ ->
rideStamina = -1F
DoubleValue.ONE
},
)
fun setInfiniteStamina() {
rideStamina = -1F
}
fun isInfiniteStamina() = rideStamina == -1F
}
}
@@ -17,18 +17,11 @@ object SpawnBaitUtils {
return grouped.map { (key, groupEffects) ->
val (type, subcategory) = key
val count = groupEffects.size
val totalChance = groupEffects.sumOf { it.chance }
val totalValue = groupEffects.sumOf { it.value }
val multiplier = when (count) {
2 -> 0.8
3 -> 0.6
else -> 1.0
}
val adjustedChance = (totalChance * multiplier).coerceAtMost(1.0)
val adjustedValue = ceil(totalValue * multiplier)
val adjustedChance = totalChance.coerceAtMost(1.0)
val adjustedValue = ceil(totalValue)
SpawnBait.Effect(type, subcategory, adjustedChance, adjustedValue)
}
@@ -12,7 +12,6 @@ import com.bedrockk.molang.runtime.MoLangEnvironment
import com.bedrockk.molang.runtime.MoLangRuntime
import com.bedrockk.molang.runtime.MoParams
import com.bedrockk.molang.runtime.struct.ArrayStruct
import com.bedrockk.molang.runtime.struct.MoStruct
import com.bedrockk.molang.runtime.struct.QueryStruct
import com.bedrockk.molang.runtime.struct.VariableStruct
import com.bedrockk.molang.runtime.value.DoubleValue
@@ -40,7 +39,6 @@ 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.moves.animations.TargetsProvider
import com.cobblemon.mod.common.api.npc.NPCClass
import com.cobblemon.mod.common.api.npc.NPCClasses
import com.cobblemon.mod.common.api.npc.configuration.interaction.DialogueNPCInteractionConfiguration
import com.cobblemon.mod.common.api.npc.configuration.interaction.ScriptNPCInteractionConfiguration
@@ -57,7 +55,6 @@ import com.cobblemon.mod.common.api.pokemon.evolution.Evolution
import com.cobblemon.mod.common.api.pokemon.experience.SidemodExperienceSource
import com.cobblemon.mod.common.api.pokemon.moves.LearnsetQuery
import com.cobblemon.mod.common.api.pokemon.stats.Stats
import com.cobblemon.mod.common.api.riding.Rideable
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.api.scheduling.ClientTaskTracker
import com.cobblemon.mod.common.api.scheduling.Schedulable
@@ -137,7 +134,6 @@ import net.minecraft.world.entity.LightningBolt
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.Mob
import net.minecraft.world.entity.PathfinderMob
import net.minecraft.world.entity.PlayerRideable
import net.minecraft.world.entity.TamableAnimal
import net.minecraft.world.entity.ai.behavior.BlockPosTracker
import net.minecraft.world.entity.ai.memory.MemoryModuleType
@@ -145,7 +141,6 @@ import net.minecraft.world.entity.ai.memory.MemoryStatus
import net.minecraft.world.entity.ai.memory.WalkTarget
import net.minecraft.world.entity.animal.Animal
import net.minecraft.world.entity.item.ItemEntity
import net.minecraft.world.entity.monster.Enemy
import net.minecraft.world.entity.monster.Monster
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.ItemStack
@@ -160,9 +155,6 @@ import net.minecraft.world.level.levelgen.Heightmap
import net.minecraft.world.level.pathfinder.PathType
import net.minecraft.world.phys.AABB
import net.minecraft.world.phys.Vec3
import kotlin.text.get
import kotlin.text.toInt
import kotlin.toString
/**
* Holds a bunch of useful MoLang trickery that can be used or extended in API
@@ -1185,16 +1177,28 @@ object MoLangFunctions {
return@put DoubleValue.ONE
}
map.put("has_memory_value") { params ->
val memoryTypes = params.params.map {
it.asString().asIdentifierDefaultingNamespace()
}.map(BuiltInRegistries.MEMORY_MODULE_TYPE::get)
return@put DoubleValue(memoryTypes.all { entity.brain.checkMemory(it, MemoryStatus.VALUE_PRESENT) })
for (param in params.params) {
if (!entity.hasMemoryFromString(param.asString())) {
return@put DoubleValue.ZERO
}
}
return@put DoubleValue.ONE
}
map.put("lacks_memory_value") { params ->
val memoryTypes = params.params.map {
it.asString().asIdentifierDefaultingNamespace()
}.map(BuiltInRegistries.MEMORY_MODULE_TYPE::get)
return@put DoubleValue(memoryTypes.none { entity.brain.checkMemory(it, MemoryStatus.VALUE_PRESENT) })
for (param in params.params) {
if (entity.hasMemoryFromString(param.asString())) {
return@put DoubleValue.ZERO
}
}
return@put DoubleValue.ONE
}
map.put("is_doing_activity") { params ->
for (param in params.params) {
if (entity.isDoingActivityFromString(param.asString())) {
return@put DoubleValue.ONE
}
}
return@put DoubleValue.ZERO
}
map.put("can_see") { params ->
val target = params.get<MoValue>(0)
@@ -1252,15 +1256,6 @@ object MoLangFunctions {
map.put("in_battle") { DoubleValue(npc.isInBattle()) }
map.put("battles") { ArrayStruct(npc.battleIds.mapNotNull { BattleRegistry.getBattle(it)?.struct }.mapIndexed { index, value -> "$index" to value }.toMap()) }
map.put("stop_battles") { _ -> npc.battleIds.forEach { BattleRegistry.getBattle(it)?.stop() } }
map.put("is_doing_activity") { params ->
val identifiers = params.params.map { it.asString().asIdentifierDefaultingNamespace(namespace = "minecraft") }
val activities = identifiers.mapNotNull { identifier -> BuiltInRegistries.ACTIVITY.get(identifier) }
if (activities.isNotEmpty()) {
return@put DoubleValue(activities.any { activity -> npc.brain.isActive(activity) })
} else {
return@put DoubleValue.ZERO
}
}
map.put("run_script_on_client") { params ->
val world = npc.level()
if (world is ServerLevel) {
@@ -1905,6 +1900,9 @@ object MoLangFunctions {
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("riding_style") {
StringValue(pokemonEntity.ifRidingAvailableSupply("") { behaviour, settings, state -> behaviour.getRidingStyle(settings, state).name })
}
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.put("is_pastured") {
@@ -53,8 +53,7 @@ open class MoveTemplate(
val pp: Int,
val priority: Int,
val critRatio: Double,
val effectChances: Array<Double>,
val actionEffect: ActionEffectTimeline?
val effectChances: Array<Double>
) {
val struct: MoStruct by lazy {
QueryStruct(hashMapOf())
@@ -87,8 +86,7 @@ open class MoveTemplate(
pp = 5,
priority = 0,
critRatio = 0.0,
effectChances = emptyArray(),
actionEffect = null
effectChances = emptyArray()
)
companion object {
@@ -92,18 +92,7 @@ object Moves : DataRegistry {
effectChances += secondaryMember.get("chance").asDouble
}
}
val actionEffect = ActionEffects.actionEffects[id.asIdentifierDefaultingNamespace()]
?: run {
ActionEffects.actionEffects["generic_move".asIdentifierDefaultingNamespace()]
// if (damageCategory == DamageCategories.STATUS) {
// ActionEffects.actionEffects[cobblemonResource("status")]
// } else {
// val type = elementalType.name.lowercase()
// val category = damageCategory.name.lowercase()
// ActionEffects.actionEffects["${category}_$type".asIdentifierDefaultingNamespace()]
// }
}
val move = MoveTemplate(id, num, elementalType, damageCategory, power, target, accuracy, pp, priority, critRatio, effectChances.toTypedArray(), actionEffect)
val move = MoveTemplate(id, num, elementalType, damageCategory, power, target, accuracy, pp, priority, critRatio, effectChances.toTypedArray())
this.register(move)
} catch (e: Exception) {
Cobblemon.LOGGER.error("Caught exception trying to resolve the move '{}'", id, e)
@@ -14,6 +14,7 @@ import com.cobblemon.mod.common.api.molang.ExpressionLike
import com.cobblemon.mod.common.api.moves.animations.keyframes.*
import com.cobblemon.mod.common.api.pokemon.stats.Stat
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.pokemon.adapters.CobblemonStatTypeAdapter
import com.cobblemon.mod.common.util.adapters.*
import com.cobblemon.mod.common.util.cobblemonResource
@@ -91,5 +92,11 @@ object ActionEffects : JsonDataRegistry<ActionEffectTimeline> {
observable.emit(this)
}
fun getEffectWithBattleContext(id: ResourceLocation, pokemon: BattlePokemon): ActionEffectTimeline? {
val species = pokemon.entity?.exposedSpecies ?: pokemon.effectedPokemon.species
val contextedEffect = actionEffects[ResourceLocation.fromNamespaceAndPath(id.namespace, id.path + "_" + species)]
return contextedEffect ?: actionEffects[id]
}
override fun sync(player: ServerPlayer) {}
}
@@ -63,7 +63,7 @@ open class OrientationController(val entity: LivingEntity) {
}
fun getRenderOrientation(delta: Float): Quaternionf {
val old = renderOrientationO ?: renderOrientation ?: orientation
val old = renderOrientationO ?: renderOrientation ?: orientation ?: Matrix3f()
val new = renderOrientation ?: old
val oldQuat = Quaternionf().setFromUnnormalized(old)
val newQuat = Quaternionf().setFromUnnormalized(new)
@@ -170,11 +170,8 @@ object Natures {
* Utility method to get a nature by string
* @return a nature type or null
*/
fun getNature(identifier: String): Nature? {
val nature = getNature(cobblemonResource(identifier))
if(nature != null) return nature
return getNature(ResourceLocation.parse(identifier))
}
fun getNature(identifier: String) = getNature(cobblemonResource(identifier))
?: getNature(ResourceLocation.parse(identifier))
/**
* Helper function for a random Nature
@@ -131,6 +131,7 @@ open class PokemonProperties {
props.species = parseSpeciesIdentifier(keyPairs)
props.form = parseForm(keyPairs)
props.friendship = parseIntProperty(keyPairs, listOf("friendship"))?.coerceIn(0, Cobblemon.config.maxPokemonFriendship)
props.fullness = parseIntProperty(keyPairs, listOf("fullness"))
props.pokeball = parseIdentifierOfRegistry(keyPairs, listOf("pokeball")) { identifier -> PokeBalls.getPokeBall(identifier)?.name?.toString() }
props.nature = parseIdentifierOfRegistry(keyPairs, listOf("nature")) { identifier -> Natures.getNature(identifier)?.name?.toString() }
props.ability = parseStringOfRegistry(keyPairs, listOf("ability")) { Abilities.get(it)?.name }
@@ -326,6 +327,7 @@ open class PokemonProperties {
var gender: Gender? = null
var level: Int? = null
var friendship: Int? = null
var fullness: Int? = null
var pokeball: String? = null
var nature: String? = null
var ability: String? = null
@@ -397,6 +399,7 @@ open class PokemonProperties {
pokemon.setFriendship(pokemon.form.baseFriendship)
}
}
fullness?.let { pokemon.currentFullness = it }
pokeball?.let { PokeBalls.getPokeBall(it.asIdentifierDefaultingNamespace())?.let { pokeball -> pokemon.caughtBall = pokeball } }
nature?.let { Natures.getNature(it.asIdentifierDefaultingNamespace())?.let { nature -> pokemon.nature = nature } }
customProperties.forEach { it.apply(pokemon) }
@@ -504,6 +507,7 @@ open class PokemonProperties {
nickname?.takeIf { it.string != pokemon.nickname?.string }?.let { return false }
form?.takeIf { !it.equals(pokemon.form.name, true) }?.let { return false }
friendship?.takeIf { it != pokemon.friendship }?.let { return false }
fullness?.takeIf { it != pokemon.currentFullness }?.let { return false }
pokeball?.takeIf { it != pokemon.caughtBall.name.toString() }?.let { return false }
nature?.takeIf { it != pokemon.nature.name.toString() }?.let { return false }
ability?.takeIf { it != pokemon.ability.name }?.let { return false }
@@ -588,6 +592,7 @@ open class PokemonProperties {
originalTrainer?.takeIf { it != properties.originalTrainer }?.let{ return false }
originalTrainerType?.takeIf { it != properties.originalTrainerType }?.let{ return false }
moves?.takeIf { it.any { move -> properties.moves?.none { it == move } == true } }?.let { return false }
fullness?.takeIf { it != properties.fullness }?.let { return false }
return true
}
@@ -636,6 +641,7 @@ open class PokemonProperties {
nickname?.let { nbt.putString(DataKeys.POKEMON_NICKNAME, Component.Serializer.toJson(it, registryLookup)) }
form?.let { nbt.putString(DataKeys.POKEMON_FORM_ID, it) }
friendship?.let { nbt.putInt(DataKeys.POKEMON_FRIENDSHIP, it) }
fullness?.let { nbt.putInt(DataKeys.POKEMON_FULLNESS, it) }
pokeball?.let { nbt.putString(DataKeys.POKEMON_CAUGHT_BALL, it) }
nature?.let { nbt.putString(DataKeys.POKEMON_NATURE, it) }
ability?.let { nbt.putString(DataKeys.POKEMON_ABILITY, it) }
@@ -667,6 +673,7 @@ open class PokemonProperties {
nickname = if (tag.contains(DataKeys.POKEMON_NICKNAME)) Component.Serializer.fromJson(tag.getString(DataKeys.POKEMON_NICKNAME), registryLookup) else null
form = if (tag.contains(DataKeys.POKEMON_FORM_ID)) tag.getString(DataKeys.POKEMON_FORM_ID) else null
friendship = if (tag.contains(DataKeys.POKEMON_FRIENDSHIP)) tag.getInt(DataKeys.POKEMON_FRIENDSHIP) else null
fullness = if (tag.contains(DataKeys.POKEMON_FULLNESS)) tag.getInt(DataKeys.POKEMON_FULLNESS) else null
pokeball = if (tag.contains(DataKeys.POKEMON_CAUGHT_BALL)) tag.getString(DataKeys.POKEMON_CAUGHT_BALL) else null
nature = if (tag.contains(DataKeys.POKEMON_NATURE)) tag.getString(DataKeys.POKEMON_NATURE) else null
ability = if (tag.contains(DataKeys.POKEMON_ABILITY)) tag.getString(DataKeys.POKEMON_ABILITY) else null
@@ -700,6 +707,7 @@ open class PokemonProperties {
//nickname?.let { json.addProperty(DataKeys.POKEMON_NICKNAME, Text.Serialization.toJsonString(it)) }
form?.let { json.addProperty(DataKeys.POKEMON_FORM_ID, it) }
friendship?.let { json.addProperty(DataKeys.POKEMON_FRIENDSHIP, it) }
fullness?.let {json.addProperty(DataKeys.POKEMON_FULLNESS, it)}
pokeball?.let { json.addProperty(DataKeys.POKEMON_CAUGHT_BALL, it) }
nature?.let { json.addProperty(DataKeys.POKEMON_NATURE, it) }
ability?.let { json.addProperty(DataKeys.POKEMON_ABILITY, it) }
@@ -732,6 +740,7 @@ open class PokemonProperties {
//nickname = json.get(DataKeys.POKEMON_NICKNAME)?.asString?.let { Text.Serialization.fromJson(it) }
form = json.get(DataKeys.POKEMON_FORM_ID)?.asString
friendship = json.get(DataKeys.POKEMON_FRIENDSHIP)?.asInt
fullness = json.get(DataKeys.POKEMON_FULLNESS)?.asInt
pokeball = json.get(DataKeys.POKEMON_CAUGHT_BALL)?.asString
nature = json.get(DataKeys.POKEMON_NATURE)?.asString
ability = json.get(DataKeys.POKEMON_ABILITY)?.asString
@@ -763,6 +772,7 @@ open class PokemonProperties {
shiny?.let { pieces.add("shiny=$it") }
gender?.let { pieces.add("gender=$it")}
friendship?.let { pieces.add("friendship=$it") }
fullness?.let {pieces.add("fullness=$it")}
pokeball?.let { pieces.add("pokeball=$it") }
nature?.let { pieces.add("nature=$it") }
ability?.let { pieces.add("ability=$it") }
@@ -796,8 +806,8 @@ open class PokemonProperties {
return PokemonProperties().loadFromJSON(this.saveToJSON())
}
// If the config value is at least 1, then do 1/x and use that as the property chance
private fun Float.checkRate(): Boolean = if (this >= 1) (Random.Default.nextFloat() < 1 / this) else Random.Default.nextFloat() < this
// If the config value is at least 0, then do 1/x and use that as the property chance
private fun Float.checkRate(): Boolean = this > 0 && (Random.nextFloat() < 1 / this)
/**
* Attempts to find an ability by ID and resolve if it should be forced or if it's legal for the given form.
@@ -39,7 +39,6 @@ import com.cobblemon.mod.common.api.pokemon.stats.Stats
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourSettings
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
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
import com.cobblemon.mod.common.api.types.adapters.ElementalTypeAdapter
@@ -129,7 +128,6 @@ object PokemonSpecies : JsonDataRegistry<Species> {
.registerTypeAdapter(MobEffect::class.java, RegistryElementAdapter<MobEffect>(BuiltInRegistries::MOB_EFFECT))
.registerTypeAdapter(ItemPredicate::class.java, LegacyItemConditionWrapperAdapter)
.registerTypeAdapter(RidingBehaviourSettings::class.java, RidingBehaviourSettingsAdapter)
.registerTypeAdapter(RidingStatDefinition::class.java, RidingStatDefinitionAdapter)
.registerTypeAdapter(RideSoundSettingsList::class.java, RideSoundSettingsListAdapter)
.registerTypeAdapter(ObtainableItemCondition::class.java, ObtainableItemConditionAdapter)
.disableHtmlEscaping()
@@ -9,8 +9,9 @@
package com.cobblemon.mod.common.api.pokemon.aspect
import com.cobblemon.mod.common.CobblemonBlocks
import com.cobblemon.mod.common.api.ai.config.task.PollinateFlowerTaskConfig
import com.cobblemon.mod.common.api.spawning.influence.SaccharineLogSlatheredInfluence
import com.cobblemon.mod.common.api.spawning.prospecting.LurePokeSnackDetector
import com.cobblemon.mod.common.block.entity.PokeSnackBlockEntity
import com.cobblemon.mod.common.client.entity.PokemonClientDelegate
import net.minecraft.core.particles.BlockParticleOption
import net.minecraft.core.particles.ParticleOptions
@@ -22,11 +23,12 @@ import net.minecraft.resources.ResourceLocation
*/
val aspectParticleMap: Map<String, ParticleData> = mapOf(
SaccharineLogSlatheredInfluence.HONEY_DRENCHED_ASPECT to ParticleData.MinecraftParticle(ParticleTypes.FALLING_HONEY, 0.075, 1),
LurePokeSnackDetector.POKE_SNACK_CRUMBED_ASPECT to ParticleData.MinecraftParticle(
PokeSnackBlockEntity.POKE_SNACK_CRUMBED_ASPECT to ParticleData.MinecraftParticle(
BlockParticleOption(
ParticleTypes.BLOCK,
CobblemonBlocks.POKE_SNACK.defaultBlockState()
), 0.05, 3),
PollinateFlowerTaskConfig.HAS_NECTAR_ASPECT to ParticleData.MinecraftParticle(ParticleTypes.FALLING_NECTAR, 0.075, 1)
)
sealed class ParticleData {
@@ -9,10 +9,7 @@
package com.cobblemon.mod.common.api.riding
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.Cobblemon
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.*
import com.cobblemon.mod.common.util.adapters.RidingBehaviourSettingsAdapter
import net.minecraft.network.RegistryFriendlyByteBuf
@@ -32,10 +29,10 @@ class RidingProperties(
{ buffer.readEnumConstant(RidingStyle::class.java) },
{
val key = buffer.readResourceLocation()
val settings = RidingBehaviourSettingsAdapter.types[key]?.getConstructor()?.newInstance()
val behaviour = RidingBehaviourSettingsAdapter.types[key]?.getConstructor()?.newInstance()
?: error("Unknown controller key: $key")
settings.decode(buffer)
settings
behaviour.decode(buffer)
behaviour
}
)
}
@@ -51,7 +48,10 @@ class RidingProperties(
buffer.writeMap(
v,
{ _, style -> buffer.writeEnumConstant(style) },
{ _, behaviour -> behaviour.encode(buffer) }
{ _, behaviour ->
buffer.writeResourceLocation(behaviour.key)
behaviour.encode(buffer)
}
)
}
}
@@ -103,6 +103,18 @@ interface RidingBehaviour<Settings : RidingBehaviourSettings, State : RidingBeha
*/
fun damageOnCollision(settings: Settings, state: State, vehicle: PokemonEntity, impactVec: Vec3): Boolean = false
/**
* Called in [com.cobblemon.mod.common.client.render.pokemon.PokemonRenderer] to allow the controller to set rotations
* through code using partialTicks and right before rendering.
* //TODO: This needs to be further refined. Maybe only the orientationController needs affected? Maybe the rotations set here don't need to be applied to the controller/camera
*/
fun applyRenderRotation(settings: Settings, state: State, vehicle: PokemonEntity, partialTicks: Float) {}
/**
* Determines if the pair of mouse inputs Pair(mouseX, mouseY) get used to modify the driverRotations or the vehicle rotations themselves.
* Driver rotations are the rotations used by passengers or a driver that is freelooking. This does not override the freelook button.
*/
fun mouseModifiesDriverRotation(settings: Settings, state: State, vehicle: PokemonEntity): Pair<Boolean, Boolean> = Pair(false, false)
/**
* Internal helpers to help behaviour specific calculations
@@ -19,6 +19,9 @@ import net.minecraft.resources.ResourceLocation
* Typically this will be initialized for each pokemon form during deserialization
* to determine how they should ride.
*
* These also exist in a datapacked folder, ride_settings, which are used for the fallback
* values if non-stat settings are omitted in the pokemon form JSON.
*
* @author landonjw
*/
interface RidingBehaviourSettings: Encodable, Decodable {
@@ -27,8 +30,6 @@ interface RidingBehaviourSettings: Encodable, Decodable {
fun calculate(stat: RidingStat, boostAmount: Float): Float {
val range = stats[stat] ?: return 0F
return range.first + boostAmount
return (range.first + boostAmount).coerceAtMost(range.endInclusive.toFloat())
}
fun hasStat(stat: RidingStat) = stats.containsKey(stat)
}
@@ -18,7 +18,7 @@ import net.minecraft.world.phys.Vec3
* 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.
* This will be destroyed when the main rider dismounts the Pokemon.
*
* @author landonjw
*/
@@ -16,6 +16,7 @@ import com.cobblemon.mod.common.api.riding.behaviour.types.land.MinekartBehaviou
import com.cobblemon.mod.common.api.riding.behaviour.types.liquid.BoatBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.types.liquid.BurstBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.types.liquid.DolphinBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.types.liquid.SubmarineBehaviour
import net.minecraft.resources.ResourceLocation
object RidingBehaviours {
@@ -34,6 +35,7 @@ object RidingBehaviours {
register(MinekartBehaviour.KEY, MinekartBehaviour())
register(HoverBehaviour.KEY, HoverBehaviour())
register(RocketBehaviour.KEY, RocketBehaviour())
register(SubmarineBehaviour.KEY, SubmarineBehaviour())
register(CompositeBehaviour.KEY, CompositeBehaviour())
}
@@ -8,13 +8,20 @@
package com.cobblemon.mod.common.api.riding.behaviour
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.DoubleJump
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.client.MountedCameraTypeHandler
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import net.minecraft.resources.ResourceLocation
import net.minecraft.tags.FluidTags
import net.minecraft.world.entity.LivingEntity
import kotlin.collections.get
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec3
import kotlin.collections.get
import kotlin.math.max
/**
* Small wrapper around a RidingBehaviour to provide sane defaults in the event that the behaviour is not active.
@@ -44,12 +51,25 @@ class RidingController(
fun tick() {
if (entity.ticksLived - lastTransitionAge < 10) return
val newTransition = checkForNewTransition(entity.controllingPassenger)
// If transitioning to air then give an upward boost
if (newTransition == RidingStyle.AIR && context?.style != RidingStyle.AIR && context?.state != null) {
val currVel = context!!.state.rideVelocity.get()
context!!.state.rideVelocity.set(Vec3(currVel.x, max(0.4, currVel.y), currVel.z))
}
val behaviourSettings = entity.pokemon.riding.behaviours?.get(newTransition)
if (newTransition != context?.style) {
if (newTransition != null && behaviourSettings != null) {
val newState = RidingBehaviours.get(behaviourSettings.key).createDefaultState(behaviourSettings)
context?.let { newState.stamina.set(it.state.stamina.get(), forced = true) }
context?.let { newState.rideVelocity.set(it.state.rideVelocity.get(), forced = true) }
val previousKey = context?.behaviour
val newKey = behaviourSettings.key
for (passenger in entity.passengers) {
if (passenger is Player && passenger !is ServerPlayer) {
MountedCameraTypeHandler.handleTransition(passenger, previousKey ?: continue, newKey)
}
}
context = ActiveRidingContext(behaviourSettings.key, behaviourSettings, newState, newTransition)
lastTransitionAge = entity.ticksLived
} else {
@@ -97,12 +117,22 @@ class RidingController(
}
private fun canTransitionToLand(): Boolean {
if (entity.isInLiquid || entity.isUnderWater) return false
val hasLandController = entity.pokemon.riding.behaviours?.get(RidingStyle.LAND) != null
if (hasLandController && (entity.isInLiquid || entity.isUnderWater)) return false
if (!hasLandController && (entity.isEyeInFluid(FluidTags.WATER) || entity.isEyeInFluid(FluidTags.LAVA))) return false
return entity.onGround()
}
private fun canTransitionToLiquid(): Boolean {
return entity.isInLiquid || entity.isUnderWater
// If it has a liquid controller to transition to and it isn't currently an air controller then transition on
// touching water. If not then check for eyes in water. This allows fliers to fly closely above water and
// for walkers that don't have a liquid controller to be able to walk in not deep water
return if (entity.pokemon.riding.behaviours?.get(RidingStyle.LIQUID) != null && context?.style != RidingStyle.AIR) {
entity.isInLiquid || entity.isUnderWater
} else {
entity.isEyeInFluid(FluidTags.WATER) || entity.isEyeInFluid(FluidTags.LAVA)
}
}
private fun canTransitionToAir(driver: LivingEntity?): Boolean {
@@ -11,6 +11,7 @@ package com.cobblemon.mod.common.api.riding.behaviour.types.air
import com.bedrockk.molang.Expression
import com.bedrockk.molang.runtime.MoLangMath.lerp
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonRideSettings
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.api.orientation.OrientationController
import com.cobblemon.mod.common.api.riding.RidingStyle
@@ -33,6 +34,7 @@ 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 org.joml.Vector3f
import kotlin.math.*
class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
@@ -49,19 +51,11 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
val poseProvider = PoseProvider<BirdSettings, BirdState>(PoseType.HOVER)
.with(PoseOption(PoseType.FLY) { _, state, _ -> state.rideVelocity.get().z > 0.2 })
val globalBird: BirdSettings
get() = CobblemonRideSettings.bird
override fun isActive(settings: BirdSettings, state: BirdState, 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
}
return !(vehicle.isInWater || vehicle.isUnderWater)
}
override fun pose(settings: BirdSettings, state: BirdState, vehicle: PokemonEntity): PoseType {
@@ -95,7 +89,8 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
vehicle: PokemonEntity,
driver: Player,
) {
val controller = (driver as OrientationControllable).orientationController
// Also checks stamina
val controller = (vehicle as OrientationControllable).orientationController
if (cos(controller.roll.toRadians()) < 0.0) {
val upsideDownRate = 1.0 / (2.0 * 20.0) // 2 seconds to max out upsideDown force
state.currUpsideDownForce.set( min(1.0, state.currUpsideDownForce.get() + upsideDownRate * abs(cos(controller.roll.toRadians()))))
@@ -114,16 +109,28 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
driver: Player
) {
val stam = state.stamina.get()
val controller = (driver as OrientationControllable).orientationController
if (vehicle.runtime.resolveBoolean(settings.infiniteStamina ?: globalBird.infiniteStamina!!)) {
return
}
// Grab the boost time in seconds and convert to ticks. Then calculate the drain rate as inversely
// proportional to the number of ticks of boost thus making a full boost take x ticks
// in short: "Stamina drains slower at higher values and also replenishes slower"
val totalStam = vehicle.runtime.resolveDouble(settings.staminaExpr) * 20.0f
val stamDrainRate = (1.0f / totalStam).toFloat()
val totalStam = vehicle.runtime.resolveDouble(settings.staminaExpr ?: globalBird.staminaExpr!!) * 20.0f
val horzMod = vehicle.runtime.resolveDouble(settings.stamDrainHorzMod ?: globalBird.stamDrainHorzMod!!)
val glideMod = vehicle.runtime.resolveFloat(settings.stamDrainPercRateGlide ?: globalBird.stamDrainPercRateGlide!!)
val stamDrainPerRateDive = vehicle.runtime.resolveDouble(settings.stamDrainPerRateDive ?: globalBird.stamDrainPerRateDive!!).coerceIn(0.0, 1.0) // Ensure its between 1 and 0 or things get weird
val newStam = if (driver.zza > 0.0) max(0.0f,stam - stamDrainRate)
else if (!state.gliding.get()) max(0.0f,stam - stamDrainRate * 0.5f) // if hovering half the stam drain
else min(1.0f,stam + stamDrainRate * 0.2f)
val stamDrainHorz = if(vehicle.deltaMovement.horizontalDistance() < 1e-6) horzMod else max(1.0 + ((horzMod - 1.0) * atan( vehicle.deltaMovement.y / vehicle.deltaMovement.horizontalDistance())), 1.0)
val diveAmount = sin(controller.pitch.toRadians()).coerceIn(0.0f, 1.0f)// from 0.0 -> 1.0 how steep is the current dive angle
val diveMod = ((1.0 - diveAmount) * ( 1.0 - stamDrainPerRateDive ) + stamDrainPerRateDive).toFloat() // smooth between 1.0 and the dive drain mod depending upon how dived the ride is
val stamDrainRate = (1.0f / totalStam).toFloat() * stamDrainHorz.toFloat()
val newStam = if (driver.deltaMovement.length() > 0.1) max(0.0f,stam - stamDrainRate) // Drain at normal fly rate
else if (state.gliding.get()) min(1.0f,stam - stamDrainRate * glideMod * diveMod) // Gliding drain logic
else max(0.0f,stam - stamDrainRate * 0.5f) // if hovering half the stam drain
state.stamina.set(newStam)
}
@@ -134,7 +141,7 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
vehicle: PokemonEntity,
driver: LivingEntity
): Vec2 {
return Vec2(driver.xRot * 0.5f, driver.yRot)
return Vec2.ZERO//Vec2(driver.xRot * 0.5f, driver.yRot)
}
override fun velocity(
@@ -144,29 +151,26 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
driver: Player,
input: Vec3
): Vec3 {
var upForce = 0.0
var forwardForce = 0.0
val controller = (vehicle as? OrientationControllable)?.orientationController
if (controller == null || controller.orientation == null) return Vec3.ZERO
//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
}
// The downward force used to encourage players to stop flying upside down.
val extraDownwardForce = state.currUpsideDownForce.get().pow(2) * -0.3 // 6 blocks a second downward
val velocity = Vec3(state.rideVelocity.get().x , upForce + extraDownwardForce, forwardForce)
return velocity
// Convert the local velocity vector into a world vector
val localVelVec = Vector3f(
state.rideVelocity.get().x.toFloat(),
(state.rideVelocity.get().y).toFloat(),
state.rideVelocity.get().z.toFloat() * -1.0f // Flip the z axis to make this left handed
)
// Unrotate preemptively as this vector gets rotatee later down the line in MC logic.
var worldVelVec = localVelVec.mul(controller.orientation).toVec3d().yRot(vehicle.yRot.toRadians())
worldVelVec = worldVelVec.add(0.0, extraDownwardForce, 0.0) // Add the stamina depletion force to bring the ride down.
return worldVelVec
}
/*
@@ -174,9 +178,9 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
*/
fun calculateRideSpaceVel(settings: BirdSettings, state: BirdState, vehicle: PokemonEntity, driver: Player) {
// retrieve stats
val topSpeed = (vehicle.runtime.resolveDouble(settings.speedExpr) / 20.0)
val accel = topSpeed / (vehicle.runtime.resolveDouble(settings.accelerationExpr) * 20.0)
val glideTopSpeed = topSpeed * vehicle.runtime.resolveDouble(settings.glidespeedExpr)
val topSpeed = (vehicle.runtime.resolveDouble(settings.speedExpr ?: globalBird.speedExpr!!) / 20.0)
val accel = topSpeed / (vehicle.runtime.resolveDouble(settings.accelerationExpr ?: globalBird.accelerationExpr!!) * 20.0)
val glideTopSpeed = topSpeed * vehicle.runtime.resolveDouble(settings.glideSpeedExpr ?: globalBird.glideSpeedExpr!!)
var glideSpeedChange = 0.0
var activeInput = false
@@ -187,8 +191,8 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
newVelocity = newVelocity.normalize().scale(vehicle.deltaMovement.length())
}
//speed up and slow down based on input
if (driver.zza != 0.0f && state.stamina.get() > 0.0) {
// speed up and slow down based on input
if (driver.zza != 0.0f && state.stamina.get() > 0.0 && newVelocity.length() < glideTopSpeed) {
//make sure it can't exceed top speed
val forwardInput = when {
state.stamina.get() == 0.0f -> 0.0
@@ -205,7 +209,7 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
activeInput = true
}
val controller = (driver as? OrientationControllable)?.orientationController
val controller = (vehicle as? OrientationControllable)?.orientationController
if (controller != null) {
//Base glide speed change on current pitch of the ride.
glideSpeedChange = sin(Math.toRadians(controller.pitch.toDouble()))
@@ -281,27 +285,28 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
driver: Player,
deltaTime: Double
): Vec3 {
if (Cobblemon.config.disableRoll) return Vec3.ZERO
if (driver !is OrientationControllable) return Vec3.ZERO
val controller = (driver as OrientationControllable).orientationController
if (vehicle !is OrientationControllable) return Vec3.ZERO
val controller = (vehicle as OrientationControllable).orientationController
val handling = vehicle.runtime.resolveDouble(settings.handlingExpr)
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr) / 20.0
val handling = vehicle.runtime.resolveDouble(settings.handlingExpr ?: globalBird.handlingExpr!!)
val yawDeltaDeg = deltaTime * handling * sin(Math.toRadians(controller.roll.toDouble())) //* sin(Math.toRadians(controller.roll.toDouble())).sign
val trueYawDelt = yawDeltaDeg * abs(cos(Math.toRadians(controller.pitch.toDouble())))// * sqrt(RidingBehaviour.scaleToRange(state.rideVelocity.get().length(), 0.0, topSpeed))
val yawDeltaDeg = deltaTime * handling * sin(Math.toRadians(controller.roll.toDouble()))
val trueYawDelt = yawDeltaDeg * abs(cos(Math.toRadians(controller.pitch.toDouble()))).coerceIn(-1.0, 1.0)
// Dampen yaw when upside down
val yawDampen = (1 - abs(min(cos(controller.roll.toRadians()),0.0f)).pow(2))
val yawDampen = (1 - abs(min(cos(controller.roll.toRadians()),0.0f)))
controller.applyGlobalYaw(trueYawDelt.toFloat() * yawDampen)
// Correct orientation when at low speeds
correctOrientation(settings, state, vehicle, controller, deltaTime)
controller.applyGlobalPitch(state.currPitchCorrectionForce.get().toRadians())
val pCorr = (state.currPitchCorrectionForce.get() * deltaTime).toFloat()
val rCorr = (state.currRollCorrectionForce.get() * deltaTime).toFloat()
controller.applyGlobalPitch(pCorr)
controller.rotateRoll(rCorr)
//yaw, pitch, roll
return Vec3(0.0, 0.0, state.currRollCorrectionForce.get())
return Vec3(0.0, 0.0, 0.0)
}
private fun correctOrientation(
@@ -312,45 +317,48 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
deltaTime: Double
) {
// Calculate correcting roll force.
// Mult by cos(pitch) to ensure that it doesn't roll correct at when diving or climbing
val rollCorrectionTimer = vehicle.runtime.resolveDouble(settings.timeToRollCorrect)
val maxRollCorrectionRate = if (state.rideVelocity.get().length() < 0.1) 15.0f else 8.0f * (cos(controller.pitch.toRadians())).pow(2) * ((state.noInputTimeRoll.get() - rollCorrectionTimer)).coerceIn(0.0, 1.0).toFloat()
val maxRollForce = maxRollCorrectionRate / 40.0
val rollArrivalDeg = 90.0
if(state.rideVelocity.get().length() < 0.1 || state.noInputTimeRoll.get() > rollCorrectionTimer) {
val arrivalInfluence = (min(rollArrivalDeg,abs(controller.roll).toDouble()) / rollArrivalDeg)
val desiredRollForce = (0 - controller.roll).sign * maxRollCorrectionRate * arrivalInfluence
var steeredRollForce = desiredRollForce - state.currRollCorrectionForce.get()
val rollCorrectionTimer = vehicle.runtime.resolveDouble(settings.timeToRollCorrect ?: globalBird.timeToRollCorrect!!)
// If the ride is moving then have the correction rate be an equal proportion of:
// - how rolled are you
// - how long has it been since last mouse input (higher is stronger correction)
// Also the correction is proportional as a whole to how NOT pitched are you so you don't spin endlessly when looking up or down
val howRolledAmI = sqrt(abs((cos(controller.roll.toRadians()) - 1.0f) * 0.5f)) // sqrt so it trends towards one pretty quickly
val maxRollCorrectionRate =
if (state.rideVelocity.get().length() < 0.2) 45.0f
else 45.0f * cos(controller.pitch.toRadians()).pow(2) * (
howRolledAmI*1.0f +
(state.noInputTimeRoll.get() - rollCorrectionTimer).coerceIn(0.0, 1.0).pow(2).toFloat()*0.5f
)
val maxRollForce = maxRollCorrectionRate / 40.0f
val rollArrivalDeg = 35.0f
val desiredRoll = 0.0f
// Ensure the 'steering' correction force doesn't exceed our maxForce
if (abs(steeredRollForce) > maxRollForce) {
steeredRollForce = steeredRollForce.sign * maxRollForce
}
// Apply the 'steered' force (its really not a steering force I need a better name)
state.currRollCorrectionForce.set(
state.currRollCorrectionForce.get() + steeredRollForce
)
} else {
state.currRollCorrectionForce.set(
//reduce rollCorrection gradually
lerp(state.currRollCorrectionForce.get(), 0.0, deltaTime * 0.96)
)
// Calculate the signed error (how far and in what direction)
val rollError = Mth.wrapDegrees(desiredRoll.angleDifference(controller.roll))
// Use the absolute error to calculate the magnitude of the influence
val influenceMagnitude = (min(rollArrivalDeg, abs(rollError)) / rollArrivalDeg)
val desiredRollForce = maxRollCorrectionRate * influenceMagnitude * -rollError.sign
var steeredRollForce = desiredRollForce - state.currRollCorrectionForce.get()
// Ensure the 'steering' correction force doesn't exceed our maxForce
if (abs(steeredRollForce) > maxRollForce) {
steeredRollForce = steeredRollForce.sign * maxRollForce
}
// Apply the 'steered' force (its really not a steering force I need a better name)
state.currRollCorrectionForce.set(
state.currRollCorrectionForce.get() + steeredRollForce
)
//Calculate correcting pitch force.
val maxPitchCorrectionRate = if (state.currUpsideDownForce.get() != 0.0) 8f * state.currUpsideDownForce.get() else 15f
val maxPitchForce = maxPitchCorrectionRate.toFloat() / 40.0f
val pitchArrivalDeg = 45.0f
var desiredPitch = if (state.currUpsideDownForce.get() != 0.0) 90.0 else 0.0
// if (cos(controller.roll.toRadians()) < 0.0) {
// desiredPitch *= -1 // If not rolled over then flip the pitch force. There are better ways to do this :(
// }
val maxPitchCorrectionRate = if (state.currUpsideDownForce.get() != 0.0) 15.0f * state.currUpsideDownForce.get().toFloat() else 30.0f
val maxPitchForce = maxPitchCorrectionRate / 40.0f
val pitchArrivalDeg = 35.0f
val desiredPitch = if (state.currUpsideDownForce.get() != 0.0) 90.0f else 0.0f
if((state.rideVelocity.get().length() < 0.1 && controller.pitch < 0.0) || state.currUpsideDownForce.get() != 0.0) {
val arrivalInfluence = (min(pitchArrivalDeg,abs(Mth.wrapDegrees(desiredPitch.toFloat().angleDifference(controller.pitch)))) / pitchArrivalDeg)
val desiredPitchForce = maxPitchCorrectionRate.toFloat() * arrivalInfluence
var steeredPitchForce = (desiredPitchForce - state.currPitchCorrectionForce.get())
if((state.rideVelocity.get().length() < 0.1) || state.currUpsideDownForce.get() != 0.0) {
val pitchErrror = Mth.wrapDegrees(desiredPitch.angleDifference(controller.pitch))
val arrivalInfluence = (min(pitchArrivalDeg,abs(pitchErrror)) / pitchArrivalDeg)
val desiredPitchForce = maxPitchCorrectionRate * arrivalInfluence * -pitchErrror.sign
var steeredPitchForce = desiredPitchForce - state.currPitchCorrectionForce.get()
// Ensure the 'steering' correction force doesn't exceed our maxForce
if (abs(steeredPitchForce) > maxPitchForce) {
@@ -400,80 +408,18 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
state.noInputTimePitch.set(0.0)
}
return when {
Cobblemon.config.disableRoll -> noRollRotation(
settings,
state,
vehicle,
driver,
mouseY,
mouseX,
mouseYSmoother,
mouseXSmoother,
sensitivity,
deltaTime
return rollRotation(
settings,
state,
vehicle,
driver,
mouseY,
mouseX,
mouseYSmoother,
mouseXSmoother,
sensitivity,
deltaTime
)
else -> rollRotation(
settings,
state,
vehicle,
driver,
mouseY,
mouseX,
mouseYSmoother,
mouseXSmoother,
sensitivity,
deltaTime
)
}
}
fun noRollRotation(
settings: BirdSettings,
state: BirdState,
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
// Set roll to zero if transitioning to noroll config
controller.rotateRoll(controller.roll * -1.0f)
val handling = vehicle.runtime.resolveDouble(settings.handlingExpr)
val mouseInputMult = vehicle.runtime.resolveDouble(settings.mouseInputMult)
//Smooth out mouse input.
val smoothingSpeed = 4.0
val mouseXc = ((mouseX).coerceIn(-60.0, 60.0) / 60.0) * mouseInputMult
val mouseYc = ((mouseY).coerceIn(-60.0, 60.0) / 60.0) * mouseInputMult
val xInput = mouseXSmoother.getNewDeltaValue(mouseXc * 0.1, deltaTime * smoothingSpeed);
val yInput = mouseYSmoother.getNewDeltaValue(mouseYc * 0.1, deltaTime * smoothingSpeed);
//Give the ability to yaw with x mouse input when at low speeds.
val yawForce = xInput * handling //* ( 1.0 - min(sqrt(RidingBehaviour.scaleToRange(state.rideVelocity.get().length(), 0.0, topSpeed)), 0.5))
//Apply yaw globally as we don't want roll or pitch changes due to local yaw when looking up or down.
controller.applyGlobalYaw(yawForce.toFloat() * abs(cos(controller.pitch.toRadians())).pow(2))
var pitchRot = yInput * handling
// Pitch up globally
if (abs(controller.pitch + pitchRot) >= 89.5 ) {
pitchRot = 0.0
mouseYSmoother.reset()
} else {
controller.applyGlobalPitch(pitchRot.toFloat() * -1.0f)
}
//yaw, pitch, roll
return Vec3.ZERO
}
fun rollRotation(
@@ -488,35 +434,37 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
sensitivity: Double,
deltaTime: Double
): Vec3 {
if (driver !is OrientationControllable) return Vec3.ZERO
val controller = (driver as OrientationControllable).orientationController
if (vehicle !is OrientationControllable) return Vec3.ZERO
val controller = (vehicle as OrientationControllable).orientationController
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr) / 20.0
val mouseInputMult = vehicle.runtime.resolveDouble(settings.mouseInputMult)
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr ?: globalBird.speedExpr!!) / 20.0
val mouseInputMult = vehicle.runtime.resolveDouble(settings.mouseInputMult ?: globalBird.mouseInputMult!!)
// Applied to mouseX to prevent turning upside down when trying to roll hard.
val rollDegreeDebuff = if(mouseX.sign.toFloat() == controller.roll.sign) 1 - 0.9f*sqrt(abs(sin(controller.roll.toRadians())))*sqrt(abs(cos(controller.pitch))) else 1.0f
//Smooth out mouse input.
val smoothingSpeed = 2.0
val mouseXc = (mouseX).coerceIn(-45.0, 45.0) * mouseInputMult
val mouseXc = (mouseX).coerceIn(-45.0, 45.0) * mouseInputMult * rollDegreeDebuff
// When upside down apply a pitching force to nudge the player towards a dive. This reduces the 'weirdness'
// of being upside down
val rolledOverPitchCorrection = -8*sqrt(abs(min(cos(controller.roll.toRadians()),0.0f))) * (cos(controller.pitch.toRadians())).pow(2)
val mouseYc = ((mouseY).coerceIn(-45.0, 45.0) * mouseInputMult) + rolledOverPitchCorrection
val xInput = mouseXSmoother.getNewDeltaValue(mouseXc * 0.1, deltaTime * smoothingSpeed);
val yInput = mouseYSmoother.getNewDeltaValue(mouseYc * 0.1, deltaTime * smoothingSpeed);
val xInput = mouseXSmoother.getNewDeltaValue(mouseXc * 0.1, deltaTime * smoothingSpeed)
val yInput = mouseYSmoother.getNewDeltaValue(mouseYc * 0.1, deltaTime * smoothingSpeed)
val currSpeed = state.rideVelocity.get().length()
val currHorzSpeed = state.rideVelocity.get().horizontalDistance()
val hoverSpeed = 0.1
val pitchDamping = abs(cos(controller.pitch.toRadians())).pow(2) // Helper value used when wanting to discard the influence of another value at steep pitches
// Yaw locally when at or below hoverSpeed but not when pitched up or rolled over
val yawDampen = abs(cos(controller.pitch.toRadians())).pow(2) * abs(cos(controller.roll.toRadians())).pow(2)
val localYaw = if (currHorzSpeed < hoverSpeed) xInput * (1 - sqrt(RidingBehaviour.scaleToRange(currHorzSpeed, hoverSpeed, topSpeed))).coerceIn(0.0, 1.0)
else 0.0 //xInput * 0.4 * cos(controller.roll.toRadians()).coerceIn(0.0f, 1.0f) * yawDampen
val globalYaw = xInput * (1 - sqrt(RidingBehaviour.scaleToRange(currHorzSpeed, hoverSpeed, topSpeed))).coerceIn(0.0, 1.0)
controller.applyGlobalYaw(globalYaw.toFloat())
// Pitch locally up or down depending upon a number of factors:
// - Reduce pitch substantially when rolled and in a steep yaw. This prevents the player from ignoring a slow handling stat
// - Do not pitch at all when at or below hover speed
// - pitch faster at higher speeds
val p = 1.0 - vehicle.runtime.resolveDouble(settings.horizontalPitchExpr)
val p = 1.0 - vehicle.runtime.resolveDouble(settings.horizontalPitchExpr ?: globalBird.horizontalPitchExpr!!)
var localPitch = yInput * (1 - abs(sin(controller.roll.toRadians())).pow(2) * p * hypot(controller.forwardVector.x.toDouble(), controller.forwardVector.z.toDouble()).toFloat())
localPitch *= if (currSpeed > hoverSpeed || yInput > 0.0) RidingBehaviour.scaleToRange(currSpeed, 0.0, topSpeed).coerceIn(0.2, 1.0)
else 0.0
@@ -525,14 +473,18 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
// Apply a roll righting force when trying to pitch hard while rolled sideways. This nudges the player towards
// pitching globally
val pitchInfluencedRollCorrection = 0.0
val howRolledAmI = (( cos(controller.roll.toRadians()) - 1).coerceIn(-1.0f, 0.0f) * -2).coerceIn(0.0f, 0.7f) //Coerce into how much roll you want to let in in the deadzone
val inputInRolledDir = if (xInput.sign.toFloat() == controller.roll.sign) 1.0 else -cos(controller.roll.toRadians()).coerceIn(-1.0f, 0.0f).toDouble()
val rollDampen = (1 - howRolledAmI * abs(cos(controller.pitch.toRadians())).pow(2) * inputInRolledDir)
val howRolledAmI = (( cos(controller.roll.toRadians()) - 1).coerceIn(-1.0f, 0.0f) * -2) //Coerce into how much roll you want to let in in the deadzone
val isInputtingTowardsRoll = if ( xInput.sign == controller.roll.sign.toDouble()) 1.0 else 0.0
val inputInRolledDir = if (isInputtingTowardsRoll == 1.0) 1.0 else -cos(controller.roll.toRadians()).coerceIn(-1.0f, 0.0f).toDouble()
val rollDampen = (1 - howRolledAmI.coerceIn(0.0f, 0.7f) * pitchDamping * inputInRolledDir)
val rollForce = if (currHorzSpeed > hoverSpeed) xInput * sqrt(abs(RidingBehaviour.scaleToRange(currHorzSpeed, hoverSpeed, topSpeed)).coerceIn(0.0,1.0)) * rollDampen + pitchInfluencedRollCorrection
else 0.0
// When already rolled significantly part of the roll force should turn to local yaw force
val localYawForce = rollForce * abs(sin(controller.roll.toRadians())).pow(2) * pitchDamping * isInputtingTowardsRoll
//yaw, pitch, roll
return Vec3(localYaw, localPitch, rollForce)
return Vec3(localYawForce, localPitch, rollForce)
}
@@ -579,12 +531,12 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
vehicle: PokemonEntity,
driver: Player
): Float {
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr) / 20.0
val glideTopSpeed = topSpeed * vehicle.runtime.resolveDouble(settings.glidespeedExpr)
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr ?: globalBird.speedExpr!!) / 20.0
val currSpeed = state.rideVelocity.get().length()
// Must I ensure that topspeed is greater than minimum?
val normalizedGlideSpeed = RidingBehaviour.scaleToRange(currSpeed, topSpeed, glideTopSpeed)
val speedOverTopSpeed = max( 0.0, currSpeed - topSpeed)
val normalizedGlideSpeed = RidingBehaviour.scaleToRange(speedOverTopSpeed, 0.0, 0.3)
// 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
@@ -639,7 +591,7 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
state: BirdState,
vehicle: PokemonEntity
): Boolean {
return false
return true
}
override fun damageOnCollision(
@@ -668,57 +620,78 @@ class BirdSettings : RidingBehaviourSettings {
override val key = BirdBehaviour.KEY
override val stats = mutableMapOf<RidingStat, IntRange>()
var infiniteAltitude: Expression = "false".asExpression()
var infiniteAltitude: Expression? = null
private set
var infiniteStamina: Expression = "false".asExpression()
var infiniteStamina: Expression? = null
private set
var timeToRollCorrect: Expression = "0.5".asExpression()
var timeToRollCorrect: Expression? = null
private set
var handlingExpr: Expression = "q.get_ride_stats('SKILL', 'AIR', 45.0, 10.0)".asExpression()
var horizontalPitchExpr: Expression = "q.get_ride_stats('SKILL', 'AIR', 0.2, 0.1)".asExpression()
var mouseInputMult: Expression = "q.get_ride_stats('SKILL', 'AIR', 1.2, 0.8)".asExpression()
var speedExpr: Expression = "q.get_ride_stats('SPEED', 'AIR', 20.0, 4.0)".asExpression()
var handlingExpr: Expression? = null
var horizontalPitchExpr: Expression? = null
var mouseInputMult: Expression? = null
var speedExpr: Expression? = null
// Seconds from stationary to top speed
var accelerationExpr: Expression = "q.get_ride_stats('ACCELERATION', 'AIR', 2.0, 8.0)".asExpression()
var staminaExpr: Expression = "q.get_ride_stats('STAMINA', 'AIR', 15.0, 2.0)".asExpression()
var accelerationExpr: Expression? = null
var staminaExpr: Expression? = null
var glidespeedExpr: Expression = "q.get_ride_stats('JUMP', 'AIR', 2.0, 1.0)".asExpression()
var glideSpeedExpr: Expression? = null
private set
// debuff to stamina drain (increases stamina drain?) depending on how horizontal you're looking (so just drain more when climbing in altitude)
// So for the base 1.5: If looking straight up and holding forward you drain at 150 percent the rate you would if
// you were horizontal.
var stamDrainHorzMod: Expression? = null
private set
// long ass variable name
// Percent of stamina drain during glide (percent of normal drain rate) So base is 25 percent. (change to 0 if you want no drain or negative if you want regen)
var stamDrainPercRateGlide: Expression? = null
private set
// Percent of stamina drain as a factor of how hard you're diving. So if its zero then at full dive you drain no stam.
// This stacks with `stamDrainPercRateGlide`
// This is coerced between 1 and 0 or things would get weird.
var stamDrainPerRateDive: Expression? = null
private set
var rideSounds: RideSoundSettingsList = RideSoundSettingsList()
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeRidingStats(stats)
rideSounds.encode(buffer)
buffer.writeExpression(infiniteAltitude)
buffer.writeExpression(infiniteStamina)
buffer.writeExpression(glidespeedExpr)
buffer.writeExpression(handlingExpr)
buffer.writeExpression(horizontalPitchExpr)
buffer.writeExpression(mouseInputMult)
buffer.writeExpression(speedExpr)
buffer.writeExpression(accelerationExpr)
buffer.writeExpression(staminaExpr)
buffer.writeExpression(timeToRollCorrect)
buffer.writeNullableExpression(infiniteAltitude)
buffer.writeNullableExpression(infiniteStamina)
buffer.writeNullableExpression(glideSpeedExpr)
buffer.writeNullableExpression(handlingExpr)
buffer.writeNullableExpression(horizontalPitchExpr)
buffer.writeNullableExpression(mouseInputMult)
buffer.writeNullableExpression(speedExpr)
buffer.writeNullableExpression(accelerationExpr)
buffer.writeNullableExpression(staminaExpr)
buffer.writeNullableExpression(stamDrainHorzMod)
buffer.writeNullableExpression(stamDrainPercRateGlide)
buffer.writeNullableExpression(stamDrainPerRateDive)
buffer.writeNullableExpression(timeToRollCorrect)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
stats.putAll(buffer.readRidingStats())
rideSounds = RideSoundSettingsList.decode(buffer)
infiniteAltitude = buffer.readExpression()
infiniteStamina = buffer.readExpression()
glidespeedExpr = buffer.readExpression()
handlingExpr = buffer.readExpression()
horizontalPitchExpr = buffer.readExpression()
mouseInputMult = buffer.readExpression()
speedExpr = buffer.readExpression()
accelerationExpr = buffer.readExpression()
staminaExpr = buffer.readExpression()
timeToRollCorrect = buffer.readExpression()
infiniteAltitude = buffer.readNullableExpression()
infiniteStamina = buffer.readNullableExpression()
glideSpeedExpr = buffer.readNullableExpression()
handlingExpr = buffer.readNullableExpression()
horizontalPitchExpr = buffer.readNullableExpression()
mouseInputMult = buffer.readNullableExpression()
speedExpr = buffer.readNullableExpression()
accelerationExpr = buffer.readNullableExpression()
staminaExpr = buffer.readNullableExpression()
stamDrainHorzMod = buffer.readNullableExpression()
stamDrainPercRateGlide = buffer.readNullableExpression()
stamDrainPerRateDive = buffer.readNullableExpression()
timeToRollCorrect = buffer.readNullableExpression()
}
}
@@ -10,6 +10,7 @@ package com.cobblemon.mod.common.api.riding.behaviour.types.air
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonRideSettings
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourState
@@ -37,6 +38,9 @@ class GliderBehaviour : RidingBehaviour<GliderSettings, RidingBehaviourState> {
override val key = KEY
val globalGlider: GliderSettings
get() = CobblemonRideSettings.glider
override fun getRidingStyle(settings: GliderSettings, state: RidingBehaviourState): RidingStyle {
return RidingStyle.AIR
}
@@ -53,7 +57,7 @@ class GliderBehaviour : RidingBehaviour<GliderSettings, RidingBehaviourState> {
}
override fun speed(settings: GliderSettings, state: RidingBehaviourState, vehicle: PokemonEntity, driver: Player): Float {
return vehicle.runtime.resolveFloat(settings.speed)
return vehicle.runtime.resolveFloat(settings.speed ?: globalGlider.speed!!)
}
override fun rotation(
@@ -72,8 +76,8 @@ class GliderBehaviour : RidingBehaviour<GliderSettings, RidingBehaviourState> {
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 xVector = if (vehicle.runtime.resolveBoolean(settings.canStrafe ?: globalGlider.canStrafe!!)) driver.xxa.toDouble() else 0.0
val yVector = -vehicle.runtime.resolveDouble(settings.glideSpeed ?: globalGlider.glideSpeed!!)
val zVector = driver.zza.toDouble()
return Vec3(xVector, yVector, zVector)
}
@@ -195,31 +199,30 @@ class GliderSettings : RidingBehaviourSettings {
override val key = GliderBehaviour.KEY
override val stats = mutableMapOf<RidingStat, IntRange>()
var glideSpeed: Expression = "0.1".asExpression()
var glideSpeed: Expression? = null
private set
var speed: Expression = "1.0".asExpression()
var speed: Expression? = null
private set
var canStrafe: Expression = "false".asExpression()
var canStrafe: Expression? = null
private set
var rideSounds: RideSoundSettingsList = RideSoundSettingsList()
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeRidingStats(stats)
rideSounds.encode(buffer)
buffer.writeExpression(glideSpeed)
buffer.writeExpression(speed)
buffer.writeExpression(canStrafe)
buffer.writeNullableExpression(glideSpeed)
buffer.writeNullableExpression(speed)
buffer.writeNullableExpression(canStrafe)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
stats.putAll(buffer.readRidingStats())
rideSounds = RideSoundSettingsList.decode(buffer)
glideSpeed = buffer.readExpression()
speed = buffer.readExpression()
canStrafe = buffer.readExpression()
glideSpeed = buffer.readNullableExpression()
speed = buffer.readNullableExpression()
canStrafe = buffer.readNullableExpression()
}
}
@@ -10,6 +10,7 @@ package com.cobblemon.mod.common.api.riding.behaviour.types.air
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonRideSettings
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviour
@@ -21,7 +22,16 @@ import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
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 com.cobblemon.mod.common.util.blockPositionsAsListRounded
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.readNullableExpression
import com.cobblemon.mod.common.util.readRidingStats
import com.cobblemon.mod.common.util.resolveDouble
import com.cobblemon.mod.common.util.writeNullableExpression
import com.cobblemon.mod.common.util.writeRidingStats
import kotlin.math.abs
import kotlin.math.pow
import kotlin.math.sin
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.SmoothDouble
@@ -30,9 +40,6 @@ 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.pow
import kotlin.math.sin
class HelicopterBehaviour : RidingBehaviour<HelicopterSettings, RidingBehaviourState> {
companion object {
@@ -41,6 +48,8 @@ class HelicopterBehaviour : RidingBehaviour<HelicopterSettings, RidingBehaviourS
}
override val key = KEY
val globalHelicopter: HelicopterSettings
get() = CobblemonRideSettings.helicopter
override fun getRidingStyle(settings: HelicopterSettings, state: RidingBehaviourState): RidingStyle {
return RidingStyle.AIR
@@ -93,8 +102,8 @@ class HelicopterBehaviour : RidingBehaviour<HelicopterSettings, RidingBehaviourS
val runtime = vehicle.runtime
//If the player is not modulating height then hover
var yVel = if (driver.jumping) runtime.resolveDouble(settings.verticalVelocity)
else if (driver.isShiftKeyDown) -runtime.resolveDouble(settings.verticalVelocity)
var yVel = if (driver.jumping) runtime.resolveDouble(settings.verticalVelocity ?: globalHelicopter.verticalVelocity!!)
else if (driver.isShiftKeyDown) -runtime.resolveDouble(settings.verticalVelocity ?: globalHelicopter.verticalVelocity!!)
else (0.0)
yVel *= 0.25
@@ -290,36 +299,35 @@ class HelicopterSettings : RidingBehaviourSettings {
override val key = HelicopterBehaviour.KEY
override val stats = mutableMapOf<RidingStat, IntRange>()
var gravity: Expression = "1.0".asExpression()
var gravity: Expression? = null
private set
var horizontalAcceleration: Expression = "0.1".asExpression()
var horizontalAcceleration: Expression? = null
private set
var verticalVelocity: Expression = "1.0".asExpression()
var verticalVelocity: Expression? = null
private set
var speed: Expression = "1.0".asExpression()
var speed: Expression? = null
private set
var rideSounds: RideSoundSettingsList = RideSoundSettingsList()
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeRidingStats(stats)
rideSounds.encode(buffer)
buffer.writeExpression(gravity)
buffer.writeExpression(horizontalAcceleration)
buffer.writeExpression(verticalVelocity)
buffer.writeExpression(speed)
buffer.writeNullableExpression(gravity)
buffer.writeNullableExpression(horizontalAcceleration)
buffer.writeNullableExpression(verticalVelocity)
buffer.writeNullableExpression(speed)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
stats.putAll(buffer.readRidingStats())
rideSounds = RideSoundSettingsList.decode(buffer)
gravity = buffer.readExpression()
horizontalAcceleration = buffer.readExpression()
verticalVelocity = buffer.readExpression()
speed = buffer.readExpression()
gravity = buffer.readNullableExpression()
horizontalAcceleration = buffer.readNullableExpression()
verticalVelocity = buffer.readNullableExpression()
speed = buffer.readNullableExpression()
}
}
@@ -9,6 +9,7 @@
package com.cobblemon.mod.common.api.riding.behaviour.types.air
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.CobblemonRideSettings
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
@@ -41,6 +42,8 @@ class HoverBehaviour : RidingBehaviour<HoverSettings, HoverState> {
}
override val key = KEY
val globalHover: HoverSettings
get() = CobblemonRideSettings.hover
override fun getRidingStyle(settings: HoverSettings, state: HoverState): RidingStyle {
return RidingStyle.AIR
@@ -48,7 +51,7 @@ class HoverBehaviour : RidingBehaviour<HoverSettings, HoverState> {
val poseProvider = PoseProvider<HoverSettings, HoverState>(PoseType.STAND)
.with(PoseOption(PoseType.WALK) { _, state, _ ->
return@PoseOption abs(state.rideVelocity.get().z) > 0.2
return@PoseOption abs(state.rideVelocity.get().length()) > 0.01
})
override fun isActive(
@@ -105,7 +108,7 @@ class HoverBehaviour : RidingBehaviour<HoverSettings, HoverState> {
state: HoverState,
vehicle: PokemonEntity,
) {
val heightLimit = vehicle.runtime.resolveDouble(settings.jumpExpr)
val heightLimit = vehicle.runtime.resolveDouble(settings.jumpExpr ?: globalHover.jumpExpr!!)
val pos = vehicle.position()
val level = Minecraft.getInstance().player?.level() ?: return
val hit = level.clip(
@@ -126,9 +129,12 @@ class HoverBehaviour : RidingBehaviour<HoverSettings, HoverState> {
state: HoverState,
vehicle: PokemonEntity
) {
if (vehicle.runtime.resolveBoolean(settings.infiniteStamina ?: globalHover.infiniteStamina!!)) {
return
}
val stam = state.stamina.get()
val stamDrainRate = (1.0f / vehicle.runtime.resolveDouble(settings.staminaExpr)).toFloat() / 20.0f
val stamReplenishRate = (1.0f / vehicle.runtime.resolveDouble(settings.stamReplenishTimeSeconds) ).toFloat() / 20.0f // 2 seconds to replenish a full bar of stamina
val stamDrainRate = (1.0f / vehicle.runtime.resolveDouble(settings.staminaExpr ?: globalHover.staminaExpr!!)).toFloat() / 20.0f
val stamReplenishRate = (1.0f / vehicle.runtime.resolveDouble(settings.stamReplenishTimeSeconds ?: globalHover.stamReplenishTimeSeconds!!) ).toFloat() / 20.0f // 2 seconds to replenish a full bar of stamina
val newStam = if (state.tooHigh.get()) max(0.0f, stam - stamDrainRate) else min(1.0f,stam + stamReplenishRate)
state.stamina.set(newStam)
@@ -175,8 +181,8 @@ class HoverBehaviour : RidingBehaviour<HoverSettings, HoverState> {
vehicle: PokemonEntity,
driver: LivingEntity
): Float {
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr) / 20.0
val handling = vehicle.runtime.resolveDouble(settings.handlingExpr) / 20.0
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr ?: globalHover.speedExpr!!) / 20.0
val handling = vehicle.runtime.resolveDouble(settings.handlingExpr ?: globalHover.handlingExpr!!) / 20.0
val maxYawDiff = 90.0f
// Normalize the current rotation diff
@@ -211,8 +217,8 @@ class HoverBehaviour : RidingBehaviour<HoverSettings, HoverState> {
driver: Player
): Vec3 {
// Convert these to their tick values
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr) / 20.0
val accel = topSpeed / (vehicle.runtime.resolveDouble(settings.accelerationExpr) * 20.0)
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr ?: globalHover.speedExpr!!) / 20.0
val accel = topSpeed / (vehicle.runtime.resolveDouble(settings.accelerationExpr ?: globalHover.accelerationExpr!!) * 20.0)
var newVelocity = state.rideVelocity.get()
@@ -427,54 +433,61 @@ class HoverSettings : RidingBehaviourSettings {
override val key = HoverBehaviour.KEY
override val stats = mutableMapOf<RidingStat, IntRange>()
var canJump = "true".asExpression()
var infiniteStamina: Expression? = null
private set
var stamReplenishTimeSeconds = "2.0".asExpression()
var canJump: Expression? = null
private set
var stamReplenishTimeSeconds: Expression? = null
private set
// Speed in block per second
var speedExpr: Expression = "q.get_ride_stats('SPEED', 'AIR', 7.0, 1.0)".asExpression()
var speedExpr: Expression? = null
private set
// Acceleration in number of seconds to top speed
var accelerationExpr: Expression =
"q.get_ride_stats('ACCELERATION', 'AIR', 2.0, 8.0)".asExpression()
var accelerationExpr: Expression? = null
private set
// Amount of seconds between boosts
var staminaExpr: Expression = "q.get_ride_stats('STAMINA', 'AIR', 30.0, 2.0)".asExpression()
var staminaExpr: Expression? = null
private set
// Blocks before stamina drain
var jumpExpr: Expression = "q.get_ride_stats('JUMP', 'AIR', 16.0, 2.0)".asExpression()
var jumpExpr: Expression? = null
private set
// Turn rate in degrees per second when stationary
var handlingExpr: Expression = "q.get_ride_stats('SKILL', 'AIR', 270.0, 45.0)".asExpression()
var handlingExpr: Expression? = null
private set
var rideSounds: RideSoundSettingsList = RideSoundSettingsList()
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeRidingStats(stats)
rideSounds.encode(buffer)
buffer.writeExpression(speedExpr)
buffer.writeExpression(accelerationExpr)
buffer.writeExpression(staminaExpr)
buffer.writeExpression(jumpExpr)
buffer.writeExpression(handlingExpr)
buffer.writeNullableExpression(infiniteStamina)
buffer.writeNullableExpression(canJump)
buffer.writeNullableExpression(stamReplenishTimeSeconds)
buffer.writeNullableExpression(speedExpr)
buffer.writeNullableExpression(accelerationExpr)
buffer.writeNullableExpression(staminaExpr)
buffer.writeNullableExpression(jumpExpr)
buffer.writeNullableExpression(handlingExpr)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
stats.putAll(buffer.readRidingStats())
rideSounds = RideSoundSettingsList.decode(buffer)
speedExpr = buffer.readExpression()
accelerationExpr = buffer.readExpression()
staminaExpr = buffer.readExpression()
jumpExpr = buffer.readExpression()
handlingExpr = buffer.readExpression()
infiniteStamina = buffer.readNullableExpression()
canJump = buffer.readNullableExpression()
stamReplenishTimeSeconds = buffer.readNullableExpression()
speedExpr = buffer.readNullableExpression()
accelerationExpr = buffer.readNullableExpression()
staminaExpr = buffer.readNullableExpression()
jumpExpr = buffer.readNullableExpression()
handlingExpr = buffer.readNullableExpression()
}
}
@@ -11,23 +11,35 @@ package com.cobblemon.mod.common.api.riding.behaviour.types.air
import com.bedrockk.molang.Expression
import com.bedrockk.molang.runtime.MoLangMath.lerp
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonRideSettings
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.behaviour.types.land.HorseSettings
import com.cobblemon.mod.common.api.riding.behaviour.types.land.HorseState
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 com.cobblemon.mod.common.api.riding.behaviour.Side
import com.cobblemon.mod.common.api.riding.behaviour.ridingState
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 com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.config.CobblemonConfig
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 com.cobblemon.mod.common.util.math.geometry.toRadians
import com.cobblemon.mod.common.util.readNullableExpression
import com.cobblemon.mod.common.util.readRidingStats
import com.cobblemon.mod.common.util.resolveDouble
import com.cobblemon.mod.common.util.toVec3d
import com.cobblemon.mod.common.util.writeNullableExpression
import com.cobblemon.mod.common.util.writeRidingStats
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
import net.minecraft.client.Minecraft
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
@@ -36,7 +48,6 @@ import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
import net.minecraft.world.phys.shapes.Shapes
import org.joml.Vector3f
import kotlin.math.*
class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
companion object {
@@ -49,6 +60,9 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
return RidingStyle.AIR
}
val globalJet: JetSettings
get() = CobblemonRideSettings.jet
val poseProvider = PoseProvider<JetSettings, JetState>(PoseType.HOVER)
.with(PoseOption(PoseType.FLY) { _, state, _ -> state.rideVelocity.get().z > 0.1 })
@@ -113,7 +127,7 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
) {
val stam = state.stamina.get()
var newStam = stam
val stamDrainRate = (1.0f / vehicle.runtime.resolveDouble(settings.staminaExpr)).toFloat() / 20.0f
val stamDrainRate = (1.0f / vehicle.runtime.resolveDouble(settings.staminaExpr ?: globalJet.staminaExpr!!)).toFloat() / 20.0f
if (state.boosting.get()) {
newStam = max(0.0f,stam - stamDrainRate * 1.5f)
@@ -147,7 +161,7 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
driver: Player,
input: Vec3
): Vec3 {
val controller = (driver as? OrientationControllable)?.orientationController
val controller = (vehicle as? OrientationControllable)?.orientationController
if (controller == null || controller.orientation == null) return Vec3.ZERO
//Calculate ride space velocity
@@ -178,12 +192,12 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
vehicle: PokemonEntity,
driver: Player
) {
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr) / 20.0
val accel = topSpeed / (vehicle.runtime.resolveDouble(settings.accelerationExpr) * 20.0)
val deccel = vehicle.runtime.resolveDouble(settings.deccelRate)//0.005
val minSpeed = topSpeed * vehicle.runtime.resolveDouble(settings.minSpeedFactor)
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr ?: globalJet.speedExpr!!) / 20.0
val accel = topSpeed / (vehicle.runtime.resolveDouble(settings.accelerationExpr ?: globalJet.accelerationExpr!!) * 20.0)
val deccel = vehicle.runtime.resolveDouble(settings.deccelRate ?: globalJet.deccelRate!!)//0.005
val minSpeed = topSpeed * vehicle.runtime.resolveDouble(settings.minSpeedFactor ?: globalJet.minSpeedFactor!!)
val speed = state.rideVelocity.get().z
val boostMult = vehicle.runtime.resolveDouble(settings.jumpExpr)
val boostMult = vehicle.runtime.resolveDouble(settings.jumpExpr ?: globalJet.jumpExpr!!)
val boostTopSpeed = topSpeed * boostMult
val boostAccel = accel * boostMult
@@ -251,14 +265,12 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
driver: Player,
deltaTime: Double
): Vec3 {
//don't yaw this way if
if (Cobblemon.config.disableRoll) return Vec3.ZERO
//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)
val yawRotRate = vehicle.runtime.resolveDouble(settings.handlingYawExpr ?: globalJet.handlingYawExpr!!)
//Base the change off of deltatime.
var handlingYaw = yawRotRate * (cappedDeltaTime)
@@ -284,53 +296,8 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
sensitivity: Double,
deltaTime: Double
): Vec3 {
return when {
Cobblemon.config.disableRoll -> noRollRotation(
settings,
state,
vehicle,
driver,
mouseY,
mouseX,
mouseYSmoother,
mouseXSmoother,
sensitivity,
deltaTime
)
else -> rollRotation(
settings,
state,
vehicle,
driver,
mouseY,
mouseX,
mouseYSmoother,
mouseXSmoother,
sensitivity,
deltaTime
)
}
}
if (vehicle !is OrientationControllable) return Vec3.ZERO
fun noRollRotation(
settings: JetSettings,
state: JetState,
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
// Set roll to zero if transitioning to noroll config
controller.rotateRoll(controller.roll * -1.0f)
//Cap at a rate of 5fps so frame skips dont lead to huge jumps
val cappedDeltaTime = min(deltaTime, 0.2)
// Accumulate the mouse input
@@ -339,73 +306,11 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
//Get handling in degrees per second
val handlingDebuff = if(state.stamina.get() == 0.0f) 0.5 else 1.0
var handling = vehicle.runtime.resolveDouble(settings.handlingExpr) * handlingDebuff
//convert it to delta time
handling *= (cappedDeltaTime)
var pitchRot = handling * state.currMouseYForce.get()
// Yaw
val yawRot = handling * 0.5 * state.currMouseXForce.get()
controller.applyGlobalYaw(yawRot.toFloat())
if (abs(controller.pitch + pitchRot) >= 89.5 ) {
pitchRot = 0.0
state.currMouseYForce.set(0.0)
mouseYSmoother.reset()
} else {
controller.applyGlobalPitch(pitchRot.toFloat() * -1.0f)
}
// Have accumulated input begin decay when no input detected
if (abs(mouseX) == 0.0) {
// Have decay on roll be much stronger.
state.currMouseXForce.set(lerp(state.currMouseXForce.get(), 0.0, 0.02))
}
if (mouseY == 0.0) {
state.currMouseYForce.set(lerp(state.currMouseYForce.get(), 0.0, 0.005))
}
//yaw, pitch, roll
return Vec3.ZERO
}
fun rollRotation(
settings: JetSettings,
state: JetState,
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)
// Accumulate the mouse input
state.currMouseXForce.set((state.currMouseXForce.get() + (0.0015 * mouseX)).coerceIn(-1.0, 1.0))
state.currMouseYForce.set((state.currMouseYForce.get() + (0.0015 * mouseY)).coerceIn(-1.0, 1.0))
//Get handling in degrees per second
val handlingDebuff = if(state.stamina.get() == 0.0f) 0.5 else 1.0
var handling = vehicle.runtime.resolveDouble(settings.handlingExpr) * handlingDebuff
var handling = vehicle.runtime.resolveDouble(settings.handlingExpr ?: globalJet.handlingExpr!!) * handlingDebuff
//convert it to delta time
handling *= (cappedDeltaTime)
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
@@ -416,10 +321,10 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
// Have accumulated input begin decay when no input detected
if (abs(mouseX) == 0.0) {
// Have decay on roll be much stronger.
state.currMouseXForce.set(lerp(state.currMouseXForce.get(), 0.0, 0.02))
state.currMouseXForce.set(lerp(state.currMouseXForce.get(), 0.0, 0.08))
}
if (mouseY == 0.0) {
state.currMouseYForce.set(lerp(state.currMouseYForce.get(), 0.0, 0.005))
state.currMouseYForce.set(lerp(state.currMouseYForce.get(), 0.0, 0.02))
}
//yaw, pitch, roll
@@ -547,72 +452,70 @@ class JetSettings : RidingBehaviourSettings {
override val key = JetBehaviour.KEY
override val stats = mutableMapOf<RidingStat, IntRange>()
var gravity: Expression = "0".asExpression()
var gravity: Expression? = null
private set
var deccelRate: Expression = "0.005".asExpression()
var deccelRate: Expression? = null
private set
// Mult to top speed in order to derive minSpeed
var minSpeedFactor: Expression = "0.25".asExpression()
var minSpeedFactor: Expression? = null
private set
var handlingYawExpr: Expression = "q.get_ride_stats('SKILL', 'AIR', 50.0, 25.0)".asExpression()
var handlingYawExpr: Expression? = null
private set
// Make configurable by json
var infiniteStamina: Expression = "false".asExpression()
var infiniteStamina: Expression? = null
private set
// Boost power. Mult for top speed and accel while boosting
var jumpExpr: Expression = "q.get_ride_stats('JUMP', 'AIR', 2.0, 1.0)".asExpression()
var jumpExpr: Expression? = null
private set
// Turn rate in degrees per second
var handlingExpr: Expression = "q.get_ride_stats('SKILL', 'AIR', 60.0, 10.0)".asExpression()
var handlingExpr: Expression? = null
private set
// Top Speed in blocks per second
var speedExpr: Expression = "q.get_ride_stats('SPEED', 'AIR', 24.0, 4.0)".asExpression()
var speedExpr: Expression? = null
private set
// Seconds to get to top speed
var accelerationExpr: Expression =
"q.get_ride_stats('ACCELERATION', 'AIR', 2.0, 8.0)".asExpression()
var accelerationExpr: Expression? = null
private set
// Time in seconds to drain full bar of stamina flying
var staminaExpr: Expression = "q.get_ride_stats('STAMINA', 'AIR', 60.0, 4.0)".asExpression()
var staminaExpr: Expression? = null
private set
var rideSounds: RideSoundSettingsList = RideSoundSettingsList()
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeRidingStats(stats)
rideSounds.encode(buffer)
buffer.writeExpression(gravity)
buffer.writeExpression(deccelRate)
buffer.writeExpression(minSpeedFactor)
buffer.writeExpression(handlingYawExpr)
buffer.writeExpression(infiniteStamina)
buffer.writeExpression(jumpExpr)
buffer.writeExpression(handlingExpr)
buffer.writeExpression(speedExpr)
buffer.writeExpression(accelerationExpr)
buffer.writeExpression(staminaExpr)
buffer.writeNullableExpression(gravity)
buffer.writeNullableExpression(deccelRate)
buffer.writeNullableExpression(minSpeedFactor)
buffer.writeNullableExpression(handlingYawExpr)
buffer.writeNullableExpression(infiniteStamina)
buffer.writeNullableExpression(jumpExpr)
buffer.writeNullableExpression(handlingExpr)
buffer.writeNullableExpression(speedExpr)
buffer.writeNullableExpression(accelerationExpr)
buffer.writeNullableExpression(staminaExpr)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
stats.putAll(buffer.readRidingStats())
rideSounds = RideSoundSettingsList.decode(buffer)
gravity = buffer.readExpression()
deccelRate = buffer.readExpression()
minSpeedFactor = buffer.readExpression()
handlingYawExpr = buffer.readExpression()
infiniteStamina = buffer.readExpression()
jumpExpr = buffer.readExpression()
handlingExpr = buffer.readExpression()
speedExpr = buffer.readExpression()
accelerationExpr = buffer.readExpression()
staminaExpr = buffer.readExpression()
gravity = buffer.readNullableExpression()
deccelRate = buffer.readNullableExpression()
minSpeedFactor = buffer.readNullableExpression()
handlingYawExpr = buffer.readNullableExpression()
infiniteStamina = buffer.readNullableExpression()
jumpExpr = buffer.readNullableExpression()
handlingExpr = buffer.readNullableExpression()
speedExpr = buffer.readNullableExpression()
accelerationExpr = buffer.readNullableExpression()
staminaExpr = buffer.readNullableExpression()
}
}
@@ -10,6 +10,7 @@ package com.cobblemon.mod.common.api.riding.behaviour.types.air
import com.bedrockk.molang.Expression
import com.bedrockk.molang.runtime.MoLangMath.lerp
import com.cobblemon.mod.common.CobblemonRideSettings
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
@@ -40,6 +41,8 @@ class RocketBehaviour : RidingBehaviour<RocketSettings, RocketState> {
}
override val key = KEY
val globalRocket: RocketSettings
get() = CobblemonRideSettings.rocket
override fun getRidingStyle(settings: RocketSettings, state: RocketState): RidingStyle {
return RidingStyle.AIR
@@ -84,7 +87,7 @@ class RocketBehaviour : RidingBehaviour<RocketSettings, RocketState> {
driver: Player
): Float {
// Use this as a "tick" function and check to see if the driver is "boosting"
if(vehicle.level().isClientSide) {
if (vehicle.level().isClientSide) {
handleBoosting(settings, state, vehicle, driver)
tickStamina(settings, state, vehicle, driver)
}
@@ -124,10 +127,14 @@ class RocketBehaviour : RidingBehaviour<RocketSettings, RocketState> {
) {
val stam = state.stamina.get()
if (vehicle.runtime.resolveBoolean(settings.infiniteStamina ?: globalRocket.infiniteStamina!!)) {
return
}
// Grab the boost time in seconds and convert to ticks. Then calculate the drain rate as inversely
// proportional to the number of ticks of boost thus making a full boost take x ticks
// in short: "Stamina drains slower at higher values and also replenishes slower"
val boostTime = vehicle.runtime.resolveDouble(settings.staminaExpr) * 20.0f
val boostTime = vehicle.runtime.resolveDouble(settings.staminaExpr ?: globalRocket.staminaExpr!!) * 20.0f
val stamDrainRate = (1.0f / boostTime).toFloat()
val newStam = if(state.boosting.get() || driver.jumping) max(0.0f,stam - stamDrainRate) else stam
@@ -179,13 +186,13 @@ class RocketBehaviour : RidingBehaviour<RocketSettings, RocketState> {
var newMomentum = state.turnMomentum.get().toDouble()
// Degrees per tick
val maxTurnMomentum = vehicle.runtime.resolveDouble(settings.maxTurnRate) / 20.0f
val maxTurnMomentum = vehicle.runtime.resolveDouble(settings.maxTurnRate ?: globalRocket.maxTurnRate!!) / 20.0f
// Turn rate increase per tick. Based off number of seconds to get to max turn rate
val turningAcceleration = maxTurnMomentum / (vehicle.runtime.resolveDouble(settings.handlingExpr) * 20.0f)
val turningAcceleration = maxTurnMomentum / (vehicle.runtime.resolveDouble(settings.handlingExpr ?: globalRocket.handlingExpr!!) * 20.0f)
val turnInput = (driver.xxa *-1.0f) * turningAcceleration
// Base boost stats off of normal turning stats
val boostHandlingMod = vehicle.runtime.resolveDouble(settings.boostHandlingMod)
val boostHandlingMod = vehicle.runtime.resolveDouble(settings.boostHandlingMod ?: globalRocket.boostHandlingMod!!)
val boostMaxTurnMomentum = maxTurnMomentum * boostHandlingMod
val boostTurnInput = turnInput * boostHandlingMod
@@ -230,9 +237,9 @@ class RocketBehaviour : RidingBehaviour<RocketSettings, RocketState> {
vehicle: PokemonEntity,
driver: Player
): Vec3 {
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
val accel = vehicle.runtime.resolveDouble(settings.accelerationExpr)
val jump = vehicle.runtime.resolveDouble(settings.jumpExpr)
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr ?: globalRocket.speedExpr!!)
val accel = vehicle.runtime.resolveDouble(settings.accelerationExpr ?: globalRocket.accelerationExpr!!)
val jump = vehicle.runtime.resolveDouble(settings.jumpExpr ?: globalRocket.jumpExpr!!)
var newVelocity = vehicle.deltaMovement
@@ -256,7 +263,7 @@ class RocketBehaviour : RidingBehaviour<RocketSettings, RocketState> {
(newVelocity.z + (accel * forwardInput.toDouble())))
} else if ((state.boosting.get())) {
val boostMod = vehicle.runtime.resolveDouble(settings.boostSpeedMod)
val boostMod = vehicle.runtime.resolveDouble(settings.boostSpeedMod ?: globalRocket.boostSpeedMod!!)
val boostSpeed = topSpeed * boostMod
val boostAccel = if(newVelocity.length() < boostSpeed) accel * (boostMod/1.5) else 0.0
val forwardInput = 1.0f
@@ -398,8 +405,8 @@ class RocketBehaviour : RidingBehaviour<RocketSettings, RocketState> {
driver: Player
): Float {
if (state.boosting.get()) {
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
val boostMod = vehicle.runtime.resolveDouble(settings.boostSpeedMod)
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr ?: globalRocket.speedExpr!!)
val boostMod = vehicle.runtime.resolveDouble(settings.boostSpeedMod ?: globalRocket.boostSpeedMod!!)
val normalizedBoostSpeed = RidingBehaviour.scaleToRange(state.rideVelocity.get().length(), topSpeed, topSpeed * boostMod)
return 1.0f + normalizedBoostSpeed.pow(2).toFloat() * 0.2f
} else {
@@ -499,60 +506,67 @@ class RocketSettings : RidingBehaviourSettings {
override val key = RocketBehaviour.KEY
override val stats = mutableMapOf<RidingStat, IntRange>()
var infiniteStamina: Expression? = null
private set
// Boost multiplier for speed
var boostSpeedMod: Expression = "5.0".asExpression()
var boostSpeedMod: Expression? = null
private set
// Boost multiplier for handling
var boostHandlingMod: Expression = "0.3".asExpression()
var boostHandlingMod: Expression? = null
private set
// Max turn rate in degrees per second
var maxTurnRate: Expression = "100.0".asExpression()
var maxTurnRate: Expression? = null
private set
var speedExpr: Expression = "q.get_ride_stats('SPEED', 'AIR', 1.2, 0.6)".asExpression()
var speedExpr: Expression? = null
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 accelerationExpr: Expression =
"q.get_ride_stats('ACCELERATION', 'AIR', (1.0 / (20.0 * 1.5)), (1.0 / (20.0 * 3.0))) * 0.5".asExpression()
var accelerationExpr: Expression? = null
private set
// air time in seconds
var staminaExpr: Expression = "q.get_ride_stats('STAMINA', 'AIR', 20.0, 10.0)".asExpression()
var staminaExpr: Expression? = null
private set
var jumpExpr: Expression = "q.get_ride_stats('JUMP', 'AIR', 2.0, 1.0)".asExpression()
var jumpExpr: Expression? = null
private set
// How long it takes to get to max turn rate from rest (in seconds)
var handlingExpr: Expression = "q.get_ride_stats('SKILL', 'AIR', 0.5, 3.0)".asExpression()
var handlingExpr: Expression? = null
private set
var rideSounds: RideSoundSettingsList = RideSoundSettingsList()
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeRidingStats(stats)
rideSounds.encode(buffer)
buffer.writeExpression(speedExpr)
buffer.writeExpression(accelerationExpr)
buffer.writeExpression(staminaExpr)
buffer.writeExpression(jumpExpr)
buffer.writeExpression(handlingExpr)
buffer.writeNullableExpression(infiniteStamina)
buffer.writeNullableExpression(boostSpeedMod)
buffer.writeNullableExpression(boostHandlingMod)
buffer.writeNullableExpression(maxTurnRate)
buffer.writeNullableExpression(speedExpr)
buffer.writeNullableExpression(accelerationExpr)
buffer.writeNullableExpression(staminaExpr)
buffer.writeNullableExpression(jumpExpr)
buffer.writeNullableExpression(handlingExpr)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
stats.putAll(buffer.readRidingStats())
rideSounds = RideSoundSettingsList.decode(buffer)
speedExpr = buffer.readExpression()
accelerationExpr = buffer.readExpression()
staminaExpr = buffer.readExpression()
jumpExpr = buffer.readExpression()
handlingExpr = buffer.readExpression()
infiniteStamina = buffer.readNullableExpression()
boostSpeedMod = buffer.readNullableExpression()
boostHandlingMod = buffer.readNullableExpression()
maxTurnRate = buffer.readNullableExpression()
speedExpr = buffer.readNullableExpression()
accelerationExpr = buffer.readNullableExpression()
staminaExpr = buffer.readNullableExpression()
jumpExpr = buffer.readNullableExpression()
handlingExpr = buffer.readNullableExpression()
}
}
class RocketState : RidingBehaviourState() {
@@ -387,9 +387,10 @@ open class CompositeSettings : RidingBehaviourSettings {
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeRidingStats(stats)
buffer.writeResourceLocation(key)
buffer.writeResourceLocation(transitionStrategy)
buffer.writeResourceLocation(defaultBehaviour.key)
defaultBehaviour.encode(buffer)
buffer.writeResourceLocation(alternateBehaviour.key)
alternateBehaviour.encode(buffer)
}
@@ -41,6 +41,10 @@ interface CompositeRidingStrategy<T : CompositeSettings> {
toState: RidingBehaviourState,
toSettings: RidingBehaviourSettings
) {
if (vehicle.level().isClientSide) {
val fromSettings = if (settings.defaultBehaviour == toSettings) settings.alternateBehaviour else settings.defaultBehaviour
}
toState.stamina.set(fromState.stamina.get())
toState.rideVelocity.set(fromState.rideVelocity.get())
fromState.reset()
@@ -9,16 +9,35 @@
package com.cobblemon.mod.common.api.riding.behaviour.types.land
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.CobblemonRideSettings
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.*
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 com.cobblemon.mod.common.api.riding.behaviour.Side
import com.cobblemon.mod.common.api.riding.behaviour.ridingState
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.sound.RideSoundSettingsList
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 com.cobblemon.mod.common.util.math.geometry.toRadians
import com.cobblemon.mod.common.util.blockPositionsAsListRounded
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.readNullableExpression
import com.cobblemon.mod.common.util.readRidingStats
import com.cobblemon.mod.common.util.resolveBoolean
import com.cobblemon.mod.common.util.resolveDouble
import com.cobblemon.mod.common.util.resolveFloat
import com.cobblemon.mod.common.util.writeNullableExpression
import com.cobblemon.mod.common.util.writeRidingStats
import net.minecraft.client.Minecraft
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
import kotlin.math.pow
import kotlin.math.sign
import kotlin.math.sqrt
import net.minecraft.core.Direction
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.RegistryFriendlyByteBuf
@@ -31,8 +50,6 @@ 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 org.joml.Matrix3f
import kotlin.math.*
class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
companion object {
@@ -40,6 +57,8 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
}
override val key = KEY
val globalHorse: HorseSettings
get() = CobblemonRideSettings.horse
override fun getRidingStyle(settings: HorseSettings, state: HorseState): RidingStyle {
return RidingStyle.LAND
@@ -47,7 +66,7 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
val poseProvider = PoseProvider<HorseSettings, HorseState>(PoseType.STAND)
.with(PoseOption(PoseType.WALK) { _, state, vehicle ->
return@PoseOption abs(state.rideVelocity.get().horizontalDistance()) > 0.0
return@PoseOption state.walking.get()
})
override fun isActive(
@@ -93,9 +112,12 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
driver: Player,
input: Vec3
) {
handleSprinting(state, driver)
inAirCheck(state, vehicle)
tickStamina(settings, state, vehicle)
if (vehicle.level().isClientSide) {
handleSprinting(state)
inAirCheck(state, vehicle)
tickStamina(settings, state, vehicle)
state.walking.set(state.rideVelocity.get().horizontalDistance() > 0.01)
}
}
fun inAirCheck(
@@ -116,15 +138,22 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
}
fun handleSprinting(
state: HorseState,
driver: Player
state: HorseState
) {
if (state.sprinting.get()) {
state.sprinting.set(driver.isSprinting && state.stamina.get() > 0.0f)
} else {
// Only allow sprinting to start if over x percentage of stamina left
val stamSprintPerc = 25.0f
state.sprinting.set(driver.isSprinting && state.stamina.get() > stamSprintPerc / 100.0f)
val tryingToSprint = Minecraft.getInstance().options.keySprint.isDown() && Minecraft.getInstance().options.keyUp.isDown()
if (state.stamina.get() <= 0.0f || !Minecraft.getInstance().options.keyUp.isDown()) {
// If stamina runs out or the player is not holding forward then stop sprinting
state.sprinting.set(false)
if (state.stamina.get() <= 0.0f) {
state.sprintToggleable.set(false)
}
} else if (!state.sprinting.get() && !state.sprintToggleable.get() && state.stamina.get() > 0.33f) {
// If you are not sprinting and sprint is not toggleable and you're not trying to sprint and stamina is above 0.3 then enable the toggle
state.sprintToggleable.set(true)
} else if (tryingToSprint && state.sprintToggleable.get()) {
// If you are trying to sprint and the toggle allows it then start sprinting and disable toggle
state.sprinting.set(true)
}
}
@@ -134,8 +163,13 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
vehicle: PokemonEntity,
) {
val stam = state.stamina.get()
if (vehicle.runtime.resolveBoolean(settings.infiniteStamina ?: globalHorse.infiniteStamina!!)) {
return
}
var newStam = stam
val stamDrainRate = (1.0f / vehicle.runtime.resolveDouble(settings.staminaExpr)).toFloat() / 20.0f
val stamDrainRate = (1.0f / vehicle.runtime.resolveDouble(settings.staminaExpr ?: globalHorse.staminaExpr!!)).toFloat() / 20.0f
if (state.sprinting.get()) {
newStam = max(0.0f,stam - stamDrainRate)
@@ -185,10 +219,10 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
vehicle: PokemonEntity,
driver: LivingEntity
): Float {
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
val handling = vehicle.runtime.resolveDouble(settings.handlingExpr)
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr ?: globalHorse.speedExpr!!)
val handling = vehicle.runtime.resolveDouble(settings.handlingExpr ?: globalHorse.handlingExpr!!)
val walkHandlingBoost = 5
val maxYawDiff = vehicle.runtime.resolveFloat(settings.lookYawLimit)
val maxYawDiff = vehicle.runtime.resolveFloat(settings.lookYawLimit ?: globalHorse.lookYawLimit!!)
// Normalize the current rotation diff
val rotDiff = Mth.wrapDegrees(driver.yRot - vehicle.yRot).coerceIn(-maxYawDiff,maxYawDiff)
@@ -232,13 +266,13 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
vehicle: PokemonEntity,
driver: Player
): Vec3 {
val canSprint = vehicle.runtime.resolveBoolean(settings.canSprint)
val canJump = vehicle.runtime.resolveBoolean(settings.canJump)
val jumpForce = vehicle.runtime.resolveDouble(settings.jumpExpr) * 0.75
val rideTopSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
val canSprint = vehicle.runtime.resolveBoolean(settings.canSprint ?: globalHorse.canSprint!!)
val canJump = vehicle.runtime.resolveBoolean(settings.canJump ?: globalHorse.canJump!!)
val jumpForce = vehicle.runtime.resolveDouble(settings.jumpExpr ?: globalHorse.jumpExpr!!) * 0.75
val rideTopSpeed = vehicle.runtime.resolveDouble(settings.speedExpr ?: globalHorse.speedExpr!!)
val walkSpeed = getWalkSpeed(vehicle)
val topSpeed = if(canSprint && state.sprinting.get()) rideTopSpeed else walkSpeed
val accel = topSpeed / (vehicle.runtime.resolveDouble(settings.accelerationExpr) * 20.0)
val accel = topSpeed / (vehicle.runtime.resolveDouble(settings.accelerationExpr ?: globalHorse.accelerationExpr!!) * 20.0)
var activeInput = false
/******************************************************
@@ -271,7 +305,7 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
// If on a tight turn then do not speed up past half of top speed in order to turn quicker
// Also determine how fast to be slowing down based on how far turned you are
val lookYawLimit = vehicle.runtime.resolveFloat(settings.lookYawLimit)
val lookYawLimit = vehicle.runtime.resolveFloat(settings.lookYawLimit ?: globalHorse.lookYawLimit!!)
val percOfMaxTurnSpeed = abs(Mth.wrapDegrees(driver.yRot - vehicle.yRot) / lookYawLimit) * 100.0f
val turnPercThresh = 0.0f
val s = min(((percOfMaxTurnSpeed - turnPercThresh) / (100.0f - turnPercThresh)).pow(1),1.0f)
@@ -513,70 +547,82 @@ class HorseSettings : RidingBehaviourSettings {
override val key = HorseBehaviour.KEY
override val stats = mutableMapOf<RidingStat, IntRange>()
var canJump = "true".asExpression()
var infiniteStamina: Expression? = null
private set
var canJump: Expression? = null
private set
var canSprint = "true".asExpression()
var canSprint: Expression? = null
private set
var lookYawLimit = "90.0f".asExpression()
var lookYawLimit: Expression? = null
private set
var speedExpr: Expression = "q.get_ride_stats('SPEED', 'LAND', 1.2, 0.1)".asExpression()
var speedExpr: Expression? = null
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 accelerationExpr: Expression =
"q.get_ride_stats('ACCELERATION', 'LAND', 0.1, 12.0)".asExpression()
var accelerationExpr: Expression? = null
private set
// Between 30 seconds and 10 seconds at the lowest when at full speed.
var staminaExpr: Expression = "q.get_ride_stats('STAMINA', 'LAND', 60.0, 4.0)".asExpression()
var staminaExpr: Expression? = null
private set
//Between a one block jump and a six block jump
var jumpExpr: Expression = "q.get_ride_stats('JUMP', 'LAND', 1.6, 0.2)".asExpression()
var jumpExpr: Expression? = null
private set
var handlingExpr: Expression = "q.get_ride_stats('SKILL', 'LAND', 90.0, 30.0)".asExpression()
var handlingExpr: Expression? = null
private set
var rideSounds: RideSoundSettingsList = RideSoundSettingsList()
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeRidingStats(stats)
rideSounds.encode(buffer)
buffer.writeExpression(speedExpr)
buffer.writeExpression(accelerationExpr)
buffer.writeExpression(staminaExpr)
buffer.writeExpression(jumpExpr)
buffer.writeExpression(handlingExpr)
buffer.writeExpression(canJump)
buffer.writeExpression(canSprint)
buffer.writeNullableExpression(infiniteStamina)
buffer.writeNullableExpression(canJump)
buffer.writeNullableExpression(canSprint)
buffer.writeNullableExpression(lookYawLimit)
buffer.writeNullableExpression(speedExpr)
buffer.writeNullableExpression(accelerationExpr)
buffer.writeNullableExpression(staminaExpr)
buffer.writeNullableExpression(jumpExpr)
buffer.writeNullableExpression(handlingExpr)
buffer.writeNullableExpression(canJump)
buffer.writeNullableExpression(canSprint)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
stats.putAll(buffer.readRidingStats())
rideSounds = RideSoundSettingsList.decode(buffer)
speedExpr = buffer.readExpression()
accelerationExpr = buffer.readExpression()
staminaExpr = buffer.readExpression()
jumpExpr = buffer.readExpression()
handlingExpr = buffer.readExpression()
canJump = buffer.readExpression()
canSprint = buffer.readExpression()
infiniteStamina = buffer.readNullableExpression()
canJump = buffer.readNullableExpression()
canSprint = buffer.readNullableExpression()
lookYawLimit = buffer.readNullableExpression()
speedExpr = buffer.readNullableExpression()
accelerationExpr = buffer.readNullableExpression()
staminaExpr = buffer.readNullableExpression()
jumpExpr = buffer.readNullableExpression()
handlingExpr = buffer.readNullableExpression()
canJump = buffer.readNullableExpression()
canSprint = buffer.readNullableExpression()
}
}
class HorseState : RidingBehaviourState() {
var sprinting = ridingState(false, Side.CLIENT)
var walking = ridingState(false, Side.BOTH)
var sprintToggleable = ridingState(false, Side.CLIENT)
var inAir = ridingState(false, Side.CLIENT)
var jumpTicks = ridingState(0, Side.CLIENT)
override fun encode(buffer: FriendlyByteBuf) {
super.encode(buffer)
buffer.writeBoolean(sprinting.get())
buffer.writeBoolean(walking.get())
buffer.writeBoolean(sprintToggleable.get())
buffer.writeBoolean(inAir.get())
buffer.writeInt(jumpTicks.get())
}
@@ -584,6 +630,8 @@ class HorseState : RidingBehaviourState() {
override fun decode(buffer: FriendlyByteBuf) {
super.decode(buffer)
sprinting.set(buffer.readBoolean(), forced = true)
walking.set(buffer.readBoolean(), forced = true)
sprintToggleable.set(buffer.readBoolean(), forced = true)
inAir.set(buffer.readBoolean(), forced = true)
jumpTicks.set(buffer.readInt(), forced = true)
}
@@ -591,6 +639,8 @@ class HorseState : RidingBehaviourState() {
override fun reset() {
super.reset()
sprinting.set(false, forced = true)
walking.set(false, forced = true)
sprintToggleable.set(false, forced = true)
inAir.set(false, forced = true)
jumpTicks.set(0, forced = true)
}
@@ -599,6 +649,8 @@ class HorseState : RidingBehaviourState() {
it.rideVelocity.set(this.rideVelocity.get(), forced = true)
it.stamina.set(this.stamina.get(), forced = true)
it.sprinting.set(this.sprinting.get(), forced = true)
it.walking.set(this.sprinting.get(), forced = true)
it.sprintToggleable.set(this.sprintToggleable.get(), forced = true)
it.inAir.set(this.inAir.get(), forced = true)
it.jumpTicks.set(this.jumpTicks.get(), forced = true)
}
@@ -606,6 +658,8 @@ class HorseState : RidingBehaviourState() {
override fun shouldSync(previous: RidingBehaviourState): Boolean {
if (previous !is HorseState) return false
if (previous.sprinting.get() != sprinting.get()) return true
if (previous.walking.get() != walking.get()) return true
if (previous.sprintToggleable.get() != sprintToggleable.get()) return true
if (previous.inAir.get() != inAir.get()) return true
return super.shouldSync(previous)
}
@@ -10,18 +10,36 @@ package com.cobblemon.mod.common.api.riding.behaviour.types.land
import com.bedrockk.molang.Expression
import com.bedrockk.molang.runtime.MoLangMath.lerp
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonRideSettings
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.behaviour.RidingBehaviour
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.Side
import com.cobblemon.mod.common.api.riding.behaviour.SidedRidingState
import com.cobblemon.mod.common.api.riding.behaviour.ridingState
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.sound.RideSoundSettingsList
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 com.cobblemon.mod.common.util.blockPositionsAsListRounded
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.math.geometry.toRadians
import com.cobblemon.mod.common.util.readNullableExpression
import com.cobblemon.mod.common.util.readRidingStats
import com.cobblemon.mod.common.util.resolveBoolean
import com.cobblemon.mod.common.util.resolveDouble
import com.cobblemon.mod.common.util.resolveFloat
import com.cobblemon.mod.common.util.writeNullableExpression
import com.cobblemon.mod.common.util.writeRidingStats
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
import kotlin.math.pow
import kotlin.math.sign
import net.minecraft.core.Direction
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.RegistryFriendlyByteBuf
@@ -32,12 +50,6 @@ 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 org.joml.Matrix3f
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
import kotlin.math.pow
import kotlin.math.sign
class MinekartBehaviour : RidingBehaviour<MinekartSettings, MinekartState> {
companion object {
@@ -45,6 +57,8 @@ class MinekartBehaviour : RidingBehaviour<MinekartSettings, MinekartState> {
}
override val key = KEY
val globalMinekart: MinekartSettings
get() = CobblemonRideSettings.minekart
override fun getRidingStyle(settings: MinekartSettings, state: MinekartState): RidingStyle {
return RidingStyle.LAND
@@ -84,7 +98,7 @@ class MinekartBehaviour : RidingBehaviour<MinekartSettings, MinekartState> {
driver: Player
): Float {
val boostMax = vehicle.runtime.resolveFloat(settings.boostLimit)
val boostMax = vehicle.runtime.resolveFloat(settings.boostLimit ?: globalMinekart.boostLimit!!)
//TODO: should this be by how much turn has accumulated or simply time turning... or both?
val boostPerSecond = 20.0f
val boostLoss = 40.0f
@@ -142,8 +156,8 @@ class MinekartBehaviour : RidingBehaviour<MinekartSettings, MinekartState> {
var newMomentum = state.turnMomentum.get().toDouble()
//TODO: maybe tie this to max turnspeeds rather than the turn acceleration
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
val turningAcceleration = (vehicle.runtime.resolveDouble(settings.handlingExpr) * 1.5 / 20.0f) / 20.0f * 4.0
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr ?: globalMinekart.speedExpr!!)
val turningAcceleration = (vehicle.runtime.resolveDouble(settings.handlingExpr ?: globalMinekart.handlingExpr!!) * 1.5 / 20.0f) / 20.0f * 4.0
val turnInput = (driver.xxa *-1.0f) * turningAcceleration
val driftInput = turnInput * 0.25f
@@ -202,9 +216,9 @@ class MinekartBehaviour : RidingBehaviour<MinekartSettings, MinekartState> {
): Vec3 {
// Check to see if the ride should be walking or sprinting
val rideTopSpeed = vehicle.runtime.resolveDouble(settings.speedExpr) * 0.6 * 2.0
val rideTopSpeed = vehicle.runtime.resolveDouble(settings.speedExpr ?: globalMinekart.speedExpr!!) * 0.6 * 2.0
val topSpeed = if (state.drifting.get()) rideTopSpeed * 0.7 else rideTopSpeed
val accel = vehicle.runtime.resolveDouble(settings.accelerationExpr) * 0.3 * 3 * 2.0
val accel = vehicle.runtime.resolveDouble(settings.accelerationExpr ?: globalMinekart.accelerationExpr!!) * 0.3 * 3 * 2.0
val driftAngle = 20.0f
//Align the direction of this vector with the vehicles current heading.
@@ -327,7 +341,7 @@ class MinekartBehaviour : RidingBehaviour<MinekartSettings, MinekartState> {
vehicle: PokemonEntity,
driver: Player
): Boolean {
return vehicle.runtime.resolveBoolean(settings.canJump)
return vehicle.runtime.resolveBoolean(settings.canJump ?: globalMinekart.canJump!!)
}
override fun setRideBar(
@@ -447,45 +461,57 @@ class MinekartSettings : RidingBehaviourSettings {
override val key = MinekartBehaviour.KEY
override val stats = mutableMapOf<RidingStat, IntRange>()
var canJump = "true".asExpression()
var infiniteStamina: Expression? = null
private set
var canJump: Expression? = null
private set
var boostLimit = "100.0f".asExpression()
var boostLimit: Expression? = null
private set
var speedExpr: Expression = "q.get_ride_stats('SPEED', 'LAND', 1.0, 0.3)".asExpression()
var speedExpr: Expression? = null
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 accelerationExpr: Expression =
"q.get_ride_stats('ACCELERATION', 'LAND', (1.0 / (20.0 * 1.5)), (1.0 / (20.0 * 5.0)))".asExpression()
var accelerationExpr: Expression? = null
private set
var staminaExpr: Expression = "q.get_ride_stats('STAMINA', 'LAND', 30.0, 10.0)".asExpression()
var staminaExpr: Expression? = null
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()
var jumpExpr: Expression? = null
private set
var handlingExpr: Expression = "q.get_ride_stats('SKILL', 'LAND', 140.0, 20.0)".asExpression()
var handlingExpr: Expression? = null
private set
var rideSounds: RideSoundSettingsList = RideSoundSettingsList()
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeRidingStats(stats)
rideSounds.encode(buffer)
buffer.writeExpression(canJump)
buffer.writeExpression(boostLimit)
buffer.writeNullableExpression(infiniteStamina)
buffer.writeNullableExpression(canJump)
buffer.writeNullableExpression(boostLimit)
buffer.writeNullableExpression(speedExpr)
buffer.writeNullableExpression(accelerationExpr)
buffer.writeNullableExpression(staminaExpr)
buffer.writeNullableExpression(jumpExpr)
buffer.writeNullableExpression(handlingExpr)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
stats.putAll(buffer.readRidingStats())
rideSounds = RideSoundSettingsList.decode(buffer)
canJump = buffer.readExpression()
boostLimit = buffer.readExpression()
infiniteStamina = buffer.readNullableExpression()
canJump = buffer.readNullableExpression()
boostLimit = buffer.readNullableExpression()
speedExpr = buffer.readNullableExpression()
accelerationExpr = buffer.readNullableExpression()
staminaExpr = buffer.readNullableExpression()
jumpExpr = buffer.readNullableExpression()
handlingExpr = buffer.readNullableExpression()
}
}
@@ -10,18 +10,35 @@ package com.cobblemon.mod.common.api.riding.behaviour.types.land
import com.bedrockk.molang.Expression
import com.bedrockk.molang.runtime.MoLangMath.lerp
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonRideSettings
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.behaviour.RidingBehaviour
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.Side
import com.cobblemon.mod.common.api.riding.behaviour.SidedRidingState
import com.cobblemon.mod.common.api.riding.behaviour.ridingState
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.sound.RideSoundSettingsList
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 com.cobblemon.mod.common.util.blockPositionsAsListRounded
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.math.geometry.toRadians
import com.cobblemon.mod.common.util.readNullableExpression
import com.cobblemon.mod.common.util.readRidingStats
import com.cobblemon.mod.common.util.resolveBoolean
import com.cobblemon.mod.common.util.resolveDouble
import com.cobblemon.mod.common.util.toVec3d
import com.cobblemon.mod.common.util.writeNullableExpression
import com.cobblemon.mod.common.util.writeRidingStats
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
import kotlin.math.sign
import net.minecraft.core.Direction
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.RegistryFriendlyByteBuf
@@ -33,10 +50,6 @@ import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
import net.minecraft.world.phys.shapes.Shapes
import org.joml.Matrix3f
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
import kotlin.math.sign
class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
companion object {
@@ -44,6 +57,8 @@ class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
}
override val key = KEY
val globalVehicle: VehicleSettings
get() = CobblemonRideSettings.vehicle
override fun getRidingStyle(settings: VehicleSettings, state: VehicleState): RidingStyle {
return RidingStyle.LAND
@@ -126,7 +141,7 @@ class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
var newMomentum = state.turnMomentum.get().toDouble()
// Grab turningAcceleration and divide 20 twice to get
val turningAcceleration = (vehicle.runtime.resolveDouble(settings.handlingExpr) * 1.5 / 20.0f) / 20.0f * 4.0
val turningAcceleration = (vehicle.runtime.resolveDouble(settings.handlingExpr ?: globalVehicle.handlingExpr!!) * 1.5 / 20.0f) / 20.0f * 4.0
val turnInput = (driver.xxa *-1.0f) * turningAcceleration
val maxTurnMomentum = 80.0f / 20.0f
@@ -137,7 +152,7 @@ class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
if(state.drifting.get() || state.inAir.get()) {
if( newMomentum < driftMaxTurnMomentum) {
driftTurnInput = driftTurnInput * (vehicle.deltaMovement.horizontalDistance() / vehicle.runtime.resolveDouble(settings.speedExpr))
driftTurnInput = driftTurnInput * (vehicle.deltaMovement.horizontalDistance() / vehicle.runtime.resolveDouble(settings.speedExpr ?: globalVehicle.speedExpr!!))
newMomentum += driftTurnInput
}
newMomentum = lerp(newMomentum, 0.0, 0.03)
@@ -177,10 +192,10 @@ class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
// Check to see if the ride should be walking or sprinting
//val walkSpeed = getWalkSpeed(vehicle)
val rideTopSpeed = vehicle.runtime.resolveDouble(settings.speedExpr) * 0.6 * 2.0
val rideTopSpeed = vehicle.runtime.resolveDouble(settings.speedExpr ?: globalVehicle.speedExpr!!) * 0.6 * 2.0
val topSpeed = if (state.drifting.get()) rideTopSpeed * 0.5 else rideTopSpeed
val accel = vehicle.runtime.resolveDouble(settings.accelerationExpr) * 0.3 * 3 * 2.0
val accel = vehicle.runtime.resolveDouble(settings.accelerationExpr ?: globalVehicle.accelerationExpr!!) * 0.3 * 3 * 2.0
//Flag for determining if player is actively inputting
var activeInput = false
@@ -250,7 +265,7 @@ class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
}
//TODO: Change this so its tied to a jumping stat and representative of the amount of jumps
val canJump = vehicle.runtime.resolveBoolean(settings.canJump)
val canJump = vehicle.runtime.resolveBoolean(settings.canJump ?: globalVehicle.canJump!!)
//Jump the thang!
if (driver.jumping && vehicle.onGround() && canJump) {
val jumpForce = 0.5
@@ -296,7 +311,7 @@ class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
vehicle: PokemonEntity,
driver: Player
): Boolean {
return vehicle.runtime.resolveBoolean(settings.canJump)
return vehicle.runtime.resolveBoolean(settings.canJump ?: globalVehicle.canJump!!)
}
override fun setRideBar(
@@ -412,66 +427,79 @@ class VehicleSettings : RidingBehaviourSettings {
override val key = VehicleBehaviour.KEY
override val stats = mutableMapOf<RidingStat, IntRange>()
var canJump = "true".asExpression()
var infiniteStamina: Expression? = null
private set
var speed = "0.3".asExpression()
var canJump: Expression? = null
private set
var driveFactor = "1.0".asExpression()
var speed: Expression? = null
private set
var reverseDriveFactor = "0.25".asExpression()
var driveFactor: Expression? = null
private set
var rotationSpeed = "45/20".asExpression()
var reverseDriveFactor: Expression? = null
private set
var lookYawLimit = "101".asExpression()
var rotationSpeed: Expression? = null
private set
var speedExpr: Expression = "q.get_ride_stats('SPEED', 'LAND', 1.0, 0.3)".asExpression()
var lookYawLimit: Expression? = null
private set
var speedExpr: Expression? = null
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 accelerationExpr: Expression =
"q.get_ride_stats('ACCELERATION', 'LAND', (1.0 / (20.0 * 1.5)), (1.0 / (20.0 * 5.0)))".asExpression()
var accelerationExpr: Expression? = null
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()
var staminaExpr: Expression? = null
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()
var jumpExpr: Expression? = null
private set
var handlingExpr: Expression = "q.get_ride_stats('SKILL', 'LAND', 140.0, 20.0)".asExpression()
var handlingExpr: Expression? = null
private set
var rideSounds: RideSoundSettingsList = RideSoundSettingsList()
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeRidingStats(stats)
rideSounds.encode(buffer)
buffer.writeExpression(canJump)
buffer.writeExpression(speed)
buffer.writeExpression(driveFactor)
buffer.writeExpression(reverseDriveFactor)
buffer.writeExpression(rotationSpeed)
buffer.writeExpression(lookYawLimit)
buffer.writeNullableExpression(infiniteStamina)
buffer.writeNullableExpression(canJump)
buffer.writeNullableExpression(speed)
buffer.writeNullableExpression(driveFactor)
buffer.writeNullableExpression(reverseDriveFactor)
buffer.writeNullableExpression(rotationSpeed)
buffer.writeNullableExpression(lookYawLimit)
buffer.writeNullableExpression(speedExpr)
buffer.writeNullableExpression(accelerationExpr)
buffer.writeNullableExpression(staminaExpr)
buffer.writeNullableExpression(jumpExpr)
buffer.writeNullableExpression(handlingExpr)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
stats.putAll(buffer.readRidingStats())
rideSounds = RideSoundSettingsList.decode(buffer)
canJump = buffer.readExpression()
speed = buffer.readExpression()
driveFactor = buffer.readExpression()
reverseDriveFactor = buffer.readExpression()
rotationSpeed = buffer.readExpression()
lookYawLimit = buffer.readExpression()
infiniteStamina = buffer.readNullableExpression()
canJump = buffer.readNullableExpression()
speed = buffer.readNullableExpression()
driveFactor = buffer.readNullableExpression()
reverseDriveFactor = buffer.readNullableExpression()
rotationSpeed = buffer.readNullableExpression()
lookYawLimit = buffer.readNullableExpression()
speedExpr = buffer.readNullableExpression()
accelerationExpr = buffer.readNullableExpression()
staminaExpr = buffer.readNullableExpression()
jumpExpr = buffer.readNullableExpression()
handlingExpr = buffer.readNullableExpression()
}
}
@@ -8,16 +8,33 @@
package com.cobblemon.mod.common.api.riding.behaviour.types.liquid
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.CobblemonRideSettings
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.behaviour.RidingBehaviour
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.Side
import com.cobblemon.mod.common.api.riding.behaviour.ridingState
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.sound.RideSoundSettingsList
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 com.cobblemon.mod.common.util.blockPositionsAsListRounded
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.readNullableExpression
import com.cobblemon.mod.common.util.readRidingStats
import com.cobblemon.mod.common.util.resolveBoolean
import com.cobblemon.mod.common.util.resolveDouble
import com.cobblemon.mod.common.util.resolveFloat
import com.cobblemon.mod.common.util.writeNullableExpression
import com.cobblemon.mod.common.util.writeRidingStats
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
import net.minecraft.core.BlockPos
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
@@ -28,9 +45,6 @@ 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.max
import kotlin.math.min
class BoatBehaviour : RidingBehaviour<BoatSettings, BoatState> {
companion object {
@@ -38,6 +52,8 @@ class BoatBehaviour : RidingBehaviour<BoatSettings, BoatState> {
}
override val key = KEY
val globalBoat: BoatSettings
get() = CobblemonRideSettings.boat
override fun getRidingStyle(settings: BoatSettings, state: BoatState): RidingStyle {
return RidingStyle.LIQUID
@@ -87,12 +103,12 @@ class BoatBehaviour : RidingBehaviour<BoatSettings, BoatState> {
if (!state.isVehicleSprinting.get()) {
if (state.staminaBuffer.get() >= 20) {
val staminaIncrease = vehicle.runtime.resolveDouble(settings.staminaIncreaseExpr)
state.stamina.set(min(1.0f, state.stamina.get() + 0.01f * staminaIncrease.toFloat()))
val staminaIncrease = (1.0f / vehicle.runtime.resolveDouble(settings.stamReplenishTimeSeconds ?: globalBoat.stamReplenishTimeSeconds!!) ).toFloat() / 20.0f
state.stamina.set(min(1.0f, state.stamina.get() + staminaIncrease))
}
}
else if (abs(state.rideVelocity.get().z) > 0.1 && state.isVehicleSprinting.get()) {
consumeStamina(vehicle, driver, settings, state, 0.01f)
consumeStamina(vehicle, driver, settings, state)
}
state.staminaBuffer.set(state.staminaBuffer.get() + 1)
super.tick(settings, state, vehicle, driver, input)
@@ -134,14 +150,14 @@ class BoatBehaviour : RidingBehaviour<BoatSettings, BoatState> {
}
private fun applyVelocityFromInput(velocity: Vec3, vehicle: PokemonEntity, driver: Player, settings: BoatSettings, state: BoatState): Vec3 {
val speed = vehicle.runtime.resolveDouble(settings.speedExpr)
val acceleration = speed / (vehicle.runtime.resolveDouble(settings.accelerationExpr) * 20)
val speed = vehicle.runtime.resolveDouble(settings.speedExpr ?: globalBoat.speedExpr!!)
val acceleration = speed / (vehicle.runtime.resolveDouble(settings.accelerationExpr ?: globalBoat.accelerationExpr!!) * 20)
if (state.jumpBuffer.get() != -1 || !(vehicle.isInWater || vehicle.isUnderWater)) {
return velocity
}
val sprintModifier = vehicle.runtime.resolveDouble(settings.sprintSpeedModifier)
val sprintModifier = vehicle.runtime.resolveDouble(settings.sprintSpeedModifier ?: globalBoat.sprintSpeedModifier!!)
val activeSprintModifier = if (state.isVehicleSprinting.get() && hasStamina(state)) sprintModifier else 1.0
val forwardInput = driver.zza.toDouble()
val delta = when {
@@ -164,7 +180,7 @@ class BoatBehaviour : RidingBehaviour<BoatSettings, BoatState> {
if (!driver.jumping) return velocity // Not jumping
if (state.jumpBuffer.get() != -1) return velocity // Already jumped very recently
val jumpStrength = vehicle.runtime.resolveDouble(settings.jumpStrengthExpr)
val jumpStrength = vehicle.runtime.resolveDouble(settings.jumpStrengthExpr ?: globalBoat.jumpStrengthExpr!!)
state.jumpBuffer.set(0)
return Vec3(velocity.x, velocity.y + jumpStrength, velocity.z)
}
@@ -178,20 +194,20 @@ class BoatBehaviour : RidingBehaviour<BoatSettings, BoatState> {
return Vec3(velocity.x, 0.0, velocity.z)
}
}
val terminalVelocity = vehicle.runtime.resolveDouble(settings.terminalVelocity)
val terminalVelocity = vehicle.runtime.resolveDouble(settings.terminalVelocity ?: globalBoat.terminalVelocity!!)
val gravity = (9.8 / ( 20.0)) * 0.2
return Vec3(velocity.x, max(velocity.y - gravity, terminalVelocity), velocity.z)
}
private fun shouldFloatHigher(vehicle: PokemonEntity, settings: BoatSettings): Boolean {
val surfaceOffset = vehicle.runtime.resolveFloat(settings.surfaceLevelOffset)
val surfaceOffset = vehicle.runtime.resolveFloat(settings.surfaceLevelOffset ?: globalBoat.surfaceLevelOffset!!)
val blockPos = BlockPos.containing(vehicle.x, vehicle.eyeY + surfaceOffset, vehicle.z)
val fluidState = vehicle.level().getFluidState(blockPos)
return !fluidState.isEmpty
}
private fun applyStrafeRotation(vehicle: PokemonEntity, driver: Player, settings: BoatSettings, state: BoatState) {
val skillModifier = vehicle.runtime.resolveDouble(settings.rotationspeedModifierExpr)
val skillModifier = vehicle.runtime.resolveDouble(settings.rotationSpeedModifierExpr ?: globalBoat.rotationSpeedModifierExpr!!)
val strafe = driver.xxa
if (abs(strafe) > 0) {
val increase = if (strafe > 0) -1 else 1
@@ -203,9 +219,12 @@ class BoatBehaviour : RidingBehaviour<BoatSettings, BoatState> {
return state.stamina.get() != 0f
}
private fun consumeStamina(vehicle: PokemonEntity, driver: Player, settings: BoatSettings, state: BoatState, drain: Float) {
val staminaModifier = vehicle.runtime.resolveFloat(settings.staminaDecreaseExpr)
state.stamina.set(max(0f, state.stamina.get() - drain / staminaModifier))
private fun consumeStamina(vehicle: PokemonEntity, driver: Player, settings: BoatSettings, state: BoatState) {
if (vehicle.runtime.resolveBoolean(settings.infiniteStamina ?: globalBoat.infiniteStamina!!)) {
return
}
val stamDrainRate = (1.0f / vehicle.runtime.resolveDouble(settings.staminaExpr ?: globalBoat.staminaExpr!!)).toFloat() / 20.0f
state.stamina.set(max(0f, state.stamina.get() - stamDrainRate))
if (state.stamina.get() == 0f) {
state.isVehicleSprinting.set(false)
driver.isSprinting = false
@@ -310,7 +329,7 @@ class BoatBehaviour : RidingBehaviour<BoatSettings, BoatState> {
vehicle: PokemonEntity,
driver: Player
): Float {
val sprintFov = vehicle.runtime.resolveFloat(settings.sprintFovModifier)
val sprintFov = vehicle.runtime.resolveFloat(settings.sprintFovModifier ?: globalBoat.sprintFovModifier!!)
return if (state.isVehicleSprinting.get()) sprintFov else 1.0f
}
@@ -355,7 +374,7 @@ class BoatBehaviour : RidingBehaviour<BoatSettings, BoatState> {
state: BoatState,
vehicle: PokemonEntity
): Boolean {
return vehicle.runtime.resolveBoolean(settings.rotatePokemonHead)
return vehicle.runtime.resolveBoolean(settings.rotatePokemonHead ?: globalBoat.rotatePokemonHead!!)
}
override fun shouldRotateRiderHead(
@@ -385,48 +404,72 @@ class BoatSettings : RidingBehaviourSettings {
override val stats = mutableMapOf<RidingStat, IntRange>()
var rideSounds: RideSoundSettingsList = RideSoundSettingsList()
var terminalVelocity = "-2.0".asExpression()
var infiniteStamina: Expression? = null
private set
var rotatePokemonHead = "true".asExpression()
var terminalVelocity: Expression? = null
private set
var staminaIncreaseExpr = "q.get_ride_stats('STAMINA', 'LIQUID', 2.0, 1.0)".asExpression()
var rotatePokemonHead: Expression? = null
private set
var staminaDecreaseExpr = "q.get_ride_stats('STAMINA', 'LIQUID', 5.0, 1.0)".asExpression()
var stamReplenishTimeSeconds: Expression? = null
private set
var rotationspeedModifierExpr = "q.get_ride_stats('SKILL', 'LIQUID', 1.5, 0.5)".asExpression()
var staminaExpr: Expression? = null
private set
var jumpStrengthExpr = "q.get_ride_stats('JUMP', 'LIQUID', 2.5, 1.0)".asExpression()
var rotationSpeedModifierExpr: Expression? = null
private set
var speedExpr = "q.get_ride_stats('SPEED', 'LIQUID', 2.0, 0.5)".asExpression()
var jumpStrengthExpr: Expression? = null
private set
var accelerationExpr = "q.get_ride_stats('ACCELERATION', 'LIQUID', 0.1, 12.0)".asExpression()
var speedExpr: Expression? = null
private set
var sprintSpeedModifier = "q.get_ride_stats('JUMP', 'LIQUID', 1.5, 1.25)".asExpression()
var accelerationExpr: Expression? = null
private set
var sprintFovModifier = "1.2".asExpression()
var sprintSpeedModifier: Expression? = null
private set
var surfaceLevelOffset = "0".asExpression()
var sprintFovModifier: Expression? = null
var surfaceLevelOffset: Expression? = null
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeRidingStats(stats)
rideSounds.encode(buffer)
buffer.writeExpression(terminalVelocity)
buffer.writeNullableExpression(infiniteStamina)
buffer.writeNullableExpression(terminalVelocity)
buffer.writeNullableExpression(rotatePokemonHead)
buffer.writeNullableExpression(stamReplenishTimeSeconds)
buffer.writeNullableExpression(staminaExpr)
buffer.writeNullableExpression(rotationSpeedModifierExpr)
buffer.writeNullableExpression(jumpStrengthExpr)
buffer.writeNullableExpression(speedExpr)
buffer.writeNullableExpression(accelerationExpr)
buffer.writeNullableExpression(sprintSpeedModifier)
buffer.writeNullableExpression(sprintFovModifier)
buffer.writeNullableExpression(surfaceLevelOffset)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
stats.putAll(buffer.readRidingStats())
rideSounds = RideSoundSettingsList.decode(buffer)
terminalVelocity = buffer.readExpression()
infiniteStamina = buffer.readNullableExpression()
terminalVelocity = buffer.readNullableExpression()
rotatePokemonHead = buffer.readNullableExpression()
stamReplenishTimeSeconds = buffer.readNullableExpression()
staminaExpr = buffer.readNullableExpression()
rotationSpeedModifierExpr = buffer.readNullableExpression()
jumpStrengthExpr = buffer.readNullableExpression()
speedExpr = buffer.readNullableExpression()
accelerationExpr = buffer.readNullableExpression()
sprintSpeedModifier = buffer.readNullableExpression()
sprintFovModifier = buffer.readNullableExpression()
surfaceLevelOffset = buffer.readNullableExpression()
}
}
@@ -8,10 +8,14 @@
package com.cobblemon.mod.common.api.riding.behaviour.types.liquid
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonRideSettings
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.behaviour.RidingBehaviour
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.Side
import com.cobblemon.mod.common.api.riding.behaviour.ridingState
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.sound.RideSoundSettingsList
@@ -21,6 +25,7 @@ import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.blockPositionsAsListRounded
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.readRidingStats
import com.cobblemon.mod.common.util.writeNullable
import com.cobblemon.mod.common.util.writeRidingStats
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
@@ -38,6 +43,8 @@ class BurstBehaviour : RidingBehaviour<BurstSettings, BurstState> {
}
override val key = KEY
val globalBurst: BurstSettings
get() = CobblemonRideSettings.burst
override fun getRidingStyle(settings: BurstSettings, state: BurstState): RidingStyle {
return RidingStyle.LIQUID
@@ -67,7 +74,7 @@ class BurstBehaviour : RidingBehaviour<BurstSettings, BurstState> {
vehicle: PokemonEntity,
driver: Player
): Float {
if(state.dashing.get()) {
if (state.dashing.get()) {
state.ticks.set(state.ticks.get() + 1)
if(state.ticks.get() >= DASH_TICKS) {
state.dashing.set(false)
@@ -77,7 +84,7 @@ class BurstBehaviour : RidingBehaviour<BurstSettings, BurstState> {
}
state.dashing.set(true)
return settings.dashSpeed
return settings.dashSpeed ?: globalBurst.dashSpeed!!
}
override fun rotation(
@@ -239,22 +246,21 @@ class BurstSettings : RidingBehaviourSettings {
override val key = BurstBehaviour.KEY
override val stats = mutableMapOf<RidingStat, IntRange>()
var dashSpeed = 1F
var dashSpeed: Float? = null
private set
var rideSounds: RideSoundSettingsList = RideSoundSettingsList()
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeRidingStats(stats)
rideSounds.encode(buffer)
buffer.writeFloat(dashSpeed)
buffer.writeNullable(dashSpeed) { buf, speed -> buf.writeFloat(speed) }
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
stats.putAll(buffer.readRidingStats())
rideSounds = RideSoundSettingsList.decode(buffer)
dashSpeed = buffer.readFloat()
dashSpeed = buffer.readNullable { buf -> buf.readFloat() }
}
}
@@ -10,21 +10,34 @@ package com.cobblemon.mod.common.api.riding.behaviour.types.liquid
import com.bedrockk.molang.Expression
import com.bedrockk.molang.runtime.MoLangMath.lerp
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonRideSettings
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.api.orientation.OrientationController
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.*
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 com.cobblemon.mod.common.api.riding.behaviour.Side
import com.cobblemon.mod.common.api.riding.behaviour.ridingState
import com.cobblemon.mod.common.api.riding.behaviour.types.air.angleDifference
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.sound.RideSoundSettingsList
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 com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.math.geometry.toDegrees
import com.cobblemon.mod.common.util.math.geometry.toRadians
import com.cobblemon.mod.common.util.readNullableExpression
import com.cobblemon.mod.common.util.readRidingStats
import com.cobblemon.mod.common.util.resolveBoolean
import com.cobblemon.mod.common.util.resolveDouble
import com.cobblemon.mod.common.util.toVec3d
import com.cobblemon.mod.common.util.writeNullableExpression
import com.cobblemon.mod.common.util.writeRidingStats
import kotlin.math.*
import net.minecraft.client.Minecraft
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.RegistryFriendlyByteBuf
@@ -35,18 +48,7 @@ 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 org.joml.Vector3f
import kotlin.math.abs
import kotlin.math.asin
import kotlin.math.cos
import kotlin.math.hypot
import kotlin.math.max
import kotlin.math.min
import kotlin.math.pow
import kotlin.math.sign
import kotlin.math.sin
import kotlin.math.sqrt
class DolphinBehaviour : RidingBehaviour<DolphinSettings, DolphinState> {
companion object {
@@ -54,13 +56,15 @@ class DolphinBehaviour : RidingBehaviour<DolphinSettings, DolphinState> {
}
override val key = KEY
val globalDolphin: DolphinSettings
get() = CobblemonRideSettings.dolphin
override fun getRidingStyle(settings: DolphinSettings, state: DolphinState): RidingStyle {
return RidingStyle.LIQUID
}
val poseProvider = PoseProvider<DolphinSettings, DolphinState>(PoseType.FLOAT)
.with(PoseOption(PoseType.SWIM) { _, _, entity -> entity.deltaMovement.length() > 0.1 })
.with(PoseOption(PoseType.SWIM) { _, state, entity -> abs(state.rideVelocity.get().length()) > 0.05 })
override fun isActive(settings: DolphinSettings, state: DolphinState, vehicle: PokemonEntity): Boolean {
return vehicle.isInWater || !vehicle.onGround()
@@ -117,10 +121,14 @@ class DolphinBehaviour : RidingBehaviour<DolphinSettings, DolphinState> {
) {
val stam = state.stamina.get()
if (vehicle.runtime.resolveBoolean(settings.infiniteStamina ?: globalDolphin.infiniteStamina!!)) {
return
}
// Grab the boost time in seconds and convert to ticks. Then calculate the drain rate as inversely
// proportional to the number of ticks of boost thus making a full boost take x ticks
// in short: "Stamina drains slower at higher values and also replenishes slower"
val boostTime = vehicle.runtime.resolveDouble(settings.staminaExpr) * 20.0f
val boostTime = vehicle.runtime.resolveDouble(settings.staminaExpr ?: globalDolphin.staminaExpr!!) * 20.0f
val stamDrainRate = (1.0f / boostTime).toFloat()
val newStam = if (state.boosting.get()) max(0.0f,stam - stamDrainRate)
@@ -145,37 +153,46 @@ class DolphinBehaviour : RidingBehaviour<DolphinSettings, DolphinState> {
driver: Player,
input: Vec3
): Vec3 {
if (driver !is OrientationControllable) return Vec3.ZERO
val controller = (driver as OrientationControllable).orientationController
if (vehicle !is OrientationControllable) return Vec3.ZERO
val controller = (vehicle as OrientationControllable).orientationController
val orientation = controller.orientation ?: return Vec3.ZERO
val runtime = vehicle.runtime
val boostMod = if (state.boosting.get()) runtime.resolveDouble(settings.jumpExpr) else 1.0
val topSpeed = (vehicle.runtime.resolveDouble(settings.speedExpr) / 20.0) * boostMod
val accel = (topSpeed / (vehicle.runtime.resolveDouble(settings.accelerationExpr) * 20.0)) * boostMod
val boostMod = if (state.boosting.get()) runtime.resolveDouble(settings.jumpExpr ?: globalDolphin.jumpExpr!!) else 1.0
val topSpeed = (vehicle.runtime.resolveDouble(settings.speedExpr ?: globalDolphin.speedExpr!!) / 20.0) * boostMod
val accel = (topSpeed / (vehicle.runtime.resolveDouble(settings.accelerationExpr ?: globalDolphin.accelerationExpr!!) * 20.0)) * boostMod
val strafeFactor = runtime.resolveDouble(settings.strafeFactor ?: globalDolphin.strafeFactor!!)
val reverseDriveFactor = runtime.resolveDouble(settings.reverseDriveFactor ?: globalDolphin.reverseDriveFactor!!)
var currVel = state.rideVelocity.get()
/*********************************************
* Breaching logic
*********************************************/
if (!vehicle.isInWater && !vehicle.isUnderWater)
{
val currVel = state.lastVelocity.get()
val breachBoost = 1.5
var prevVel = state.lastVelocity.get()
val breachBoost = 0.3
state.clearMouseSmoothers.set(false)
if (!state.hasBreached.get()) {
// Apply an upwards breaching force if boosting. This will enhance jumping out of water to feel more
// fun
// Apply an upwards breaching force if boosting. This will enhance jumping out of water to feel more fun
if (state.boosting.get()) {
state.lastVelocity.set(Vec3(currVel.x, currVel.y + breachBoost*sin(-controller.pitch.toRadians()).coerceIn(0.0f,0.5f), currVel.z))
prevVel = Vec3(prevVel.x, prevVel.y + breachBoost*(sin(-controller.pitch.toRadians()).coerceIn(0.0f,0.5f)*2.0).pow(2), prevVel.z)
}
state.hasBreached.set(true)
} else {
val gravity = (9.8 / ( 20.0)) * 0.2 * 0.4
val terminalVel = 2.0
state.lastVelocity.set(Vec3(currVel.x, max(-terminalVel, currVel.y - gravity), currVel.z)) //.yRot(vehicle.yRot.toRadians()))
prevVel = Vec3(prevVel.x, max(-terminalVel, prevVel.y - gravity), prevVel.z)
}
return state.lastVelocity.get()
state.lastVelocity.set(prevVel)
return state.lastVelocity.get().yRot(vehicle.yRot.toRadians())
} else {
state.lastVelocity.set(vehicle.deltaMovement.yRot(vehicle.yRot.toRadians()))
if(state.hasBreached.get()) {
state.clearMouseSmoothers.set(true)
}
state.hasBreached.set(false)
currVel = Vec3(currVel.x, 0.0, currVel.z) // Reset y velocity to account for y vel persistance edgecase
}
/*********************************************
@@ -184,32 +201,30 @@ class DolphinBehaviour : RidingBehaviour<DolphinSettings, DolphinState> {
if (vehicle.horizontalCollision || vehicle.verticalCollision) {
// not actually friction
val frictionLimit = 0.1
val postColSpeed = min(max(vehicle.deltaMovement.length(), frictionLimit), state.speed.get())
val postColSpeed = min(max(state.rideVelocity.get().length(), frictionLimit), state.speed.get())
state.speed.set(postColSpeed)
}
val currSpeed = state.speed.get()
val strafeFactor = runtime.resolveDouble(settings.strafeFactor)
val reverseDriveFactor = runtime.resolveDouble(settings.reverseDriveFactor)
val f = -driver.xxa * strafeFactor
when {
driver.zza > 0.0 -> state.speed.set(min(topSpeed, currSpeed + accel))
driver.zza < 0.0 -> state.speed.set(max( -topSpeed * reverseDriveFactor, currSpeed - accel))
else -> state.speed.set( lerp(currSpeed, 0.0, 0.05))
val currZVel = state.rideVelocity.get().z
val newZVel = when {
driver.zza > 0.0 -> min(topSpeed, currZVel + accel)
driver.zza < 0.0 -> max( -topSpeed * reverseDriveFactor, currZVel - accel)
else -> lerp(currZVel, 0.0, 0.05)
}
val vertInput = when {
driver.jumping && state.stamina.get() != 0.0f -> 1.0
driver.isShiftKeyDown -> -1.0
else -> 0.0
} * strafeFactor
state.rideVelocity.set(Vec3(currVel.x, currVel.y, newZVel))
var newVelocity = Vec3(f, vertInput,-state.speed.get())
// Convert the local velocity vector into a world vector
val localVelVec = Vector3f(
state.rideVelocity.get().x.toFloat(),
(state.rideVelocity.get().y).toFloat(),
state.rideVelocity.get().z.toFloat() * -1.0f // Flip the z axis to make this left handed
)
// Align to the ride
newVelocity = controller.orientation?.transform(newVelocity.toVector3f())?.toVec3d() ?: Vec3.ZERO
newVelocity = newVelocity.yRot(vehicle.yRot.toRadians())
val desiredVec = newVelocity.subtract(vehicle.deltaMovement)
return vehicle.deltaMovement.add(desiredVec)
val worldVelVec = localVelVec.mul(orientation).toVec3d()
state.lastVelocity.set(worldVelVec) // Set last velocity to be used during breaching
// Unrotate preemptively as this vector gets rotated later down the line in MC logic.
return worldVelVec.yRot(vehicle.yRot.toRadians())
}
override fun angRollVel(
@@ -219,17 +234,16 @@ class DolphinBehaviour : RidingBehaviour<DolphinSettings, DolphinState> {
driver: Player,
deltaTime: Double
): Vec3 {
if (Cobblemon.config.disableRoll) return Vec3.ZERO
if (driver !is OrientationControllable) return Vec3.ZERO
val controller = (driver as OrientationControllable).orientationController
if (vehicle !is OrientationControllable) return Vec3.ZERO
val controller = (vehicle as OrientationControllable).orientationController
val currSpeed = vehicle.deltaMovement.length()
val handling = vehicle.runtime.resolveDouble(settings.handlingExpr)
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr) / 20.0
val handling = vehicle.runtime.resolveDouble(settings.handlingExpr ?: globalDolphin.handlingExpr!!)
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr ?: globalDolphin.speedExpr!!) / 20.0
val yawDeltaDeg = deltaTime * handling * sin(Math.toRadians(controller.roll.toDouble())).pow(2) * sin(Math.toRadians(controller.roll.toDouble())).sign
val trueYawDelt = yawDeltaDeg * abs(cos(Math.toRadians(controller.pitch.toDouble()))) * sqrt(RidingBehaviour.scaleToRange(currSpeed, 0.0, topSpeed))
val trueYawDelt = yawDeltaDeg * abs(cos(Math.toRadians(controller.pitch.toDouble()))*2).coerceIn(-1.0, 1.0) * sqrt(RidingBehaviour.scaleToRange(currSpeed, 0.0, topSpeed))
// Dampen yaw when upside down
val yawDampen = (1 - abs(min(cos(controller.roll.toRadians()),0.0f)))
@@ -250,45 +264,62 @@ class DolphinBehaviour : RidingBehaviour<DolphinSettings, DolphinState> {
driver: Player,
deltaTime: Double
) {
// Calculate correcting roll force.
// Mult by cos(pitch) to ensure that it doesn't roll correct at when diving or climbing
val currSpeed = vehicle.deltaMovement.length()
val rollCorrectionTimer = vehicle.runtime.resolveDouble(settings.timeToRollCorrect)
val maxRollCorrectionRate = if (!(vehicle.isInWater || vehicle.isUnderWater)) 15.0f
else 10.0f * (cos(controller.pitch.toRadians())).pow(2) * ((state.noInputTime.get() - rollCorrectionTimer) * 2).coerceIn(0.0, 1.0).toFloat()
val maxRollForce = maxRollCorrectionRate / 40.0
val rollArrivalDeg = 90.0
if((state.speed.get() < 0.1 && driver.zza.toDouble() == 0.0) || state.noInputTime.get() > rollCorrectionTimer || !(vehicle.isInWater || vehicle.isUnderWater)) {
val arrivalInfluence = (min(rollArrivalDeg,abs(controller.roll).toDouble()) / rollArrivalDeg)
val desiredRollForce = (0 - controller.roll).sign * maxRollCorrectionRate * arrivalInfluence
var steeredRollForce = desiredRollForce - state.currRollCorrectionForce.get()
// Ensure the 'steering' correction force doesn't exceed our maxForce
if (abs(steeredRollForce) > maxRollForce) {
steeredRollForce = steeredRollForce.sign * maxRollForce
}
// Apply the 'steered' force (its really not a steering force I need a better name)
// Stop roll and pitch correction when breached
if (state.hasBreached.get()) {
state.currRollCorrectionForce.set(
state.currRollCorrectionForce.get() + steeredRollForce
lerp(state.currRollCorrectionForce.get(), 0.0, deltaTime * 0.98)
)
} else {
state.currRollCorrectionForce.set(
//reduce rollCorrection gradually
lerp(state.currRollCorrectionForce.get(), 0.0, deltaTime * 0.96)
state.currPitchCorrectionForce.set(
lerp(state.currPitchCorrectionForce.get(), 0.0, deltaTime * 0.98)
)
return
}
//Calculate correcting pitch force.
val maxPitchCorrectionRate = if (!(vehicle.isInWater || vehicle.isUnderWater)) 15.0 else 5.0f * ((state.noInputTime.get() - rollCorrectionTimer) * 2).coerceIn(0.0, 1.0).toFloat() * (1 - (currSpeed * 0.5).coerceIn(0.0, 1.0))
val maxPitchForce = maxPitchCorrectionRate / 40.0
val pitchArrivalDeg = 45.0
val targetPitch = if ((vehicle.isInWater || vehicle.isUnderWater)) 0.0f else (-asin(vehicle.deltaMovement.y / vehicle.deltaMovement.length()).toDegrees()).coerceIn(-45.0f, 45.0f)
// Calculate correcting roll force.
val rollCorrectionTimer = vehicle.runtime.resolveDouble(settings.timeToRollCorrect ?: globalDolphin.timeToRollCorrect!!)
// If the ride is moving then have the correction rate be an equal proportion of:
// - how rolled are you
// - how long has it been since last mouse input (higher is stronger correction)
// Also the correction is proportional as a whole to how NOT pitched are you so you don't spin endlessly when looking up or down
val howRolledAmI = sqrt(abs((cos(controller.roll.toRadians()) - 1.0f) * 0.5f)) // sqrt so it trends towards one pretty quickly
val maxRollCorrectionRate =
if (state.rideVelocity.get().length() < 0.2) 10.0f * abs(cos(controller.pitch.toRadians()))
else 10.0f * cos(controller.pitch.toRadians()).pow(2) * (
howRolledAmI*1.0f +
(state.noInputTime.get() - rollCorrectionTimer).coerceIn(0.0, 1.0).pow(2).toFloat()*0.5f
)
val maxRollForce = maxRollCorrectionRate / 40.0f
val rollArrivalDeg = 60.0f
val desiredRoll = 0.0f
if((state.noInputTime.get() > rollCorrectionTimer && driver.zza.toDouble() == 0.0) || !(vehicle.isInWater || vehicle.isUnderWater)) {
val arrivalInfluence = min(rollArrivalDeg, abs(Mth.wrapDegrees(targetPitch - controller.pitch.toDouble()))) / pitchArrivalDeg
val desiredPitchForce = (targetPitch - controller.pitch).sign * maxPitchCorrectionRate * arrivalInfluence
// Calculate the signed error (how far and in what direction)
val rollError = Mth.wrapDegrees(desiredRoll.angleDifference(controller.roll))
// Use the absolute error to calculate the magnitude of the influence
val influenceMagnitude = (min(rollArrivalDeg, abs(rollError)) / rollArrivalDeg)
val desiredRollForce = maxRollCorrectionRate * influenceMagnitude * -rollError.sign
var steeredRollForce = desiredRollForce - state.currRollCorrectionForce.get()
// Ensure the 'steering' correction force doesn't exceed our maxForce
if (abs(steeredRollForce) > maxRollForce) {
steeredRollForce = steeredRollForce.sign * maxRollForce
}
// Apply the 'steered' force (its really not a steering force I need a better name)
state.currRollCorrectionForce.set(
state.currRollCorrectionForce.get() + steeredRollForce
)
//Calculate correcting pitch force.
val maxPitchCorrectionRate = 5.0f * (state.noInputTime.get() - rollCorrectionTimer).coerceIn(0.0, 1.0).pow(2).toFloat()*0.5f
val maxPitchForce = maxPitchCorrectionRate / 40.0f
val pitchArrivalDeg = 45.0f
val desiredPitch = 0.0f
if(state.rideVelocity.get().length() < 0.1 && state.noInputTime.get() > rollCorrectionTimer) {
val pitchErrror = Mth.wrapDegrees(desiredPitch.angleDifference(controller.pitch))
val arrivalInfluence = (min(pitchArrivalDeg,abs(pitchErrror)) / pitchArrivalDeg)
val desiredPitchForce = maxPitchCorrectionRate * arrivalInfluence * -pitchErrror.sign
var steeredPitchForce = desiredPitchForce - state.currPitchCorrectionForce.get()
// Ensure the 'steering' correction force doesn't exceed our maxForce
@@ -327,130 +358,72 @@ class DolphinBehaviour : RidingBehaviour<DolphinSettings, DolphinState> {
} else {
state.noInputTime.set(0.0)
}
return when {
Cobblemon.config.disableRoll -> noRollRotation(
settings,
state,
vehicle,
driver,
mouseY,
mouseX,
mouseYSmoother,
mouseXSmoother,
sensitivity,
deltaTime
)
else -> rollRotation(
settings,
state,
vehicle,
driver,
mouseY,
mouseX,
mouseYSmoother,
mouseXSmoother,
sensitivity,
deltaTime
)
}
}
fun noRollRotation(
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
val controller = (driver as OrientationControllable).orientationController
// Set roll to zero if transitioning to noroll config
controller.rotateRoll(controller.roll * -1.0f)
val boostingHandleDebuff = if (state.boosting.get()) 0.2 else 1.0
val handling = vehicle.runtime.resolveDouble(settings.handlingExpr) * boostingHandleDebuff
val mouseInputMult = vehicle.runtime.resolveDouble(settings.mouseInputMult)
if (vehicle !is OrientationControllable) return Vec3.ZERO
val controller = (vehicle as OrientationControllable).orientationController
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr ?: globalDolphin.speedExpr!!) / 20.0
val boostingHandleDebuff = if (state.boosting.get()) 0.5 else 1.0
val mouseInputMult = vehicle.runtime.resolveDouble(settings.mouseInputMult ?: globalDolphin.mouseInputMult!!) * boostingHandleDebuff
// Applied to mouseX to prevent turning upside down when trying to roll hard.
val rollDegreeDebuff = if(mouseX.sign.toFloat() == controller.roll.sign) 1 - 0.9f*sqrt(abs(sin(controller.roll.toRadians())))*sqrt(abs(cos(controller.pitch))) else 1.0f
//Smooth out mouse input.
val smoothingSpeed = 4.0
val mouseXc = ((mouseX).coerceIn(-60.0, 60.0) / 60.0) * mouseInputMult
val mouseYc = ((mouseY).coerceIn(-60.0, 60.0) / 60.0) * mouseInputMult
val xInput = mouseXSmoother.getNewDeltaValue(mouseXc * 0.1, deltaTime * smoothingSpeed);
val yInput = mouseYSmoother.getNewDeltaValue(mouseYc * 0.1, deltaTime * smoothingSpeed);
val smoothingSpeed = if(state.hasBreached.get()) 1.0f else 2.0f
// When upside down apply a pitching force to nudge the player towards a dive. This reduces the 'weirdness'
// of being upside down
val rolledOverPitchCorrection = -8*sqrt(abs(min(cos(controller.roll.toRadians()),0.0f))) * (cos(controller.pitch.toRadians())).pow(2)
val mouseXc = (mouseX).coerceIn(-45.0, 45.0) * mouseInputMult * rollDegreeDebuff
val mouseYc = (mouseY).coerceIn(-45.0, 45.0) * mouseInputMult + rolledOverPitchCorrection
val mouseModForce = if (state.hasBreached.get()) 0.15 else 0.1 // Used to effect more spin momentum during breaching
//Give the ability to yaw with x mouse input when at low speeds.
val yawForce = xInput * handling
//Apply yaw globally as we don't want roll or pitch changes due to local yaw when looking up or down.
controller.applyGlobalYaw(yawForce.toFloat() * abs(cos(controller.pitch.toRadians())).pow(2))
var pitchRot = yInput * handling
// Pitch up globally
if (abs(controller.pitch + pitchRot) >= 89.5 ) {
pitchRot = 0.0
if (state.clearMouseSmoothers.get()) {
mouseXSmoother.reset()
mouseYSmoother.reset()
} else {
controller.applyGlobalPitch(pitchRot.toFloat() * -1.0f)
state.clearMouseSmoothers.set(false)
}
val xInput = mouseXSmoother.getNewDeltaValue(mouseXc * mouseModForce, deltaTime * smoothingSpeed);
val yInput = mouseYSmoother.getNewDeltaValue(mouseYc * mouseModForce, deltaTime * smoothingSpeed);
val currSpeed = state.rideVelocity.get().length()
val currHorzSpeed = state.rideVelocity.get().horizontalDistance()
val hoverSpeed = 0.2
val pitchDamping = abs(cos(controller.pitch.toRadians())).pow(2) // Helper value used when wanting to discard the influence of another value at steep pitches
//yaw, pitch, roll
return Vec3.ZERO
}
fun rollRotation(
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
val controller = (driver as OrientationControllable).orientationController
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr) / 20.0
val boostingHandleDebuff = if (state.boosting.get()) 0.2 else 1.0
val mouseInputMult = vehicle.runtime.resolveDouble(settings.mouseInputMult) * boostingHandleDebuff
//Smooth out mouse input.
val smoothingSpeed = 4.0
val mouseXc = (mouseX).coerceIn(-45.0, 45.0) * mouseInputMult
val mouseYc = (mouseY).coerceIn(-45.0, 45.0) * mouseInputMult
val xInput = mouseXSmoother.getNewDeltaValue(mouseXc * 0.1, deltaTime * smoothingSpeed);
val yInput = mouseYSmoother.getNewDeltaValue(mouseYc * 0.1, deltaTime * smoothingSpeed);
val currSpeed = vehicle.deltaMovement.length()
// Yaw locally
// Dampen yaw when rolled or pitched substantially
val yawDampen = abs(cos(controller.pitch.toRadians())).pow(2) * abs(cos(controller.roll.toRadians())).pow(2)
val localYaw = xInput * (1 - (RidingBehaviour.scaleToRange(currSpeed, 0.0, topSpeed))).coerceIn(0.4, 1.0) * yawDampen
// Yaw locally when at or below hoverSpeed but not when pitched up or rolled over
val globalYaw = xInput * (1 - sqrt(RidingBehaviour.scaleToRange(currSpeed, 0.0, hoverSpeed))).coerceIn(0.0, 1.0)
controller.rotateYaw(pitchDamping*globalYaw.toFloat())
controller.rotateRoll((1-pitchDamping)*globalYaw.toFloat())
// Pitch locally up or down depending upon a number of factors:
// - Reduce pitch substantially when rolled and in a steep yaw. This prevents the player from ignoring a slow handling stat
// - Do not pitch at all when at or below hover speed
// - pitch faster at higher speeds
val p = 1.0 - vehicle.runtime.resolveDouble(settings.horizontalPitchExpr)
val localPitch = yInput * (1 - abs(sin(controller.roll.toRadians())) * p * hypot(controller.forwardVector.x.toDouble(), controller.forwardVector.z.toDouble()).toFloat())
// localPitch *= if (currSpeed > hoverSpeed) RidingBehaviour.scaleToRange(currSpeed, hoverSpeed, topSpeed)
// else 0.0
val p = 1.0 - vehicle.runtime.resolveDouble(settings.horizontalPitchExpr ?: globalDolphin.horizontalPitchExpr!!)
val localPitch = yInput * (1 - abs(sin(controller.roll.toRadians())).pow(2) * p * hypot(controller.forwardVector.x.toDouble(), controller.forwardVector.z.toDouble()).toFloat())
var localYawForce = 0.0
var rollForce = 0.0
// If it has breached then turn the yaw into roll
if (state.hasBreached.get()) {
localYawForce = xInput
} else {
// Roll less when slow and not at all when at hoverSpeed or lower.
// Apply a roll righting force when trying to pitch hard while rolled sideways. This nudges the player towards
// pitching globally
val pitchInfluencedRollCorrection = 0.0
val howRolledAmI = (( cos(controller.roll.toRadians()) - 1).coerceIn(-1.0f, 0.0f) * -2) //Coerce into how much roll you want to let in in the deadzone
val isInputtingTowardsRoll = if ( xInput.sign == controller.roll.sign.toDouble()) 1.0 else 0.0
val inputInRolledDir = if (isInputtingTowardsRoll == 1.0) 1.0 else -cos(controller.roll.toRadians()).coerceIn(-1.0f, 0.0f).toDouble()
val rollDampen = (1 - howRolledAmI.coerceIn(0.0f, 0.5f) * pitchDamping * inputInRolledDir)
rollForce = xInput * sqrt(abs(RidingBehaviour.scaleToRange(currHorzSpeed, 0.0, topSpeed)).coerceIn(0.0,1.0)) * rollDampen + pitchInfluencedRollCorrection
// When already rolled significantly part of the roll force should turn to local yaw force
localYawForce = rollForce * abs(sin(controller.roll.toRadians())).pow(2) * pitchDamping * isInputtingTowardsRoll
}
// Roll less when slow and not at all when at hoverSpeed or lower.
// Apply a roll righting force when trying to pitch hard while rolled sideways. This nudges the player towards
// pitching globally
val rollForce = xInput * ((RidingBehaviour.scaleToRange(currSpeed, 0.0, topSpeed))).coerceIn(0.2, 1.0) //+ pitchInfluencedRollCorrection
//yaw, pitch, roll
return Vec3(localYaw, localPitch, rollForce)
return Vec3(localYawForce, localPitch, rollForce)
}
override fun canJump(
@@ -560,62 +533,68 @@ class DolphinSettings : RidingBehaviourSettings {
override val key = DolphinBehaviour.KEY
override val stats = mutableMapOf<RidingStat, IntRange>()
var canJump = "true".asExpression()
var infiniteStamina: Expression? = null
private set
var reverseDriveFactor = "0.25".asExpression()
var canJump: Expression? = null
private set
var strafeFactor = "0.2".asExpression()
var reverseDriveFactor: Expression? = null
private set
var timeToRollCorrect: Expression = "0.5".asExpression()
var strafeFactor: Expression? = null
private set
var timeToRollCorrect: Expression? = null
private set
// Boost influence when boosting
var jumpExpr: Expression = "q.get_ride_stats('JUMP', 'LIQUID', 2.2, 1.2)".asExpression()
var jumpExpr: Expression? = null
// Yaw rate in degrees per second
var handlingExpr: Expression = "q.get_ride_stats('SKILL', 'LIQUID', 100.0, 45.0)".asExpression()
var horizontalPitchExpr: Expression = "q.get_ride_stats('SKILL', 'LIQUID', 0.3, 0.2)".asExpression()
var mouseInputMult: Expression = "q.get_ride_stats('SKILL', 'LIQUID', 1.2, 0.8)".asExpression()
var handlingExpr: Expression? = null
var horizontalPitchExpr: Expression? = null
var mouseInputMult: Expression? = null
// Top speed in Bl/s
var speedExpr: Expression = "q.get_ride_stats('SPEED', 'LIQUID', 15.0, 5.0)".asExpression()
var speedExpr: Expression? = null
// Seconds to get to top speed
var accelerationExpr: Expression = "q.get_ride_stats('ACCELERATION', 'LIQUID', 2.0, 5.0)".asExpression()
var accelerationExpr: Expression? = null
// Seconds of boost
var staminaExpr: Expression = "q.get_ride_stats('STAMINA', 'LIQUID', 7.0, 3.0)".asExpression()
var staminaExpr: Expression? = null
var rideSounds: RideSoundSettingsList = RideSoundSettingsList()
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeRidingStats(stats)
rideSounds.encode(buffer)
buffer.writeExpression(canJump)
buffer.writeExpression(reverseDriveFactor)
buffer.writeExpression(strafeFactor)
buffer.writeExpression(jumpExpr)
buffer.writeExpression(handlingExpr)
buffer.writeExpression(horizontalPitchExpr)
buffer.writeExpression(mouseInputMult)
buffer.writeExpression(speedExpr)
buffer.writeExpression(accelerationExpr)
buffer.writeExpression(staminaExpr)
buffer.writeNullableExpression(infiniteStamina)
buffer.writeNullableExpression(canJump)
buffer.writeNullableExpression(reverseDriveFactor)
buffer.writeNullableExpression(strafeFactor)
buffer.writeNullableExpression(timeToRollCorrect)
buffer.writeNullableExpression(jumpExpr)
buffer.writeNullableExpression(handlingExpr)
buffer.writeNullableExpression(horizontalPitchExpr)
buffer.writeNullableExpression(mouseInputMult)
buffer.writeNullableExpression(speedExpr)
buffer.writeNullableExpression(accelerationExpr)
buffer.writeNullableExpression(staminaExpr)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
stats.putAll(buffer.readRidingStats())
rideSounds = RideSoundSettingsList.decode(buffer)
canJump = buffer.readExpression()
reverseDriveFactor = buffer.readExpression()
strafeFactor = buffer.readExpression()
jumpExpr = buffer.readExpression()
handlingExpr = buffer.readExpression()
horizontalPitchExpr = buffer.readExpression()
mouseInputMult = buffer.readExpression()
speedExpr = buffer.readExpression()
accelerationExpr = buffer.readExpression()
staminaExpr = buffer.readExpression()
infiniteStamina = buffer.readNullableExpression()
canJump = buffer.readNullableExpression()
reverseDriveFactor = buffer.readNullableExpression()
strafeFactor = buffer.readNullableExpression()
timeToRollCorrect = buffer.readNullableExpression()
jumpExpr = buffer.readNullableExpression()
handlingExpr = buffer.readNullableExpression()
horizontalPitchExpr = buffer.readNullableExpression()
mouseInputMult = buffer.readNullableExpression()
speedExpr = buffer.readNullableExpression()
accelerationExpr = buffer.readNullableExpression()
staminaExpr = buffer.readNullableExpression()
}
}
@@ -628,6 +607,7 @@ class DolphinState : RidingBehaviourState() {
var boostIsToggleable = ridingState(false, Side.CLIENT)
var speed = ridingState(0.0, Side.CLIENT)
var hasBreached = ridingState(false, Side.CLIENT)
var clearMouseSmoothers = ridingState(false, Side.CLIENT)
override fun encode(buffer: FriendlyByteBuf) {
super.encode(buffer)
@@ -649,6 +629,7 @@ class DolphinState : RidingBehaviourState() {
boostIsToggleable.set(true, forced = true)
speed.set(0.0, forced = true)
hasBreached.set(false, forced = true)
clearMouseSmoothers.set(false, forced = true)
}
override fun toString(): String {
@@ -666,6 +647,7 @@ class DolphinState : RidingBehaviourState() {
it.boostIsToggleable.set(this.boosting.get(), forced = true)
it.speed.set(this.speed.get(), forced = true)
it.hasBreached.set(this.hasBreached.get(), forced = true)
it.clearMouseSmoothers.set(this.clearMouseSmoothers.get(), forced = true)
}
override fun shouldSync(previous: RidingBehaviourState): Boolean {
@@ -0,0 +1,647 @@
/*
* 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.liquid
import com.bedrockk.molang.Expression
import com.bedrockk.molang.runtime.MoLangMath.lerp
import com.cobblemon.mod.common.CobblemonRideSettings
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.api.orientation.OrientationController
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.sound.RideSoundSettingsList
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.cobblemonResource
import com.cobblemon.mod.common.util.math.geometry.toRadians
import com.cobblemon.mod.common.util.readNullableExpression
import com.cobblemon.mod.common.util.readRidingStats
import com.cobblemon.mod.common.util.resolveBoolean
import com.cobblemon.mod.common.util.resolveDouble
import com.cobblemon.mod.common.util.toVec3d
import com.cobblemon.mod.common.util.writeNullableExpression
import com.cobblemon.mod.common.util.writeRidingStats
import net.minecraft.core.BlockPos
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import net.minecraft.tags.FluidTags
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 org.joml.Vector3f
import kotlin.math.*
class SubmarineBehaviour : RidingBehaviour<SubmarineSettings, SubmarineState> {
companion object {
val KEY = cobblemonResource("liquid/submarine")
}
override val key = KEY
val globalSubmarine: SubmarineSettings
get() = CobblemonRideSettings.submarine
override fun getRidingStyle(settings: SubmarineSettings, state: SubmarineState): RidingStyle {
return RidingStyle.LIQUID
}
val poseProvider = PoseProvider<SubmarineSettings, SubmarineState>(PoseType.FLOAT)
.with(PoseOption(PoseType.SWIM) { _, state, entity -> state.rideVelocity.get().z > 0.05 && !state.onSurface.get()})
.with(PoseOption(PoseType.STAND) { _, state, entity -> state.rideVelocity.get().z <= 0.05 && state.onSurface.get()})
.with(PoseOption(PoseType.STAND) { _, state, entity -> state.rideVelocity.get().z > 0.05 && state.onSurface.get()})
override fun isActive(settings: SubmarineSettings, state: SubmarineState, vehicle: PokemonEntity): Boolean {
return vehicle.isInWater || !vehicle.onGround()
}
override fun pose(settings: SubmarineSettings, state: SubmarineState, vehicle: PokemonEntity): PoseType {
return poseProvider.select(settings, state, vehicle)
}
override fun speed(settings: SubmarineSettings, state: SubmarineState, vehicle: PokemonEntity, driver: Player): Float {
return state.speed.get().toFloat()
}
override fun tick(
settings: SubmarineSettings,
state: SubmarineState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
) {
if(vehicle.level().isClientSide) {
tickStamina(settings, state, vehicle)
surfaceCheck(settings, state, vehicle, driver)
}
}
fun tickStamina(
settings: SubmarineSettings,
state: SubmarineState,
vehicle: PokemonEntity
) {
val stam = state.stamina.get()
if (vehicle.runtime.resolveBoolean(settings.infiniteStamina ?: globalSubmarine.infiniteStamina!!)) {
return
}
// Grab the boost time in seconds and convert to ticks. Then calculate the drain rate as inversely
// proportional to the number of ticks of boost thus making a full boost take x ticks
// in short: "Stamina drains slower at higher values and also replenishes slower"
val breathTime = vehicle.runtime.resolveDouble(settings.staminaExpr ?: globalSubmarine.staminaExpr!!) * 20.0f
val stamRegenRate = 1.0f / (vehicle.runtime.resolveDouble(settings.staminaRegenTime ?: globalSubmarine.staminaRegenTime!!).toFloat() * 20.0f)
val stamDrainRate = (1.0f / breathTime).toFloat()
val driver = vehicle.controllingPassenger
val newStam = if (driver?.isEyeInFluid(FluidTags.WATER) ?: false) max(0.0f,stam - stamDrainRate)
else min(1.0f,stam + stamRegenRate)
state.stamina.set(newStam)
}
private fun surfaceCheck(
settings: SubmarineSettings,
state: SubmarineState,
vehicle: PokemonEntity,
driver: Player,
) {
// determine if the vehicle is in water.
// if it isn't then set surface flag to false and return
if (!vehicle.isInWater) {
state.onSurface.set(false)
return
}
val hitboxBreachPercentage = vehicle.runtime.resolveDouble(settings.hitboxBreachPercentage ?: globalSubmarine.hitboxBreachPercentage!!)
// Grab the position of the vehicle that when out of the water is considered breached
val vehicleBreachPoint = vehicle.position().add(0.0, vehicle.bbHeight * hitboxBreachPercentage, 0.0)
// If the block above the center is not fluid but this one is then move the center
val centerBlockPos = BlockPos.containing(vehicleBreachPoint)
val centerInWater = !vehicle.level().getFluidState(centerBlockPos).isEmpty
val abovePos = centerBlockPos.above()
val aboveInWater = !vehicle.level().getFluidState(abovePos).isEmpty
if (!centerInWater && !aboveInWater) {
state.onSurface.set(true)
} else {
state.onSurface.set(false)
}
//
// Function for if x mouse and y mouse "get through" to affecting the mounts rotation. Return a Pair<Boolean, Boolean>
}
override fun rotation(
settings: SubmarineSettings,
state: SubmarineState,
vehicle: PokemonEntity,
driver: LivingEntity
): Vec2 {
return Vec2(driver.xRot * 0.5f, driver.yRot)
}
override fun velocity(
settings: SubmarineSettings,
state: SubmarineState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
if (vehicle !is OrientationControllable) return Vec3.ZERO
val controller = (vehicle as OrientationControllable).orientationController
val orientation = controller.orientation ?: return Vec3.ZERO
val runtime = vehicle.runtime
val topSpeed = (vehicle.runtime.resolveDouble(settings.speedExpr ?: globalSubmarine.speedExpr!!) / 20.0)
val accel = (topSpeed / (vehicle.runtime.resolveDouble(settings.accelerationExpr ?: globalSubmarine.accelerationExpr!!) * 20.0))
val strafeSpeed = (runtime.resolveDouble(settings.jumpExpr ?: globalSubmarine.jumpExpr!!) / 20.0)
val strafeAccel = (strafeSpeed / (vehicle.runtime.resolveDouble(settings.accelerationExpr ?: globalSubmarine.accelerationExpr!!) * 20.0))
var currVel = state.rideVelocity.get()
/*********************************************
* Handle collisions
*********************************************/
if (vehicle.horizontalCollision || vehicle.verticalCollision) {
// not actually friction
val frictionLimit = 0.1
val postColSpeed = min(max(state.rideVelocity.get().length(), frictionLimit), state.speed.get())
state.speed.set(postColSpeed)
}
if (vehicle.verticalCollision) {
state.rideVelocity.set(Vec3(state.rideVelocity.get().x, 0.0, state.rideVelocity.get().z))
}
/*********************************************
* Calculate Forward Velocity
*********************************************/
val currZVel = state.rideVelocity.get().z
val newZVel = when {
driver.zza > 0.0 -> min(topSpeed, currZVel + accel)
driver.zza < 0.0 -> max( -strafeSpeed, currZVel - strafeAccel) //TODO: Might need to be applied in addition to the normal braking force?
else -> lerp(currZVel, 0.0, 0.05)
}
currVel = Vec3(currVel.x, currVel.y, newZVel)
/*********************************************
* Calculate up/down Velocity and gravity
*********************************************/
val vertTopSpeed = strafeSpeed
val currYVel = state.rideVelocity.get().y
val gravity = (9.8 / ( 20.0)) * 0.2 * 0.4
val terminalYVel = 2.0
val driverYInput = if(driver.jumping) 1.0 else if (driver.isShiftKeyDown) -1.0 else 0.0
val newYVel = when {
(!vehicle.isInWater) -> max(-terminalYVel, currYVel - gravity)
//(vehicle.isInWater && !vehicle.isUnderWater && currYVel > 0.001) -> lerp(currYVel, 0.0, 0.1) // Make sure that yVel is above a threshold before doing this otherwise it'll block submerging.
//driverYInput != 0.0 && currYVel > 0.001 && driverYInput.sign != currYVel.sign -> lerp(currYVel, 0.0, 0.05)
driverYInput > 0.0 -> min(vertTopSpeed, currYVel + strafeAccel)
driverYInput < 0.0 -> max(-vertTopSpeed, currYVel - strafeAccel)
else -> lerp(currYVel, 0.0, 0.05)
}
currVel = Vec3(currVel.x, newYVel, currVel.z)
/*********************************************
* Calculate left/right strafing Velocity
*********************************************/
val currXVel = state.rideVelocity.get().x
val horzTopSpeed = strafeSpeed
val newXVel = when {
driver.xxa > 0.0 -> min(horzTopSpeed, currXVel + strafeAccel)
driver.xxa < 0.0 -> max( -horzTopSpeed, currXVel - strafeAccel)
else -> lerp(currXVel, 0.0, 0.01)
}
currVel = Vec3(newXVel, currVel.y, currVel.z)
// Set the new velocity
state.rideVelocity.set(currVel)
/*********************************************
* Separate and apply velocities
*********************************************/
// If the driver and vehicles eyes are out of the water but the vehicle is still in it then move only horizontally or downwards as you have surfaced
if (state.onSurface.get()) {
val worldVelVec = Vec3(currVel.x, min(0.0, currVel.y), currVel.z)
return worldVelVec
} else {
val vertVec = Vec3(0.0, currVel.y, 0.0)
val horzVec = Vec3(currVel.x, 0.0, 0.0)
val localZVelVec = Vector3f(0.0f, 0.0f, currVel.z.toFloat() * -1.0f) // Flip the z axis to make this left handed
// Unrotate preemptively as this vector gets rotated later down the line in MC logic.
val worldVelVec = localZVelVec.mul(orientation).toVec3d().yRot(vehicle.yRot.toRadians())
// Add back the horizontal and vertical components.
return worldVelVec.add(vertVec).add(horzVec)
}
}
override fun angRollVel(
settings: SubmarineSettings,
state: SubmarineState,
vehicle: PokemonEntity,
driver: Player,
deltaTime: Double
): Vec3 {
return Vec3.ZERO
}
override fun applyRenderRotation(
settings: SubmarineSettings,
state: SubmarineState,
vehicle: PokemonEntity,
partialTicks: Float
) {
if (vehicle !is OrientationControllable) return
val controller = (vehicle as OrientationControllable).orientationController
var handling = vehicle.runtime.resolveDouble(settings.handlingExpr ?: globalSubmarine.handlingExpr!!)
val surfaceHandlingBoost = if(state.onSurface.get()) 3.0 else 1.0
handling *= surfaceHandlingBoost
val strafeSpeed = (vehicle.runtime.resolveDouble(settings.jumpExpr ?: globalSubmarine.jumpExpr!!) / 20.0)
// Grab the roll and see if its hit the limit yet. If it hasn't then trend it towards its desired roll
val maxRollDeg = if (state.onSurface.get()) 5 else 15
val maxPitchDeg = 15
var rollAmount = 0.0
var pitchAmount = 0.0
// Do horizontal speed based rolling
val maxHorzSpeed = strafeSpeed
val currHorzSpeed = state.rideVelocity.get().x
var desiredRoll = -(currHorzSpeed / maxHorzSpeed).coerceIn(-1.0, 1.0) * maxRollDeg
rollAmount += lerp(controller.roll.toDouble(), desiredRoll, partialTicks.toDouble()) - controller.roll
//val rotAmount //Maybe use some lerp?
controller.rotateRoll(rollAmount.toFloat())
// Do yawRate based rolling
val maxYawRate = handling
val currYawRate = vehicle.ridingAnimationData.rotDeltaSpring.getInterpolated(partialTicks.toDouble(), 2).y()
desiredRoll = -(currYawRate / maxYawRate).coerceIn(-1.0, 1.0) * maxRollDeg
rollAmount += desiredRoll - controller.roll
controller.rotateRoll(rollAmount.toFloat())
//TODO: why does this affect the actual controller but rolling doesn't??
// Do upwards movement based pitching
// val maxVertSpeed = 0.25 //TODO: CONNECT UP TO JUMP
// val currVertSpeed = state.rideVelocity.get().y
// var desiredPitch = -(currVertSpeed / maxVertSpeed) * maxPitchDeg
// pitchAmount += lerp(controller.pitch.toDouble(), desiredPitch, partialTicks.toDouble()) - controller.pitch
// controller.rotatePitch(pitchAmount.toFloat())
}
private fun correctOrientation(
settings: SubmarineSettings,
state: SubmarineState,
vehicle: PokemonEntity,
controller: OrientationController,
driver: Player,
deltaTime: Double
) {
// TODO: remove if not needed
// // Calculate correcting roll force.
// // If the ride is moving then have the correction rate be an equal proportion of:
// // - how rolled are you
// // - how long has it been since last mouse input (higher is stronger correction)
// // Also the correction is proportional as a whole to how NOT pitched are you so you don't spin endlessly when looking up or down
// val howRolledAmI = sqrt(abs((cos(controller.roll.toRadians()) - 1.0f) * 0.5f)) // sqrt so it trends towards one pretty quickly
// val maxRollCorrectionRate =
// if (state.rideVelocity.get().length() < 0.2) 10.0f * abs(cos(controller.pitch.toRadians()))
// else 10.0f * cos(controller.pitch.toRadians()).pow(2) * (
// howRolledAmI*1.0f +
// (state.noInputTime.get() - rollCorrectionTimer).coerceIn(0.0, 1.0).pow(2).toFloat()*0.5f
// )
// val maxRollForce = maxRollCorrectionRate / 40.0f
// val rollArrivalDeg = 60.0f
// val desiredRoll = 0.0f
//
// // Calculate the signed error (how far and in what direction)
// val rollError = Mth.wrapDegrees(desiredRoll.angleDifference(controller.roll))
//
// // Use the absolute error to calculate the magnitude of the influence
// val influenceMagnitude = (min(rollArrivalDeg, abs(rollError)) / rollArrivalDeg)
// val desiredRollForce = maxRollCorrectionRate * influenceMagnitude * -rollError.sign
// var steeredRollForce = desiredRollForce - state.currRollCorrectionForce.get()
//
// // Ensure the 'steering' correction force doesn't exceed our maxForce
// if (abs(steeredRollForce) > maxRollForce) {
// steeredRollForce = steeredRollForce.sign * maxRollForce
// }
//
// // Apply the 'steered' force (its really not a steering force I need a better name)
// state.currRollCorrectionForce.set(
// state.currRollCorrectionForce.get() + steeredRollForce
// )
//Calculate correcting pitch force.
// val rollCorrectionTimer = vehicle.runtime.resolveDouble(settings.timeToRollCorrect ?: globalSubmarine.timeToRollCorrect!!)
// val maxPitchCorrectionRate = 5.0f * (state.noInputTime.get() - rollCorrectionTimer).coerceIn(0.0, 1.0).pow(2).toFloat()*0.5f
// val maxPitchForce = maxPitchCorrectionRate / 40.0f
// val pitchArrivalDeg = 45.0f
// val desiredPitch = 0.0f
//
// if(state.rideVelocity.get().length() < 0.1 && state.noInputTime.get() > rollCorrectionTimer) {
// val pitchErrror = Mth.wrapDegrees(desiredPitch.angleDifference(controller.pitch))
// val arrivalInfluence = (min(pitchArrivalDeg,abs(pitchErrror)) / pitchArrivalDeg)
// val desiredPitchForce = maxPitchCorrectionRate * arrivalInfluence * -pitchErrror.sign
// var steeredPitchForce = desiredPitchForce - state.currPitchCorrectionForce.get()
//
// // Ensure the 'steering' correction force doesn't exceed our maxForce
// if (abs(steeredPitchForce) > maxPitchForce) {
// steeredPitchForce = steeredPitchForce.sign * maxPitchForce
// }
//
// // Apply the 'steered' force (its really not a steering force I need a better name)
// state.currPitchCorrectionForce.set(
// state.currPitchCorrectionForce.get() + steeredPitchForce
// )
// } else {
// state.currPitchCorrectionForce.set(
// //reduce rollCorrection gradually
// lerp(state.currPitchCorrectionForce.get(), 0.0, deltaTime * 0.98)
// )
// }
}
override fun rotationOnMouseXY(
settings: SubmarineSettings,
state: SubmarineState,
vehicle: PokemonEntity,
driver: Player,
mouseY: Double,
mouseX: Double,
mouseYSmoother: SmoothDouble,
mouseXSmoother: SmoothDouble,
sensitivity: Double,
deltaTime: Double
): Vec3 {
if (vehicle !is OrientationControllable) return Vec3.ZERO
val controller = (vehicle as OrientationControllable).orientationController
// Set roll to zero if transitioning to noroll config
controller.rotateRoll(controller.roll * -1.0f)
val handling = vehicle.runtime.resolveDouble(settings.handlingExpr ?: globalSubmarine.handlingExpr!!)
val surfaceHandlingBoost = if(state.onSurface.get()) 3.0 else 1.0
val effHandling = if(state.onSurface.get()) 1000 * deltaTime else handling * deltaTime
//Smooth out mouse input.
val smoothingSpeed = if(state.onSurface.get()) 10.0 else 2.0
val mouseXc = (mouseX * 0.1).coerceIn(-effHandling, effHandling)
val mouseYc = (mouseY * 0.1).coerceIn(-effHandling, effHandling)
val xInput = mouseXSmoother.getNewDeltaValue(mouseXc, deltaTime * smoothingSpeed);
val yInput = mouseYSmoother.getNewDeltaValue(mouseYc, deltaTime * smoothingSpeed);
// Give the ability to yaw with x mouse input when at low speeds.
val yawForce = xInput
// Apply yaw globally as we don't want roll or pitch changes due to local yaw when looking up or down.
controller.applyGlobalYaw(yawForce.toFloat())
var pitchRot = yInput
// Pitch up globally
val pitchLimit = 45.0
if (abs(controller.pitch + pitchRot) >= pitchLimit ) {
pitchRot = 0.0
mouseYSmoother.reset()
} else {
controller.applyGlobalPitch(pitchRot.toFloat())
}
// If on the surface then conditionally bring the pitch to 0 degrees.
if (state.onSurface.get()) {
//move back at max 30 degrees per second.
val maxCorrectionSpeed = 30.0
val arrivalDegreeWidth = 30.0
val currentPitch = controller.pitch
val correctionDegrees = (-currentPitch / arrivalDegreeWidth).coerceIn(-1.0, 1.0) * maxCorrectionSpeed * deltaTime
controller.applyGlobalPitch(correctionDegrees.toFloat())
// Compensate player ride rotations
// val playerRotater = driver as RideRotation
// playerRotater.`cobblemon$setRideXRot`(playerRotater.`cobblemon$getRideXRot`() - correctionDegrees.toFloat() )
}
//yaw, pitch, roll
return Vec3.ZERO
}
override fun canJump(
settings: SubmarineSettings,
state: SubmarineState,
vehicle: PokemonEntity,
driver: Player
): Boolean {
return false
}
override fun setRideBar(
settings: SubmarineSettings,
state: SubmarineState,
vehicle: PokemonEntity,
driver: Player
): Float {
return (state.stamina.get() / 1.0f)
}
override fun jumpForce(
settings: SubmarineSettings,
state: SubmarineState,
vehicle: PokemonEntity,
driver: Player,
jumpStrength: Int
): Vec3 {
return Vec3.ZERO
}
override fun gravity(
settings: SubmarineSettings,
state: SubmarineState,
vehicle: PokemonEntity,
regularGravity: Double
): Double {
return 0.0
}
override fun rideFovMultiplier(
settings: SubmarineSettings,
state: SubmarineState,
vehicle: PokemonEntity,
driver: Player
): Float {
return 1.0f
}
override fun useAngVelSmoothing(settings: SubmarineSettings, state: SubmarineState, vehicle: PokemonEntity): Boolean {
return false
}
override fun useRidingAltPose(
settings: SubmarineSettings,
state: SubmarineState,
vehicle: PokemonEntity,
driver: Player
): ResourceLocation {
return cobblemonResource("no_pose")
}
override fun inertia(settings: SubmarineSettings, state: SubmarineState, vehicle: PokemonEntity): Double {
return if (!vehicle.isUnderWater) 1.0 else 0.1
}
override fun shouldRoll(settings: SubmarineSettings, state: SubmarineState, vehicle: PokemonEntity): Boolean {
return true
}
override fun turnOffOnGround(settings: SubmarineSettings, state: SubmarineState, vehicle: PokemonEntity): Boolean {
return false
}
override fun dismountOnShift(settings: SubmarineSettings, state: SubmarineState, vehicle: PokemonEntity): Boolean {
return false
}
override fun mouseModifiesDriverRotation(settings: SubmarineSettings, state: SubmarineState, vehicle: PokemonEntity): Pair<Boolean, Boolean> {
return Pair(false, state.onSurface.get())
}
override fun shouldRotatePokemonHead(
settings: SubmarineSettings,
state: SubmarineState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun shouldRotateRiderHead(
settings: SubmarineSettings,
state: SubmarineState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun getRideSounds(
settings: SubmarineSettings,
state: SubmarineState,
vehicle: PokemonEntity
): RideSoundSettingsList {
return settings.rideSounds
}
override fun createDefaultState(settings: SubmarineSettings) = SubmarineState()
}
class SubmarineSettings : RidingBehaviourSettings {
override val key = SubmarineBehaviour.KEY
override val stats = mutableMapOf<RidingStat, IntRange>()
var infiniteStamina: Expression? = null
private set
var staminaRegenTime: Expression? = null
private set
// Percentage (0.0->1.0) of ride needing to be breached out of the water to be considered surfaced.
var hitboxBreachPercentage: Expression? = null
private set
// Tope strafe speed in Bl/s
var jumpExpr: Expression? = null
// Yaw rate in degrees per second
var handlingExpr: Expression? = null
// Top speed in Bl/s
var speedExpr: Expression? = null
// Seconds to get to top speed
var accelerationExpr: Expression? = null
// Seconds of air provided by mount
var staminaExpr: Expression? = null
var rideSounds: RideSoundSettingsList = RideSoundSettingsList()
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeRidingStats(stats)
rideSounds.encode(buffer)
buffer.writeNullableExpression(infiniteStamina)
buffer.writeNullableExpression(staminaRegenTime)
buffer.writeNullableExpression(hitboxBreachPercentage)
buffer.writeNullableExpression(jumpExpr)
buffer.writeNullableExpression(handlingExpr)
buffer.writeNullableExpression(speedExpr)
buffer.writeNullableExpression(accelerationExpr)
buffer.writeNullableExpression(staminaExpr)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
stats.putAll(buffer.readRidingStats())
rideSounds = RideSoundSettingsList.decode(buffer)
infiniteStamina = buffer.readNullableExpression()
staminaRegenTime = buffer.readNullableExpression()
hitboxBreachPercentage = buffer.readNullableExpression()
jumpExpr = buffer.readNullableExpression()
handlingExpr = buffer.readNullableExpression()
speedExpr = buffer.readNullableExpression()
accelerationExpr = buffer.readNullableExpression()
staminaExpr = buffer.readNullableExpression()
}
}
class SubmarineState : RidingBehaviourState() {
var speed = ridingState(0.0, Side.CLIENT)
var currRollCorrectionForce = ridingState(0.0, Side.CLIENT)
var currPitchCorrectionForce = ridingState(0.0, Side.CLIENT)
var noInputTime = ridingState(0.0, Side.CLIENT)
var onSurface = ridingState(false, Side.BOTH)
override fun encode(buffer: FriendlyByteBuf) {
super.encode(buffer)
buffer.writeBoolean(onSurface.get())
}
override fun decode(buffer: FriendlyByteBuf) {
super.decode(buffer)
onSurface.set(buffer.readBoolean(), forced = true)
}
override fun reset() {
super.reset()
speed.set(0.0, forced = true)
currRollCorrectionForce.set(0.0, forced = true)
currPitchCorrectionForce.set(0.0, forced = true)
noInputTime.set(0.0, forced = true)
onSurface.set(false, forced = true)
}
override fun copy() = SubmarineState().also {
it.stamina.set(stamina.get(), forced = true)
it.speed.set(speed.get(), forced = true)
it.rideVelocity.set(rideVelocity.get(), forced = true)
it.currRollCorrectionForce.set(this.currRollCorrectionForce.get(), forced = true)
it.currPitchCorrectionForce.set(this.currPitchCorrectionForce.get(), forced = true)
it.noInputTime.set(this.noInputTime.get(), forced = true)
it.onSurface.set(this.onSurface.get(), forced = true)
}
override fun shouldSync(previous: RidingBehaviourState): Boolean {
if (previous !is SubmarineState) return false
if (previous.onSurface.get() != onSurface.get()) return true
return super.shouldSync(previous)
}
}
@@ -11,6 +11,7 @@ package com.cobblemon.mod.common.api.riding.stats
import com.cobblemon.mod.common.api.cooking.Flavour
import com.cobblemon.mod.common.util.lang
import net.minecraft.network.chat.Component
import net.minecraft.network.chat.MutableComponent
/**
* A stat that is used for riding Pokémon. The exact implementation of each of these depends on the [com.cobblemon.mod.common.api.riding.controller.RideController].
@@ -29,10 +30,9 @@ enum class RidingStat(
companion object {
fun getByName(name: String) = RidingStat.entries.firstOrNull { it.name.equals(name, ignoreCase = true) }
fun getByFlavour(flavour: Flavour) = RidingStat.entries.firstOrNull { it.flavour == flavour }
}
val displayName: Component
get() = lang("riding.stat.${name.lowercase()}.name")
val description: Component
get() = lang("riding.stat.${name.lowercase()}.desc")
val displayName: MutableComponent
get() = lang("ui.stats.ride.${name.lowercase()}")
}
@@ -1,70 +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.stats
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.net.IntSize
import com.cobblemon.mod.common.util.readSizedInt
import com.cobblemon.mod.common.util.readText
import com.cobblemon.mod.common.util.writeSizedInt
import com.cobblemon.mod.common.util.writeText
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.network.chat.Component
/**
* The definition of all the different information related to a specific [RidingStat] on a Pokémon species.
*
* @author Hiroku
* @since February 17th, 2025
*/
class RidingStatDefinition {
// This object gets read from a JSON adapter manually (field-by-field) so don't add fields without updating the adapter.
/** A map from [RidingStyle] to a minimum and maximum value for this stat. */
val ranges = mutableMapOf<RidingStyle, IntRange>()
/** Display name for the stat, if null falls back to [RidingStat.displayName]. */
var displayName: Component? = null
/** Description for the stat, if null falls back to [RidingStat.description]. */
var description: Component? = null
fun calculate(style: RidingStyle, aprijuiceBoost: Int): Float {
val range = ranges[style] ?: ranges[RidingStyle.LAND] ?: return 0F
val boostQuotient = aprijuiceBoost / 255F
return range.first + (range.last - range.first) * boostQuotient
}
fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeNullable(displayName) { _, it -> buffer.writeText(it) }
buffer.writeNullable(description) { _, it -> buffer.writeText(it) }
buffer.writeMap(
ranges,
{ _, it -> buffer.writeEnum(it) },
{ _, it ->
buffer.writeSizedInt(IntSize.U_BYTE, it.first)
buffer.writeSizedInt(IntSize.U_BYTE, it.last)
}
)
}
companion object {
fun decode(buffer: RegistryFriendlyByteBuf): RidingStatDefinition {
val displayName = buffer.readNullable { buffer.readText() }
val description = buffer.readNullable { buffer.readText() }
val ranges = buffer.readMap<RidingStyle, IntRange>(
{ buffer.readEnum<RidingStyle>(RidingStyle::class.java) },
{ IntRange(buffer.readSizedInt(IntSize.U_BYTE), buffer.readSizedInt(IntSize.U_BYTE)) }
)
return RidingStatDefinition().apply {
this.displayName = displayName
this.description = description
this.ranges.putAll(ranges)
}
}
}
}
@@ -74,7 +74,7 @@ class RidingAnimationData(val ride: PokemonEntity)
}
if( r ) {
//TODO: should this return?
val controller = (ride.firstPassenger as? OrientationControllable)?.orientationController ?: return
val controller = (ride as? OrientationControllable)?.orientationController ?: return
val orientation = controller.orientation ?: Matrix3f()
val currPitch = controller.pitch
@@ -18,18 +18,12 @@ import com.cobblemon.mod.common.api.spawning.condition.SeafloorSpawningCondition
import com.cobblemon.mod.common.api.spawning.condition.SpawningCondition
import com.cobblemon.mod.common.api.spawning.condition.SubmergedSpawningCondition
import com.cobblemon.mod.common.api.spawning.condition.SurfaceSpawningCondition
import com.cobblemon.mod.common.api.spawning.position.calculators.GroundedSpawnablePositionCalculator
import com.cobblemon.mod.common.api.spawning.position.calculators.LavafloorSpawnablePositionCalculator
import com.cobblemon.mod.common.api.spawning.position.calculators.SeafloorSpawnablePositionCalculator
import com.cobblemon.mod.common.api.spawning.position.calculators.SpawnablePositionCalculator
import com.cobblemon.mod.common.api.spawning.position.calculators.SubmergedSpawnablePositionCalculator
import com.cobblemon.mod.common.api.spawning.position.calculators.SurfaceSpawnablePositionCalculator
import com.cobblemon.mod.common.api.spawning.detail.NPCSpawnDetail
import com.cobblemon.mod.common.api.spawning.detail.PokemonHerdSpawnDetail
import com.cobblemon.mod.common.api.spawning.detail.PokemonSpawnDetail
import com.cobblemon.mod.common.api.spawning.detail.SpawnAction
import com.cobblemon.mod.common.api.spawning.detail.SpawnDetail
import com.cobblemon.mod.common.api.spawning.fishing.FishingSpawner
import com.cobblemon.mod.common.api.spawning.influence.BucketMultiplyingInfluence
import com.cobblemon.mod.common.api.spawning.influence.SpawningInfluence
import com.cobblemon.mod.common.api.spawning.position.AreaSpawnablePositionResolver
import com.cobblemon.mod.common.api.spawning.position.FishingSpawnablePosition
@@ -39,16 +33,21 @@ import com.cobblemon.mod.common.api.spawning.position.SeafloorSpawnablePosition
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
import com.cobblemon.mod.common.api.spawning.position.SubmergedSpawnablePosition
import com.cobblemon.mod.common.api.spawning.position.SurfaceSpawnablePosition
import com.cobblemon.mod.common.api.spawning.position.calculators.GroundedSpawnablePositionCalculator
import com.cobblemon.mod.common.api.spawning.position.calculators.LavafloorSpawnablePositionCalculator
import com.cobblemon.mod.common.api.spawning.position.calculators.SeafloorSpawnablePositionCalculator
import com.cobblemon.mod.common.api.spawning.position.calculators.SpawnablePositionCalculator
import com.cobblemon.mod.common.api.spawning.position.calculators.SubmergedSpawnablePositionCalculator
import com.cobblemon.mod.common.api.spawning.position.calculators.SurfaceSpawnablePositionCalculator
import com.cobblemon.mod.common.api.spawning.preset.BasicSpawnDetailPreset
import com.cobblemon.mod.common.api.spawning.preset.BestSpawnerConfig
import com.cobblemon.mod.common.api.spawning.preset.PokemonSpawnDetailPreset
import com.cobblemon.mod.common.api.spawning.selection.SpawningSelector
import com.cobblemon.mod.common.api.spawning.spawner.AreaSpawner
import com.cobblemon.mod.common.api.spawning.spawner.BasicSpawner
import com.cobblemon.mod.common.api.spawning.spawner.FixedAreaSpawner
import com.cobblemon.mod.common.api.spawning.spawner.PlayerSpawner
import com.cobblemon.mod.common.api.spawning.spawner.PlayerSpawnerFactory
import com.cobblemon.mod.common.api.spawning.spawner.Spawner
import com.cobblemon.mod.common.api.spawning.spawner.TickingSpawner
import com.cobblemon.mod.common.entity.pokemon.CobblemonAgingDespawner
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import net.minecraft.server.MinecraftServer
@@ -66,14 +65,13 @@ import net.minecraft.server.MinecraftServer
* In the case of more specialized use, the creation of a [SpawnablePosition] that motivates most of the spawn
* process can be created manually, skipping the first two steps.
*
* An individually spawnable entity is defined as a [SpawnDetail]. A processor handling this process is a [Spawner].
* Various subclasses exist for more specialized cases. A spawner that is constantly ticking and will spawn things
* without prompts is a [TickingSpawner], and one of those which occurs within a defined area is a [AreaSpawner]. If
* An individually spawnable thing is defined as a [SpawnDetail]. A processor handling this process is a [Spawner].
* Some subclasses exist for more specialized cases. If you are spawning in a fixed area and the spawner controlling
* that area is unmoving then it is a [FixedAreaSpawner] whereas if it is actively following the player it is a
* [PlayerSpawner].
* [PlayerSpawner]. You can also use a [BasicSpawner] for more general-purpose spawning without any issue.
*
* Spawning is coordinated and ticked using a [SpawnerManager], and all the current managers are accessible from
* [BestSpawner.spawnerManagers].
* PlayerSpawners are stored inside ServerPlayers using a mixin and tick at the end of a player's tick. The [PlayerSpawnerFactory]
* is used to build those. An extension function exists for getting the spawner for a player.
*
* Spawners and spawnable positions are often put under the effects of [SpawningInfluence]s which can be used to make
* temporary or lasting changes to spawning for whatever component they are attached to (whether that is a spawner or a
@@ -87,9 +85,9 @@ import net.minecraft.server.MinecraftServer
*/
object BestSpawner {
var config = BestSpawnerConfig()
val spawnerManagers = mutableListOf<SpawnerManager>(CobblemonWorldSpawnerManager)
lateinit var defaultPokemonDespawner: Despawner<PokemonEntity>
lateinit var fishingSpawner: FishingSpawner
lateinit var fishingSpawner: BasicSpawner
fun init() {
LOGGER.info("Starting the Best Spawner...")
@@ -137,12 +135,23 @@ object BestSpawner {
fun reloadConfig() {
loadConfig()
spawnerManagers.forEach(SpawnerManager::onConfigReload)
}
fun onServerStarted(server: MinecraftServer) {
CobblemonSpawnPools.onServerLoad(server)
spawnerManagers.forEach(SpawnerManager::onServerStarted)
fishingSpawner = FishingSpawner()
fishingSpawner = BasicSpawner(
name = "fishing",
spawnPool = CobblemonSpawnPools.WORLD_SPAWN_POOL
).also {
it.influences.add(
BucketMultiplyingInfluence(
multipliers = mapOf(
"uncommon" to 2.25f,
"rare" to 5.5f,
"ultra-rare" to 5.5f,
)
)
)
}
}
}
@@ -40,7 +40,6 @@ object CobblemonSpawnPools {
fun onServerLoad(server: MinecraftServer) {
Cobblemon.LOGGER.info("Optimizing spawn pools...")
WORLD_SPAWN_POOL.forEach { it.onServerLoad(server) }
WORLD_SPAWN_POOL.precalculate()
WORLD_SPAWN_POOL.onServerLoad(server)
}
}
@@ -1,76 +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.spawning
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.spawning.spawner.PlayerSpawner
import com.cobblemon.mod.common.api.spawning.spawner.PlayerSpawnerFactory
import com.cobblemon.mod.common.platform.events.PlatformEvents
import com.cobblemon.mod.common.world.gamerules.CobblemonGameRules
import net.minecraft.server.level.ServerLevel
import java.util.UUID
import net.minecraft.server.level.ServerPlayer
/**
* The [SpawnerManager] that handles Cobblemon's world spawner, which is made up
* of [PlayerSpawner] instances. This manager listens for login and logout and
* cleans up where relevant.
*
* @author Hiroku
* @since February 14th, 2022
*/
object CobblemonWorldSpawnerManager : SpawnerManager() {
var spawnersForPlayers = mutableMapOf<UUID, PlayerSpawner>()
init {
PlatformEvents.SERVER_PLAYER_LOGIN.subscribe { this.onPlayerLogin(it.player) }
PlatformEvents.SERVER_PLAYER_LOGOUT.subscribe { this.onPlayerLogout(it.player) }
PlatformEvents.CHANGE_DIMENSION.subscribe { this.onDimensionChange(it.player, it.origin, it.destination) }
}
fun onPlayerLogin(player: ServerPlayer) {
// Disables spawning
if (!Cobblemon.config.enableSpawning || player.level().gameRules.getBoolean(CobblemonGameRules.DO_POKEMON_SPAWNING) == false) {
return;
}
registerPlayerSpawner(player)
}
fun onPlayerLogout(player: ServerPlayer) {
unregisterPlayerSpawner(player)
}
fun onDimensionChange(player: ServerPlayer, origin: ServerLevel?, destination: ServerLevel?) {
fun getSpawnRule(level: ServerLevel?) = level?.gameRules?.getBoolean(CobblemonGameRules.DO_POKEMON_SPAWNING)
if (!Cobblemon.config.enableSpawning || getSpawnRule(origin) == getSpawnRule(destination)) {
return
}
val spawnerAction = if (destination?.gameRules?.getBoolean(CobblemonGameRules.DO_POKEMON_SPAWNING) == false) {
::unregisterPlayerSpawner
} else {
::registerPlayerSpawner
}
spawnerAction(player)
}
private fun unregisterPlayerSpawner(player: ServerPlayer) {
spawnersForPlayers.remove(player.uuid)?.let { unregisterSpawner(it) }
}
private fun registerPlayerSpawner(player: ServerPlayer) {
PlayerSpawnerFactory.create(this, player).also {
spawnersForPlayers[player.uuid] = it
registerSpawner(it)
}
}
}
@@ -17,16 +17,21 @@ import net.minecraft.world.entity.EntityType
open class SpawnCause(
val spawner: Spawner,
entity: Entity? = null
): SpawningInfluence {
) : SpawningInfluence {
val entityWorldId = entity?.level()?.dimension()
val entityId = entity?.id
val entityUUID = entity?.uuid
val entityType = entity?.type
val entity: Entity?
get() = if (entityType == EntityType.PLAYER) {
server()?.playerList?.getPlayer(entityUUID!!)
} else {
server()?.getLevel(entityWorldId!!)?.getEntity(entityId!!)
}
get() =
if (entityType == EntityType.PLAYER) {
entityUUID?.let {
server()?.playerList?.getPlayer(it)
}
} else if (entityWorldId != null && entityId != null) {
server()?.getLevel(entityWorldId)?.getEntity(entityId)
} else {
null
}
}
@@ -1,85 +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.spawning
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.spawning.influence.SpawningInfluence
import com.cobblemon.mod.common.api.spawning.spawner.PlayerSpawner
import com.cobblemon.mod.common.api.spawning.spawner.Spawner
import com.cobblemon.mod.common.api.spawning.spawner.TickingSpawner
import com.cobblemon.mod.common.util.server
import com.cobblemon.mod.common.world.gamerules.CobblemonGameRules.DO_POKEMON_SPAWNING
/**
* A manager of various spawners. This is a class in which you should register
* ticking spawners so that they can be automatically ticked by the server.
* This also supports registering [SpawningInfluence]s to apply to each spawner
* that is registered.
*
* @author Hiroku
* @since February 5th, 2022
*/
open class SpawnerManager {
val spawners = mutableListOf<Spawner>()
get() {
if (server()?.isSameThread == false) {
Cobblemon.LOGGER.error("Illegal access to spawners list from non-server thread!")
Exception().printStackTrace()
}
return field
}
val influences = mutableListOf<SpawningInfluence>()
get() {
if (server()?.isSameThread == false) {
Cobblemon.LOGGER.error("Illegal access to influences from non-server thread!")
Exception().printStackTrace()
}
return field
}
inline fun <reified T : Spawner> getSpawnersOfType() = spawners.filterIsInstance<T>()
open fun getSpawnerByName(name: String) = spawners.find { it.name == name }
open fun registerSpawner(spawner: Spawner) {
spawners.add(spawner)
if (spawner !is TickingSpawner) {
spawner.influences.addAll(influences)
}
}
open fun unregisterSpawner(spawner: Spawner) {
spawners.remove(spawner)
if (spawner !is TickingSpawner) {
spawner.influences.removeAll(influences)
}
}
open fun onConfigReload() {
spawners.filterIsInstance<PlayerSpawner>().forEach {
it.ticksBetweenSpawns = Cobblemon.config.ticksBetweenSpawnAttempts
}
}
open fun onServerStarted() {
spawners.clear()
}
open fun onServerTick() {
// Disables spawning
if (!Cobblemon.config.enableSpawning) {
return
}
influences.removeIf { it.isExpired() }
getSpawnersOfType<TickingSpawner>().forEach {
if (it.getCauseEntity()?.level()?.gameRules?.getBoolean(DO_POKEMON_SPAWNING) == false) {
return@forEach
}
it.tick()
}
}
}
@@ -33,15 +33,15 @@ class TimeRange : IntRanges {
"any" to TimeRange(0..23999),
"day" to TimeRange(23460..23999, 0..12541),
"night" to TimeRange(12542..23459),
"noon" to TimeRange(5000..6999),
"midnight" to TimeRange(17000..18999),
"dawn" to TimeRange(22300..23999, 0..999),
"dusk" to TimeRange(11834..13701),
"twilight" to TimeRange(11834..13701, 22300..23999, 0..166),
"morning" to TimeRange(23000..23999, 0..4999),
"noon" to TimeRange(5000..6999),
"afternoon" to TimeRange(7000..12999),
"predawn" to TimeRange(19000..22999),
"evening" to TimeRange(13000..16999)
"evening" to TimeRange(13000..16999),
"midnight" to TimeRange(17000..18999),
"predawn" to TimeRange(19000..22999),
"dawn" to TimeRange(22300..23999, 0..166),
"dusk" to TimeRange(11834..13701),
"twilight" to TimeRange(11834..13701, 22300..23999, 0..166)
)
}
@@ -39,9 +39,13 @@ import net.minecraft.server.MinecraftServer
*/
abstract class SpawnDetail : ModDependant {
companion object {
val pokemonTypes = mutableListOf<String>()
val spawnDetailTypes = mutableMapOf<String, RegisteredSpawnDetail<*>>()
fun <T : SpawnDetail> registerSpawnType(name: String, detailClass: Class<T>) {
spawnDetailTypes[name] = RegisteredSpawnDetail(detailClass)
if (detailClass == PokemonSpawnDetail::class.java || detailClass == PokemonHerdSpawnDetail::class.java) {
pokemonTypes.add(name)
}
}
}
@@ -24,6 +24,7 @@ import com.cobblemon.mod.common.util.server
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.MinecraftServer
import net.minecraft.server.level.ServerPlayer
import net.minecraft.server.packs.PackType
@@ -33,7 +34,7 @@ import net.minecraft.server.packs.PackType
* A single spawn pool may be used for many different [Spawner]s. Note that changing the
* [details] list will change the spawns for any [Spawner] sharing this pool. If you want
* to make a change for a pool specifically to one spawner, take a copy of the pool using
* [copy], and change that spawner's pool using [Spawner.setSpawnPool].
* [copy], and change that spawner's pool using [Spawner.spawnPool] setters.
*
* @author Hiroku
* @since February 9th, 2022
@@ -72,6 +73,13 @@ class SpawnPool(val name: String) : JsonDataRegistry<SpawnSet>, Iterable<SpawnDe
return this
}
fun onServerLoad(server: MinecraftServer) {
for (detail in details) {
detail.onServerLoad(server)
}
precalculate()
}
/**
* Precalculates spawns into hash mappings using the [precalculators] included
* in this pool as well as the range of spawnable positions mentioned in the pool.

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