Compare commits

..
Author SHA1 Message Date
jackowes da73fd5616 Velocity based motion sick cam (WIP) 2025-11-03 00:45:38 -06:00
jackowes 729485f540 - Make stamina regen rate a setting and consistent no matter the stamina pool (currently 5 seconds)
- Sync passenger Rotation info with the server.
- Fix arrow spawning on rollable rides (still needs fixed for non rolling rides)
2025-10-31 13:20:37 -05:00
jackowes 586fe259ad Fixed passenger support. 2025-10-31 01:17:52 -05:00
jackowes 49cc86331f A few subamrine tweaks 2025-10-31 00:45:48 -05:00
jackowes b923bcad50 Add per axis mouse input handling to whether the input is used for driver rotation or vehicle rotation. Add better checks for submarine surface calculation. Add smooth transition from underwater to surface breaching. WIP still. 2025-10-29 22:57:05 -05:00
jackowes b49cad021e Fix player xRot and yRot values. Fixes the view angle issues when on a rollable ride. Began fixing eyeheight and projectile spawning locations. 2025-10-29 15:34:14 -05:00
Carmendar 78c733e1c1 Updated Dolphin and Submarine expressions. 2025-10-29 03:07:47 -05:00
jackowes 2825e13e7a - Make it so only forward movement gives the swim posetype.
- increase the downward angle needed to submerge by inputting fowards (could change to only crouch)
- Add rolling into turns for more fluidity.
- Tie in jump stat to strafing speed.
- temp fix for passenger seat rotation on blastoise.
- Cleanup unused submarine settings.
2025-10-26 21:42:41 -05:00
jackowes bcfd4cad49 - Submarine breaching logic refined
- Submarine breathe drain through stamina implemented
- supplied a hook to the controllers so that rotations may be set using partialticks alongside rendering
- Temporarily adjusted blastoises seating rotations
- Added beginnings of leaning animations through submarine.
2025-10-26 11:57:41 -05:00
jackowes 037dd79a79 Got most of submarine done. 2025-10-26 00:30:16 -05:00
jackowes fc3da8a476 - Fix dolphin stationary orientation righting by damping it with pitch.
- Fix yaw axis flip when stationary by performing a blend between local roll and local yaw instead of globalyaw.
2025-10-25 12:42:50 -05:00
jackowes 46eac875e7 - make pitch smoothing... smoother
- no orientation correction when breached
- Increase breaching angle range.
- Yaw was backwards when upside down and breached. Fixed that.
- Allow for more rotation momentum gain during breaching. (better flips)
- zero mouse smoothers on entering water so as not to carry over yaw momentum from breach into roll momentum.
2025-10-24 17:33:30 -05:00
jackowes 8725f5c8b9 Fixed:
- sustained downward force after beaching and then reentering the water
- Crash on a server when mounting dolphin
- Fixed passenger fov jitter (I think this is where a lot of the weirdness for the passenger was actually coming from)
2025-10-24 16:19:00 -05:00
jackowes f2c4ebef7c - Fix angle sweetspot on banking to be wider and easier to hit. 2025-10-24 01:32:38 -05:00
jackowes d228da9eb5 - Constant adjusted roll correction on bird.
- Trend dolphin roll towards zero when rolling to keep it upright more naturally
- Yaw when breached instead of roll
- Adjust roll correction completely on dolphin
- Make sprint debuffs more forgiving.
- Fixed swim animation not playing
- Fix hover animation not playing
- Improve bird roll handling to keep more upright
- Fix breaching so it doesn't air strafe
2025-10-24 01:14:32 -05:00
jackowes f30209baf5 Make out of water jump not strafe you 2025-10-23 00:18:10 -05:00
jackowes 70d316b990 Start of dolphin fixes and changes 2025-10-22 22:03:41 -05:00
jackowes b3c56488d8 Fix the menued mouse handling I broke. Improve orientation correction and also make it rely onu frametimes. 2025-10-20 20:24:21 -05:00
jackowes 1b06085772 Try and consolidate something then realize I am dumb. But some cleanup and fixes to the new orientation use when in reverse nonetheless. 2025-10-18 16:33:02 -05:00
jackowes 7e9a9c6dd9 Merge branch 'main' into orientation-refactor 2025-10-18 13:36:18 -05:00
jackowes eb38284e63 Fix mistake in passenger rolling transition. 2025-10-18 11:39:11 -05:00
jackowes 54095d535a Fix passenger transition to and from riding and active rolling mount. 2025-10-18 11:21:31 -05: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
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
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
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
jackowes 3944aaefd0 reorganize 2025-10-15 23:57:56 -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
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
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
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
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
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
jackowes 3487617e1d Smooth transitions to and from rollables.
TODO:
- passengers need to align their x and yrots properly as they mount a ride and as the ride transtitions to and from a rollable controller.
- Clean up current functionality
2025-10-11 21:04:54 -05:00
Paul 76fdd1fa98 No more tricky riding pokemon after battles start 2025-10-11 21:04:59 +10: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
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
jackowes 9c7aba7481 Have I made it worse? Idk, tune in next time to probably not find out either.
When the controller is not active do not allow changes from the base rotation function to be applied in the ridetick. Still so jank right now but that might have everything to do with how the camera is reset in MountedCameraRenderer
2025-10-07 21:49:57 -05:00
JazzMcNade 20b7eb17f3 simplify 2025-10-07 00:52:36 -05: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
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
Nadja Reitzenstein 83aba7e4a6 Make RideSettingsSyncHandler be correctly sided 2025-10-02 18:57:37 +02: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
Carmendar 4450164d9c Garchomp can swim in liquid water now. 2025-09-28 23:56:10 -05:00
Carmendar b99899353c Merge branch 'main' into implement_pokemon 2025-09-28 22:39:22 -05:00
jackowes e4e0846a24 Smooth out transition to rollables. Transition from rollables to non rollables is broken. 2025-09-28 15:27:12 -05:00
violet 608b711833 minor fix to exeggutor's animations 🥚 2025-09-28 00:59:44 -04:00
jackowes 7d70af44ba WIP multi passenger rolling support (it works but will likely need a better smoothing function eventually)
Still to do:
- zero driver rotx and roty when mounting.
- Fix the odd camera transition when going from rollable to non rollable (kick out of rollable camera logic earlier probably)
- position logic for freelook camera is wrong in reverse 3rd person.
2025-09-27 23:59:39 -05: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
jackowes 7c8fcdcd48 Freelook support (position logic for 3rd person no view bob still needs fixed 2025-09-27 11:54:23 -05:00
Carmendar 03edb5a385 Merge branch 'main' into implement_pokemon 2025-09-27 02:47:44 -05:00
Carmendar cf77713860 No more Hydreigon quirks while flying. 2025-09-27 01:08:41 -05: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
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
JazzMcNade a9ceddd94a Clean up, disabled hive storage for non-wild bees 2025-09-25 00:50:04 -05:00
jackowes 67684ff9e8 Merge branch 'main' into orientation-refactor
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/api/riding/behaviour/types/air/BirdBehaviour.kt
2025-09-24 21:32:45 -05:00
jackowes 7144aa8dce split orientations 2025-09-24 21:31:14 -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
jackowes 6801035c1d messing around with some temp changes 2025-09-23 20:18:22 -05:00
Jerzykv 1a30109f1c Galarian Farfetch'd can no longer fly 2025-09-23 15:03:21 +02:00
violet 5088497575 MANECTRIC. 2025-09-21 18:55:04 -04:00
JazzMcNade 31129ef14d Merge branch 'main' into Bee-Polite-Bee-Efficient 2025-09-20 19:44:59 -05:00
JazzMcNade e80eb91e69 Refactored Combees to only pass through Saccharine Leaves 2025-09-08 01:03:31 -05:00
JazzMcNade 2a295a8796 Merge branch 'main' into Bee-Polite-Bee-Efficient 2025-09-07 03:55:50 -05:00
JazzMcNade 245226f4f6 Refresh/delete hive memory on exiting a hive 2025-09-02 20:31:13 -05:00
JazzMcNade d3e1c2c79d Merge branch 'main' into Bee-Polite-Bee-Efficient 2025-09-02 01:34:58 -05:00
JazzMcNade e04e82c6de Added combee aggression for defending hives, more refactoring 2025-08-27 00:45:12 -05: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
JazzMcNade 3f18e7c0bf Combees may pass through leaves to deposit honey in saccharine leaves. 2025-08-19 23:37:24 -05: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
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
JazzMcNade ec3e0bfd04 More beelike hive logic. Nectar is applied more closely to how minecraft bees function 2025-08-11 00:38:42 -05: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
JazzMcNade be8dfd7fa2 Utilize setPositionSafely in breakFree() 2025-07-01 22:04:20 -05:00
556 changed files with 10712 additions and 4293 deletions
+7
View File
@@ -34,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
@@ -293,6 +294,7 @@
- Tauros - Paldea Blaze
- Tauros - Paldea Combat
- Dewgong
- Exeggutor - Kanto
### Model updates for the following Pokémon
- Cleffa
@@ -403,6 +405,9 @@
- Revavroom
- Nickit
- Thievul
- Litwick
- Lampent
- Chandelure
### Changes
- Changed pokemon caught and seen count to update based on the current pokedex being looked
@@ -467,6 +472,7 @@
- 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 can no longer be held by Pokémon. Thanks Monocle ;) You could re-enable this with a datapack but you'd be crazy.
### Fixes
- Fixed game crashing when removing national pokedex using datapacks
@@ -990,6 +996,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,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,42 @@
package com.cobblemon.mod.common.mixin;
import com.cobblemon.mod.common.OrientationControllable;
import com.cobblemon.mod.common.duck.RidePassenger;
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 {
// Target the constructor that takes a LivingEntity owner
@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") // Inject at the very end of the constructor
)
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;
var vehicleController = vehicle.getOrientationController();
// If this is a rolling ride passenger then use the rider eye position to set the arrow spawn position.
if (owner instanceof RidePassenger ridePassenger && vehicleController != null && vehicleController.getActive()) {
var eyePos = ridePassenger.cobblemon$getRideEyePos().add(0.0, -2.0, 0.0);
arrow.setPos(eyePos);
}
}
}
@@ -0,0 +1,100 @@
/*
* 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;
@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) {
CompoundTag compoundTag = new CompoundTag();
entity.save(compoundTag);
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);
}
}
@@ -8,18 +8,23 @@
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 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 +37,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 {
@@ -47,6 +53,12 @@ public abstract class LivingEntityMixin extends Entity implements OrientationCon
return cobblemon$orientationController;
}
@Inject(method = "baseTick", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/multiplayer/ClientPacketListener;send(Lnet/minecraft/network/protocol/Packet;)V"))
private void cobblemon$updateRotationMatrix(CallbackInfo ci) {
if (!cobblemon$orientationController.isActive() || cobblemon$orientationController.getOrientation() == null) return;
CobblemonNetwork.INSTANCE.sendToServer(new ServerboundUpdateOrientationPacket(this.getId(), cobblemon$orientationController.getOrientation()));
}
@Inject(method = "onEffectRemoved", at = @At(value = "TAIL"))
private void cobblemon$onEffectRemoved(MobEffectInstance effect, CallbackInfo ci) {
final LivingEntity entity = (LivingEntity) (Object) this;
@@ -116,4 +128,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;
}
}
@@ -14,6 +14,7 @@ import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
import com.llamalad7.mixinextras.sugar.Local;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.player.Player;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
@@ -25,16 +26,17 @@ public abstract class ServerPlayerMixin {
@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 +44,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
@@ -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,12 +14,15 @@ 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.joml.Matrix3f;
@@ -33,6 +36,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 net.minecraft.util.Mth.abs;
import static net.minecraft.util.Mth.atan2;
@Mixin(Camera.class)
public abstract class CameraMixin {
@Shadow private Entity entity;
@@ -48,45 +54,43 @@ 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;
@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();
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"))
@@ -119,20 +123,128 @@ public abstract class CameraMixin {
@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
if (Cobblemon.config.getDisableRoll()) {
var eulerAngs = newRotation.getEulerAnglesYXZ(new Vector3f());
var currAngs = this.rotation.getEulerAnglesYXZ(new Vector3f());
var vehicle = (PokemonEntity)driver.getVehicle();
var vel = vehicle.getRidingAnimationData().getVelocitySpring().getInterpolated(this.partialTickTime, 0);
// lerp the new rotation towards this y rot at a rate proportional to the horizontal movement speed?
var yaw = Mth.wrapDegrees(Math.toDegrees(Mth.atan2(vel.x(), vel.z())) + 180.0f);
var degDiff = Mth.degreesDifference((float)Math.toDegrees(currAngs.y()), (float)yaw);
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 = 2.0f; // Some smoothing constant k
var smoothingFactor = lerpRateMod * cobblemon$frameTime * k;
var newYaw = currAngs.y() + (Math.toRadians(degDiff) * smoothingFactor);
newRotation.set(new Quaternionf().rotateYXZ((float)newYaw, eulerAngs.x(), 0.0f));
}
var k = 0;
if (Minecraft.getInstance().options.getCameraType().isMirrored()) {
newRotation.rotateY((float)Math.toRadians(180));
k = 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 + k);
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();
@@ -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;
@@ -83,19 +83,33 @@ public abstract class LocalPlayerMixin extends AbstractClientPlayer implements O
@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 +129,27 @@ 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;
// Only send rotation updates if the player is riding a vehicle that is currently rollable
if (!(this.getVehicle() instanceof OrientationControllable controllableVehicle)) return;
if (!controllableVehicle.getOrientationController().getActive()) 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();
// Send new info to server. Currently not checking to see if this has change substantially
CobblemonNetwork.INSTANCE.sendToServer(new ServerboundUpdateRiderRotationPacket(rideXRot, rideYRot, rideEyePos));
}
@Unique
private boolean cobblemon$shouldUseCustomOrientation(LocalPlayer player) {
var playerVehicle = player.getVehicle();
@@ -129,8 +164,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)
@@ -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;
@@ -126,20 +132,22 @@ 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();
@@ -148,12 +156,55 @@ public abstract class MouseHandlerMixin {
return returnValue;
}
// 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 +214,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 +236,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 +302,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;
@@ -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,46 @@ 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;
// If Orientation is currently being controlled then use the stored eye position from camera positioning.
if (vehicleController.getActive() && vehicleController.getOrientation() != null) {
// 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;
// If Orientation is currently being controlled then use the stored eye position from camera positioning.
if (vehicleController.getActive() && vehicleController.getOrientation() != null) {
// TODO: Determine if it needs partialtick involved.
Vec3 customEyePos = ridePassenger.cobblemon$getRideEyePos();
cir.setReturnValue(customEyePos);
}
}
}
@@ -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? */
@@ -179,6 +179,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
@@ -231,6 +232,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
@@ -584,6 +586,7 @@ object CobblemonNetwork {
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,6 +26,7 @@ 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
@@ -63,6 +64,7 @@ object CobblemonRideSettings : DataRegistry {
var boat = BoatSettings()
var burst = BurstSettings()
var dolphin = DolphinSettings()
var submarine = SubmarineSettings()
override fun sync(player: ServerPlayer) {
player.sendPacket(
@@ -78,7 +80,8 @@ object CobblemonRideSettings : DataRegistry {
vehicle = vehicle,
boat = boat,
burst = burst,
dolphin = dolphin
dolphin = dolphin,
submarine = submarine
)
)
}
@@ -96,6 +99,7 @@ object CobblemonRideSettings : DataRegistry {
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 {
@@ -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
@@ -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
@@ -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,
@@ -9,6 +9,7 @@
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.client.entity.PokemonClientDelegate
@@ -27,6 +28,7 @@ val aspectParticleMap: Map<String, ParticleData> = mapOf(
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 {
@@ -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
@@ -30,6 +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())
}
}
@@ -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())
}
@@ -19,6 +19,7 @@ import net.minecraft.world.entity.LivingEntity
import kotlin.collections.get
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.entity.player.Player
import kotlin.collections.get
/**
* Small wrapper around a RidingBehaviour to provide sane defaults in the event that the behaviour is not active.
@@ -109,12 +110,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 {
@@ -34,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> {
@@ -54,18 +55,7 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
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 {
@@ -99,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()))))
@@ -118,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 ?: globalBird.staminaExpr!!) * 20.0f
val stamDrainRate = (1.0f / totalStam).toFloat()
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)
}
@@ -138,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(
@@ -148,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
}
/*
@@ -191,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
@@ -209,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()))
@@ -285,15 +285,13 @@ 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 ?: globalBird.handlingExpr!!)
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr ?: globalBird.speedExpr!!) / 20.0
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()))*2).coerceIn(-1.0, 1.0)
// Dampen yaw when upside down
val yawDampen = (1 - abs(min(cos(controller.roll.toRadians()),0.0f)).pow(2))
@@ -302,10 +300,13 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
// 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(
@@ -316,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 ?: globalBird.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()
// 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) {
@@ -404,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 ?: globalBird.handlingExpr!!)
val mouseInputMult = vehicle.runtime.resolveDouble(settings.mouseInputMult ?: globalBird.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(
@@ -492,29 +434,31 @@ 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 ?: 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
@@ -529,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)
}
@@ -584,11 +532,11 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
driver: Player
): Float {
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr ?: globalBird.speedExpr!!) / 20.0
val glideTopSpeed = topSpeed * vehicle.runtime.resolveDouble(settings.glideSpeedExpr ?: globalBird.glideSpeedExpr!!)
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
@@ -692,6 +640,22 @@ class BirdSettings : RidingBehaviourSettings {
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) {
@@ -706,6 +670,9 @@ class BirdSettings : RidingBehaviourSettings {
buffer.writeNullableExpression(speedExpr)
buffer.writeNullableExpression(accelerationExpr)
buffer.writeNullableExpression(staminaExpr)
buffer.writeNullableExpression(stamDrainHorzMod)
buffer.writeNullableExpression(stamDrainPercRateGlide)
buffer.writeNullableExpression(stamDrainPerRateDive)
buffer.writeNullableExpression(timeToRollCorrect)
}
@@ -721,6 +688,9 @@ class BirdSettings : RidingBehaviourSettings {
speedExpr = buffer.readNullableExpression()
accelerationExpr = buffer.readNullableExpression()
staminaExpr = buffer.readNullableExpression()
stamDrainHorzMod = buffer.readNullableExpression()
stamDrainPercRateGlide = buffer.readNullableExpression()
stamDrainPerRateDive = buffer.readNullableExpression()
timeToRollCorrect = buffer.readNullableExpression()
}
}
@@ -51,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(
@@ -161,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
@@ -265,8 +265,6 @@ 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)
@@ -298,8 +296,7 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
sensitivity: Double,
deltaTime: Double
): Vec3 {
return when {
Cobblemon.config.disableRoll -> noRollRotation(
return rollRotation(
settings,
state,
vehicle,
@@ -310,78 +307,7 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
mouseXSmoother,
sensitivity,
deltaTime
)
else -> rollRotation(
settings,
state,
vehicle,
driver,
mouseY,
mouseX,
mouseYSmoother,
mouseXSmoother,
sensitivity,
deltaTime
)
}
}
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
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 ?: globalJet.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(
@@ -396,9 +322,8 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
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
if (vehicle !is OrientationControllable) return Vec3.ZERO
val cappedDeltaTime = min(deltaTime, 0.2)
// Accumulate the mouse input
@@ -412,14 +337,6 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
//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
@@ -430,10 +347,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
@@ -103,12 +103,12 @@ class BoatBehaviour : RidingBehaviour<BoatSettings, BoatState> {
if (!state.isVehicleSprinting.get()) {
if (state.staminaBuffer.get() >= 20) {
val staminaIncrease = vehicle.runtime.resolveDouble(settings.staminaIncreaseExpr ?: globalBoat.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)
@@ -219,12 +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 ?: globalBoat.staminaDecreaseExpr!!)
if (staminaModifier == 0F) {
private fun consumeStamina(vehicle: PokemonEntity, driver: Player, settings: BoatSettings, state: BoatState) {
if (vehicle.runtime.resolveBoolean(settings.infiniteStamina ?: globalBoat.infiniteStamina!!)) {
return
}
state.stamina.set(max(0f, state.stamina.get() - drain / staminaModifier))
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
@@ -404,16 +404,19 @@ class BoatSettings : RidingBehaviourSettings {
override val stats = mutableMapOf<RidingStat, IntRange>()
var rideSounds: RideSoundSettingsList = RideSoundSettingsList()
var infiniteStamina: Expression? = null
private set
var terminalVelocity: Expression? = null
private set
var rotatePokemonHead: Expression? = null
private set
var staminaIncreaseExpr: Expression? = null
var stamReplenishTimeSeconds: Expression? = null
private set
var staminaDecreaseExpr: Expression? = null
var staminaExpr: Expression? = null
private set
var rotationSpeedModifierExpr: Expression? = null
@@ -438,10 +441,11 @@ class BoatSettings : RidingBehaviourSettings {
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeRidingStats(stats)
rideSounds.encode(buffer)
buffer.writeNullableExpression(infiniteStamina)
buffer.writeNullableExpression(terminalVelocity)
buffer.writeNullableExpression(rotatePokemonHead)
buffer.writeNullableExpression(staminaIncreaseExpr)
buffer.writeNullableExpression(staminaDecreaseExpr)
buffer.writeNullableExpression(stamReplenishTimeSeconds)
buffer.writeNullableExpression(staminaExpr)
buffer.writeNullableExpression(rotationSpeedModifierExpr)
buffer.writeNullableExpression(jumpStrengthExpr)
buffer.writeNullableExpression(speedExpr)
@@ -454,10 +458,11 @@ class BoatSettings : RidingBehaviourSettings {
override fun decode(buffer: RegistryFriendlyByteBuf) {
stats.putAll(buffer.readRidingStats())
rideSounds = RideSoundSettingsList.decode(buffer)
infiniteStamina = buffer.readNullableExpression()
terminalVelocity = buffer.readNullableExpression()
rotatePokemonHead = buffer.readNullableExpression()
staminaIncreaseExpr = buffer.readNullableExpression()
staminaDecreaseExpr = buffer.readNullableExpression()
stamReplenishTimeSeconds = buffer.readNullableExpression()
staminaExpr = buffer.readNullableExpression()
rotationSpeedModifierExpr = buffer.readNullableExpression()
jumpStrengthExpr = buffer.readNullableExpression()
speedExpr = buffer.readNullableExpression()
@@ -20,6 +20,7 @@ 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
@@ -47,6 +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 org.joml.Vector3f
class DolphinBehaviour : RidingBehaviour<DolphinSettings, DolphinState> {
companion object {
@@ -62,7 +64,7 @@ class DolphinBehaviour : RidingBehaviour<DolphinSettings, DolphinState> {
}
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()
@@ -151,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 ?: 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
}
/*********************************************
@@ -190,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 ?: globalDolphin.strafeFactor!!)
val reverseDriveFactor = runtime.resolveDouble(settings.reverseDriveFactor ?: globalDolphin.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(
@@ -225,9 +234,8 @@ 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()
@@ -235,7 +243,7 @@ class DolphinBehaviour : RidingBehaviour<DolphinSettings, DolphinState> {
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)))
@@ -256,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 ?: globalDolphin.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
@@ -333,81 +358,18 @@ 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
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: 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 ?: globalDolphin.handlingExpr!!) * boostingHandleDebuff
val mouseInputMult = vehicle.runtime.resolveDouble(settings.mouseInputMult ?: globalDolphin.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
//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(
@@ -422,41 +384,71 @@ class DolphinBehaviour : RidingBehaviour<DolphinSettings, DolphinState> {
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 ?: globalDolphin.speedExpr!!) / 20.0
val boostingHandleDebuff = if (state.boosting.get()) 0.2 else 1.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(-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()
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
// 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
if (state.clearMouseSmoothers.get()) {
mouseXSmoother.reset()
mouseYSmoother.reset()
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 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 ?: globalDolphin.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 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(
@@ -603,6 +595,7 @@ class DolphinSettings : RidingBehaviourSettings {
buffer.writeNullableExpression(canJump)
buffer.writeNullableExpression(reverseDriveFactor)
buffer.writeNullableExpression(strafeFactor)
buffer.writeNullableExpression(timeToRollCorrect)
buffer.writeNullableExpression(jumpExpr)
buffer.writeNullableExpression(handlingExpr)
buffer.writeNullableExpression(horizontalPitchExpr)
@@ -619,6 +612,7 @@ class DolphinSettings : RidingBehaviourSettings {
canJump = buffer.readNullableExpression()
reverseDriveFactor = buffer.readNullableExpression()
strafeFactor = buffer.readNullableExpression()
timeToRollCorrect = buffer.readNullableExpression()
jumpExpr = buffer.readNullableExpression()
handlingExpr = buffer.readNullableExpression()
horizontalPitchExpr = buffer.readNullableExpression()
@@ -638,6 +632,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)
@@ -659,6 +654,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 {
@@ -676,6 +672,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,643 @@
/*
* 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
}
// if it is then check half the bbheight above and below the center point.
// If the point above is air and the point below is water then you know to trend towards on surface.
// Grab the center position of the vehicle (vehicle.position.add(bbheight/2)
val vehicleCenter = vehicle.position().add(0.0, vehicle.bbHeight/2.0, 0.0)
// If the block above the center is not fluid but this one is then move the center
val centerBlockPos = BlockPos.containing(vehicleCenter)
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
// 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(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()
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)
}
}
@@ -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
@@ -8,6 +8,8 @@
package com.cobblemon.mod.common.block
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.entity.MoLangScriptingEntity
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.core.dispenser.DispenseItemBehavior
@@ -25,6 +27,7 @@ import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.Item
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Items
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.Level
import net.minecraft.world.level.LevelAccessor
import net.minecraft.world.level.block.Block
@@ -40,6 +43,9 @@ import net.minecraft.world.level.material.FluidState
import net.minecraft.world.level.material.Fluids
import net.minecraft.world.level.pathfinder.PathComputationType
import net.minecraft.world.phys.BlockHitResult
import net.minecraft.world.phys.shapes.CollisionContext
import net.minecraft.world.phys.shapes.EntityCollisionContext
import net.minecraft.world.phys.shapes.Shapes
import net.minecraft.world.phys.shapes.VoxelShape
class SaccharineLeafBlock(settings: Properties) : LeavesBlock(settings) {
@@ -90,7 +96,13 @@ class SaccharineLeafBlock(settings: Properties) : LeavesBlock(settings) {
}
}
if (!added) Containers.dropItemStack(level, source.pos.x.toDouble(), source.pos.y.toDouble(), source.pos.z.toDouble(), outputStack)
if (!added) Containers.dropItemStack(
level,
source.pos.x.toDouble(),
source.pos.y.toDouble(),
source.pos.z.toDouble(),
outputStack
)
}
}
stack
@@ -108,7 +120,27 @@ class SaccharineLeafBlock(settings: Properties) : LeavesBlock(settings) {
)
}
override fun isRandomlyTicking(state: BlockState): Boolean = !state.getValue(PERSISTENT) && state.getValue(DISTANCE) > 6
override fun getCollisionShape(
state: BlockState,
level: BlockGetter,
pos: BlockPos,
context: CollisionContext
): VoxelShape? {
if (context is EntityCollisionContext) {
val entity = context.entity
if (entity != null
&& entity is MoLangScriptingEntity
&& entity.config.map.getOrDefault("can_path_through_sacc_leaves", DoubleValue.ZERO).asDouble() == 1.0
) {
return Shapes.empty()
}
}
return super.getCollisionShape(state, level, pos, context)
}
override fun isRandomlyTicking(state: BlockState): Boolean =
true //!state.getValue(PERSISTENT) && state.getValue(DISTANCE) > 6
override fun randomTick(state: BlockState, world: ServerLevel, pos: BlockPos, random: RandomSource) {
val currentAge = state.getValue(AGE)
@@ -123,8 +155,8 @@ class SaccharineLeafBlock(settings: Properties) : LeavesBlock(settings) {
} else if (belowState.block is SaccharineLeafBlock) {
val belowAge = belowState.getValue(AGE)
if (belowAge < MAX_AGE) {
changeAge(state, -1)
changeAge(belowState, 1)
world.setBlock(pos, changeAge(state, -1), UPDATE_CLIENTS)
world.setBlock(belowPos, changeAge(belowState, 1), UPDATE_CLIENTS)
}
break
} else {
@@ -217,7 +249,15 @@ class SaccharineLeafBlock(settings: Properties) : LeavesBlock(settings) {
override fun isPathfindable(state: BlockState, type: PathComputationType): Boolean = false
override fun useItemOn(stack: ItemStack, state: BlockState, level: Level, pos: BlockPos, player: Player, hand: InteractionHand, hit: BlockHitResult): ItemInteractionResult {
override fun useItemOn(
stack: ItemStack,
state: BlockState,
level: Level,
pos: BlockPos,
player: Player,
hand: InteractionHand,
hit: BlockHitResult
): ItemInteractionResult {
if (!state.getValue(WATERLOGGED)) {
val itemStack = player.getItemInHand(hand)
val isGlassBottle = itemStack.`is`(Items.GLASS_BOTTLE)
@@ -69,14 +69,11 @@ import com.cobblemon.mod.common.client.tooltips.RecipeSeasoningAbsorbtionTooltip
import com.cobblemon.mod.common.client.tooltips.SeasoningTooltipGenerator
import com.cobblemon.mod.common.client.tooltips.TooltipManager
import com.cobblemon.mod.common.client.trade.ClientTrade
import com.cobblemon.mod.common.debug.RidingDebugRenderer
import com.cobblemon.mod.common.entity.boat.CobblemonBoatType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.platform.events.PlatformEvents
import com.cobblemon.mod.common.pokedex.scanner.PokedexUsageContext
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.isLookingAt
import io.github.flemmli97.debugutils.api.RegisterDebugRenderers
import net.minecraft.client.Minecraft
import net.minecraft.client.color.block.BlockColor
import net.minecraft.client.gui.GuiGraphics
@@ -196,11 +193,6 @@ object CobblemonClient {
}
ClientPlayerIcon.onTick()
}
val debugId = cobblemonResource("riding")
RegisterDebugRenderers.registerCustomDebugRenderer(debugId, RidingDebugRenderer)
RegisterDebugRenderers.registerServerToggle(debugId)
RegisterDebugRenderers.registerClientHandler(debugId) { RidingDebugRenderer.clientHandler(it) }
}
private fun registerTooltipManagers() {
@@ -45,6 +45,7 @@ class CobblemonConfigScreen(
val clonedConfig = Cobblemon.config.clone()
val defaultConfig = CobblemonConfig()
val hideServerConfigs = Minecraft.getInstance().currentServer != null && !Minecraft.getInstance().isLocalServer
private lateinit var layout: HeaderAndFooterLayout
private lateinit var searchEdit: EditBox
@@ -21,6 +21,7 @@ import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.config.Category
import com.cobblemon.mod.common.config.CobblemonConfig
import com.cobblemon.mod.common.config.CobblemonConfigField
import com.cobblemon.mod.common.config.CobblemonConfigField.CobblemonConfigSide.SERVER
import com.cobblemon.mod.common.util.asTranslated
import net.minecraft.SharedConstants
import net.minecraft.client.Minecraft
@@ -32,6 +33,7 @@ import net.minecraft.client.gui.narration.NarratableEntry
import kotlin.math.min
import kotlin.reflect.KClass
import kotlin.reflect.KMutableProperty1
import kotlin.reflect.full.findAnnotation
import kotlin.reflect.full.memberProperties
import kotlin.reflect.jvm.javaField
@@ -62,6 +64,11 @@ class CobblemonConfigVariableList(
return@map null
}
val fieldAnnotation = property.javaField!!.getAnnotation(CobblemonConfigField::class.java)
if (parent.hideServerConfigs && fieldAnnotation.side == SERVER) {
return@map null
}
val entry: CobblemonConfigVariable? =
when (val classifier = property.returnType.classifier) {
String::class -> CobblemonConfigStringVariable(this, property as KMutableProperty1<out CobblemonConfig, String>)
@@ -10,6 +10,7 @@ package com.cobblemon.mod.common.client.gui.debug.riding
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.CobblemonNetwork
import com.cobblemon.mod.common.CobblemonRideSettings
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourSettings
import com.cobblemon.mod.common.api.text.text
@@ -89,7 +90,7 @@ class RideSettingsEditorGUI(val parentScreen: Screen, val vehicle: PokemonEntity
for (variable in expressionVariables) {
val (name, value) = variable
val editBox = EditBox(this.minecraft!!.font, 110, currentY, 300, 20, name.text())
val editBox = EditBox(this.minecraft!!.font, 130, currentY, 300, 20, name.text())
editBox.setMaxLength(256)
editBox.value = value
@@ -158,6 +159,10 @@ class RideSettingsEditorGUI(val parentScreen: Screen, val vehicle: PokemonEntity
val clazz = settings.javaClass
val fields = clazz.declaredFields
val gsField = CobblemonRideSettings.javaClass.declaredFields.firstOrNull { it.type == clazz }
gsField?.isAccessible = true
val controllerGlobalSettings = gsField?.get(CobblemonRideSettings) as? RidingBehaviourSettings?
// Iterate through all expression fields and return the list of those that
// aer expressions
return fields.mapNotNull { field ->
@@ -165,10 +170,18 @@ class RideSettingsEditorGUI(val parentScreen: Screen, val vehicle: PokemonEntity
field.isAccessible = true // Make private fields accessible
val name = field.name
// Get the value, cast it, and call .getString() on it
val value = (field.get(settings) as Expression).getString()
if (field.get(settings) != null) {
val value = (field.get(settings) as Expression).getString()
// Create the pair and return it for the list
name to value
// Create the pair and return it for the list
name to value
} else {
// If the settings field is null then check for a global setting
val value = getMatchingGlobalSettingsValue(settings, controllerGlobalSettings, name) ?: "novalue"
name to value
}
} else {
// If it's not an Expression (they all honestly should be if being used by the
// behaviour in question) then ignore
@@ -176,4 +189,13 @@ class RideSettingsEditorGUI(val parentScreen: Screen, val vehicle: PokemonEntity
}
}
}
private fun getMatchingGlobalSettingsValue(settings: RidingBehaviourSettings, globalSettings: RidingBehaviourSettings?, fieldName: String): String? {
if (globalSettings == null) return null
val gssField = globalSettings.javaClass.declaredFields.firstOrNull {
it.name == fieldName
}
gssField?.isAccessible = true
return (gssField?.get(globalSettings) as? Expression)?.getString()
}
}
@@ -25,6 +25,8 @@ import com.cobblemon.mod.common.client.gui.summary.widgets.SoundlessWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.screens.stats.features.FriendshipFeatureRenderer
import com.cobblemon.mod.common.client.gui.summary.widgets.screens.stats.features.FullnessFeatureRenderer
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.pokemon.EVs
import com.cobblemon.mod.common.pokemon.IVs
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.lang
@@ -74,6 +76,7 @@ class StatWidget(
private val statsBaseResource = cobblemonResource("textures/gui/summary/summary_stats_chart_base.png")
private val statsChartResource = cobblemonResource("textures/gui/summary/summary_stats_chart.png")
private val statsChartPentagonResource = cobblemonResource("textures/gui/summary/summary_stats_chart_pentagon.png")
private val statsChartPentagonHighlightResource = cobblemonResource("textures/gui/summary/summary_stats_chart_pentagon_highlight.png")
private val statsOtherBaseResource = cobblemonResource("textures/gui/summary/summary_stats_other_base.png")
private val statsOtherSidebarsResource = cobblemonResource("textures/gui/summary/summary_stats_other_sidebars.png")
private val statsOtherSidebarArrowLeft = cobblemonResource("textures/gui/summary/summary_stats_other_sidebar_arrow_left.png")
@@ -290,33 +293,34 @@ class StatWidget(
}
} else {
if (renderPentagonStats) {
blitk(
matrixStack = matrices,
texture = statsChartPentagonResource,
x= (x + 20.5) / SCALE,
y = (y + 22) / SCALE,
width = 186,
height = 176,
scale = SCALE
)
pokemon.form.riding.behaviours?.let {
val behaviours = it.entries.toList()
val selectedBehaviour = behaviours[rideBehaviourIndex]
val canSwitchStyle = behaviours.size > 1 && centerHovered(pMouseX, pMouseY)
blitk(
matrixStack = matrices,
texture = if (canSwitchStyle) statsChartPentagonHighlightResource else statsChartPentagonResource,
x= (x + 20.5) / SCALE,
y = (y + 22) / SCALE,
width = 186,
height = 176,
scale = SCALE
)
val pentagonColour = when (selectedBehaviour.key) {
RidingStyle.AIR -> {
if (behaviours.size > 1 && centerHovered(pMouseX, pMouseY))
if (canSwitchStyle)
Vector3f(70F/255F, 235F/225F, 195F/255F)
else Vector3f(40F/255F, 205F/255F, 165F/255F)
}
RidingStyle.LIQUID -> {
if (behaviours.size > 1 && centerHovered(pMouseX, pMouseY))
if (canSwitchStyle)
Vector3f(95F/255F, 165F/255F, 1F)
else Vector3f(65F/255F, 135F/255F, 1F)
}
else -> {
if (behaviours.size > 1 && centerHovered(pMouseX, pMouseY))
if (canSwitchStyle)
Vector3f(1F, 195F/255F, 30F/255F)
else Vector3f(1F, 165F/255F, 0F)
}
@@ -353,8 +357,8 @@ class StatWidget(
context = context,
labels = RidingStat.entries.toList().map { stat ->
if (statLabelsHovered(pentagonVerticesOffset, pMouseX, pMouseY))
"${floor((pokemon.getRideStat(selectedBehaviour.key, stat) / (selectedBehaviour.value.stats[stat]?.endInclusive ?: 1)) * 100)}%".text()
else floor(pokemon.getRideStat(selectedBehaviour.key, stat)).toString().text()
"+${floor(pokemon.getRideBoost(stat) / pokemon.getMaxRideBoost(stat) * 100)}%".text()
else floor(pokemon.getRideStat(selectedBehaviour.key, stat)).toString().text()
},
verticesOffset = pentagonVerticesOffset,
offsetY = 5.5
@@ -389,7 +393,7 @@ class StatWidget(
colour = Vector3f(216F/255, 100F/255, 1F)
)
EV -> drawStatPolygon(
statLabels.values.map { (pokemon.evs[it]?.toFloat() ?: 0F) / 252F },
statLabels.values.map { (pokemon.evs[it]?.toFloat() ?: 0F) / EVs.MAX_STAT_VALUE.toFloat() },
colour = Vector3f(1F, 1F, 100F/255F)
)
}
@@ -403,9 +407,9 @@ class StatWidget(
"${
if (pokemon.ivs.isHyperTrained(stat.value)) "${pokemon.ivs[stat.value]} (${pokemon.ivs.hyperTrainedIVs[stat.value]})"
else pokemon.ivs[stat.value].toString()
}/31".text()
}/${IVs.MAX_VALUE}".text()
}
EV -> "${pokemon.evs.getOrDefault(stat.value)}/252".text()
EV -> "${pokemon.evs.getOrDefault(stat.value)}/${EVs.MAX_STAT_VALUE}".text()
else -> stat.key
}
} else { stat.key }
@@ -490,14 +494,14 @@ class StatWidget(
STATS -> (if (stat == Stats.HP) "${pokemon.currentHealth} / ${pokemon.maxHealth}" else pokemon.getStat(stat).toString()).text()
IV -> {
(if (asPercent) {
"${floor((((if (pokemon.ivs.isHyperTrained(stat)) pokemon.ivs.hyperTrainedIVs[stat] else pokemon.ivs[stat]) ?: 0) / 31.0) * 100)}%"
"${floor((((if (pokemon.ivs.isHyperTrained(stat)) pokemon.ivs.hyperTrainedIVs[stat] else pokemon.ivs[stat]) ?: 0) / IVs.MAX_VALUE.toDouble()) * 100)}%"
} else {
if (pokemon.ivs.isHyperTrained(stat)) "${pokemon.ivs[stat]} (${pokemon.ivs.hyperTrainedIVs[stat]})"
else pokemon.ivs[stat].toString()
}).text()
}
EV -> {
(if (asPercent) "${floor((pokemon.evs.getOrDefault(stat) / 252.0) * 100)}%"
(if (asPercent) "${floor((pokemon.evs.getOrDefault(stat) / EVs.MAX_STAT_VALUE.toDouble()) * 100)}%"
else pokemon.evs.getOrDefault(stat).toString()).text()
}
else -> "0".text()
@@ -558,7 +562,9 @@ class StatWidget(
}
private fun centerHovered(pMouseX: Number, pMouseY: Number): Boolean {
return (pMouseX.toInt() in (x + 44)..(x + 90)) && (pMouseY.toInt() in (y + 47)..(y + 93))
val centerX = x + (WIDTH / 2)
val centerY = y + (HEIGHT / 2)
return (pMouseX.toInt() in (centerX - 30)..(centerX + 30)) && (pMouseY.toInt() in (centerY - 30)..(centerY + 30))
}
private fun statLabelsHovered(labelOffsets: List<Pair<Double, Double>>, mouseX: Int, mouseY: Int): Boolean {
@@ -9,13 +9,12 @@
package com.cobblemon.mod.common.client.net.data
import com.cobblemon.mod.common.CobblemonRideSettings
import com.cobblemon.mod.common.api.net.ServerNetworkPacketHandler
import com.cobblemon.mod.common.api.net.ClientNetworkPacketHandler
import com.cobblemon.mod.common.net.messages.client.data.RideSettingsSyncPacket
import net.minecraft.server.MinecraftServer
import net.minecraft.server.level.ServerPlayer
import net.minecraft.client.Minecraft
object RideSettingsSyncHandler : ServerNetworkPacketHandler<RideSettingsSyncPacket> {
override fun handle(packet: RideSettingsSyncPacket, server: MinecraftServer, player: ServerPlayer) {
object RideSettingsSyncHandler : ClientNetworkPacketHandler<RideSettingsSyncPacket> {
override fun handle(packet: RideSettingsSyncPacket, client: Minecraft) {
CobblemonRideSettings.bird = packet.bird
CobblemonRideSettings.glider = packet.glider
CobblemonRideSettings.helicopter = packet.helicopter
@@ -30,5 +29,6 @@ object RideSettingsSyncHandler : ServerNetworkPacketHandler<RideSettingsSyncPack
CobblemonRideSettings.boat = packet.boat
CobblemonRideSettings.burst = packet.burst
CobblemonRideSettings.dolphin = packet.dolphin
CobblemonRideSettings.submarine = packet.submarine
}
}
@@ -10,22 +10,41 @@ package com.cobblemon.mod.common.client.net.orientation
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.api.net.ClientNetworkPacketHandler
import com.cobblemon.mod.common.duck.RidePassenger
import com.cobblemon.mod.common.net.messages.client.orientation.ClientboundUpdateOrientationPacket
import net.minecraft.client.Minecraft
import net.minecraft.util.Mth
object S2CUpdateOrientationHandler : ClientNetworkPacketHandler<ClientboundUpdateOrientationPacket> {
override fun handle(packet: ClientboundUpdateOrientationPacket, client: Minecraft) {
client.executeIfPossible {
val level = client.level ?: return@executeIfPossible
val entity = level.getEntity(packet.entityId)
if (entity is OrientationControllable) {
packet.active?.let {
entity.orientationController.active = it
if (!it) {
entity.orientationController.reset()
val player = client.player ?: return@executeIfPossible
val vehicle = level.getEntity(packet.entityId)
val vehicleController = if (vehicle is OrientationControllable) vehicle.orientationController else return@executeIfPossible
// If the client is also the driver of this entity then don't update it as the driver is the source
// of truth on vehicle orientation updates.
if (vehicle.controllingPassenger != player) {
packet.active?.let { shouldUseCustomOrientation ->
val playerIsPassenger = vehicle.passengers.contains(player)
// If the player has just switched to riding a custom orientation ride then set their
// ride 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 && playerIsPassenger) {
// Set local to the vehicle x and yrot
val playerRotater = player as RidePassenger?
if (playerRotater != null) {
playerRotater.`cobblemon$setRideXRot`(Mth.wrapDegrees(player.getXRot() - vehicle.getXRot()))
playerRotater.`cobblemon$setRideYRot`(Mth.wrapDegrees(player.getYRot() - vehicle.getYRot()))
}
}
vehicle.orientationController.active = shouldUseCustomOrientation
}
entity.orientationController.updateOrientation { _ -> packet.orientation }
vehicle.orientationController.updateOrientation { _ -> packet.orientation }
}
}
}
@@ -15,23 +15,23 @@ import com.cobblemon.mod.common.client.entity.PokemonClientDelegate
import com.cobblemon.mod.common.client.render.models.blockbench.FloatingState
import com.cobblemon.mod.common.client.render.models.blockbench.PosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.repository.VaryingModelRepository.getPoser
import com.cobblemon.mod.common.duck.RidePassenger
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.mixin.accessor.CameraAccessor
import com.mojang.math.Axis
import net.minecraft.client.Camera
import net.minecraft.util.Mth
import net.minecraft.world.entity.Entity
import net.minecraft.world.level.ClipContext
import net.minecraft.world.phys.HitResult
import net.minecraft.world.phys.Vec3
import org.joml.Matrix3f
import org.joml.Quaternionf
import org.joml.Vector3f
object MountedCameraRenderer
{
fun getCameraPosition(
instance: Camera,
entity: Entity,
driver: Entity,
vehicle: Entity,
x: Double,
y: Double,
@@ -51,99 +51,86 @@ object MountedCameraRenderer
Mth.lerp(instance.partialTickTime.toDouble(), vehicle.zOld, vehicle.z)
)
setup(vehicle, instance.partialTickTime)
val rollable = entity as OrientationControllable
val controller = rollable.getOrientationController()
val rollable = vehicle as OrientationControllable
val vehicleController = rollable.getOrientationController()
val model = getPoser(vehicle.pokemon.species.resourceIdentifier, FloatingState())
val locatorName = delegate.getSeatLocator(driver)
val locator = delegate.locatorStates[locatorName] ?: return null
// First person and view bobbing are handled the same way.
if (!instance.isDetached || Cobblemon.config.thirdPersonViewBobbing) {
val delegate = vehicle.delegate as PokemonClientDelegate
val locatorName = delegate.getSeatLocator(entity)
val locator = delegate.locatorStates[locatorName]
val currEyeHeight: Double = Mth.lerp(instance.partialTickTime.toDouble(), eyeHeight, eyeHeightOld)
var offset = Vector3f(0f, (currEyeHeight - (driver.bbHeight / 2)).toFloat(), 0f)
var eyeOffset = Vector3f(0f, (currEyeHeight - (driver.bbHeight / 2)).toFloat(), 0f)
if (locator == null) {
return null
}
// Get additional offset from poser and add to the eyeHeight offset
val shouldFlip = !(vehicleController.active && vehicleController.orientation != null) // Do not flip the offset for 3rd person reverse unless we are using normal mc camera rotation.
val locatorOffset = Vec3(locator.matrix.getTranslation(Vector3f()))
// Get Eye height
val currEyeHeight: Double = Mth.lerp(instance.partialTickTime.toDouble(), eyeHeight, eyeHeightOld)
var offset = Vector3f(0f, (currEyeHeight - (entity.bbHeight / 2)).toFloat(), 0f)
// Get additional offset from poser and add to the eyeHeight offset
if (!instance.isDetached) {
offset.add(
getFirstPersonOffset(model, locatorName)
)
} else {
offset.add(
getThirdPersonOffset(thirdPersonReverse, model.thirdPersonCameraOffset, locatorName)
)
}
if (controller.isActive()) {
offset = controller.getRenderOrientation(instance.partialTickTime).transform(offset)
} else {
offset = Axis.YP.rotationDegrees(180f - instance.yRot).transform(offset)
}
val pos = locatorOffset.add(entityPos).toVector3f()
val offsetDistance = offset.length()
val offsetDirection = offset.mul(1 / offsetDistance)
// Use getMaxZoom to calculate clipping
val maxZoom: Float = getMaxZoom(offsetDistance, offsetDirection, pos, instance)
return Vec3(offsetDirection.mul(maxZoom).add(pos))
eyeOffset.add(getFirstPersonOffset(model, locatorName))
if (!instance.isDetached) {
offset.add(
getFirstPersonOffset(model, locatorName)
)
} else if( Cobblemon.config.thirdPersonViewBobbing) {
offset.add(
getThirdPersonOffset(thirdPersonReverse, model.thirdPersonCameraOffset, locatorName, shouldFlip)
)
} else {
val xRot = (-1 * Math.toRadians(instance.xRot.toDouble())).toFloat()
offset.add(
getThirdPersonOffset(thirdPersonReverse, model.thirdPersonCameraOffsetNoViewBobbing, locatorName, shouldFlip)
)
}
// get orientation based on OrientationController or normal rotations
val orientation =
if (controller.isActive() && controller.orientation != null)
controller.orientation!!
else
Matrix3f()
.rotateY(Math.toRadians((180f - instance.yRot).toDouble()).toFloat())
.rotateX(xRot)
// Grab the current camera rotation to use as the offset rotation.
val offsetRot = instance.rotation()
offset = offsetRot.transform(offset)
eyeOffset = offsetRot.transform(eyeOffset)
// Get pivot from poser
val pivotOffsets = model.thirdPersonPivotOffset
val pokemonCenter = Vector3f(0f, entity.bbHeight/2, 0f)
val eyeLocatorOffset = Vec3(locator.matrix.getTranslation(Vector3f()))
val eyePos = eyeLocatorOffset.add(entityPos).toVector3f()
var pivot = Vector3f(pokemonCenter)
val locatorName = delegate.getSeatLocator(entity)
if (pivotOffsets.containsKey(locatorName)) {
pivot = pivotOffsets[locatorName]!!.toVector3f()
pivot.sub(pokemonCenter)
orientation.transform(pivot)
pivot.add(pokemonCenter)
// Get the camera position. If 3rd person viewbobbing is enabled or the player is
// in first person then base the camera position off the seat locator offset
val pos = if (!instance.isDetached || Cobblemon.config.thirdPersonViewBobbing) {
val locatorOffset = Vec3(locator.matrix.getTranslation(Vector3f()))
locatorOffset.add(entityPos).toVector3f()
} else {
val pokemonCenter = Vector3f(0f, driver.bbHeight/2, 0f)
entityPos.toVector3f().add(pokemonCenter)
}
val pos = entityPos.toVector3f().add(pivot)
val offsetDistance = offset.length()
val offsetDirection = offset.mul(1 / offsetDistance)
// Get offset from poser
val offset = getThirdPersonOffset(thirdPersonReverse, model.thirdPersonCameraOffsetNoViewBobbing, locatorName)
val eyeOffsetDistance = eyeOffset.length()
val eyeOffsetDirection = eyeOffset.mul(1 / eyeOffsetDistance)
val rotatedOffset = orientation.transform(offset)
val offsetDistance = rotatedOffset.length()
val offsetDirection = rotatedOffset.mul(1 / offsetDistance)
// Use getMaxZoom to calculate clipping
val maxZoom: Float = getMaxZoom(offsetDistance, offsetDirection, pos, instance)
val eyeMaxZoom: Float = getMaxZoom(eyeOffsetDistance, eyeOffsetDirection, eyePos, instance)
// Use getMaxZoom to calculate clipping
val maxZoom: Float = getMaxZoom(offsetDistance, offsetDirection, pos, instance)
val playerRotater = driver as RidePassenger
playerRotater.`cobblemon$setRideEyePos`(Vec3(eyeOffsetDirection.mul(eyeMaxZoom).add(eyePos)).add(0.0, 2.0, 0.0))
return Vec3(offsetDirection.mul(maxZoom).add(pos))
}
return Vec3(offsetDirection.mul(maxZoom).add(pos))
}
fun resetDriverRotations(
instance: Camera,
driver: Entity,
) {
val eulerAngs = Quaternionf(instance.rotation()).getEulerAnglesYXZ(Vector3f())
val playerRotater = driver as RidePassenger
// Backticks because kotlin explodes otherwise due to syntax
playerRotater.`cobblemon$setRideXRot`(-eulerAngs.x() * Mth.RAD_TO_DEG)
playerRotater.`cobblemon$setRideYRot`(180.0f - (eulerAngs.y() * Mth.RAD_TO_DEG))
}
private fun getThirdPersonOffset(
thirdPersonReverse: Boolean,
cameraOffsets: Map<String, Vec3>,
locatorName: String
locatorName: String,
shouldFlip: Boolean = true
): Vector3f {
val offset = if (thirdPersonReverse && cameraOffsets.containsKey(locatorName + "_reverse")) {
cameraOffsets[locatorName + "_reverse"]!!.toVector3f()
@@ -152,7 +139,10 @@ object MountedCameraRenderer
} else {
Vector3f(0f, 2f, 4f)
}
if (thirdPersonReverse) offset.z *= -1
// Don't need to account for this since orientation is derived from camera rotation and that z is already flipped.
// Flip only when not on a rollable.
if (thirdPersonReverse && shouldFlip) offset.z *= -1
return offset
}
@@ -747,9 +747,8 @@ open class PosableModel(@Transient override val rootPart: Bone) : ModelFrame {
// If scale is 0 we start getting NaNs
scale.coerceAtLeast(0.01f)
if (entity.passengers.isNotEmpty() && entity.controllingPassenger is OrientationControllable
&& (entity.controllingPassenger as OrientationControllable).orientationController.active){
val controllingPassenger = entity.controllingPassenger as OrientationControllable
val controller = controllingPassenger.orientationController
&& (entity as OrientationControllable).orientationController.active){
val controller = entity.orientationController
val transformationMatrix = Matrix4f()
val center = Vector3f(0f, entity.bbHeight/2, 0f)
transformationMatrix.translate(center)
@@ -1,67 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.render.models.blockbench.pokemon.gen5
import com.cobblemon.mod.common.client.render.models.blockbench.frame.BimanualFrame
import com.cobblemon.mod.common.client.render.models.blockbench.frame.BipedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.frame.HeadedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.CryProvider
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.PokemonPosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Pose
import com.cobblemon.mod.common.entity.PoseType
import net.minecraft.client.model.geom.ModelPart
import net.minecraft.world.phys.Vec3
class BeheeyemModel (root: ModelPart) : PokemonPosableModel(root), HeadedFrame, BipedFrame, BimanualFrame {
override val rootPart = root.registerChildWithAllChildren("beheeyem")
override val head = getPart("head")
override val rightArm = getPart("arm_right")
override val leftArm = getPart("arm_left")
override val rightLeg = getPart("leg_right")
override val leftLeg = getPart("leg_left")
override var portraitScale = 3.0F
override var portraitTranslation = Vec3(-0.34, -0.8, 0.0)
override var profileScale = 0.8F
override var profileTranslation = Vec3(0.0, 0.6, 0.0)
lateinit var sleep: Pose
lateinit var standing: Pose
lateinit var walk: Pose
override val cryAnimation = CryProvider { bedrockStateful("beheeyem", "cry") }
override fun registerPoses() {
sleep = registerPose(
poseType = PoseType.SLEEP,
animations = arrayOf(bedrock("beheeyem", "sleep"))
)
standing = registerPose(
poseName = "standing",
poseTypes = PoseType.STATIONARY_POSES + PoseType.UI_POSES,
transformTicks = 10,
animations = arrayOf(
singleBoneLook(),
bedrock("beheeyem", "ground_idle")
)
)
walk = registerPose(
poseName = "walk",
poseTypes = PoseType.MOVING_POSES,
transformTicks = 5,
animations = arrayOf(
singleBoneLook(),
bedrock("beheeyem", "ground_walk")
)
)
}
}
@@ -1,94 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.render.models.blockbench.pokemon.gen5
import com.cobblemon.mod.common.client.render.models.blockbench.frame.HeadedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.CryProvider
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.PokemonPosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.pose.CobblemonPose
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.util.isBattling
import net.minecraft.client.model.geom.ModelPart
import net.minecraft.world.phys.Vec3
class ChandelureModel (root: ModelPart) : PokemonPosableModel(root), HeadedFrame {
override val rootPart = root.registerChildWithAllChildren("chandelure")
override val head = getPart("head")
override var portraitScale = 1.85F
override var portraitTranslation = Vec3(-0.3, -0.19, 0.0)
override var profileScale = 0.81F
override var profileTranslation = Vec3(0.0, 0.68, 0.0)
lateinit var standing: CobblemonPose
lateinit var walk: CobblemonPose
lateinit var hover: CobblemonPose
lateinit var flying: CobblemonPose
lateinit var battle_idle: CobblemonPose
lateinit var sleep: CobblemonPose
override val cryAnimation = CryProvider { bedrockStateful("chandelure", "cry") }
override fun registerPoses() {
val blink = quirk { bedrockStateful("chandelure", "blink") }
standing = registerPose(
poseName = "standing",
poseTypes = PoseType.STATIONARY_POSES + PoseType.UI_POSES - PoseType.HOVER,
quirks = arrayOf(blink),
condition = { !it.isBattling },
animations = arrayOf(
bedrock("chandelure", "ground_idle")
)
)
walk = registerPose(
poseName = "walk",
poseTypes = PoseType.MOVING_POSES - PoseType.FLY,
quirks = arrayOf(blink),
animations = arrayOf(
bedrock("chandelure", "ground_walk")
)
)
hover = registerPose(
poseName = "hover",
poseType = PoseType.HOVER,
quirks = arrayOf(blink),
animations = arrayOf(
bedrock("chandelure", "air_idle")
)
)
flying = registerPose(
poseName = "flying",
poseType = PoseType.FLY,
quirks = arrayOf(blink),
animations = arrayOf(
bedrock("chandelure", "air_fly")
)
)
battle_idle = registerPose(
poseName = "battle_idle",
poseTypes = PoseType.STATIONARY_POSES,
quirks = arrayOf(blink),
condition = { it.isBattling },
animations = arrayOf(
bedrock("chandelure", "battle_idle")
)
)
}
// override fun getFaintAnimation(
// pokemonEntity: PokemonEntity,
// state: PosableState<PokemonEntity>
// ) = if (state.isPosedIn(standing, walk)) bedrockStateful("chandelure", "faint") else null
}
@@ -1,113 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.render.models.blockbench.pokemon.gen5
import com.cobblemon.mod.common.client.render.models.blockbench.PosableState
import com.cobblemon.mod.common.client.render.models.blockbench.createTransformation
import com.cobblemon.mod.common.client.render.models.blockbench.frame.BimanualFrame
import com.cobblemon.mod.common.client.render.models.blockbench.frame.BipedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.frame.HeadedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.CryProvider
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.PokemonPosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.pose.ModelPartTransformation
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Pose
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.util.isBattling
import net.minecraft.client.model.geom.ModelPart
import net.minecraft.world.phys.Vec3
class ElgyemModel (root: ModelPart) : PokemonPosableModel(root), HeadedFrame, BipedFrame, BimanualFrame {
override val rootPart = root.registerChildWithAllChildren("elgyem")
override val head = getPart("head")
override val rightArm = getPart("arm_right")
override val leftArm = getPart("arm_left")
override val rightLeg = getPart("leg_right")
override val leftLeg = getPart("leg_left")
override var portraitScale = 2.5F
override var portraitTranslation = Vec3(-0.2, -0.8, 0.0)
override var profileScale = 0.85F
override var profileTranslation = Vec3(0.0, 0.5, 0.0)
lateinit var sleep: Pose
lateinit var standing: Pose
lateinit var walk: Pose
lateinit var battleidle: Pose
lateinit var shoulderLeft: Pose
lateinit var shoulderRight: Pose
override val cryAnimation = CryProvider { bedrockStateful("elgyem", "cry") }
val shoulderOffset = 5.6
override fun registerPoses() {
sleep = registerPose(
poseType = PoseType.SLEEP,
animations = arrayOf(bedrock("elgyem", "sleep"))
)
standing = registerPose(
poseName = "standing",
poseTypes = PoseType.STATIONARY_POSES + PoseType.UI_POSES,
transformTicks = 10,
condition = { !it.isBattling },
animations = arrayOf(
singleBoneLook(),
bedrock("elgyem", "ground_idle")
)
)
walk = registerPose(
poseName = "walk",
poseTypes = PoseType.MOVING_POSES,
transformTicks = 5,
animations = arrayOf(
singleBoneLook(),
bedrock("elgyem", "ground_walk")
)
)
battleidle = registerPose(
poseName = "battle_idle",
poseTypes = PoseType.STATIONARY_POSES,
transformTicks = 10,
condition = { it.isBattling },
animations = arrayOf(
singleBoneLook(),
bedrock("elgyem", "battle_idle")
)
)
shoulderLeft = registerPose(
poseType = PoseType.SHOULDER_LEFT,
//quirks = arrayOf(blink),
animations = arrayOf(
singleBoneLook(),
bedrock("elgyem", "ground_idle")
),
transformedParts = arrayOf(
rootPart.createTransformation().addPosition(ModelPartTransformation.X_AXIS, shoulderOffset)
)
)
shoulderRight = registerPose(
poseType = PoseType.SHOULDER_RIGHT,
//quirks = arrayOf(blink),
animations = arrayOf(
singleBoneLook(),
bedrock("elgyem", "ground_idle")
),
transformedParts = arrayOf(
rootPart.createTransformation().addPosition(ModelPartTransformation.X_AXIS, -shoulderOffset)
)
)
}
override fun getFaintAnimation(state: PosableState) = if (state.isPosedIn(standing, walk, battleidle, sleep)) bedrockStateful("elgyem", "faint") else null
}
@@ -1,102 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.render.models.blockbench.pokemon.gen5
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.PokemonPosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.frame.HeadedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.CryProvider
import com.cobblemon.mod.common.client.render.models.blockbench.pose.CobblemonPose
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.util.isBattling
import net.minecraft.client.model.geom.ModelPart
import net.minecraft.world.phys.Vec3
class LampentModel (root: ModelPart) : PokemonPosableModel(root), HeadedFrame {
override val rootPart = root.registerChildWithAllChildren("lampent")
override val head = getPart("head")
override var portraitScale = 2.2F
override var portraitTranslation = Vec3(-0.19, -0.17, 0.0)
override var profileScale = 0.8F
override var profileTranslation = Vec3(0.0, 0.7, 0.0)
lateinit var standing: CobblemonPose
lateinit var walk: CobblemonPose
lateinit var hover: CobblemonPose
lateinit var flying: CobblemonPose
lateinit var battle_idle: CobblemonPose
lateinit var sleep: CobblemonPose
override val cryAnimation = CryProvider { bedrockStateful("lampent", "cry") }
override fun registerPoses() {
val blink = quirk { bedrockStateful("lampent", "blink") }
sleep = registerPose(
poseName = "sleep",
poseType = PoseType.SLEEP,
animations = arrayOf(
bedrock("lampent", "sleep")
)
)
standing = registerPose(
poseName = "standing",
poseTypes = PoseType.STATIONARY_POSES + PoseType.UI_POSES - PoseType.HOVER,
quirks = arrayOf(blink),
condition = { !it.isBattling },
animations = arrayOf(
bedrock("lampent", "ground_idle")
)
)
walk = registerPose(
poseName = "walk",
poseTypes = PoseType.MOVING_POSES - PoseType.FLY,
quirks = arrayOf(blink),
animations = arrayOf(
bedrock("lampent", "ground_walk")
)
)
hover = registerPose(
poseName = "hover",
poseType = PoseType.HOVER,
quirks = arrayOf(blink),
animations = arrayOf(
bedrock("lampent", "air_idle")
)
)
flying = registerPose(
poseName = "flying",
poseType = PoseType.FLY,
quirks = arrayOf(blink),
animations = arrayOf(
bedrock("lampent", "air_fly")
)
)
battle_idle = registerPose(
poseName = "battle_idle",
poseTypes = PoseType.STATIONARY_POSES,
quirks = arrayOf(blink),
condition = { it.isBattling },
animations = arrayOf(
bedrock("lampent", "battle_idle")
)
)
}
// override fun getFaintAnimation(
// pokemonEntity: PokemonEntity,
// state: PosableState<PokemonEntity>
// ) = if (state.isPosedIn(standing, walk)) bedrockStateful("lampent", "faint") else null
}
@@ -1,110 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.render.models.blockbench.pokemon.gen5
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.PokemonPosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.createTransformation
import com.cobblemon.mod.common.client.render.models.blockbench.frame.HeadedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.CryProvider
import com.cobblemon.mod.common.client.render.models.blockbench.pose.CobblemonPose
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.util.isBattling
import net.minecraft.client.model.geom.ModelPart
import net.minecraft.world.phys.Vec3
class LitwickModel (root: ModelPart) : PokemonPosableModel(root), HeadedFrame {
override val rootPart = root.registerChildWithAllChildren("litwick")
override val head = getPart("head")
override var portraitScale = 2.33F
override var portraitTranslation = Vec3(0.01, -1.9, 0.0)
override var profileScale = 0.86F
override var profileTranslation = Vec3(0.0, 0.44, 0.0)
lateinit var standing: CobblemonPose
lateinit var walk: CobblemonPose
lateinit var sleep: CobblemonPose
lateinit var battle_idle: CobblemonPose
lateinit var shoulderLeft: CobblemonPose
lateinit var shoulderRight: CobblemonPose
override val cryAnimation = CryProvider { bedrockStateful("litwick", "cry") }
val shoulderOffset = 5.5
val wax = getPart("wax_trail")
override fun registerPoses() {
val blink = quirk { bedrockStateful("litwick", "blink") }
sleep = registerPose(
poseName = "sleep",
poseType = PoseType.SLEEP,
animations = arrayOf(
bedrock("litwick", "sleep")
)
)
standing = registerPose(
poseName = "standing",
poseTypes = PoseType.STATIONARY_POSES + PoseType.UI_POSES,
condition = { !it.isBattling },
quirks = arrayOf(blink),
animations = arrayOf(
bedrock("litwick", "ground_idle")
)
)
walk = registerPose(
poseName = "walk",
poseTypes = PoseType.MOVING_POSES,
quirks = arrayOf(blink),
animations = arrayOf(
bedrock("litwick", "ground_walk")
)
)
battle_idle = registerPose(
poseName = "battle_idle",
poseTypes = PoseType.STATIONARY_POSES,
quirks = arrayOf(blink),
condition = { it.isBattling },
animations = arrayOf(
bedrock("litwick", "battle_idle")
)
)
shoulderLeft = registerPose(
poseType = PoseType.SHOULDER_LEFT,
quirks = arrayOf(blink),
animations = arrayOf(
bedrock("litwick", "ground_idle")
),
transformedParts = arrayOf(
wax.createTransformation().withVisibility(visibility = false)
)
)
shoulderRight = registerPose(
poseType = PoseType.SHOULDER_RIGHT,
quirks = arrayOf(blink),
animations = arrayOf(
bedrock("litwick", "ground_idle")
),
transformedParts = arrayOf(
wax.createTransformation().withVisibility(visibility = false)
)
)
}
// override fun getFaintAnimation(
// pokemonEntity: PokemonEntity,
// state: PosableState<PokemonEntity>
// ) = if (state.isPosedIn(standing, walk)) bedrockStateful("litwick", "faint") else null
}
@@ -361,8 +361,6 @@ object VaryingModelRepository {
inbuilt("chespin", :: ChespinModel)
inbuilt("quilladin", :: QuilladinModel)
inbuilt("chesnaught", :: ChesnaughtModel)
inbuilt("elgyem", :: ElgyemModel)
inbuilt("beheeyem", :: BeheeyemModel)
inbuilt("pineco", :: PinecoModel)
inbuilt("forretress", :: ForretressModel)
inbuilt("lotad", :: LotadModel)
@@ -554,9 +552,6 @@ object VaryingModelRepository {
inbuilt("gurdurr", ::GurdurrModel)
inbuilt("conkeldurr", ::ConkeldurrModel)
inbuilt("clodsire", ::ClodsireModel)
inbuilt("litwick", ::LitwickModel)
inbuilt("lampent", ::LampentModel)
inbuilt("chandelure", ::ChandelureModel)
inbuilt("gimmighoulchest", ::GimmighoulChestModel)
inbuilt("drifloon", ::DrifloonModel)
inbuilt("lileep", ::LileepModel)
@@ -42,14 +42,9 @@ import com.cobblemon.mod.common.util.lang
import com.cobblemon.mod.common.util.math.DoubleRange
import com.cobblemon.mod.common.util.math.geometry.toRadians
import com.cobblemon.mod.common.util.math.remap
import com.google.common.primitives.Floats
import com.mojang.blaze3d.vertex.PoseStack
import com.mojang.math.Axis
import kotlin.math.PI
import kotlin.math.ceil
import kotlin.math.max
import kotlin.math.min
import kotlin.math.pow
import kotlin.math.sqrt
import net.minecraft.ChatFormatting
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.Font.DisplayMode
@@ -67,11 +62,8 @@ import net.minecraft.resources.ResourceLocation
import net.minecraft.util.Mth
import net.minecraft.world.entity.Entity
import net.minecraft.world.phys.Vec3
import org.joml.Math
import org.joml.Matrix4f
import org.joml.Quaternionf
import org.joml.Vector3f
import org.joml.Vector4f
import org.joml.*
import kotlin.math.*
class PokemonRenderer(
context: EntityRendererProvider.Context
@@ -189,12 +181,14 @@ class PokemonRenderer(
buffer: MultiBufferSource,
packedLight: Int
) {
val driver = entity.firstPassenger ?: return
val rollable = driver as? OrientationControllable ?: return
val rollable = entity as? OrientationControllable ?: return
val controller = rollable.orientationController
poseMatrix.pushPose()
if (!DISABLE_ROLLING_DEBUG && controller.active) {
// Allow the ride controller to modify its rotations using partialTick
entity.applyRenderRotation(partialTicks)
val matrix = poseMatrix.last().pose()
val yaw = Mth.rotLerp(partialTicks, entity.yBodyRotO, entity.yBodyRot)
val center = Vector3f(0f, entity.bbHeight/2, 0f)
@@ -23,9 +23,6 @@ object CobblemonTooltipGenerator : TooltipGenerator() {
val resultLines = mutableListOf<Component>()
if (stack.item.builtInRegistryHolder().unwrapKey().isPresent && stack.item.builtInRegistryHolder().unwrapKey().get().location().namespace == Cobblemon.MODID) {
if (stack.get(DataComponents.HIDE_ADDITIONAL_TOOLTIP) != null) {
return null
}
val language = Language.getInstance()
val key = this.baseLangKeyForItem(stack)
if (language.has(key)) {
@@ -31,9 +31,6 @@ object FishingBaitTooltipGenerator : TooltipGenerator() {
private val fishingBaitItemClass by lazy { lang("item_class.fishing_bait").blue() }
override fun generateCategoryTooltip(stack: ItemStack, lines: MutableList<Component>): MutableList<Component>? {
if (stack.get(DataComponents.HIDE_ADDITIONAL_TOOLTIP) != null) {
return null
}
if (!SpawnBaitEffects.isFishingBait(stack)) {
return null
}
@@ -41,9 +38,6 @@ object FishingBaitTooltipGenerator : TooltipGenerator() {
}
override fun generateAdditionalTooltip(stack: ItemStack, lines: MutableList<Component>): MutableList<Component>? {
if (stack.get(DataComponents.HIDE_ADDITIONAL_TOOLTIP) != null) {
return null
}
val resultLines = mutableListOf<Component>()
if (SpawnBaitEffects.isFishingBait(stack)) resultLines.add(this.fishingBaitItemClass)
@@ -23,9 +23,6 @@ import net.minecraft.world.item.ItemStack
object FishingRodTooltipGenerator : TooltipGenerator() {
override fun generateTooltip(stack: ItemStack, lines: MutableList<Component>): MutableList<Component>? {
if (stack.get(DataComponents.HIDE_ADDITIONAL_TOOLTIP) != null) {
return null
}
val resultLines = mutableListOf<Component>()
val rod = (stack.item as? PokerodItem)?.pokeRodId?.let { PokeRods.getPokeRod(it) } ?: return null
@@ -61,10 +61,6 @@ object SeasoningTooltipGenerator : TooltipGenerator() {
}
override fun generateAdditionalTooltip(stack: ItemStack, lines: MutableList<Component>): MutableList<Component>? {
if (stack.get(DataComponents.HIDE_ADDITIONAL_TOOLTIP) != null) {
return null
}
val isSeasoningIngredient = Seasonings.isSeasoning(stack)
val flavors = stack.get(CobblemonItemComponents.FLAVOUR)?.flavours
val food = stack.get(CobblemonItemComponents.FOOD)
@@ -10,6 +10,7 @@ package com.cobblemon.mod.common.client.tooltips
import com.cobblemon.mod.common.api.text.yellow
import com.cobblemon.mod.common.util.lang
import net.minecraft.core.component.DataComponents
import net.minecraft.network.chat.Component
import net.minecraft.world.item.ItemStack
@@ -22,6 +23,12 @@ object TooltipManager {
}
fun generateTooltips(stack: ItemStack, lines: MutableList<Component>, hasShiftDown: Boolean) {
// This will need to be changed in 1.21.5 as 25w04a changed the following:
// "Removed the hide_tooltip and hide_additional_tooltip components in favor of the new tooltip_display component."
if (stack.get(DataComponents.HIDE_ADDITIONAL_TOOLTIP) != null) {
return
}
val standardLines = tooltipGenerators.flatMap {
val innerLines = mutableListOf<Component>()
val regularTooltip = it.generateTooltip(stack, lines)
@@ -12,6 +12,8 @@ import com.cobblemon.mod.common.api.drop.ItemDropMethod
import com.cobblemon.mod.common.api.pokeball.catching.calculators.CaptureCalculator
import com.cobblemon.mod.common.api.pokemon.status.Statuses
import com.cobblemon.mod.common.client.gui.portrait.PortraitStyle
import com.cobblemon.mod.common.config.CobblemonConfigField.CobblemonConfigSide.CLIENT
import com.cobblemon.mod.common.config.CobblemonConfigField.CobblemonConfigSide.SERVER
import com.cobblemon.mod.common.config.constraint.IntConstraint
import com.cobblemon.mod.common.pokeball.catching.calculators.CobblemonCaptureCalculator
import com.cobblemon.mod.common.util.adapters.CaptureCalculatorAdapter
@@ -19,9 +21,9 @@ import com.cobblemon.mod.common.util.adapters.IdentifierAdapter
import com.cobblemon.mod.common.util.adapters.IntRangeAdapter
import com.google.gson.GsonBuilder
import com.google.gson.annotations.SerializedName
import net.minecraft.resources.ResourceLocation
import kotlin.reflect.full.memberProperties
import kotlin.reflect.jvm.isAccessible
import net.minecraft.resources.ResourceLocation
class CobblemonConfig {
companion object {
@@ -37,123 +39,123 @@ class CobblemonConfig {
var lastSavedVersion: String = "0.0.1"
@CobblemonConfigField(Category.Pokemon, lang = "max_pokemon_level")
@CobblemonConfigField(Category.Pokemon, lang = "max_pokemon_level", SERVER)
@IntConstraint(min = 1, max = 1000)
var maxPokemonLevel = 100
@CobblemonConfigField(Category.Pokemon, lang = "max_pokemon_friendship")
@CobblemonConfigField(Category.Pokemon, lang = "max_pokemon_friendship", SERVER)
@IntConstraint(min = 0, max = 1000)
var maxPokemonFriendship = 255
@CobblemonConfigField(Category.Pokemon, lang = "announce_drop_items")
@CobblemonConfigField(Category.Pokemon, lang = "announce_drop_items", SERVER)
var announceDropItems = true
@CobblemonConfigField(Category.Pokemon, lang = "default_drop_item_method")
@CobblemonConfigField(Category.Pokemon, lang = "default_drop_item_method", SERVER)
var defaultDropItemMethod = ItemDropMethod.ON_ENTITY
@CobblemonConfigField(Category.Pokemon, lang = "drops_after_death_animation")
@CobblemonConfigField(Category.Pokemon, lang = "drops_after_death_animation", SERVER)
var dropAfterDeathAnimation = false
@CobblemonConfigField(Category.Pokemon, lang = "ambient_pokemon_cry_ticks")
@CobblemonConfigField(Category.Pokemon, lang = "ambient_pokemon_cry_ticks", SERVER)
@LastChangedVersion("1.4.0")
var ambientPokemonCryTicks = 1080
@CobblemonConfigField(Category.Storage, lang = "default_box_count")
@CobblemonConfigField(Category.Storage, lang = "default_box_count", SERVER)
@IntConstraint(min = 1, max = 1000)
@LastChangedVersion("1.7.0")
var defaultBoxCount = 40
@CobblemonConfigField(Category.Storage, lang = "pokemon_save_interval_seconds")
@CobblemonConfigField(Category.Storage, lang = "pokemon_save_interval_seconds", SERVER)
@IntConstraint(min = 1, max = 120)
var pokemonSaveIntervalSeconds = 30
@CobblemonConfigField(Category.Storage, lang = "storage_format")
@CobblemonConfigField(Category.Storage, lang = "storage_format", SERVER)
var storageFormat = "nbt"
@CobblemonConfigField(Category.Storage, lang = "prevent_complete_party_deposit")
@CobblemonConfigField(Category.Storage, lang = "prevent_complete_party_deposit", SERVER)
var preventCompletePartyDeposit = false
@CobblemonConfigField(Category.Storage, lang = "mongo_db_connection_string")
@CobblemonConfigField(Category.Storage, lang = "mongo_db_connection_string", SERVER)
var mongoDBConnectionString = "mongodb://localhost:27017"
@CobblemonConfigField(Category.Storage, lang = "mongo_db_database_name")
@CobblemonConfigField(Category.Storage, lang = "mongo_db_database_name", SERVER)
var mongoDBDatabaseName = "cobblemon"
@CobblemonConfigField(Category.Spawning, lang = "max_vertical_correction_blocks")
@CobblemonConfigField(Category.Spawning, lang = "max_vertical_correction_blocks", SERVER)
@IntConstraint(min = 1, max = 200)
var maxVerticalCorrectionBlocks = 64
@CobblemonConfigField(Category.Spawning, lang = "minimum_level_range_max")
@CobblemonConfigField(Category.Spawning, lang = "minimum_level_range_max", SERVER)
@IntConstraint(min = 1, max = 1000)
var minimumLevelRangeMax = 10
@CobblemonConfigField(Category.Spawning, lang = "enable_spawning")
@CobblemonConfigField(Category.Spawning, lang = "enable_spawning", SERVER)
var enableSpawning = true
@CobblemonConfigField(Category.Spawning, lang = "minimum_distance_between_entities")
@CobblemonConfigField(Category.Spawning, lang = "minimum_distance_between_entities", SERVER)
var minimumDistanceBetweenEntities = 8.0
@CobblemonConfigField(Category.Spawning, lang = "max_nearby_blocks_horizontal_range")
@CobblemonConfigField(Category.Spawning, lang = "max_nearby_blocks_horizontal_range", SERVER)
var maxNearbyBlocksHorizontalRange = 4
@CobblemonConfigField(Category.Spawning, lang = "max_nearby_blocks_vertical_range")
@CobblemonConfigField(Category.Spawning, lang = "max_nearby_blocks_vertical_range", SERVER)
var maxNearbyBlocksVerticalRange = 2
@CobblemonConfigField(Category.Spawning, lang = "max_vertical_space")
@CobblemonConfigField(Category.Spawning, lang = "max_vertical_space", SERVER)
var maxVerticalSpace = 8
@CobblemonConfigField(Category.Spawning, lang = "spawning_zone_diameter")
@CobblemonConfigField(Category.Spawning, lang = "spawning_zone_diameter", SERVER)
@SerializedName("spawningZoneDiameter", alternate = ["worldSliceDiameter"])
var spawningZoneDiameter = 8
@CobblemonConfigField(Category.Spawning, lang = "spawning_zone_height")
@CobblemonConfigField(Category.Spawning, lang = "spawning_zone_height", SERVER)
@SerializedName("spawningZoneHeight", alternate = ["worldSliceHeight"])
var spawningZoneHeight = 16
@CobblemonConfigField(Category.Spawning, lang = "ticks_between_spawn_attempts")
@CobblemonConfigField(Category.Spawning, lang = "ticks_between_spawn_attempts", SERVER)
var ticksBetweenSpawnAttempts = 20F
@CobblemonConfigField(Category.Spawning, lang = "minimum_spawning_zone_distance_from_player")
@CobblemonConfigField(Category.Spawning, lang = "minimum_spawning_zone_distance_from_player", SERVER)
@SerializedName("minimumSpawningZoneDistanceFromPlayer", alternate = ["minimumSliceDistanceFromPlayer"])
var minimumSpawningZoneDistanceFromPlayer = 16F
@CobblemonConfigField(Category.Spawning, lang = "maximum_spawning_zone_distance_from_player")
@CobblemonConfigField(Category.Spawning, lang = "maximum_spawning_zone_distance_from_player", SERVER)
@SerializedName("maximumSpawningZoneDistanceFromPlayer", alternate = ["maximumSliceDistanceFromPlayer"])
var maximumSpawningZoneDistanceFromPlayer = 16 * 4F
@CobblemonConfigField(Category.Spawning, lang = "maximum_spawns_per_pass")
@CobblemonConfigField(Category.Spawning, lang = "maximum_spawns_per_pass", SERVER)
var maximumSpawnsPerPass = 8
@CobblemonConfigField(Category.Spawning, lang = "export_spawn_config")
@CobblemonConfigField(Category.Spawning, lang = "export_spawn_config", SERVER)
var exportSpawnConfig = false
@CobblemonConfigField(Category.Spawning, lang = "save_pokemon_to_world")
@CobblemonConfigField(Category.Spawning, lang = "save_pokemon_to_world", SERVER)
var savePokemonToWorld = true
@CobblemonConfigField(Category.Starter, lang = "export_starter_config")
@CobblemonConfigField(Category.Starter, lang = "export_starter_config", SERVER)
var exportStarterConfig = false
@CobblemonConfigField(Category.Battles, lang = "auto_update_showdown")
@CobblemonConfigField(Category.Battles, lang = "auto_update_showdown", SERVER)
var autoUpdateShowdown = true
@CobblemonConfigField(Category.Battles, lang = "default_flee_distance")
@CobblemonConfigField(Category.Battles, lang = "default_flee_distance", SERVER)
var defaultFleeDistance = 16F * 2
@CobblemonConfigField(Category.Battles, lang = "allow_experience_from_pvp")
@CobblemonConfigField(Category.Battles, lang = "allow_experience_from_pvp", SERVER)
var allowExperienceFromPvP = true
@CobblemonConfigField(Category.Battles, lang = "experience_share_multiplier")
@CobblemonConfigField(Category.Battles, lang = "experience_share_multiplier", SERVER)
var experienceShareMultiplier = .5
@CobblemonConfigField(Category.Battles, lang = "lucky_egg_multiplier")
@CobblemonConfigField(Category.Battles, lang = "lucky_egg_multiplier", SERVER)
var luckyEggMultiplier = 1.5
@CobblemonConfigField(Category.Battles, lang = "allow_spectating")
@CobblemonConfigField(Category.Battles, lang = "allow_spectating", SERVER)
var allowSpectating = true
@CobblemonConfigField(Category.Pokemon, lang = "experience_multiplier")
@CobblemonConfigField(Category.Pokemon, lang = "experience_multiplier", SERVER)
var experienceMultiplier = 2F
@CobblemonConfigField(Category.Spawning, lang = "pokemon_per_chunk")
@CobblemonConfigField(Category.Spawning, lang = "pokemon_per_chunk", SERVER)
var pokemonPerChunk = 1F
@CobblemonConfigField(Category.PassiveStatus, lang = "passive_statuses")
@CobblemonConfigField(Category.PassiveStatus, lang = "passive_statuses", SERVER)
var passiveStatuses = mutableMapOf(
Statuses.POISON.configEntry(),
Statuses.POISON_BADLY.configEntry(),
@@ -163,165 +165,165 @@ class CobblemonConfig {
Statuses.BURN.configEntry()
)
@CobblemonConfigField(Category.Healing, lang = "infinite_healer_charge")
@CobblemonConfigField(Category.Healing, lang = "infinite_healer_charge", SERVER)
var infiniteHealerCharge = false
@CobblemonConfigField(Category.Healing, lang = "max_healer_charge")
@CobblemonConfigField(Category.Healing, lang = "max_healer_charge", SERVER)
var maxHealerCharge = 6.0f
@CobblemonConfigField(Category.Healing, lang = "seconds_to_charge_healing_machine")
@CobblemonConfigField(Category.Healing, lang = "seconds_to_charge_healing_machine", SERVER)
var secondsToChargeHealingMachine = 900.0
@CobblemonConfigField(Category.Healing, lang = "default_faint_timer")
@CobblemonConfigField(Category.Healing, lang = "default_faint_timer", SERVER)
var defaultFaintTimer = 300
@CobblemonConfigField(Category.Healing, lang = "faint_awaken_health_percent")
@CobblemonConfigField(Category.Healing, lang = "faint_awaken_health_percent", SERVER)
var faintAwakenHealthPercent = 0.2f
@CobblemonConfigField(Category.Healing, lang = "heal_percent")
@CobblemonConfigField(Category.Healing, lang = "heal_percent", SERVER)
var healPercent = 0.05
@CobblemonConfigField(Category.Healing, lang = "heal_timer")
@CobblemonConfigField(Category.Healing, lang = "heal_timer", SERVER)
var healTimer = 60
@CobblemonConfigField(Category.Spawning, lang = "base_apricorn_tree_generation_chance")
@CobblemonConfigField(Category.Spawning, lang = "base_apricorn_tree_generation_chance", SERVER)
var baseApricornTreeGenerationChance = 0.1F
@CobblemonConfigField(Category.Pokemon, lang = "display_entity_level_label")
@CobblemonConfigField(Category.Pokemon, lang = "display_entity_level_label", CLIENT)
var displayEntityLevelLabel = true
@CobblemonConfigField(Category.Pokemon, lang = "display_entity_name_label")
@CobblemonConfigField(Category.Pokemon, lang = "display_entity_name_label", CLIENT)
var displayEntityNameLabel = true
@CobblemonConfigField(Category.Pokemon, lang = "display_name_for_unknown_pokemon")
@CobblemonConfigField(Category.Pokemon, lang = "display_name_for_unknown_pokemon", CLIENT)
var displayNameForUnknownPokemon = false
@CobblemonConfigField(Category.Pokemon, lang = "display_entity_labels_when_crouching_only")
@CobblemonConfigField(Category.Pokemon, lang = "display_entity_labels_when_crouching_only", CLIENT)
var displayEntityLabelsWhenCrouchingOnly = false
@CobblemonConfigField(Category.Spawning, lang = "shiny_rate")
@CobblemonConfigField(Category.Spawning, lang = "shiny_rate", SERVER)
var shinyRate = 8192F
@CobblemonConfigField(Category.Pokemon, lang = "shiny_notice_particles_distance")
@CobblemonConfigField(Category.Pokemon, lang = "shiny_notice_particles_distance", CLIENT)
var shinyNoticeParticlesDistance = 24F
@CobblemonConfigField(Category.Pokemon, lang = "capture_calculator")
@CobblemonConfigField(Category.Pokemon, lang = "capture_calculator", SERVER)
var captureCalculator: CaptureCalculator = CobblemonCaptureCalculator
@CobblemonConfigField(Category.Pokemon, lang = "player_damage_pokemon")
@CobblemonConfigField(Category.Pokemon, lang = "player_damage_pokemon", SERVER)
var playerDamagePokemon = true
@CobblemonConfigField(Category.World, lang = "apple_leftovers_chance")
@CobblemonConfigField(Category.World, lang = "apple_leftovers_chance", SERVER)
var appleLeftoversChance = 0.025
@CobblemonConfigField(Category.World, lang = "max_roots_in_area")
@CobblemonConfigField(Category.World, lang = "max_roots_in_area", SERVER)
var maxRootsInArea = 5
@CobblemonConfigField(Category.World, lang = "big_root_propagation_chance")
@CobblemonConfigField(Category.World, lang = "big_root_propagation_chance", SERVER)
var bigRootPropagationChance = 0.1
@CobblemonConfigField(Category.World, lang = "energy_root_chance")
@CobblemonConfigField(Category.World, lang = "energy_root_chance", SERVER)
var energyRootChance = 0.25
@CobblemonConfigField(Category.Pokemon, lang = "max_dynamax_level")
@CobblemonConfigField(Category.Pokemon, lang = "max_dynamax_level", SERVER)
@IntConstraint(min = 0, max = 10)
var maxDynamaxLevel = 10
@CobblemonConfigField(Category.Spawning, lang = "tera_type_rate")
@CobblemonConfigField(Category.Spawning, lang = "tera_type_rate", SERVER)
var teraTypeRate = 20F
@CobblemonConfigField(Category.World, lang = "default_pastured_pokemon_limit")
@CobblemonConfigField(Category.World, lang = "default_pastured_pokemon_limit", SERVER)
var defaultPasturedPokemonLimit = 16
@CobblemonConfigField(Category.World, lang = "pasture_block_update_ticks")
@CobblemonConfigField(Category.World, lang = "pasture_block_update_ticks", SERVER)
var pastureBlockUpdateTicks = 40
@CobblemonConfigField(Category.World, lang = "pasture_max_wander_distance")
@CobblemonConfigField(Category.World, lang = "pasture_max_wander_distance", SERVER)
var pastureMaxWanderDistance = 64
@CobblemonConfigField(Category.World, lang = "pasture_max_per_chunk")
@CobblemonConfigField(Category.World, lang = "pasture_max_per_chunk", SERVER)
var pastureMaxPerChunk = 4F
@CobblemonConfigField(Category.World, lang = "max_inserted_fossil_items")
@CobblemonConfigField(Category.World, lang = "max_inserted_fossil_items", SERVER)
var maxInsertedFossilItems = 2
@CobblemonConfigField(Category.Battles, lang = "walking_in_battle_animations")
@CobblemonConfigField(Category.Battles, lang = "walking_in_battle_animations", CLIENT)
var walkingInBattleAnimations = false
@CobblemonConfigField(Category.Battles, lang = "battle_wild_max_distance")
@CobblemonConfigField(Category.Battles, lang = "battle_wild_max_distance", SERVER)
var battleWildMaxDistance = 12F
@CobblemonConfigField(Category.World, lang = "trade_max_distance")
@CobblemonConfigField(Category.World, lang = "trade_max_distance", SERVER)
var tradeMaxDistance = 12F
@CobblemonConfigField(Category.Battles, lang = "battle_pvp_max_distance")
@CobblemonConfigField(Category.Battles, lang = "battle_pvp_max_distance", SERVER)
@SerializedName("battlePvPMaxDistance", alternate = ["BattlePvPMaxDistance"])
var battlePvPMaxDistance = 32F
@CobblemonConfigField(Category.Battles, lang = "battle_spectate_max_distance")
@CobblemonConfigField(Category.Battles, lang = "battle_spectate_max_distance", SERVER)
var battleSpectateMaxDistance = 64F
@CobblemonConfigField(Category.Pokedex, lang = "max_pokedex_scanning_detection_range")
@CobblemonConfigField(Category.Pokedex, lang = "max_pokedex_scanning_detection_range", SERVER)
var maxPokedexScanningDetectionRange = 10.0
@CobblemonConfigField(Category.Pokedex, lang = "hide_unimplemented_pokemon_in_the_pokedex")
@CobblemonConfigField(Category.Pokedex, lang = "hide_unimplemented_pokemon_in_the_pokedex", CLIENT)
var hideUnimplementedPokemonInThePokedex = false
@CobblemonConfigField(Category.Interface, lang = "summary_pokemon_follow_cursor")
@CobblemonConfigField(Category.Interface, lang = "summary_pokemon_follow_cursor", CLIENT)
var summaryPokemonFollowCursor = true
@CobblemonConfigField(Category.Interface, lang = "party_portrait_animations")
@CobblemonConfigField(Category.Interface, lang = "party_portrait_animations", CLIENT)
var partyPortraitAnimations = PortraitStyle.NEVER_ANIMATE
@CobblemonConfigField(Category.Riding, lang = "third_person_view_bobbing")
@CobblemonConfigField(Category.Riding, lang = "third_person_view_bobbing", CLIENT)
var thirdPersonViewBobbing = true
@CobblemonConfigField(Category.Riding, lang = "invert_roll")
@CobblemonConfigField(Category.Riding, lang = "invert_roll", CLIENT)
var invertRoll = false
@CobblemonConfigField(Category.Riding, lang = "invert_pitch")
@CobblemonConfigField(Category.Riding, lang = "invert_pitch", CLIENT)
var invertPitch = false
@CobblemonConfigField(Category.Riding, lang = "invert_yaw")
@CobblemonConfigField(Category.Riding, lang = "invert_yaw", CLIENT)
var invertYaw = false
@CobblemonConfigField(Category.Riding, lang = "x_axis_sensitivity")
@CobblemonConfigField(Category.Riding, lang = "x_axis_sensitivity", CLIENT)
var xAxisSensitivity = 1.0
@CobblemonConfigField(Category.Riding, lang = "y_axis_sensitivity")
@CobblemonConfigField(Category.Riding, lang = "y_axis_sensitivity", CLIENT)
var yAxisSensitivity = 1.0
@CobblemonConfigField(Category.Riding, lang = "swap_x_and_y_axes")
@CobblemonConfigField(Category.Riding, lang = "swap_x_and_y_axes", CLIENT)
var swapXAndYAxes = false
@CobblemonConfigField(Category.Riding, lang = "automatic_righting_delay")
@CobblemonConfigField(Category.Riding, lang = "automatic_righting_delay", CLIENT)
var rightingDelay = -1.0
@CobblemonConfigField(Category.Riding, lang = "disable_roll")
@CobblemonConfigField(Category.Riding, lang = "disable_roll", CLIENT)
var disableRoll = false
@CobblemonConfigField(Category.Riding, lang = "display_controls_duration_seconds")
@CobblemonConfigField(Category.Riding, lang = "display_controls_duration_seconds", CLIENT)
var displayControlSeconds = 3
@CobblemonConfigField(Category.Riding, lang = "infinite_ride_stamina")
@CobblemonConfigField(Category.Riding, lang = "infinite_ride_stamina", SERVER)
var infiniteRideStamina = false
@CobblemonConfigField(Category.Riding, lang = "remember_riding_camera")
@CobblemonConfigField(Category.Riding, lang = "remember_riding_camera", CLIENT)
var rememberRidingCamera = true
@CobblemonConfigField(Category.Debug, lang = "enable_debug_keys")
@CobblemonConfigField(Category.Debug, lang = "enable_debug_keys", CLIENT)
var enableDebugKeys = true
@CobblemonConfigField(Category.Spawning, lang = "despawner_near_distance")
@CobblemonConfigField(Category.Spawning, lang = "despawner_near_distance", SERVER)
var despawnerNearDistance = 32f
@CobblemonConfigField(Category.Spawning, lang = "despawner_far_distance")
@CobblemonConfigField(Category.Spawning, lang = "despawner_far_distance", SERVER)
var despawnerFarDistance = 96f
@CobblemonConfigField(Category.Spawning, lang = "despawner_min_age_ticks")
@CobblemonConfigField(Category.Spawning, lang = "despawner_min_age_ticks", SERVER)
var despawnerMinAgeTicks = 600
@CobblemonConfigField(Category.Spawning, lang = "despawner_max_age_ticks")
@CobblemonConfigField(Category.Spawning, lang = "despawner_max_age_ticks", SERVER)
var despawnerMaxAgeTicks = 3600
fun clone(): CobblemonConfig {
@@ -13,4 +13,10 @@ package com.cobblemon.mod.common.config
annotation class CobblemonConfigField(
val category: Category,
val lang: String,
)
val side: CobblemonConfigSide,
) {
enum class CobblemonConfigSide {
CLIENT,
SERVER,
}
}
@@ -10,8 +10,9 @@ package com.cobblemon.mod.common.config.starter
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.config.Category
import com.cobblemon.mod.common.config.LastChangedVersion
import com.cobblemon.mod.common.config.CobblemonConfigField
import com.cobblemon.mod.common.config.CobblemonConfigField.CobblemonConfigSide.SERVER
import com.cobblemon.mod.common.config.LastChangedVersion
import com.cobblemon.mod.common.util.adapters.pokemonPropertiesShortAdapter
import com.google.gson.GsonBuilder
@@ -24,17 +25,17 @@ class StarterConfig {
.create()
}
@CobblemonConfigField(Category.Starter, lang = "allow_starter_on_join")
@CobblemonConfigField(Category.Starter, lang = "allow_starter_on_join", SERVER)
var allowStarterOnJoin = true
@CobblemonConfigField(Category.Starter, lang = "prompt_starter_once_only")
@CobblemonConfigField(Category.Starter, lang = "prompt_starter_once_only", SERVER)
@LastChangedVersion("1.5.0")
var promptStarterOnceOnly = true
@CobblemonConfigField(Category.Starter, lang = "use_config_starters")
@CobblemonConfigField(Category.Starter, lang = "use_config_starters", SERVER)
var useConfigStarters = false
@CobblemonConfigField(Category.Starter, lang = "starters")
@CobblemonConfigField(Category.Starter, lang = "starters", SERVER)
var starters = mutableListOf(
StarterCategory(
name = "Kanto",
@@ -1,58 +0,0 @@
package com.cobblemon.mod.common.debug
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.mojang.blaze3d.vertex.PoseStack
import net.minecraft.client.Minecraft
import net.minecraft.client.renderer.MultiBufferSource
import net.minecraft.client.renderer.debug.DebugRenderer
import net.minecraft.core.BlockPos
import net.minecraft.core.Position
import java.util.function.Consumer
object RidingDebugRenderer : DebugRenderer.SimpleDebugRenderer {
var enabled = false
override fun render(
poseStack: PoseStack,
buffer: MultiBufferSource,
camX: Double,
camY: Double,
camZ: Double
) {
val minecraft = Minecraft.getInstance() ?: return
val vehicle = minecraft.player?.vehicle
if (vehicle != null && vehicle is PokemonEntity) {
DebugRenderer.renderFloatingText(poseStack, buffer, "riding", camX, camY, camZ, -1, 1f, false, 0.5f, true)
renderRidingInfo(vehicle, poseStack, buffer)
}
}
private fun renderRidingInfo(vehicle: PokemonEntity, poseStack: PoseStack, buffer: MultiBufferSource) {
val ridingController = vehicle.ridingController
if (ridingController != null) {
var i = 0
ridingController.context?.let { context ->
val debugString = "Currently active: ${context.behaviour}"
renderTextOverVehicle(poseStack, buffer, vehicle.position(), i, debugString, -1, 0.03f)
i++
}
ridingController.behaviours.forEach { (style, settings) ->
val debugString = "Available: $style"
renderTextOverVehicle(poseStack, buffer, vehicle.position(), i, debugString, -1, 0.03f)
i++
}
}
}
private fun renderTextOverVehicle(poseStack: PoseStack, buffer: MultiBufferSource, pos: Position, layer: Int, text: String, color: Int, scale: Float) {
val blockPos = BlockPos.containing(pos)
val x = blockPos.x.toDouble() + 0.5
val y = blockPos.y.toDouble() + 2.4 + layer * 0.25
val z = blockPos.z.toDouble() + 0.5
DebugRenderer.renderFloatingText(poseStack, buffer, text, x, y, z, color, scale, false, 0.5f, true)
}
fun clientHandler(enabled: Boolean) {
this.enabled = enabled
}
}
@@ -25,6 +25,7 @@ interface OmniPathingEntity {
fun canSwimInLava(): Boolean
fun canWalkOnLava(): Boolean
fun canSwimUnderFluid(fluidState: FluidState): Boolean
fun canPathThroughSaccLeaves(): Boolean
fun canFly(): Boolean
fun isFlying(): Boolean
fun setFlying(state: Boolean)
@@ -8,13 +8,16 @@
package com.cobblemon.mod.common.entity.ai
import com.cobblemon.mod.common.CobblemonBlocks
import com.cobblemon.mod.common.entity.OmniPathingEntity
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.pokemon.ai.OmniPathNodeMaker
import com.cobblemon.mod.common.util.deleteNode
import com.cobblemon.mod.common.util.getWaterAndLavaIn
import com.cobblemon.mod.common.util.toVec3d
import com.google.common.collect.ImmutableSet
import net.minecraft.core.BlockPos
import net.minecraft.tags.BlockTags
import net.minecraft.tags.FluidTags
import net.minecraft.util.Mth
import net.minecraft.world.damagesource.DamageSource
@@ -181,7 +184,9 @@ class OmniPathNavigation(val world: Level, val entity: Mob) : GroundPathNavigati
target = blockPos
}
val path = if (!this.world.getBlockState(target).isSolid) {
val blockState = this.world.getBlockState(target)
val path = if (!blockState.isSolid
|| (blockState.`is`(BlockTags.LEAVES) && blockState.block == CobblemonBlocks.SACCHARINE_LEAVES && (this.entity as PokemonEntity).canPathThroughSaccLeaves())) {
findPath(target, distance)
} else {
blockPos = target.above()
@@ -264,6 +269,9 @@ class OmniPathNavigation(val world: Level, val entity: Mob) : GroundPathNavigati
// If we can fly and we're airborne, return the current Y position
return vec.y
}
if (world.getBlockState(blockPos).block == CobblemonBlocks.SACCHARINE_LEAVES && (this.entity as PokemonEntity).canPathThroughSaccLeaves()) {
return vec.y + 0.5
}
return if ((canFloat()) && blockGetter.getFluidState(blockPos).`is`(FluidTags.WATER)) vec.y + 0.5 else super.getGroundY(vec)
}
@@ -11,6 +11,7 @@ package com.cobblemon.mod.common.entity.npc
import com.cobblemon.mod.common.CobblemonBehaviours
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.config.AddVariablesConfig
import com.cobblemon.mod.common.api.ai.config.ApplyBehaviours
import com.cobblemon.mod.common.api.ai.config.BehaviourConfig
import com.cobblemon.mod.common.api.npc.NPCClass
@@ -22,6 +23,7 @@ object NPCBrain {
if (npcEntity.behavioursAreCustom) {
behaviourConfigurations = listOf(ApplyBehaviours().apply { behaviours.addAll(npcEntity.behaviours) })
}
behaviourConfigurations = behaviourConfigurations + AddVariablesConfig(npcClass.config)
val ctx = BehaviourConfigurationContext()
ctx.addMemories(CobblemonMemories.DIALOGUES)
@@ -708,6 +708,10 @@ class NPCEntity(world: Level) : AgeableMob(CobblemonEntities.NPC, world), Npc, P
return false
}
override fun canPathThroughSaccLeaves(): Boolean {
return false
}
override fun canFly(): Boolean {
return false
}
@@ -393,7 +393,8 @@ class EmptyPokeBallEntity : ThrowableItemProjectile, PosableEntity, WaterDragMod
private fun breakFree() {
val pokemon = capturingPokemon ?: return
pokemon.setPos(position())
pokemon.setPositionSafely(position())
pokemon.beamMode = 2
pokemon.isInvisible = false
pokemon.isSilent = false
@@ -490,7 +491,6 @@ class EmptyPokeBallEntity : ThrowableItemProjectile, PosableEntity, WaterDragMod
fun beginCapture() {
// We have hit the ground, time to stop falling and start shaking! Calculate capture.
capturingPokemon?.setPositionSafely(position())
val thrower = owner as LivingEntity
val captureResult = Cobblemon.config.captureCalculator.processCapture(thrower, this, capturingPokemon!!).let {
val event = PokeBallCaptureCalculatedEvent(thrower = thrower, pokemonEntity = capturingPokemon!!, pokeBallEntity = this, captureResult = it)
@@ -18,6 +18,7 @@ import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.CobblemonNetwork.sendPacket
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.drop.DropTable
import com.cobblemon.mod.common.api.entity.Despawner
@@ -165,6 +166,7 @@ import net.minecraft.world.entity.ai.attributes.AttributeSupplier
import net.minecraft.world.entity.ai.attributes.Attributes
import net.minecraft.world.entity.ai.control.MoveControl
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.entity.ai.memory.MemoryStatus
import net.minecraft.world.entity.ai.sensing.Sensor
import net.minecraft.world.entity.ai.sensing.SensorType
import net.minecraft.world.entity.animal.Animal
@@ -1186,6 +1188,7 @@ open class PokemonEntity(
if (!this.canRide(player)) return@ifRidingAvailableSupply false
if (tethering != null) return@ifRidingAvailableSupply false
if (seats.isEmpty()) return@ifRidingAvailableSupply false
if ((owner as? ServerPlayer)?.isInBattle() == true) return@ifRidingAvailableSupply false
if (this.owner != player && this.passengers.isEmpty()) return@ifRidingAvailableSupply false
return@ifRidingAvailableSupply behaviour.isActive(settings, state, this)
}
@@ -1244,11 +1247,19 @@ open class PokemonEntity(
}
override fun checkDespawn() {
if (pokemon.getOwnerUUID() == null && !isPersistenceRequired && (!this.pokemon.canDropHeldItem || this.pokemon.heldItem.isEmpty) && despawner.shouldDespawn(this) ) {
if (pokemon.getOwnerUUID() == null && !isPersistenceRequired && despawner.shouldDespawn(this) ) {
discard()
}
}
override fun isPersistenceRequired(): Boolean {
return super.isPersistenceRequired()
|| (this.pokemon.canDropHeldItem && !this.pokemon.heldItem.isEmpty)
|| this.brain.checkMemory(CobblemonMemories.HIVE_LOCATION, MemoryStatus.VALUE_PRESENT)
|| this.brain.checkMemory(CobblemonMemories.HIVE_COOLDOWN, MemoryStatus.VALUE_PRESENT)
|| this.brain.checkMemory(CobblemonMemories.NEARBY_SACC_LEAVES, MemoryStatus.VALUE_PRESENT)
}
fun setBehaviourFlag(flag: PokemonBehaviourFlag, on: Boolean) {
entityData.set(BEHAVIOUR_FLAGS, setBitForByte(entityData.get(BEHAVIOUR_FLAGS), flag.bit, on))
}
@@ -2177,9 +2188,19 @@ open class PokemonEntity(
}
this.yRotO = this.yRot
val rotation = behaviour.rotation(settings, state, this, driver)
setRot(rotation.y, rotation.x)
if (this is OrientationControllable) {
val controller = this.orientationController
if (!controller.isActive()) {
val rotation = behaviour.rotation(settings, state, this, driver)
// TODO: Find a better solution than setting the vehicle xrot to zero.
// The problem is that nothing actually effects the vehicle/pokemon xrot so when it gets set to
// -45 degrees by a rollable ride controller it gets saved off and not modified until you try
// and takeoff again. And at that point you snap to -45 pitch in the orientationControllers matrix
setRot(rotation.y, 0.0f)
}
}
this.yHeadRot = this.yRot
this.yBodyRot = this.yRot
this.passengers.filterIsInstance<LivingEntity>()
@@ -2300,6 +2321,12 @@ open class PokemonEntity(
return loc.compareTo(resourceLocation) == 0
}
fun applyRenderRotation(partialTicks: Float) {
ifRidingAvailable { behaviour, settings, state ->
behaviour.applyRenderRotation(settings, state, this, partialTicks)
}
}
var jumpInputStrength: Int = 0 // move this
override fun onPlayerJump(strength: Int) {
// See if this controls the hot bar element
@@ -2430,6 +2457,7 @@ open class PokemonEntity(
override fun canWalkOnWater() = exposedForm.behaviour.moving.swim.canWalkOnWater
override fun canFly() = exposedForm.behaviour.moving.fly.canFly
override fun canSwimInLava() = exposedForm.behaviour.moving.swim.canSwimInLava
override fun canPathThroughSaccLeaves() = this.config.getMap().getOrDefault("can_path_through_sacc_leaves", DoubleValue.ZERO).asDouble() == 1.0
override fun canWalkOnLava() = exposedForm.behaviour.moving.swim.canWalkOnLava
override fun entityOnGround() = onGround()
@@ -9,17 +9,22 @@
package com.cobblemon.mod.common.entity.pokemon.ai.sensors
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.block.SaccharineLeafBlock
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.server.level.ServerLevel
import net.minecraft.tags.PoiTypeTags
import net.minecraft.world.entity.ai.sensing.Sensor
import net.minecraft.world.level.block.BeehiveBlock
import net.minecraft.world.entity.ai.village.poi.PoiManager
import net.minecraft.world.entity.ai.village.poi.PoiRecord
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.Blocks
import net.minecraft.world.level.block.entity.BeehiveBlockEntity
import net.minecraft.world.level.block.entity.BlockEntity
class BeeHiveSensor : Sensor<PokemonEntity>(300) {
override fun requires() = setOf(CobblemonMemories.HIVE_LOCATION)
override fun requires() = setOf(CobblemonMemories.HIVE_LOCATION, CobblemonMemories.HIVE_BLACKLIST)
override fun doTick(world: ServerLevel, entity: PokemonEntity) {
val brain = entity.brain
val currentHive = brain.getMemory(CobblemonMemories.HIVE_LOCATION).orElse(null)
@@ -27,35 +32,30 @@ class BeeHiveSensor : Sensor<PokemonEntity>(300) {
if (currentHive != null) {
val state = world.getBlockState(currentHive)
if (!isValidHiveOrLeaf(state) || !hasReachableAdjacentSide(world, entity, currentHive)) {
brain.eraseMemory(CobblemonMemories.HIVE_LOCATION)
} else if (isAtMaxHoney(state)) {
if (!isValidHive(state) || !hasReachableAdjacentSide(world, currentHive)) {
brain.eraseMemory(CobblemonMemories.HIVE_LOCATION)
} else {
return // We already have a valid hive, no need to search for another
}
}
// Search for nearest hive/nest
val searchRadius = 16 // is this too big for us to use?
val centerPos = entity.blockPosition()
var closestHivePos: BlockPos? = null
var closestDistance = Double.MAX_VALUE
BlockPos.betweenClosedStream(
centerPos.offset(-searchRadius, -2, -searchRadius),
centerPos.offset(searchRadius, 2, searchRadius)
).forEach { pos ->
val state = world.getBlockState(pos)
val list = findNearbyHivesWithSpace(entity)
if (isValidHiveOrLeaf(state) && !isAtMaxHoney(state) && hasReachableAdjacentSide(world, entity, pos)) {
val distance = pos.distToCenterSqr(centerPos.x + 0.5, centerPos.y + 0.5, centerPos.z + 0.5)
if (distance < closestDistance) {
closestDistance = distance
closestHivePos = pos.immutable()
if (!list.isEmpty()) {
val blackList = brain.getMemory(CobblemonMemories.HIVE_BLACKLIST).orElse(emptyList())
for (blockPos in list) {
if (blackList.contains(blockPos)) continue
if (hasReachableAdjacentSide(world, blockPos)) {
closestHivePos = blockPos
break
}
}
if (closestHivePos == null) {
brain.eraseMemory(CobblemonMemories.HIVE_BLACKLIST)
closestHivePos = list.first()
}
}
if (closestHivePos != null) {
@@ -63,37 +63,47 @@ class BeeHiveSensor : Sensor<PokemonEntity>(300) {
}
}
private fun isAtMaxHoney(state: net.minecraft.world.level.block.state.BlockState): Boolean {
return when {
isHiveBlock(state) -> state.getValue(BeehiveBlock.HONEY_LEVEL) == BeehiveBlock.MAX_HONEY_LEVELS
isSaccharineLeafBlock(state) -> state.getValue(SaccharineLeafBlock.AGE) == SaccharineLeafBlock.MAX_AGE
else -> true // Unknown block type
}
private fun isValidHive(state: net.minecraft.world.level.block.state.BlockState): Boolean {
return isHiveBlock(state)
}
private fun isValidHiveOrLeaf(state: net.minecraft.world.level.block.state.BlockState): Boolean {
return isHiveBlock(state) || isSaccharineLeafBlock(state)
}
private fun isSaccharineLeafBlock(state: net.minecraft.world.level.block.state.BlockState): Boolean {
return state.block is SaccharineLeafBlock
}
private fun isHiveBlock(state: net.minecraft.world.level.block.state.BlockState): Boolean {
return state.`is`(Blocks.BEEHIVE) || state.`is`(Blocks.BEE_NEST)
}
private fun hasReachableAdjacentSide(world: ServerLevel, entity: PokemonEntity, pos: BlockPos): Boolean {
private fun hasReachableAdjacentSide(world: ServerLevel, pos: BlockPos): Boolean {
for (dir in Direction.entries) {
val adjacentPos = pos.relative(dir)
if (world.isEmptyBlock(adjacentPos)) {
val nav = entity.navigation
val path = nav.createPath(adjacentPos, 0)
if (path != null && path.canReach()) {
return true
}
return true
}
}
return false
}
private fun findNearbyHivesWithSpace(entity: PokemonEntity): List<BlockPos> {
val blockPos = entity.blockPosition()
val poiManager = (entity.level() as ServerLevel).poiManager
val stream = poiManager.getInRange(
{ holder -> holder.`is`(PoiTypeTags.BEE_HOME) },
blockPos,
20,
PoiManager.Occupancy.ANY
)
return stream
.map(PoiRecord::getPos)
.filter { pos -> doesHiveHaveSpace(pos, entity.level()) }
.sorted { pos1, pos2 -> pos1.distSqr(blockPos).toInt() - pos2.distSqr(blockPos).toInt() }
.toList()
}
private fun doesHiveHaveSpace(hivePos: BlockPos, world: Level): Boolean {
val blockEntity: BlockEntity? = world.getBlockEntity(hivePos)
if (blockEntity is BeehiveBlockEntity) {
return !blockEntity.isFull
} else {
return false
}
}
}
@@ -10,46 +10,73 @@ package com.cobblemon.mod.common.entity.pokemon.ai.sensors
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.distanceTo
import com.cobblemon.mod.common.util.getMemorySafely
import net.minecraft.core.BlockPos
import net.minecraft.core.Vec3i
import net.minecraft.server.level.ServerLevel
import net.minecraft.tags.BlockTags
import net.minecraft.world.entity.ai.sensing.Sensor
import net.minecraft.world.level.block.Blocks
import net.minecraft.world.level.block.DoublePlantBlock
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.properties.BlockStateProperties
import net.minecraft.world.level.block.state.properties.DoubleBlockHalf
class FlowerSensor : Sensor<PokemonEntity>(120) {
override fun requires() = setOf(CobblemonMemories.NEARBY_FLOWERS)
override fun requires() = setOf(CobblemonMemories.NEARBY_FLOWER)
override fun doTick(world: ServerLevel, entity: PokemonEntity) {
val brain = entity.brain
val searchRadius = 32
val centerPos = entity.blockPosition()
val flowers = mutableListOf<BlockPos>()
// Check curr memory
val currPos = brain.getMemorySafely(CobblemonMemories.NEARBY_FLOWER).orElse(null)
if (currPos != null && isFlower(entity.level().getBlockState(currPos)) && entity.distanceTo(currPos) <= 32) {
return
}
val searchRadius = 5
val centerPos = entity.blockPosition()
var flowerPos: BlockPos? = null
var shortestDist = Double.MAX_VALUE
BlockPos.betweenClosedStream(
centerPos.offset(-searchRadius, -2, -searchRadius),
centerPos.offset(searchRadius, 2, searchRadius)
).forEach { pos ->
val state = world.getBlockState(pos)
if (isFlower(state) && isPathfindableTo(entity, pos)) {
flowers += pos.immutable()
if (isFlower(state)) {
val distance = pos.distSqr(Vec3i(centerPos.x, centerPos.y, centerPos.z))
if (distance < shortestDist) {
flowerPos = BlockPos(pos)
shortestDist = distance
}
}
}
if (!flowers.isEmpty()) {
brain.setMemory(CobblemonMemories.NEARBY_FLOWERS, flowers)
if (flowerPos != null) {
brain.setMemory(CobblemonMemories.NEARBY_FLOWER, flowerPos)
} else {
brain.eraseMemory(CobblemonMemories.NEARBY_FLOWERS)
brain.eraseMemory(CobblemonMemories.NEARBY_FLOWER)
}
}
private fun isFlower(state: BlockState): Boolean {
return state.`is`(BlockTags.FLOWERS)
}
private fun isPathfindableTo(entity: PokemonEntity, pos: BlockPos): Boolean {
val nav = entity.navigation
val path = nav.createPath(pos, 0)
return path != null && path.canReach()
// Borrowed from Bee.class
if (state.hasProperty(BlockStateProperties.WATERLOGGED) && state.getValue(
BlockStateProperties.WATERLOGGED
) as Boolean
) {
return false
} else if (state.`is`(BlockTags.FLOWERS)) {
if (state.`is`(Blocks.SUNFLOWER)) {
return state.getValue(DoublePlantBlock.HALF) == DoubleBlockHalf.UPPER
} else {
return true
}
} else {
return false
}
}
}
@@ -0,0 +1,79 @@
/*
* 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.entity.pokemon.ai.sensors
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.block.SaccharineLeafBlock
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.distanceTo
import com.cobblemon.mod.common.util.getMemorySafely
import net.minecraft.core.BlockPos
import net.minecraft.core.Vec3i
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.entity.ai.sensing.Sensor
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.properties.BlockStateProperties
import kotlin.time.measureTime
class SacLeavesSensor : Sensor<PokemonEntity>(120) {
override fun requires() = setOf(CobblemonMemories.NEARBY_SACC_LEAVES)
override fun doTick(world: ServerLevel, entity: PokemonEntity) {
val brain = entity.brain
val searchRadius = 7
val tooFarDistance = 30
val wayTooFarDistance = 64
val currPos: BlockPos? = brain.getMemorySafely(CobblemonMemories.NEARBY_SACC_LEAVES).orElse(null)
if (currPos != null && isValidLeafBlock(entity.level().getBlockState(currPos))) {
val distance = entity.distanceTo(currPos)
if (distance <= tooFarDistance) {
return
} else if (distance > wayTooFarDistance) {
brain.eraseMemory(CobblemonMemories.NEARBY_SACC_LEAVES)
}
}
val centerPos = entity.blockPosition()
var leavesPos: BlockPos? = null
var shortestDist = Double.MAX_VALUE
BlockPos.betweenClosedStream(
centerPos.offset(-searchRadius, -2, -searchRadius),
centerPos.offset(searchRadius, searchRadius, searchRadius)
).forEach { pos ->
val state = world.getBlockState(pos)
if (isValidLeafBlock(state)) {
val distance = pos.distSqr(Vec3i(centerPos.x, centerPos.y, centerPos.z))
if (distance < shortestDist) {
leavesPos = BlockPos(pos)
shortestDist = distance
}
}
}
if (leavesPos != null) {
brain.setMemory(CobblemonMemories.NEARBY_SACC_LEAVES, leavesPos)
} else {
brain.eraseMemory(CobblemonMemories.NEARBY_SACC_LEAVES)
}
}
private fun isValidLeafBlock(state: BlockState): Boolean {
if (state.block !is SaccharineLeafBlock) return false
if (state.hasProperty(BlockStateProperties.WATERLOGGED) && state.getValue(
BlockStateProperties.WATERLOGGED
) as Boolean
) {
return false
}
return state.getValue(SaccharineLeafBlock.AGE) != SaccharineLeafBlock.MAX_AGE
}
}
@@ -1,77 +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.entity.pokemon.ai.tasks
import com.cobblemon.mod.common.CobblemonMemories
import net.minecraft.core.BlockPos
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.PathfinderMob
import net.minecraft.world.entity.ai.behavior.BlockPosTracker
import net.minecraft.world.entity.ai.behavior.OneShot
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.entity.ai.memory.WalkTarget
import net.minecraft.world.phys.Vec3
object PathToBeeHiveTask {
fun create(): OneShot<in LivingEntity> {
return BehaviorBuilder.create {
it.group(
it.registered(MemoryModuleType.LOOK_TARGET),
it.absent(MemoryModuleType.WALK_TARGET),
it.present(CobblemonMemories.POLLINATED),
it.present(CobblemonMemories.HIVE_LOCATION),
it.absent(CobblemonMemories.HIVE_COOLDOWN)
).apply(it) { lookTarget, walkTarget, pollinated, hiveMemory, hiveCooldown ->
Trigger { world, entity, time ->
if (entity !is PathfinderMob || !entity.isAlive) {
return@Trigger false
}
val hiveLocation = it.get(hiveMemory)
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@Trigger false
}
}
val targetVec = Vec3.atCenterOf(openSide)
// Set path target toward hive
walkTarget.set(WalkTarget(targetVec, 0.35F, 0))
lookTarget.set(BlockPosTracker(targetVec.add(0.0, entity.eyeHeight.toDouble(), 0.0)))
return@Trigger true
}
}
}
}
}
@@ -9,6 +9,7 @@
package com.cobblemon.mod.common.entity.pokemon.ai.tasks
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.util.getMemorySafely
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.PathfinderMob
import net.minecraft.world.entity.ai.behavior.BlockPosTracker
@@ -20,31 +21,26 @@ import net.minecraft.world.entity.ai.memory.WalkTarget
import net.minecraft.world.phys.Vec3
object PathToFlowerTask {
fun create(): OneShot<in LivingEntity> {
return BehaviorBuilder.create {
it.group(
it.registered(MemoryModuleType.LOOK_TARGET),
it.absent(MemoryModuleType.WALK_TARGET),
it.present(CobblemonMemories.NEARBY_FLOWERS),
it.absent(CobblemonMemories.POLLINATED),
it.present(CobblemonMemories.NEARBY_FLOWER),
it.registered(CobblemonMemories.HAS_NECTAR),
it.absent(CobblemonMemories.PATH_TO_NEARBY_FLOWER_COOLDOWN),
it.absent(CobblemonMemories.HIVE_COOLDOWN)
).apply(it) { lookTarget, walkTarget, flowerMemory, pollinated, hiveCooldown ->
).apply(it) { lookTarget, walkTarget, flowerMemory, pollinated, flowerCooldown, hiveCooldown ->
Trigger { world, entity, time ->
if (entity !is PathfinderMob || !entity.isAlive) return@Trigger false
val flowerLocations = it.get(flowerMemory).map(Vec3::atCenterOf)
if (flowerLocations.isEmpty()) {
return@Trigger false
}
val sortedByDistance = flowerLocations.sortedBy { it.distanceTo(entity.position()) }
val closestHalf = sortedByDistance.subList(0, flowerLocations.size / 2 + 1)
if (closestHalf.isEmpty()) {
return@Trigger false
}
val targetVec = closestHalf.random()
if (entity !is PathfinderMob || !entity.isAlive || world.isNight || world.isRaining || entity.brain.getMemorySafely(CobblemonMemories.HAS_NECTAR).orElse(false)) return@Trigger false
val flowerLocation = it.get(flowerMemory)
val targetVec = Vec3.atCenterOf(flowerLocation)
// if we're really close to one then forget it, pollination is gonna occur
if (flowerLocations.any { it.distanceToSqr(entity.x, entity.y, entity.z) <= 1.0 }) {
if (targetVec.distanceToSqr(entity.x, entity.y, entity.z) <= 1.0 ) {
return@Trigger false
}
@@ -10,33 +10,34 @@ package com.cobblemon.mod.common.entity.pokemon.ai.tasks
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.toVec3d
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.PathfinderMob
import net.minecraft.world.entity.ai.behavior.BlockPosTracker
import net.minecraft.world.entity.ai.behavior.OneShot
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.phys.Vec3
import net.minecraft.world.entity.ai.memory.WalkTarget
object PathToSacLeafTask {
object PollinateFlowerTask {
fun create(): OneShot<in LivingEntity> {
return BehaviorBuilder.create {
it.group(
it.present(CobblemonMemories.NEARBY_FLOWERS),
it.registered(MemoryModuleType.LOOK_TARGET),
it.absent(MemoryModuleType.WALK_TARGET),
it.absent(CobblemonMemories.POLLINATED),
it.absent(CobblemonMemories.HIVE_COOLDOWN)
).apply(it) { flowerMemory, walkTarget, pollinated, hiveCooldown ->
it.present(CobblemonMemories.HAS_NECTAR),
it.present(CobblemonMemories.NEARBY_SACC_LEAVES)
).apply(it) { lookTarget, walkTarget, pollinated, saccLeavesPos ->
Trigger { world, entity, time ->
if (entity !is PathfinderMob || !entity.isAlive) return@Trigger false
val flowerLocations = it.get(flowerMemory)
if (flowerLocations.any { it.distSqr(entity.blockPosition()) < 1 }) {
entity.brain.setMemoryWithExpiry(CobblemonMemories.POLLINATED, true, 20 * 20L) // 20 seconds to dump the pollen
if (entity is PokemonEntity) {
entity.pokemon.updateAspects()
}
if (entity !is PathfinderMob || !entity.isAlive || entity !is PokemonEntity || !it.get(pollinated) || world.isRaining || world.isNight) {
return@Trigger false
}
val targetPos = it.get(saccLeavesPos)
walkTarget.set(WalkTarget(targetPos, 0.35F, 0))
lookTarget.set(BlockPosTracker(targetPos.toVec3d().add(0.0, entity.eyeHeight.toDouble(), 0.0)))
return@Trigger true
}
@@ -9,31 +9,33 @@
package com.cobblemon.mod.common.entity.pokemon.ai.tasks
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.block.SaccharineLeafBlock
import net.minecraft.world.entity.LivingEntity
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.getMemorySafely
import net.minecraft.world.entity.PathfinderMob
import net.minecraft.world.entity.ai.behavior.OneShot
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.entity.ai.memory.MemoryStatus
import net.minecraft.world.level.block.BeehiveBlock
import net.minecraft.world.level.block.entity.BeehiveBlockEntity
import net.minecraft.world.phys.Vec3
object PlaceHoneyInHiveTask {
fun create(): OneShot<in LivingEntity> {
fun create(): OneShot<PokemonEntity> {
return BehaviorBuilder.create {
it.group(
it.absent(MemoryModuleType.WALK_TARGET),
it.present(CobblemonMemories.POLLINATED),
it.registered(CobblemonMemories.HAS_NECTAR),
it.present(CobblemonMemories.HIVE_LOCATION),
it.absent(CobblemonMemories.HIVE_COOLDOWN)
it.absent(CobblemonMemories.HIVE_COOLDOWN),
).apply(it) { walkTarget, pollinated, hiveMemory, hiveCooldown ->
Trigger { world, entity, time ->
if (entity !is PathfinderMob) {
if (entity !is PathfinderMob || !wantsToEnterHive(entity)) {
return@Trigger false
}
val hiveCooldown = 1200L
val hiveLocation = it.get(hiveMemory)
val targetVec = Vec3.atCenterOf(hiveLocation)
@@ -45,19 +47,13 @@ object PlaceHoneyInHiveTask {
val state = world.getBlockState(hiveLocation)
val block = state.block
if (block is BeehiveBlock) {
val currentLevel = state.getValue(BeehiveBlock.HONEY_LEVEL)
if (currentLevel < BeehiveBlock.MAX_HONEY_LEVELS) {
world.setBlock(hiveLocation, state.setValue(BeehiveBlock.HONEY_LEVEL, currentLevel + 1), 3)
entity.brain.setMemoryWithExpiry(CobblemonMemories.HIVE_COOLDOWN, true, hiveCooldown)
entity.brain.eraseMemory(CobblemonMemories.POLLINATED)
}
} else if (block is SaccharineLeafBlock) {
val currentAge = state.getValue(SaccharineLeafBlock.AGE)
if (currentAge < SaccharineLeafBlock.MAX_AGE) {
world.setBlock(hiveLocation, state.setValue(SaccharineLeafBlock.AGE, currentAge + 1), 3)
entity.brain.setMemoryWithExpiry(CobblemonMemories.HIVE_COOLDOWN, true, hiveCooldown)
entity.brain.eraseMemory(CobblemonMemories.POLLINATED)
val blockEntity = world.getBlockEntity(hiveLocation)
if (blockEntity !is BeehiveBlockEntity) return@Trigger false
if (blockEntity.isFull) {
// Erase Hive memory and give the bee a moment to locate a new hive
entity.brain.eraseMemory(CobblemonMemories.HIVE_LOCATION)
}
blockEntity.addOccupant(entity)
}
return@Trigger true
@@ -65,4 +61,15 @@ object PlaceHoneyInHiveTask {
}
}
}
fun wantsToEnterHive( entity: PokemonEntity) : Boolean {
val result = !entity.brain.checkMemory(CobblemonMemories.HIVE_COOLDOWN, MemoryStatus.VALUE_PRESENT)
&& (entity.level().isRaining || entity.level().isNight || entity.brain.getMemory(CobblemonMemories.HAS_NECTAR).orElse(false))
if (result) {
val blockPos = entity.brain.getMemorySafely(CobblemonMemories.HIVE_LOCATION).orElse(null)
val blockEntity = entity.level().getBlockEntity(blockPos)
return blockEntity is BeehiveBlockEntity && !blockEntity.isFireNearby
}
return false
}
}
@@ -102,13 +102,11 @@ class AprijuiceItem(val type: Apricorn): CobblemonItem(Properties().stacksTo(16)
if (!canUseOnPokemon(stack, pokemon)) {
return InteractionResultHolder.fail(stack)
}
val boosts = getBoosts(stack, pokemon)
// Feed the Pokémon 1 fullness point
pokemon.feedPokemon(1)
boosts.forEach { (stat, value) ->
pokemon.addRideBoost(stat, value)
}
pokemon.feedPokemon(1)
val boosts = getBoosts(stack, pokemon)
pokemon.addRideBoosts(boosts)
stack.consume(1, player)
@@ -21,6 +21,7 @@ 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.util.cobblemonResource
import net.minecraft.network.RegistryFriendlyByteBuf
@@ -39,7 +40,8 @@ class RideSettingsSyncPacket(
val vehicle: VehicleSettings,
val boat: BoatSettings,
val burst: BurstSettings,
val dolphin: DolphinSettings
val dolphin: DolphinSettings,
val submarine: SubmarineSettings
) : NetworkPacket<RideSettingsSyncPacket> {
override val id = ID
@@ -56,6 +58,7 @@ class RideSettingsSyncPacket(
boat.encode(buffer)
burst.encode(buffer)
dolphin.encode(buffer)
submarine.encode(buffer)
}
companion object {
@@ -73,7 +76,8 @@ class RideSettingsSyncPacket(
vehicle = VehicleSettings(),
boat = BoatSettings(),
burst = BurstSettings(),
dolphin = DolphinSettings()
dolphin = DolphinSettings(),
submarine = SubmarineSettings()
).apply {
bird.decode(buffer)
glider.decode(buffer)
@@ -87,6 +91,7 @@ class RideSettingsSyncPacket(
boat.decode(buffer)
burst.decode(buffer)
dolphin.decode(buffer)
submarine.decode(buffer)
}
}
}
@@ -15,9 +15,13 @@ import com.cobblemon.mod.common.util.writeMatrix3f
import net.minecraft.network.RegistryFriendlyByteBuf
import org.joml.Matrix3f
class ServerboundUpdateOrientationPacket internal constructor(val orientation: Matrix3f?) : NetworkPacket<ServerboundUpdateOrientationPacket> {
class ServerboundUpdateOrientationPacket internal constructor(
val entity: Int,
val orientation: Matrix3f?
) : NetworkPacket<ServerboundUpdateOrientationPacket> {
override val id = ID
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeInt(entity)
buffer.writeBoolean(orientation != null)
if (orientation != null) {
buffer.writeMatrix3f(orientation)
@@ -27,8 +31,9 @@ class ServerboundUpdateOrientationPacket internal constructor(val orientation: M
companion object {
val ID = cobblemonResource("c2s_update_orientation")
fun decode(buffer: RegistryFriendlyByteBuf): ServerboundUpdateOrientationPacket {
val entity = buffer.readInt()
val orientation = if (buffer.readBoolean()) buffer.readMatrix3f() else null
return ServerboundUpdateOrientationPacket(orientation)
return ServerboundUpdateOrientationPacket(entity, orientation)
}
}
}
@@ -0,0 +1,37 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.net.messages.server.riding
import com.cobblemon.mod.common.api.net.NetworkPacket
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.world.phys.Vec3
class ServerboundUpdateRiderRotationPacket internal constructor(
val riderXRot: Float,
val riderYRot: Float,
val rideEyePos: Vec3
) : NetworkPacket<ServerboundUpdateRiderRotationPacket> {
override val id = ID
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeFloat(riderXRot)
buffer.writeFloat(riderYRot)
buffer.writeVec3(rideEyePos)
}
companion object {
val ID = cobblemonResource("c2s_update_rider_rotation")
fun decode(buffer: RegistryFriendlyByteBuf): ServerboundUpdateRiderRotationPacket {
val riderXRot = buffer.readFloat()
val riderYRot = buffer.readFloat()
val rideEyePos = buffer.readVec3()
return ServerboundUpdateRiderRotationPacket(riderXRot, riderYRot, rideEyePos)
}
}
}
@@ -10,15 +10,19 @@ package com.cobblemon.mod.common.net.serverhandling.orientation
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.api.net.ServerNetworkPacketHandler
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.net.messages.server.orientation.ServerboundUpdateOrientationPacket
import net.minecraft.server.MinecraftServer
import net.minecraft.server.level.ServerPlayer
import kotlin.math.atan2
object OrientationPacketHandler : ServerNetworkPacketHandler<ServerboundUpdateOrientationPacket> {
override fun handle(packet: ServerboundUpdateOrientationPacket, server: MinecraftServer, player: ServerPlayer) {
if (player is OrientationControllable) {
player.orientationController.updateOrientation { _ -> packet.orientation }
}
val entity = player.level().getEntity(packet.entity) ?: return
if (entity !is PokemonEntity) return
if (entity.controllingPassenger != player) return
if (entity !is OrientationControllable) return
entity.orientationController.updateOrientation { _ -> packet.orientation }
}
}
@@ -0,0 +1,30 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.net.serverhandling.riding
import com.cobblemon.mod.common.api.net.ServerNetworkPacketHandler
import com.cobblemon.mod.common.duck.RidePassenger
import com.cobblemon.mod.common.net.messages.server.riding.ServerboundUpdateRiderRotationPacket
import net.minecraft.server.MinecraftServer
import net.minecraft.server.level.ServerPlayer
object ServerboundUpdateRiderRotationHandler : ServerNetworkPacketHandler<ServerboundUpdateRiderRotationPacket> {
override fun handle(
packet: ServerboundUpdateRiderRotationPacket,
server: MinecraftServer,
player: ServerPlayer
) {
if (player is RidePassenger) {
player.`cobblemon$setRideXRot`(packet.riderXRot)
player.`cobblemon$setRideYRot`(packet.riderYRot)
player.`cobblemon$setRideEyePos`(packet.rideEyePos)
}
}
}
@@ -1867,19 +1867,40 @@ open class Pokemon : ShowdownIdentifiable {
}
val max = getMaxRideBoost(stat)
rideBoosts[stat] = (getRideBoost(stat) + boostAmount).coerceAtMost(max)
onChange(RideBoostsUpdatePacket({ this }, rideBoosts))
onChange(RideBoostsUpdatePacket({ this }, getRideBoosts()))
return true
}
fun addRideBoosts(boosts: Map<RidingStat, Float>) {
var changed = false
for (boost in boosts) {
val (stat, boostAmount) = boost
if (!canAddRideBoost(stat)) {
continue
}
val max = getMaxRideBoost(stat)
rideBoosts[stat] = (getRideBoost(stat) + boostAmount).coerceAtMost(max)
changed = true
}
if (changed) {
onChange(RideBoostsUpdatePacket({ this }, getRideBoosts()))
}
}
fun setRideBoost(stat: RidingStat, boost: Float) {
rideBoosts[stat] = boost.coerceIn(0F, getMaxRideBoost(stat))
onChange(RideBoostsUpdatePacket({ this }, rideBoosts))
onChange(RideBoostsUpdatePacket({ this }, getRideBoosts()))
}
fun setRideBoosts(boosts: Map<RidingStat, Float>) {
rideBoosts.clear()
rideBoosts.putAll(boosts.mapValues { it.value.coerceIn(0F, getMaxRideBoost(it.key)) })
onChange(RideBoostsUpdatePacket({ this }, rideBoosts))
onChange(RideBoostsUpdatePacket({ this }, getRideBoosts()))
}
fun getExperienceToNextLevel() = getExperienceToLevel(level + 1)
@@ -14,10 +14,11 @@ import com.cobblemon.mod.common.api.molang.ObjectValue
class BlockBehavior {
private val immuneToSweetBerryBushBlock = false
private val canStandOnPowderSnow = false
val canPathThroughSaccLeaves = false
@Transient
val struct = ObjectValue(this).also {
it.addFunction("immune_to_sweet_berry_bush_block") { DoubleValue(immuneToSweetBerryBushBlock) }
it.addFunction("can_stand_on_powder_snow") { DoubleValue(canStandOnPowderSnow) }
it.addFunction("can_path_through_sacc_leaves") { DoubleValue(canPathThroughSaccLeaves) }
}
}
@@ -12,7 +12,7 @@ import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.api.molang.ObjectValue
class CombatBehaviour {
var willDefendSelf = false
var willDefendSelf = true
var willFlee = true
var willDefendOwner = false
var fightsMelee = true
@@ -8,15 +8,13 @@
package com.cobblemon.mod.common.pokemon.ai
import com.cobblemon.mod.common.CobblemonBlocks
import com.cobblemon.mod.common.entity.OmniPathingEntity
import com.cobblemon.mod.common.util.canFit
import com.google.common.collect.Maps
import it.unimi.dsi.fastutil.longs.Long2ObjectFunction
import it.unimi.dsi.fastutil.longs.Long2ObjectMap
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap
import java.util.EnumSet
import java.util.function.Predicate
import kotlin.math.max
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.tags.BlockTags
@@ -29,15 +27,14 @@ import net.minecraft.world.level.block.BaseRailBlock
import net.minecraft.world.level.block.Blocks
import net.minecraft.world.level.block.FenceGateBlock
import net.minecraft.world.level.material.FluidState
import net.minecraft.world.level.pathfinder.Node
import net.minecraft.world.level.pathfinder.NodeEvaluator
import net.minecraft.world.level.pathfinder.PathComputationType
import net.minecraft.world.level.pathfinder.PathType
import net.minecraft.world.level.pathfinder.PathfindingContext
import net.minecraft.world.level.pathfinder.*
import net.minecraft.world.level.pathfinder.Target
import net.minecraft.world.level.pathfinder.WalkNodeEvaluator
import net.minecraft.world.phys.AABB
import net.minecraft.world.phys.Vec3
import java.util.*
import java.util.function.Predicate
import kotlin.math.max
/**
* A path node maker that constructs paths knowing that the entity might be capable of
@@ -88,6 +85,7 @@ class OmniPathNodeMaker : NodeEvaluator() {
Long2ObjectFunction<PathType?> { this.getPathTypeOfMob(currentContext, x, y, z, entity) }
)
}
fun doesBlockHavePartialCollision(pathType: PathType): Boolean {
return pathType == PathType.FENCE || pathType == PathType.DOOR_WOOD_CLOSED || pathType == PathType.DOOR_IRON_CLOSED
}
@@ -96,7 +94,15 @@ class OmniPathNodeMaker : NodeEvaluator() {
return max(1.125, mob.maxUpStep().toDouble())
}
protected fun findAcceptedNodeWalk(x: Int, y: Int, z: Int, verticalDeltaLimit: Int, nodeFloorLevel: Double, direction: Direction?, pathType: PathType?): Node? {
protected fun findAcceptedNodeWalk(
x: Int,
y: Int,
z: Int,
verticalDeltaLimit: Int,
nodeFloorLevel: Double,
direction: Direction?,
pathType: PathType?
): Node? {
var node: Node? = null
val mutableBlockPos = BlockPos.MutableBlockPos()
val d: Double = this.getFloorLevel(mutableBlockPos.set(x, y, z))
@@ -113,12 +119,23 @@ class OmniPathNodeMaker : NodeEvaluator() {
if (f >= 0.0f) {
node = this.getNodeAndUpdateCostToMax(x, y, z, pathType2, f)
}
if ( pathType?.let { doesBlockHavePartialCollision(it) } == true && node != null && node.costMalus >= 0.0f && !this.canReachWithoutCollision(node)) {
if (pathType?.let { doesBlockHavePartialCollision(it) } == true && node != null && node.costMalus >= 0.0f && !this.canReachWithoutCollision(
node
)) {
node = null
}
if (pathType2 != PathType.WALKABLE ) {
if (pathType2 != PathType.WALKABLE) {
if ((node == null || node.costMalus < 0.0f) && verticalDeltaLimit > 0 && (pathType2 != PathType.FENCE || canWalkOverFences()) && pathType2 != PathType.UNPASSABLE_RAIL && pathType2 != PathType.TRAPDOOR && pathType2 != PathType.POWDER_SNOW) {
node = this.tryJumpOn(x, y, z, verticalDeltaLimit, nodeFloorLevel, direction!!, pathType!!, mutableBlockPos)
node = this.tryJumpOn(
x,
y,
z,
verticalDeltaLimit,
nodeFloorLevel,
direction!!,
pathType!!,
mutableBlockPos
)
} else if (pathType2 == PathType.WATER && !canFloat()) {
node = this.tryFindFirstNonWaterBelow(x, y, z, node)
} else if (pathType2 == PathType.OPEN) {
@@ -143,7 +160,11 @@ class OmniPathNodeMaker : NodeEvaluator() {
private fun canReachWithoutCollision(node: Node): Boolean {
var aABB = mob.boundingBox
var vec3 = Vec3(node.x.toDouble() - mob.x + aABB.xsize / 2.0, node.y.toDouble() - mob.y + aABB.ysize / 2.0, node.z.toDouble() - mob.z + aABB.zsize / 2.0)
var vec3 = Vec3(
node.x.toDouble() - mob.x + aABB.xsize / 2.0,
node.y.toDouble() - mob.y + aABB.ysize / 2.0,
node.z.toDouble() - mob.z + aABB.zsize / 2.0
)
val i = Mth.ceil(vec3.length() / aABB.size)
vec3 = vec3.scale((1.0f / i.toFloat()).toDouble())
for (j in 1..i) {
@@ -187,13 +208,22 @@ class OmniPathNodeMaker : NodeEvaluator() {
}
private fun getNodeAndUpdateCostToMax(x: Int, y: Int, z: Int, pathType: PathType, malus: Float): Node? {
val node = this.getNode(x, y, z) ?: super.getNode(x,y,z) ?: return null
val node = this.getNode(x, y, z) ?: super.getNode(x, y, z) ?: return null
node.type = pathType
node.costMalus = max(node.costMalus.toDouble(), malus.toDouble()).toFloat()
return node
}
private fun tryJumpOn(x: Int, y: Int, z: Int, verticalDeltaLimit: Int, nodeFloorLevel: Double, direction: Direction, pathType: PathType, pos: BlockPos.MutableBlockPos): Node? {
private fun tryJumpOn(
x: Int,
y: Int,
z: Int,
verticalDeltaLimit: Int,
nodeFloorLevel: Double,
direction: Direction,
pathType: PathType,
pos: BlockPos.MutableBlockPos
): Node? {
val node = findAcceptedNodeWalk(x, y + 1, z, verticalDeltaLimit - 1, nodeFloorLevel, direction, pathType)
return if (node == null) {
null
@@ -205,7 +235,20 @@ class OmniPathNodeMaker : NodeEvaluator() {
val d = (x - direction.stepX).toDouble() + 0.5
val e = (z - direction.stepZ).toDouble() + 0.5
val f = mob.bbWidth.toDouble() / 2.0
val aABB = AABB(d - f, this.getFloorLevel(pos.set(d, (y + 1).toDouble(), e)) + 0.001, e - f, d + f, mob.bbHeight.toDouble() + this.getFloorLevel(pos.set(node.x.toDouble(), node.y.toDouble(), node.z.toDouble())) - 0.002, e + f)
val aABB = AABB(
d - f,
this.getFloorLevel(pos.set(d, (y + 1).toDouble(), e)) + 0.001,
e - f,
d + f,
mob.bbHeight.toDouble() + this.getFloorLevel(
pos.set(
node.x.toDouble(),
node.y.toDouble(),
node.z.toDouble()
)
) - 0.002,
e + f
)
if (this.hasCollisions(aABB)) null else node
}
}
@@ -227,7 +270,9 @@ class OmniPathNodeMaker : NodeEvaluator() {
if ((canFloat()) && blockGetter.getFluidState(pos).`is`(FluidTags.WATER)) {
return pos.y.toDouble() + 0.5
}
return if ((canFloat()) && blockGetter.getFluidState(pos).`is`(FluidTags.WATER)) pos.y.toDouble() + 0.5 else WalkNodeEvaluator.getFloorLevel(blockGetter, pos)
return if ((canFloat()) && blockGetter.getFluidState(pos)
.`is`(FluidTags.WATER)
) pos.y.toDouble() + 0.5 else WalkNodeEvaluator.getFloorLevel(blockGetter, pos)
}
override fun getNeighbors(successors: Array<Node?>, node: Node): Int {
@@ -241,11 +286,20 @@ class OmniPathNodeMaker : NodeEvaluator() {
// Non-diagonal surroundings in 3d space
for (direction in Direction.entries) {
var pathNode : Node?
var pathNode: Node?
if (mob.isInWater || canFly()) {
pathNode = this.getNode(node.x + direction.stepX, node.y + direction.stepY, node.z + direction.stepZ) ?: continue
pathNode = this.getNode(node.x + direction.stepX, node.y + direction.stepY, node.z + direction.stepZ)
?: continue
} else {
pathNode = findAcceptedNodeWalk(node.x + direction.stepX, node.y + direction.stepY, node.z + direction.stepZ, if (direction == Direction.DOWN || direction == Direction.UP) 0 else 1, d, direction, node.type) ?: continue
pathNode = findAcceptedNodeWalk(
node.x + direction.stepX,
node.y + direction.stepY,
node.z + direction.stepZ,
if (direction == Direction.DOWN || direction == Direction.UP) 0 else 1,
d,
direction,
node.type
) ?: continue
}
map[direction] = pathNode
if (!hasNotVisited(pathNode, node)) {
@@ -259,21 +313,34 @@ class OmniPathNodeMaker : NodeEvaluator() {
val direction2 = direction.clockWise
val x = node.x + direction.stepX + direction2.stepX
val z = node.z + direction.stepZ + direction2.stepZ
var pathNode2 : Node?
var pathNode2: Node?
if (mob.isInWater || canFly()) {
pathNode2 = this.getNode(x, node.y, z) ?: continue
} else {
pathNode2 = findAcceptedNodeWalk(x, node.y, z, if (direction == Direction.DOWN || direction == Direction.UP) 0 else 1, d, direction, node.type) ?: continue
pathNode2 = findAcceptedNodeWalk(
x,
node.y,
z,
if (direction == Direction.DOWN || direction == Direction.UP) 0 else 1,
d,
direction,
node.type
) ?: continue
}
// Skip 'inaccessible' diagonals if we're pathing from a blocked node since we're trying to get unstuck
if (isAccessibleDiagonal(pathNode2, map[direction], map[direction2]) || (node.type == PathType.BLOCKED && !pathNode2.closed)) {
if (isAccessibleDiagonal(
pathNode2,
map[direction],
map[direction2]
) || (node.type == PathType.BLOCKED && !pathNode2.closed)
) {
successors[i++] = pathNode2
}
}
if (canFly() || mob.isInWater) {
// Upward non-diagonals
for (direction in Direction.Plane.HORIZONTAL.iterator()) {
var pathNode2 : Node? = null
var pathNode2: Node? = null
pathNode2 = getNode(node.x + direction.stepX, node.y + 1, node.z + direction.stepZ) ?: continue
if (upIsOpen && hasNotVisited(pathNode2, node)) {
@@ -285,8 +352,12 @@ class OmniPathNodeMaker : NodeEvaluator() {
// Upward diagonals
for (direction in Direction.Plane.HORIZONTAL.iterator()) {
val direction2 = direction.clockWise
var pathNode2 : Node? = null
pathNode2 = getNode(node.x + direction.stepX + direction2.stepX, node.y + 1, node.z + direction.stepZ + direction2.stepZ) ?: continue
var pathNode2: Node? = null
pathNode2 = getNode(
node.x + direction.stepX + direction2.stepX,
node.y + 1,
node.z + direction.stepZ + direction2.stepZ
) ?: continue
if (isAccessibleDiagonal(pathNode2, upperMap[direction], upperMap[direction2])) {
successors[i++] = pathNode2
@@ -309,7 +380,11 @@ class OmniPathNodeMaker : NodeEvaluator() {
// Downward diagonals
for (direction in Direction.Plane.HORIZONTAL.iterator()) {
val direction2 = direction.clockWise
val pathNode2 = getNode(node.x + direction.stepX + direction2.stepX, node.y - 1, node.z + direction.stepZ + direction2.stepZ) ?: continue
val pathNode2 = getNode(
node.x + direction.stepX + direction2.stepX,
node.y - 1,
node.z + direction.stepZ + direction2.stepZ
) ?: continue
if (isAccessibleDiagonal(pathNode2, lowerMap[direction], lowerMap[direction2])) {
successors[i++] = pathNode2
}
@@ -352,7 +427,12 @@ class OmniPathNodeMaker : NodeEvaluator() {
}
fun isAccessibleDiagonal(pathNode: Node, vararg borderNodes: Node?): Boolean {
return borderNodes.all{ it != null && hasNotVisited(pathNode, it) } && borderNodes.all { it != null && it.costMalus >= 0.0F }
return borderNodes.all {
it != null && hasNotVisited(
pathNode,
it
)
} && borderNodes.all { it != null && it.costMalus >= 0.0F }
}
fun isValidPathType(type: PathType): Boolean {
@@ -385,7 +465,9 @@ class OmniPathNodeMaker : NodeEvaluator() {
}
fun addNodePos(x: Int, y: Int, z: Int): PathType {
return nodePosToType.computeIfAbsent(BlockPos.asLong(x, y, z), Long2ObjectFunction { getPathTypeOfMob(currentContext, x, y, z, mob) })
return nodePosToType.computeIfAbsent(
BlockPos.asLong(x, y, z),
Long2ObjectFunction { getPathTypeOfMob(currentContext, x, y, z, mob) })
}
override fun getPathType(pfContext: PathfindingContext, x: Int, y: Int, z: Int): PathType {
@@ -411,7 +493,7 @@ class OmniPathNodeMaker : NodeEvaluator() {
var figuredNode = if (blockState.`is`(BlockTags.FENCES) || blockState.`is`(BlockTags.WALLS)) {
PathType.FENCE
} else if (blockStateBelow.block is FenceGateBlock) {
if(blockStateBelow.getValue(FenceGateBlock.OPEN)) {
if (blockStateBelow.getValue(FenceGateBlock.OPEN)) {
PathType.OPEN
} else {
PathType.FENCE
@@ -440,6 +522,8 @@ class OmniPathNodeMaker : NodeEvaluator() {
// PathType.WALKABLE
// } else if (blockState.canPathfindThrough(world, pos, NavigationType.AIR) && !blockStateBelow.isSideSolid(world, below, Direction.UP, SideShapeType.FULL)) {
// PathType.OPEN
} else if (blockState.`is`(BlockTags.LEAVES) && blockState.block == CobblemonBlocks.SACCHARINE_LEAVES && canPathThroughLeaves()) {
return PathType.OPEN
} else PathType.BLOCKED
if (figuredNode == PathType.OPEN && pos.y >= pfContext.level().getMinBuildHeight() + 1) {
@@ -470,10 +554,15 @@ class OmniPathNodeMaker : NodeEvaluator() {
val sizeX = (mob.boundingBox.maxX - mob.boundingBox.minX).toInt() + 1
val sizeY = (mob.boundingBox.maxY - mob.boundingBox.minY).toInt() + 1
val sizeZ = (mob.boundingBox.maxZ - mob.boundingBox.minZ).toInt() + 1
val type = findNearbyNodeTypes(pfContext, x, y, z, sizeX, sizeY, sizeZ, canOpenDoors, canPassDoors, set, PathType.BLOCKED,
val type = findNearbyNodeTypes(
pfContext, x, y, z, sizeX, sizeY, sizeZ, canOpenDoors, canPassDoors, set, PathType.BLOCKED,
BlockPos(x, y, z)
)
if (PathType.LEAVES in set) {
return PathType.OPEN
}
if (PathType.FENCE in set) {
return PathType.FENCE
}
@@ -574,7 +663,11 @@ class OmniPathNodeMaker : NodeEvaluator() {
} else if (type == PathType.RAIL && block !is BaseRailBlock && pfContext.getBlockState(pos.below()).block !is BaseRailBlock) {
PathType.UNPASSABLE_RAIL
} else if (type == PathType.LEAVES) {
PathType.BLOCKED
if (blockState.block === CobblemonBlocks.SACCHARINE_LEAVES && canPathThroughLeaves()) {
PathType.OPEN
} else {
PathType.BLOCKED
}
} else type
}
@@ -594,13 +687,13 @@ class OmniPathNodeMaker : NodeEvaluator() {
}
}
fun canSwimInWater(): Boolean {
return if (this.mob is OmniPathingEntity) {
(this.mob as OmniPathingEntity).canSwimInWater()
} else {
false
}
}
fun canSwimInWater(): Boolean {
return if (this.mob is OmniPathingEntity) {
(this.mob as OmniPathingEntity).canSwimInWater()
} else {
false
}
}
fun canSwimUnderFluid(fluidState: FluidState): Boolean {
return if (this.mob is OmniPathingEntity) {
@@ -610,6 +703,14 @@ class OmniPathNodeMaker : NodeEvaluator() {
}
}
fun canPathThroughLeaves() : Boolean {
return if (this.mob is OmniPathingEntity) {
(this.mob as OmniPathingEntity).canPathThroughSaccLeaves()
} else {
false
}
}
fun canWalkOnWater(): Boolean {
return if (this.mob is OmniPathingEntity) {
(this.mob as OmniPathingEntity).canWalkOnWater()
@@ -195,8 +195,8 @@ object PokemonBrain {
CobblemonMemories.RECENTLY_ATE_GRASS,
CobblemonMemories.HIVE_LOCATION,
CobblemonMemories.HIVE_COOLDOWN,
CobblemonMemories.NEARBY_FLOWERS,
CobblemonMemories.POLLINATED,
CobblemonMemories.NEARBY_FLOWER,
CobblemonMemories.HAS_NECTAR,
CobblemonMemories.RECENTLY_ATE_GRASS,
CobblemonMemories.HERD_LEADER,
CobblemonMemories.HERD_SIZE,
@@ -100,53 +100,57 @@ object CobblemonHeldItemManager : BaseCobblemonHeldItemManager() {
return this.showdownIdOf(itemStack.item)
}
override fun handleStartInstruction(pokemon: BattlePokemon, battle: PokemonBattle, battleMessage: BattleMessage) {
val itemID = battleMessage.effectAt(1)?.id ?: return
val consumeHeldItems = this.shouldConsumeItem(pokemon, battle, itemID)
if (battleMessage.hasOptionalArgument("silent")) {
if (consumeHeldItems) {
this.take(pokemon, itemID)
}
return
}
val effect = battleMessage.effect()
val battlerName = pokemon.getName()
val itemName = this.nameOf(itemID)
// Airballoon is the only item using the null effect gimmick
if (effect == null) {
battle.broadcastChatMessage(battleLang("item.$itemID", battlerName, itemName))
return
}
val sourceName = battleMessage.battlePokemonFromOptional(battle)?.getName() ?: Component.literal("UNKNOWN")
val effectId = effect.id
val text = when (effectId) {
"magician", "pickpocket", "covet", "thief" -> battleLang("item.thief", battlerName, itemName, sourceName) // The "source" is actually the target here
"pickup", "recycle" -> battleLang("item.recycle", battlerName, itemName)
"switcheroo", "trick" -> battleLang("item.trick", battlerName, itemName)
else -> battleLang("item.$effectId", battlerName, itemName, sourceName)
}
battle.broadcastChatMessage(text)
// If it's a take and give effect, we don't want to follow through if we are not consuming held items
if (this.takeItemEffect.contains(effectId) && this.giveItemEffect.contains(effectId) && !consumeHeldItems) {
return
}
// Block item swapping in PVP until we have a rule
if (battle.isPvP && !consumeHeldItems) {
return
}
// Block stealing from NPCs
if (battle.isPvN) {
return
}
// if items aren't consumed, then we don't want to give them to wild pokemon (dupe)
if (this.giveItemEffect.contains(effectId) && (pokemon.actor is PlayerBattleActor || consumeHeldItems)) {
this.give(pokemon, itemID)
}
// allow players to steal wild held items
if (this.takeItemEffect.contains(effectId) && (pokemon.actor !is PlayerBattleActor || consumeHeldItems)) {
battleMessage.actorAndActivePokemonFromOptional(battle)?.second?.battlePokemon?.let { this.take(it, itemID) }
override fun handleStartInstruction(pokemon: BattlePokemon, battle: PokemonBattle, battleMessage: BattleMessage) {
val itemID = battleMessage.effectAt(1)?.id ?: return
val consumeHeldItems = this.shouldConsumeItem(pokemon, battle, itemID)
if (battleMessage.hasOptionalArgument("silent")) {
if (consumeHeldItems) {
this.take(pokemon, itemID)
}
return
}
val effect = battleMessage.effect()
val battlerName = pokemon.getName()
val itemName = this.nameOf(itemID)
// Airballoon is the only item using the null effect gimmick
if (effect == null) {
battle.broadcastChatMessage(battleLang("item.$itemID", battlerName, itemName))
return
}
val sourceName = battleMessage.battlePokemonFromOptional(battle)?.getName() ?: Component.literal("UNKNOWN")
val effectId = effect.id
val text = when (effectId) {
"magician", "pickpocket", "covet", "thief" -> battleLang("item.thief", battlerName, itemName, sourceName) // The "source" is actually the target here
"pickup", "recycle" -> battleLang("item.recycle", battlerName, itemName)
"switcheroo", "trick" -> battleLang("item.trick", battlerName, itemName)
else -> battleLang("item.$effectId", battlerName, itemName, sourceName)
}
battle.broadcastChatMessage(text)
// If it's a take and give effect, we don't want to follow through if we are not consuming held items
if (this.takeItemEffect.contains(effectId) && this.giveItemEffect.contains(effectId) && !consumeHeldItems) {
return
}
// Prevent duping
if (battle.isPvP && this.takeItemEffect.contains(effectId) || this.giveItemEffect.contains(effectId)) {
return
}
// Block item swapping in PVP until we have a rule
if (battle.isPvP && !consumeHeldItems) {
return
}
// Block stealing from NPCs
if (battle.isPvN) {
return
}
// if items aren't consumed, then we don't want to give them to wild pokemon (dupe)
if (this.giveItemEffect.contains(effectId) && (pokemon.actor is PlayerBattleActor || consumeHeldItems)) {
this.give(pokemon, itemID)
}
// allow players to steal wild held items
if (this.takeItemEffect.contains(effectId) && (pokemon.actor !is PlayerBattleActor || consumeHeldItems)) {
battleMessage.actorAndActivePokemonFromOptional(battle)?.second?.battlePokemon?.let { this.take(it, itemID) }
}
}
override fun handleEndInstruction(pokemon: BattlePokemon, battle: PokemonBattle, battleMessage: BattleMessage) {
val itemID = battleMessage.effectAt(1)?.id ?: return
@@ -50,6 +50,7 @@ object RidingBehaviourSettingsAdapter : JsonDeserializer<RidingBehaviourSettings
MinekartBehaviour.KEY to MinekartSettings::class.java,
HoverBehaviour.KEY to HoverSettings::class.java,
RocketBehaviour.KEY to RocketSettings::class.java,
SubmarineBehaviour.KEY to SubmarineSettings::class.java,
CompositeBehaviour.KEY to CompositeSettings::class.java,
/*
@@ -21,7 +21,7 @@
"per_update_expression": "variable.radius = 0.6;"
},
"minecraft:emitter_rate_steady": {
"spawn_rate": 30,
"spawn_rate": 60,
"max_particles": 10000
},
"minecraft:emitter_lifetime_expression": {
@@ -29,14 +29,14 @@
},
"minecraft:emitter_shape_sphere": {
"offset": [0, 0, -1],
"radius": "1.25+(v.particle_random_4*1.25)",
"radius": "0.85+(v.particle_random_4*1.25)",
"surface_only": true,
"direction": ["math.random(-0.1,0.1)*3", "math.random(-0.1,0.1)*1.25", "-0.5-math.random(-0.1,0.1)*2"]
"direction": ["math.random(-0.1,0.1)*1", "math.random(-0.1,0.1)*1", "-math.random(-0.1,0.1)*1"]
},
"minecraft:particle_lifetime_expression": {
"max_lifetime": 1
"max_lifetime": 1.25
},
"minecraft:particle_initial_speed": 1,
"minecraft:particle_initial_speed": 0.3,
"minecraft:particle_motion_dynamic": {
"linear_drag_coefficient": "0.25+v.particle_random_4"
},
@@ -50,8 +50,9 @@
"base_UV": [0, 0],
"size_UV": [9, 9],
"step_UV": [9, 0],
"frames_per_second": 20,
"max_frame": 11
"frames_per_second": 12,
"max_frame": 11,
"loop": true
}
}
},
@@ -87,6 +87,9 @@
},
"cannon_left": {
"position": [0, 0, 3]
},
"locator_seat_water2": {
"rotation": [32.5, 0, 0]
}
}
},
@@ -550,6 +553,9 @@
},
"cannon_left": {
"position": [0, 0, 3]
},
"locator_seat_water2": {
"rotation": [35, 0, 0]
}
}
},
@@ -702,6 +708,12 @@
"cannon_left": {
"position": [0, 0, 6.7],
"scale": [1, 1, 0.9]
},
"locator_seat_water2": {
"rotation": [-65, 0, 0]
},
"locator_seat_water": {
"rotation": [-60, 0, 0]
}
}
},
@@ -801,6 +813,12 @@
"cannon_left": {
"position": [0, 0, 7],
"scale": 0
},
"locator_seat_water2": {
"rotation": [-85, 0, 0]
},
"locator_seat_water": {
"rotation": [-90, 0, 0]
}
}
},
@@ -904,6 +922,12 @@
"body": {
"rotation": [9.93357, 0.75431, 0.939],
"position": [0, -24.2, 0]
},
"locator_seat_water2": {
"rotation": [-67.5, 0, 0]
},
"locator_seat_water": {
"rotation": [-67.5, 0, 0]
}
}
},
@@ -1007,6 +1031,12 @@
"body": {
"rotation": [-2.5, 0, 0],
"position": [0, -21, 0]
},
"locator_seat_water2": {
"rotation": [-85, 0, 0]
},
"locator_seat_water": {
"rotation": [-85, 0, 0]
}
}
},
@@ -1959,29 +1989,83 @@
"eyelid_left": {
"position": {
"0.0": [0, 0, 0],
"0.0417": {"pre": [0, 0, 0], "post": [0.1, 0, -0.1]},
"0.5833": {"pre": [0.1, 0, -0.1], "post": [0.1, 0, -0.1]},
"0.625": {"pre": [0.1, 0, -0.1], "post": [0, 0, 0]},
"1.0": {"pre": [0, 0, 0], "post": [0, 0, 0]},
"1.0417": {"pre": [0, 0, 0], "post": [0.1, 0, -0.1]},
"1.4583": {"pre": [0.1, 0, -0.1], "post": [0.1, 0, -0.1]},
"1.5": {"pre": [0.1, 0, -0.1], "post": [0, 0, 0]},
"1.7917": {"pre": [0, 0, 0], "post": [0, 0, 0]},
"1.8333": {"pre": [0, 0, 0], "post": [0.1, 0, -0.1]}
"0.0417": {
"pre": [0, 0, 0],
"post": [0.1, 0, -0.1]
},
"0.5833": {
"pre": [0.1, 0, -0.1],
"post": [0.1, 0, -0.1]
},
"0.625": {
"pre": [0.1, 0, -0.1],
"post": [0, 0, 0]
},
"1.0": {
"pre": [0, 0, 0],
"post": [0, 0, 0]
},
"1.0417": {
"pre": [0, 0, 0],
"post": [0.1, 0, -0.1]
},
"1.4583": {
"pre": [0.1, 0, -0.1],
"post": [0.1, 0, -0.1]
},
"1.5": {
"pre": [0.1, 0, -0.1],
"post": [0, 0, 0]
},
"1.7917": {
"pre": [0, 0, 0],
"post": [0, 0, 0]
},
"1.8333": {
"pre": [0, 0, 0],
"post": [0.1, 0, -0.1]
}
}
},
"eyelid_right": {
"position": {
"0.0": [0, 0, 0],
"0.0417": {"pre": [0, 0, 0], "post": [-0.1, 0, -0.1]},
"0.5833": {"pre": [-0.1, 0, -0.1], "post": [-0.1, 0, -0.1]},
"0.625": {"pre": [-0.1, 0, -0.1], "post": [0, 0, 0]},
"1.0": {"pre": [0, 0, 0], "post": [0, 0, 0]},
"1.0417": {"pre": [0, 0, 0], "post": [-0.1, 0, -0.1]},
"1.4583": {"pre": [-0.1, 0, -0.1], "post": [-0.1, 0, -0.1]},
"1.5": {"pre": [-0.1, 0, -0.1], "post": [0, 0, 0]},
"1.7917": {"pre": [0, 0, 0], "post": [0, 0, 0]},
"1.8333": {"pre": [0, 0, 0], "post": [-0.1, 0, -0.1]}
"0.0417": {
"pre": [0, 0, 0],
"post": [-0.1, 0, -0.1]
},
"0.5833": {
"pre": [-0.1, 0, -0.1],
"post": [-0.1, 0, -0.1]
},
"0.625": {
"pre": [-0.1, 0, -0.1],
"post": [0, 0, 0]
},
"1.0": {
"pre": [0, 0, 0],
"post": [0, 0, 0]
},
"1.0417": {
"pre": [0, 0, 0],
"post": [-0.1, 0, -0.1]
},
"1.4583": {
"pre": [-0.1, 0, -0.1],
"post": [-0.1, 0, -0.1]
},
"1.5": {
"pre": [-0.1, 0, -0.1],
"post": [0, 0, 0]
},
"1.7917": {
"pre": [0, 0, 0],
"post": [0, 0, 0]
},
"1.8333": {
"pre": [0, 0, 0],
"post": [-0.1, 0, -0.1]
}
}
}
}
@@ -2241,25 +2325,67 @@
"eyelid_left": {
"position": {
"0.0": [0, 0, 0],
"0.0417": {"pre": [0, 0, 0], "post": [0.1, 0, -0.1]},
"0.25": {"pre": [0.1, 0, -0.1], "post": [0.1, 0, -0.1]},
"0.2917": {"pre": [0.1, 0, -0.1], "post": [0, 0, 0]},
"1.375": {"pre": [0, 0, 0], "post": [0, 0, 0]},
"1.4167": {"pre": [0, 0, 0], "post": [0.1, 0, -0.1]},
"1.625": {"pre": [0.1, 0, -0.1], "post": [0.1, 0, -0.1]},
"1.6667": {"pre": [0.1, 0, -0.1], "post": [0, 0, 0]}
"0.0417": {
"pre": [0, 0, 0],
"post": [0.1, 0, -0.1]
},
"0.25": {
"pre": [0.1, 0, -0.1],
"post": [0.1, 0, -0.1]
},
"0.2917": {
"pre": [0.1, 0, -0.1],
"post": [0, 0, 0]
},
"1.375": {
"pre": [0, 0, 0],
"post": [0, 0, 0]
},
"1.4167": {
"pre": [0, 0, 0],
"post": [0.1, 0, -0.1]
},
"1.625": {
"pre": [0.1, 0, -0.1],
"post": [0.1, 0, -0.1]
},
"1.6667": {
"pre": [0.1, 0, -0.1],
"post": [0, 0, 0]
}
}
},
"eyelid_right": {
"position": {
"0.0": [0, 0, 0],
"0.0417": {"pre": [0, 0, 0], "post": [-0.1, 0, -0.1]},
"0.25": {"pre": [-0.1, 0, -0.1], "post": [-0.1, 0, -0.1]},
"0.2917": {"pre": [-0.1, 0, -0.1], "post": [0, 0, 0]},
"1.375": {"pre": [0, 0, 0], "post": [0, 0, 0]},
"1.4167": {"pre": [0, 0, 0], "post": [-0.1, 0, -0.1]},
"1.625": {"pre": [-0.1, 0, -0.1], "post": [-0.1, 0, -0.1]},
"1.6667": {"pre": [-0.1, 0, -0.1], "post": [0, 0, 0]}
"0.0417": {
"pre": [0, 0, 0],
"post": [-0.1, 0, -0.1]
},
"0.25": {
"pre": [-0.1, 0, -0.1],
"post": [-0.1, 0, -0.1]
},
"0.2917": {
"pre": [-0.1, 0, -0.1],
"post": [0, 0, 0]
},
"1.375": {
"pre": [0, 0, 0],
"post": [0, 0, 0]
},
"1.4167": {
"pre": [0, 0, 0],
"post": [-0.1, 0, -0.1]
},
"1.625": {
"pre": [-0.1, 0, -0.1],
"post": [-0.1, 0, -0.1]
},
"1.6667": {
"pre": [-0.1, 0, -0.1],
"post": [0, 0, 0]
}
}
}
}
@@ -3372,25 +3498,67 @@
"eyelid_left": {
"position": {
"0.0833": [0, 0, 0],
"0.125": {"pre": [0, 0, 0], "post": [0.1, 0, -0.1]},
"0.7083": {"pre": [0.1, 0, -0.1], "post": [0.1, 0, -0.1]},
"0.75": {"pre": [0.1, 0, -0.1], "post": [0, 0, 0]},
"2.0833": {"pre": [0, 0, 0], "post": [0, 0, 0]},
"2.125": {"pre": [0, 0, 0], "post": [0.1, 0, -0.1]},
"2.2917": {"pre": [0.1, 0, -0.1], "post": [0.1, 0, -0.1]},
"2.3333": {"pre": [0.1, 0, -0.1], "post": [0, 0, 0]}
"0.125": {
"pre": [0, 0, 0],
"post": [0.1, 0, -0.1]
},
"0.7083": {
"pre": [0.1, 0, -0.1],
"post": [0.1, 0, -0.1]
},
"0.75": {
"pre": [0.1, 0, -0.1],
"post": [0, 0, 0]
},
"2.0833": {
"pre": [0, 0, 0],
"post": [0, 0, 0]
},
"2.125": {
"pre": [0, 0, 0],
"post": [0.1, 0, -0.1]
},
"2.2917": {
"pre": [0.1, 0, -0.1],
"post": [0.1, 0, -0.1]
},
"2.3333": {
"pre": [0.1, 0, -0.1],
"post": [0, 0, 0]
}
}
},
"eyelid_right": {
"position": {
"0.0833": [0, 0, 0],
"0.125": {"pre": [0, 0, 0], "post": [-0.1, 0, -0.1]},
"0.7083": {"pre": [-0.1, 0, -0.1], "post": [-0.1, 0, -0.1]},
"0.75": {"pre": [-0.1, 0, -0.1], "post": [0, 0, 0]},
"2.0833": {"pre": [0, 0, 0], "post": [0, 0, 0]},
"2.125": {"pre": [0, 0, 0], "post": [-0.1, 0, -0.1]},
"2.2917": {"pre": [-0.1, 0, -0.1], "post": [-0.1, 0, -0.1]},
"2.3333": {"pre": [-0.1, 0, -0.1], "post": [0, 0, 0]}
"0.125": {
"pre": [0, 0, 0],
"post": [-0.1, 0, -0.1]
},
"0.7083": {
"pre": [-0.1, 0, -0.1],
"post": [-0.1, 0, -0.1]
},
"0.75": {
"pre": [-0.1, 0, -0.1],
"post": [0, 0, 0]
},
"2.0833": {
"pre": [0, 0, 0],
"post": [0, 0, 0]
},
"2.125": {
"pre": [0, 0, 0],
"post": [-0.1, 0, -0.1]
},
"2.2917": {
"pre": [-0.1, 0, -0.1],
"post": [-0.1, 0, -0.1]
},
"2.3333": {
"pre": [-0.1, 0, -0.1],
"post": [0, 0, 0]
}
}
},
"foot_right": {
@@ -4135,25 +4303,67 @@
"eyelid_left": {
"position": {
"0.0": [0, 0, 0],
"0.0417": {"pre": [0, 0, 0], "post": [0.1, 0, -0.1]},
"0.3333": {"pre": [0.1, 0, -0.1], "post": [0.1, 0, -0.1]},
"0.375": {"pre": [0.1, 0, -0.1], "post": [0, 0, 0]},
"1.25": {"pre": [0, 0, 0], "post": [0, 0, 0]},
"1.2917": {"pre": [0, 0, 0], "post": [0.1, 0, -0.1]},
"1.5": {"pre": [0.1, 0, -0.1], "post": [0.1, 0, -0.1]},
"1.5417": {"pre": [0.1, 0, -0.1], "post": [0, 0, 0]}
"0.0417": {
"pre": [0, 0, 0],
"post": [0.1, 0, -0.1]
},
"0.3333": {
"pre": [0.1, 0, -0.1],
"post": [0.1, 0, -0.1]
},
"0.375": {
"pre": [0.1, 0, -0.1],
"post": [0, 0, 0]
},
"1.25": {
"pre": [0, 0, 0],
"post": [0, 0, 0]
},
"1.2917": {
"pre": [0, 0, 0],
"post": [0.1, 0, -0.1]
},
"1.5": {
"pre": [0.1, 0, -0.1],
"post": [0.1, 0, -0.1]
},
"1.5417": {
"pre": [0.1, 0, -0.1],
"post": [0, 0, 0]
}
}
},
"eyelid_right": {
"position": {
"0.0": [0, 0, 0],
"0.0417": {"pre": [0, 0, 0], "post": [-0.1, 0, -0.1]},
"0.3333": {"pre": [-0.1, 0, -0.1], "post": [-0.1, 0, -0.1]},
"0.375": {"pre": [-0.1, 0, -0.1], "post": [0, 0, 0]},
"1.25": {"pre": [0, 0, 0], "post": [0, 0, 0]},
"1.2917": {"pre": [0, 0, 0], "post": [-0.1, 0, -0.1]},
"1.5": {"pre": [-0.1, 0, -0.1], "post": [-0.1, 0, -0.1]},
"1.5417": {"pre": [-0.1, 0, -0.1], "post": [0, 0, 0]}
"0.0417": {
"pre": [0, 0, 0],
"post": [-0.1, 0, -0.1]
},
"0.3333": {
"pre": [-0.1, 0, -0.1],
"post": [-0.1, 0, -0.1]
},
"0.375": {
"pre": [-0.1, 0, -0.1],
"post": [0, 0, 0]
},
"1.25": {
"pre": [0, 0, 0],
"post": [0, 0, 0]
},
"1.2917": {
"pre": [0, 0, 0],
"post": [-0.1, 0, -0.1]
},
"1.5": {
"pre": [-0.1, 0, -0.1],
"post": [-0.1, 0, -0.1]
},
"1.5417": {
"pre": [-0.1, 0, -0.1],
"post": [0, 0, 0]
}
}
},
"foot_right": {
@@ -4730,17 +4940,35 @@
"eyelid_left": {
"position": {
"0.2917": [0, 0, 0],
"0.3333": {"pre": [0, 0, 0], "post": [0.1, 0, -0.1]},
"1.1667": {"pre": [0.1, 0, -0.1], "post": [0.1, 0, -0.1]},
"1.2083": {"pre": [0.1, 0, -0.1], "post": [0, 0, 0]}
"0.3333": {
"pre": [0, 0, 0],
"post": [0.1, 0, -0.1]
},
"1.1667": {
"pre": [0.1, 0, -0.1],
"post": [0.1, 0, -0.1]
},
"1.2083": {
"pre": [0.1, 0, -0.1],
"post": [0, 0, 0]
}
}
},
"eyelid_right": {
"position": {
"0.2917": [0, 0, 0],
"0.3333": {"pre": [0, 0, 0], "post": [-0.1, 0, -0.1]},
"1.1667": {"pre": [-0.1, 0, -0.1], "post": [-0.1, 0, -0.1]},
"1.2083": {"pre": [-0.1, 0, -0.1], "post": [0, 0, 0]}
"0.3333": {
"pre": [0, 0, 0],
"post": [-0.1, 0, -0.1]
},
"1.1667": {
"pre": [-0.1, 0, -0.1],
"post": [-0.1, 0, -0.1]
},
"1.2083": {
"pre": [-0.1, 0, -0.1],
"post": [0, 0, 0]
}
}
}
}
@@ -4785,15 +5013,27 @@
"eyelid_left": {
"position": {
"0.0": [0.1, 0, -0.1],
"0.25": {"pre": [0.1, 0, -0.1], "post": [0.1, 0, -0.1]},
"0.2917": {"pre": [0.1, 0, -0.1], "post": [0, 0, 0]}
"0.25": {
"pre": [0.1, 0, -0.1],
"post": [0.1, 0, -0.1]
},
"0.2917": {
"pre": [0.1, 0, -0.1],
"post": [0, 0, 0]
}
}
},
"eyelid_right": {
"position": {
"0.0": [-0.1, 0, -0.1],
"0.25": {"pre": [-0.1, 0, -0.1], "post": [-0.1, 0, -0.1]},
"0.2917": {"pre": [-0.1, 0, -0.1], "post": [0, 0, 0]}
"0.25": {
"pre": [-0.1, 0, -0.1],
"post": [-0.1, 0, -0.1]
},
"0.2917": {
"pre": [-0.1, 0, -0.1],
"post": [0, 0, 0]
}
}
}
}
@@ -163,7 +163,7 @@
},
"animation.exeggutor.ground_walk": {
"loop": true,
"animation_length": 4.79167,
"animation_length": 4.7917,
"bones": {
"leg_left": {
"rotation": {
@@ -724,126 +724,36 @@
}
}
},
"animation.exeggutor.cry": {
"animation_length": 1.5,
"bones": {
"mouth": {
"position": {
"0.0": [0, 0, 0],
"0.25": [0, -2, 0],
"1.25": [0, -2, 0],
"1.5": [0, 0, 0]
}
},
"mouth2": {
"position": {
"0.0": [0, 0, 0],
"0.25": [0, -5, 0],
"1.25": [0, -5, 0],
"1.5": [0, 0, 0]
}
},
"jaw": {
"position": {
"0.0": [0, 0, 0],
"0.25": [0, -3, 0],
"1.25": [0, -3, 0],
"1.5": [0, 0, 0]
}
},
"head": {
"rotation": {
"0.0": [0, 0, 0],
"0.25": [-15, 0, 0],
"1.25": [-15, 0, 0],
"1.5": [0, 0, 0]
}
},
"mouth_closed": {
"position": {
"0.0": [0, 0, -0.1],
"0.25": {"pre": [0, 0, -0.1], "post": [0, 0, 0]},
"1.25": {"pre": [0, 0, 0], "post": [0, 0, -0.1]}
}
},
"mouth_open": {
"position": {
"0.0": [0, 0, 0.1],
"0.25": {"pre": [0, 0, 0.1], "post": [0, 0, 0]},
"1.25": {"pre": [0, 0, 0], "post": [0, 0, 0.1]}
}
},
"head2": {
"rotation": {
"0.0": [0, 0, 0],
"0.25": [-15, 0, 0],
"1.25": [-15, 0, 0],
"1.5": [0, 0, 0]
}
},
"mouth_closed2": {
"position": {
"0.0": [0, 0, 0],
"0.25": {"pre": [0, 0, 0], "post": [0, 0, 0.2]},
"1.25": {"pre": [0, 0, 0.2], "post": [0, 0, 0]}
}
},
"mouth_open2": {
"position": {
"0.0": [0, 0, 0],
"0.25": {"pre": [0, 0, 0], "post": [0, 0, -0.1]},
"1.25": {"pre": [0, 0, -0.1], "post": [0, 0, 0]}
}
},
"head3": {
"rotation": {
"0.0": [0, 0, 0],
"0.25": [-15, 0, 0],
"1.25": [-15, 0, 0],
"1.5": [0, 0, 0]
}
},
"mouth_closed3": {
"position": {
"0.0": [0, 0, -0.1],
"0.25": {"pre": [0, 0, -0.1], "post": [0, 0, 0]},
"1.25": {"pre": [0, 0, 0], "post": [0, 0, -0.1]}
}
},
"mouth_open3": {
"position": {
"0.0": [0, 0, 0.1],
"0.25": {"pre": [0, 0, 0.1], "post": [0, 0, 0]},
"1.25": {"pre": [0, 0, 0], "post": [0, 0, 0.1]}
}
}
}
},
"animation.exeggutor.battle_idle": {
"loop": true,
"animation_length": 1.58333,
"animation_length": 1.5833,
"bones": {
"leg_left": {
"rotation": {
"0.0": ["-15+math.clamp(-math.sin(q.anim_time*90*2.5+225)*65,-90,0)", -35, 0],
"0.0": [-15, -35, 0],
"0.5833": [-15, -35, 0],
"0.625": ["-15+math.clamp(-math.sin(q.anim_time*90*2.5+225)*65,-90,0)", -35, 0],
"1.125": ["-15+math.clamp(-math.sin(q.anim_time*90*2.5+225)*65,-90,0)", -35, 0],
"0.7917": [-65, -35, 0],
"0.9583": {
"pre": [-75, -35, 0],
"post": [-75, -35, 0],
"lerp_mode": "catmullrom"
},
"1.125": {
"post": [-65, -35, 0],
"lerp_mode": "catmullrom"
},
"1.2917": [-15, -35, 0],
"1.5833": [-15, -35, 0]
},
"position": {
"0.0": [
"math.clamp(math.sin(q.anim_time*90*2.5+225)*0.5,0,90)",
"math.clamp(math.sin(q.anim_time*90*2.5+225)*3.5,0,90)",
"math.clamp(-math.sin(q.anim_time*90*2.5+225)*2,-90,0)"
],
"0.0": [0, 0, 0],
"0.5833": [0, 0, 0],
"0.625": [
"math.clamp(math.sin(q.anim_time*90*2.5+225)*0.5,0,90)",
"math.clamp(math.sin(q.anim_time*90*2.5+225)*3.5,0,90)",
"math.clamp(-math.sin(q.anim_time*90*2.5+225)*2,-90,0)"
],
"0.625": [0.06, 0.42, -0.24],
"0.7083": [0.18, 1.26, -0.72],
"0.7917": [0.15, 1.08, -0.62],
"0.9167": [0.11, 0.81, -0.47],
"1.0417": [0.07, 0.54, -0.31],
"1.1667": [0.04, 0.27, -0.16],
"1.2917": [0, 0, 0],
"1.5833": [0, 0, 0]
}
@@ -852,7 +762,7 @@
"rotation": {
"0.0": [20, 0, 0],
"0.5833": [20, 0, 0],
"0.875": [5, 0, 0],
"0.875": [40, 0, 0],
"1.125": [20, 0, 0]
}
},
@@ -860,53 +770,55 @@
"rotation": {
"0.0": [-5, 0, 0],
"0.5833": [-5, 0, 0],
"0.7083": [30, 0, 0],
"0.9167": [10, 0, 0],
"1.125": [-5, 0, 0],
"1.25": [-5, 0, 0]
"0.7083": [17.5, 0, 0],
"0.9167": [-5, 0, 0]
}
},
"leg_right": {
"rotation": {
"0.0": ["-15+math.clamp(math.sin(q.anim_time*90*2.5+225)*65,-90,0)", 35, 0],
"0.3333": ["-15+math.clamp(math.sin(q.anim_time*90*2.5+225)*65,-90,0)", 35, 0],
"0.0": [-65, 35, 0],
"0.1667": {
"pre": [-75, 35, 0],
"post": [-75, 35, 0],
"lerp_mode": "catmullrom"
},
"0.3333": {
"post": [-65, 35, 0],
"lerp_mode": "catmullrom"
},
"0.5": [-15, 35, 0],
"1.3333": [-15, 35, 0],
"1.375": ["-15+math.clamp(math.sin(q.anim_time*90*2.5+225)*65,-90,0)", 35, 0]
"1.375": [-15, 35, 0],
"1.5833": [-65, 35, 0]
},
"position": {
"0.0": [
"math.clamp(math.sin(q.anim_time*90*2.5+225)*0.5,-90,0)",
"math.clamp(-math.sin(q.anim_time*90*2.5+225)*3.5,0,90)",
"math.clamp(math.sin(q.anim_time*90*2.5+225)*2,-90,0)"
],
"0.0": [-0.37, 2.61, -1.49],
"0.0417": [-0.37, 2.61, -1.49],
"0.125": [-0.3, 2.13, -1.22],
"0.25": [-0.2, 1.42, -0.81],
"0.375": [-0.1, 0.71, -0.4],
"0.5": [0, 0, 0],
"1.3333": [0, 0, 0],
"1.375": [
"math.clamp(math.sin(q.anim_time*90*2.5+225)*0.5,-90,0)",
"math.clamp(-math.sin(q.anim_time*90*2.5+225)*3.5,0,90)",
"math.clamp(math.sin(q.anim_time*90*2.5+225)*2,-90,0)"
]
"1.375": [0, 0, 0],
"1.5833": [-0.37, 2.61, -1.49]
}
},
"leg_right2": {
"rotation": {
"0.0": [7, 0, 0],
"0.125": [5, 0, 0],
"0.0": [35, 0, 0],
"0.125": [40, 0, 0],
"0.375": [20, 0, 0],
"1.375": [20, 0, 0],
"1.5833": [7, 0, 0]
"1.5833": [35, 0, 0]
}
},
"foot_right": {
"rotation": {
"0.0": [22, 0, 0],
"0.125": [10, 0, 0],
"0.3333": [-5, 0, 0],
"0.4583": [-5, 0, 0],
"0.0": [4, 0, 0],
"0.0833": [-5, 0, 0],
"1.375": [-5, 0, 0],
"1.5": [30, 0, 0],
"1.5833": [22, 0, 0]
"1.5": [17.5, 0, 0],
"1.5833": [8.5, 0, 0]
}
},
"body": {
@@ -1063,6 +975,106 @@
}
}
},
"animation.exeggutor.cry": {
"animation_length": 1.5,
"bones": {
"mouth": {
"position": {
"0.0": [0, 0, 0],
"0.25": [0, -2, 0],
"1.25": [0, -2, 0],
"1.5": [0, 0, 0]
}
},
"mouth2": {
"position": {
"0.0": [0, 0, 0],
"0.25": [0, -5, 0],
"1.25": [0, -5, 0],
"1.5": [0, 0, 0]
}
},
"jaw": {
"position": {
"0.0": [0, 0, 0],
"0.25": [0, -3, 0],
"1.25": [0, -3, 0],
"1.5": [0, 0, 0]
}
},
"head": {
"rotation": {
"0.0": [0, 0, 0],
"0.25": [-5, 0, 0],
"1.25": [-2.5, 0, 0],
"1.5": [0, 0, 0]
}
},
"mouth_closed": {
"position": {
"0.0": [-1, 0, -0.1],
"0.25": {"pre": [-1, 0, -0.1], "post": [-1, 0, 0]},
"1.25": {"pre": [-1, 0, 0], "post": [-1, 0, -0.1]}
}
},
"mouth_open": {
"position": {
"0.0": [0, 0, 0.1],
"0.25": {"pre": [0, 0, 0.1], "post": [0, 0, 0]},
"1.25": {"pre": [0, 0, 0], "post": [0, 0, 0.1]}
}
},
"head2": {
"rotation": {
"0.0": [0, 0, 0],
"0.25": [-5, 0, 0],
"1.25": [-2.5, 0, 0],
"1.5": [0, 0, 0]
}
},
"mouth_closed2": {
"position": {
"0.0": [0, 0, 0],
"0.25": {"pre": [0, 0, 0], "post": [0, 0, 0.2]},
"1.25": {"pre": [0, 0, 0.2], "post": [0, 0, 0]}
}
},
"mouth_open2": {
"position": {
"0.0": [0, 0, 0],
"0.25": {"pre": [0, 0, 0], "post": [0, 0, -0.1]},
"1.25": {"pre": [0, 0, -0.1], "post": [0, 0, 0]}
}
},
"head3": {
"rotation": {
"0.0": [0, 0, 0],
"0.25": [-5, 0, 0],
"1.25": [-2.5, 0, 0],
"1.5": [0, 0, 0]
}
},
"mouth_closed3": {
"position": {
"0.0": [-1, 0, -0.1],
"0.25": {"pre": [-1, 0, -0.1], "post": [-1, 0, 0]},
"1.25": {"pre": [-1, 0, 0], "post": [-1, 0, -0.1]}
}
},
"mouth_open3": {
"position": {
"0.0": [0, 0, 0.1],
"0.25": {"pre": [0, 0, 0.1], "post": [0, 0, 0]},
"1.25": {"pre": [0, 0, 0], "post": [0, 0, 0.1]}
}
}
},
"sound_effects": {
"0.25": {
"effect": "pokemon.exeggutor.cry"
}
}
},
"animation.exeggutor.sleep": {
"loop": true,
"animation_length": 8,
@@ -1294,7 +1306,7 @@
}
},
"animation.exeggutor.blink3": {
"animation_length": 0.16667,
"animation_length": 0.1667,
"bones": {
"eyelid_right3": {
"position": {
@@ -5,47 +5,78 @@
"loop": true,
"bones": {
"body": {
"rotation": ["5+(math.sin(q.anim_time*90*2*1+180)*6)*0.6", 0, "0+(math.sin(q.anim_time*90*2*1+180)*1)*0.6"],
"position": [0, "2+(math.cos(q.anim_time*90*2*1+180)*0.8)*0.6", 0]
"rotation": ["-5+math.cos(query.anim_time*90*1)*2", 0, "0+math.cos(query.anim_time*90*1/2)*2"],
"position": ["0+math.sin(query.anim_time*90*1/2)*0.75", "3+math.sin(query.anim_time*90*1)*0.5", 0]
},
"torso_top": {
"rotation": ["0-(math.sin(q.anim_time*90*2*1+180)*1.5)*0.6", 0, 0]
"rotation": ["2.5-math.cos(query.anim_time*90*1-30)*2", 0, "0+math.cos(query.anim_time*90*1/2-60)*2"]
},
"collar_back": {
"rotation": ["0-(math.sin(q.anim_time*90*2*1+180)*5)*0.6", 0, 0]
"rotation": ["0+math.cos(query.anim_time*90*1-30)*2", 0, 0]
},
"collar_front": {
"rotation": ["0+(math.sin(q.anim_time*90*2*1+180)*5)*0.6", 0, 0]
"rotation": ["27.5-math.cos(query.anim_time*90*1-60)*1", 0, 0]
},
"head": {
"rotation": ["5-(math.sin(q.anim_time*90*2*1+180)*3)*0.6", 0, 0]
"eyes_bright": {
"position": [0, 0, -0.1]
},
"arm_right": {
"rotation": [0, "(math.sin(q.anim_time*90*2*1+180)*2)*1", "-60-(math.sin(q.anim_time*90*2*1+180)*2)*0.6"]
"rotation": [
"26.4641+math.cos(query.anim_time*90*1/2-120)*7",
"19.3475-math.cos(query.anim_time*90*1/2-60)*6",
"-42.5248-math.cos(query.anim_time*90*1-90)*2"
]
},
"arm_right2": {
"rotation": [0, "0+(math.cos(q.anim_time*90*2*1+180)*1)*0.6", 0]
"rotation": [
"48.1034+math.cos(query.anim_time*90*1-90)*6",
"-28.22+math.cos(query.anim_time*90*1/2-90)*8",
"-9.9783-math.cos(query.anim_time*90*1-120)*6"
]
},
"hand_right": {
"rotation": [0, 0, -10]
"rotation": [12.42995, "-10.0859-math.cos(query.anim_time*90*1-120)*5", "-31.3315+math.cos(query.anim_time*90*1-150)*6"]
},
"arm_left": {
"rotation": [0, "-(math.sin(q.anim_time*90*2*1+180)*2)*1", "60+(math.sin(q.anim_time*90*2*1+180)*2)*0.6"]
"rotation": [
"26.4641+math.cos(query.anim_time*90*1/2-120)*7",
"-19.3475+math.cos(query.anim_time*90*1/2-60)*6",
"42.5248+math.cos(query.anim_time*90*1-90)*2"
]
},
"arm_left2": {
"rotation": [0, "0-(math.sin(q.anim_time*90*2*1+180)*5)*0.6", 0]
"rotation": [
"48.1034+math.cos(query.anim_time*90*1-90)*6",
"28.22-math.cos(query.anim_time*90*1/2-90)*8",
"9.9783+math.cos(query.anim_time*90*1-120)*6"
]
},
"hand_left": {
"rotation": [0, 0, 10]
"rotation": [12.42995, "10.0859+math.cos(query.anim_time*90*1-120)*5", "31.3315-math.cos(query.anim_time*90*1-150)*6"]
},
"torso_bottom": {
"rotation": ["-7.5+math.cos(query.anim_time*90*1-60)*2", 0, "0-math.cos(query.anim_time*90*1/2-120)*2"]
},
"bottom_coat": {
"rotation": ["0-(math.cos(q.anim_time*90*2*1+180)*3)*0.6", 0, 0]
"rotation": ["-3+math.cos(query.anim_time*90*1-90)*2", 0, "0-math.cos(query.anim_time*90*1/2-90)*3"]
},
"leg_right": {
"rotation": ["0+(math.sin(q.anim_time*90*2*1+180)*3)*0.6", 0, "0-(math.sin(q.anim_time*90*2*1+180)*3)*0.6"]
"rotation": ["2.5062+math.cos(query.anim_time*90*1-120)*6", 10.02087, "2.4142-math.cos(query.anim_time*90*1/2-90)*8"]
},
"leg_right2": {
"rotation": ["22.5+math.cos(query.anim_time*90*1-170)*10", 0, "NaN-math.cos(query.anim_time*90*1/2-150)*6"]
},
"leg_left": {
"rotation": ["0+(math.sin(q.anim_time*90*2*1+180)*3)*0.6", 0, "0+(math.sin(q.anim_time*90*2*1+180)*3)*0.6"]
"rotation": ["2.5062+math.cos(query.anim_time*90*1-120)*6", -10.02087, "-2.4142-math.cos(query.anim_time*90*1/2-90)*8"]
},
"leg_left2": {
"rotation": ["22.5+math.cos(query.anim_time*90*1-170)*10", 0, "-math.cos(query.anim_time*90*1/2-150)*6"]
},
"eyes_dim": {
"position": [0, 0, 0.1]
},
"head": {
"rotation": ["15+math.cos(query.anim_time*90*1)*1", 0, "0-math.cos(query.anim_time*90*1/2-120)*3"]
}
}
},
@@ -53,47 +84,157 @@
"loop": true,
"bones": {
"body": {
"rotation": ["17.5-(math.cos(q.anim_time*90*2*1+180)*10)*0.6", 0, "0-(math.sin(q.anim_time*90*2*1+180)*1)*0.6"],
"position": [0, "2+(math.cos(q.anim_time*90*2*1+180)*1.5)*0.6", "0+(math.sin(q.anim_time*90*2*1+180)*1.8)*0.6"]
"rotation": ["25+math.cos(query.anim_time*90*3/2)*2", "0+math.cos(query.anim_time*90*3/2/2)*4", "0+math.cos(query.anim_time*90*3/2/2-60)*2"],
"position": ["0-math.cos(query.anim_time*90*3/2/2-30)*1", "3+math.sin(query.anim_time*90*3/2)*0.7", "0-math.sin(query.anim_time*90*3/2-60)*1"]
},
"torso_top": {
"rotation": ["0+(math.cos(q.anim_time*90*2*1+180)*3)*0.6", 0, 0]
"rotation": ["10-math.cos(query.anim_time*90*3/2-30)*2", "0+math.cos(query.anim_time*90*3/2/2)*4", 0]
},
"collar_back": {
"rotation": ["0-(math.cos(q.anim_time*90*2*1+180)*6)*0.6", 0, 0]
"rotation": ["-15+math.cos(query.anim_time*90*3/2-30)*2", "0+math.cos(query.anim_time*90*3/2/2-90)*2", 0]
},
"collar_front": {
"rotation": ["0+(math.cos(q.anim_time*90*2*1+180)*6)*0.6", 0, 0]
"rotation": ["27.5-math.cos(query.anim_time*90*3/2-60)*1", 0, 0]
},
"head": {
"rotation": ["0+(math.cos(q.anim_time*90*2*1+180)*6)*0.6", 0, 0]
"eyes_bright": {
"position": [0, 0, -0.1]
},
"arm_right": {
"rotation": [0, "20-(math.sin(q.anim_time*90*2*1+180)*3)*0.6", "-55-(math.sin(q.anim_time*90*2*1+180)*6)*0.6"]
"rotation": [
"20.433+math.cos(query.anim_time*90*3/2/2-120)*7",
"31.8177-math.cos(query.anim_time*90*3/2-60)*6",
"-45.313-math.cos(query.anim_time*90*3/2-90)*2"
]
},
"arm_right2": {
"rotation": ["-(math.cos(q.anim_time*90*2*1+180)*6)*0.6", 0, "0+(math.cos(q.anim_time*90*2*1+180)*1)*0.6"]
"rotation": [
"39.442+math.cos(query.anim_time*90*3/2-90)*6",
"-27.7035+math.cos(query.anim_time*90*3/2-90)*8",
"-8.2892-math.cos(query.anim_time*90*3/2-120)*6"
]
},
"hand_right": {
"rotation": [0, 0, -10]
"rotation": [19.42631, "0.9236-math.cos(query.anim_time*90*3/2-120)*5", "-39.9562+math.cos(query.anim_time*90*3/2-150)*6"]
},
"arm_left": {
"rotation": [0, "-20+(math.sin(q.anim_time*90*2*1+180)*3)*0.6", "55+(math.sin(q.anim_time*90*2*1+180)*6)*0.6"]
"rotation": [
"20.433+math.cos(query.anim_time*90*3/2/2-120)*7",
"-31.8177+math.cos(query.anim_time*90*3/2-60)*6",
"45.313+math.cos(query.anim_time*90*3/2-90)*2"
]
},
"arm_left2": {
"rotation": ["(math.cos(q.anim_time*90*2*1+180)*6)*0.6", 0, "0-(math.cos(q.anim_time*90*2*1+180)*1)*0.6"]
"rotation": [
"39.442+math.cos(query.anim_time*90*3/2-90)*6",
"27.7035-math.cos(query.anim_time*90*3/2-90)*8",
"8.2892+math.cos(query.anim_time*90*3/2-120)*6"
]
},
"hand_left": {
"rotation": [0, 0, 10]
"rotation": [19.42631, "-0.9236+math.cos(query.anim_time*90*3/2-120)*5", "39.9562-math.cos(query.anim_time*90*3/2-150)*6"]
},
"torso_bottom": {
"rotation": ["2.5+math.cos(query.anim_time*90*3/2-60)*2", 0, "0+math.cos(query.anim_time*90*3/2/2-60)*4"]
},
"bottom_coat": {
"rotation": ["-2.5-(math.cos(q.anim_time*90*2*1+180)*6)*0.6", 0, 0]
"rotation": ["-8+math.cos(query.anim_time*90*3/2-90)*2", "0+math.cos(query.anim_time*90*3/2/2-120)*2", "0+math.cos(query.anim_time*90*3/2/2-120)*2"]
},
"leg_right": {
"rotation": ["0-(math.cos(q.anim_time*90*2*1+180)*5)*2", 0, "0-(math.cos(q.anim_time*90*2*1+180)*1)*2"]
"rotation": ["4.5574+math.cos(query.anim_time*90*3/2-120)*6", 10.64348, 7.53105]
},
"leg_right2": {
"rotation": ["22.5+math.cos(query.anim_time*90*3/2-170)*10", 0, 0]
},
"leg_left": {
"rotation": ["0-(math.cos(q.anim_time*90*2*1+180)*5)*2", 0, "0+(math.cos(q.anim_time*90*2*1+180)*1)*2"]
"rotation": ["4.5574+math.cos(query.anim_time*90*3/2-120)*6", -10.64348, -7.53105]
},
"leg_left2": {
"rotation": ["22.5+math.cos(query.anim_time*90*3/2-170)*10", 0, 0]
},
"eyes_dim": {
"position": [0, 0, 0.1]
},
"head": {
"rotation": ["-12.5+math.cos(query.anim_time*90*3/2)*3", "0-math.cos(query.anim_time*90*3/2/2)*4", 0]
}
}
},
"animation.beheeyem.battle_idle": {
"loop": true,
"bones": {
"body": {
"rotation": ["-5+math.cos(query.anim_time*90*1)*2", 0, "0+math.cos(query.anim_time*90*1/2)*1"],
"position": ["0+math.sin(query.anim_time*90*1/2)*0.5", "3+math.sin(query.anim_time*90*1)*0.5", 0]
},
"torso_top": {
"rotation": ["2.5-math.cos(query.anim_time*90*1-30)*2", 0, "0+math.cos(query.anim_time*90*1/2-60)*2"]
},
"collar_back": {
"rotation": ["7.5+math.cos(query.anim_time*90*1-30)*2", 0, 0]
},
"collar_front": {
"rotation": ["27.5-math.cos(query.anim_time*90*1-60)*1", 0, 0]
},
"eyes_bright": {
"position": [0, 0, -0.1]
},
"arm_right": {
"rotation": [
"19.1787+math.cos(query.anim_time*90*1/2-120)*4",
"22.2175-math.cos(query.anim_time*90*1-60)*3",
"-48.3831-math.cos(query.anim_time*90*1-90)*2"
]
},
"arm_right2": {
"rotation": ["38.8131+math.cos(query.anim_time*90*1-90)*6", "-35.024+math.cos(query.anim_time*90*1-90)*4", "3.02+math.cos(query.anim_time*90*1-120)*2"]
},
"hand_right": {
"rotation": [17.41964, "-20.6269-math.cos(query.anim_time*90*1-120)*3", "-23.4374+math.cos(query.anim_time*90*1-150)*8"]
},
"arm_left": {
"rotation": [
"19.1787+math.cos(query.anim_time*90*1/2-120)*4",
"-22.2175+math.cos(query.anim_time*90*1-60)*3",
"48.3831+math.cos(query.anim_time*90*1-90)*2"
]
},
"arm_left2": {
"rotation": ["38.8131+math.cos(query.anim_time*90*1-90)*6", "35.024-math.cos(query.anim_time*90*1-90)*4", "-3.02-math.cos(query.anim_time*90*1-120)*2"]
},
"hand_left": {
"rotation": [17.41964, "20.6269+math.cos(query.anim_time*90*1-120)*3", "23.4374-math.cos(query.anim_time*90*1-150)*8"]
},
"torso_bottom": {
"rotation": ["-7.5+math.cos(query.anim_time*90*1-60)*2", 0, "0-math.cos(query.anim_time*90*1/2-120)*1"]
},
"bottom_coat": {
"rotation": ["-10.5+math.cos(query.anim_time*90*1-90)*2", 0, "-math.cos(query.anim_time*90*1/2-90)*2"]
},
"leg_right": {
"rotation": ["2.0651+math.cos(query.anim_time*90*1-120)*6", 9.9897, "-0.1242-math.cos(query.anim_time*90*1/2-90)*4"]
},
"leg_right2": {
"rotation": ["22.5+math.cos(query.anim_time*90*1-170)*10", 0, "NaN-math.cos(query.anim_time*90*1/2-150)*3"]
},
"leg_left": {
"rotation": ["2.9483+math.cos(query.anim_time*90*1-120)*6", -10.03277, "-4.953-math.cos(query.anim_time*90*1/2-90)*4"]
},
"leg_left2": {
"rotation": ["22.5+math.cos(query.anim_time*90*1-170)*10", 0, "-math.cos(query.anim_time*90*1/2-150)*3"]
},
"pupil_left": {
"position": [0, 0, -0.1],
"scale": 0.75
},
"pupil_right": {
"position": [0, 0, -0.1],
"scale": 0.75
},
"eyes_dim": {
"position": [0, 0, 0.1]
},
"head": {
"rotation": ["25+math.cos(query.anim_time*90*1)*1", 0, "0-math.cos(query.anim_time*90*1/2-120)*3"]
}
}
},
@@ -101,50 +242,341 @@
"loop": true,
"bones": {
"body": {
"rotation": ["0+(math.sin(q.anim_time*90*1+180)*1.5)*0.6", 0, "0+(math.sin(q.anim_time*90*1+180)*3)*0.6"],
"position": [0, "1+(math.cos(q.anim_time*90*1+180)*0.8)*0.6", 0]
"rotation": [40, 0, 0],
"position": [0, 3, 0]
},
"torso_top": {
"rotation": ["0+(math.sin(q.anim_time*90*1+180)*5)*0.6", 0, 0]
},
"collar_back": {
"rotation": ["0-(math.cos(q.anim_time*90*1+180)*3)*0.6", 0, 0]
},
"collar_front": {
"rotation": ["0-(math.cos(q.anim_time*90*1+180)*3)*0.6", 0, 0]
},
"head": {
"rotation": ["25+(math.sin(q.anim_time*90*1+180)*3)*0.6", 0, 0]
},
"arm_right": {
"rotation": [0, "0-(math.cos(q.anim_time*90*1+180)*10)*0.3", "-60+(math.sin(q.anim_time*90*1+180)*5)*0.6"]
},
"arm_right2": {
"rotation": [0, "0-(math.cos(q.anim_time*90*1+180)*10)*0.3", 0]
},
"hand_right": {
"rotation": [0, 0, -10]
},
"arm_left": {
"rotation": [0, "0+(math.cos(q.anim_time*90*1+180)*10)*0.3", "60-(math.sin(q.anim_time*90*1+180)*5)*0.6"]
},
"arm_left2": {
"rotation": [0, "0+(math.cos(q.anim_time*90*1+180)*10)*0.3", 0]
},
"hand_left": {
"rotation": [0, 0, 10]
},
"torso_bottom": {
"rotation": ["0+(math.sin(q.anim_time*90*1+180)*5)*0.6", 0, 0]
},
"bottom_coat": {
"rotation": [-2.5, 0, 0]
},
"collar_back": {
"rotation": [10, 0, 0]
},
"collar_front": {
"rotation": [-15, 0, 0]
},
"arm_right": {
"rotation": [30.18785, -13.49583, -55.38615]
},
"arm_right2": {
"rotation": [8.0521, -19.65857, 17.64818]
},
"hand_right": {
"rotation": [12.21635, -29.21703, -28.76232]
},
"arm_left": {
"rotation": [30.18785, 13.49583, 55.38615]
},
"arm_left2": {
"rotation": [8.0521, 19.65857, -17.64818]
},
"hand_left": {
"rotation": [12.21635, 29.21703, 28.76232]
},
"torso_bottom": {
"rotation": [-17.5, 0, 0]
},
"bottom_coat": {
"rotation": [-17.5, 0, 0]
},
"leg_right": {
"rotation": ["0+(math.sin(q.anim_time*90*1+180)*20)*0.6", 0, "0+(math.cos(q.anim_time*90*1+180)*5)*0.6"]
"rotation": [-22.5, 0, 0]
},
"leg_right2": {
"rotation": [10, 0, 0]
},
"leg_left": {
"rotation": ["0-(math.sin(q.anim_time*90*1+180)*20)*0.6", 0, "0-(math.cos(q.anim_time*90*1+180)*5)*0.6"]
"rotation": [-10, 0, 0]
},
"head": {
"rotation": [20, 0, 0]
}
}
},
"animation.beheeyem.alt_sleep": {
"loop": true,
"bones": {
"body": {
"rotation": [-52.5, 0, 0],
"position": [0, 3, 0]
},
"torso_top": {
"rotation": [2.5, 0, 0]
},
"collar_back": {
"rotation": [5, 0, 0]
},
"collar_front": {
"rotation": [27.5, 0, 0]
},
"arm_right": {
"rotation": [-40.58835, 28.2876, -5.84768]
},
"arm_right2": {
"rotation": [-23.10066, 18.44452, 79.80343]
},
"hand_right": {
"rotation": [-103.94239, -26.14027, 28.61413]
},
"arm_left": {
"rotation": [-40.58835, -28.2876, 5.84768]
},
"arm_left2": {
"rotation": [-23.10066, -18.44452, -79.80343]
},
"hand_left": {
"rotation": [-103.94239, 26.14027, -28.61413]
},
"torso_bottom": {
"rotation": [-12.5, 0, 0]
},
"bottom_coat": {
"rotation": [-17.5, 0, 0]
},
"leg_right": {
"rotation": [-19.76968, -1.50179, -17.43745]
},
"leg_right2": {
"rotation": [10, 0, 0]
},
"leg_left": {
"rotation": [17.24721, -1.93597, 14.87739]
}
}
},
"animation.beheeyem.blink": {
"animation_length": 0.1667,
"bones": {
"eyes_bright": {
"position": {
"0.0": [0, 0, 0.1],
"0.125": [0, 0, 0.1],
"0.1667": {"pre": [0, 0, 0.1], "post": [0, 0, 0]}
}
}
}
},
"animation.beheeyem.pose": {
"loop": true,
"bones": {
"body": {
"rotation": [-2.5, 0, 0],
"position": [0, 3, 0]
},
"torso_top": {
"rotation": [2.5, 0, 0]
},
"collar_back": {
"rotation": [5, 0, 0]
},
"collar_front": {
"rotation": [-7.5, 0, 0]
},
"eyes_bright": {
"position": [0, 0, -0.1]
},
"arm_right": {
"rotation": [0.18641, 11.50404, -55.30513]
},
"arm_right2": {
"rotation": [24.59434, -22.85086, 0.87852]
},
"hand_right": {
"rotation": [19.6995, -3.27667, -28.1772]
},
"arm_left": {
"rotation": [0.18641, -11.50404, 55.30513]
},
"arm_left2": {
"rotation": [24.59434, 22.85086, -0.87852]
},
"hand_left": {
"rotation": [19.6995, 3.27667, 28.1772]
},
"torso_bottom": {
"rotation": [-2.5, 0, 0]
},
"bottom_coat": {
"rotation": [-17.5, 0, 0]
},
"leg_right": {
"rotation": [-5, 0, 0]
},
"leg_right2": {
"rotation": [10, 0, 0]
},
"leg_left": {
"rotation": [7.5, 0, 0]
},
"leg_left2": {
"rotation": [15, 0, 0]
},
"eyes_dim": {
"position": [0, 0, 0.1]
},
"head": {
"rotation": [10, 0, 0]
}
}
},
"animation.beheeyem.lights_quirk1": {
"loop": true,
"animation_length": 1.5,
"bones": {
"right_finger_bright_red2": {
"scale": {
"0.0": [1.05, 1.05, 1.05],
"0.5": {"pre": [1.05, 1.05, 1.05], "post": [1, 1, 1]},
"1.0": [1, 1, 1],
"1.5": {"pre": [1, 1, 1], "post": [1.05, 1.05, 1.05]}
}
},
"right_finger_bright_yellow": {
"scale": {
"0.0": [1, 1, 1],
"0.5": {"pre": [1, 1, 1], "post": [1.05, 1.05, 1.05]},
"1.0": {"pre": [1.05, 1.05, 1.05], "post": [1, 1, 1]},
"1.5": [1, 1, 1]
}
},
"right_finger_bright_green2": {
"scale": {
"0.0": [1, 1, 1],
"0.5": [1, 1, 1],
"1.0": {"pre": [1, 1, 1], "post": [1.05, 1.05, 1.05]},
"1.5": {"pre": [1.05, 1.05, 1.05], "post": [1, 1, 1]}
}
},
"left_finger_bright_red2": {
"scale": {
"0.0": [1.05, 1.05, 1.05],
"0.5": {"pre": [1.05, 1.05, 1.05], "post": [1, 1, 1]},
"1.0": [1, 1, 1],
"1.5": {"pre": [1, 1, 1], "post": [1.05, 1.05, 1.05]}
}
},
"left_finger_bright_yellow": {
"scale": {
"0.0": [1, 1, 1],
"0.5": {"pre": [1, 1, 1], "post": [1.05, 1.05, 1.05]},
"1.0": {"pre": [1.05, 1.05, 1.05], "post": [1, 1, 1]},
"1.5": [1, 1, 1]
}
},
"left_finger_bright_green2": {
"scale": {
"0.0": [1, 1, 1],
"0.5": [1, 1, 1],
"1.0": {"pre": [1, 1, 1], "post": [1.05, 1.05, 1.05]},
"1.5": {"pre": [1.05, 1.05, 1.05], "post": [1, 1, 1]}
}
}
}
},
"animation.beheeyem.lights_quirk2": {
"loop": true,
"animation_length": 1,
"bones": {
"right_finger_bright_red2": {
"scale": {
"0.0": [1.05, 1.05, 1.05],
"0.3333": {"pre": [1.05, 1.05, 1.05], "post": [1, 1, 1]},
"0.6667": [1, 1, 1],
"1.0": {"pre": [1, 1, 1], "post": [1.05, 1.05, 1.05]}
}
},
"right_finger_bright_yellow": {
"scale": {
"0.0": [1, 1, 1],
"0.3333": {"pre": [1, 1, 1], "post": [1.05, 1.05, 1.05]},
"0.6667": {"pre": [1.05, 1.05, 1.05], "post": [1, 1, 1]},
"1.0": [1, 1, 1]
}
},
"right_finger_bright_green2": {
"scale": {
"0.0": [1, 1, 1],
"0.3333": [1, 1, 1],
"0.6667": {"pre": [1, 1, 1], "post": [1.05, 1.05, 1.05]},
"1.0": {"pre": [1.05, 1.05, 1.05], "post": [1, 1, 1]}
}
},
"left_finger_bright_red2": {
"scale": {
"0.0": [1.05, 1.05, 1.05],
"0.3333": {"pre": [1.05, 1.05, 1.05], "post": [1, 1, 1]},
"0.6667": [1, 1, 1],
"1.0": {"pre": [1, 1, 1], "post": [1.05, 1.05, 1.05]}
}
},
"left_finger_bright_yellow": {
"scale": {
"0.0": [1, 1, 1],
"0.3333": {"pre": [1, 1, 1], "post": [1.05, 1.05, 1.05]},
"0.6667": {"pre": [1.05, 1.05, 1.05], "post": [1, 1, 1]},
"1.0": [1, 1, 1]
}
},
"left_finger_bright_green2": {
"scale": {
"0.0": [1, 1, 1],
"0.3333": [1, 1, 1],
"0.6667": {"pre": [1, 1, 1], "post": [1.05, 1.05, 1.05]},
"1.0": {"pre": [1.05, 1.05, 1.05], "post": [1, 1, 1]}
}
}
}
},
"animation.beheeyem.lights_quirk3": {
"loop": true,
"animation_length": 0.5,
"bones": {
"right_finger_bright_red2": {
"scale": {
"0.0": [1.05, 1.05, 1.05],
"0.1667": {"pre": [1.05, 1.05, 1.05], "post": [1, 1, 1]},
"0.3333": [1, 1, 1],
"0.5": {"pre": [1, 1, 1], "post": [1.05, 1.05, 1.05]}
}
},
"right_finger_bright_yellow": {
"scale": {
"0.0": [1, 1, 1],
"0.1667": {"pre": [1, 1, 1], "post": [1.05, 1.05, 1.05]},
"0.3333": {"pre": [1.05, 1.05, 1.05], "post": [1, 1, 1]},
"0.5": [1, 1, 1]
}
},
"right_finger_bright_green2": {
"scale": {
"0.0": [1, 1, 1],
"0.1667": [1, 1, 1],
"0.3333": {"pre": [1, 1, 1], "post": [1.05, 1.05, 1.05]},
"0.5": {"pre": [1.05, 1.05, 1.05], "post": [1, 1, 1]}
}
},
"left_finger_bright_red2": {
"scale": {
"0.0": [1.05, 1.05, 1.05],
"0.1667": {"pre": [1.05, 1.05, 1.05], "post": [1, 1, 1]},
"0.3333": [1, 1, 1],
"0.5": {"pre": [1, 1, 1], "post": [1.05, 1.05, 1.05]}
}
},
"left_finger_bright_yellow": {
"scale": {
"0.0": [1, 1, 1],
"0.1667": {"pre": [1, 1, 1], "post": [1.05, 1.05, 1.05]},
"0.3333": {"pre": [1.05, 1.05, 1.05], "post": [1, 1, 1]},
"0.5": [1, 1, 1]
}
},
"left_finger_bright_green2": {
"scale": {
"0.0": [1, 1, 1],
"0.1667": [1, 1, 1],
"0.3333": {"pre": [1, 1, 1], "post": [1.05, 1.05, 1.05]},
"0.5": {"pre": [1.05, 1.05, 1.05], "post": [1, 1, 1]}
}
}
}
},
@@ -178,15 +610,6 @@
"1.4583": [0, 0, 0]
}
},
"head": {
"rotation": {
"0.0": ["0+(math.cos(q.anim_time*90*2*1+180)*0.8)*0.6", 0, 0],
"0.125": [11.5, 0, 0],
"0.4167": ["-6-(math.cos(q.anim_time*90*80+180)*2)*2", 0, 0],
"0.9583": ["-6+(math.cos(q.anim_time*90*8+180)*2)*0.6", 0, 0],
"1.4583": [0, 0, 0]
}
},
"arm_right": {
"rotation": {
"0.0": [0, 0, 0],
@@ -222,11 +645,94 @@
"0.9583": [7.3679, 40.2246, -30.8095],
"1.4583": [0, 0, 0]
}
},
"head": {
"rotation": {
"0.0": ["0+(math.cos(q.anim_time*90*2*1+180)*0.8)*0.6", 0, 0],
"0.125": [11.5, 0, 0],
"0.4167": ["-6-(math.cos(q.anim_time*90*80+180)*2)*2", 0, 0],
"0.9583": ["-6+(math.cos(q.anim_time*90*8+180)*2)*0.6", 0, 0],
"1.4583": [0, 0, 0]
}
}
},
"sound_effects": {
"0.04": {
"effect": "pokemon.beheeyem.cry"
}
},
"animation.beheeyem.new": {
"loop": true,
"bones": {
"beheeyem": {
"position": [0, 0, "33-query.anim_time*90*0.08"]
}
}
},
"animation.beheeyem.pose2": {
"loop": true,
"bones": {
"body": {
"rotation": [-2.5, 0, 0],
"position": [0, 3, 0]
},
"torso_top": {
"rotation": [2.5, 0, 0]
},
"collar_back": {
"rotation": [5, 0, 0]
},
"collar_front": {
"rotation": [-7.5, 0, 0]
},
"eyes_bright": {
"position": [0, 0, -0.1]
},
"arm_right": {
"rotation": [-16.69715, -44.79233, -31.70444]
},
"arm_right2": {
"rotation": [24.59434, -22.85086, 0.87852]
},
"hand_right": {
"rotation": [19.6995, -3.27667, -28.1772]
},
"arm_left": {
"rotation": [0.18641, -11.50404, 55.30513]
},
"arm_left2": {
"rotation": [24.59434, 22.85086, -0.87852]
},
"hand_left": {
"rotation": [19.6995, 3.27667, 28.1772]
},
"torso_bottom": {
"rotation": [-2.5, 0, 0]
},
"bottom_coat": {
"rotation": [-17.5, 0, 0]
},
"leg_right": {
"rotation": [-5, 0, 0]
},
"leg_right2": {
"rotation": [10, 0, 0]
},
"leg_left": {
"rotation": [7.5, 0, 0]
},
"leg_left2": {
"rotation": [15, 0, 0]
},
"pupil_left": {
"position": [-0.25, 0, -0.1],
"scale": 0.75
},
"pupil_right": {
"position": [-0.25, 0, -0.1],
"scale": 0.75
},
"eyes_dim": {
"position": [0, 0, 0.1]
},
"head": {
"rotation": [10, 0, 0]
}
}
}
@@ -26,6 +26,9 @@
},
"eyes": {
"scale": [1, "1-math.sin(q.anim_time*90)*0.03", 1]
},
"wax_trail": {
"position": [0, 0.02, 0]
}
}
},
@@ -43,7 +46,7 @@
"scale": [1, "1-math.sin(q.anim_time*90*4-135)*0.01", 1]
},
"flame": {
"rotation": ["(math.sin(q.anim_time*90)*6)*(math.sin(q.anim_time*90-90))", 0, "-(math.sin(q.anim_time*90-45)*6)*(math.sin(q.anim_time*90-90))"],
"rotation": ["-2.5+(math.sin(q.anim_time*90)*6)*(math.sin(q.anim_time*90-90))", 0, "0-(math.sin(q.anim_time*90-45)*6)*(math.sin(q.anim_time*90-90))"],
"scale": [1, "1+math.sin(q.anim_time*90*4+125)*0.1", 1]
},
"arm_right": {
@@ -61,7 +64,10 @@
"scale": [1, "1+math.sin(q.anim_time*90*4+90)*0.01", 1]
},
"wax_trail2": {
"position": [0, 0, "0.3-math.sin(q.anim_time*90*4+75)*0.33"]
"scale": [1, 1, "1-math.sin(q.anim_time*90*4+75)*0.1"]
},
"wax_trail": {
"position": [0, 0.03, 0]
}
}
},
@@ -100,7 +106,7 @@
"position": ["(math.sin(q.anim_time*90*2)*0.5)", 0, 0]
},
"wax_trail": {
"position": ["(math.sin(q.anim_time*90*2-90)*0.2)", 0, 0],
"position": ["(math.sin(q.anim_time*90*2-90)*0.2)", 0.02, 0],
"scale": ["1+(math.sin(q.anim_time*90*4+45)*0.04)", 1, 1]
},
"lip": {
@@ -141,7 +147,7 @@
"scale": [1, 0.9, 1]
},
"wax_trail": {
"position": [0, 0.5, 0],
"position": [0, 0.52, 0],
"scale": 1.075
},
"eyelid_right": {
@@ -153,62 +159,129 @@
"lip": {
"rotation": [4.98, 0, 0],
"position": [0, 0, -0.008]
},
"eye_left_open": {
"position": [0, 0, 0.1]
},
"eye_closed_left": {
"position": [0, 0, -0.1]
},
"eye_right_open": {
"position": [0, 0, 0.1]
},
"eye_closed_right": {
"position": [0, 0, -0.1]
}
}
},
"animation.litwick.blink": {
"animation_length": 0.20833,
"animation_length": 0.1667,
"bones": {
"eyelid_right": {
"eye_left_open": {
"position": {
"0.0": [-0.1, 0, -0.1],
"0.1667": [-0.1, 0, -0.1],
"0.2083": [0, 0, 0]
"0.0": [0, 0, 0.1],
"0.125": [0, 0, 0.1],
"0.1667": [0, 0, 0]
}
},
"eyelid_left": {
"eye_closed_left": {
"position": {
"0.0": [0.1, 0, -0.1],
"0.1667": [0.1, 0, -0.1],
"0.2083": [0, 0, 0]
"0.0": [0, 0, -0.1],
"0.125": [0, 0, -0.1],
"0.1667": [0, 0, 0]
}
},
"eye_right_open": {
"position": {
"0.0": [0, 0, 0.1],
"0.125": [0, 0, 0.1],
"0.1667": [0, 0, 0]
}
},
"eye_closed_right": {
"position": {
"0.0": [0, 0, -0.1],
"0.125": [0, 0, -0.1],
"0.1667": [0, 0, 0]
}
}
}
},
"animation.litwick.pose": {
"animation.litwick.shock": {
"loop": true,
"bones": {
"head": {
"rotation": [-7.56397, -7.43547, 0.98448]
"eye_left_open": {
"position": [0, 0.25, 0],
"scale": [1, 0.5, 1]
},
"hair": {
"rotation": [-2.5, 0, 0]
"eye_right_open": {
"position": [0, 0.25, 0]
},
"flame": {
"rotation": [-19.8423, -2.55868, -12.05239]
},
"arm_right": {
"rotation": [0, 0, -50]
},
"arm_left": {
"rotation": [0, 0, 52.5]
},
"lip": {
"rotation": [-7.5, 0, 0]
},
"mouth_small": {
"position": [0, 0, 0]
},
"mouth_large": {
"position": [0, 0, 0]
"mouth_open": {
"position": [0, 0, -0.1],
"scale": [0.9, 1, 1]
}
}
},
"animation.litwick.cry": {
"animation_length": 1.2,
"sound_effects": {
"0.04": {
"effect": "pokemon.litwick.cry"
"animation.litwick.angry": {
"loop": true,
"bones": {
"eye_expression_left": {
"position": [-0.5, 0, -0.1]
},
"eye_expression_right": {
"position": [0.5, 0, -0.1]
}
}
},
"animation.litwick.sad": {
"loop": true,
"bones": {
"eye_expression_left": {
"position": [0.5, 0, -0.1]
},
"eye_expression_right": {
"position": [-0.5, 0, -0.1]
}
}
},
"animation.litwick.happy": {
"loop": true,
"bones": {
"eye_left_open": {
"position": [0, 0, 0.1]
},
"eye_happy_left": {
"position": [0, 0, -0.1]
},
"eye_right_open": {
"position": [0, 0, 0.1]
},
"eye_happy_right": {
"position": [0, 0, -0.1]
},
"mouth_open": {
"position": [0, 0, -0.1],
"scale": [0.9, 1, 1]
}
}
},
"animation.litwick.hurt": {
"loop": true,
"bones": {
"eye_left_open": {
"position": [0, 0, 0.1]
},
"eye_closed_left": {
"rotation": [0, 0, -27.5],
"position": [0, 0, -0.1]
},
"eye_right_open": {
"position": [0, 0, 0.1]
},
"eye_closed_right": {
"rotation": [0, 0, 27.5],
"position": [0, 0, -0.1]
}
}
}
@@ -20,7 +20,7 @@
"rotation": ["-2.5 + math.sin(q.anim_time*90*2-90)", 5, "7.5 + 5*math.sin(q.anim_time*90*2)"]
},
"arm_left2": {
"rotation": [0, 0, "10 + 5*math.sin(q.anim_time*90*2+90)"]
"rotation": [0, 0, "5*math.sin(q.anim_time*90*2+90)"]
},
"arm_left3": {
"rotation": [0, 0, "10 + 5*math.sin(q.anim_time*90*2+180)"]
@@ -38,7 +38,7 @@
"rotation": ["-2.5+math.sin(q.anim_time*90*2-90)", -5, "-7.5 - 5*math.sin(q.anim_time*90*2)"]
},
"arm_right2": {
"rotation": [0, 0, "-10 - 5*math.sin(q.anim_time*90*2+90)"]
"rotation": [0, 0, "- 5*math.sin(q.anim_time*90*2+90)"]
},
"arm_right3": {
"rotation": [0, 0, "-10 - 5*math.sin(q.anim_time*90*2+180)"]
@@ -59,7 +59,13 @@
"scale": 0
},
"body": {
"position": [0, 4, 0]
"position": [0, 1, 0]
},
"arm_left7": {
"rotation": [0, 0, 57.5]
},
"arm_right7": {
"rotation": [0, 0, -57.5]
}
}
},
@@ -82,7 +88,7 @@
"rotation": ["10.2854 + math.sin(q.anim_time*90*2-90)*2", 6.26335, "18.6835 + 5*math.sin(q.anim_time*90*2)"]
},
"arm_left2": {
"rotation": [0, 0, "18 + 5*math.sin(q.anim_time*90*2+90)"]
"rotation": [0, 0, "10.5 + 5*math.sin(q.anim_time*90*2+90)"]
},
"arm_left3": {
"rotation": [0, 0, "20 + 5*math.sin(q.anim_time*90*2+180)"]
@@ -100,7 +106,7 @@
"rotation": ["10.2854 + math.sin(q.anim_time*90*2-90)*2", -6.26335, "-18.6835+ 5*math.sin(q.anim_time*90*2)"]
},
"arm_right2": {
"rotation": [0, 0, "-18+ 5*math.sin(q.anim_time*90*2+90)"]
"rotation": [0, 0, "-10.5+ 5*math.sin(q.anim_time*90*2+90)"]
},
"arm_right3": {
"rotation": [0, 0, "-20+ 5*math.sin(q.anim_time*90*2+180)"]
@@ -125,6 +131,12 @@
},
"head": {
"rotation": [0, 0, 0]
},
"arm_left7": {
"rotation": [0, 0, 57.5]
},
"arm_right7": {
"rotation": [-2.50463, -13.04546, -71.15153]
}
}
},
@@ -187,6 +199,12 @@
},
"body": {
"position": [0, 4, 0]
},
"arm_left7": {
"rotation": [0, 0, 47.5]
},
"arm_right7": {
"rotation": [0, 0, -47.5]
}
}
},
@@ -209,7 +227,7 @@
"rotation": ["10.2854 + math.sin(q.anim_time*90*2-90)*2", 6.26335, "18.6835 + 5*math.sin(q.anim_time*90*2)"]
},
"arm_left2": {
"rotation": [0, 0, "18 + 5*math.sin(q.anim_time*90*2+90)"]
"rotation": [0, 0, "10.5 + 5*math.sin(q.anim_time*90*2+90)"]
},
"arm_left3": {
"rotation": [0, 0, "20 + 5*math.sin(q.anim_time*90*2+180)"]
@@ -227,7 +245,7 @@
"rotation": ["10.2854 + math.sin(q.anim_time*90*2-90)*2", -6.26335, "-18.6835+ 5*math.sin(q.anim_time*90*2)"]
},
"arm_right2": {
"rotation": [0, 0, "-18+ 5*math.sin(q.anim_time*90*2+90)"]
"rotation": [0, 0, "-10.5+ 5*math.sin(q.anim_time*90*2+90)"]
},
"arm_right3": {
"rotation": [0, 0, "-20+ 5*math.sin(q.anim_time*90*2+180)"]
@@ -252,6 +270,12 @@
},
"head": {
"rotation": [0, 0, 0]
},
"arm_left7": {
"rotation": [0, 0, 57.5]
},
"arm_right7": {
"rotation": [0, 0, -57.5]
}
}
},
@@ -297,13 +321,13 @@
"rotation": [0, 0, "22+5*math.sin(q.anim_time*90*2)*0.5"]
},
"arm_right3": {
"rotation": [0, 0, "35+5*math.sin(q.anim_time*90*2)"]
"rotation": [0, 0, "25+5*math.sin(q.anim_time*90*2)"]
},
"arm_right4": {
"rotation": [0, 0, "19.5+5*math.sin(q.anim_time*90*2)*0.5"]
},
"arm_right5": {
"rotation": [0, 0, "30+5*math.sin(q.anim_time*90*2)*0.5"]
"rotation": [0, 0, "15+5*math.sin(q.anim_time*90*2)*0.5"]
},
"arm_right6": {
"rotation": [0, 0, "24+5*math.sin(q.anim_time*90*2)*0.5"]
@@ -316,6 +340,12 @@
},
"body": {
"position": [0, 4, 0]
},
"arm_left7": {
"rotation": [0, 0, 70]
},
"arm_right7": {
"rotation": [0, 0, 12.5]
}
}
},
@@ -379,31 +409,29 @@
"body": {
"position": [0, 4, 0]
},
"eye_left": {
"scale": 0
"arm_left7": {
"rotation": [0, 0, 67.5]
},
"eye_right": {
"scale": 0
"arm_right7": {
"rotation": [0, 0, -67.5]
},
"eye_left_open": {
"position": [0, 4, 0]
},
"eye_closed_left": {
"position": [0, -4, 0]
},
"eye_right_open": {
"position": [0, 4, 0]
},
"eye_closed_right": {
"position": [0, -4, 0]
}
}
},
"animation.lampent.blink": {
"animation_length": 0.16667,
"animation_length": 0.1667,
"bones": {
"eye_left": {
"scale": {
"0.0": [0, 0, 0],
"0.125": [0, 0, 0],
"0.1667": [1, 1, 1]
}
},
"eye_right": {
"scale": {
"0.0": [0, 0, 0],
"0.125": [0, 0, 0],
"0.1667": [1, 1, 1]
}
},
"eyelid_left": {
"scale": {
"0.0": [1, 1, 1],
@@ -417,68 +445,116 @@
"0.125": [1, 1, 1],
"0.1667": [1, 1, 1]
}
},
"eye_left_open": {
"position": {
"0.0": [0, 4, 0],
"0.125": {"pre": [0, 4, 0], "post": [0, 4, 0]},
"0.1667": {"pre": [0, 4, 0], "post": [0, 0, 0]}
}
},
"eye_closed_left": {
"position": {
"0.0": [0, -4, 0],
"0.125": {"pre": [0, -4, 0], "post": [0, -4, 0]},
"0.1667": {"pre": [0, -4, 0], "post": [0, 0, 0]}
}
},
"eye_right_open": {
"position": {
"0.0": [0, 4, 0],
"0.125": {"pre": [0, 4, 0], "post": [0, 4, 0]},
"0.1667": {"pre": [0, 4, 0], "post": [0, 0, 0]}
}
},
"eye_closed_right": {
"position": {
"0.0": [0, -4, 0],
"0.125": {"pre": [0, -4, 0], "post": [0, -4, 0]},
"0.1667": {"pre": [0, -4, 0], "post": [0, 0, 0]}
}
}
}
},
"animation.lampent.pose": {
"animation.lampent.shock": {
"loop": true,
"bones": {
"body": {
"rotation": [0, -5, 7.5]
"eye_left_open": {
"position": [0, 0.25, 0],
"scale": [0.7, 0.7, 1]
},
"head": {
"rotation": [14.60945, 2.9986, -5.59467]
"eye_left_open2": {
"scale": 0
},
"arm_left": {
"rotation": [0, 25, -7.5]
"eye_right_open": {
"position": [0, 0.25, 0],
"scale": [0.7, 0.7, 1]
},
"arm_left2": {
"rotation": [0, 0, -22.5]
},
"arm_left3": {
"rotation": [0, 0, -17.5]
},
"arm_left4": {
"rotation": [0, 0, -22.5]
},
"arm_left5": {
"rotation": [0, 0, -30]
},
"arm_left6": {
"rotation": [0, 0, -28.5]
},
"arm_right": {
"rotation": [0, 0, -5]
},
"arm_right2": {
"rotation": [0, 0, -20]
},
"arm_right3": {
"rotation": [0, 0, -25]
},
"arm_right4": {
"rotation": [0, 0, -40]
},
"arm_right5": {
"rotation": [0, 0, -38.5]
},
"arm_right6": {
"rotation": [0, 0, -36.5]
},
"swing": {
"rotation": [-13.63677, 25.79103, 6.17047],
"position": [4.2, 4, 11]
},
"flame": {
"rotation": [0, 22.5, 0]
"eye_right_open2": {
"position": [-0.25, -0.25, 0],
"scale": 0
}
}
},
"animation.lampent.cry": {
"animation_length": 1.2,
"sound_effects": {
"0.04": {
"effect": "pokemon.lampent.cry"
"animation.lampent.angry": {
"loop": true,
"bones": {
"eye_left_open2": {
"position": [0.25, -0.25, 0],
"scale": [0.5, 0.5, 1]
},
"eye_right_open2": {
"position": [-0.25, -0.25, 0],
"scale": [0.5, 0.5, 1]
}
}
},
"animation.lampent.sad": {
"loop": true,
"bones": {
"eye_left_open2": {
"position": [-0.25, -0.25, 0],
"scale": [0.5, 0.5, 1]
},
"eye_right_open2": {
"position": [0.25, -0.25, 0],
"scale": [0.5, 0.5, 1]
}
}
},
"animation.lampent.happy": {
"loop": true,
"bones": {
"eye_left_open": {
"position": [0, 4, 0]
},
"eye_happy_left": {
"position": [0, -4, 0]
},
"eye_right_open": {
"position": [0, 4, 0]
},
"eye_happy_right": {
"position": [0, -4, 0]
}
}
},
"animation.lampent.hurt": {
"loop": true,
"bones": {
"eye_left_open": {
"position": [0, 4, 0]
},
"eye_closed_left": {
"rotation": [0, 0, -20],
"position": [0, -4, 0]
},
"eye_right_open": {
"position": [0, 4, 0]
},
"eye_closed_right": {
"rotation": [0, 0, 20],
"position": [0, -4, 0]
}
}
}

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