Compare commits

...
Author SHA1 Message Date
jackowes 09eb3395c0 <temp and broken> add beginnings of recurring sound function 2025-08-18 21:42:26 -05:00
jackowes 48d289279a add char tests 2025-08-14 20:42:50 -05:00
Polymeta d141e6b46d Merge branch 'riding-controllers-expanded' into 'main'
Controller additions as well as many controller fixes and polish

See merge request cable-mc/cobblemon!1762
2025-08-12 15:13:04 +00:00
jackowes e683dedc2a rename rotateheadFunctions to unbreak 2025-08-12 09:25:16 -05:00
jackowes c7d6107cea Merge branch 'main' into riding-controllers-expanded 2025-08-11 22:52:55 -05:00
jackowes ef7e76dc07 move driverInput over to Player and check if input has changed before sending driverInput to the server. 2025-08-11 22:50:53 -05:00
jackowes 5697fa53f0 move driverInput over to ServerPlayer 2025-08-11 21:35:53 -05:00
Polymeta 2e87894dac Merge branch 'molang-commands' into 'main'
Add command to run MoLang expressions as well as add `entity` to RunMolangScriptCommand.kt.

See merge request cable-mc/cobblemon!1743
2025-08-11 13:46:05 +00:00
Jordan 2dd4373890 Add command to run MoLang expressions as well as add entity to RunMolangScriptCommand.kt. 2025-08-11 13:46:05 +00:00
Test e33587fefc pc walls 2025-08-10 23:50:02 -04:00
Polymeta 1089fb2571 various fixes for suspicious stew (specifically pep up flower ingredient too) interactions with mooshtanks
Signed-off-by: Polymeta <40173118+Polymeta@users.noreply.github.com>
2025-08-11 00:57:22 +01:00
Polymeta db777381e7 Merge branch 'josh/locator-flip-fix' into 'main'
Only flip locators Z axis if model is attached to an entity

See merge request cable-mc/cobblemon!1767
2025-08-10 21:48:32 +00:00
Polymeta 8fee43c0b8 Merge branch 'orientable-entities' into 'main'
Orientation now works for Living Entities

See merge request cable-mc/cobblemon!1601
2025-08-10 21:37:48 +00:00
Jordan d7160948cd Orientation now works for Living Entities 2025-08-10 21:37:48 +00:00
Polymeta 868b8ae662 Merge branch 'neva-enough-molang' into 'main'
Neva enough molang

See merge request cable-mc/cobblemon!1755
2025-08-10 21:31:29 +00:00
Jordan d65884de5b Neva enough molang 2025-08-10 21:31:29 +00:00
Josh 7714d60b2e Only flip locators Z axis if model is attached to an entity 2025-08-10 16:52:08 -04:00
jackowes f4f6cbee38 Address MR comments 2025-08-10 02:02:49 -05:00
Hiroku 09883fd239 Merge branch 'pitch-tilting' into 'main'
Added pitch tilting animation for fishies and birds, implemented for magikarp and pidgey

See merge request cable-mc/cobblemon!1766
2025-08-09 05:25:38 +00:00
Paul 50cea69dfb Added delta_movement query function to entities 2025-08-09 15:12:08 +10:00
Paul 583892364d Added pitch tilting animation for fishies and birds, implemented for magikarp and pidgey 2025-08-09 13:02:34 +10:00
Paul 325d1af484 Fixed swim wanders not working 2025-08-09 10:44:55 +10:00
Polymeta 6ae71f28e6 Merge branch 'spicy-molang' into 'main'
Added string utility related MoLang functions

See merge request cable-mc/cobblemon!1754
2025-08-08 23:29:38 +00:00
Spicy 5687a71de4 Added string utility related MoLang functions 2025-08-08 23:29:38 +00:00
jackowes 1664b917f8 Dolphin time 2025-08-08 16:48:07 -05:00
Polymeta 8199f8d7c2 Merge branch 'josh/helditem-render-fix' into 'main'
Fixed locators being flipped for shoulder mounts and model widgets

See merge request cable-mc/cobblemon!1764
2025-08-08 21:41:09 +00:00
Polymeta 6b39251d65 Merge branch 'Shipwreck-Loot-Table-Adjustments' into 'main'
Shipwreck loot table adjustments

See merge request cable-mc/cobblemon!1765
2025-08-08 21:40:50 +00:00
Slippy cd03e6f8c4 Replaced the Phantoms with Breezes in the Lush Shipwreck Cove 2025-08-08 00:48:19 -05:00
Slippy a1b53f90ad Adjusting the loot table to trial spawners in shipwreck coves a little bit. 2025-08-08 00:40:54 -05:00
Test 55587908a2 update state icons/ride controls overlay 2025-08-07 22:05:09 -04:00
jackowes e1ed483d67 Remove altitude limits on rides 2025-08-07 11:49:59 -05:00
jackowes c460bac177 Accessibility rides for jet and bird. Need hooked up to cobble settings/config and auto changed out if accessibility rides are enabled. Further refinement needed but waiting for the rolling counterparts to be polished first. 2025-08-06 20:29:08 -05:00
jackowes ad6771f8f4 minekart improvements 2025-08-05 21:34:52 -05:00
jackowes ad1a68cefe Add base of minekart behaviour and fix player head movement flag for a few rides 2025-08-05 16:19:18 -05:00
Josh c0677c6353 Fixed item rendering being flipped for shoulder mounts and model widgets 2025-08-05 13:11:14 -04:00
jackowes e8ab8fda71 Fix sound attenuation, inverse input_right() animation data, and properly destroy sounds after dismounting a pokmeon. 2025-08-05 10:09:00 -05:00
jackowes 7def27e437 dupe function from merge 2025-08-04 20:37:24 -05:00
jackowes f3db0ed61d hover anim tests 2025-08-04 20:29:10 -05:00
jackowes fdcff83a2d Merge branch 'main' into riding-controllers-expanded
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/api/riding/behaviour/types/land/VehicleBehaviour.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/riding/behaviour/types/liquid/BoatBehaviour.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/entity/pokemon/PokemonEntity.kt
#	common/src/main/resources/assets/cobblemon/sounds.json
#	common/src/main/resources/data/cobblemon/species/generation1/charizard.json
2025-08-04 20:11:56 -05:00
jackowes 8815758bc0 Adjust all ride params after double tick error. Mirror deltamovement scale to ridevel to fix the catching on terrain issue. Scale smoothly between sprinting turn rate and walking turn rate on horse. 2025-08-04 20:00:34 -05:00
Paul 756989b0c0 Fixed storages not saving (OOPS) 2025-08-04 21:26:01 +10:00
Paul 58a4267f42 Added a living entity look_at_position function so we can make funny battle effects 2025-08-04 21:25:54 +10:00
Hiroku e8826a3d0e Merge branch 'trigger-behaviour' into 'main'
EntityCallbacks

See merge request cable-mc/cobblemon!1757
2025-08-04 07:58:43 +00:00
Veraxiel 0606d57ef1 Edit README.md 2025-08-04 03:50:21 -04:00
Veraxiel ef2578c1a7 Fixed typo on tag readme 2025-08-04 03:49:26 -04:00
Veraxiel 3812de7558 Edit README.md 2025-08-04 03:46:47 -04:00
Hiroku ef558981c2 Add readme 2025-08-04 06:18:14 +00:00
Paul e4f928bce1 Revert "Removed #c:is_cold from #cobblemon:is_cold to prevent pokes in the end that should not be in the end"
This reverts commit 6090fce254.
2025-08-04 15:55:43 +10:00
Paul 6090fce254 Removed #c:is_cold from #cobblemon:is_cold to prevent pokes in the end that should not be in the end 2025-08-04 14:29:20 +10:00
Hiroku 5a5d43404f Merge branch 'safer-change-detection' into 'main'
Removed observable use from Pokemon to better secure saving from rollback issues

See merge request cable-mc/cobblemon!1759
2025-08-04 00:52:56 +00:00
Paul 931c3a7e6c Removed observable use from Pokemon to better secure saving from rollback issues 2025-08-04 10:09:16 +10:00
Paul d5a6e0dffd Ohhh, THAT's why it was written that way 2025-08-03 15:40:56 +10:00
Polymeta f56c357e4e Merge branch 'retype' into 'main'
Small Asset Updates

See merge request cable-mc/cobblemon!1758
2025-08-02 21:16:38 +00:00
dhi_holo 4bfca0da43 Small Asset Updates 2025-08-02 21:16:38 +00:00
jackowes 1aaea961fb Fix cursed golurk jsons 2025-08-02 02:59:52 -05:00
jackowes 8253b96a67 Add multiplayer support for driver input tracking 2025-08-02 01:42:15 -05:00
Paul 52cff2e99d prepare_{id}, damage_{id} hooks for action effects, q.has_argument for battle messages, added q.hit_count to move action effects 2025-08-02 13:11:20 +10:00
Paul 52f21ffc82 License and registration please 2025-08-01 20:38:14 +10:00
Paul 9c31899f3c Fixed up some missing lang for behaviours 2025-08-01 20:32:17 +10:00
Polymeta 9a5172f1cc fix encoding issue with boat ride style
Signed-off-by: Polymeta <40173118+Polymeta@users.noreply.github.com>
2025-08-01 10:48:23 +01:00
Polymeta 11051f2b54 Merge branch 'riding-looping-sound' into 'main'
Ride specific sound implementation

See merge request cable-mc/cobblemon!1641
2025-08-01 08:12:32 +00:00
Jackson Bowes 8b48cfaa50 Ride specific sound implementation 2025-08-01 08:12:32 +00:00
jackowes 6593fcc250 fix jittery mons 2025-08-01 00:59:09 -05:00
jackowes f51376f9c3 quick fixes 2025-07-31 21:58:23 -05:00
jackowes 23ecc3dd05 Merge branch 'main' into riding-controllers-expanded
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/api/riding/behaviour/RidingBehaviour.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/riding/behaviour/RidingController.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/riding/behaviour/types/GenericSwimBehaviour.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/riding/behaviour/types/air/BirdBehaviour.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/riding/behaviour/types/air/GliderBehaviour.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/riding/behaviour/types/air/JetBehaviour.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/riding/behaviour/types/land/HorseBehaviour.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/riding/behaviour/types/land/VehicleBehaviour.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/riding/behaviour/types/liquid/BurstBehaviour.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/entity/pokemon/PokemonEntity.kt
#	common/src/main/resources/assets/cobblemon/bedrock/pokemon/animations/0967_cyclizar/cyclizar.animation.json
#	common/src/main/resources/assets/cobblemon/bedrock/pokemon/models/0967_cyclizar/cyclizar.geo.json
#	common/src/main/resources/assets/cobblemon/bedrock/pokemon/posers/0967_cyclizar/cyclizar.json
#	common/src/main/resources/data/cobblemon/species/generation1/charizard.json
#	common/src/main/resources/data/cobblemon/species/generation9/cyclizar.json
2025-07-31 21:25:01 -05:00
jackowes 9e6f27162e quick tweak 2025-07-31 21:13:06 -05:00
jackowes 6c279e0986 vehicle tweaks 2025-07-31 21:13:06 -05:00
jackowes 76d1291180 vehicle v3 becoming 2025-07-31 21:13:06 -05:00
jackowes 627f09010b Start of vehicle v3 2025-07-31 21:13:03 -05:00
jackowes 9afd0e6fc3 nope 2025-07-31 21:11:28 -05:00
jackowes 1ea361cce9 I cannot for fine the culprit of this increase in vertical impulse when moving as opposed to standing still. :(. Also rocket improvements. 2025-07-31 21:11:28 -05:00
jackowes 41bf22c43a More rocket improvements
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/api/riding/behaviour/types/GenericSwimBehaviour.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/riding/behaviour/types/RocketBehaviour.kt
2025-07-31 21:11:24 -05:00
jackowes 71989292d3 Various rocket improvements including: lower boost turning speed, turn momentum, behaviour switch time decrease, temp golurk flight animation tweaks for fluidity (need to check with gingle), also tweaked the feel and air resistance of the ride as a whole 2025-07-31 21:10:35 -05:00
jackowes f46b13da6a Quick fix to velocity zeroing for dimensions that horse and rocket don't care about. 2025-07-31 21:10:35 -05:00
Polymeta 50c3ffc774 Merge branch 'bugfix-entity-health' into 'main'
Couple Pokémon HP / Entity health in a more sensible way.

See merge request cable-mc/cobblemon!1750
2025-07-28 08:58:23 +00:00
Polymeta daf32a5cf3 Merge branch 'food-tweaks' into 'main'
Eat your, well, lots of things

Closes #1616

See merge request cable-mc/cobblemon!1756
2025-07-28 08:56:41 +00:00
dhi_holo af4d2077ae Eat your, well, lots of things 2025-07-28 08:56:41 +00:00
Paul 3f090ce691 Merge remote-tracking branch 'origin/main' into trigger-behaviour 2025-07-28 11:34:37 +10:00
Paul 8e6681b3aa Introduced EntityCallbacks 2025-07-27 22:02:30 +10:00
Paul 50427965ab Merge remote-tracking branch 'origin/main' into trigger-behaviour 2025-07-26 21:41:09 +10:00
Paul 3966607373 Tidy up the script actions for adding/removing a Behaviour on an entity 2025-07-26 18:34:40 +10:00
Nadja Reitzenstein 29833df50e Couple Pokémon HP / Entity health in a more sensible way.
Specifically, don't touch Battle HP for wild Pokémon so that you can't hurt a Pokémon down out of battle for an easier catch, and always round entity health up so that only a HP of 0 is a health of 0
2025-07-24 13:09:33 +02:00
JacksonB ab1f169fcc add forgotten rocket loop 2025-05-20 22:35:59 -05:00
JacksonB ce5928e783 Add rocket sounds 2025-05-20 22:35:15 -05:00
JacksonB e3e40627e8 Beginnings of rocket controller (for real this time) 2025-05-16 21:41:20 -05:00
JacksonB 140158f5bb Default riding sounds to only be heard by passengers and default sounds to not muffle or reduce in volume when behind a block. Also move the attenuation logic out of RideLoopSound and have openAL handle all attenuation models. 2025-05-11 19:33:00 -05:00
JacksonB aeec991e7b add license 2025-04-27 22:44:06 -05:00
JacksonB 7751b13611 merge main 2025-04-27 18:50:35 -05:00
JacksonB e0cbc0bdc3 Riding Sound refactor to support and arbitrary number of sounds per behaviour per species. Implemented a fade in and out for any rideSound instance and added support for a handful of configurations when specifying the sound. 2025-04-27 16:52:04 -05:00
JacksonB 2f64ab1018 merge main 2025-04-26 17:33:09 -05:00
JacksonB 6c74d30ce8 Fix some filter issues and add saucer noise for magnezone 2025-04-26 17:26:35 -05:00
JacksonB 25956d5f23 temp commit for sound fixes 2025-04-25 16:16:15 -05:00
JacksonB f6832cd44c temp commit for cone raycasting 2025-04-25 11:46:33 -05:00
JacksonB 313e9dadb6 Add soundOcclusion, doppler effect, and custom non linear sound attenuation. 2025-04-25 09:05:43 -05:00
JacksonB 1d171268ed Behaviours can specify a looping ride sound and control its volume and pitch dynamically. Still need to allow multiple sound instances and also clean up my wip code 2025-04-23 00:34:13 -05:00
301 changed files with 15279 additions and 2504 deletions

No files matched your search

+2
View File
@@ -12,9 +12,11 @@
### Developer
- baseScale in NPCs has been removed from classes and presets, and in the entity it is replaced with hitboxScale and renderScale.
- Added `EntityCallbacks` to `NPCEntity` and `PokemonEntity`. You can trigger custom callback types by using `[Entity].callbacks.process(...)`
### MoLang & Datapacks
- Added a `behaviours` datapack folder which motivates the Behaviour Editor screen on NPCs and Pokémon.
- Added "callback" support on PokemonEntity and NPCEntity that allows MoLang scripts to be executed in response to certain scenarios.
- Added very many behaviours to choose from.
- Removed many NPC presets that now exist as behaviours. This is a breaking change if you are using NPCs with these presets.
- NPCs with these presets should be modified to instead list under `"behaviours"` all the equivalent behaviours.
+18 -2
View File
@@ -40,7 +40,9 @@
- Ground type Pokémon are immune to lightning damage
- Added functionality to Everstone when held by a Pokémon; suppresses evolution notification and hides evolve button in summary interface.
- Added new optional property `attachment_options` for most EmitterShapes to be attached to the locator/entities scale, rotation, and/or position. Position is true by default.
- Galarica Nut bushes now generate on beaches
- Galarica Nut bushes now generate on beaches.
- Some Pokémon now pitch their bodies in the direction they're moving.
- Added `/runmolang <molang> [<npc>|<player>|<pokemon>]` command that executes a MoLang expression with the provided options as environment variables, as well as the entity (as `q.entity`) that executed the command.
### Pokémon Added
@@ -239,6 +241,12 @@
- Reorganised the `block` texture folder to be more organised, in line with the `item` texture folder.
- Offset in EmitterShape now ignores scale to be more like Blockbench by default. You can get this behaviour back by adding `"scale": true` in the `attachment_options` property in most EmitterShapes.
- Not specifying a dex in `/pokedex printcalculations {player} {dex}` will now print the National Dex statistics instead of showing all dexes. `/pokedex printcalculations {player} all` is how to view all dex statistics in one command.
- Removed Braised Vivichoke
- Updated Pokémon state icons shown in party interfaces
- MoLang `face` function can now be run on any `PosableEntity` (Like Pokémon!) instead of just NPCs.
- MoLang `run_action_effect` now works on Pokémon.
- Changed MoLang entity function `is_standing_on` to allow for block tags in the list.
- Added entity (as `q.entity`) that executed the command to the `executemolangscript` command.
### Fixes
- Fixed game crashing when removing national pokedex using datapacks
@@ -306,6 +314,8 @@
- Fixed Moon Ball moon phase logic to actually work correctly
- Fixed `/pokedex printcalculations` to now show the correct percentage completed of the Pokedex
- Fixed mod incompatibility with the `Raised` mod
- Fixed a vulnerability that could cause party and PC rollbacks under specific circumstances.
- Fixed a rare edge case where sorting your PC could be rolled back later.
### Developer
- A finished battle now has winners and losers set inside of `PokemonBattle` instead of them always being empty.
@@ -346,7 +356,8 @@
- Added PokedexManager.obtain as a replacement for .catch which is not a friendly function name in Java.
- Added `Pokemon#hyperTrainIV()` and `IVs#setHyperTrainedIV(Stat, Int)`
- `ElementalType` now implelments `ShowdownIdentifiable` to ensure the communcation with showdown stays consistent (also in regards to TeraTypes)
- `ElementalType` now implements `ShowdownIdentifiable` to ensure the communcation with showdown stays consistent (also in regards to TeraTypes)
- Pokemon no longer have a change observable
### MoLang & Datapacks
- The following usages for item predicates can now use item conditions like advancements do, you can learn about them in the [Minecraft wiki](https://minecraft.wiki/w/Advancement_definition#minecraft:filled_bucket)
@@ -390,6 +401,11 @@
- Adds Flows for `STARTER_CHOSEN`, `EV_GAINED`, `POKEMON_RELEASED`, `POKEMON_NICKNAMED`, `HELD_ITEM`, and `TRADE_COMPLETED` events
- Adds Pokemon functions for `pokeball`, `held_item`, `remove_held_item`, `add_aspects`, and `remove_aspects`
- Added `pokemon.hyper_train_iv` as an available Molang function.
- Added `prepare_{effect}` and `damage_{effect}` action effect hooks in battles for more battle particle effects.
- Added `q.has_argument(<argument_name>, [argument_value])` MoLang function to several battle-related action effect contexts.
- Added `q.has_argument_at(<index>, [argument_value])` MoLang function to several battle-related action effect contexts.
- Added `q.hit_count` MoLang function to move action effect contexts.
- Added `is_included`, `to_lower`, `to_upper`, and `string_length` as available Molang functions.
## [1.6.1 (January 26th, 2025)](#1-6-1)
@@ -9,15 +9,6 @@
package com.cobblemon.mod.common;
import com.cobblemon.mod.common.api.orientation.OrientationController;
import net.minecraft.util.Mth;
import net.minecraft.util.SmoothDouble;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.Nullable;
import org.joml.Matrix3f;
import org.joml.Vector3f;
import java.util.function.Function;
public interface OrientationControllable {
OrientationController getOrientationController();
@@ -0,0 +1,17 @@
/*
* 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;
public interface ChannelDuck {
void cobblemon$applyLowPassFilter(float gain, float hfGain);
void cobblemon$inverseAttenuation(float rolloffFactor);
void cobblemon$clearFilters();
// void cobblemon$exponentialAttenuation(float rolloff, float refDistance, float maxDistance);
}
@@ -0,0 +1,18 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.duck;
import org.joml.Vector3f;
public interface PlayerDuck {
void setDriverInput(Vector3f driverInput);
Vector3f getDriverInput();
void setLastSentDriverInput(Vector3f driverInput);
Vector3f getLastSentDriverInput();
}
@@ -0,0 +1,69 @@
/*
* 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.duck.ChannelDuck;
import com.mojang.blaze3d.audio.Channel;
import org.lwjgl.openal.AL10;
import org.lwjgl.openal.EXTEfx;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
@Mixin(Channel.class)
public abstract class ChannelMixin implements ChannelDuck {
@Shadow
private int source;
// Id of filter applied when sound is obstructed
private int cobblemon$lowPassFilterId = 0;
@Override
public void cobblemon$applyLowPassFilter(float gain, float hfGain) {
// Delete old filter if it exists
if (cobblemon$lowPassFilterId != 0) {
AL10.alSourcei(source, EXTEfx.AL_DIRECT_FILTER, 0); // Detach filter from source
EXTEfx.alDeleteFilters(cobblemon$lowPassFilterId); // Delete old filter
cobblemon$lowPassFilterId = 0;
}
// Generate a fresh filter
int filterId = EXTEfx.alGenFilters();
if (!EXTEfx.alIsFilter(filterId)) {
System.err.println("Failed to create OpenAL low-pass filter");
return;
}
EXTEfx.alFilteri(filterId, EXTEfx.AL_FILTER_TYPE, EXTEfx.AL_FILTER_LOWPASS);
EXTEfx.alFilterf(filterId, EXTEfx.AL_LOWPASS_GAIN, gain);
EXTEfx.alFilterf(filterId, EXTEfx.AL_LOWPASS_GAINHF, hfGain);
AL10.alSourcei(source, EXTEfx.AL_DIRECT_FILTER, filterId);
// Save the new filter ID
cobblemon$lowPassFilterId = filterId;
}
@Override
public void cobblemon$inverseAttenuation(float rolloffFactor) {
AL10.alSourcei(source, AL10.AL_DISTANCE_MODEL, AL10.AL_INVERSE_DISTANCE);
AL10.alSourcef(source, AL10.AL_ROLLOFF_FACTOR, rolloffFactor);
AL10.alSourcef(source, AL10.AL_REFERENCE_DISTANCE, 1.0f);
AL10.alSourcef(source, AL10.AL_MAX_DISTANCE, 0.0f);
}
@Override
public void cobblemon$clearFilters() {
AL10.alSourcei(source, EXTEfx.AL_DIRECT_FILTER, 0);
if (EXTEfx.alIsFilter(cobblemon$lowPassFilterId)) {
EXTEfx.alDeleteFilters(cobblemon$lowPassFilterId);
cobblemon$lowPassFilterId = 0;
}
}
}
@@ -8,17 +8,44 @@
package com.cobblemon.mod.common.mixin;
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.Seat;
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 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.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;
import org.joml.Quaternionf;
import org.joml.Vector3f;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(LivingEntity.class)
public abstract class LivingEntityMixin {
public abstract class LivingEntityMixin extends Entity implements OrientationControllable {
public LivingEntityMixin(EntityType<?> entityType, Level level) {
super(entityType, level);
}
@Unique
private final OrientationController cobblemon$orientationController = new OrientationController((LivingEntity) (Object) this);
public OrientationController getOrientationController() {
return cobblemon$orientationController;
}
@Inject(method = "onEffectRemoved", at = @At(value = "TAIL"))
private void cobblemon$onEffectRemoved(MobEffectInstance effect, CallbackInfo ci) {
@@ -28,4 +55,67 @@ public abstract class LivingEntityMixin {
}
}
@Override
public @NotNull HitResult pick(double hitDistance, float partialTicks, boolean hitFluids) {
Entity vehicle = this.getVehicle();
if (vehicle instanceof PokemonEntity pokemonEntity) {
int seatIndex = pokemonEntity.getPassengers().indexOf(this);
Seat seat = pokemonEntity.getSeats().get(seatIndex);
PokemonClientDelegate delegate = (PokemonClientDelegate) pokemonEntity.getDelegate();
MatrixWrapper locator = delegate.getLocatorStates().get(seat.getLocator());
Vec3 locatorOffset = new Vec3(locator.getMatrix().getTranslation(new Vector3f()));
Vec3 eyePosition = cobblemon$getEyePosition(partialTicks, pokemonEntity, locatorOffset);
Vec3 viewVector = this.getViewVector(partialTicks);
Vec3 viewDistanceVector = eyePosition.add(viewVector.x * hitDistance, viewVector.y * hitDistance, viewVector.z * hitDistance);
return this.level()
.clip(
new ClipContext(
eyePosition, viewDistanceVector, ClipContext.Block.OUTLINE, hitFluids ? net.minecraft.world.level.ClipContext.Fluid.ANY : net.minecraft.world.level.ClipContext.Fluid.NONE, this
)
);
}
return super.pick(hitDistance, partialTicks, hitFluids);
}
@Unique
private Vec3 cobblemon$getEyePosition(float partialTicks, PokemonEntity pokemonEntity, Vec3 locatorOffset) {
OrientationController controller = this.getOrientationController();
float currEyeHeight = this.getEyeHeight();
Vec3 offset = locatorOffset.add(pokemonEntity.position());
if (controller.isActive() && controller.getOrientation() != null) {
Quaternionf orientation = controller.getRenderOrientation(partialTicks);
Vec3 rotatedEyeHeight = new Vec3(orientation.transform(new Vector3f(0f, currEyeHeight - (this.getBbHeight() / 2), 0f)));
offset.add(rotatedEyeHeight);
}
return offset;
}
@Inject(method = "tick", at = @At("HEAD"))
private void cobblemon$updateRenderOrientation(CallbackInfo ci) {
this.cobblemon$orientationController.tick();
}
@Override
public void absMoveTo(double x, double y, double z, float yaw, float pitch) {
if (cobblemon$orientationController.getActive()) {
this.absMoveTo(x, y, z);
this.setYRot(yaw % 360.0f);
this.setXRot(pitch % 360.0f);
this.yRotO = this.getYRot();
this.xRotO = this.getXRot();
} else {
this.absMoveTo(x, y, z);
this.absRotateTo(yaw, pitch);
}
}
}
@@ -18,6 +18,7 @@ import com.cobblemon.mod.common.api.pokemon.PokemonSpecies;
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.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.food.FoodProperties;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import org.jetbrains.annotations.Nullable;
import org.joml.Vector3f;
import org.objectweb.asm.Opcodes;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
@@ -61,7 +63,12 @@ import java.util.*;
import java.util.stream.Collectors;
@Mixin(Player.class)
public abstract class PlayerMixin extends LivingEntity implements ScannableEntity, OrientationControllable {
public abstract class PlayerMixin extends LivingEntity implements ScannableEntity, OrientationControllable, PlayerDuck {
@Unique
private Vector3f cobblemon$driverInput;
@Unique
private Vector3f cobblemon$lastSentDriverInput;
@Shadow public abstract CompoundTag getShoulderEntityLeft();
@@ -279,4 +286,25 @@ public abstract class PlayerMixin extends LivingEntity implements ScannableEntit
public OrientationController getOrientationController() {
return cobblemon$orientationController;
}
@Override
public void setDriverInput(Vector3f driverInput) {
cobblemon$driverInput = driverInput;
}
@Override
public Vector3f getDriverInput() {
return cobblemon$driverInput;
}
@Override
public void setLastSentDriverInput(Vector3f lastSentDriverInput) {
cobblemon$lastSentDriverInput = lastSentDriverInput;
}
@Override
public Vector3f getLastSentDriverInput() {
return cobblemon$lastSentDriverInput;
}
}
@@ -12,9 +12,11 @@ import com.cobblemon.mod.common.CobblemonNetwork;
import com.cobblemon.mod.common.OrientationControllable;
import com.cobblemon.mod.common.api.net.NetworkPacket;
import com.cobblemon.mod.common.api.orientation.OrientationController;
import com.cobblemon.mod.common.duck.PlayerDuck;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import com.cobblemon.mod.common.mixin.accessor.ChunkMapAccessor;
import com.cobblemon.mod.common.mixin.accessor.TrackedEntityAccessor;
import com.cobblemon.mod.common.net.messages.client.orientation.ClientboundUpdateDriverInputPacket;
import com.cobblemon.mod.common.net.messages.client.orientation.ClientboundUpdateOrientationPacket;
import com.cobblemon.mod.common.net.messages.client.pokemon.update.ClientboundUpdateRidingStatePacket;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
@@ -26,6 +28,7 @@ import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.network.ServerPlayerConnection;
import net.minecraft.world.entity.Entity;
import org.joml.Matrix3f;
import org.joml.Vector3f;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
@@ -55,6 +58,7 @@ public abstract class ServerEntityMixin {
private void cobblemon$sendChanges(CallbackInfo ci) {
cobblemon$sendOrientationChanges();
cobblemon$sendRidingStateChanges();
cobblemon$sendDriverInput();
}
private void cobblemon$sendRidingStateChanges() {
@@ -82,6 +86,19 @@ public abstract class ServerEntityMixin {
cobblemon$broadcast(new ClientboundUpdateOrientationPacket(currOrientation, currActive, entity.getId()));
}
private void cobblemon$sendDriverInput() {
if (!(this.entity instanceof ServerPlayer serverPlayer)) return;
if(!(serverPlayer.getVehicle() instanceof PokemonEntity)) return;
Vector3f driverInput = ((PlayerDuck)serverPlayer).getDriverInput();
if(driverInput == null) return;
// If no change in input is detected then don't send a new packet
Vector3f lastSentDriverInput = ((PlayerDuck)serverPlayer).getLastSentDriverInput();
if (driverInput.equals(lastSentDriverInput)) return;
((PlayerDuck)serverPlayer).setLastSentDriverInput(driverInput);
cobblemon$broadcast(new ClientboundUpdateDriverInputPacket(driverInput, entity.getId()));
}
private void cobblemon$broadcast(NetworkPacket<?> packet) {
if (!(entity.level() instanceof ServerLevel level)) return;
ChunkMap chunkMap = level.getChunkSource().chunkMap;
@@ -97,5 +114,4 @@ public abstract class ServerEntityMixin {
}
}
}
@@ -8,23 +8,33 @@
package com.cobblemon.mod.common.mixin;
import com.cobblemon.mod.common.api.riding.sound.RideAttenuationModel;
import com.cobblemon.mod.common.api.riding.sound.RideLoopSound;
import com.cobblemon.mod.common.client.sound.BattleMusicController;
import com.cobblemon.mod.common.duck.ChannelDuck;
import com.cobblemon.mod.common.duck.SoundEngineDuck;
import com.google.common.collect.Multimap;
import com.llamalad7.mixinextras.sugar.Local;
import com.mojang.blaze3d.audio.Channel;
import net.minecraft.client.resources.sounds.Sound;
import net.minecraft.client.resources.sounds.SoundInstance;
import net.minecraft.client.resources.sounds.TickableSoundInstance;
import net.minecraft.client.sounds.ChannelAccess;
import net.minecraft.client.sounds.SoundEngine;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Final;
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.LocalCapture;
import java.util.Iterator;
import java.util.Map;
@Mixin(SoundEngine.class)
@@ -45,6 +55,86 @@ public abstract class SoundEngineMixin implements SoundEngineDuck {
@Shadow
public abstract boolean isActive(SoundInstance sound);
@Inject(method = "play",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/client/sounds/ChannelAccess$ChannelHandle;execute(Ljava/util/function/Consumer;)V"
),
cancellable = true
)
private void cobblemon$overrideChannelExecute(
SoundInstance sound,
CallbackInfo ci,
@Local(name = "h") float h,
@Local(name = "i") float i,
@Local(name = "g") float g,
@Local(name = "bl2") boolean bl2,
@Local(name = "bl3") boolean bl3,
@Local(name = "bl") boolean bl,
@Local(name = "vec3") Vec3 vec3,
@Local(name = "channelHandle") ChannelAccess.ChannelHandle handle
) {
if (!(sound instanceof RideLoopSound rideSound)) return;
if (handle == null) return;
handle.execute((channel) -> {
channel.setPitch(i);
channel.setVolume(h);
if (rideSound.getRideAttenuation() == RideAttenuationModel.LINEAR) {
channel.linearAttenuation(g);
} else if (rideSound.getRideAttenuation() == RideAttenuationModel.EXPONENTIAL){
((ChannelDuck) channel).cobblemon$inverseAttenuation(g);
} else {
channel.disableAttenuation();
}
// Apply low pass filter if the rideSound dictate that it should be muffled
if (rideSound.getShouldMuffle()) {
((ChannelDuck) channel).cobblemon$applyLowPassFilter(1.0f, rideSound.getMuffleAmount());
} else {
((ChannelDuck) channel).cobblemon$clearFilters();
}
channel.setLooping(bl2 && !bl3);
channel.setSelfPosition(vec3);
channel.setRelative(bl);
});
}
@Inject(
method = "tickNonPaused",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/client/sounds/ChannelAccess$ChannelHandle;execute(Ljava/util/function/Consumer;)V",
ordinal = 0
)
)
private void cobblemon$tickableSoundTick(
CallbackInfo ci,
@Local(name = "tickableSoundInstance") TickableSoundInstance instance,
@Local(name = "channelHandle", ordinal = 0) ChannelAccess.ChannelHandle handle,
@Local(name = "f", ordinal = 0) float volume,
@Local(name = "g", ordinal = 0) float pitch,
@Local(name = "vec3", ordinal = 0) Vec3 vec3
) {
if (!(instance instanceof RideLoopSound rideSound)) return;
handle.execute(channel -> {
channel.setVolume(volume);
channel.setPitch(pitch);
channel.setSelfPosition(vec3);
// Apply low pass filter if the rideSound dictate that it should be muffled
if (rideSound.getShouldMuffle()) {
((ChannelDuck) channel).cobblemon$applyLowPassFilter(1.0f, rideSound.getMuffleAmount());
} else {
((ChannelDuck) channel).cobblemon$clearFilters();
}
});
}
/** Resumes the Source belonging to the queried SoundInstance. */
private void resume(SoundInstance sound) {
ChannelAccess.ChannelHandle sourceManager = this.instanceToChannel.get(sound);
@@ -8,6 +8,7 @@
package com.cobblemon.mod.common.mixin.accessor;
import net.minecraft.server.level.ServerEntity;
import net.minecraft.server.network.ServerPlayerConnection;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
@@ -18,4 +19,7 @@ import java.util.Set;
public interface TrackedEntityAccessor {
@Accessor("seenBy")
Set<ServerPlayerConnection> getSeenBy();
@Accessor("serverEntity")
ServerEntity getServerEntity();
}
@@ -8,6 +8,7 @@
package com.cobblemon.mod.common.mixin.client;
import com.cobblemon.mod.common.OrientationControllable;
import com.cobblemon.mod.common.client.render.player.MountedPlayerRenderer;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import net.minecraft.client.model.HumanoidModel;
@@ -59,9 +60,9 @@ public class HumanoidModelMixin {
}
Entity vehicle = entity.getVehicle();
if (vehicle instanceof PokemonEntity pokemonEntity && entity instanceof AbstractClientPlayer) {
if (vehicle instanceof PokemonEntity pokemonEntity && entity instanceof OrientationControllable) {
var shouldRotatePlayerHead = pokemonEntity.ifRidingAvailableSupply(false, (behaviour, settings, state) -> {
return behaviour.shouldRotatePlayerHead(settings, state, pokemonEntity);
return behaviour.shouldRotateRiderHead(settings, state, pokemonEntity);
});
if (!shouldRotatePlayerHead) {
@@ -73,7 +74,9 @@ public class HumanoidModelMixin {
}
}
MountedPlayerRenderer.INSTANCE.animate(pokemonEntity, (AbstractClientPlayer) entity, relevantPartsByName, netHeadYaw, headPitch, ageInTicks, limbSwing, limbSwingAmount);
if (!(entity instanceof AbstractClientPlayer player)) return;
MountedPlayerRenderer.INSTANCE.animate(pokemonEntity, player, relevantPartsByName, netHeadYaw, headPitch, ageInTicks, limbSwing, limbSwingAmount);
}
}
}
@@ -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.mixin.client;
import com.cobblemon.mod.common.client.render.entity.MountedLivingEntityRenderer;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.model.EntityModel;
import net.minecraft.client.renderer.entity.LivingEntityRenderer;
import net.minecraft.world.entity.LivingEntity;
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(LivingEntityRenderer.class)
public class LivingEntityRendererMixin<T extends LivingEntity, M extends EntityModel<T>> {
@Inject(
method = "setupRotations",
at = @At("HEAD")
)
private void cobblemon$modifyRoll(LivingEntity entity, PoseStack poseStack, float bob, float yBodyRot, float partialTick, float scale, CallbackInfo ci) {
if (entity.isPassenger()) {
var vehicle = entity.getVehicle();
if (vehicle instanceof PokemonEntity pokemonEntity) {
MountedLivingEntityRenderer.INSTANCE.render(entity, pokemonEntity, poseStack, bob, yBodyRot, partialTick, scale);
}
}
}
}
@@ -17,8 +17,10 @@ import com.cobblemon.mod.common.client.render.MatrixWrapper;
import com.cobblemon.mod.common.client.render.models.blockbench.FloatingState;
import com.cobblemon.mod.common.client.render.models.blockbench.PosableModel;
import com.cobblemon.mod.common.client.render.models.blockbench.repository.VaryingModelRepository;
import com.cobblemon.mod.common.duck.PlayerDuck;
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.mojang.authlib.GameProfile;
import net.minecraft.client.CameraType;
import net.minecraft.client.Minecraft;
@@ -28,6 +30,7 @@ import net.minecraft.client.player.LocalPlayer;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.ClipContext;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.HitResult;
@@ -83,6 +86,23 @@ public abstract class LocalPlayerMixin extends AbstractClientPlayer implements O
controllable.getOrientationController().setActive(shouldUseCustomOrientation);
}
@Inject(method = "rideTick", at = @At("HEAD"))
private void cobblemon$updateDriverInputRideTick(CallbackInfo ci) {
if (Minecraft.getInstance().player != (Object)this) return;
if (!(this.getVehicle() instanceof PokemonEntity pokemonEntity)) return;
// Gather driver Input
float vertInput = this.jumping ? 1.0f : (((Player)this).isShiftKeyDown() ? -1.0f : 0.0f);
Vector3f driverInput = new Vector3f(Math.signum(this.xxa), vertInput, Math.signum(this.zza));
// Check if player input has changed. If it has then update the last sent and send it
Vector3f lastSentDriverInput = ((PlayerDuck)this).getLastSentDriverInput();
if (driverInput.equals(lastSentDriverInput)) return;
((PlayerDuck)this).setLastSentDriverInput(driverInput);
CobblemonNetwork.INSTANCE.sendToServer(new ServerboundUpdateDriverInputPacket(driverInput));
}
@Unique
private boolean cobblemon$shouldUseCustomOrientation(LocalPlayer player) {
var playerVehicle = player.getVehicle();
@@ -127,7 +147,7 @@ public abstract class LocalPlayerMixin extends AbstractClientPlayer implements O
return super.pick(hitDistance, partialTicks, hitFluids);
}
Vec3 locatorOffset = new Vec3(locator.getMatrix().getTranslation(new Vector3f()));
Vec3 locatorOffset = locator != null ? new Vec3(locator.getMatrix().getTranslation(new Vector3f())) : Vec3.ZERO;
OrientationController controller = this.getOrientationController();
@@ -9,12 +9,10 @@
package com.cobblemon.mod.common.mixin.client;
import com.cobblemon.mod.common.client.render.ClientPlayerIcon;
import com.cobblemon.mod.common.client.render.player.MountedPlayerRenderer;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.model.EntityModel;
import net.minecraft.client.model.PlayerModel;
import net.minecraft.client.player.AbstractClientPlayer;
import net.minecraft.client.renderer.MultiBufferSource;
@@ -55,17 +53,4 @@ public abstract class PlayerRendererMixin extends LivingEntityRenderer<AbstractC
playerModel.crouching = false;
}
}
@Inject(
method = "setupRotations(Lnet/minecraft/client/player/AbstractClientPlayer;Lcom/mojang/blaze3d/vertex/PoseStack;FFFF)V",
at = @At("HEAD")
)
private void cobblemon$modifyRoll(AbstractClientPlayer player, PoseStack poseStack, float a, float b, float partialTicks, float i, CallbackInfo ci) {
if (player.isPassenger()) {
var vehicle = player.getVehicle();
if (vehicle instanceof PokemonEntity entity) {
MountedPlayerRenderer.INSTANCE.render(player, entity, poseStack, a, b, partialTicks, i);
}
}
}
}
@@ -75,6 +75,7 @@ import com.cobblemon.mod.common.config.constraint.IntConstraint
import com.cobblemon.mod.common.config.starter.StarterConfig
import com.cobblemon.mod.common.data.CobblemonDataProvider
import com.cobblemon.mod.common.events.AdvancementHandler
import com.cobblemon.mod.common.events.EntityCallbackHandler
import com.cobblemon.mod.common.events.FlowHandler
import com.cobblemon.mod.common.events.PokedexHandler
import com.cobblemon.mod.common.events.ServerTickHandler
@@ -313,6 +314,7 @@ object Cobblemon {
CobblemonEvents.POKEMON_PROPERTY_INITIALISED.emit(Unit)
FlowHandler.setup()
EntityCallbackHandler.setup()
ifDedicatedServer {
isDedicatedServer = true
@@ -59,6 +59,7 @@ object CobblemonCommands {
ApplyPlayerTextureCommand.register(dispatcher)
RenameBoxCommand.register(dispatcher)
ChangeBoxWallpaperCommand.register(dispatcher)
RunMolangCommand.register(dispatcher)
RunMolangScriptCommand.register(dispatcher)
SpectateBattleCommand.register(dispatcher)
CobblemonConfigCommand.register(dispatcher)
@@ -389,40 +389,28 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
val SWEET_HEART = noSettingsItem("sweet_heart") // todo make a SweetHeartItem class for breeding purposes
@JvmField
val TASTY_TAIL = noSettingsItem("tasty_tail")
val TASTY_TAIL = create("tasty_tail", foodItem(3, 0.3f))
@JvmField // todo get final values for this from vera
val PEWTER_CRUNCHIES = regionalFoodItem("pewter_crunchies", 16, 10, 1.2f, MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1f, false)
@JvmField // todo get final values for this from vera
val RAGE_CANDY_BAR = regionalFoodItem("rage_candy_bar", 16, 10, 1.2f, MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1f, false)
@JvmField // todo get final values for this from vera
val LAVA_COOKIE = regionalFoodItem("lava_cookie", 16, 10, 1.2f, MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1f)
@JvmField // todo get final values for this from vera
val OLD_GATEAU = regionalFoodItem("old_gateau", 16, 10, 1.2f, MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1f)
@JvmField // todo get final values for this from vera
val CASTELIACONE = regionalFoodItem("casteliacone", 16, 10, 1.2f, MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1f)
@JvmField // todo get final values for this from vera
val SHALOUR_SABLE = regionalFoodItem("shalour_sable", 16, 10, 1.2f, MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1f)
@JvmField // todo get final values for this from vera
val LUMIOSE_GALETTE = regionalFoodItem("lumiose_galette", 16, 10, 1.2f, MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1f)
@JvmField // todo get final values for this from vera
val BIG_MALASADA = regionalFoodItem("big_malasada", 16, 10, 1.2f, MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1f)
@JvmField // todo get final values for this from vera
val SMOKED_TAIL_CURRY = regionalFoodItem("smoked_tail_curry", 16, 10, 1.2f, MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1f, false, ItemStack(Items.BOWL, 1))
@JvmField // todo get final values for this from vera
val JUBILIFE_MUFFIN = regionalFoodItem("jubilife_muffin", 16, 10, 1.2f, MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1f)
@JvmField // todo get final values for this from vera
val OPEN_FACED_SANDWICH = regionalFoodItem("open_faced_sandwich", 16, 10, 1.2f, MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1f)
@JvmField
val PEWTER_CRUNCHIES = regionalFoodItem("pewter_crunchies", 16, 7, 0.343f, false)
@JvmField
val RAGE_CANDY_BAR = regionalFoodItem("rage_candy_bar", 16, 8, 0.2f, false)
@JvmField
val LAVA_COOKIE = regionalFoodItem("lava_cookie", 16, 7, 0.343f, false)
@JvmField
val OLD_GATEAU = regionalFoodItem("old_gateau", 16, 9, 0.1335f, false)
@JvmField
val CASTELIACONE = regionalFoodItem("casteliacone", 16, 5, .28f, false)
@JvmField
val LUMIOSE_GALETTE = regionalFoodItem("lumiose_galette", 16, 8, 0.2f, false)
@JvmField
val BIG_MALASADA = regionalFoodItem("big_malasada", 16, 7, 0.343f, false)
@JvmField
val SMOKED_TAIL_CURRY = regionalFoodItem("smoked_tail_curry", 16, 10, 0.6f, false, ItemStack(Items.BOWL, 1))
@JvmField
val JUBILIFE_MUFFIN = regionalFoodItem("jubilife_muffin", 16, 8, 0.2f, false)
@JvmField
val OPEN_FACED_SANDWICH = regionalFoodItem("open_faced_sandwich", 16, 13, 0.5f, false)
// todo we might need to wait on these for later? These impact battles and may be harder to do
/*@JvmField
@@ -435,28 +423,21 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val POTATO_MOCHI = create("potato_mochi", CobblemonItem(Properties().stacksTo(16)
.food(FoodProperties.Builder()
.nutrition(10) // todo get final values for this from vera
.saturationModifier(1.2F) // todo get final values for this from vera
.build())))
@JvmField
val FRIED_RICE = create("fried_rice", CobblemonItem(Properties().stacksTo(16)
.food(FoodProperties.Builder()
.nutrition(10) // todo get final values for this from vera
.saturationModifier(1.2F) // todo get final values for this from vera
.usingConvertsTo(Items.BOWL)
.nutrition(8)
.saturationModifier(0.2375F)
.build())))
@JvmField
val CANDIED_APPLE = create("candied_apple", CobblemonItem(Properties().stacksTo(16)
.food(FoodProperties.Builder()
.nutrition(10) // todo get final values for this from vera
.saturationModifier(1.2F) // todo get final values for this from vera
.nutrition(6)
.saturationModifier(0.2335F)
.usingConvertsTo(Items.STICK)
.build())))
@JvmField
val CANDIED_BERRY = create("candied_berry", CobblemonItem(Properties().stacksTo(16)
.food(FoodProperties.Builder()
.nutrition(10) // todo get final values for this from vera
.saturationModifier(1.2F) // todo get final values for this from vera
.nutrition(5)
.saturationModifier(0.22F)
.usingConvertsTo(Items.STICK)
.build())))
@@ -684,14 +665,12 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val MEDICINAL_LEEK = heldItem("medicinal_leek", MedicinalLeekItem(CobblemonBlocks.MEDICINAL_LEEK, Item.Properties().food(FoodProperties.Builder().fast().nutrition(1).saturationModifier(0.2f).build())), "leek")
@JvmField
val ROASTED_LEEK = compostableItem("roasted_leek", CobblemonItem(Item.Properties().food(FoodProperties.Builder().fast().nutrition(3).saturationModifier(0.3f).build())), 0.85f)
@JvmField
val BRAISED_VIVICHOKE = compostableItem("braised_vivichoke", foodItem(6, 0.6F), 0.85f)
val ROASTED_LEEK = compostableItem("roasted_leek", CobblemonItem(Item.Properties().food(FoodProperties.Builder().fast().nutrition(3).saturationModifier(0.2f).build())), 0.85f)
@JvmField
val VIVICHOKE_DIP = create("vivichoke_dip", object : CobblemonItem(Properties().stacksTo(1)
.food(FoodProperties.Builder()
.nutrition(10)
.saturationModifier(1.2F)
.saturationModifier(0.6F)
.effect(MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1F)
.alwaysEdible()
.usingConvertsTo(Items.BOWL)
@@ -702,7 +681,7 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
}
})
@JvmField
val ENERGY_ROOT = compostableItem("energy_root", EnergyRootItem(CobblemonBlocks.ENERGY_ROOT, Item.Properties().food(FoodProperties.Builder().nutrition(1).fast().saturationModifier(0.2f).build())), 0.60f)
val ENERGY_ROOT = compostableItem("energy_root", EnergyRootItem(CobblemonBlocks.ENERGY_ROOT, Properties()), 0.60f)
@JvmField
val REVIVAL_HERB = compostableItem("revival_herb", RevivalHerbItem(CobblemonBlocks.REVIVAL_HERB))
@JvmField
@@ -732,7 +711,7 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val HEAL_POWDER = create("heal_powder", HealPowderItem())
@JvmField
val LEEK_AND_POTATO_STEW = create("leek_and_potato_stew", CobblemonItem(Item.Properties().food(FoodProperties.Builder().nutrition(8).saturationModifier(0.6f).usingConvertsTo(Items.BOWL).build()).stacksTo(1)))
val LEEK_AND_POTATO_STEW = create("leek_and_potato_stew", CobblemonItem(Item.Properties().food(FoodProperties.Builder().nutrition(6).saturationModifier(0.6f).usingConvertsTo(Items.BOWL).build()).stacksTo(1)))
@JvmField
val REVIVE = create("revive", ReviveItem(max = false))
@JvmField
@@ -1543,8 +1522,6 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
stacksTo: Int,
nutrition: Int,
saturationModifier: Float,
effect: MobEffectInstance? = null,
effectChance: Float? = null,
alwaysEdible: Boolean = false,
convertsToOnUse: ItemStack? = null
): RegionalFoodItem {
@@ -1552,10 +1529,6 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
.nutrition(nutrition)
.saturationModifier(saturationModifier)
if (effect != null && effectChance != null) {
foodPropertiesBuilder.effect(effect, effectChance)
}
if (alwaysEdible == true) {
foodPropertiesBuilder.alwaysEdible()
}
@@ -41,6 +41,7 @@ import com.cobblemon.mod.common.client.net.pasture.UpdatePastureConflictFlagHand
import com.cobblemon.mod.common.client.net.pokedex.ServerConfirmedRegisterHandler
import com.cobblemon.mod.common.client.net.pokemon.update.ClientboundUpdateRidingStateHandler
import com.cobblemon.mod.common.client.net.pokemon.update.PokemonUpdatePacketHandler
import com.cobblemon.mod.common.client.net.riding.ClientboundUpdateDriverInputHandler
import com.cobblemon.mod.common.client.net.settings.OpenCobblemonConfigEditorHandler
import com.cobblemon.mod.common.client.net.settings.ServerSettingsPacketHandler
import com.cobblemon.mod.common.client.net.sound.UnvalidatedPlaySoundS2CPacketHandler
@@ -83,6 +84,7 @@ import com.cobblemon.mod.common.net.messages.client.effect.SpawnSnowstormEntityP
import com.cobblemon.mod.common.net.messages.client.effect.SpawnSnowstormParticlePacket
import com.cobblemon.mod.common.net.messages.client.npc.CloseNPCEditorPacket
import com.cobblemon.mod.common.net.messages.client.npc.OpenNPCEditorPacket
import com.cobblemon.mod.common.net.messages.client.orientation.ClientboundUpdateDriverInputPacket
import com.cobblemon.mod.common.net.messages.client.orientation.ClientboundUpdateOrientationPacket
import com.cobblemon.mod.common.net.messages.client.pasture.ClosePasturePacket
import com.cobblemon.mod.common.net.messages.client.pasture.OpenPasturePacket
@@ -173,6 +175,7 @@ import com.cobblemon.mod.common.net.messages.server.pokemon.update.SetMarkingsPa
import com.cobblemon.mod.common.net.messages.server.pokemon.update.ServerboundUpdateRidingStatePacket
import com.cobblemon.mod.common.net.messages.server.pokemon.update.evolution.AcceptEvolutionPacket
import com.cobblemon.mod.common.net.messages.server.riding.DismountPokemonPacket
import com.cobblemon.mod.common.net.messages.server.riding.ServerboundUpdateDriverInputPacket
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
@@ -223,6 +226,7 @@ import com.cobblemon.mod.common.net.serverhandling.pokemon.update.SetActiveMarkH
import com.cobblemon.mod.common.net.serverhandling.pokemon.update.SetMarkingsHandler
import com.cobblemon.mod.common.net.serverhandling.pokemon.update.ServerboundUpdateRidingStateHandler
import com.cobblemon.mod.common.net.serverhandling.riding.DismountPokemonPacketHandler
import com.cobblemon.mod.common.net.serverhandling.riding.DriverInputPacketHandler
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
@@ -465,6 +469,7 @@ object CobblemonNetwork {
// Riding
list.add(PacketRegisterInfo(ClientboundUpdateRidingStatePacket.ID, ClientboundUpdateRidingStatePacket::decode, ClientboundUpdateRidingStateHandler))
list.add(PacketRegisterInfo(ClientboundUpdateDriverInputPacket.ID, ClientboundUpdateDriverInputPacket::decode, ClientboundUpdateDriverInputHandler))
return list
}
@@ -571,6 +576,7 @@ object CobblemonNetwork {
list.add(PacketRegisterInfo(ServerboundUpdateRidingStatsPacket.ID, ServerboundUpdateRidingStatsPacket::decode, ServerboundUpdateRidingStatsHandler))
list.add(PacketRegisterInfo(ServerboundUpdateRidingStatRangePacket.ID, ServerboundUpdateRidingStatRangePacket::decode, ServerboundUpdateRidingStatRangeHandler))
list.add(PacketRegisterInfo(DismountPokemonPacket.ID, DismountPokemonPacket::decode, DismountPokemonPacketHandler))
list.add(PacketRegisterInfo(ServerboundUpdateDriverInputPacket.ID, ServerboundUpdateDriverInputPacket::decode, DriverInputPacketHandler))
// Cooking
list.add(PacketRegisterInfo(ToggleCookingPotLidPacket.ID, ToggleCookingPotLidPacket::decode, ToggleCookingPotLidHandler))
@@ -275,6 +275,11 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, ResourceKey<Regi
RELIC_COIN_SACK_STEP
)
@JvmField
val RIDE_LOOP_LEATHER = create("ride.loop.leather")
@JvmField
val RIDE_LOOP_PLUMAGE = create("ride.loop.plumage")
@JvmField
val MOVE_QUICKATTACK_TARGET = this.create("move.quickattack.target")
@JvmField
@@ -319,6 +324,8 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, ResourceKey<Regi
@JvmField
val IMPACT_WATER = this.create("impact.water")
@JvmField
val TUMBLESTONE_SOUNDS = SoundType(1f, 1.1f,
TUMBLESTONE_BREAK,
@@ -8,8 +8,15 @@
package com.cobblemon.mod.common.api.ai
import com.bedrockk.molang.runtime.MoLangRuntime
import com.cobblemon.mod.common.api.ai.config.BehaviourConfig
import com.cobblemon.mod.common.api.molang.ExpressionLike
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.setup
import com.cobblemon.mod.common.api.scripting.CobblemonScripts
import com.cobblemon.mod.common.entity.MoLangScriptingEntity
import com.cobblemon.mod.common.util.resolve
import com.cobblemon.mod.common.util.withQueryValue
import com.mojang.serialization.Dynamic
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.entity.LivingEntity
@@ -28,6 +35,9 @@ class BehaviourConfigurationContext {
val memories = mutableSetOf<MemoryModuleType<*>>()
val sensors = mutableSetOf<SensorType<*>>()
val onAddScripts = mutableListOf<ResourceLocation>()
val onAdd = mutableListOf<ExpressionLike>()
fun getOrCreateActivity(activity: Activity): ActivityConfigurationContext {
return activities.firstOrNull { it.activity == activity } ?: ActivityConfigurationContext(activity).also(activities::add)
}
@@ -48,6 +58,14 @@ class BehaviourConfigurationContext {
this.sensors.addAll(sensors)
}
fun addOnAddScript(script: ResourceLocation) {
onAddScripts.add(script)
}
fun addOnAddScript(script: ExpressionLike) {
onAdd.add(script)
}
fun apply(entity: LivingEntity, behaviourConfigs: List<BehaviourConfig>, dynamic: Dynamic<*>) {
if (entity is MoLangScriptingEntity) {
@@ -62,6 +80,11 @@ class BehaviourConfigurationContext {
// Apply the brain config
if (entity is MoLangScriptingEntity) {
brain = entity.assignNewBrainWithMemoriesAndSensors(dynamic, memories, sensors)
val runtime = MoLangRuntime().setup()
runtime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
onAddScripts.forEach { CobblemonScripts.run(it, runtime) }
onAdd.forEach { runtime.resolve(it) }
}
activities.forEach { it.apply(entity) }
brain.setCoreActivities(coreActivities)
@@ -9,7 +9,17 @@
package com.cobblemon.mod.common.api.ai
import com.bedrockk.molang.Expression
import com.bedrockk.molang.runtime.MoLangRuntime
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.setup
import com.cobblemon.mod.common.api.molang.ObjectValue
import com.cobblemon.mod.common.api.scripting.CobblemonScripts
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import com.cobblemon.mod.common.util.resolve
import net.minecraft.world.entity.Entity
/**
* This is used in the AI system to store optional additional information about the attack target,
@@ -30,5 +40,32 @@ import com.cobblemon.mod.common.util.asExpression
*/
class CobblemonAttackTargetData(
/** An expression that returns false when the target is no longer worth attacking. */
val shouldContinue: Expression = "true".asExpression()
)
var shouldContinue: Expression = "true".asExpression(),
var hitsUntilDisengage: Int = -1, // -1 means no limit
var disengageAction: (entity: Entity) -> Unit = {}
) {
val struct = ObjectValue<CobblemonAttackTargetData>(this).also {
it.addFunction("hits_until_disengage") { DoubleValue(hitsUntilDisengage.toDouble()) }
it.addFunction("set_hits_until_disengage") { params ->
val value = params.getDouble(0)
hitsUntilDisengage = value.toInt()
DoubleValue(hitsUntilDisengage.toDouble())
}
it.addFunction("set_disengage_action") { params ->
val script = params.getString(0).asIdentifierDefaultingNamespace()
val function = CobblemonScripts.scripts[script] ?: run {
Cobblemon.LOGGER.warn("set_disengage_action was given script '$script' which does not exist.")
return@addFunction DoubleValue.ZERO
}
disengageAction = { entity ->
val runtime = MoLangRuntime().setup()
runtime.environment.query.addFunction("entity") { entity.asMostSpecificMoLangValue() }
runtime.resolve(function)
}
DoubleValue.ONE
}
it.addFunction("unset_disengage_action") { params ->
disengageAction = {}
}
}
}
@@ -17,6 +17,7 @@ import com.cobblemon.mod.common.api.scripting.CobblemonScripts
import com.cobblemon.mod.common.util.asTranslated
import com.cobblemon.mod.common.util.resolve
import com.cobblemon.mod.common.util.withQueryValue
import com.google.gson.annotations.SerializedName
import net.minecraft.network.chat.Component
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.entity.LivingEntity
@@ -28,23 +29,35 @@ class CobblemonBehaviour(
val visible: Boolean = true,
val entityType: ResourceLocation? = null,
val configurations: List<BehaviourConfig> = mutableListOf(),
val undo: ExpressionLike? = null,
val undoScript: ResourceLocation? = null,
// I feel like the onAdd+onAddScript etc could be merged using some interface and a clever deserializer,
// detect if it's a ResourceLocation and failing that, Expression. The on[..]Script fields are kinda fringe though.
@SerializedName("onRemove", alternate = ["undo"])
val onRemove: ExpressionLike? = null,
@SerializedName("onRemoveScript", alternate = ["undoScript"])
val onRemoveScript: ResourceLocation? = null,
val onAdd: ExpressionLike? = null,
val onAddScript: ResourceLocation? = null,
) {
fun canBeApplied(entity: LivingEntity) = entityType?.let { entityType == entity.type.builtInRegistryHolder().unwrapKey().get().location() } != false
fun configure(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext) {
if (onAdd != null) {
behaviourConfigurationContext.addOnAddScript(onAdd)
}
if (onAddScript != null) {
behaviourConfigurationContext.addOnAddScript(onAddScript)
}
configurations.forEach { it.configure(entity, behaviourConfigurationContext) }
}
/** Undoes anything that needs undoing once this configuration is being removed from an entity that had it before. */
fun undo(entity: LivingEntity) {
fun onRemove(entity: LivingEntity) {
val runtime = MoLangRuntime().setup()
runtime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
if (undoScript != null) {
CobblemonScripts.run(undoScript, runtime)
if (onRemoveScript != null) {
CobblemonScripts.run(onRemoveScript, runtime)
}
if (undo != null) {
runtime.resolve(undo)
if (onRemove != null) {
runtime.resolve(onRemove)
}
}
}
@@ -0,0 +1,29 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.ai
import net.minecraft.world.entity.ai.behavior.BlockPosTracker
import net.minecraft.world.phys.Vec3
/**
* Wraps around [BlockPosTracker] to add additional flags for Cobblemon-specific behavior.
*
* @author Hiroku
* @since August 4th, 2025
*/
class CobblemonBlockPosTracker(
pos: Vec3,
val flags: Set<String> = emptySet(),
) : BlockPosTracker(pos) {
companion object {
const val BATTLE_LOOK_BYPASS_FLAG = "battle_look_bypass"
}
fun isBattleLookBypass() = flags.contains(BATTLE_LOOK_BYPASS_FLAG)
}
@@ -108,7 +108,7 @@ class WaterWanderTaskConfig : SingleTaskConfig {
completionRange = 0
)
)
lookTarget.set(BlockPosTracker(target /* trust me dude. */))
lookTarget.set(BlockPosTracker(target))
return@Trigger true
}
}
@@ -13,6 +13,7 @@ import com.bedrockk.molang.runtime.struct.QueryStruct
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.Cobblemon.LOGGER
import com.cobblemon.mod.common.CobblemonBuildDetails
import com.cobblemon.mod.common.CobblemonNetwork
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.actor.ActorType
@@ -77,7 +78,7 @@ open class PokemonBattle(
val side2: BattleSide
) {
/** Whether logging will be silenced for this battle. */
var mute = true
var mute = !CobblemonBuildDetails.SNAPSHOT
val struct = this.asMoLangValue()
val runtime = MoLangRuntime().also { it.environment.query = struct }
@@ -10,6 +10,7 @@ package com.cobblemon.mod.common.api.events.pokeball
import com.cobblemon.mod.common.api.events.Cancelable
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.moLangFunctionMap
import com.cobblemon.mod.common.entity.pokeball.EmptyPokeBallEntity
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
@@ -20,8 +21,9 @@ class ThrownPokeballHitEvent(
val pokeBall : EmptyPokeBallEntity,
val pokemon : PokemonEntity
) : Cancelable() {
val context = mutableMapOf(
"pokeball" to pokeBall.struct,
"pokemon" to pokemon.asMoLangValue()
val functions = moLangFunctionMap(
"pokeball" to { pokeBall.struct },
"pokemon" to { pokemon.asMoLangValue() },
cancelFunc
)
}
@@ -23,7 +23,9 @@ import com.cobblemon.mod.common.CobblemonBlockEntities
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.CobblemonUnlockableWallpapers
import com.cobblemon.mod.common.Environment
import com.cobblemon.mod.common.api.ai.CobblemonBlockPosTracker
import com.cobblemon.mod.common.api.ai.CobblemonWanderControl
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.battles.model.actor.ActorType
import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
@@ -32,11 +34,11 @@ import com.cobblemon.mod.common.api.dialogue.ReferenceDialogueFaceProvider
import com.cobblemon.mod.common.api.drop.DropEntry
import com.cobblemon.mod.common.api.mark.Marks
import com.cobblemon.mod.common.api.moves.BenchedMove
import com.cobblemon.mod.common.api.moves.MoveTemplate
import com.cobblemon.mod.common.api.moves.Moves
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
import com.cobblemon.mod.common.api.moves.animations.ActionEffects
import com.cobblemon.mod.common.api.moves.animations.NPCProvider
import com.cobblemon.mod.common.api.moves.animations.TargetsProvider
import com.cobblemon.mod.common.api.npc.NPCClasses
import com.cobblemon.mod.common.api.npc.configuration.interaction.DialogueNPCInteractionConfiguration
import com.cobblemon.mod.common.api.npc.configuration.interaction.ScriptNPCInteractionConfiguration
@@ -57,6 +59,7 @@ import com.cobblemon.mod.common.api.scheduling.ClientTaskTracker
import com.cobblemon.mod.common.api.scheduling.Schedulable
import com.cobblemon.mod.common.api.scheduling.ServerTaskTracker
import com.cobblemon.mod.common.api.scripting.CobblemonScripts
import com.cobblemon.mod.common.api.spawning.TimeRange
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
import com.cobblemon.mod.common.api.storage.PokemonStore
import com.cobblemon.mod.common.api.storage.party.NPCPartyStore
@@ -68,13 +71,11 @@ import com.cobblemon.mod.common.api.tags.CobblemonItemTags
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.battles.BattleBuilder
import com.cobblemon.mod.common.battles.BattleFormat
import com.cobblemon.mod.common.battles.BattleFormat.Companion.GEN_9_DOUBLES
import com.cobblemon.mod.common.battles.BattleFormat.Companion.GEN_9_SINGLES
import com.cobblemon.mod.common.battles.BattleFormat.Companion.GEN_9_TRIPLES
import com.cobblemon.mod.common.battles.BattleRegistry
import com.cobblemon.mod.common.battles.actor.PlayerBattleActor
import com.cobblemon.mod.common.battles.actor.PokemonBattleActor
import com.cobblemon.mod.common.client.render.models.blockbench.wavefunction.WaveFunctions
import com.cobblemon.mod.common.entity.MoLangScriptingEntity
import com.cobblemon.mod.common.entity.PosableEntity
import com.cobblemon.mod.common.entity.npc.NPCBattleActor
import com.cobblemon.mod.common.entity.npc.NPCEntity
@@ -168,6 +169,20 @@ object MoLangFunctions {
val replace = params.getString(2)
return@Function StringValue(text.replace(search, replace))
},
"is_included" to java.util.function.Function { params ->
val text = params.getString(0)
val search = params.getString(1)
return@Function DoubleValue(text.contains(search))
},
"to_lower" to java.util.function.Function { params ->
return@Function StringValue(params.getString(0).lowercase())
},
"to_upper" to java.util.function.Function { params ->
return@Function StringValue(params.getString(0).uppercase())
},
"string_length" to java.util.function.Function { params ->
return@Function DoubleValue(params.getString(0).length)
},
"is_blank" to java.util.function.Function { params ->
val arg = params.get<MoValue>(0)
return@Function DoubleValue((arg is StringValue && (arg.value.isBlank() || arg.value.toDoubleOrNull() == 0.0)) || (arg is DoubleValue && arg.value == 0.0))
@@ -305,6 +320,12 @@ object MoLangFunctions {
{ worldHolder ->
val world = worldHolder.value()
val map = hashMapOf<String, java.util.function.Function<MoParams, Any>>()
map.put("is_time_of_day") { params ->
val timeOfDay = TimeRange.timeRanges[params.getString(0).lowercase()]
?: return@put DoubleValue.ZERO
val time = world.dayTime % 24000
return@put DoubleValue(timeOfDay.contains(time.toInt()))
}
map.put("game_time") { _ -> DoubleValue(world.gameTime.toDouble()) }
map.put("time_of_day") {
val time = world.dayTime % 24000
@@ -687,6 +708,37 @@ object MoLangFunctions {
val owner = entity.owner
return@put owner?.asMostSpecificMoLangValue() ?: DoubleValue.ZERO
}
map.put("delta_movement") {
return@put listOf(entity.deltaMovement.x, entity.deltaMovement.y, entity.deltaMovement.z).asArrayValue(::DoubleValue)
}
if (entity is MoLangScriptingEntity) {
map.put("add_callback") { params ->
val type = params.getString(0).asIdentifierDefaultingNamespace()
val callback = params.getString(1).asIdentifierDefaultingNamespace()
val allowDuplicates = params.getBooleanOrNull(2) ?: false
entity.callbacks.addCallback(type, callback, allowDuplicates)
}
map.put("remove_callback") { params ->
val type = params.getString(0).asIdentifierDefaultingNamespace()
val callback = params.getString(1).asIdentifierDefaultingNamespace()
entity.callbacks.removeCallback(type, callback)
}
map.put("has_callback") { params ->
val type = params.getString(0).asIdentifierDefaultingNamespace()
val callback = params.getString(1).asIdentifierDefaultingNamespace()
return@put DoubleValue(callback in (entity.callbacks[type] ?: emptySet()))
}
map.put("clear_callbacks") { params ->
val type = params.getStringOrNull(0)?.asIdentifierDefaultingNamespace()
if (type != null) {
entity.callbacks[type]?.clear()
} else {
entity.callbacks.clear()
}
return@put DoubleValue.ONE
}
}
if (entity is PathfinderMob) {
map.put("walk_to") { params ->
@@ -803,14 +855,12 @@ object MoLangFunctions {
}
map.put("is_standing_on_blocks") { params ->
val depth = params.getDouble(0).toInt()
val blocks: MutableSet<Block> = mutableSetOf()
val blockStrings: MutableSet<String> = mutableSetOf()
for (blockIndex in 1..<params.params.size) {
val blockString = params.getString(blockIndex)
val block = BuiltInRegistries.BLOCK.get(blockString.asIdentifierDefaultingNamespace("minecraft"))
blocks.add(block)
blockStrings.add(params.getString(blockIndex))
}
return@put if (entity.isStandingOn(blocks, depth)) DoubleValue.ONE else DoubleValue.ZERO
return@put if (entity.isStandingOn(blockStrings, depth)) DoubleValue.ONE else DoubleValue.ZERO
}
if (entity is PosableEntity) {
map.put("play_animation") { params ->
@@ -836,6 +886,10 @@ object MoLangFunctions {
return@put DoubleValue.ONE
}
}
map.put("face") { params -> ObjectValue(ReferenceDialogueFaceProvider(entity.id, params.getBooleanOrNull(0) != false))
}
}
if (entity is Schedulable) {
map.put("run_molang_after") { params ->
@@ -877,6 +931,15 @@ object MoLangFunctions {
map.put("is_sleeping") { _ -> DoubleValue(entity.isSleeping) }
map.put("health") { _ -> DoubleValue(entity.health) }
map.put("max_health") { _ -> DoubleValue(entity.maxHealth) }
map.put("look_at_position") { params ->
val x = params.getDouble(0)
val y = params.getDouble(1)
val z = params.getDouble(2)
val duration = params.getIntOrNull(3) ?: 20
val flags = params.params.subList(4, params.params.size).map { it.asString() }
val brain = entity.brain
brain.setMemoryWithExpiry(MemoryModuleType.LOOK_TARGET, CobblemonBlockPosTracker(Vec3(x, y, z), flags.toSet()), duration.toLong())
}
map.put("get_wander_control_memory") {
val value = entity.brain.getMemorySafely(CobblemonMemories.WANDER_CONTROL).orElse(null)
?: CobblemonWanderControl()
@@ -914,6 +977,22 @@ object MoLangFunctions {
return@put DoubleValue.ZERO
}
}
map.put("set_uuid_memory") { params ->
val memory = params.getString(0).asIdentifierDefaultingNamespace().let(BuiltInRegistries.MEMORY_MODULE_TYPE::get) as MemoryModuleType<UUID>
val uuid = params.getString(1).asUUID ?: return@put DoubleValue.ZERO
val expiry = params.getIntOrNull(2) ?: -1
if (expiry != -1) {
entity.brain.setMemoryWithExpiry(memory, uuid, expiry.toLong())
} else {
entity.brain.setMemory(memory, uuid)
}
return@put DoubleValue.ONE
}
map.put("erase_memory") { params ->
val memories = params.params.map { it.asString().asIdentifierDefaultingNamespace() }.map(BuiltInRegistries.MEMORY_MODULE_TYPE::get)
memories.forEach { entity.brain.eraseMemory(it) }
return@put DoubleValue.ONE
}
map.put("has_memory_value") { params ->
val memoryTypes = params.params.map {
it.asString().asIdentifierDefaultingNamespace()
@@ -980,7 +1059,6 @@ object MoLangFunctions {
map.put("name") { StringValue(npc.name.string) }
map.put("level") { DoubleValue(npc.level) }
map.put("has_aspect") { params -> DoubleValue(npc.aspects.contains(params.getString(0))) }
map.put("face") { params -> ObjectValue(ReferenceDialogueFaceProvider(npc.id, params.getBooleanOrNull(0) != false)) }
map.put("in_battle") { DoubleValue(npc.isInBattle()) }
map.put("battles") { ArrayStruct(npc.battleIds.mapNotNull { BattleRegistry.getBattle(it)?.struct }.mapIndexed { index, value -> "$index" to value }.toMap()) }
map.put("stop_battles") { _ -> npc.battleIds.forEach { BattleRegistry.getBattle(it)?.stop() } }
@@ -1232,6 +1310,23 @@ object MoLangFunctions {
}
)
val battleMessageFunctions = mutableListOf<(BattleMessage) -> HashMap<String, java.util.function.Function<MoParams, Any>>>(
{ message ->
val map = hashMapOf<String, java.util.function.Function<MoParams, Any>>()
map.put("has_argument_at") { params ->
val index = params.getInt(0)
val value = params.getStringOrNull(1) ?: ""
return@put DoubleValue(value == message.argumentAt(index))
}
map.put("has_argument") { params ->
val argumentName = params.getString(0)
val value = params.getStringOrNull(1) ?: ""
return@put DoubleValue(value == message.optionalArgument(argumentName))
}
return@mutableListOf map
}
)
val pokemonFunctions = mutableListOf<(Pokemon) -> HashMap<String, java.util.function.Function<MoParams, Any>>>(
{ pokemon ->
val map = hashMapOf<String, java.util.function.Function<MoParams, Any>>()
@@ -1569,6 +1664,8 @@ object MoLangFunctions {
map.put("get_alt_pose") { StringValue(pokemonEntity.getAltPose()) }
map.put("is_gliding") { DoubleValue(pokemonEntity.isUsingAltPose(cobblemonResource("gliding"))) }
map.put("is_sprinting") { DoubleValue(pokemonEntity.isUsingAltPose(cobblemonResource("sprinting"))) }
map.put("is_drifting") { DoubleValue(pokemonEntity.isUsingAltPose(cobblemonResource("drifting"))) }
map.put("is_powered_drifting") { DoubleValue(pokemonEntity.isUsingAltPose(cobblemonResource("powered_drifting"))) }
map.put("in_air") { DoubleValue(pokemonEntity.isUsingAltPose(cobblemonResource("in_air"))) }
map.put("is_wild") { DoubleValue(pokemonEntity.ownerUUID == null) }
map.put("is_in_party") { DoubleValue(pokemonEntity.pokemon.storeCoordinates.get()?.store is PartyStore) }
@@ -1586,6 +1683,34 @@ object MoLangFunctions {
map.put("pasture_conflict_enabled") {
DoubleValue(pokemonEntity.getBehaviourFlag(PokemonBehaviourFlag.PASTURE_CONFLICT))
}
map.put("run_action_effect") { params ->
val runtime = MoLangRuntime().setup()
runtime.environment.cloneFrom(params.environment)
runtime.withQueryValue("entity", pokemonEntity.struct)
val actionEffect = ActionEffects.actionEffects[params.getString(0).asIdentifierDefaultingNamespace()]
if (actionEffect != null) {
val context = ActionEffectContext(
actionEffect = actionEffect,
providers = mutableListOf(TargetsProvider(pokemonEntity)),
runtime = runtime,
level = pokemonEntity.level()
)
// This was taken from NPC run_action_effect but this variable isn't used, so I just commented it out.
// pokemonEntity.actionEffect = context
pokemonEntity.brain.setMemory(CobblemonMemories.ACTIVE_ACTION_EFFECT, context)
pokemonEntity.brain.setActiveActivityIfPossible(CobblemonActivities.ACTION_EFFECT)
actionEffect.run(context).thenRun {
val pokemonActionEffect = pokemonEntity.brain.getMemory(CobblemonMemories.ACTIVE_ACTION_EFFECT).orElse(null)
if (pokemonActionEffect == context && pokemonEntity.brain.isActive(CobblemonActivities.ACTION_EFFECT)) {
pokemonEntity.brain.eraseMemory(CobblemonMemories.ACTIVE_ACTION_EFFECT)
//pokemonEntity.actionEffect = null
}
}
return@put DoubleValue.ONE
}
return@put DoubleValue.ZERO
}
map
}
)
@@ -2190,6 +2315,14 @@ object MoLangFunctions {
return this
}
fun QueryStruct.addBattleMessageFunctions(battleMessage: BattleMessage): QueryStruct {
val addedFunctions = battleMessageFunctions
.flatMap { it.invoke(battleMessage).entries }
.associate { it.key to it.value }
functions.putAll(addedFunctions)
return this
}
fun QueryStruct.addPokemonEntityFunctions(pokemonEntity: PokemonEntity): QueryStruct {
val addedFunctions = pokemonEntityFunctions
.flatMap { it.invoke(pokemonEntity).entries }
@@ -10,14 +10,12 @@ package com.cobblemon.mod.common.api.orientation
import com.cobblemon.mod.common.util.math.geometry.toDegrees
import com.cobblemon.mod.common.util.math.geometry.toRadians
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.Mth
import net.minecraft.world.entity.LivingEntity
import org.joml.Matrix3f
import org.joml.Quaternionf
import org.joml.Vector3f
import kotlin.math.asin
import kotlin.math.sign
open class OrientationController(val entity: LivingEntity) {
@@ -132,6 +132,8 @@ object CobblemonPermissions {
@JvmStatic
val ABANDON_MULTITEAM = this.create("${COMMAND_PREFIX}abandonmultiteam", PermissionLevel.NONE)
@JvmStatic
val RUN_MOLANG = this.create("${COMMAND_PREFIX}runmolang", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
@JvmStatic
val RUN_MOLANG_SCRIPT = this.create("${COMMAND_PREFIX}runmolangscript", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
@JvmStatic
val COBBLEMON_CONFIG_RELOAD = this.create("${COMMAND_PREFIX}cobblemonconfig.reload", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
@@ -37,6 +37,7 @@ import com.cobblemon.mod.common.api.pokemon.stats.Stat
import com.cobblemon.mod.common.api.pokemon.stats.Stats
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourSettings
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
import com.cobblemon.mod.common.api.riding.stats.RidingStatDefinition
import com.cobblemon.mod.common.api.spawning.TimeRange
import com.cobblemon.mod.common.api.types.ElementalType
@@ -127,6 +128,7 @@ object PokemonSpecies : JsonDataRegistry<Species> {
.registerTypeAdapter(ItemPredicate::class.java, LegacyItemConditionWrapperAdapter)
.registerTypeAdapter(RidingBehaviourSettings::class.java, RidingBehaviourSettingsAdapter)
.registerTypeAdapter(RidingStatDefinition::class.java, RidingStatDefinitionAdapter)
.registerTypeAdapter(RideSoundSettingsList::class.java, RideSoundSettingsListAdapter)
.registerTypeAdapter(Expression::class.java, ExpressionAdapter)
.disableHtmlEscaping()
.enableComplexMapKeySerialization()
@@ -9,6 +9,7 @@
package com.cobblemon.mod.common.api.riding.behaviour
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import net.minecraft.resources.ResourceLocation
@@ -86,7 +87,9 @@ interface RidingBehaviour<Settings : RidingBehaviourSettings, State : RidingBeha
fun shouldRotatePokemonHead(settings: Settings, state: State, vehicle: PokemonEntity): Boolean
fun shouldRotatePlayerHead(settings: Settings, state: State, vehicle: PokemonEntity): Boolean
fun shouldRotateRiderHead(settings: Settings, state: State, vehicle: PokemonEntity): Boolean
fun getRideSounds(settings: Settings, state: State, vehicle: PokemonEntity): RideSoundSettingsList
fun maxUpStep(settings: Settings, state: State, vehicle: PokemonEntity): Float? = null
@@ -12,6 +12,7 @@ import com.cobblemon.mod.common.api.riding.behaviour.types.air.*
import com.cobblemon.mod.common.api.riding.behaviour.types.composite.CompositeBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.types.land.HorseBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.types.land.VehicleBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.types.land.MinekartBehaviour
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
@@ -22,15 +23,19 @@ object RidingBehaviours {
init {
register(BirdBehaviour.KEY, BirdBehaviour())
register(BirdNoRollBehaviour.KEY, BirdNoRollBehaviour())
register(DolphinBehaviour.KEY, DolphinBehaviour())
register(HorseBehaviour.KEY, HorseBehaviour())
register(BoatBehaviour.KEY, BoatBehaviour())
register(GliderBehaviour.KEY, GliderBehaviour())
register(HelicopterBehaviour.KEY, HelicopterBehaviour())
register(JetBehaviour.KEY, JetBehaviour())
register(JetNoRollBehaviour.KEY, JetNoRollBehaviour())
register(BurstBehaviour.KEY, BurstBehaviour())
register(VehicleBehaviour.KEY, VehicleBehaviour())
register(MinekartBehaviour.KEY, MinekartBehaviour())
register(HoverBehaviour.KEY, HoverBehaviour())
register(RocketBehaviour.KEY, RocketBehaviour())
register(CompositeBehaviour.KEY, CompositeBehaviour())
}
@@ -9,6 +9,7 @@
package com.cobblemon.mod.common.api.riding.behaviour
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.resources.ResourceLocation
@@ -119,9 +120,9 @@ class RidingController<Settings : RidingBehaviourSettings, State : RidingBehavio
return behaviour.canJump(settings, state, vehicle, driver)
}
override fun shouldRotatePlayerHead(settings: Settings, state: State, vehicle: PokemonEntity): Boolean {
override fun shouldRotateRiderHead(settings: Settings, state: State, vehicle: PokemonEntity): Boolean {
if (!isActive(settings, state, vehicle)) return false
return behaviour.shouldRotatePlayerHead(settings, state, vehicle)
return behaviour.shouldRotateRiderHead(settings, state, vehicle)
}
override fun shouldRotatePokemonHead(settings: Settings, state: State, vehicle: PokemonEntity): Boolean {
@@ -190,6 +191,11 @@ class RidingController<Settings : RidingBehaviourSettings, State : RidingBehavio
return behaviour.maxUpStep(settings, state, vehicle)
}
override fun getRideSounds(settings: Settings, state: State, vehicle: PokemonEntity): RideSoundSettingsList {
if (!isActive(settings, state, vehicle)) return RideSoundSettingsList()
return behaviour.getRideSounds(settings, state, vehicle)
}
override fun createDefaultState(settings: Settings): State {
return behaviour.createDefaultState(settings)
}
@@ -16,6 +16,7 @@ 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.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.*
@@ -104,14 +105,6 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
upForce -= 0.3
}
val altitudeLimit = vehicle.runtime.resolveDouble(settings.jumpExpr)
//Only limit altitude if altitude is not infinite
if (!vehicle.runtime.resolveBoolean(settings.infiniteAltitude)) {
//Provide a hard limit on altitude
upForce = if (vehicle.y >= altitudeLimit && upForce > 0) 0.0 else upForce
}
val velocity = Vec3(state.rideVelocity.get().x , upForce, forwardForce)
return velocity
@@ -126,12 +119,7 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
val glideTopSpeed = vehicle.runtime.resolveDouble(settings.glidespeedExpr)
val accel = vehicle.runtime.resolveDouble(settings.accelerationExpr)
val staminaStat = vehicle.runtime.resolveDouble(settings.staminaExpr)
var glideSpeedChange = 0.0
val currSpeed = state.rideVelocity.get().length()
//Flag for determining if player is actively inputting
var activeInput = false
var newVelocity = Vec3(state.rideVelocity.get().x, state.rideVelocity.get().y, state.rideVelocity.get().z)
@@ -461,12 +449,20 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
return false
}
override fun shouldRotatePlayerHead(
override fun shouldRotateRiderHead(
settings: BirdSettings,
state: BirdState,
vehicle: PokemonEntity
): Boolean {
return true
return false
}
override fun getRideSounds(
settings: BirdSettings,
state: BirdState,
vehicle: PokemonEntity
): RideSoundSettingsList {
return settings.rideSounds
}
override fun createDefaultState(settings: BirdSettings) = BirdState()
@@ -491,8 +487,11 @@ class BirdSettings : RidingBehaviourSettings {
var glidespeedExpr: Expression = "q.get_ride_stats('SPEED', 'AIR', 2.0, 1.0)".asExpression()
private set
var rideSounds: RideSoundSettingsList = RideSoundSettingsList()
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
rideSounds.encode(buffer)
buffer.writeExpression(infiniteAltitude)
buffer.writeExpression(infiniteStamina)
buffer.writeExpression(glidespeedExpr)
@@ -504,6 +503,7 @@ class BirdSettings : RidingBehaviourSettings {
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
rideSounds = RideSoundSettingsList.decode(buffer)
infiniteAltitude = buffer.readExpression()
infiniteStamina = buffer.readExpression()
glidespeedExpr = buffer.readExpression()
@@ -0,0 +1,518 @@
/*
* 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.air
import com.bedrockk.molang.Expression
import com.bedrockk.molang.runtime.MoLangMath.lerp
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.*
import com.cobblemon.mod.common.api.riding.posing.PoseOption
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.*
import com.cobblemon.mod.common.util.math.geometry.toRadians
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
import org.joml.Vector3f
import kotlin.math.*
class BirdNoRollBehaviour : RidingBehaviour<BirdNoRollSettings, BirdNoRollState> {
companion object {
val KEY = cobblemonResource("air/birdnoroll")
}
override val key: ResourceLocation = KEY
override fun getRidingStyle(settings: BirdNoRollSettings, state: BirdNoRollState): RidingStyle {
return RidingStyle.AIR
}
val poseProvider = PoseProvider<BirdNoRollSettings, BirdNoRollState>(PoseType.HOVER)
.with(PoseOption(PoseType.FLY) { _, state, _ -> state.rideVelocity.get().z > 0.2 })
override fun isActive(settings: BirdNoRollSettings, state: BirdNoRollState, vehicle: PokemonEntity): Boolean {
return true
}
override fun pose(settings: BirdNoRollSettings, state: BirdNoRollState, vehicle: PokemonEntity): PoseType {
return poseProvider.select(settings, state, vehicle)
}
override fun speed(
settings: BirdNoRollSettings,
state: BirdNoRollState,
vehicle: PokemonEntity,
driver: Player
): Float {
return state.rideVelocity.get().length().toFloat()
}
override fun rotation(
settings: BirdNoRollSettings,
state: BirdNoRollState,
vehicle: PokemonEntity,
driver: LivingEntity
): Vec2 {
return Vec2(driver.xRot * 0.5f, driver.yRot)
}
override fun velocity(
settings: BirdNoRollSettings,
state: BirdNoRollState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
var yVel = 0.0
var leftForce = 0.0
var upForce = 0.0
var forwardForce = 0.0
//Perform ride velocity update
calculateRideSpaceVel(settings, state, vehicle, driver)
//Translate ride space velocity to world space velocity.
val controller = (driver as? OrientationControllable)?.orientationController
if (controller != null) {
upForce += -1.0 * sin(Math.toRadians(controller.pitch.toDouble())) * state.rideVelocity.get().z
forwardForce += cos(Math.toRadians(controller.pitch.toDouble())) * state.rideVelocity.get().z
upForce += cos(Math.toRadians(controller.pitch.toDouble())) * state.rideVelocity.get().y
forwardForce += sin(Math.toRadians(controller.pitch.toDouble())) * state.rideVelocity.get().y
}
//Bring the ride out of the sky when stamina is depleted.
if (state.stamina.get() <= 0.0) {
upForce -= 0.3
}
val velocity = Vec3(state.rideVelocity.get().x , upForce, forwardForce)
return velocity
}
/*
* Calculates the change in the ride space vector due to player input and ride state
*/
fun calculateRideSpaceVel(settings: BirdNoRollSettings, state: BirdNoRollState, vehicle: PokemonEntity, driver: Player) {
//retrieve stats
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
val glideTopSpeed = vehicle.runtime.resolveDouble(settings.glidespeedExpr)
val accel = vehicle.runtime.resolveDouble(settings.accelerationExpr)
val staminaStat = vehicle.runtime.resolveDouble(settings.staminaExpr)
var glideSpeedChange = 0.0
val currSpeed = state.rideVelocity.get().length()
//Flag for determining if player is actively inputting
var activeInput = false
var newVelocity = Vec3(state.rideVelocity.get().x, state.rideVelocity.get().y, state.rideVelocity.get().z)
//speed up and slow down based on input
if (driver.zza != 0.0f && state.stamina.get() > 0.0) {
//make sure it can't exceed top speed
val forwardInput = when {
driver.zza > 0 && newVelocity.z > topSpeed -> 0.0
driver.zza < 0 && newVelocity.z < (-topSpeed / 3.0) -> 0.0
else -> driver.zza.sign
}
newVelocity = Vec3(
newVelocity.x,
newVelocity.y,
(newVelocity.z + (accel * forwardInput.toDouble())))
activeInput = true
}
val controller = (driver as? OrientationControllable)?.orientationController
if (controller != null) {
//Base glide speed change on current pitch of the ride.
glideSpeedChange = sin(Math.toRadians(controller.pitch.toDouble()))
glideSpeedChange = glideSpeedChange * 0.25
//TODO: Possibly create a deadzone around parallel where glide doesn't affect speed?
if (glideSpeedChange <= 0.0) {
//Ensures that a propelling force is still able to be applied when
//climbing in height
if (driver.zza <= 0) {
//speed decrease should be 2x speed increase?
//state.currSpeed = max(state.currSpeed + (0.0166) * glideSpeedChange, 0.0 )
newVelocity = Vec3(
newVelocity.x,
newVelocity.y,
lerp( newVelocity.z, 0.0,glideSpeedChange * -0.0166 * 2 )
)
}
} else {
// only add to the speed if it hasn't exceeded the current
//glide angles maximum amount of speed that it can give.
//state.currSpeed = min(state.currSpeed + ((0.0166 * 2) * glideSpeedChange), maxGlideSpeed)
newVelocity = Vec3(
newVelocity.x,
newVelocity.y,
min(newVelocity.z + ((0.0166 * 2) * glideSpeedChange), glideTopSpeed)
)
}
}
//Vertical movement based on driver input.
val vertTopSpeed = topSpeed / 2.0
val vertInput = when {
driver.jumping && state.rideVelocity.get().length() < 0.3 -> 1.0
driver.isShiftKeyDown && state.rideVelocity.get().length() < 0.3 -> -1.0
else -> 0.0
}
if (vertInput != 0.0 && state.stamina.get() > 0.0) {
newVelocity = Vec3(
newVelocity.x,
(newVelocity.y + (accel * vertInput)).coerceIn(-vertTopSpeed, vertTopSpeed),
newVelocity.z)
activeInput = true
}
else {
newVelocity = Vec3(
newVelocity.x,
lerp(newVelocity.y, 0.0, vertTopSpeed / 20.0),
newVelocity.z)
}
//Check if the ride should be gliding
if (driver.isLocalPlayer) {
if (activeInput && state.stamina.get() > 0.0) {
state.gliding.set(false)
} else {
state.gliding.set(true)
}
}
//air resistance
if( abs(newVelocity.z) > 0) {
newVelocity = newVelocity.subtract(0.0, 0.0, min(0.0015 * newVelocity.z.sign, newVelocity.z))
}
state.rideVelocity.set(newVelocity)
}
override fun angRollVel(
settings: BirdNoRollSettings,
state: BirdNoRollState,
vehicle: PokemonEntity,
driver: Player,
deltaTime: Double
): Vec3 {
if (driver !is OrientationControllable) return Vec3.ZERO
val controller = (driver as OrientationControllable).orientationController
//limit rolling based on handling and current speed.
//modulated by speed so that when flapping idle in air you are ont wobbling around to look around
//TODO: Tie handling into yaw rate
val handling = vehicle.runtime.resolveDouble(settings.handlingExpr)
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
//This rotation limit will be used to know if the ride has exceeded it and its needs correcting.
val rotMin = 15.0
val rotLimit = max(handling * sqrt(normalizeVal(state.rideVelocity.get().length(), 0.0, topSpeed)), rotMin)
var yawDeltaDeg = deltaTime * handling * sin(Math.toRadians(controller.roll.toDouble())) //sin(Math.toRadians(controller.roll.toDouble())) * abs(cos(Math.toRadians(controller.pitch.toDouble())))
val trueYawDelt = yawDeltaDeg * abs(cos(Math.toRadians(controller.pitch.toDouble())))
//val pitchedYawDelt = yawDeltaDeg - trueYawDelt
controller.applyGlobalYaw(trueYawDelt.toFloat())
//Calculate correcting roll force.
val correctionRate = 5.0
if(abs(controller.roll) > rotLimit && state.rideVelocity.get().length() < 0.2) {
state.currRollCorrectionForce.set(
state.currRollCorrectionForce.get() + ((abs(controller.roll) - rotLimit) / 180 - rotLimit) * controller.roll.sign * correctionRate * deltaTime * 0.01
)
} else {
state.currRollCorrectionForce.set(
//reduce rollCorrection gradually
lerp(state.currRollCorrectionForce.get(), 0.0, deltaTime * 0.98)
)
}
//yaw, pitch, roll
return Vec3(0.0, 0.0, state.currRollCorrectionForce.get())
}
override fun rotationOnMouseXY(
settings: BirdNoRollSettings,
state: BirdNoRollState,
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
//TODO: hook up handling in parity with normal bird
val handling = vehicle.runtime.resolveDouble(settings.handlingExpr)
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
//Smooth out mouse input.
val smoothingSpeed = 4.0
val invertRoll = if (Cobblemon.config.invertRoll) -1 else 1
val invertPitch = if (Cobblemon.config.invertPitch) -1 else 1
val mouseXc = (mouseX).coerceIn(-60.0, 60.0)
val mouseYc = (mouseY).coerceIn(-60.0, 60.0)
val xInput = mouseXSmoother.getNewDeltaValue(mouseXc * 0.1 * invertRoll, deltaTime * smoothingSpeed);
val yInput = mouseYSmoother.getNewDeltaValue(mouseYc * 0.1 * invertPitch, deltaTime * smoothingSpeed);
//Give the ability to yaw with x mouse input when at low speeds.
val yawForce = xInput * ( 1.0 - min(sqrt(normalizeVal(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())
var pitchRot = yInput
// 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
}
/*
* Normalizes the given value between a min and a max.
* The result is clamped between 0.0 and 1.0, where 0.0 represents x is at or below min
* and 1.0 represents x is at or above it.
*/
private fun normalizeVal(x: Double, min: Double, max: Double): Double {
require(max > min) { "max must be greater than min" }
return ((x - min) / (max - min)).coerceIn(0.0, 1.0)
}
override fun canJump(
settings: BirdNoRollSettings,
state: BirdNoRollState,
vehicle: PokemonEntity,
driver: Player
): Boolean {
return false
}
override fun setRideBar(
settings: BirdNoRollSettings,
state: BirdNoRollState,
vehicle: PokemonEntity,
driver: Player
): Float {
return (state.stamina.get() / 1.0f)
}
override fun jumpForce(
settings: BirdNoRollSettings,
state: BirdNoRollState,
vehicle: PokemonEntity,
driver: Player,
jumpStrength: Int
): Vec3 {
return Vec3.ZERO
}
override fun gravity(
settings: BirdNoRollSettings,
state: BirdNoRollState,
vehicle: PokemonEntity,
regularGravity: Double
): Double {
return 0.0
}
override fun rideFovMultiplier(
settings: BirdNoRollSettings,
state: BirdNoRollState,
vehicle: PokemonEntity,
driver: Player
): Float {
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
val glideTopSpeed = vehicle.runtime.resolveDouble(settings.glidespeedExpr)
//Must I ensure that topspeed is greater than minimum?
val normalizedGlideSpeed = normalizeVal(state.rideVelocity.get().length(), topSpeed, glideTopSpeed)
//Only ever want the fov change to be a max of 0.2 and for it to have non linear scaling.
return 1.0f + normalizedGlideSpeed.pow(2).toFloat() * 0.2f
}
override fun useAngVelSmoothing(
settings: BirdNoRollSettings,
state: BirdNoRollState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun useRidingAltPose(
settings: BirdNoRollSettings,
state: BirdNoRollState,
vehicle: PokemonEntity,
driver: Player
): ResourceLocation {
if (state.gliding.get()) {
return cobblemonResource("gliding")
}
return cobblemonResource("no_pose")
}
override fun inertia(settings: BirdNoRollSettings, state: BirdNoRollState, vehicle: PokemonEntity): Double {
return 0.5
}
override fun shouldRoll(settings: BirdNoRollSettings, state: BirdNoRollState, vehicle: PokemonEntity): Boolean {
return true
}
override fun turnOffOnGround(settings: BirdNoRollSettings, state: BirdNoRollState, vehicle: PokemonEntity): Boolean {
return false
}
override fun dismountOnShift(settings: BirdNoRollSettings, state: BirdNoRollState, vehicle: PokemonEntity): Boolean {
return false
}
override fun shouldRotatePokemonHead(
settings: BirdNoRollSettings,
state: BirdNoRollState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun shouldRotateRiderHead(
settings: BirdNoRollSettings,
state: BirdNoRollState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun getRideSounds(
settings: BirdNoRollSettings,
state: BirdNoRollState,
vehicle: PokemonEntity
): RideSoundSettingsList {
return settings.rideSounds
}
override fun createDefaultState(settings: BirdNoRollSettings) = BirdNoRollState()
}
class BirdNoRollSettings : RidingBehaviourSettings {
override val key = BirdNoRollBehaviour.KEY
var infiniteAltitude: Expression = "false".asExpression()
private set
var infiniteStamina: Expression = "false".asExpression()
private set
//max y level for the ride
var jumpExpr: Expression = "q.get_ride_stats('JUMP', 'AIR', 200.0, 128.0)".asExpression()
var handlingExpr: Expression = "q.get_ride_stats('SKILL', 'AIR', 135.0, 45.0)".asExpression()
var speedExpr: Expression = "q.get_ride_stats('SPEED', 'AIR', 1.0, 0.35)".asExpression()
var accelerationExpr: Expression = "q.get_ride_stats('ACCELERATION', 'AIR', (1.0 / (20.0 * 3.0)), (1.0 / (20.0 * 8.0)))".asExpression()
var staminaExpr: Expression = "q.get_ride_stats('STAMINA', 'AIR', 120.0, 20.0)".asExpression()
var glidespeedExpr: Expression = "q.get_ride_stats('SPEED', 'AIR', 2.0, 1.0)".asExpression()
private set
var rideSounds: RideSoundSettingsList = RideSoundSettingsList()
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
rideSounds.encode(buffer)
buffer.writeExpression(infiniteAltitude)
buffer.writeExpression(infiniteStamina)
buffer.writeExpression(glidespeedExpr)
buffer.writeExpression(jumpExpr)
buffer.writeExpression(handlingExpr)
buffer.writeExpression(speedExpr)
buffer.writeExpression(accelerationExpr)
buffer.writeExpression(staminaExpr)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
rideSounds = RideSoundSettingsList.decode(buffer)
infiniteAltitude = buffer.readExpression()
infiniteStamina = buffer.readExpression()
glidespeedExpr = buffer.readExpression()
jumpExpr = buffer.readExpression()
handlingExpr = buffer.readExpression()
speedExpr = buffer.readExpression()
accelerationExpr = buffer.readExpression()
staminaExpr = buffer.readExpression()
}
}
class BirdNoRollState : RidingBehaviourState() {
var gliding = ridingState(false, Side.CLIENT)
var lastGlide = ridingState(-100L, Side.CLIENT)
var currRollCorrectionForce = ridingState(0.0, Side.CLIENT)
override fun encode(buffer: FriendlyByteBuf) {
super.encode(buffer)
buffer.writeFloat(stamina.get())
buffer.writeBoolean(gliding.get())
}
override fun decode(buffer: FriendlyByteBuf) {
super.decode(buffer)
gliding.set(buffer.readBoolean(), forced = true)
}
override fun reset() {
super.reset()
gliding.set(false, forced = true)
lastGlide.set(-100L, forced = true)
currRollCorrectionForce.set(0.0, forced = true)
}
override fun toString(): String {
return "BirdNoRollState(rideVelocity=${rideVelocity.get()}, stamina=${stamina.get()}, gliding=${gliding.get()})"
}
override fun copy() = BirdNoRollState().also {
it.rideVelocity.set(this.rideVelocity.get(), forced = true)
it.stamina.set(this.stamina.get(), forced = true)
it.gliding.set(this.gliding.get(), forced = true)
it.lastGlide.set(this.lastGlide.get(), forced = true)
it.currRollCorrectionForce.set(this.currRollCorrectionForce.get(), forced = true)
}
override fun shouldSync(previous: RidingBehaviourState): Boolean {
if (previous !is BirdNoRollState) return false
if (previous.gliding.get() != gliding.get()) return true
return super.shouldSync(previous)
}
}
@@ -17,6 +17,7 @@ import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourSettings
import com.cobblemon.mod.common.api.riding.posing.PoseOption
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
import com.cobblemon.mod.common.api.riding.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
@@ -183,10 +184,14 @@ class GliderBehaviour : RidingBehaviour<GliderSettings, RidingBehaviourState> {
return false
}
override fun shouldRotatePlayerHead(settings: GliderSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Boolean {
override fun shouldRotateRiderHead(settings: GliderSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Boolean {
return false
}
override fun getRideSounds(settings: GliderSettings, state: RidingBehaviourState, vehicle: PokemonEntity): RideSoundSettingsList {
return settings.rideSounds
}
override fun createDefaultState(settings: GliderSettings) = RidingBehaviourState()
}
@@ -202,14 +207,18 @@ class GliderSettings : RidingBehaviourSettings {
var canStrafe: Expression = "false".asExpression()
private set
var rideSounds: RideSoundSettingsList = RideSoundSettingsList()
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
rideSounds.encode(buffer)
buffer.writeExpression(glideSpeed)
buffer.writeExpression(speed)
buffer.writeExpression(canStrafe)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
rideSounds = RideSoundSettingsList.decode(buffer)
glideSpeed = buffer.readExpression()
speed = buffer.readExpression()
canStrafe = buffer.readExpression()
@@ -12,11 +12,12 @@ import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourState
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourSettings
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourState
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.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.*
@@ -268,10 +269,18 @@ class HelicopterBehaviour : RidingBehaviour<HelicopterSettings, RidingBehaviourS
return false
}
override fun shouldRotatePlayerHead(settings: HelicopterSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Boolean {
override fun shouldRotateRiderHead(
settings: HelicopterSettings,
state: RidingBehaviourState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun getRideSounds(settings: HelicopterSettings, state: RidingBehaviourState, vehicle: PokemonEntity): RideSoundSettingsList {
return settings.rideSounds
}
override fun createDefaultState(settings: HelicopterSettings) = RidingBehaviourState()
}
@@ -291,8 +300,11 @@ class HelicopterSettings : RidingBehaviourSettings {
var speed: Expression = "1.0".asExpression()
private set
var rideSounds: RideSoundSettingsList = RideSoundSettingsList()
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
rideSounds.encode(buffer)
buffer.writeExpression(gravity)
buffer.writeExpression(horizontalAcceleration)
buffer.writeExpression(verticalVelocity)
@@ -300,6 +312,7 @@ class HelicopterSettings : RidingBehaviourSettings {
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
rideSounds = RideSoundSettingsList.decode(buffer)
gravity = buffer.readExpression()
horizontalAcceleration = buffer.readExpression()
verticalVelocity = buffer.readExpression()
@@ -13,6 +13,7 @@ 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.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.*
@@ -450,7 +451,7 @@ class HoverBehaviour : RidingBehaviour<HoverSettings, HoverState> {
return false
}
override fun shouldRotatePlayerHead(
override fun shouldRotateRiderHead(
settings: HoverSettings,
state: HoverState,
vehicle: PokemonEntity
@@ -458,12 +459,24 @@ class HoverBehaviour : RidingBehaviour<HoverSettings, HoverState> {
return false
}
override fun getRideSounds(
settings: HoverSettings,
state: HoverState,
vehicle: PokemonEntity
): RideSoundSettingsList {
return settings.rideSounds
}
override fun createDefaultState(settings: HoverSettings) = HoverState()
}
class HoverSettings : RidingBehaviourSettings {
override val key = HoverBehaviour.KEY
var rideSound: ResourceLocation = "ride.loop.saucer".asIdentifierDefaultingNamespace()
var volumeExpr: Expression = "math.pow(math.min(q.ride_velocity() / 0.8, 1.0),2)".asExpression()
var pitchExpr: Expression = "math.pow(math.min(1.0 + 0.2*(q.ride_velocity() / 0.8), 1.2),2)".asExpression()
var canJump = "true".asExpression()
private set
@@ -486,8 +499,11 @@ class HoverSettings : RidingBehaviourSettings {
var handlingExpr: Expression = "q.get_ride_stats('SKILL', 'AIR', 140.0, 20.0)".asExpression()
private set
var rideSounds: RideSoundSettingsList = RideSoundSettingsList()
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
rideSounds.encode(buffer)
buffer.writeExpression(speedExpr)
buffer.writeExpression(accelerationExpr)
buffer.writeExpression(staminaExpr)
@@ -496,6 +512,7 @@ class HoverSettings : RidingBehaviourSettings {
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
rideSounds = RideSoundSettingsList.decode(buffer)
speedExpr = buffer.readExpression()
accelerationExpr = buffer.readExpression()
staminaExpr = buffer.readExpression()
@@ -21,6 +21,7 @@ import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.*
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.RegistryFriendlyByteBuf
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
@@ -136,14 +137,6 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
//If stamina has run out then initiate forced glide down.
upForce = if (state.stamina.get() > 0.0) upForce else -0.7
val altitudeLimit = vehicle.runtime.resolveDouble(settings.jumpExpr)
//Only limit altitude if altitude is not infinite
if (!vehicle.runtime.resolveBoolean(settings.infiniteAltitude)) {
//Provide a hard limit on altitude
upForce = if (vehicle.y >= altitudeLimit && upForce > 0) 0.0 else upForce
}
val velocity = Vec3(0.0, upForce, forwardForce)
return velocity
@@ -160,17 +153,11 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
) {
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
val accel = vehicle.runtime.resolveDouble(settings.accelerationExpr)
val altitudeLimit = vehicle.runtime.resolveDouble(settings.jumpExpr)
val minSpeed = vehicle.runtime.resolveDouble(settings.minSpeed)
val speed = state.rideVelocity.get().length()
//Give no altitude limit if at max jump stat.
val pushingHeightLimit = if (vehicle.runtime.resolveBoolean(settings.infiniteStamina)) false
else (vehicle.y >= altitudeLimit && vehicle.xRot <= 0)
//speed up and slow down based on input
if (driver.zza > 0.0 && speed < topSpeed && state.stamina.get() > 0.0f && !pushingHeightLimit) {
if (driver.zza > 0.0 && speed < topSpeed && state.stamina.get() > 0.0f) {
//modify acceleration to be slower when at closer speeds to top speed
val accelMod = max(-(normalizeSpeed(speed, minSpeed, topSpeed)) + 1, 0.0)
state.rideVelocity.set(
@@ -180,7 +167,7 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
min(state.rideVelocity.get().z + (accel * accelMod), topSpeed)
)
)
} else if (driver.zza >= 0.0 && (state.stamina.get() == 0.0f || pushingHeightLimit)) {
} else if (driver.zza >= 0.0 && (state.stamina.get() == 0.0f)) {
state.rideVelocity.set(
Vec3(
state.rideVelocity.get().x,
@@ -380,10 +367,22 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
return false
}
override fun shouldRotatePlayerHead(settings: JetSettings, state: JetState, vehicle: PokemonEntity): Boolean {
override fun shouldRotateRiderHead(
settings: JetSettings,
state: JetState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun getRideSounds(
settings: JetSettings,
state: JetState,
vehicle: PokemonEntity
): RideSoundSettingsList {
return settings.rideSounds
}
override fun createDefaultState(settings: JetSettings) = JetState()
override fun damageOnCollision(
@@ -413,9 +412,6 @@ class JetSettings : RidingBehaviourSettings {
var infiniteStamina: Expression = "false".asExpression()
private set
var infiniteAltitude: Expression = "false".asExpression()
private set
var jumpExpr: Expression = "q.get_ride_stats('JUMP', 'AIR', 300.0, 128.0)".asExpression()
private set
var handlingExpr: Expression = "q.get_ride_stats('SKILL', 'AIR', 140.0, 20.0)".asExpression()
@@ -428,13 +424,15 @@ class JetSettings : RidingBehaviourSettings {
var staminaExpr: Expression = "q.get_ride_stats('STAMINA', 'AIR', 60.0, 10.0)".asExpression()
private set
var rideSounds: RideSoundSettingsList = RideSoundSettingsList()
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
rideSounds.encode(buffer)
buffer.writeExpression(gravity)
buffer.writeExpression(minSpeed)
buffer.writeExpression(handlingYawExpr)
buffer.writeExpression(infiniteStamina)
buffer.writeExpression(infiniteAltitude)
buffer.writeExpression(jumpExpr)
buffer.writeExpression(handlingExpr)
buffer.writeExpression(speedExpr)
@@ -443,11 +441,11 @@ class JetSettings : RidingBehaviourSettings {
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
rideSounds = RideSoundSettingsList.decode(buffer)
gravity = buffer.readExpression()
minSpeed = buffer.readExpression()
handlingYawExpr = buffer.readExpression()
infiniteStamina = buffer.readExpression()
infiniteAltitude = buffer.readExpression()
jumpExpr = buffer.readExpression()
handlingExpr = buffer.readExpression()
speedExpr = buffer.readExpression()
@@ -483,8 +481,6 @@ class JetState : RidingBehaviourState() {
it.currSpeed.set(currSpeed.get(), forced = true)
it.stamina.set(stamina.get(), forced = true)
it.rideVelocity.set(rideVelocity.get(), forced = true)
// it.currMouseXForce.set(currMouseXForce.get(), forced = true)
// it.currMouseYForce.set(currMouseYForce.get(), forced = true)
}
override fun shouldSync(previous: RidingBehaviourState): Boolean {
@@ -0,0 +1,477 @@
/*
* 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.air
import com.bedrockk.molang.Expression
import com.bedrockk.molang.runtime.MoLangMath.lerp
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.*
import com.cobblemon.mod.common.api.riding.posing.PoseOption
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.*
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.RegistryFriendlyByteBuf
import com.bedrockk.molang.runtime.MoLangMath.lerp
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
import net.minecraft.world.phys.shapes.Shapes
import kotlin.math.*
class JetNoRollBehaviour : RidingBehaviour<JetNoRollSettings, JetNoRollState> {
companion object {
val KEY = cobblemonResource("air/jetnoroll")
}
override val key = KEY
override fun getRidingStyle(settings: JetNoRollSettings, state: JetNoRollState): RidingStyle {
return RidingStyle.AIR
}
val poseProvider = PoseProvider<JetNoRollSettings, JetNoRollState>(PoseType.HOVER)
.with(PoseOption(PoseType.FLY) { _, state, _ -> state.rideVelocity.get().z > 0.1 })
override fun isActive(settings: JetNoRollSettings, state: JetNoRollState, vehicle: PokemonEntity): Boolean {
return Shapes.create(vehicle.boundingBox).blockPositionsAsListRounded().any {
//Need to check other fluids
if (vehicle.isInWater || vehicle.isUnderWater) {
return@any false
}
//This might not actually work, depending on what the yPos actually is. yPos of the middle of the entity? the feet?
if (it.y.toDouble() == (vehicle.position().y)) {
val blockState = vehicle.level().getBlockState(it.below())
return@any !(!blockState.isAir && blockState.fluidState.isEmpty)
}
true
}
}
override fun pose(settings: JetNoRollSettings, state: JetNoRollState, vehicle: PokemonEntity): PoseType {
return poseProvider.select(settings, state, vehicle)
}
override fun speed(settings: JetNoRollSettings, state: JetNoRollState, vehicle: PokemonEntity, driver: Player): Float {
//retrieve stats
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
val staminaStat = vehicle.runtime.resolveDouble(settings.staminaExpr)
//retrieve minSpeed
val minSpeed = vehicle.runtime.resolveDouble(settings.minSpeed)
//TODO: Reintroduce stamina drain once stats start to be polished and tweaked
state.stamina.set(1.0f)
return state.rideVelocity.get().length().toFloat()
}
//TODO: Move these functions to a riding util class.
/*
* Normalizes the current speed between minSpeed and maxSpeed.
* The result is clamped between 0.0 and 1.0, where 0.0 represents minSpeed and 1.0 represents maxSpeed.
*/
private fun normalizeSpeed(currSpeed: Double, minSpeed: Double, maxSpeed: Double): Double {
require(maxSpeed > minSpeed) { "maxSpeed must be greater than minSpeed" }
return ((currSpeed - minSpeed) / (maxSpeed - minSpeed)).coerceIn(0.0, 1.0)
}
override fun rotation(
settings: JetNoRollSettings,
state: JetNoRollState,
vehicle: PokemonEntity,
driver: LivingEntity
): Vec2 {
return Vec2(driver.xRot * 0.5f, driver.yRot)
}
override fun velocity(
settings: JetNoRollSettings,
state: JetNoRollState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
var upForce = 0.0
var forwardForce = 0.0
val controller = (driver as? OrientationControllable)?.orientationController
//Calculate ride space velocity
calculateRideSpaceVel(settings, state, vehicle, driver)
//Translate ride space velocity to world space velocity.
if (controller != null) {
upForce = -1.0 * sin(Math.toRadians(controller.pitch.toDouble())) * state.rideVelocity.get().z
forwardForce = cos(Math.toRadians(controller.pitch.toDouble())) * state.rideVelocity.get().z
}
//If stamina has run out then initiate forced glide down.
upForce = if (state.stamina.get() > 0.0) upForce else -0.7
val velocity = Vec3(0.0, upForce, forwardForce)
return velocity
}
/*
* Calculates the change in the ride space vector due to player input and ride state
*/
private fun calculateRideSpaceVel(
settings: JetNoRollSettings,
state: JetNoRollState,
vehicle: PokemonEntity,
driver: Player
) {
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
val accel = vehicle.runtime.resolveDouble(settings.accelerationExpr)
val minSpeed = vehicle.runtime.resolveDouble(settings.minSpeed)
val speed = state.rideVelocity.get().length()
//speed up and slow down based on input
if (driver.zza > 0.0 && speed < topSpeed && state.stamina.get() > 0.0f) {
//modify acceleration to be slower when at closer speeds to top speed
val accelMod = max(-(normalizeSpeed(speed, minSpeed, topSpeed)) + 1, 0.0)
state.rideVelocity.set(
Vec3(
state.rideVelocity.get().x,
state.rideVelocity.get().y,
min(state.rideVelocity.get().z + (accel * accelMod), topSpeed)
)
)
} else if (driver.zza >= 0.0 && (state.stamina.get() == 0.0f)) {
state.rideVelocity.set(
Vec3(
state.rideVelocity.get().x,
state.rideVelocity.get().y,
max(state.rideVelocity.get().z - ((accel) / 4), minSpeed)
)
)
} else if (driver.zza < 0.0 && speed > minSpeed) {
//modify deccel to be slower when at closer speeds to minimum speed
val deccelMod = max((normalizeSpeed(speed, minSpeed, topSpeed) - 1).pow(2) * 4, 0.1)
//Decelerate currently always a constant half of max acceleration.
state.rideVelocity.set(
Vec3(
state.rideVelocity.get().x,
state.rideVelocity.get().y,
max(state.rideVelocity.get().z - ((accel * deccelMod) / 2), minSpeed)
)
)
}
}
override fun angRollVel(
settings: JetNoRollSettings,
state: JetNoRollState,
vehicle: PokemonEntity,
driver: Player,
deltaTime: Double
): Vec3 {
// //Cap at a rate of 5fps so frame skips dont lead to huge jumps
// val cappedDeltaTime = min(deltaTime, 0.2)
//
// //Get handling in degrees per second
// val yawRotRate = vehicle.runtime.resolveDouble(settings.handlingYawExpr)
//
// //Base the change off of deltatime.
// var handlingYaw = yawRotRate * (cappedDeltaTime)
//
// //apply stamina debuff if applicable
// handlingYaw *= if (state.stamina.get() > 0.0) 1.0 else 0.5
//
// //A+D to yaw
// val yawForce = driver.xxa * handlingYaw * -1
return Vec3.ZERO
}
override fun rotationOnMouseXY(
settings: JetNoRollSettings,
state: JetNoRollState,
vehicle: PokemonEntity,
driver: Player,
mouseY: Double,
mouseX: Double,
mouseYSmoother: SmoothDouble,
mouseXSmoother: SmoothDouble,
sensitivity: Double,
deltaTime: Double
): Vec3 {
if (driver !is OrientationControllable) return Vec3.ZERO
//TODO: figure out a cleaner solution to this issue of large jumps when skipping frames or lagging
//Cap at a rate of 5fps so frame skips dont lead to huge jumps
val cappedDeltaTime = min(deltaTime, 0.2)
val invertRoll = if (Cobblemon.config.invertRoll) -1 else 1
val invertPitch = if (Cobblemon.config.invertPitch) -1 else 1
// Accumulate the mouse input
state.currMouseXForce.set((state.currMouseXForce.get() + (0.0015 * mouseX * invertRoll)).coerceIn(-1.0, 1.0))
state.currMouseYForce.set((state.currMouseYForce.get() + (0.0015 * mouseY * invertPitch)).coerceIn(-1.0, 1.0))
//Get handling in degrees per second
var handling = vehicle.runtime.resolveDouble(settings.handlingExpr)
//convert it to delta time
handling *= (cappedDeltaTime)
//apply stamina debuff if applicable
handling *= if (state.stamina.get() > 0.0) 1.0 else 0.5
var pitchRot = handling * state.currMouseYForce.get()
// Yaw
val yawRot = handling * 0.5 * state.currMouseXForce.get()
val controller = (driver as OrientationControllable).orientationController
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)
}
val mouseRotation = Vec3(0.0, pitchRot, 0.0)
// 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
}
override fun canJump(
settings: JetNoRollSettings,
state: JetNoRollState,
vehicle: PokemonEntity,
driver: Player
): Boolean {
return false
}
override fun setRideBar(
settings: JetNoRollSettings,
state: JetNoRollState,
vehicle: PokemonEntity,
driver: Player
): Float {
return (state.stamina.get() / 1.0f)
}
override fun jumpForce(
settings: JetNoRollSettings,
state: JetNoRollState,
vehicle: PokemonEntity,
driver: Player,
jumpStrength: Int
): Vec3 {
return Vec3.ZERO
}
override fun gravity(
settings: JetNoRollSettings,
state: JetNoRollState,
vehicle: PokemonEntity,
regularGravity: Double
): Double {
return 0.0
}
override fun rideFovMultiplier(
settings: JetNoRollSettings,
state: JetNoRollState,
vehicle: PokemonEntity,
driver: Player
): Float {
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
val minSpeed = vehicle.runtime.resolveDouble(settings.minSpeed)
//Must I ensure that topspeed is greater than minimum?
val normalizedSpeed = normalizeSpeed(state.rideVelocity.get().length(), minSpeed, topSpeed)
//TODO: Determine if this should be based on max possible speed instead of top speed.
//Only ever want the fov change to be a max of 0.2 and for it to have non linear scaling.
return 1.0f + normalizedSpeed.pow(2).toFloat() * 0.2f
}
override fun useAngVelSmoothing(settings: JetNoRollSettings, state: JetNoRollState, vehicle: PokemonEntity): Boolean {
return true
}
override fun useRidingAltPose(
settings: JetNoRollSettings,
state: JetNoRollState,
vehicle: PokemonEntity,
driver: Player
): ResourceLocation {
return cobblemonResource("no_pose")
}
override fun inertia(settings: JetNoRollSettings, state: JetNoRollState, vehicle: PokemonEntity): Double {
return 1.0
}
override fun shouldRoll(settings: JetNoRollSettings, state: JetNoRollState, vehicle: PokemonEntity): Boolean {
return true
}
override fun turnOffOnGround(settings: JetNoRollSettings, state: JetNoRollState, vehicle: PokemonEntity): Boolean {
return false
}
override fun dismountOnShift(settings: JetNoRollSettings, state: JetNoRollState, vehicle: PokemonEntity): Boolean {
return false
}
override fun shouldRotatePokemonHead(
settings: JetNoRollSettings,
state: JetNoRollState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun shouldRotateRiderHead(
settings: JetNoRollSettings,
state: JetNoRollState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun getRideSounds(
settings: JetNoRollSettings,
state: JetNoRollState,
vehicle: PokemonEntity
): RideSoundSettingsList {
return settings.rideSounds
}
override fun createDefaultState(settings: JetNoRollSettings) = JetNoRollState()
override fun damageOnCollision(
settings: JetNoRollSettings,
state: JetNoRollState,
vehicle: PokemonEntity,
impactVec: Vec3
): Boolean {
val impactSpeed = impactVec.horizontalDistance().toFloat() * 10f
return vehicle.causeFallDamage(impactSpeed, 1f, vehicle.damageSources().flyIntoWall())
}
}
class JetNoRollSettings : RidingBehaviourSettings {
override val key = JetNoRollBehaviour.KEY
var gravity: Expression = "0".asExpression()
private set
var minSpeed: Expression = "1.2".asExpression()
private set
var handlingYawExpr: Expression = "q.get_ride_stats('SKILL', 'AIR', 50.0, 25.0)".asExpression()
private set
// Make configurable by json
var infiniteStamina: Expression = "false".asExpression()
private set
var jumpExpr: Expression = "q.get_ride_stats('JUMP', 'AIR', 300.0, 128.0)".asExpression()
private set
var handlingExpr: Expression = "q.get_ride_stats('SKILL', 'AIR', 140.0, 20.0)".asExpression()
private set
var speedExpr: Expression = "q.get_ride_stats('SPEED', 'AIR', 1.8, 1.0)".asExpression()
private set
var accelerationExpr: Expression =
"q.get_ride_stats('ACCELERATION', 'AIR', (1.0 / (20.0 * 1.0)), (1.0 / (20.0 * 5.0)))".asExpression()
private set
var staminaExpr: Expression = "q.get_ride_stats('STAMINA', 'AIR', 60.0, 10.0)".asExpression()
private set
var rideSounds: RideSoundSettingsList = RideSoundSettingsList()
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
rideSounds.encode(buffer)
buffer.writeExpression(gravity)
buffer.writeExpression(minSpeed)
buffer.writeExpression(handlingYawExpr)
buffer.writeExpression(infiniteStamina)
buffer.writeExpression(jumpExpr)
buffer.writeExpression(handlingExpr)
buffer.writeExpression(speedExpr)
buffer.writeExpression(accelerationExpr)
buffer.writeExpression(staminaExpr)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
rideSounds = RideSoundSettingsList.decode(buffer)
gravity = buffer.readExpression()
minSpeed = buffer.readExpression()
handlingYawExpr = buffer.readExpression()
infiniteStamina = buffer.readExpression()
jumpExpr = buffer.readExpression()
handlingExpr = buffer.readExpression()
speedExpr = buffer.readExpression()
accelerationExpr = buffer.readExpression()
staminaExpr = buffer.readExpression()
}
}
class JetNoRollState : RidingBehaviourState() {
var currSpeed = ridingState(0.0, Side.CLIENT)
var currMouseXForce = ridingState(0.0, Side.CLIENT)
var currMouseYForce = ridingState(0.0, Side.CLIENT)
override fun encode(buffer: FriendlyByteBuf) {
super.encode(buffer)
buffer.writeDouble(currSpeed.get())
buffer.writeFloat(stamina.get())
}
override fun decode(buffer: FriendlyByteBuf) {
super.decode(buffer)
currSpeed.set(buffer.readDouble(), forced = true)
}
override fun reset() {
super.reset()
currSpeed.set(0.0, forced = true)
currMouseXForce.set(0.0, forced = true)
currMouseYForce.set(0.0, forced = true)
}
override fun copy() = JetNoRollState().also {
it.currSpeed.set(currSpeed.get(), forced = true)
it.stamina.set(stamina.get(), forced = true)
it.rideVelocity.set(rideVelocity.get(), forced = true)
// it.currMouseXForce.set(currMouseXForce.get(), forced = true)
// it.currMouseYForce.set(currMouseYForce.get(), forced = true)
}
override fun shouldSync(previous: RidingBehaviourState): Boolean {
if (previous !is JetNoRollState) return false
if (previous.currSpeed != currSpeed) return true
return super.shouldSync(previous)
}
}
@@ -0,0 +1,566 @@
/*
* 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.air
import com.bedrockk.molang.Expression
import com.bedrockk.molang.runtime.MoLangMath.lerp
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.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.*
import com.cobblemon.mod.common.util.math.geometry.toRadians
import net.minecraft.client.Minecraft
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.Mth
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
import net.minecraft.world.phys.shapes.Shapes
import org.joml.Matrix3f
import kotlin.math.*
class RocketBehaviour : RidingBehaviour<RocketSettings, RocketState> {
companion object {
val KEY = cobblemonResource("air/rocket")
}
override val key = KEY
override fun getRidingStyle(settings: RocketSettings, state: RocketState): RidingStyle {
return RidingStyle.AIR
}
val poseProvider = PoseProvider<RocketSettings, RocketState>(PoseType.HOVER)
.with(PoseOption(PoseType.FLY) { _, state, _ ->
return@PoseOption state.boosting.get()
})
override fun isActive(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity
): Boolean {
return Shapes.create(vehicle.boundingBox).blockPositionsAsListRounded().any {
//Need to check other fluids
if (vehicle.isInWater || vehicle.isUnderWater) {
return@any false
}
//This might not actually work, depending on what the yPos actually is. yPos of the middle of the entity? the feet?
if (it.y.toDouble() == (vehicle.position().y)) {
val blockState = vehicle.level().getBlockState(it.below())
return@any !blockState.isAir && blockState.fluidState.isEmpty
}
true
}
}
override fun pose(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity
): PoseType {
return this.poseProvider.select(settings, state, vehicle)
}
override fun speed(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity,
driver: Player
): Float {
// Use this as a "tick" function and check to see if the driver is "boosting"
if(vehicle.level().isClientSide) {
//If the forward key is not held then it cannot be boosting
if(Minecraft.getInstance().options.keyUp.isDown()) {
val boostKeyPressed = Minecraft.getInstance().options.keySprint.isDown()
//If on the previous tick the boost key was held then don't change if the ride is boosting
if(state.boostIsToggleable.get() && boostKeyPressed) {
//flip the boosting state if boost key is pressed
state.boosting.set(!state.boosting.get())
}
//If the boost key is not held then next tick boosting is toggleable
state.boostIsToggleable.set(!boostKeyPressed)
} else {
//Turn off boost and reset boost params
state.boostIsToggleable.set(true)
state.boosting.set(false)
state.canSpeedBurst.set(true)
}
}
return state.rideVelocity.get().length().toFloat()
}
override fun updatePassengerRotation(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity,
driver: LivingEntity
) {
return
}
override fun clampPassengerRotation(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity,
driver: LivingEntity
) {
val f = Mth.wrapDegrees(driver.yRot - vehicle.yRot)
val lookYawLimit = 90.0f
val g = Mth.clamp(f, -lookYawLimit, lookYawLimit)
val ticksBeforeCorrection = 40
if(state.prevCamYRot.get() == driver.yRot){
state.ticksNoInput.set(state.ticksNoInput.get() + 1)
} else {
state.ticksNoInput.set(0)
}
val correctionForce = if(state.ticksNoInput.get() >= ticksBeforeCorrection && f != 0.0f && state.boosting.get()) 1f * min(sqrt(lookYawLimit / (abs(f))), 10.0f) else 0.0f
driver.yRotO += g - f
driver.yRot = driver.yRot + g - f //- ( clamp(f.sign * correctionForce, -abs(f), abs(f)) )
//driver.yRot = driver.yRot + g - f - ( min )
state.prevCamYRot.set(driver.yRot)
driver.setYHeadRot(driver.yRot)
}
override fun rotation(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity,
driver: LivingEntity
): Vec2 {
var newMomentum = state.turnMomentum.get().toDouble()
// Grab turningAcceleration and divide 20 twice to get
val turningAcceleration = (vehicle.runtime.resolveDouble(settings.handlingExpr) / 20.0f) / 20.0f * 2
val turnInput = (driver.xxa *-1.0f) * turningAcceleration
val maxTurnMomentum = 80.0f / 20.0f
// Base boost stats off of normal turning stats
val boostMaxTurnMomentum = maxTurnMomentum * 0.1f
val boostTurnInput = turnInput * 0.15f
if(state.boosting.get()) {
if(driver.xxa != 0.0f && abs(newMomentum + turnInput) < (boostMaxTurnMomentum)) { //If max momentum will not be exceeded then modulate
newMomentum += boostTurnInput
} else {
newMomentum = lerp(newMomentum, 0.0, 0.05)
}
} else {
if(driver.xxa == 0.0f) { //If no turning input then lerp to 0
newMomentum = lerp(newMomentum, 0.0, 0.05)
} else if(abs(newMomentum + turnInput) < maxTurnMomentum) { //If max momentum will not be exceeded then modulate
newMomentum += turnInput
}
}
state.turnMomentum.set(newMomentum.toFloat())
driver.yRot += newMomentum.toFloat()
return Vec2(driver.xRot, vehicle.yRot + newMomentum.toFloat())
}
override fun velocity(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
val retVel = calculateRideSpaceVel(settings, state, vehicle, driver)
state.rideVelocity.set(retVel)
return retVel
}
private fun calculateRideSpaceVel(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity,
driver: Player
): Vec3 {
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr) * 2
val accel = vehicle.runtime.resolveDouble(settings.accelerationExpr) * 0.5 * 2
var newVelocity = vehicle.deltaMovement
// Align the direction of movement with the world coordinate space so as not to modify it through turning
val yawAligned = Matrix3f().rotateY(vehicle.yRot.toRadians())
newVelocity = (newVelocity.toVector3f().mul(yawAligned)).toVec3d()
//speed up and slow down based on input
if (driver.zza != 0.0f && state.stamina.get() > 0.0 && !state.boosting.get()) {
//make sure it can't exceed top speed
val forwardInput = when {
vehicle.deltaMovement.horizontalDistance() > topSpeed && (driver.zza.sign == newVelocity.z.sign.toFloat()) -> 0.0
else -> driver.zza.sign
}
newVelocity = Vec3(
newVelocity.x,
newVelocity.y,
(newVelocity.z + (accel * forwardInput.toDouble())))
} else if ((state.stamina.get() > 0.0 && state.boosting.get())) {
val boostSpeed = topSpeed * 5
val boostAccel = if(newVelocity.length() < boostSpeed) accel * 3 else 0.0
val forwardInput = 1.0f
var burst = 0.0f
if(state.canSpeedBurst.get()) {
burst = 0.25f
state.canSpeedBurst.set(false)
}
newVelocity = Vec3(
newVelocity.x,
newVelocity.y,
newVelocity.z + (boostAccel * forwardInput.toDouble()) + burst)
}
//Vertical movement based on driver input.
val maxVertSpeed = 0.5f
var vertInput = 0.0
//If boosting then don't lose altitude
//otherwise propel up or fall
if (state.boosting.get() && !driver.jumping && vehicle.deltaMovement.y > (-maxVertSpeed * 0.25)) {
vertInput = -0.7
newVelocity = Vec3(
newVelocity.x,
newVelocity.y + accel * vertInput,
newVelocity.z)
} else if(state.boosting.get() && vehicle.deltaMovement.y < maxVertSpeed) {
vertInput = when {
driver.jumping -> 2.0
else -> 0.0
}
newVelocity = Vec3(
newVelocity.x,
(newVelocity.y + (accel * 0.5 * vertInput)),
newVelocity.z)
} else {
if (driver.jumping && !(vehicle.deltaMovement.y > maxVertSpeed && (newVelocity.y.sign.toFloat() > 0.0))) {
vertInput = 1.0
// Reset falldistance if upward motion is detected
vehicle.resetFallDistance()
} else {
vertInput = -2.5
}
if (state.stamina.get() > 0.0) {
newVelocity = Vec3(
newVelocity.x,
(newVelocity.y + (accel * vertInput)),
newVelocity.z)
}
}
return newVelocity
}
/*
* Normalizes the current speed between minSpeed and maxSpeed.
* The result is clamped between 0.0 and 1.0, where 0.0 represents minSpeed and 1.0 represents maxSpeed.
*/
private fun normalizeVal(currSpeed: Double, minSpeed: Double, maxSpeed: Double): Double {
require(maxSpeed > minSpeed) { "maxSpeed must be greater than minSpeed" }
return ((currSpeed - minSpeed) / (maxSpeed - minSpeed)).coerceIn(0.0, 1.0)
}
override fun angRollVel(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity,
driver: Player,
deltaTime: Double
): Vec3 {
return Vec3.ZERO
}
override fun rotationOnMouseXY(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity,
driver: Player,
mouseY: Double,
mouseX: Double,
mouseYSmoother: SmoothDouble,
mouseXSmoother: SmoothDouble,
sensitivity: Double,
deltaTime: Double
): Vec3 {
//Smooth out mouse input.
val smoothingSpeed = 4
val xInput = mouseXSmoother.getNewDeltaValue(mouseX * 0.1, deltaTime * smoothingSpeed);
val yInput = mouseYSmoother.getNewDeltaValue(mouseY * 0.1, deltaTime * smoothingSpeed);
//yaw, pitch, roll
return Vec3(0.0, yInput, xInput)
}
override fun canJump(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity,
driver: Player
): Boolean {
return false
}
override fun setRideBar(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity,
driver: Player
): Float {
//Retrieve stamina from state and use it to set the "stamina bar"
return (state.stamina.get() / 1.0f)
}
override fun jumpForce(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity,
driver: Player,
jumpStrength: Int
): Vec3 {
return Vec3.ZERO
}
override fun gravity(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity,
regularGravity: Double
): Double {
return 0.0
}
override fun rideFovMultiplier(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity,
driver: Player
): Float {
if (state.boosting.get()) {
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
//val exceededSpeed = max(vehicle.deltaMovement.length() - topSpeed, 0.0)
//TODO: Remove this magic number and get a better comparison for boost top speed
val normalizedBoostSpeed = normalizeVal(state.rideVelocity.get().length(), topSpeed, topSpeed * 3)
return 1.0f + normalizedBoostSpeed.pow(2).toFloat() * 0.2f
} else {
return 1.0f
}
}
override fun useAngVelSmoothing(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun useRidingAltPose(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity,
driver: Player
): ResourceLocation {
return cobblemonResource("no_pose")
}
override fun inertia(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity
): Double {
return 1.0
}
override fun shouldRoll(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun turnOffOnGround(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun dismountOnShift(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun shouldRotatePokemonHead(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun shouldRotateRiderHead(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun getRideSounds(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity
): RideSoundSettingsList {
return settings.rideSounds
}
override fun createDefaultState(settings: RocketSettings) = RocketState()
}
class RocketSettings : RidingBehaviourSettings {
override val key = RocketBehaviour.KEY
var speedExpr: Expression = "q.get_ride_stats('SPEED', 'AIR', 0.65, 0.3) * 0.25".asExpression()
private set
// Max accel is a whole 1.0 in 1 second. The conversion in the function below is to convert seconds to ticks
var accelerationExpr: Expression =
"q.get_ride_stats('ACCELERATION', 'AIR', (1.0 / (20.0 * 1.5)), (1.0 / (20.0 * 3.0))) * 0.5".asExpression()
private set
// Between 30 seconds and 10 seconds at the lowest when at full speed.
var staminaExpr: Expression = "q.get_ride_stats('STAMINA', 'AIR', 30.0, 10.0)".asExpression()
private set
//Between a one block jump and a ten block jump
var jumpExpr: Expression = "q.get_ride_stats('JUMP', 'AIR', 10.0, 1.0)".asExpression()
private set
// Controls the acceleration of the turning speed since the max turning speed is locked at 60 degrees/second
// So this is a range of 60 degrees/second per second to 20. Meaning at max stats when holding a direction it
// takes a full second to be turning at max speed.
var handlingExpr: Expression = "q.get_ride_stats('SKILL', 'AIR', 60.0, 20.0)".asExpression()
private set
var rideSounds: RideSoundSettingsList = RideSoundSettingsList()
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
rideSounds.encode(buffer)
buffer.writeExpression(speedExpr)
buffer.writeExpression(accelerationExpr)
buffer.writeExpression(staminaExpr)
buffer.writeExpression(jumpExpr)
buffer.writeExpression(handlingExpr)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
rideSounds = RideSoundSettingsList.decode(buffer)
speedExpr = buffer.readExpression()
accelerationExpr = buffer.readExpression()
staminaExpr = buffer.readExpression()
jumpExpr = buffer.readExpression()
handlingExpr = buffer.readExpression()
}
}
class RocketState : RidingBehaviourState() {
var boosting = ridingState(false, Side.BOTH)
var boostIsToggleable = ridingState(false, Side.BOTH)
var canSpeedBurst = ridingState(false, Side.BOTH)
var prevCamYRot: SidedRidingState<Float> = ridingState(0.0f, Side.CLIENT)
var ticksNoInput: SidedRidingState<Int> = ridingState(0, Side.CLIENT)
val turnMomentum: SidedRidingState<Float> = ridingState(0.0f, Side.CLIENT)
override fun reset() {
super.reset()
boosting.set(false, forced = true)
boostIsToggleable.set(false, forced = true)
canSpeedBurst.set(true, forced = true)
prevCamYRot.set(0.0f, forced = true)
ticksNoInput.set(0, forced = true)
turnMomentum.set(0.0f, forced = true)
}
override fun copy() = RocketState().also {
it.rideVelocity.set(this.rideVelocity.get(), forced = true)
it.stamina.set(this.stamina.get(), forced = true)
it.boosting.set(this.boosting.get(), forced = true)
it.boostIsToggleable.set(this.boosting.get(), forced = true)
it.canSpeedBurst.set(this.canSpeedBurst.get(), forced = true)
it.prevCamYRot.set(this.prevCamYRot.get(), forced = true)
it.ticksNoInput.set(this.ticksNoInput.get(), forced = true)
it.turnMomentum.set(this.turnMomentum.get(), forced = true)
}
override fun encode(buffer: FriendlyByteBuf) {
super.encode(buffer)
buffer.writeBoolean(boosting.get())
buffer.writeBoolean(boostIsToggleable.get())
buffer.writeBoolean(canSpeedBurst.get())
buffer.writeFloat(prevCamYRot.get())
buffer.writeInt(ticksNoInput.get())
buffer.writeFloat(turnMomentum.get())
}
override fun decode(buffer: FriendlyByteBuf) {
super.decode(buffer)
boosting.set(buffer.readBoolean(), forced = true)
boostIsToggleable.set(buffer.readBoolean(), forced = true)
canSpeedBurst.set(buffer.readBoolean(), forced = true)
prevCamYRot.set(buffer.readFloat(), forced = true)
ticksNoInput.set(buffer.readInt(), forced = true)
turnMomentum.set(buffer.readFloat(), forced = true)
}
override fun shouldSync(previous: RidingBehaviourState): Boolean {
if (previous !is RocketState) return false
if (previous.boosting.get() != boosting.get()) return true
if (previous.boostIsToggleable.get() != boostIsToggleable.get()) return true
if (previous.canSpeedBurst.get() != canSpeedBurst.get()) return true
if (previous.prevCamYRot.get() != prevCamYRot.get()) return true
if (previous.ticksNoInput.get() != ticksNoInput.get()) return true
if (previous.turnMomentum.get() != turnMomentum.get()) return true
return super.shouldSync(previous)
}
}
@@ -11,6 +11,7 @@ package com.cobblemon.mod.common.api.riding.behaviour.types.composite
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.*
import com.cobblemon.mod.common.api.riding.behaviour.types.composite.strategies.CompositeRidingStrategies
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.adapters.RidingBehaviourSettingsAdapter
@@ -106,13 +107,13 @@ class CompositeBehaviour : RidingBehaviour<CompositeSettings, CompositeState> {
}
}
override fun shouldRotatePlayerHead(
override fun shouldRotateRiderHead(
settings: CompositeSettings,
state: CompositeState,
vehicle: PokemonEntity
): Boolean {
return chooseBehaviour(settings, state) { behaviour, behaviourSettings, behaviourState ->
behaviour.shouldRotatePlayerHead(behaviourSettings, behaviourState, vehicle)
behaviour.shouldRotateRiderHead(behaviourSettings, behaviourState, vehicle)
}
}
@@ -327,6 +328,16 @@ class CompositeBehaviour : RidingBehaviour<CompositeSettings, CompositeState> {
}
}
override fun getRideSounds(
settings: CompositeSettings,
state: CompositeState,
vehicle: PokemonEntity
): RideSoundSettingsList {
return chooseBehaviour(settings, state) { behaviour, behaviourSettings, behaviourState ->
behaviour.getRideSounds(behaviourSettings, behaviourState, vehicle)
}
}
override fun isActive(
settings: CompositeSettings,
state: CompositeState,
@@ -45,7 +45,7 @@ object JumpStrategy : CompositeRidingStrategy<CompositeSettings> {
): Boolean {
if (state.activeBehaviour.get() == defaultSettings.key) return false
if (!vehicle.onGround()) return false
if (state.lastTransition.get() + 20 >= vehicle.level().gameTime) return false
if (state.lastTransition.get() + 5 >= vehicle.level().gameTime) return false
val defaultBehaviour = RidingBehaviours.get(defaultSettings.key)
return defaultBehaviour.isActive(defaultSettings, state.defaultBehaviourState, vehicle)
}
@@ -58,7 +58,7 @@ object JumpStrategy : CompositeRidingStrategy<CompositeSettings> {
): Boolean {
if (state.activeBehaviour.get() == alternativeSettings.key) return false
if (!driver.jumping) return false
if (state.lastTransition.get() + 20 >= vehicle.level().gameTime) return false
if (state.lastTransition.get() + 5 >= vehicle.level().gameTime) return false
val alternativeBehaviour = RidingBehaviours.get(alternativeSettings.key)
return alternativeBehaviour.isActive(alternativeSettings, state.alternateBehaviourState, vehicle)
}
@@ -13,6 +13,7 @@ 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.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.*
@@ -82,7 +83,15 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
// Use this as a "tick" function and calculate sprinting and inAir state here
state.sprinting.set(driver.isSprinting)
inAirCheck(state, vehicle)
return state.rideVelocity.get().length().toFloat()
}
fun inAirCheck(
state: HorseState,
vehicle: PokemonEntity,
) {
// Check both vertical movement and if there are blocks below.
val posBelow = vehicle.blockPosition().below()
val blockStateBelow = vehicle.level().getBlockState(posBelow)
@@ -91,14 +100,10 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
val canSupportEntity = blockStateBelow.isFaceSturdy(vehicle.level(), posBelow, Direction.UP)
val standingOnSolid = canSupportEntity && !isAirOrLiquid
// val level = vehicle.level()
// val toesBox = vehicle.boundingBox.move(0.0, -0.1, 0.0)
// inAir if not on the ground
val inAir = !(vehicle.deltaMovement.y == 0.0 || standingOnSolid)
state.inAir.set(inAir)
return state.rideVelocity.get().length().toFloat()
}
override fun updatePassengerRotation(
@@ -106,26 +111,7 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
state: HorseState,
vehicle: PokemonEntity,
driver: LivingEntity
) {
//Take the inverse so that it cancels out how
//much the entity rotates.
val turnAmount = calcRotAmount(settings, state, vehicle, driver)
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
val maxYawDiff = 90.0f
//Normalize the current rotation diff
val rotMod = Mth.wrapDegrees(driver.yRot - vehicle.yRot) / maxYawDiff
val rotAmount = 10.0f
//Take the inverse so that you turn more at higher speeds
val normSpeed = 1.0f - 0.5f*normalizeVal(state.rideVelocity.get().length(), 0.0, topSpeed).toFloat()
//driver.yRot += (entity.riding.deltaRotation.y - turnAmount)
//driver.setYHeadRot(driver.yHeadRot + (entity.riding.deltaRotation.y) - turnAmount)
}
) { return }
override fun clampPassengerRotation(
settings: HorseSettings,
@@ -148,9 +134,9 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
vehicle: PokemonEntity,
driver: LivingEntity
): Vec2 {
val turnAmount = calcRotAmount(settings, state, vehicle, driver)
var turnAmount = calcRotAmount(settings, state, vehicle, driver)
return Vec2(vehicle.xRot, vehicle.yRot + turnAmount )
return Vec2(vehicle.xRot, vehicle.yRot + turnAmount * 0.5f )
}
@@ -168,9 +154,9 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
): Float {
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
//In degrees per second
val walkHandling = 140.0 * 2
val handling = vehicle.runtime.resolveDouble(settings.handlingExpr) * 2
val handling = vehicle.runtime.resolveDouble(settings.handlingExpr) * 3
val walkHandlingBoost = 5
val walkHandling = handling * walkHandlingBoost
val maxYawDiff = 90.0f
@@ -184,13 +170,14 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
val rotDiffMod = (sqrt(abs(rotDiffNorm)) * rotDiffNorm.sign)
//Take the inverse so that you turn less at higher speeds
val normSpeed = 1.0f // = 1.0f - 0.5f*normalizeVal(state.rideVelocityocity.length(), 0.0, topSpeed).toFloat()
val normSpeed = 1.0f
// TurnRate should always be quick if not sprinting
val turnRate = if(state.sprinting.get()) (handling.toFloat() / 20.0f) else (walkHandling.toFloat() / 20.0f)
val invRelSpeed = (inverseLerp(vehicle.deltaMovement.horizontalDistance(), 0.0, topSpeed ).pow(0.5) - 1.0f) * -1.0f
val turnRate = ((handling.toFloat() / 20.0f) * max(walkHandlingBoost * invRelSpeed, 1.0)).toFloat()
//Ensure you only ever rotate as much difference as there is between the angles.
val turnSpeed = turnRate * rotDiffMod * normSpeed
val turnSpeed = turnRate * rotDiffMod * normSpeed
val rotAmount = turnSpeed.coerceIn(-abs(rotDiff), abs(rotDiff))
return rotAmount
@@ -217,16 +204,16 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
// Check to see if the ride should be walking or sprinting
val walkSpeed = getWalkSpeed(vehicle)
val rideTopSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
val rideTopSpeed = vehicle.runtime.resolveDouble(settings.speedExpr) * 1.3
val topSpeed = if(state.sprinting.get()) rideTopSpeed else walkSpeed
val accel = vehicle.runtime.resolveDouble(settings.accelerationExpr)
val accel = vehicle.runtime.resolveDouble(settings.accelerationExpr) * 2
//Flag for determining if player is actively inputting
var activeInput = false
var newVelocity = Vec3(state.rideVelocity.get().x, state.rideVelocity.get().y, state.rideVelocity.get().z)
var newVelocity = Vec3(state.rideVelocity.get().x, state.rideVelocity.get().y, state.rideVelocity.get().z).normalize().scale(vehicle.deltaMovement.length())
//var newVelocity = vehicle.deltaMovement.normalize()
//speed up and slow down based on input
if (driver.zza != 0.0f && state.stamina.get() > 0.0) {
@@ -245,11 +232,14 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
activeInput = true
}
//Gravity logic
// Gravity logic
if (vehicle.onGround()) {
newVelocity = Vec3(newVelocity.x, 0.0, newVelocity.z)
} else {
val gravity = (9.8 / ( 20.0)) * 0.2
//TODO: Should we just go back to standard minecraft gravity or do the lerp modifications prevent that?
//I think minecrafts gravity logic is also too harsh and isn't gamefied enough for mounts maybe? Need
//to do some testing and get other's opinions
val gravity = (9.8 / ( 20.0)) * 0.2 * 0.8
val terminalVel = 2.0
val fallingForce = gravity - ( newVelocity.z.sign *gravity *(abs(newVelocity.z) / 2.0))
@@ -261,27 +251,34 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
newVelocity = newVelocity.subtract(0.0, 0.0, min(0.03 * newVelocity.z.sign, newVelocity.z))
}
//TODO: Change this so its tied to a jumping stat and representative of the amount of jumps
val canJump = vehicle.runtime.resolveBoolean(settings.canJump)
//Jump the thang!
if (driver.jumping && vehicle.onGround()) {
if (driver.jumping && vehicle.onGround() && canJump) {
val jumpForce = 1.0
val horz = state.rideVelocity.get().horizontalDistance()
val velMag = newVelocity.length()
newVelocity = newVelocity.add(0.0, jumpForce, 0.0)
val test = 0.0
//Ensure this doesn't add unwanted forward velocity
//val mag = if(newVelocity.length() < rideTopSpeed) newVelocity.length() else rideTopSpeed
//newVelocity = newVelocity.normalize().scale(mag)
}
//Zero out lateral velocity possibly picked up from a controller transition
newVelocity = Vec3(0.0, newVelocity.y, newVelocity.z)
return newVelocity
}
/*
* Normalizes the current speed between minSpeed and maxSpeed.
* The result is clamped between 0.0 and 1.0, where 0.0 represents minSpeed and 1.0 represents maxSpeed.
* lerps the current val between minVal and maxVal.
* The result is clamped between 0.0 and 1.0, where 0.0 represents minVal and 1.0 represents maxVal.
*/
private fun normalizeVal(currSpeed: Double, minSpeed: Double, maxSpeed: Double): Double {
require(maxSpeed > minSpeed) { "maxSpeed must be greater than minSpeed" }
return ((currSpeed - minSpeed) / (maxSpeed - minSpeed)).coerceIn(0.0, 1.0)
private fun inverseLerp(currVal: Double, minVal: Double, maxVal: Double): Double {
require(maxVal > minVal) { "minVal must be greater than maxVal" }
return ((currVal - minVal) / (maxVal - minVal)).coerceIn(0.0, 1.0)
}
private fun getWalkSpeed( vehicle: PokemonEntity ): Double {
@@ -431,7 +428,7 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
return false
}
override fun shouldRotatePlayerHead(
override fun shouldRotateRiderHead(
settings: HorseSettings,
state: HorseState,
vehicle: PokemonEntity
@@ -439,6 +436,14 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
return true
}
override fun getRideSounds(
settings: HorseSettings,
state: HorseState,
vehicle: PokemonEntity
): RideSoundSettingsList {
return settings.rideSounds
}
override fun createDefaultState(settings: HorseSettings) = HorseState()
}
@@ -467,8 +472,11 @@ class HorseSettings : RidingBehaviourSettings {
var handlingExpr: Expression = "q.get_ride_stats('SKILL', 'LAND', 140.0, 20.0)".asExpression()
private set
var rideSounds: RideSoundSettingsList = RideSoundSettingsList()
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
rideSounds.encode(buffer)
buffer.writeExpression(speedExpr)
buffer.writeExpression(accelerationExpr)
buffer.writeExpression(staminaExpr)
@@ -477,13 +485,13 @@ class HorseSettings : RidingBehaviourSettings {
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
rideSounds = RideSoundSettingsList.decode(buffer)
speedExpr = buffer.readExpression()
accelerationExpr = buffer.readExpression()
staminaExpr = buffer.readExpression()
jumpExpr = buffer.readExpression()
handlingExpr = buffer.readExpression()
}
}
class HorseState : RidingBehaviourState() {
@@ -0,0 +1,563 @@
/*
* 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.land
import com.bedrockk.molang.Expression
import com.bedrockk.molang.runtime.MoLangMath.lerp
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.*
import com.cobblemon.mod.common.api.riding.posing.PoseOption
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.*
import com.cobblemon.mod.common.util.math.geometry.toRadians
import net.minecraft.core.Direction
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
import net.minecraft.world.phys.shapes.Shapes
import org.joml.Matrix3f
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
import kotlin.math.pow
import kotlin.math.sign
class MinekartBehaviour : RidingBehaviour<MinekartSettings, MinekartState> {
companion object {
val KEY = cobblemonResource("land/minekart")
}
override val key = KEY
override fun getRidingStyle(settings: MinekartSettings, state: MinekartState): RidingStyle {
return RidingStyle.LAND
}
val poseProvider = PoseProvider<MinekartSettings, MinekartState>(PoseType.STAND)
.with(PoseOption(PoseType.WALK) { _, state, _ ->
return@PoseOption abs(state.rideVelocity.get().horizontalDistance()) > 0.0
})
// val poseProvider = PoseProvider<MinekartSettings, MinekartState>(PoseType.STAND)
// .with(PoseOption(PoseType.WALK) { _, _, entity -> entity.entityData.get(PokemonEntity.MOVING) })
override fun isActive(settings: MinekartSettings, state: MinekartState, vehicle: PokemonEntity): Boolean {
return Shapes.create(vehicle.boundingBox).blockPositionsAsListRounded().any {
//Need to check other fluids
if (vehicle.isInWater || vehicle.isUnderWater) {
return@any false
}
//This might not actually work, depending on what the yPos actually is. yPos of the middle of the entity? the feet?
if (it.y.toDouble() == (vehicle.position().y)) {
val blockState = vehicle.level().getBlockState(it.below())
return@any !(!blockState.isAir && blockState.fluidState.isEmpty)
}
true
}
}
override fun pose(settings: MinekartSettings, state: MinekartState, vehicle: PokemonEntity): PoseType {
return poseProvider.select(settings, state, vehicle)
}
override fun speed(
settings: MinekartSettings,
state: MinekartState,
vehicle: PokemonEntity,
driver: Player
): Float {
val boostMax = vehicle.runtime.resolveFloat(settings.boostLimit)
//TODO: should this be by how much turn has accumulated or simply time turning... or both?
val boostPerSecond = 20.0f
val boostLoss = 40.0f
if (driver.jumping && driver.zza > 0.0f) {
if (!state.drifting.get()) {
if (driver.xxa != 0.0f) {
state.clockwiseDrift.set(driver.xxa.sign > 0.0)
state.turnMomentum.set(0.0f)
state.drifting.set(true)
state.boost.set(0.0f)
} else {
//TODO: Add jumping logic
}
} else {
state.boost.set((state.boost.get() + boostPerSecond / 20.0f).coerceIn(0.0f, boostMax))
}
} else {
if(driver.zza <= 0.0 && state.boost.get() != 0.0f){
state.boost.set(0.0f)
} else if( state.boost.get() > 0.0f ) {
state.boost.set(max(state.boost.get() - boostLoss, 0.0f))
}
state.drifting.set(false)
}
inAirCheck(state, vehicle)
return driver.deltaMovement.length().toFloat()
}
fun inAirCheck(
state: MinekartState,
vehicle: PokemonEntity,
) {
// Check both vertical movement and if there are blocks below.
val posBelow = vehicle.blockPosition().below()
val blockStateBelow = vehicle.level().getBlockState(posBelow)
val isAirOrLiquid = blockStateBelow.isAir || !blockStateBelow.fluidState.isEmpty
val canSupportEntity = blockStateBelow.isFaceSturdy(vehicle.level(), posBelow, Direction.UP)
val standingOnSolid = canSupportEntity && !isAirOrLiquid
// inAir if not on the ground
val inAir = !(vehicle.deltaMovement.y == 0.0 || standingOnSolid)
state.inAir.set(inAir)
}
override fun rotation(
settings: MinekartSettings,
state: MinekartState,
vehicle: PokemonEntity,
driver: LivingEntity
): Vec2 {
var newMomentum = state.turnMomentum.get().toDouble()
//TODO: maybe tie this to max turnspeeds rather than the turn acceleration
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
val turningAcceleration = (vehicle.runtime.resolveDouble(settings.handlingExpr) * 1.5 / 20.0f) / 20.0f * 4.0
val turnInput = (driver.xxa *-1.0f) * turningAcceleration
val driftInput = turnInput * 0.25f
val lowSpeedTurnBoost = (inverseLerp(vehicle.deltaMovement.horizontalDistance(), 0.0, topSpeed ).pow(0.5) - 2.0f) * -1.0f
val forcedDriftTurnMomentum = (80.0f / 20.0f) * lowSpeedTurnBoost
val maxTurnMomentum = (60.0f / 20.0f) * lowSpeedTurnBoost
val maxDriftMomentum = forcedDriftTurnMomentum * 0.6f
if (!state.drifting.get()){
// perform non drift turning
if(abs(newMomentum) <= maxTurnMomentum && turnInput != 0.0 ) {
newMomentum += turnInput
newMomentum = min(max(newMomentum, maxTurnMomentum * -1.0), maxTurnMomentum.toDouble())
} else {
newMomentum = lerp(newMomentum, 0.0, 0.15)
}
state.turnMomentum.set(newMomentum.toFloat())
driver.yRot += newMomentum.toFloat()
return Vec2(driver.xRot, vehicle.yRot + newMomentum.toFloat())
} else {
// perform drift turning
if(abs(newMomentum) <= maxDriftMomentum && driftInput != 0.0 ) {
newMomentum += driftInput
newMomentum = min(max(newMomentum, maxDriftMomentum * -1.0), maxDriftMomentum.toDouble())
} else {
newMomentum = lerp(newMomentum, 0.0, 0.15)
}
val signedDriftTurn = if(state.clockwiseDrift.get()) forcedDriftTurnMomentum * -1.0f else forcedDriftTurnMomentum
state.turnMomentum.set(newMomentum.toFloat())
driver.yRot += newMomentum.toFloat() + signedDriftTurn.toFloat()
return Vec2(driver.xRot, vehicle.yRot + newMomentum.toFloat() + signedDriftTurn.toFloat())
}
}
override fun velocity(
settings: MinekartSettings,
state: MinekartState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
state.rideVelocity.set(calculateRideSpaceVel(settings, state, vehicle, driver))
return state.rideVelocity.get()
}
private fun calculateRideSpaceVel(
settings: MinekartSettings,
state: MinekartState,
vehicle: PokemonEntity,
driver: Player
): Vec3 {
// Check to see if the ride should be walking or sprinting
val rideTopSpeed = vehicle.runtime.resolveDouble(settings.speedExpr) * 0.6 * 2.0
val topSpeed = if (state.drifting.get()) rideTopSpeed * 0.7 else rideTopSpeed
val accel = vehicle.runtime.resolveDouble(settings.accelerationExpr) * 0.3 * 3 * 2.0
val driftAngle = 20.0f
//Align the direction of this vector with the vehicles current heading.
var newVelocity = vehicle.deltaMovement
newVelocity = newVelocity.yRot(vehicle.yRot.toRadians())
// rotate the angle of force if drifting
if (state.drifting.get()) {
val signedDriftAngle = if(state.clockwiseDrift.get()) driftAngle else driftAngle * -1.0f
newVelocity = newVelocity.yRot(signedDriftAngle.toRadians())
}
//speed up and slow down based on input
if (driver.zza != 0.0f && state.stamina.get() > 0.0 && !state.inAir.get()) {
//make sure it can't exceed top speed
var forwardInput = when {
driver.zza > 0 && newVelocity.z > topSpeed -> 0.0
driver.zza < 0 && newVelocity.z <= 0.0 -> 0.0
else -> driver.zza.sign
}
if (!state.drifting.get() && state.boost.get() != 0.0f) {
forwardInput = 2.0;
}
// If drifting we need more acceleration due to the constant turn
val driftAccelBoost = if (state.drifting.get()) 1.5 else 1.0
newVelocity = Vec3(
newVelocity.x,
newVelocity.y,
(newVelocity.z + (accel * forwardInput.toDouble() * driftAccelBoost)))
}
// Gravity logic
if (vehicle.onGround()) {
newVelocity = Vec3(newVelocity.x, 0.0, newVelocity.z)
} else {
//TODO: Should we just go back to standard minecraft gravity or do the lerp modifications prevent that?
//I think minecrafts gravity logic is also too harsh and isn't gamefied enough for mounts maybe? Need
//to do some testing and get other's opinions
val gravity = (9.8 / ( 20.0)) * 0.2 * 0.25 * 3.0
val terminalVel = 2.0
val fallingForce = gravity - ( newVelocity.z.sign *gravity *(abs(newVelocity.z) / 2.0))
newVelocity = Vec3(newVelocity.x, max(newVelocity.y - fallingForce, -terminalVel), newVelocity.z)
}
//ground Friction
var friction = 0.003 * 2.0
if (state.drifting.get()) {friction *= 0.5}
if (!state.inAir.get()) {
newVelocity = newVelocity.subtract(
min(friction , abs(newVelocity.x)) * newVelocity.x.sign,
0.0,
min(friction , abs(newVelocity.z)) * newVelocity.z.sign
)
// TEST FOR LATERAL SLIP OR WHATEVER
//TODO: calc lateral slip and friction better!
if (!state.drifting.get()) {
newVelocity = newVelocity.subtract(
min(friction * 10, abs(newVelocity.x)) * newVelocity.x.sign,
0.0,
0.0,
)
}
}
//TODO: Change this so its tied to a jumping stat and representative of the amount of jumps
// val canJump = vehicle.runtime.resolveBoolean(settings.canJump)
// //Jump the thang!
// if (driver.jumping && vehicle.onGround() && canJump) {
// val jumpForce = 0.5
//
// newVelocity = newVelocity.add(0.0, jumpForce, 0.0)
//
// }
// unrotate the angle of force if drifting
if (state.drifting.get()) {
val inverseSignedDriftAngle = if(state.clockwiseDrift.get()) driftAngle * -1.0f else driftAngle
newVelocity = newVelocity.yRot(inverseSignedDriftAngle.toRadians())
}
return newVelocity
}
/*
* lerps the current val between minVal and maxVal.
* The result is clamped between 0.0 and 1.0, where 0.0 represents minVal and 1.0 represents maxVal.
*/
private fun inverseLerp(currVal: Double, minVal: Double, maxVal: Double): Double {
require(maxVal > minVal) { "minVal must be greater than maxVal" }
return ((currVal - minVal) / (maxVal - minVal)).coerceIn(0.0, 1.0)
}
override fun angRollVel(
settings: MinekartSettings,
state: MinekartState,
vehicle: PokemonEntity,
driver: Player,
deltaTime: Double
): Vec3 {
return Vec3.ZERO
}
override fun rotationOnMouseXY(
settings: MinekartSettings,
state: MinekartState,
vehicle: PokemonEntity,
driver: Player,
mouseY: Double,
mouseX: Double,
mouseYSmoother: SmoothDouble,
mouseXSmoother: SmoothDouble,
sensitivity: Double,
deltaTime: Double
): Vec3 {
if (driver !is OrientationControllable) return Vec3.ZERO
//Might need to add the smoothing here for default.
val invertRoll = if (Cobblemon.config.invertRoll) -1 else 1
val invertPitch = if (Cobblemon.config.invertPitch) -1 else 1
return Vec3(0.0, mouseY * invertPitch, mouseX * invertRoll)
}
override fun canJump(
settings: MinekartSettings,
state: MinekartState,
vehicle: PokemonEntity,
driver: Player
): Boolean {
return vehicle.runtime.resolveBoolean(settings.canJump)
}
override fun setRideBar(
settings: MinekartSettings,
state: MinekartState,
vehicle: PokemonEntity,
driver: Player
): Float {
return 0.0f
}
override fun jumpForce(
settings: MinekartSettings,
state: MinekartState,
vehicle: PokemonEntity,
driver: Player,
jumpStrength: Int
): Vec3 {
return Vec3.ZERO
}
override fun gravity(
settings: MinekartSettings,
state: MinekartState,
vehicle: PokemonEntity,
regularGravity: Double
): Double {
return 0.0
}
override fun rideFovMultiplier(
settings: MinekartSettings,
state: MinekartState,
vehicle: PokemonEntity,
driver: Player
): Float {
var fov = 1.0f
if(!state.drifting.get() && state.boost.get() != 0.0f) {
//TODO: is this too abrupt? Possibly caluclate differently?
fov = 1.2f
}
return fov
}
override fun useAngVelSmoothing(
settings: MinekartSettings,
state: MinekartState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun useRidingAltPose(
settings: MinekartSettings,
state: MinekartState,
vehicle: PokemonEntity,
driver: Player
): ResourceLocation {
when {
state.drifting.get() -> return cobblemonResource("drifting")
}
return cobblemonResource("no_pose")
}
override fun inertia(settings: MinekartSettings, state: MinekartState, vehicle: PokemonEntity): Double {
return 1.0
}
override fun shouldRoll(settings: MinekartSettings, state: MinekartState, vehicle: PokemonEntity): Boolean {
return false
}
override fun turnOffOnGround(
settings: MinekartSettings,
state: MinekartState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun dismountOnShift(
settings: MinekartSettings,
state: MinekartState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun shouldRotatePokemonHead(
settings: MinekartSettings,
state: MinekartState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun shouldRotateRiderHead(
settings: MinekartSettings,
state: MinekartState,
vehicle: PokemonEntity
): Boolean {
return true
}
override fun getRideSounds(
settings: MinekartSettings,
state: MinekartState,
vehicle: PokemonEntity
): RideSoundSettingsList {
return settings.rideSounds
}
override fun createDefaultState(settings: MinekartSettings) = MinekartState()
}
class MinekartSettings : RidingBehaviourSettings {
override val key = MinekartBehaviour.KEY
var canJump = "true".asExpression()
private set
var boostLimit = "100.0f".asExpression()
private set
var speedExpr: Expression = "q.get_ride_stats('SPEED', 'LAND', 1.0, 0.3)".asExpression()
private set
// Max accel is a whole 1.0 in 1 second. The conversion in the function below is to convert seconds to ticks
var accelerationExpr: Expression =
"q.get_ride_stats('ACCELERATION', 'LAND', (1.0 / (20.0 * 1.5)), (1.0 / (20.0 * 5.0)))".asExpression()
private set
var staminaExpr: Expression = "q.get_ride_stats('STAMINA', 'LAND', 30.0, 10.0)".asExpression()
private set
//Between a one block jump and a ten block jump
var jumpExpr: Expression = "q.get_ride_stats('JUMP', 'LAND', 10.0, 1.0)".asExpression()
private set
var handlingExpr: Expression = "q.get_ride_stats('SKILL', 'LAND', 140.0, 20.0)".asExpression()
private set
var rideSounds: RideSoundSettingsList = RideSoundSettingsList()
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
rideSounds.encode(buffer)
buffer.writeExpression(canJump)
buffer.writeExpression(boostLimit)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
rideSounds = RideSoundSettingsList.decode(buffer)
canJump = buffer.readExpression()
boostLimit = buffer.readExpression()
}
}
class MinekartState : RidingBehaviourState() {
var currSpeed = ridingState(0.0, Side.BOTH)
var deltaRotation = ridingState(Vec2.ZERO, Side.BOTH)
var drifting = ridingState(false, Side.CLIENT)
var clockwiseDrift = ridingState(false, Side.CLIENT)
val boost: SidedRidingState<Float> = ridingState(0.0f, Side.CLIENT)
val turnMomentum: SidedRidingState<Float> = ridingState(0.0f, Side.CLIENT)
var inAir = ridingState(false, Side.CLIENT)
override fun reset() {
super.reset()
currSpeed.set(0.0, forced = true)
deltaRotation.set(Vec2.ZERO, forced = true)
drifting.set(false, forced = true)
clockwiseDrift.set(false, forced = true)
boost.set(0.0f, forced = true)
turnMomentum.set(0.0f, forced = true)
inAir.set(false, forced = true)
}
override fun encode(buffer: FriendlyByteBuf) {
super.encode(buffer)
buffer.writeBoolean(drifting.get())
buffer.writeBoolean(clockwiseDrift.get())
buffer.writeFloat(boost.get())
buffer.writeFloat(turnMomentum.get())
buffer.writeBoolean(inAir.get())
}
override fun decode(buffer: FriendlyByteBuf) {
super.decode(buffer)
drifting.set(buffer.readBoolean(), forced = true)
clockwiseDrift.set(buffer.readBoolean(), forced = true)
boost.set(buffer.readFloat(), forced = true)
turnMomentum.set(buffer.readFloat(), forced = true)
inAir.set(buffer.readBoolean(), forced = true)
}
override fun toString(): String {
return "MinekartState(currSpeed=${currSpeed.get()}, deltaRotation=${deltaRotation.get()})"
}
override fun copy() = MinekartState().also {
it.rideVelocity.set(rideVelocity.get(), forced = true)
it.stamina.set(stamina.get(), forced = true)
it.currSpeed.set(currSpeed.get(), forced = true)
it.deltaRotation.set(deltaRotation.get(), forced = true)
it.drifting.set(drifting.get(), forced = true)
it.clockwiseDrift.set(clockwiseDrift.get(), forced = true)
it.boost.set(boost.get(), forced = true)
it.turnMomentum.set(turnMomentum.get(), forced = true)
it.inAir.set(inAir.get(), forced = true)
}
override fun shouldSync(previous: RidingBehaviourState): Boolean {
if (previous !is MinekartState) return false
if (previous.drifting.get() != drifting.get()) return true
if (previous.clockwiseDrift.get() != clockwiseDrift.get()) return true
if (previous.boost.get() != boost.get()) return true
if (previous.turnMomentum.get() != turnMomentum.get()) return true
if (previous.inAir.get() != inAir.get()) return true
return super.shouldSync(previous)
}
}
@@ -8,17 +8,21 @@
package com.cobblemon.mod.common.api.riding.behaviour.types.land
import com.bedrockk.molang.runtime.value.DoubleValue
import com.bedrockk.molang.Expression
import com.bedrockk.molang.runtime.MoLangMath.lerp
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.*
import com.cobblemon.mod.common.api.riding.posing.PoseOption
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.*
import com.cobblemon.mod.common.util.math.geometry.toRadians
import net.minecraft.core.Direction
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.SmoothDouble
@@ -27,29 +31,15 @@ import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
import net.minecraft.world.phys.shapes.Shapes
import kotlin.math.absoluteValue
import org.joml.Matrix3f
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
import kotlin.math.sign
class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
companion object {
val KEY = cobblemonResource("land/vehicle")
val MAX_TOP_SPEED = 1.0 // 20 bl/s
val MIN_TOP_SPEED = 0.35 // 7 bl/s
//val MIN_SPEED = 0.25 // 5 bl/s
//Accel will lie between 1.0 second and 5.0 seconds
val MAX_ACCEL = (MAX_TOP_SPEED) / (20*3) //3.0 second to max speed
val MIN_ACCEL = (MAX_TOP_SPEED) / (20*8) // 8 seconds to max speed
//Can rotate 90 degrees
val MAX_HANDLING = 90.0
val MIN_HANDLING = 30.0
val MAX_YAW_HANDLING = 16.0
val MIN_YAW_HANDLING = 8.0
}
override val key = KEY
@@ -59,7 +49,12 @@ class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
}
val poseProvider = PoseProvider<VehicleSettings, VehicleState>(PoseType.STAND)
.with(PoseOption(PoseType.WALK) { _, _, entity -> entity.entityData.get(PokemonEntity.MOVING) })
.with(PoseOption(PoseType.WALK) { _, state, _ ->
return@PoseOption abs(state.rideVelocity.get().horizontalDistance()) > 0.0
})
// val poseProvider = PoseProvider<VehicleSettings, VehicleState>(PoseType.STAND)
// .with(PoseOption(PoseType.WALK) { _, _, entity -> entity.entityData.get(PokemonEntity.MOVING) })
override fun isActive(settings: VehicleSettings, state: VehicleState, vehicle: PokemonEntity): Boolean {
return Shapes.create(vehicle.boundingBox).blockPositionsAsListRounded().any {
@@ -86,18 +81,38 @@ class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
vehicle: PokemonEntity,
driver: Player
): Float {
val topSpeed = vehicle.getRideStat(RidingStat.SPEED, RidingStyle.AIR, MIN_TOP_SPEED, MAX_TOP_SPEED)
val accel = vehicle.getRideStat(RidingStat.ACCELERATION, RidingStyle.AIR, MIN_ACCEL, MAX_ACCEL)
//speed up and slow down based on input
if (driver.zza > 0.0 && state.currSpeed.get() < topSpeed) {
state.currSpeed.set(min(state.currSpeed.get() + accel , topSpeed))
} else if (driver.zza < 0.0 && state.currSpeed.get() > 0.0) {
//Decelerate is now always a constant half of max acceleration.
state.currSpeed.set(max(state.currSpeed.get() - (MAX_ACCEL / 2), 0.0))
if (driver.isShiftKeyDown) {
state.drifting.set(true)
if(driver.zza != 0.0f) {
state.poweredDrifting.set(true)
} else {
state.poweredDrifting.set(false)
}
} else {
state.drifting.set(false)
state.poweredDrifting.set(false)
}
return state.currSpeed.get().toFloat()
inAirCheck(state, vehicle)
return driver.deltaMovement.length().toFloat()
}
fun inAirCheck(
state: VehicleState,
vehicle: PokemonEntity,
) {
// Check both vertical movement and if there are blocks below.
val posBelow = vehicle.blockPosition().below()
val blockStateBelow = vehicle.level().getBlockState(posBelow)
val isAirOrLiquid = blockStateBelow.isAir || !blockStateBelow.fluidState.isEmpty
val canSupportEntity = blockStateBelow.isFaceSturdy(vehicle.level(), posBelow, Direction.UP)
val standingOnSolid = canSupportEntity && !isAirOrLiquid
// inAir if not on the ground
val inAir = !(vehicle.deltaMovement.y == 0.0 || standingOnSolid)
state.inAir.set(inAir)
}
override fun rotation(
@@ -106,10 +121,38 @@ class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
vehicle: PokemonEntity,
driver: LivingEntity
): Vec2 {
val rotationDegrees = driver.xxa * vehicle.runtime.resolveFloat(settings.rotationSpeed)
val rotation = Vec2(driver.xRot, vehicle.yRot - rotationDegrees)
state.deltaRotation.set(Vec2(rotation.x - driver.rotationVector.x, rotation.y - vehicle.rotationVector.y))
return rotation
var newMomentum = state.turnMomentum.get().toDouble()
// Grab turningAcceleration and divide 20 twice to get
val turningAcceleration = (vehicle.runtime.resolveDouble(settings.handlingExpr) * 1.5 / 20.0f) / 20.0f * 4.0
val turnInput = (driver.xxa *-1.0f) * turningAcceleration
val maxTurnMomentum = 80.0f / 20.0f
// Base boost stats off of normal turning stats
val driftMaxTurnMomentum = maxTurnMomentum * 3.0f
var driftTurnInput = turnInput * 0.4f
if(state.drifting.get() || state.inAir.get()) {
if( newMomentum < driftMaxTurnMomentum) {
driftTurnInput = driftTurnInput * (vehicle.deltaMovement.horizontalDistance() / vehicle.runtime.resolveDouble(settings.speedExpr))
newMomentum += driftTurnInput
}
newMomentum = lerp(newMomentum, 0.0, 0.03)
if( abs(newMomentum) < 0.05 ) {newMomentum = 0.0}
} else {
if(abs(newMomentum) <= maxTurnMomentum && turnInput != 0.0 ) {
newMomentum += turnInput
newMomentum.coerceIn(-maxTurnMomentum.toDouble(), maxTurnMomentum.toDouble())
} else {
newMomentum = lerp(newMomentum, 0.0, 0.15)
}
}
state.turnMomentum.set(newMomentum.toFloat())
driver.yRot += newMomentum.toFloat()
return Vec2(driver.xRot, vehicle.yRot + newMomentum.toFloat())
}
override fun velocity(
@@ -119,18 +162,106 @@ class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
driver: Player,
input: Vec3
): Vec3 {
val runtime = vehicle.runtime
val driveFactor = runtime.resolveFloat(settings.driveFactor)
var g = driver.zza * driveFactor
if (g <= 0.0f) {
g *= runtime.resolveFloat(settings.reverseDriveFactor)
}
if (driver.xxa != 0F && g.absoluteValue < runtime.resolveFloat(settings.minimumSpeedToTurn)) {
driver.xxa = 0F
}
val velocity = Vec3(0.0 /* Maybe do drifting here later */, 0.0, g.toDouble() * state.currSpeed.get())
state.rideVelocity.set(calculateRideSpaceVel(settings, state, vehicle, driver))
return state.rideVelocity.get()
}
return velocity
private fun calculateRideSpaceVel(
settings: VehicleSettings,
state: VehicleState,
vehicle: PokemonEntity,
driver: Player
): Vec3 {
// Check to see if the ride should be walking or sprinting
//val walkSpeed = getWalkSpeed(vehicle)
val rideTopSpeed = vehicle.runtime.resolveDouble(settings.speedExpr) * 0.6 * 2.0
val topSpeed = if (state.drifting.get()) rideTopSpeed * 0.5 else rideTopSpeed
val accel = vehicle.runtime.resolveDouble(settings.accelerationExpr) * 0.3 * 3 * 2.0
//Flag for determining if player is actively inputting
var activeInput = false
var newVelocity = Vec3.ZERO
if (!state.drifting.get()) {
newVelocity = vehicle.deltaMovement
newVelocity = newVelocity.yRot(vehicle.yRot.toRadians())
} else {
state
newVelocity = vehicle.deltaMovement
// Align the direction of movement with the world coordinate space so as not to modify it through turning
val yawAligned = Matrix3f().rotateY(vehicle.yRot.toRadians())
newVelocity = (newVelocity.toVector3f().mul(yawAligned)).toVec3d()
}
//speed up and slow down based on input
if (driver.zza != 0.0f && state.stamina.get() > 0.0 && !state.inAir.get()) {
//make sure it can't exceed top speed
val forwardInput = when {
driver.zza > 0 && newVelocity.z > topSpeed -> 0.0
driver.zza < 0 && newVelocity.z <= 0.0 -> 0.0
else -> driver.zza.sign
}
newVelocity = Vec3(
newVelocity.x,
newVelocity.y,
(newVelocity.z + (accel * forwardInput.toDouble())))
activeInput = true
}
// Gravity logic
if (vehicle.onGround()) {
newVelocity = Vec3(newVelocity.x, 0.0, newVelocity.z)
} else {
//TODO: Should we just go back to standard minecraft gravity or do the lerp modifications prevent that?
//I think minecrafts gravity logic is also too harsh and isn't gamefied enough for mounts maybe? Need
//to do some testing and get other's opinions
val gravity = (9.8 / ( 20.0)) * 0.2 * 0.25 * 3.0
val terminalVel = 2.0
val fallingForce = gravity - ( newVelocity.z.sign *gravity *(abs(newVelocity.z) / 2.0))
newVelocity = Vec3(newVelocity.x, max(newVelocity.y - fallingForce, -terminalVel), newVelocity.z)
}
//ground Friction
var friction = 0.003 * 2.0
if (state.drifting.get()) {friction *= 0.1}
if (!state.inAir.get()) {
newVelocity = newVelocity.subtract(
min(friction , abs(newVelocity.x)) * newVelocity.x.sign,
0.0,
min(friction , abs(newVelocity.z)) * newVelocity.z.sign
)
// TEST FOR LATERAL SLIP OR WHATEVER
//TODO: calc lateral slip and friction better!
if (!state.drifting.get()) {
newVelocity = newVelocity.subtract(
min(friction * 10, abs(newVelocity.x)) * newVelocity.x.sign,
0.0,
0.0,
)
}
}
//TODO: Change this so its tied to a jumping stat and representative of the amount of jumps
val canJump = vehicle.runtime.resolveBoolean(settings.canJump)
//Jump the thang!
if (driver.jumping && vehicle.onGround() && canJump) {
val jumpForce = 0.5
newVelocity = newVelocity.add(0.0, jumpForce, 0.0)
}
return newVelocity
}
override fun angRollVel(
@@ -188,10 +319,7 @@ class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
driver: Player,
jumpStrength: Int
): Vec3 {
val runtime = vehicle.runtime
runtime.environment.query.addFunction("jump_strength") { DoubleValue(jumpStrength.toDouble()) }
val jumpVector = settings.jumpVector.map { runtime.resolveFloat(it) }
return Vec3(jumpVector[0].toDouble(), jumpVector[1].toDouble(), jumpVector[2].toDouble())
return Vec3.ZERO
}
override fun gravity(
@@ -200,7 +328,7 @@ class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
vehicle: PokemonEntity,
regularGravity: Double
): Double {
return regularGravity
return 0.0
}
override fun rideFovMultiplier(
@@ -226,11 +354,15 @@ class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
vehicle: PokemonEntity,
driver: Player
): ResourceLocation {
when {
state.poweredDrifting.get() -> return cobblemonResource("powered_drifting")
state.drifting.get() -> return cobblemonResource("drifting")
}
return cobblemonResource("no_pose")
}
override fun inertia(settings: VehicleSettings, state: VehicleState, vehicle: PokemonEntity): Double {
return 0.5
return 1.0
}
override fun shouldRoll(settings: VehicleSettings, state: VehicleState, vehicle: PokemonEntity): Boolean {
@@ -261,12 +393,20 @@ class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
return false
}
override fun shouldRotatePlayerHead(
override fun shouldRotateRiderHead(
settings: VehicleSettings,
state: VehicleState,
vehicle: PokemonEntity
): Boolean {
return false
return true
}
override fun getRideSounds(
settings: VehicleSettings,
state: VehicleState,
vehicle: PokemonEntity
): RideSoundSettingsList {
return settings.rideSounds
}
override fun createDefaultState(settings: VehicleSettings) = VehicleState()
@@ -278,9 +418,6 @@ class VehicleSettings : RidingBehaviourSettings {
var canJump = "true".asExpression()
private set
var jumpVector = listOf("0".asExpression(), "0.3".asExpression(), "0".asExpression())
private set
var speed = "0.3".asExpression()
private set
@@ -290,40 +427,50 @@ class VehicleSettings : RidingBehaviourSettings {
var reverseDriveFactor = "0.25".asExpression()
private set
var minimumSpeedToTurn = "0.1".asExpression()
private set
var rotationSpeed = "45/20".asExpression()
private set
var lookYawLimit = "101".asExpression()
private set
var speedExpr: Expression = "q.get_ride_stats('SPEED', 'LAND', 1.0, 0.3)".asExpression()
private set
// Max accel is a whole 1.0 in 1 second. The conversion in the function below is to convert seconds to ticks
var accelerationExpr: Expression =
"q.get_ride_stats('ACCELERATION', 'LAND', (1.0 / (20.0 * 1.5)), (1.0 / (20.0 * 5.0)))".asExpression()
private set
// Between 30 seconds and 10 seconds at the lowest when at full speed.
var staminaExpr: Expression = "q.get_ride_stats('STAMINA', 'LAND', 30.0, 10.0)".asExpression()
private set
//Between a one block jump and a ten block jump
var jumpExpr: Expression = "q.get_ride_stats('JUMP', 'LAND', 10.0, 1.0)".asExpression()
private set
var handlingExpr: Expression = "q.get_ride_stats('SKILL', 'LAND', 140.0, 20.0)".asExpression()
private set
var rideSounds: RideSoundSettingsList = RideSoundSettingsList()
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
rideSounds.encode(buffer)
buffer.writeExpression(canJump)
buffer.writeExpression(jumpVector[0])
buffer.writeExpression(jumpVector[1])
buffer.writeExpression(jumpVector[2])
buffer.writeExpression(speed)
buffer.writeExpression(driveFactor)
buffer.writeExpression(reverseDriveFactor)
buffer.writeExpression(minimumSpeedToTurn)
buffer.writeExpression(rotationSpeed)
buffer.writeExpression(lookYawLimit)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
rideSounds = RideSoundSettingsList.decode(buffer)
canJump = buffer.readExpression()
jumpVector = listOf(
buffer.readExpression(),
buffer.readExpression(),
buffer.readExpression()
)
speed = buffer.readExpression()
driveFactor = buffer.readExpression()
reverseDriveFactor = buffer.readExpression()
minimumSpeedToTurn = buffer.readExpression()
rotationSpeed = buffer.readExpression()
lookYawLimit = buffer.readExpression()
}
@@ -332,11 +479,36 @@ class VehicleSettings : RidingBehaviourSettings {
class VehicleState : RidingBehaviourState() {
var currSpeed = ridingState(0.0, Side.BOTH)
var deltaRotation = ridingState(Vec2.ZERO, Side.BOTH)
var drifting = ridingState(false, Side.CLIENT)
var poweredDrifting = ridingState(false, Side.CLIENT)
val turnMomentum: SidedRidingState<Float> = ridingState(0.0f, Side.CLIENT)
var inAir = ridingState(false, Side.CLIENT)
override fun reset() {
super.reset()
currSpeed.set(0.0, forced = true)
deltaRotation.set(Vec2.ZERO, forced = true)
drifting.set(false, forced = true)
poweredDrifting.set(false, forced = true)
turnMomentum.set(0.0f, forced = true)
inAir.set(false, forced = true)
}
override fun encode(buffer: FriendlyByteBuf) {
super.encode(buffer)
buffer.writeBoolean(drifting.get())
buffer.writeBoolean(poweredDrifting.get())
buffer.writeFloat(turnMomentum.get())
buffer.writeBoolean(inAir.get())
}
override fun decode(buffer: FriendlyByteBuf) {
super.decode(buffer)
drifting.set(buffer.readBoolean(), forced = true)
poweredDrifting.set(buffer.readBoolean(), forced = true)
turnMomentum.set(buffer.readFloat(), forced = true)
inAir.set(buffer.readBoolean(), forced = true)
}
override fun toString(): String {
@@ -348,5 +520,18 @@ class VehicleState : RidingBehaviourState() {
it.stamina.set(stamina.get(), forced = true)
it.currSpeed.set(currSpeed.get(), forced = true)
it.deltaRotation.set(deltaRotation.get(), forced = true)
it.drifting.set(drifting.get(), forced = true)
it.poweredDrifting.set(poweredDrifting.get(), forced = true)
it.turnMomentum.set(turnMomentum.get(), forced = true)
it.inAir.set(inAir.get(), forced = true)
}
override fun shouldSync(previous: RidingBehaviourState): Boolean {
if (previous !is VehicleState) return false
if (previous.drifting.get() != drifting.get()) return true
if (previous.poweredDrifting.get() != poweredDrifting.get()) return true
if (previous.turnMomentum.get() != turnMomentum.get()) return true
if (previous.inAir.get() != inAir.get()) return true
return super.shouldSync(previous)
}
}
@@ -14,6 +14,7 @@ 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
@@ -329,7 +330,7 @@ class BoatBehaviour : RidingBehaviour<BoatSettings, BoatState> {
return vehicle.runtime.resolveBoolean(settings.rotatePokemonHead)
}
override fun shouldRotatePlayerHead(
override fun shouldRotateRiderHead(
settings: BoatSettings,
state: BoatState,
vehicle: PokemonEntity
@@ -337,6 +338,14 @@ class BoatBehaviour : RidingBehaviour<BoatSettings, BoatState> {
return true
}
override fun getRideSounds(
settings: BoatSettings,
state: BoatState,
vehicle: PokemonEntity
): RideSoundSettingsList {
return settings.rideSounds
}
override fun maxUpStep(settings: BoatSettings, state: BoatState, vehicle: PokemonEntity) = 0f
override fun createDefaultState(settings: BoatSettings) = BoatState()
@@ -345,6 +354,7 @@ class BoatBehaviour : RidingBehaviour<BoatSettings, BoatState> {
class BoatSettings : RidingBehaviourSettings {
override val key = BoatBehaviour.KEY
var rideSounds: RideSoundSettingsList = RideSoundSettingsList()
var terminalVelocity = "-2.0".asExpression()
private set
@@ -354,10 +364,12 @@ class BoatSettings : RidingBehaviourSettings {
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
rideSounds.encode(buffer)
buffer.writeExpression(terminalVelocity)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
rideSounds = RideSoundSettingsList.decode(buffer)
terminalVelocity = buffer.readExpression()
}
}
@@ -14,6 +14,7 @@ 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.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.blockPositionsAsListRounded
@@ -214,7 +215,7 @@ class BurstBehaviour : RidingBehaviour<BurstSettings, BurstState> {
return false
}
override fun shouldRotatePlayerHead(
override fun shouldRotateRiderHead(
settings: BurstSettings,
state: BurstState,
vehicle: PokemonEntity
@@ -222,6 +223,14 @@ class BurstBehaviour : RidingBehaviour<BurstSettings, BurstState> {
return false
}
override fun getRideSounds(
settings: BurstSettings,
state: BurstState,
vehicle: PokemonEntity
): RideSoundSettingsList {
return settings.rideSounds
}
override fun createDefaultState(settings: BurstSettings) = BurstState()
}
@@ -231,12 +240,16 @@ class BurstSettings : RidingBehaviourSettings {
var dashSpeed = 1F
private set
var rideSounds: RideSoundSettingsList = RideSoundSettingsList()
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
rideSounds.encode(buffer)
buffer.writeFloat(dashSpeed)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
rideSounds = RideSoundSettingsList.decode(buffer)
dashSpeed = buffer.readFloat()
}
}
@@ -8,6 +8,7 @@
package com.cobblemon.mod.common.api.riding.behaviour.types.liquid
import com.bedrockk.molang.Expression
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.OrientationControllable
@@ -15,9 +16,11 @@ 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.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.*
import com.cobblemon.mod.common.util.math.geometry.toRadians
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.SmoothDouble
@@ -28,7 +31,9 @@ import net.minecraft.world.phys.Vec3
import net.minecraft.world.phys.shapes.Shapes
import kotlin.math.abs
import kotlin.math.cos
import kotlin.math.min
import kotlin.math.sin
import kotlin.math.sqrt
class DolphinBehaviour : RidingBehaviour<DolphinSettings, DolphinState> {
companion object {
@@ -42,7 +47,7 @@ class DolphinBehaviour : RidingBehaviour<DolphinSettings, DolphinState> {
}
val poseProvider = PoseProvider<DolphinSettings, DolphinState>(PoseType.FLOAT)
.with(PoseOption(PoseType.SWIM) { _, _, entity -> entity.entityData.get(PokemonEntity.MOVING) })
.with(PoseOption(PoseType.SWIM) { _, _, entity -> entity.deltaMovement.length() > 0.1 })
override fun isActive(settings: DolphinSettings, state: DolphinState, vehicle: PokemonEntity): Boolean {
return Shapes.create(vehicle.boundingBox).blockPositionsAsListRounded().any {
@@ -160,11 +165,59 @@ class DolphinBehaviour : RidingBehaviour<DolphinSettings, DolphinState> {
deltaTime: Double
): Vec3 {
if (driver !is OrientationControllable) return Vec3.ZERO
val controller = (driver as OrientationControllable).orientationController
//Might need to add the smoothing here for default.
//TODO:hook up handling
val handling = vehicle.runtime.resolveDouble(settings.handlingExpr)
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
//Interpret mouse input
val smoothingSpeed = 4.0
val invertRoll = if (Cobblemon.config.invertRoll) -1 else 1
val invertPitch = if (Cobblemon.config.invertPitch) -1 else 1
return Vec3(0.0, mouseY * invertPitch, mouseX * invertRoll)
val mouseXc = (mouseX).coerceIn(-60.0, 60.0)
val mouseYc = (mouseY).coerceIn(-60.0, 60.0)
val xInput = mouseXSmoother.getNewDeltaValue(mouseXc * 0.1 * invertRoll, deltaTime * smoothingSpeed);
val yInput = mouseYSmoother.getNewDeltaValue(mouseYc * 0.1 * invertPitch, deltaTime * smoothingSpeed);
val rollRot = xInput
val pitchRot = yInput
// Roll
// if (abs(controller.pitch + rollRot) >= 89.5 ) {
// mouseXSmoother.reset()
// } else {
//
// }
controller.rotateRoll(rollRot.toFloat())
// Yaw
// TODO:move all magic numbers to handling expression
val yawSpeed = handling * deltaTime * 0.25
val yawForce = sin(controller.roll.toRadians()) * yawSpeed * ( 1.0 - min(sqrt(normalizeVal(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())
// Pitch
if (abs(controller.pitch + pitchRot) >= 89.5 ) {
mouseYSmoother.reset()
} else {
//controller.applyGlobalPitch(pitchRot.toFloat() * -1.0f)
controller.rotatePitch(pitchRot.toFloat())
}
//yaw, pitch, roll
return Vec3.ZERO
}
/*
* Normalizes the given value between a min and a max.
* The result is clamped between 0.0 and 1.0, where 0.0 represents x is at or below min
* and 1.0 represents x is at or above it.
*/
private fun normalizeVal(x: Double, min: Double, max: Double): Double {
require(max > min) { "max must be greater than min" }
return ((x - min) / (max - min)).coerceIn(0.0, 1.0)
}
override fun canJump(
@@ -253,7 +306,7 @@ class DolphinBehaviour : RidingBehaviour<DolphinSettings, DolphinState> {
return false
}
override fun shouldRotatePlayerHead(
override fun shouldRotateRiderHead(
settings: DolphinSettings,
state: DolphinState,
vehicle: PokemonEntity
@@ -261,6 +314,14 @@ class DolphinBehaviour : RidingBehaviour<DolphinSettings, DolphinState> {
return false
}
override fun getRideSounds(
settings: DolphinSettings,
state: DolphinState,
vehicle: PokemonEntity
): RideSoundSettingsList {
return settings.rideSounds
}
override fun createDefaultState(settings: DolphinSettings) = DolphinState()
}
@@ -286,8 +347,17 @@ class DolphinSettings : RidingBehaviourSettings {
var strafeFactor = "0.2".asExpression()
private set
var jumpExpr: Expression = "q.get_ride_stats('JUMP', 'AIR', 200.0, 128.0)".asExpression()
var handlingExpr: Expression = "q.get_ride_stats('SKILL', 'AIR', 135.0, 45.0)".asExpression()
var speedExpr: Expression = "q.get_ride_stats('SPEED', 'AIR', 1.0, 0.35)".asExpression()
var accelerationExpr: Expression = "q.get_ride_stats('ACCELERATION', 'AIR', (1.0 / (20.0 * 3.0)), (1.0 / (20.0 * 8.0)))".asExpression()
var staminaExpr: Expression = "q.get_ride_stats('STAMINA', 'AIR', 120.0, 20.0)".asExpression()
var rideSounds: RideSoundSettingsList = RideSoundSettingsList()
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
rideSounds.encode(buffer)
buffer.writeExpression(canJump)
buffer.writeExpression(jumpVector[0])
buffer.writeExpression(jumpVector[1])
@@ -299,6 +369,7 @@ class DolphinSettings : RidingBehaviourSettings {
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
rideSounds = RideSoundSettingsList.decode(buffer)
canJump = buffer.readExpression()
jumpVector = listOf(
buffer.readExpression(),
@@ -0,0 +1,24 @@
/*
* 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.sound;
/**
* Enum of attenuations models for ride sounds
*
* @author Jackowes
* @since May 11th, 2025
*/
enum class RideAttenuationModel {
NONE,
LINEAR,
EXPONENTIAL
}
@@ -0,0 +1,190 @@
/*
* 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.sound
import com.cobblemon.mod.common.api.riding.behaviour.types.composite.CompositeBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.types.composite.CompositeState
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.resolveDouble
import net.minecraft.client.Minecraft
import net.minecraft.client.resources.sounds.AbstractTickableSoundInstance
import net.minecraft.client.resources.sounds.SoundInstance
import net.minecraft.resources.ResourceLocation
import net.minecraft.sounds.SoundEvent
import net.minecraft.sounds.SoundSource
import net.minecraft.util.Mth
import net.minecraft.util.Mth.lerp
import net.minecraft.world.level.ClipContext
import net.minecraft.world.phys.HitResult
import net.minecraft.world.phys.Vec3
import kotlin.math.max
import kotlin.math.min
import kotlin.math.pow
import kotlin.math.sin
/**
* Class to handle looping ride sounds for clients.
*
* @author Jackowes
* @since April 21st, 2025
*/
class RideLoopSound(val ride: PokemonEntity, val soundSettings: RideSoundSettings) :
AbstractTickableSoundInstance(SoundEvent.createVariableRangeEvent(soundSettings.soundLocation), SoundSource.NEUTRAL, SoundInstance.createUnseededRandom()) {
var shouldStop: Boolean = false
var fade: Float = 0.0f
var shouldMuffle: Boolean = soundSettings.muffleEnabled
var rideAttenuation: RideAttenuationModel = soundSettings.attenuationModel
var muffleAmount: Float = 1.0f // Currently slightly misleading. 1.0 is on muffle and 0.0 is full hFgain muting
// TODO: revisit below flag once multi passenger support is enabled
var isPassenger: Boolean = Minecraft.getInstance().player?.id == ride.controllingPassenger?.id
init {
this.looping = true
this.delay = 0
this.volume = 0.1f
this.attenuation = SoundInstance.Attenuation.LINEAR // This class performs its own attenuation
if (this.isPassenger) this.relative = true
}
override fun tick() {
// Check if the sound should fade in/out or stop
this.runFade()
// If the ride is out of attenuation distance then don't play
if (this.outOfRange()) {
this.volume = 0.0f
return
}
// Perform data driven pitch and volume calculation
this.volume = ((soundSettings.volumeExpr.let { ride.runtime.resolveDouble(it) })).toFloat()
this.volume *= this.fade
this.pitch = ((soundSettings.pitchExpr.let { ride.runtime.resolveDouble(it) })).toFloat()
if (!this.isPassenger && this.soundSettings.playForNonPassengers) {
this.setPos()
// This should now be done fully through openal. Add to RideAttneuationModel and the
// ChannelMixin to expand on the attenuation models.
// Attenuate due to distance
// this.volume *= attenuation()
// Calculate influence due to the doppler effect
this.pitch *= calcDopplerInfluence().toFloat()
// Calculate muffling due to occlusion
val soundOc = soundOcclusion()
if (soundOc != 0.0f) {
val newMuffle = lerp(soundOc,1.0f, 0.2f).coerceIn(0.2f,1.0f)
// lerp muffle smoothly
this.muffleAmount = lerp(0.1f, this.muffleAmount, newMuffle)
this.shouldMuffle = true
this.volume *= muffleAmount
} else {
this.shouldMuffle = true
this.muffleAmount = lerp(0.1f, this.muffleAmount, 1.0f)
this.volume *= muffleAmount
}
}
}
fun runFade() {
if (this.shouldStop && this.fade == 0.0f) {
this.stop()
} else if(this.shouldStop) {
this.fade = max(0.0f, this.fade - 0.05f)
} else if(!this.shouldStop && this.fade != 1.0f){
this.fade = min(1.0f, this.fade + 0.05f)
}
}
fun outOfRange(): Boolean {
val attenDist = this.sound.attenuationDistance
val listenerPosition = Minecraft.getInstance().player?.eyePosition ?: return false
return listenerPosition.distanceTo(ride.position()) > attenDist
}
fun calcDopplerInfluence(): Double {
val rideVel = ride.getRideVelocity()
// TODO: Expand this to multiple players
val listener = Minecraft.getInstance().player ?: return 1.0
val listenerVel = listener.deltaMovement//listener.position().subtract(Vec3(listener.xOld, listener.yOld, listener.zOld))
// Tweaked constant based off speed of sound in room temp air. Exaggerated for a better effect in minecraft
val waveSpeed = 343.0f / 50.0f
// listener->ride los velocity
val lineOfSightToRide = ride.position().subtract(listener.position()).normalize()
//val lineOfSightToPlayer = listener.position().subtract(ride.position()).normalize()
// Speed of the observer towards the sound source
val velObserver = listenerVel.dot(lineOfSightToRide)
// Speed of the sound source towards the observer
val velSrc = rideVel.dot(lineOfSightToRide.scale(-1.0))
// Use the doppler effect equation to determine the pitch change
val pitchFactor = (waveSpeed + velObserver) / (waveSpeed - velSrc)
return pitchFactor
}
fun soundOcclusion(): Float {
val listener = Minecraft.getInstance().player ?: return 0.0f
val level = listener.level()
val maxAttenDist = this.sound.attenuationDistance
var totalMuffle = 0.0
val center = ride.boundingBox.center
val hit = level.clip(
ClipContext(
listener.eyePosition,
center,
ClipContext.Block.COLLIDER,
ClipContext.Fluid.NONE,
listener
)
)
if(hit.type != HitResult.Type.MISS) {
//Calculate the distance the hit was away from the listener
val hitDist = hit.location.distanceTo(listener.eyePosition)
//Calculate the influence based on distance of this particular ray hit
val hitMuffle = lerp((1 - (hitDist / maxAttenDist)), 0.3, 1.0)
totalMuffle += hitMuffle
}
return totalMuffle.toFloat()
}
// fun attenuation(): Float {
// val listener = Minecraft.getInstance().player ?: return 1.0f
// val soundDistance = (ride.position().subtract(listener.eyePosition)).length()
// val maxAttenDistance = this.sound.attenuationDistance
//
// return (1 - (soundDistance / maxAttenDistance).pow(2)).toFloat()
// }
fun setPos() {
this.x = ride.x
this.y = ride.y
this.z = ride.z
}
fun stopSound() {
this.shouldStop = true
//Minecraft.getInstance().soundManager.stop(this)
}
}
@@ -0,0 +1,77 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.riding.sound
import com.cobblemon.mod.common.api.riding.behaviour.types.composite.CompositeBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.types.composite.CompositeState
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.client.Minecraft
import net.minecraft.resources.ResourceLocation
/**
* Class to control playing all ride sounds
*
* @author Jackowes
* @since April 26th, 2025
*/
class RideSoundManager(
private val ride: PokemonEntity
) {
private val activeSounds = mutableListOf<RideLoopSound>()
private var rideActive: Boolean = false
private var currKey: ResourceLocation = cobblemonResource("no_key")
fun tick() {
val currActive = ride.hasControllingPassenger()
if( !currActive && !this.rideActive) return
// Remove stopped sounds
activeSounds.removeIf { it.isStopped}
if (!currActive && this.rideActive) {
this.rideActive = false
this.currKey = cobblemonResource("no_key")
this.stop()
} else if (currActive && !this.rideActive) {
this.rideActive = true
this.currKey = getActiveRideKey()
this.getBehaviourSounds()
} else if (currActive && this.rideActive && this.currKey != getActiveRideKey()) {
this.currKey = getActiveRideKey()
this.stop()
this.getBehaviourSounds()
}
}
fun getBehaviourSounds() {
val rideSounds = ride.ifRidingAvailableSupply(null) { behaviour, settings, state ->
behaviour.getRideSounds(settings, state, ride)
}
rideSounds?.sounds?.forEach {
val loopingRideSound = RideLoopSound(ride,it)
activeSounds.add(loopingRideSound)
Minecraft.getInstance().soundManager.play(loopingRideSound)
}
}
fun getActiveRideKey(): ResourceLocation {
val key = ride.ridingBehaviourSettings?.key ?: cobblemonResource("no_key")
if (key == CompositeBehaviour.KEY) {
return (ride.ridingState as? CompositeState)?.activeBehaviour?.get() ?: cobblemonResource("no_key")
} else {
return key
}
}
fun stop() {
activeSounds.forEach { it.stopSound() }
}
}
@@ -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.api.riding.sound;
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.api.net.Encodable
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.readExpression
import com.cobblemon.mod.common.util.writeExpression
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation;
/**
* Class to store data for looping sounds played during riding.
*
* @author Jackowes
* @since April 26th, 2025
*/
data class RideSoundSettings(
val soundLocation: ResourceLocation,
val volumeExpr: Expression = "1.0".asExpression(),
val pitchExpr: Expression = "1.0".asExpression(),
val playForNonPassengers: Boolean = false,
val muffleEnabled: Boolean = false,
val attenuationModel: RideAttenuationModel = RideAttenuationModel.NONE
): Encodable {
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(soundLocation)
buffer.writeExpression(volumeExpr)
buffer.writeExpression(pitchExpr)
buffer.writeBoolean(playForNonPassengers)
buffer.writeBoolean(muffleEnabled)
}
companion object {
fun decode(buffer: RegistryFriendlyByteBuf): RideSoundSettings {
// Try to read if available, fallback to default otherwise
val soundLocation = buffer.readResourceLocation()
val volumeExpr = buffer.readExpression()
val pitchExpr = buffer.readExpression()
val playForNonPassengers = buffer.readBoolean()
val muffleEnabled = buffer.readBoolean()
val attenuationModel = RideAttenuationModel.EXPONENTIAL
return RideSoundSettings(
soundLocation,
volumeExpr,
pitchExpr,
playForNonPassengers,
muffleEnabled,
attenuationModel
)
}
}
}
class RideSoundSettingsList(var sounds: List<RideSoundSettings> = mutableListOf()) {
fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeCollection(sounds) { _, sound -> sound.encode(buffer) }
}
companion object {
fun decode(buffer: RegistryFriendlyByteBuf): RideSoundSettingsList {
val list = RideSoundSettingsList()
list.sounds = buffer.readList { RideSoundSettings.decode(buffer) }.toMutableList()
return list
}
}
}
@@ -16,6 +16,7 @@ import net.minecraft.world.phys.Vec3
import org.joml.Matrix3f
import org.joml.Quaternionf
import org.joml.Vector3f
import kotlin.math.sign
/**
* Class to handle the updating and containment of ride data used in animation.
@@ -25,7 +26,7 @@ import org.joml.Vector3f
*/
class RidingAnimationData
class RidingAnimationData(val ride: PokemonEntity)
{
var prevOrientation: Matrix3f = Matrix3f()
var prevRot: Vec3 = Vec3.ZERO
@@ -33,30 +34,32 @@ class RidingAnimationData
var rotSpring: Vec3Spring = Vec3Spring()
var rotDeltaSpring: Vec3Spring = Vec3Spring()
var localVelocitySpring: Vec3Spring = Vec3Spring()
var driverInputSpring: Vec3Spring = Vec3Spring()
fun update(entity: PokemonEntity) {
fun update() {
val activeRide = ride.hasControllingPassenger()
if (!activeRide) return
var currRot = Vec3.ZERO
var angDelta = Vec3.ZERO
val stiffness = 90.0
val damping = 18.0
// val stiffness = 16.0
// val damping = 8.0
// Update velocity first
val currentVelocity = Vec3(
entity.x - entity.xOld,
entity.y - entity.yOld,
entity.z - entity.zOld
ride.x - ride.xOld,
ride.y - ride.yOld,
ride.z - ride.zOld
)
val r = entity.ifRidingAvailableSupply(false) { behaviour, settings, state ->
behaviour.shouldRoll(settings, state, entity);
val r = ride.ifRidingAvailableSupply(false) { behaviour, settings, state ->
behaviour.shouldRoll(settings, state, ride);
}
if( r ) {
//TODO: should this return?
val controller = (entity.firstPassenger as? OrientationControllable)?.orientationController ?: return
val controller = (ride.firstPassenger as? OrientationControllable)?.orientationController ?: return
val orientation = controller.orientation ?: Matrix3f()
val currPitch = controller.pitch
@@ -105,8 +108,8 @@ class RidingAnimationData
// Update previous values
prevOrientation.set(currOrientation)
} else {
val currPitch = entity.xRot
val currYaw = entity.yRot
val currPitch = ride.xRot
val currYaw = ride.yRot
val currRoll = 0.0f
currRot = Vec3(currPitch.toDouble(), currYaw.toDouble(), currRoll.toDouble())
@@ -120,7 +123,7 @@ class RidingAnimationData
/******************************************************
* Calculate ride local velocity
*****************************************************/
val yaw360 = -1.0 * (entity.yRot + 360.0f) % 360.0f
val yaw360 = -1.0 * (ride.yRot + 360.0f) % 360.0f
val orientationNonRollable = Matrix3f().rotateY(yaw360.toRadians())
//Convert to Vector3f
@@ -135,6 +138,14 @@ class RidingAnimationData
/******************************************************
* Update springs
*****************************************************/
val driver = ride.controllingPassenger
val vertInput = when {
driver != null && driver.jumping-> 1.0
driver != null && driver.isShiftKeyDown -> -1.0
else -> 0.0
}
val input = if(driver != null) Vec3(driver.xxa.toDouble() * -1, vertInput, driver.zza.toDouble()) else Vec3.ZERO
driverInputSpring.update(input, stiffness * 2, damping)
rotDeltaSpring.update(angDelta, stiffness, damping)
velocitySpring.update(currentVelocity, stiffness, damping)
rotSpring.update(currRot, stiffness, damping)
@@ -8,7 +8,6 @@
package com.cobblemon.mod.common.api.storage
import com.cobblemon.mod.common.api.reactive.Observable.Companion.stopAfter
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.DataKeys
@@ -45,9 +44,6 @@ open class BottomlessStore(override val uuid: UUID) : PokemonStore<BottomlessPos
override fun initialize() {
pokemon.forEachIndexed { index, pokemon ->
pokemon.storeCoordinates.set(StoreCoordinates(this, BottomlessPosition(index)))
pokemon.changeObservable.pipe(
stopAfter { pokemon.storeCoordinates.get()?.store != this }
).subscribe { storeChangeObservable.emit(Unit) }
}
}
@@ -116,4 +112,8 @@ open class BottomlessStore(override val uuid: UUID) : PokemonStore<BottomlessPos
storeChangeObservable.emit(Unit)
}
}
override fun onPokemonChanged(pokemon: Pokemon) {
this.storeChangeObservable.emit(Unit)
}
}
@@ -46,6 +46,8 @@ abstract class PokemonStore<T : StorePosition> : Iterable<Pokemon> {
abstract fun getObservingPlayers(): Iterable<ServerPlayer>
/** Sends the contents of this store to a player as if they've never seen it before. This initializes the store then sends each contained Pokémon. */
abstract fun sendTo(player: ServerPlayer)
/** Notifies the store that the provided Pokémon has changed in some way that would require persisting. */
abstract fun onPokemonChanged(pokemon: Pokemon)
/**
* Runs initialization logic for this store, knowing that it has just been constructed in a [PokemonStoreFactory].
@@ -11,7 +11,6 @@ package com.cobblemon.mod.common.api.storage.party
import com.cobblemon.mod.common.CobblemonNetwork.sendPacket
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.api.reactive.Observable
import com.cobblemon.mod.common.api.reactive.Observable.Companion.stopAfter
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.api.storage.InvalidSpeciesException
import com.cobblemon.mod.common.api.storage.PokemonStore
@@ -62,22 +61,10 @@ open class PartyStore(override val uuid: UUID) : PokemonStore<PartyPosition>() {
throw IllegalArgumentException("Slot position is out of bounds")
} else {
slots[position.slot] = pokemon
if (pokemon != null) {
if (pokemon.storeCoordinates.get()?.store != this) {
// It's new to this store. Attach the listener
trackPokemon(pokemon)
}
}
anyChangeObservable.emit(Unit)
}
}
fun trackPokemon(pokemon: Pokemon) {
pokemon.changeObservable
.pipe(stopAfter { pokemon.storeCoordinates.get()?.store != this })
.subscribe { anyChangeObservable.emit(Unit) }
}
override fun getFirstAvailablePosition(): PartyPosition? {
for (i in slots.indices) {
if (slots[i] == null) {
@@ -148,10 +135,13 @@ open class PartyStore(override val uuid: UUID) : PokemonStore<PartyPosition>() {
for (slot in slots.indices) {
val pokemon = get(slot) ?: continue
pokemon.storeCoordinates.set(StoreCoordinates(this, PartyPosition(slot)))
trackPokemon(pokemon)
}
}
override fun onPokemonChanged(pokemon: Pokemon) {
anyChangeObservable.emit(Unit)
}
fun toGappyList() = slots.toList()
/** Maps the slots of the party using the giving mapper function, but preserving the nulls in the party at the right spots. */
@@ -62,14 +62,7 @@ open class PCBox(val pc: PCStore) : Iterable<Pokemon> {
open operator fun set(index: Int, pokemon: Pokemon?) {
if (index in 0 until POKEMON_PER_BOX) {
this.pokemon[index] = pokemon
if (pokemon != null) {
val previousCoordinates = pokemon.storeCoordinates.get()
val position = previousCoordinates?.position
pokemon.storeCoordinates.set(StoreCoordinates(pc, PCPosition(boxNumber, index)))
if (previousCoordinates?.store !is PCStore || previousCoordinates.store.uuid != pc.uuid || (position as PCPosition).box != boxNumber) {
trackPokemon(pokemon)
}
}
pokemon?.storeCoordinates?.set(StoreCoordinates(pc, PCPosition(boxNumber, index)))
if (emit) {
boxChangeEmitter.emit(Unit)
}
@@ -97,7 +90,6 @@ open class PCBox(val pc: PCStore) : Iterable<Pokemon> {
if (pokemon != null) {
val position = PCPosition(box, slot)
pokemon.storeCoordinates.set(StoreCoordinates(pc, position))
trackPokemon(pokemon)
}
}
boxChangeEmitter.subscribe { pc.pcChangeObservable.emit(Unit) }
@@ -108,17 +100,7 @@ open class PCBox(val pc: PCStore) : Iterable<Pokemon> {
pokemon.forEachIndexed { slot, pokemon ->
pokemon?.storeCoordinates?.set(StoreCoordinates(pc, PCPosition(boxNumber, slot)))
}
}
fun trackPokemon(pokemon: Pokemon) {
pokemon.changeObservable
.pipe(
stopAfter {
val coordinates = it.storeCoordinates.get() ?: return@stopAfter true
return@stopAfter coordinates.store !is PCStore || coordinates.store.uuid != pc.uuid || (coordinates.position as PCPosition).box != boxNumber
}
)
.subscribe { boxChangeEmitter.emit(Unit) }
boxChangeEmitter.emit(Unit)
}
fun sendTo(player: ServerPlayer) {
@@ -284,6 +284,10 @@ open class PCStore(
}
}
override fun onPokemonChanged(pokemon: Pokemon) {
pcChangeObservable.emit(Unit)
}
fun clearPC() {
boxes.forEach { box ->
box.getNonEmptySlots().forEach{
@@ -21,126 +21,126 @@ object ElementalTypes {
val NORMAL = register(
name = "Normal",
displayName = Component.translatable("cobblemon.type.normal"),
hue = 0xDDDDCF,
hue = 0xE8E8DA,
textureXMultiplier = 0
)
val FIRE = register(
name = "Fire",
displayName = Component.translatable("cobblemon.type.fire"),
hue = 0xE55C32,
hue = 0xFF6E21,
textureXMultiplier = 1
)
val WATER = register(
name = "Water",
displayName = Component.translatable("cobblemon.type.water"),
hue = 0x4A9BE8,
hue = 0x3FA5FF,
textureXMultiplier = 2
)
val GRASS = register(
name = "Grass",
displayName = Component.translatable("cobblemon.type.grass"),
hue = 0x4DBC3C,
hue = 0x62D14F,
textureXMultiplier = 3
)
val ELECTRIC = register(
name = "Electric",
displayName = Component.translatable("cobblemon.type.electric"),
hue = 0xEFD128,
hue = 0xFFD314,
textureXMultiplier = 4
)
val ICE = register(
name = "Ice",
displayName = Component.translatable("cobblemon.type.ice"),
hue = 0x6BC3EF,
hue = 0x54F2F2,
textureXMultiplier = 5
)
val FIGHTING = register(
name = "Fighting",
displayName = Component.translatable("cobblemon.type.fighting"),
hue = 0xC44C5C,
hue = 0xEF565D,
textureXMultiplier = 6
)
val POISON = register(
name = "Poison",
displayName = Component.translatable("cobblemon.type.poison"),
hue = 0xA24BD8,
hue = 0xD651FF,
textureXMultiplier = 7
)
val GROUND = register(
name = "Ground",
displayName = Component.translatable("cobblemon.type.ground"),
hue = 0xD89950,
hue = 0xF4A453,
textureXMultiplier = 8
)
val FLYING = register(
name = "Flying",
displayName = Component.translatable("cobblemon.type.flying"),
hue = 0xBCC1FF,
hue = 0xB8B2FF,
textureXMultiplier = 9
)
val PSYCHIC = register(
name = "Psychic",
displayName = Component.translatable("cobblemon.type.psychic"),
hue = 0xD86AD6,
hue = 0xFF5E9E,
textureXMultiplier = 10
)
val BUG = register(
name = "Bug",
displayName = Component.translatable("cobblemon.type.bug"),
hue = 0xA2C831,
hue = 0xD3D319,
textureXMultiplier = 11
)
val ROCK = register(
name = "Rock",
displayName = Component.translatable("cobblemon.type.rock"),
hue = 0xAA9666,
hue = 0xB7A16E,
textureXMultiplier = 12
)
val GHOST = register(
name = "Ghost",
displayName = Component.translatable("cobblemon.type.ghost"),
hue = 0x9572E5,
hue = 0x9C80F7,
textureXMultiplier = 13
)
val DRAGON = register(
name = "Dragon",
displayName = Component.translatable("cobblemon.type.dragon"),
hue = 0x535DE8,
hue = 0x7580FF,
textureXMultiplier = 14
)
val DARK = register(
name = "Dark",
displayName = Component.translatable("cobblemon.type.dark"),
hue = 0x5C6CB2,
hue = 0x587DA0,
textureXMultiplier = 15
)
val STEEL = register(
name = "Steel",
displayName = Component.translatable("cobblemon.type.steel"),
hue = 0xC3CCE0,
hue = 0xABD1F4,
textureXMultiplier = 16
)
val FAIRY = register(
name = "Fairy",
displayName = Component.translatable("cobblemon.type.fairy"),
hue = 0xEA727E,
hue = 0xFF7FE5,
textureXMultiplier = 17
)
@@ -11,6 +11,7 @@ package com.cobblemon.mod.common.battles.interpreter.instructions
import com.bedrockk.molang.runtime.MoLangRuntime
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.molang.MoLangFunctions.addBattleMessageFunctions
import com.cobblemon.mod.common.api.moves.Moves
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
import com.cobblemon.mod.common.api.moves.animations.ActionEffects
@@ -41,6 +42,11 @@ class ActivateInstruction(val instructionSet: InstructionSet, val message: Battl
override var holds = mutableSetOf<String>()
override val id = cobblemonResource("activate")
override fun addMolangQueries(runtime: MoLangRuntime) {
super.addMolangQueries(runtime)
runtime.environment.query.addBattleMessageFunctions(message)
}
override fun preActionEffect(battle: PokemonBattle) {
val pokemon = message.battlePokemon(0, battle) ?: return
val effect = message.effectAt(1) ?: return
@@ -13,6 +13,7 @@ import com.cobblemon.mod.common.api.battles.interpreter.BattleContext
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.interpreter.InvalidInstructionException
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.molang.MoLangFunctions.addBattleMessageFunctions
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
import com.cobblemon.mod.common.api.moves.animations.ActionEffects
import com.cobblemon.mod.common.api.moves.animations.UsersProvider
@@ -43,6 +44,11 @@ class BoostInstruction(battle: PokemonBattle, val message: BattleMessage, val is
val stages = message.argumentAt(2)?.toInt() ?: throw InvalidInstructionException(message)
val stat = Stats.getStat(statKey).displayName
override fun addMolangQueries(runtime: MoLangRuntime) {
super.addMolangQueries(runtime)
runtime.environment.query.addBattleMessageFunctions(message)
}
override fun preActionEffect(battle: PokemonBattle) {
}
@@ -11,6 +11,7 @@ package com.cobblemon.mod.common.battles.interpreter.instructions
import com.bedrockk.molang.runtime.MoLangRuntime
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.molang.MoLangFunctions.addBattleMessageFunctions
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
import com.cobblemon.mod.common.api.moves.animations.UsersProvider
import com.cobblemon.mod.common.api.pokemon.status.Statuses
@@ -35,6 +36,12 @@ class CantInstruction(val message: BattleMessage): ActionEffectInstruction {
override var future: CompletableFuture<*> = CompletableFuture.completedFuture(Unit)
override var holds = mutableSetOf<String>()
override val id = cobblemonResource("cant")
override fun addMolangQueries(runtime: MoLangRuntime) {
super.addMolangQueries(runtime)
runtime.environment.query.addBattleMessageFunctions(message)
}
override fun preActionEffect(battle: PokemonBattle) {
}
@@ -25,7 +25,6 @@ import com.cobblemon.mod.common.battles.dispatch.UntilDispatch
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.net.messages.client.animation.PlayPosableAnimationPacket
import com.cobblemon.mod.common.net.messages.client.battle.BattleHealthChangePacket
import com.cobblemon.mod.common.net.messages.client.effect.RunPosableMoLangPacket
import com.cobblemon.mod.common.pokemon.evolution.progress.DamageTakenEvolutionProgress
import com.cobblemon.mod.common.pokemon.evolution.progress.RecoilEvolutionProgress
import com.cobblemon.mod.common.pokemon.status.statuses.persistent.PoisonStatus
@@ -103,10 +102,18 @@ class DamageInstruction(
status = pokemon.effectedPokemon.status?.status ?: status
}
// Damage effects without a action effect receive the generic damage effect
val actionEffect = status?.getActionEffect() ?: effect?.let {
ActionEffects.actionEffects["generic_damage".asIdentifierDefaultingNamespace()]
} ?: return@dispatch GO
// Damage inflicted by an effect (not just a move) will potentially get its own action effect.
// We don't play an effect at all if there's no effect on the damage because we anticipate that
// the move's action effect will handle that. Status action effects are specially defined (we could
// migrate this back to damage_{status} later tbh) but then it will try damage_{status} and failing
// that then we should try to show something about the fact that some kind of effect damage is being applied.
val actionEffect = status?.getActionEffect()
?: effect?.let {
ActionEffects.actionEffects["damage_${it.id}".asIdentifierDefaultingNamespace()]
?: ActionEffects.actionEffects["generic_damage".asIdentifierDefaultingNamespace()]
}
?: return@dispatch GO // will occur if the damage is from a move
val providers = mutableListOf<Any>(battle)
battlePokemon.effectedPokemon.entity?.let { UsersProvider(it) }?.let(providers::add)
@@ -210,7 +217,7 @@ class DamageInstruction(
} else if (causedFaint) {
GO
} else {
UntilDispatch { "effects" !in holds}
UntilDispatch { "effects" !in holds }
}
}
}
@@ -21,11 +21,11 @@ import com.cobblemon.mod.common.util.battleLang
* @since December 30th, 2022
*/
class HitCountInstruction(val message: BattleMessage): InterpreterInstruction {
val hitCount = message.argumentAt(1)?.toIntOrNull()
override fun invoke(battle: PokemonBattle) {
battle.dispatchGo {
val battlePokemon = message.battlePokemon(0, battle) ?: return@dispatchGo
val hitCount = message.argumentAt(1)?.toIntOrNull() ?: return@dispatchGo
val hitCount = hitCount ?: return@dispatchGo
val lang = if (hitCount == 1) battleLang("hit_count_singular") else battleLang("hit_count", hitCount)
battle.minorBattleActions[battlePokemon.uuid] = message
battle.broadcastChatMessage(lang)
@@ -15,7 +15,9 @@ import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.interpreter.Effect
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.molang.MoLangFunctions.addBattleMessageFunctions
import com.cobblemon.mod.common.api.molang.MoLangFunctions.addStandardFunctions
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
import com.cobblemon.mod.common.api.moves.Moves
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
import com.cobblemon.mod.common.api.moves.animations.TargetsProvider
@@ -28,9 +30,11 @@ import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.battles.dispatch.UntilDispatch
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.pokemon.evolution.progress.UseMoveEvolutionProgress
import com.cobblemon.mod.common.util.asArrayValue
import com.cobblemon.mod.common.util.battleLang
import com.cobblemon.mod.common.util.cobblemonResource
import java.util.concurrent.CompletableFuture
import net.minecraft.world.entity.LivingEntity
/**
* Format: |move|POKEMON|MOVE|TARGET
@@ -93,15 +97,27 @@ class MoveInstruction(
battle.broadcastChatMessage(lang)
battle.majorBattleActions[userPokemon.uuid] = message
val providers = mutableListOf<Any>(battle)
userPokemon.effectedPokemon.entity?.let { UsersProvider(it) }?.let(providers::add)
if (spreadTargetPokemon.isNotEmpty()) {
providers.add(TargetsProvider(spreadTargetPokemon.filter { it?.effectedPokemon?.entity != null}.mapNotNull { spreadTarget -> spreadTarget?.effectedPokemon?.entity }))
} else {
targetPokemon?.effectedPokemon?.entity?.let { TargetsProvider(it) }?.let(providers::add)
}
val runtime = MoLangRuntime().also {
battle.addQueryFunctions(it.environment.query).addStandardFunctions()
it.environment.query.addBattleMessageFunctions(message)
}
val providers = mutableListOf<Any>(battle)
userPokemon.effectedPokemon.entity?.let { entity ->
runtime.environment.query.addFunction("user") { entity.asMostSpecificMoLangValue()}
UsersProvider(entity)
}?.let(providers::add)
if (spreadTargetPokemon.isNotEmpty()) {
val targetEntities = spreadTargetPokemon.mapNotNull { it?.effectedPokemon?.entity }
runtime.environment.query.addFunction("targets") { targetEntities.asArrayValue { it.asMostSpecificMoLangValue() } }
providers.add(TargetsProvider(targetEntities))
} else {
targetPokemon?.effectedPokemon?.entity?.let { entity ->
runtime.environment.query.addFunction("target") { entity.asMostSpecificMoLangValue() }
val provider = TargetsProvider(entity)
providers.add(provider)
}
}
actionEffect ?: return@dispatch GO
@@ -114,6 +130,7 @@ class MoveInstruction(
val subsequentInstructions = instructionSet.findInstructionsCausedBy(this)
val missedTargets = subsequentInstructions.filterIsInstance<MissInstruction>().mapNotNull { it.target }
val hitCountInstruction = subsequentInstructions.filterIsInstance<HitCountInstruction>().firstOrNull()
runtime.environment.query.addFunction("missed") { params ->
if (params.params.size == 0) {
@@ -134,6 +151,10 @@ class MoveInstruction(
}
}
if (hitCountInstruction != null && hitCountInstruction.hitCount != null) {
runtime.environment.query.addFunction("hit_count") { DoubleValue(hitCountInstruction.hitCount.toDouble()) }
}
runtime.environment.query.addFunction("move") { move.struct }
runtime.environment.query.addFunction("instruction_id") { StringValue(cobblemonResource("move").toString()) }
@@ -8,10 +8,26 @@
package com.cobblemon.mod.common.battles.interpreter.instructions
import com.bedrockk.molang.runtime.MoLangRuntime
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.molang.MoLangFunctions.addBattleMessageFunctions
import com.cobblemon.mod.common.api.molang.MoLangFunctions.addFunctions
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
import com.cobblemon.mod.common.api.moves.animations.ActionEffects
import com.cobblemon.mod.common.api.moves.animations.UsersProvider
import com.cobblemon.mod.common.api.pokemon.status.Statuses
import com.cobblemon.mod.common.battles.ShowdownInterpreter
import com.cobblemon.mod.common.battles.dispatch.ActionEffectInstruction
import com.cobblemon.mod.common.battles.dispatch.GO
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.battles.dispatch.UntilDispatch
import com.cobblemon.mod.common.pokemon.status.statuses.persistent.PoisonStatus
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import com.cobblemon.mod.common.util.battleLang
import com.cobblemon.mod.common.util.cobblemonResource
import java.util.concurrent.CompletableFuture
import net.minecraft.resources.ResourceLocation
/**
* Format: |-prepare|ATTACKER|MOVE and |-prepare|ATTACKER|MOVE|DEFENDER
@@ -20,13 +36,56 @@ import com.cobblemon.mod.common.util.battleLang
* @author Renaissance
* @since March 24th, 2023
*/
class PrepareInstruction(val message: BattleMessage): InterpreterInstruction {
class PrepareInstruction(val message: BattleMessage): ActionEffectInstruction {
override var future: CompletableFuture<*> = CompletableFuture.completedFuture(Unit)
override var holds = mutableSetOf<String>()
override val id = cobblemonResource("prepare")
override fun invoke(battle: PokemonBattle) {
battle.dispatchWaiting(1.5F) {
val pokemon = message.battlePokemon(0, battle) ?: return@dispatchWaiting
override fun addMolangQueries(runtime: MoLangRuntime) {
super.addMolangQueries(runtime)
runtime.environment.query.addBattleMessageFunctions(message)
}
override fun preActionEffect(battle: PokemonBattle) {
val pokemon = message.battlePokemon(0, battle) ?: return
val effect = message.effectAt(1) ?: return
ShowdownInterpreter.broadcastOptionalAbility(battle, effect, pokemon)
battle.dispatch{
ShowdownInterpreter.lastCauser[battle.battleId] = message
battle.minorBattleActions[pokemon.uuid] = message
GO
}
}
override fun runActionEffect(battle: PokemonBattle, runtime: MoLangRuntime) {
val effect = message.effectAt(1)
val battlePokemon = message.battlePokemon(0, battle) ?: return
battle.dispatch {
val actionEffect = effect?.let { ActionEffects.actionEffects["prepare_${it.id}".asIdentifierDefaultingNamespace()] }
?: return@dispatch GO // not likely
val providers = mutableListOf<Any>(battle)
battlePokemon.effectedPokemon.entity?.let { UsersProvider(it) }?.let(providers::add)
val context = ActionEffectContext(
actionEffect = actionEffect,
runtime = runtime,
providers = providers,
level = battle.players.firstOrNull()?.level()
)
this.future = actionEffect.run(context)
holds = context.holds // Reference so future things can check on this action effect's holds
future.thenApply { holds.clear() }
return@dispatch GO
}
}
override fun postActionEffect(battle: PokemonBattle) {
battle.dispatch {
val pokemon = message.battlePokemon(0, battle) ?: return@dispatch GO
val pokemonName = pokemon.getName()
val effectID = message.effectAt(1)?.id ?: return@dispatchWaiting
val effectID = message.effectAt(1)?.id ?: return@dispatch GO
//Prevents spam when the move Role Play is used
val lang = when (effectID) {
"shadowforce" -> battleLang("prepare.phantomforce", pokemonName) //Phantom Force and Shadow Force share the same text
@@ -35,6 +94,7 @@ class PrepareInstruction(val message: BattleMessage): InterpreterInstruction {
}
battle.broadcastChatMessage(lang)
battle.minorBattleActions[pokemon.uuid] = message
UntilDispatch { "effects" !in holds }
}
}
}
@@ -21,6 +21,7 @@ import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.BaseEntityBlock
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.HorizontalDirectionalBlock
import net.minecraft.world.level.block.Rotation
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.StateDefinition
@@ -79,7 +80,12 @@ class MonitorBlock(settings: Properties) : MultiblockBlock(settings) {
pos: BlockPos,
collisionContext: CollisionContext
): VoxelShape {
return HITBOX
return when (state.getValue(HorizontalDirectionalBlock.FACING)) {
Direction.WEST -> HITBOX_WEST
Direction.EAST -> HITBOX_EAST
Direction.SOUTH -> HITBOX_SOUTH
else -> HITBOX_NORTH
}
}
@Deprecated("Deprecated in Java")
@@ -97,13 +103,34 @@ class MonitorBlock(settings: Properties) : MultiblockBlock(settings) {
//0 is off
val SCREEN = EnumProperty.create("screen", MonitorScreen::class.java)
val HITBOX = Shapes.or(
val HITBOX_SOUTH = Shapes.or(
Shapes.box(0.0625, 0.0, 0.0625, 0.9375, 0.375, 0.9375),
Shapes.box(0.0625, 0.875, 0.0625, 0.9375, 1.0, 0.9375),
Shapes.box(0.8125, 0.375, 0.0625, 0.9375, 0.875, 0.9375),
Shapes.box(0.1875, 0.375, 0.125, 0.8125, 0.875, 0.9375),
Shapes.box(0.0625, 0.375, 0.0625, 0.1875, 0.875, 0.9375)
)
val HITBOX_NORTH = Shapes.or(
Shapes.box(0.0625, 0.0, 0.0625, 0.9375, 0.375, 0.9375),
Shapes.box(0.0625, 0.875, 0.0625, 0.9375, 1.0, 0.9375),
Shapes.box(0.0625, 0.375, 0.0625, 0.1875, 0.875, 0.9375),
Shapes.box(0.1875, 0.375, 0.0625, 0.8125, 0.875, 0.875),
Shapes.box(0.8125, 0.375, 0.0625, 0.9375, 0.875, 0.9375)
)
val HITBOX_EAST = Shapes.or(
Shapes.box(0.0625, 0.0, 0.0625, 0.9375, 0.375, 0.9375),
Shapes.box(0.0625, 0.875, 0.0625, 0.9375, 1.0, 0.9375),
Shapes.box(0.0625, 0.375, 0.0625, 0.9375, 0.875, 0.1875),
Shapes.box(0.125, 0.375, 0.1875, 0.9375, 0.875, 0.8125),
Shapes.box(0.0625, 0.375, 0.8125, 0.9375, 0.875, 0.9375)
)
val HITBOX_WEST = Shapes.or(
Shapes.box(0.0625, 0.0, 0.0625, 0.9375, 0.375, 0.9375),
Shapes.box(0.0625, 0.875, 0.0625, 0.9375, 1.0, 0.9375),
Shapes.box(0.0625, 0.375, 0.8125, 0.9375, 0.875, 0.9375),
Shapes.box(0.0625, 0.375, 0.1875, 0.875, 0.875, 0.8125),
Shapes.box(0.0625, 0.375, 0.0625, 0.9375, 0.875, 0.1875)
)
}
enum class MonitorScreen : StringRepresentable {
OFF,
@@ -19,7 +19,15 @@ import com.cobblemon.mod.common.api.molang.ObjectValue
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.client.render.models.blockbench.ExcludedLabels
import com.cobblemon.mod.common.client.render.models.blockbench.PosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.animation.*
import com.cobblemon.mod.common.client.render.models.blockbench.animation.ActiveAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.animation.BimanualSwingAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.animation.BipedWalkAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.animation.PitchTiltAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.animation.PrimaryAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.animation.PunchAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.animation.QuadrupedWalkAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.animation.SingleBoneLookAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.animation.WingFlapIdleAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.pose.ModelPartTransformation
import com.cobblemon.mod.common.client.render.models.blockbench.wavefunction.WaveFunction
import com.cobblemon.mod.common.client.render.models.blockbench.wavefunction.sineFunction
@@ -117,6 +125,22 @@ object ClientMoLangFunctions {
ObjectValue(anim)
}
},
"pitch_tilt" to Function {
model -> Function { params ->
val bone = params.getStringOrNull(0) ?: "root"
val maxChangePerTick = params.getDoubleOrNull(1) ?: 1.5F
val minPitch = params.getDoubleOrNull(2) ?: -45F
val maxPitch = params.getDoubleOrNull(3) ?: 45F
ObjectValue(
PitchTiltAnimation(
bone = model.getPart(bone),
minPitch = minPitch.toFloat(),
maxPitch = maxPitch.toFloat(),
maxChangePerTick = maxChangePerTick.toFloat()
)
)
}
},
"look" to Function {
model -> Function { params ->
val boneName = params.getString(0)
@@ -14,6 +14,7 @@ import com.cobblemon.mod.common.api.riding.behaviour.types.air.*
import com.cobblemon.mod.common.api.riding.behaviour.types.composite.CompositeBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.types.composite.CompositeState
import com.cobblemon.mod.common.api.riding.behaviour.types.land.HorseBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.types.land.MinekartBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.types.land.VehicleBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.types.liquid.BoatBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.types.liquid.BurstBehaviour
@@ -133,25 +134,34 @@ class RideControlsOverlay : Gui(Minecraft.getInstance()) {
// Configure what controls to show for each behaviour
when (currentBehaviourKey) {
BirdBehaviour.KEY -> {}
BoatBehaviour.KEY -> {}
BurstBehaviour.KEY -> {}
DolphinBehaviour.KEY -> {}
GliderBehaviour.KEY -> {}
HelicopterBehaviour.KEY -> {}
HorseBehaviour.KEY -> {
showVerticalMouse = false
disableHorizontalMovementKeys = true
showSneakKey = false
}
BoatBehaviour.KEY -> {}
GliderBehaviour.KEY -> {}
HelicopterBehaviour.KEY -> {}
HoverBehaviour.KEY -> {
showVerticalMouse = false
}
JetBehaviour.KEY -> {
showMovementKeys = false
showSneakKey = false
showJumpKey = false
}
BurstBehaviour.KEY -> {}
VehicleBehaviour.KEY -> {}
HoverBehaviour.KEY -> {
MinekartBehaviour.KEY -> {
showVerticalMouse = false
showHorizontalMouse = false
showSneakKey = false
}
RocketBehaviour.KEY -> {
showVerticalMouse = false
showHorizontalMouse = false
}
VehicleBehaviour.KEY -> {}
}
val centerX = minecraft.window.guiScaledWidth / 2
@@ -719,6 +719,7 @@ class PCGUI(
if (pasture.pastureScrollList.isHovered) pasture.pastureScrollList.mouseScrolled(mouseX, mouseY, amount, verticalAmount)
}
if (storageWidget.isHovered && mouseX < (storageWidget.x + StorageWidget.SCREEN_WIDTH)) this.storageWidget.box -= verticalAmount.toInt()
if (wallpaperWidget.isHovered) wallpaperWidget.mouseScrolled(mouseX, mouseY, amount, verticalAmount)
return super.mouseScrolled(mouseX, mouseY, amount, verticalAmount)
}
@@ -14,7 +14,6 @@ import com.cobblemon.mod.common.CobblemonNetwork.sendToServer
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.moves.Move
import com.cobblemon.mod.common.api.reactive.Observable.Companion.stopAfter
import com.cobblemon.mod.common.api.scheduling.Schedulable
import com.cobblemon.mod.common.api.scheduling.SchedulingTracker
import com.cobblemon.mod.common.api.storage.party.PartyPosition
@@ -25,7 +24,11 @@ import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import com.cobblemon.mod.common.client.gui.ExitButton
import com.cobblemon.mod.common.client.gui.TypeIcon
import com.cobblemon.mod.common.client.gui.summary.widgets.*
import com.cobblemon.mod.common.client.gui.summary.widgets.EvolutionSelectScreen
import com.cobblemon.mod.common.client.gui.summary.widgets.MarkingsWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.ModelWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.NicknameEntryWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.PartyWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.screens.SummaryTab
import com.cobblemon.mod.common.client.gui.summary.widgets.screens.info.InfoWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.screens.marks.MarksWidget
@@ -328,9 +331,6 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
saveMarkings()
this.selectedPokemon.moveSet.changeFunction = {}
this.party.getOrNull(newSelection)?.let { newPokemon ->
newPokemon.changeObservable.pipe( stopAfter { Minecraft.getInstance().screen != this || this.selectedPokemon != newPokemon } ).subscribe {
updatePokemonInfo()
}
this.selectedPokemon = newPokemon
}
updatePokemonInfo()
@@ -0,0 +1,29 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.net.riding
import com.cobblemon.mod.common.api.net.ClientNetworkPacketHandler
import com.cobblemon.mod.common.net.messages.client.orientation.ClientboundUpdateDriverInputPacket
import net.minecraft.client.Minecraft
import net.minecraft.client.player.RemotePlayer
object ClientboundUpdateDriverInputHandler : ClientNetworkPacketHandler<ClientboundUpdateDriverInputPacket> {
override fun handle(packet: ClientboundUpdateDriverInputPacket, client: Minecraft) {
client.executeIfPossible {
val level = client.level ?: return@executeIfPossible
val entity = level.getEntity(packet.entityId)
if (entity is RemotePlayer) {
entity.xxa = packet.driverInput.x
entity.zza = packet.driverInput.z
entity.jumping = packet.driverInput.y == 1.0f
entity.isShiftKeyDown = packet.driverInput.y == -1.0f
}
}
}
}
@@ -0,0 +1,70 @@
/*
* 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.entity
import com.cobblemon.mod.common.api.riding.Rideable
import com.cobblemon.mod.common.client.MountedPokemonAnimationRenderController
import com.cobblemon.mod.common.client.entity.PokemonClientDelegate
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.mojang.blaze3d.vertex.PoseStack
import com.mojang.math.Axis
import net.minecraft.util.Mth
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.phys.Vec3
import org.joml.AxisAngle4f
import org.joml.Vector3f
object MountedLivingEntityRenderer {
fun render(
entity: LivingEntity,
pokemon: PokemonEntity,
stack: PoseStack,
bob: Float,
yBodyRot: Float,
partialTicks: Float,
i: Float
) {
if (entity.vehicle !is Rideable) return
val matrix = stack.last().pose()
val seatIndex = pokemon.passengers.indexOf(entity)
val seat = pokemon.seats[seatIndex]
val delegate = pokemon.delegate as PokemonClientDelegate
val locator = delegate.locatorStates[seat.locator]
//Positions player
if (locator != null) {
MountedPokemonAnimationRenderController.setup(pokemon, partialTicks)
//Undo seat position
val playerPos = Vec3(
Mth.lerp(partialTicks.toDouble(), entity.xOld, entity.x),
Mth.lerp(partialTicks.toDouble(), entity.yOld, entity.y),
Mth.lerp(partialTicks.toDouble(), entity.zOld, entity.z),
)
val entityPos = Vec3(
Mth.lerp(partialTicks.toDouble(), pokemon.xOld, pokemon.x),
Mth.lerp(partialTicks.toDouble(), pokemon.yOld, pokemon.y),
Mth.lerp(partialTicks.toDouble(), pokemon.zOld, pokemon.z),
)
matrix.translate(playerPos.subtract(entityPos).toVector3f().negate())
matrix.translate(locator.matrix.getTranslation(Vector3f()))
val offset = Vector3f(0f, entity.bbHeight / 2, 0f).mul(-1f)
matrix.rotate(locator.matrix.getRotation(AxisAngle4f()))
matrix.rotate(Axis.YP.rotationDegrees(180 + yBodyRot))
matrix.translate(offset)
matrix.translate(Vector3f(0f, 0.35f, 0f))
}
}
}
@@ -607,6 +607,7 @@ open class PosableModel(@Transient override val rootPart: Bone) : ModelFrame {
setDefault()
// Applies any of the state's queued actions.
state.preRender()
state.renderMarkers.clear()
// Performs a check that the current pose is correct and returns back which pose we should be applying. Even if
// a change of pose is necessary, if it's going to gradually transition there then we're still going to keep
// applying our current pose until that process is done.
@@ -773,8 +774,8 @@ open class PosableModel(@Transient override val rootPart: Bone) : ModelFrame {
matrixStack.scale(scale, scale, scale)
matrixStack.mulPose(Axis.YP.rotationDegrees(yRot))
//For some reason locators are positioned upside-down so this fixes that
matrixStack.mulPose(Axis.ZP.rotationDegrees(180f))
// Entities' locators are positioned upside-down so this fixes that
if(entity != null) matrixStack.mulPose(Axis.ZP.rotationDegrees(180f))
locatorAccess.update(matrixStack, entity, scale, state.locatorStates, isRoot = true)
}
@@ -131,6 +131,14 @@ abstract class PosableState : Schedulable {
/** A list of items to be rendered on a PosableModel during an animation */
val animationItems = mutableMapOf<String, ItemStack>()
/** Just some numbers that might be getting arbitrarily set and referenced. */
val numbers = mutableMapOf<String, Float>()
/**
* Markers that have been added to the state this render, cleared when animation is about to start.
* This is used to hand basic state from one animation to another.
*/
val renderMarkers = mutableSetOf<String>()
/** All of the MoLang functions that can be expose current riding data. */
val ridingFunctions = QueryStruct(hashMapOf())
.addFunction("pitch") { params ->
@@ -255,6 +263,36 @@ abstract class PosableState : Schedulable {
//?: return@addFunction DoubleValue(0.0)
return@addFunction DoubleValue((partialTickZVel / topSpeed).coerceIn(-1.0,1.0))
}
.addFunction("input_right") { params ->
val lookBackTick = params.getInt(0)
val pokemon = getEntity() as? PokemonEntity ?: return@addFunction DoubleValue(0.0)
val partialTickZVel = pokemon.ridingAnimationData.driverInputSpring.getInterpolated(currentPartialTicks.toDouble(), lookBackTick).x
val topSpeed = 1.0//pokemon.riding.getController(pokemon)?.getStat(pokemon, RidingStat.SPEED)
//?: return@addFunction DoubleValue(0.0)
return@addFunction DoubleValue((partialTickZVel / topSpeed).coerceIn(-1.0,1.0) * -1.0)
}
.addFunction("input_up") { params ->
val lookBackTick = params.getInt(0)
val pokemon = getEntity() as? PokemonEntity ?: return@addFunction DoubleValue(0.0)
val partialTickZVel = pokemon.ridingAnimationData.driverInputSpring.getInterpolated(currentPartialTicks.toDouble(), lookBackTick).y
val topSpeed = 1.0//pokemon.riding.getController(pokemon)?.getStat(pokemon, RidingStat.SPEED)
//?: return@addFunction DoubleValue(0.0)
return@addFunction DoubleValue((partialTickZVel / topSpeed).coerceIn(-1.0,1.0))
}
.addFunction("input_forward") { params ->
val lookBackTick = params.getInt(0)
val pokemon = getEntity() as? PokemonEntity ?: return@addFunction DoubleValue(0.0)
val partialTickZVel = pokemon.ridingAnimationData.driverInputSpring.getInterpolated(currentPartialTicks.toDouble(), lookBackTick).z
val topSpeed = 1.0//pokemon.riding.getController(pokemon)?.getStat(pokemon, RidingStat.SPEED)
//?: return@addFunction DoubleValue(0.0)
return@addFunction DoubleValue((partialTickZVel / topSpeed).coerceIn(-1.0,1.0))
}
/** All of the MoLang functions that can be applied to something with this state. */
@@ -288,6 +326,31 @@ abstract class PosableState : Schedulable {
}
}
}
.addFunction("recurring_sound") { params ->
if (params.get<MoValue>(0) !is StringValue) {
return@addFunction Unit
}
//param 0 - sound
//param 1 - offset
//param 2 -
val curr_time = reusableAnimTime.value
val entity = getEntity()
val soundEvent = SoundEvent.createVariableRangeEvent(params.getString(0).asIdentifierDefaultingNamespace())
val offset = params.getDouble(1)
val loopLength = params.getDouble(2)
if (soundEvent != null) {
val volume = 1F
val pitch = 1F
if( )
if (entity != null) {
if (!entity.isSilent) {
entity.level().playLocalSound(entity, soundEvent, entity.soundSource, volume, pitch)
}
} else {
Minecraft.getInstance().soundManager.play(SimpleSoundInstance.forUI(soundEvent, volume, pitch))
}
}
}
.addFunction("render_item") { params ->
if (params.get<MoValue>(0) !is StringValue) return@addFunction Unit
@@ -10,15 +10,12 @@ package com.cobblemon.mod.common.client.render.models.blockbench
import com.bedrockk.molang.runtime.MoLangRuntime
import com.cobblemon.mod.common.client.render.models.blockbench.animation.ActiveAnimation
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.PosableEntity
import com.cobblemon.mod.common.util.asExpressionLike
import com.cobblemon.mod.common.util.isBattling
import com.cobblemon.mod.common.util.isDusk
import com.cobblemon.mod.common.util.isStandingOnRedSand
import com.cobblemon.mod.common.util.isStandingOnSand
import com.cobblemon.mod.common.util.isStandingOnSandOrRedSand
import com.cobblemon.mod.common.util.isStandingOn
import com.google.gson.JsonDeserializationContext
import com.google.gson.JsonDeserializer
import com.google.gson.JsonElement
@@ -57,9 +54,9 @@ class PoseAdapter(
addCondition("isUnderWater") { entity, expectedValue -> entity.isUnderWater == expectedValue }
// Kept for compatibility
addCondition("isStandingOnRedSand") { entity, expectedValue -> entity.isStandingOnRedSand() == expectedValue }
addCondition("isStandingOnSand") { entity, expectedValue -> entity.isStandingOnSand() == expectedValue }
addCondition("isStandingOnSandOrRedSand") { entity, expectedValue -> entity.isStandingOnSandOrRedSand() == expectedValue }
addCondition("isStandingOnRedSand") { entity, expectedValue -> entity.isStandingOn(setOf("minecraft:red_sand")) == expectedValue }
addCondition("isStandingOnSand") { entity, expectedValue -> entity.isStandingOn(setOf("minecraft:sand")) == expectedValue }
addCondition("isStandingOnSandOrRedSand") { entity, expectedValue -> entity.isStandingOn(setOf("minecraft:sand", "minecraft:red_sand")) == expectedValue }
addCondition("isDusk") { entity, expectedValue -> entity.isDusk() == expectedValue }
@@ -0,0 +1,83 @@
/*
* 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.animation
import com.cobblemon.mod.common.client.render.models.blockbench.PosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.PosableState
import com.cobblemon.mod.common.client.render.models.blockbench.addRotation
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Bone
import com.cobblemon.mod.common.client.render.models.blockbench.repository.RenderContext
import com.cobblemon.mod.common.util.math.geometry.toDegrees
import com.cobblemon.mod.common.util.math.geometry.toRadians
import net.minecraft.util.Mth
/**
* Animation used to tilt the targeted bone of the entity by a pitch that is proportionate
* to the direction the entity is moving. This is used to make birds and fish tilt upwards
* or downwards based on their motion.
*
* @author Hiroku
* @since August 9th, 2025
*/
class PitchTiltAnimation(
val bone: Bone,
val maxChangePerTick: Float = 1F,
val minPitch: Float = -45F,
val maxPitch: Float = 45F,
) : PoseAnimation() {
companion object {
const val PREVIOUS_ANGLE = "previous_pitch_tilt_angle"
const val PITCHED_TILT = "has_pitched_tilt"
}
override fun setupAnim(
context: RenderContext,
model: PosableModel,
state: PosableState,
limbSwing: Float,
limbSwingAmount: Float,
ageInTicks: Float,
headYaw: Float,
headPitch: Float,
intensity: Float
) {
val entity = context.entity ?: return
if (entity.passengers.isNotEmpty()) {
return
}
val delta = entity.deltaMovement
// The angleOfMotion should be 0 in the case that horizontal movement is 1 and vertical movement is 0.
// It should be the angle in degrees that becomes higher as the entity's motion becomes more upwards, and negative as the entity's motion becomes more downwards
val vertical = delta.y
val horizontal = delta.horizontalDistance()
val angleOfMotion = if (horizontal == 0.0) {
when {
vertical > 0 -> maxPitch
vertical < 0 -> minPitch
else -> 0F
}
} else {
Mth.atan2(vertical, horizontal).toDegrees()
}.coerceIn(minPitch, maxPitch)
val maxChangePerTick = this.maxChangePerTick * intensity
val lastAngle = state.numbers[PREVIOUS_ANGLE] ?: 0F
val interpolatedAngle = if (angleOfMotion - lastAngle > maxChangePerTick) {
lastAngle + maxChangePerTick
} else if (angleOfMotion - lastAngle < -maxChangePerTick) {
lastAngle - maxChangePerTick
} else {
angleOfMotion
}
bone.addRotation(0, -interpolatedAngle.toRadians())
state.numbers[PREVIOUS_ANGLE] = interpolatedAngle
state.renderMarkers += PITCHED_TILT
}
}
@@ -11,6 +11,8 @@ package com.cobblemon.mod.common.client.render.models.blockbench.animation
import com.cobblemon.mod.common.client.render.models.blockbench.PosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.PosableState
import com.cobblemon.mod.common.client.render.models.blockbench.addRotation
import com.cobblemon.mod.common.client.render.models.blockbench.animation.PitchTiltAnimation.Companion.PITCHED_TILT
import com.cobblemon.mod.common.client.render.models.blockbench.animation.PitchTiltAnimation.Companion.PREVIOUS_ANGLE
import com.cobblemon.mod.common.client.render.models.blockbench.frame.HeadedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Bone
import com.cobblemon.mod.common.client.render.models.blockbench.pose.ModelPartTransformation.Companion.X_AXIS
@@ -63,7 +65,10 @@ class SingleBoneLookAnimation(
val head = bone ?: return
val pitch = pitchMultiplier * headPitch.coerceIn(minPitch, maxPitch)
val yaw = yawMultiplier * headYaw.coerceIn(minYaw, maxYaw)
head.addRotation(X_AXIS, pitch.toRadians() * intensity)
// If PitchTiltAnimation was applied then we should counteract that tilt. It does kinda
// assume that the tilt was on the body bone, though.
val counterTiltDegrees = if (PITCHED_TILT in state.renderMarkers) { state.numbers[PREVIOUS_ANGLE] ?: 0 } else 0F
head.addRotation(X_AXIS, (pitch.toRadians() * intensity) + counterTiltDegrees.toRadians())
head.addRotation(Y_AXIS, yaw.toRadians() * intensity)
}
}
@@ -8,6 +8,7 @@
package com.cobblemon.mod.common.client.render.models.blockbench.pokemon.gen1
import com.cobblemon.mod.common.client.render.models.blockbench.animation.PitchTiltAnimation
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
@@ -86,6 +87,7 @@ class PidgeyModel(root: ModelPart) : PokemonPosableModel(root), HeadedFrame {
transformTicks = 10,
quirks = arrayOf(blink),
animations = arrayOf(
PitchTiltAnimation(bone = getPart("body"), maxChangePerTick = 1F),
singleBoneLook(),
bedrock("pidgey", "air_idle")
)
@@ -103,6 +105,7 @@ class PidgeyModel(root: ModelPart) : PokemonPosableModel(root), HeadedFrame {
transformTicks = 10,
quirks = arrayOf(blink),
animations = arrayOf(
PitchTiltAnimation(bone = getPart("body"), maxChangePerTick = 1F),
singleBoneLook(),
bedrock("pidgey", "air_fly")
)
@@ -15,9 +15,7 @@ import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.PokemonP
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 com.cobblemon.mod.common.util.isStandingOnRedSand
import com.cobblemon.mod.common.util.isStandingOnSand
import com.cobblemon.mod.common.util.isStandingOnSandOrRedSand
import com.cobblemon.mod.common.util.isStandingOn
import net.minecraft.client.model.geom.ModelPart
import net.minecraft.world.phys.Vec3
@@ -63,7 +61,7 @@ class HippowdonModel (root: ModelPart) : PokemonPosableModel(root), HeadedFrame
sleep = registerPose(
poseName = "sleep",
poseType = PoseType.SLEEP,
condition = { it.getEntity()?.isStandingOnSand() != true },
condition = { it.getEntity()?.isStandingOn(setOf("minecraft:sand")) != true },
transformedParts = arrayOf(
sand.createTransformation().withVisibility(visibility = false),
redsand.createTransformation().withVisibility(visibility = false)
@@ -77,7 +75,7 @@ class HippowdonModel (root: ModelPart) : PokemonPosableModel(root), HeadedFrame
sleepredsand = registerPose(
poseName = "sleepsand",
poseType = PoseType.SLEEP,
condition = { it.getEntity()?.isStandingOnRedSand() == true },
condition = { it.getEntity()?.isStandingOn(setOf("minecraft:red_sand")) == true },
transformedParts = arrayOf(
sand.createTransformation().withVisibility(visibility = false),
redsand.createTransformation().withVisibility(visibility = false)
@@ -91,7 +89,7 @@ class HippowdonModel (root: ModelPart) : PokemonPosableModel(root), HeadedFrame
standing = registerPose(
poseName = "standing",
poseTypes = PoseType.UI_POSES + PoseType.STATIONARY_POSES,
condition = { !it.isBattling && it.getEntity()?.isStandingOnSandOrRedSand() != true },
condition = { !it.isBattling && it.getEntity()?.isStandingOn(setOf("minecraft:sand", "minecraft:red_sand")) != true },
quirks = arrayOf(blink, idlequirk),
transformedParts = arrayOf(
sand.createTransformation().withVisibility(visibility = false),
@@ -106,7 +104,7 @@ class HippowdonModel (root: ModelPart) : PokemonPosableModel(root), HeadedFrame
walk = registerPose(
poseName = "walk",
poseTypes = PoseType.MOVING_POSES,
condition = { it.getEntity()?.isStandingOnSandOrRedSand() != true },
condition = { it.getEntity()?.isStandingOn(setOf("minecraft:sand", "minecraft:red_sand")) != true },
quirks = arrayOf(blink),
transformedParts = arrayOf(
sand.createTransformation().withVisibility(visibility = false),
@@ -122,7 +120,7 @@ class HippowdonModel (root: ModelPart) : PokemonPosableModel(root), HeadedFrame
poseName = "standingsand",
poseTypes = PoseType.STATIONARY_POSES,
quirks = arrayOf(blink, idlequirk),
condition = { !it.isBattling && it.getEntity()?.isStandingOnSand() == true },
condition = { !it.isBattling && it.getEntity()?.isStandingOn(setOf("minecraft:sand")) == true },
transformedParts = arrayOf(
sand.createTransformation().withVisibility(visibility = false),
redsand.createTransformation().withVisibility(visibility = false)
@@ -137,7 +135,7 @@ class HippowdonModel (root: ModelPart) : PokemonPosableModel(root), HeadedFrame
poseName = "standingredsand",
poseTypes = PoseType.STATIONARY_POSES,
quirks = arrayOf(blink, idlequirk),
condition = { !it.isBattling && it.getEntity()?.isStandingOnRedSand() == true },
condition = { !it.isBattling && it.getEntity()?.isStandingOn(setOf("minecraft:red_sand")) == true },
transformedParts = arrayOf(
sand.createTransformation().withVisibility(visibility = false),
redsand.createTransformation().withVisibility(visibility = false)
@@ -151,7 +149,7 @@ class HippowdonModel (root: ModelPart) : PokemonPosableModel(root), HeadedFrame
walksand = registerPose(
poseName = "walksand",
poseTypes = PoseType.MOVING_POSES,
condition = { it.getEntity()?.isStandingOnSand() == true },
condition = { it.getEntity()?.isStandingOn(setOf("minecraft:sand")) == true },
quirks = arrayOf(blink),
transformedParts = arrayOf(
sand.createTransformation().withVisibility(visibility = false),
@@ -165,7 +163,7 @@ class HippowdonModel (root: ModelPart) : PokemonPosableModel(root), HeadedFrame
walkredsand = registerPose(
poseName = "walkredsand",
poseTypes = PoseType.MOVING_POSES,
condition = { it.getEntity()?.isStandingOnRedSand() == true },
condition = { it.getEntity()?.isStandingOn(setOf("minecraft:red_sand")) == true },
quirks = arrayOf(blink),
transformedParts = arrayOf(
sand.createTransformation().withVisibility(visibility = false),
@@ -179,7 +177,7 @@ class HippowdonModel (root: ModelPart) : PokemonPosableModel(root), HeadedFrame
battleidle = registerPose(
poseName = "battleidle",
poseTypes = PoseType.STATIONARY_POSES,
condition = { it.isBattling && it.getEntity()?.isStandingOnSandOrRedSand() != true },
condition = { it.isBattling && it.getEntity()?.isStandingOn(setOf("minecraft:sand", "minecraft:red_sand")) != true },
quirks = arrayOf(blink),
transformedParts = arrayOf(
sand.createTransformation().withVisibility(visibility = false),
@@ -194,7 +192,7 @@ class HippowdonModel (root: ModelPart) : PokemonPosableModel(root), HeadedFrame
battleidlesand = registerPose(
poseName = "battleidlesand",
poseTypes = PoseType.STATIONARY_POSES,
condition = { it.getEntity()?.isStandingOnSand() == true && it.isBattling },
condition = { it.getEntity()?.isStandingOn(setOf("minecraft:sand")) == true && it.isBattling },
quirks = arrayOf(blink),
transformedParts = arrayOf(
sand.createTransformation().withVisibility(visibility = true),
@@ -209,7 +207,7 @@ class HippowdonModel (root: ModelPart) : PokemonPosableModel(root), HeadedFrame
battleidleredsand = registerPose(
poseName = "battleidleredsand",
poseTypes = PoseType.STATIONARY_POSES,
condition = { it.getEntity()?.isStandingOnRedSand() == true && it.isBattling },
condition = { it.getEntity()?.isStandingOn(setOf("minecraft:red_sand")) == true && it.isBattling },
quirks = arrayOf(blink),
transformedParts = arrayOf(
sand.createTransformation().withVisibility(visibility = false),
@@ -1,73 +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.animation.ActiveAnimation
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 GolurkModel (root: ModelPart) : PokemonPosableModel(root), HeadedFrame {
override val rootPart = root.registerChildWithAllChildren("golurk")
override val head = getPart("head")
override var portraitScale = 3.0F
override var portraitTranslation = Vec3(-0.3, 5.4, 0.0)
override var profileScale = 0.4F
override var profileTranslation = Vec3(0.0, 1.15, 0.0)
lateinit var standing: Pose
lateinit var walk: Pose
lateinit var sleep: Pose
override val cryAnimation = CryProvider { bedrockStateful("golurk", "cry") }
override fun registerPoses() {
val blink = quirk { bedrockStateful("golurk", "blink") }
standing = registerPose(
poseName = "standing",
poseTypes = PoseType.STATIONARY_POSES + PoseType.UI_POSES,
transformTicks = 20,
quirks = arrayOf(blink),
animations = arrayOf(
singleBoneLook(),
bedrock("golurk", "ground_idle")
)
)
walk = registerPose(
poseName = "walk",
poseTypes = PoseType.MOVING_POSES,
transformTicks = 20,
quirks = arrayOf(blink),
animations = arrayOf(
singleBoneLook(),
bedrock("golurk", "ground_walk")
)
)
sleep = registerPose(
poseName = "sleep",
transformTicks = 20,
poseType = PoseType.SLEEP,
animations = arrayOf(bedrock("golurk", "sleep"))
)
}
override fun getFaintAnimation(state: PosableState): ActiveAnimation? {
return if (state.isNotPosedIn(sleep)) bedrockStateful("golurk", "faint") else null
}
}
@@ -405,7 +405,6 @@ object VaryingModelRepository {
inbuilt("lombre", :: LombreModel)
inbuilt("ludicolo", :: LudicoloModel)
inbuilt("golett", :: GolettModel)
inbuilt("golurk", :: GolurkModel)
inbuilt("stantler", :: StantlerModel)
inbuilt("sneasel", :: SneaselModel)
inbuilt("weavile", :: WeavileModel)
@@ -36,16 +36,26 @@ import com.cobblemon.mod.common.entity.pokeball.EmptyPokeBallEntity
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity.Companion.SPAWN_DIRECTION
import com.cobblemon.mod.common.pokeball.PokeBall
import com.cobblemon.mod.common.util.*
import com.cobblemon.mod.common.util.effectiveName
import com.cobblemon.mod.common.util.isLookingAt
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.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
import net.minecraft.client.renderer.*
import net.minecraft.client.renderer.LightTexture
import net.minecraft.client.renderer.MultiBufferSource
import net.minecraft.client.renderer.RenderType
import net.minecraft.client.renderer.entity.EntityRendererProvider
import net.minecraft.client.renderer.entity.ItemRenderer
import net.minecraft.client.renderer.entity.MobRenderer
@@ -57,8 +67,11 @@ 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.*
import kotlin.math.*
import org.joml.Math
import org.joml.Matrix4f
import org.joml.Quaternionf
import org.joml.Vector3f
import org.joml.Vector4f
class PokemonRenderer(
context: EntityRendererProvider.Context
@@ -127,7 +140,7 @@ class PokemonRenderer(
clientDelegate
)
}
if(entity.platform != PlatformType.NONE) {
if (entity.platform != PlatformType.NONE) {
drawPlatform(
poseMatrix,
entity,
@@ -144,8 +157,7 @@ class PokemonRenderer(
if (entity.passengers.isNotEmpty()) {
renderRiding(entity, entityYaw, partialTicks, poseMatrix, buffer, packedLight)
}
else {
} else {
super.render(entity, entityYaw, partialTicks, poseMatrix, buffer, packedLight)
}
@@ -182,7 +194,7 @@ class PokemonRenderer(
val controller = rollable.orientationController
poseMatrix.pushPose()
if(!DISABLE_ROLLING_DEBUG && controller.active){
if (!DISABLE_ROLLING_DEBUG && controller.active) {
val matrix = poseMatrix.last().pose()
val yaw = Mth.rotLerp(partialTicks, entity.yBodyRotO, entity.yBodyRot)
val center = Vector3f(0f, entity.bbHeight/2, 0f)
@@ -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.command
import com.bedrockk.molang.runtime.MoLangRuntime
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.setup
import com.cobblemon.mod.common.api.permission.CobblemonPermissions
import com.cobblemon.mod.common.entity.npc.NPCEntity
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.asExpressionLike
import com.cobblemon.mod.common.util.commandLang
import com.cobblemon.mod.common.util.entity
import com.cobblemon.mod.common.util.permission
import com.cobblemon.mod.common.util.player
import com.cobblemon.mod.common.util.string
import com.cobblemon.mod.common.util.withQueryValue
import com.mojang.brigadier.Command
import com.mojang.brigadier.CommandDispatcher
import com.mojang.brigadier.arguments.StringArgumentType
import com.mojang.brigadier.context.CommandContext
import net.minecraft.commands.CommandSourceStack
import net.minecraft.commands.Commands.argument
import net.minecraft.commands.Commands.literal
import net.minecraft.commands.arguments.EntityArgument
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.entity.Entity
object RunMolangCommand {
private const val NAME = "runmolang"
private const val MOLANG = "molang"
private const val PLAYER = "player"
private const val NPC = "npc"
private const val POKEMON = "pokemon"
fun register(dispatcher : CommandDispatcher<CommandSourceStack>) {
dispatcher.register(literal(NAME)
.permission(CobblemonPermissions.RUN_MOLANG)
.then(
argument(MOLANG, StringArgumentType.string())
.executes { execute(it, it.string(MOLANG), null, null, null) }
.then(argument(PLAYER, EntityArgument.player())
.executes { execute(it, it.string(MOLANG), it.player(PLAYER), null, null) }
.then(argument(NPC, EntityArgument.entity())
.executes { execute(it, it.string(MOLANG), it.player(PLAYER), it.entity(
NPC
), null) }
.then(argument(POKEMON, EntityArgument.entity())
.executes { execute(it, it.string(MOLANG), it.player(PLAYER), it.entity(
NPC
), it.entity(POKEMON)) }
)
)
.then(argument(POKEMON, EntityArgument.entity())
.executes { execute(it, it.string(MOLANG), it.player(PLAYER), null, it.entity(
POKEMON
)) }
)
)
.then(argument(NPC, EntityArgument.entity())
.executes { execute(it, it.string(MOLANG), null, it.entity(NPC), null) }
.then(argument(POKEMON, EntityArgument.entity())
.executes { execute(it, it.string(MOLANG), null, it.entity(NPC), it.entity(
POKEMON
)) }
)
)
.then(argument(POKEMON, StringArgumentType.string())
.executes { execute(it, it.string(MOLANG), null, null, it.entity(POKEMON)) }
)
)
)
}
private fun execute(context: CommandContext<CommandSourceStack>, molang: String, player: ServerPlayer?, npc: Entity?, pokemon: Entity? = null): Int {
try {
val runtime = MoLangRuntime().setup()
val entity = context.source.entity
(npc as? NPCEntity)?.let { runtime.withQueryValue("npc", npc.struct) }
(pokemon as? PokemonEntity)?.let { runtime.withQueryValue("pokemon", pokemon.struct) }
player?.let { runtime.withQueryValue("player", player.asMoLangValue()) }
entity?.let { runtime.withQueryValue("entity", entity.asMostSpecificMoLangValue()) }
val out = molang.asExpressionLike().resolve(runtime)
context.source.sendSuccess({ commandLang("molang.out", out.asString()) }, true)
} catch (e: Exception) {
e.printStackTrace()
}
return Command.SINGLE_SUCCESS
}
}
@@ -10,20 +10,20 @@ package com.cobblemon.mod.common.command
import com.bedrockk.molang.runtime.MoLangRuntime
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.setup
import com.cobblemon.mod.common.api.permission.CobblemonPermissions
import com.cobblemon.mod.common.api.scripting.CobblemonScripts
import com.cobblemon.mod.common.entity.npc.NPCEntity
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.entity
import com.cobblemon.mod.common.util.permission
import com.cobblemon.mod.common.util.player
import com.cobblemon.mod.common.util.resourceLocation
import com.cobblemon.mod.common.util.uuid
import com.cobblemon.mod.common.util.withQueryValue
import com.mojang.brigadier.Command
import com.mojang.brigadier.CommandDispatcher
import com.mojang.brigadier.arguments.StringArgumentType
import com.mojang.brigadier.context.CommandContext
import java.util.UUID
import net.minecraft.commands.CommandSourceStack
import net.minecraft.commands.Commands.argument
import net.minecraft.commands.Commands.literal
@@ -31,6 +31,7 @@ import net.minecraft.commands.arguments.EntityArgument
import net.minecraft.commands.arguments.ResourceLocationArgument
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.entity.Entity
object RunMolangScriptCommand {
private const val NAME = "runmolangscript"
@@ -47,38 +48,38 @@ object RunMolangScriptCommand {
.executes { execute(it, it.resourceLocation(SCRIPT), null, null, null) }
.then(argument(PLAYER, EntityArgument.player())
.executes { execute(it, it.resourceLocation(SCRIPT), it.player(PLAYER), null, null) }
.then(argument(NPC, StringArgumentType.string())
.executes { execute(it, it.resourceLocation(SCRIPT), it.player(PLAYER), it.uuid(NPC), null) }
.then(argument(POKEMON, StringArgumentType.string())
.executes { execute(it, it.resourceLocation(SCRIPT), it.player(PLAYER), it.uuid(NPC), it.uuid(POKEMON)) }
.then(argument(NPC, EntityArgument.entity())
.executes { execute(it, it.resourceLocation(SCRIPT), it.player(PLAYER), it.entity(NPC), null) }
.then(argument(POKEMON, EntityArgument.entity())
.executes { execute(it, it.resourceLocation(SCRIPT), it.player(PLAYER), it.entity(NPC), it.entity(POKEMON)) }
)
)
.then(argument(POKEMON, StringArgumentType.string())
.executes { execute(it, it.resourceLocation(SCRIPT), it.player(PLAYER), null, it.uuid(POKEMON)) }
.then(argument(POKEMON, EntityArgument.entity())
.executes { execute(it, it.resourceLocation(SCRIPT), it.player(PLAYER), null, it.entity(POKEMON)) }
)
)
.then(argument(NPC, StringArgumentType.string())
.executes { execute(it, it.resourceLocation(SCRIPT), null, it.uuid(NPC), null) }
.then(argument(POKEMON, StringArgumentType.string())
.executes { execute(it, it.resourceLocation(SCRIPT), null, it.uuid(NPC), it.uuid(POKEMON)) }
.then(argument(NPC, EntityArgument.entity())
.executes { execute(it, it.resourceLocation(SCRIPT), null, it.entity(NPC), null) }
.then(argument(POKEMON, EntityArgument.entity())
.executes { execute(it, it.resourceLocation(SCRIPT), null, it.entity(NPC), it.entity(POKEMON)) }
)
)
.then(argument(POKEMON, StringArgumentType.string())
.executes { execute(it, it.resourceLocation(SCRIPT), null, null, it.uuid(POKEMON)) }
.then(argument(POKEMON, EntityArgument.entity())
.executes { execute(it, it.resourceLocation(SCRIPT), null, null, it.entity(POKEMON)) }
)
)
)
}
private fun execute(context: CommandContext<CommandSourceStack>, scriptId: ResourceLocation, player: ServerPlayer?, npcId: UUID?, pokemonId: UUID? = null): Int {
private fun execute(context: CommandContext<CommandSourceStack>, scriptId: ResourceLocation, player: ServerPlayer?, npc: Entity?, pokemon: Entity? = null): Int {
try {
val runtime = MoLangRuntime().setup()
val npc = npcId?.let { context.source.level.getEntity(it) as? NPCEntity }
val pokemon = pokemonId?.let { context.source.level.getEntity(it) as? NPCEntity }
val entity = context.source.entity
npc?.let { runtime.withQueryValue("npc", npc.struct) }
pokemon?.let { runtime.withQueryValue("pokemon", pokemon.struct) }
(npc as? NPCEntity)?.let { runtime.withQueryValue("npc", npc.struct) }
(pokemon as? PokemonEntity)?.let { runtime.withQueryValue("pokemon", pokemon.struct) }
player?.let { runtime.withQueryValue("player", player.asMoLangValue()) }
entity?.let { runtime.withQueryValue("entity", entity.asMostSpecificMoLangValue()) }
CobblemonScripts.run(scriptId, runtime)
} catch (e: Exception) {
@@ -85,7 +85,7 @@ object StarterDataLoader : JsonDataRegistry<StarterCategory> {
}
}
observable.emit()
observable.emit(this)
}
override fun sync(player: ServerPlayer) {}
@@ -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
import com.bedrockk.molang.runtime.MoLangRuntime
import com.bedrockk.molang.runtime.MoParams
import com.bedrockk.molang.runtime.value.MoValue
import com.cobblemon.mod.common.api.ai.CobblemonBehaviour
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.setup
import com.cobblemon.mod.common.api.scripting.CobblemonScripts
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.resolve
import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.ListTag
import net.minecraft.nbt.StringTag
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.entity.Entity
/**
* A class that holds callbacks for an entity, allowing scripts to be associated with specific events.
* Mainly keeps code bloat away from the entity classes. There's likely to be some overlap between these
* and the datapacked MoLang callbacks but the difference is that these are per-entity which allows
* [CobblemonBehaviour]s to add callbacks very gracefully, rather than running the same code for all
* entities then looking for some other property on the entity.
*
* @author Hiroku
* @since July 26th, 2025
*/
class EntityCallbacks(val entity: Entity) : HashMap<ResourceLocation, MutableList<ResourceLocation>>() {
companion object {
val HIT_BY_POKEBALL = cobblemonResource("hit_by_pokeball")
val SAW_ENTITY = cobblemonResource("saw_entity")
val HURT = cobblemonResource("hurt")
val DIED = cobblemonResource("died")
}
val runtime = MoLangRuntime().setup().also {
it.environment.query.addFunction("entity") { entity.asMostSpecificMoLangValue() }
}
/**
* Adds a script to the callbacks for a specific type. If `allowDuplicates` is false, it will not add
* the script if it already exists in the list for that type.
*
* Returns true if the script was added, false if it was not added due to a duplicate being found and respected.
*/
fun addCallback(type: ResourceLocation, callback: ResourceLocation, allowDuplicates: Boolean): Boolean {
val callbacks = this.getOrPut(type) { mutableListOf() }
if (!allowDuplicates && callback in callbacks) {
return false
}
callbacks.add(callback)
return true
}
fun removeCallback(type: ResourceLocation, callback: ResourceLocation): Boolean {
val callbacks = this[type] ?: return false
return callbacks.remove(callback)
}
fun process(type: ResourceLocation, functions: Map<String, (MoParams) -> MoValue> = hashMapOf()) {
if (type !in this) {
return
}
functions.forEach { (name, function) -> runtime.environment.query.addFunction(name, function) }
val callbacks = this[type]?.mapNotNull { CobblemonScripts.scripts[it] } ?: return
for (callback in callbacks) {
runtime.resolve(callback)
}
}
}
@@ -17,11 +17,11 @@ import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
import com.cobblemon.mod.common.entity.npc.NPCEntity
import com.cobblemon.mod.common.util.DataKeys
import com.mojang.serialization.Dynamic
import com.mojang.serialization.DynamicOps
import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.ListTag
import net.minecraft.nbt.StringTag
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.entity.Entity
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.Brain
import net.minecraft.world.entity.ai.memory.MemoryModuleType
@@ -40,6 +40,7 @@ interface MoLangScriptingEntity {
val registeredVariables: MutableList<MoLangConfigVariable>
var config: VariableStruct
var data: VariableStruct
var callbacks: EntityCallbacks
fun getExtraVariables(): List<MoLangConfigVariable> = emptyList()
fun remakeBrain()
@@ -100,7 +101,7 @@ interface MoLangScriptingEntity {
fun updateBehaviours(behaviours: Collection<ResourceLocation>) {
val removingBehaviours = this@MoLangScriptingEntity.behaviours.filterNot(behaviours::contains).mapNotNull(CobblemonBehaviours.behaviours::get)
removingBehaviours.forEach { behaviour ->
behaviour.undo(this as LivingEntity)
behaviour.onRemove(this as LivingEntity)
}
this@MoLangScriptingEntity.behaviours.clear()
this@MoLangScriptingEntity.behaviours.addAll(behaviours)
@@ -38,6 +38,7 @@ import com.cobblemon.mod.common.api.scheduling.SchedulingTracker
import com.cobblemon.mod.common.api.storage.party.NPCPartyStore
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.entity.BehaviourEditingTracker
import com.cobblemon.mod.common.entity.EntityCallbacks
import com.cobblemon.mod.common.entity.MoLangScriptingEntity
import com.cobblemon.mod.common.entity.OmniPathingEntity
import com.cobblemon.mod.common.entity.PosableEntity
@@ -242,6 +243,7 @@ class NPCEntity(world: Level) : AgeableMob(CobblemonEntities.NPC, world), Npc, P
override val registeredVariables: MutableList<MoLangConfigVariable> = mutableListOf()
override var data = VariableStruct()
override var config = VariableStruct()
override var callbacks = EntityCallbacks(this)
val aspects: Set<String>
get() = entityData.get(ASPECTS)
@@ -9,6 +9,7 @@
package com.cobblemon.mod.common.entity.pokeball
import com.bedrockk.molang.runtime.struct.QueryStruct
import com.bedrockk.molang.runtime.value.DoubleValue
import com.bedrockk.molang.runtime.value.StringValue
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonEntities.EMPTY_POKEBALL
@@ -17,6 +18,7 @@ import com.cobblemon.mod.common.api.events.CobblemonEvents
import com.cobblemon.mod.common.api.events.pokeball.PokeBallCaptureCalculatedEvent
import com.cobblemon.mod.common.api.events.pokeball.ThrownPokeballHitEvent
import com.cobblemon.mod.common.api.events.pokemon.PokemonCapturedEvent
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
import com.cobblemon.mod.common.api.net.serializers.StringSetDataSerializer
import com.cobblemon.mod.common.api.net.serializers.Vec3DataSerializer
import com.cobblemon.mod.common.api.pokeball.PokeBalls
@@ -120,6 +122,7 @@ class EmptyPokeBallEntity : ThrowableItemProjectile, PosableEntity, WaterDragMod
.addFunction("capture_state") { StringValue(captureState.name) }
.addFunction("ball_type") { StringValue(pokeBall.name.toString()) }
.addFunction("aspects") { aspects.asArrayValue { StringValue(it) } }
.addFunction("thrower") { owner?.asMostSpecificMoLangValue() ?: DoubleValue.ZERO }
override fun defineSynchedData(builder: SynchedEntityData.Builder) {
pokeBall = PokeBalls.POKE_BALL
@@ -445,14 +448,7 @@ class EmptyPokeBallEntity : ThrowableItemProjectile, PosableEntity, WaterDragMod
level().playSoundServer(position(), CobblemonSounds.POKE_BALL_HIT, volume = 1F)
// Hit Pokémon plays recoil animation
val pkt = PlayPosableAnimationPacket(pokemonEntity.id, setOf("recoil"), emptyList())
pkt.sendToPlayersAround(
x = pokemonEntity.x,
y = pokemonEntity.y,
z = pokemonEntity.z,
worldKey = pokemonEntity.level().dimension(),
distance = 50.0
)
pokemonEntity.playAnimation("recoil")
// Bounce backwards away from the hit Pokémon
deltaMovement = displace.multiply(-1.0, 0.0, -1.0).normalize().yRot(mul * PI/3).multiply(0.1, 0.0, 0.1).add(0.0, 1.0 / 3, 0.0)
@@ -52,6 +52,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.RidingBehaviours
import com.cobblemon.mod.common.api.riding.events.SelectDriverEvent
import com.cobblemon.mod.common.api.riding.sound.RideSoundManager
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.api.riding.util.RidingAnimationData
import com.cobblemon.mod.common.api.scheduling.Schedulable
@@ -99,10 +100,18 @@ import com.cobblemon.mod.common.pokemon.evolution.variants.ItemInteractionEvolut
import com.cobblemon.mod.common.pokemon.feature.StashHandler
import com.cobblemon.mod.common.pokemon.properties.UncatchableProperty
import com.cobblemon.mod.common.util.*
import com.cobblemon.mod.common.util.math.geometry.toRadians
import com.cobblemon.mod.common.world.gamerules.CobblemonGameRules
import com.mojang.serialization.Codec
import com.mojang.serialization.Dynamic
import java.util.Optional
import java.util.UUID
import java.util.concurrent.CompletableFuture
import kotlin.math.PI
import kotlin.math.ceil
import net.minecraft.client.Minecraft
import net.minecraft.core.BlockPos
import net.minecraft.core.component.DataComponents
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.core.registries.Registries
import net.minecraft.nbt.CompoundTag
@@ -151,15 +160,13 @@ import net.minecraft.world.item.ItemUtils
import net.minecraft.world.item.Items
import net.minecraft.world.level.Level
import net.minecraft.world.level.LightLayer
import net.minecraft.world.level.block.SuspiciousEffectHolder
import net.minecraft.world.level.gameevent.GameEvent
import net.minecraft.world.level.material.FluidState
import net.minecraft.world.level.pathfinder.PathType
import net.minecraft.world.phys.AABB
import net.minecraft.world.phys.Vec3
import java.util.*
import java.util.concurrent.CompletableFuture
import kotlin.math.PI
import kotlin.math.ceil
import kotlin.math.min
@Suppress("unused")
open class PokemonEntity(
@@ -282,7 +289,8 @@ open class PokemonEntity(
var enablePoseTypeRecalculation = true
val ridingAnimationData: RidingAnimationData = RidingAnimationData()
val ridingAnimationData: RidingAnimationData = RidingAnimationData(this)
var rideSoundManager: RideSoundManager = RideSoundManager(this)
var previousRidingState: RidingBehaviourState? = null
var ridingState: RidingBehaviourState? = null
@@ -297,6 +305,8 @@ open class PokemonEntity(
.withQueryValue("entity", struct)
.also {
it.environment.query.addFunction("passenger_count") { DoubleValue(passengers.size.toDouble()) }
it.environment.query.addFunction("ride_velocity") { DoubleValue(getRideVelocity().length()) }
it.environment.query.addFunction("driver_input") { DoubleValue(min(ridingAnimationData.driverInputSpring.value.length(),1.0)) }
it.environment.query.addFunction("get_ride_stats") { params ->
val rideStat = RidingStat.valueOf(params.getString(0).uppercase())
val rideStyle = RidingStyle.valueOf(params.getString(1).uppercase())
@@ -377,6 +387,7 @@ open class PokemonEntity(
override val registeredVariables = mutableListOf<MoLangConfigVariable>()
override var config = VariableStruct()
override var data = VariableStruct()
override var callbacks = EntityCallbacks(this)
var platform: PlatformType
get() = entityData.get(PLATFORM_TYPE)
@@ -555,8 +566,12 @@ open class PokemonEntity(
isPokemonFlying = flyDist - flyDistO > 0.005F
isPokemonWalking = walkDist - walkDistO > 0.005F
if (passengers.isNotEmpty()) {
ridingAnimationData.update(this)
if (passengers.isNotEmpty() && level().isClientSide) {
rideSoundManager.tick()
ridingAnimationData.update()
} else if (!passengers.isNotEmpty() && level().isClientSide)
{
rideSoundManager.stop()
}
flyDistO = flyDist
@@ -639,6 +654,11 @@ open class PokemonEntity(
super.customServerAiStep()
}
fun getRideVelocity(): Vec3 {
return this.ridingAnimationData.velocitySpring.value
//return this.position().subtract(prevPosition)
}
fun setMoveControl(moveControl: MoveControl) {
this.moveControl = moveControl
}
@@ -1057,22 +1077,10 @@ open class PokemonEntity(
player.playSound(SoundEvents.MOOSHROOM_MILK, 1.0f, 1.0f)
// if the Mooshtank ate a Flower beforehand
if (pokemon.lastFlowerFed != ItemStack.EMPTY && pokemon.aspects.any { it.contains("mooshtank-brown") }) {
when (pokemon.lastFlowerFed.item) {
Items.ALLIUM -> MobEffects.FIRE_RESISTANCE to 80
Items.AZURE_BLUET -> MobEffects.BLINDNESS to 160
Items.BLUE_ORCHID, Items.DANDELION -> MobEffects.SATURATION to 7
Items.CORNFLOWER -> MobEffects.JUMP to 120
Items.LILY_OF_THE_VALLEY -> MobEffects.POISON to 240
Items.OXEYE_DAISY -> MobEffects.REGENERATION to 160
Items.POPPY, Items.TORCHFLOWER -> MobEffects.NIGHT_VISION to 100
Items.PINK_TULIP, Items.RED_TULIP, Items.WHITE_TULIP, Items.ORANGE_TULIP -> MobEffects.WEAKNESS to 180
Items.WITHER_ROSE -> MobEffects.WITHER to 160
CobblemonItems.PEP_UP_FLOWER -> MobEffects.LEVITATION to 160
else -> null
}?.let {
SuspiciousEffectHolder.tryGet(pokemon.lastFlowerFed.item)?.let {
// modify the suspicious stew with the effect
val susStewStack = Items.SUSPICIOUS_STEW.defaultInstance
//SuspiciousStewItem.addEffectsToStew(susStewStack, listOf(StewEffect(it.first, it.second)))
val susStewStack = Items.SUSPICIOUS_STEW.defaultInstance.copy()
susStewStack.set(DataComponents.SUSPICIOUS_STEW_EFFECTS, it.suspiciousEffects)
val susStewEffect = ItemUtils.createFilledResult(itemStack, player, susStewStack)
//give player modified Suspicious Stew
player.setItemInHand(hand, susStewEffect)
@@ -1105,7 +1113,7 @@ open class PokemonEntity(
itemStack.`is`(Items.WITHER_ROSE) ||
itemStack.`is`(CobblemonItems.PEP_UP_FLOWER)
) {
if (pokemon.aspects.any { it.contains("mooshtank") }) {
if (pokemon.aspects.any { it.contains("mooshtank-brown") }) {
player.playSound(SoundEvents.MOOSHROOM_EAT, 1.0f, 1.0f)
pokemon.lastFlowerFed = itemStack
return InteractionResult.sidedSuccess(level().isClientSide)
@@ -1184,8 +1192,7 @@ open class PokemonEntity(
this.getUUID(),
canSitOnShoulder() && pokemon in player.party(),
!(pokemon.heldItemNoCopy().isEmpty && itemStack.isEmpty),
(!pokemon.cosmeticItem.isEmpty && itemStack.isEmpty) || cosmeticItemDefinition != null,
canRide
(!pokemon.cosmeticItem.isEmpty && itemStack.isEmpty) || cosmeticItemDefinition != null, canRide
).sendToPlayer(player)
}
else {
@@ -1215,7 +1222,8 @@ open class PokemonEntity(
if (this.health == 0F) {
pokemon.currentHealth = 0
} else {
} else if (this.ownerUUID != null) {
// Only touch battle HP for non-wild Pokémon so that quick ball et. al. aren't owerpowered.
pokemon.currentHealth = (pokemon.maxHealth * (this.health / this.maxHealth)).toInt()
}
true
@@ -1755,8 +1763,8 @@ open class PokemonEntity(
}
// Rotate velocity vector to face the current y rotation
val f = Mth.sin(this.yRot * 0.017453292f)
val g = Mth.cos(this.yRot * 0.017453292f)
val f = Mth.sin(this.yRot.toRadians())
val g = Mth.cos(this.yRot.toRadians())
val v = Vec3(
inp.x * g.toDouble() - inp.z * f.toDouble(),
inp.y,
@@ -2117,8 +2125,9 @@ open class PokemonEntity(
}
}
val rotation = behaviour.rotation(settings, state, this, driver)
this.yRotO = this.yRot
val rotation = behaviour.rotation(settings, state, this, driver)
setRot(rotation.y, rotation.x)
this.yHeadRot = this.yRot
this.yBodyRot = this.yRot
@@ -2142,23 +2151,6 @@ open class PokemonEntity(
this.jumpInputStrength = 0
}
}
// val lookAngle: Vec3 = driver.getLookAngle()
// if (!driver.isNearGround() || lookAngle.y >= 0.3) {
// setBehaviourFlag(PokemonBehaviourFlag.FLYING, true)
// entityData.set(POSE_TYPE, PoseType.HOVER)
//
// val deltaMovement = this.deltaMovement
// this.setDeltaMovement(
// lookAngle.x * 0.1 + (lookAngle.x * 1.5 - deltaMovement.x) * 1,
// lookAngle.y * 0.1 + (lookAngle.y * 1.5 - deltaMovement.y) * 1,
// lookAngle.z * 0.1 + (lookAngle.z * 1.5 - deltaMovement.z) * 1
// )
// }
// else {
// setBehaviourFlag(PokemonBehaviourFlag.FLYING, false)
// this.setDeltaMovement(0.0, 0.0, 0.0)
// }
}
fun Entity.isNearGround(): Boolean {
@@ -2166,21 +2158,6 @@ open class PokemonEntity(
return this.level().getBlockState(blockBelow).isSolid
}
// override fun jump() {
//// val vec3d = this.velocity
////
////
////
//// this.setVelocity(vec3d.x, this.jumpVelocity.toDouble(), vec3d.z)
//// if (this.isSprinting) {
//// val f = this.yaw * 0.017453292f
//// this.velocity =
//// velocity.add((-MathHelper.sin(f) * 0.2f).toDouble(), 0.0, (MathHelper.cos(f) * 0.2f).toDouble())
//// }
////
//// this.velocityDirty = true
// }
override fun onPassengerTurned(entityToUpdate: Entity) {
if (entityToUpdate !is LivingEntity) return
ifRidingAvailable { behaviour, settings, state ->
@@ -50,6 +50,8 @@ import net.minecraft.world.level.pathfinder.PathType
import org.joml.Matrix3f
import org.joml.Vector3f
import java.util.*
import kotlin.math.ceil
import kotlin.math.max
import kotlin.math.min
import kotlin.math.pow
import kotlin.math.round
@@ -155,11 +157,16 @@ class PokemonServerDelegate : PokemonSideDelegate {
fun updateHealth(pokemon: Pokemon) {
if (acknowledgedHPValue != pokemon.currentHealth) {
acknowledgedHPValue = pokemon.currentHealth
val currentHPRatio: Float = pokemon.currentHealth / pokemon.maxHealth.toFloat()
if (currentHPRatio.isFinite()) {
// Entity Health is stored as float but only ever in-/decreased by whole numbers.
entity.health = round(entity.maxHealth * currentHPRatio)
if (pokemon.currentHealth <= 0) {
entity.health = 0.0f
} else {
val currentHPRatio: Float = pokemon.currentHealth / pokemon.maxHealth.toFloat()
if (currentHPRatio.isFinite()) {
// Entity Health is stored as float but only ever in-/decreased by whole numbers.
entity.health = ceil(entity.maxHealth * currentHPRatio)
}
}
}
}
@@ -9,6 +9,7 @@
package com.cobblemon.mod.common.entity.pokemon.ai.tasks
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.ai.CobblemonBlockPosTracker
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import net.minecraft.world.entity.ai.behavior.EntityTracker
import net.minecraft.world.entity.ai.behavior.OneShot
@@ -33,11 +34,16 @@ object LookAtTargetedBattlePokemonTask {
Trigger { world, entity, _ ->
val targeted = it.tryGet(targetedBattlePokemon).orElse(null)
val targetedEntity = targeted?.let { world.getEntity(it) as? PokemonEntity }
val look = it.tryGet(lookTarget).orElse(null) as? EntityTracker
var look = it.tryGet(lookTarget).orElse(null)
if (look is CobblemonBlockPosTracker && look.isBattleLookBypass()) {
return@Trigger false
} else if (look !is EntityTracker) {
look = null
}
if (targeted != null && targetedEntity == null) {
entity.brain.eraseMemory(CobblemonMemories.TARGETED_BATTLE_POKEMON)
return@Trigger false
} else if (targetedEntity != null && look?.entity != targetedEntity) {
} else if (targetedEntity != null && (look as? EntityTracker)?.entity != targetedEntity) {
entity.brain.setMemory(MemoryModuleType.LOOK_TARGET, EntityTracker(targetedEntity, true))
return@Trigger true
} else if (targetedEntity == null) {
@@ -0,0 +1,44 @@
/*
* 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.events
import com.bedrockk.molang.runtime.MoParams
import com.bedrockk.molang.runtime.value.MoValue
import com.cobblemon.mod.common.api.Priority
import com.cobblemon.mod.common.api.events.CobblemonEvents.THROWN_POKEBALL_HIT
import com.cobblemon.mod.common.api.events.pokeball.ThrownPokeballHitEvent
import com.cobblemon.mod.common.api.reactive.Observable
import com.cobblemon.mod.common.entity.EntityCallbacks
import com.cobblemon.mod.common.entity.EntityCallbacks.Companion.HIT_BY_POKEBALL
import com.cobblemon.mod.common.entity.MoLangScriptingEntity
import net.minecraft.resources.ResourceLocation
/**
* Handles the registration of entity callbacks for events related to entities. You can add your own
* as well, all this actually does is subscribe to the event and pass it through the [EntityCallbacks] of
* the entity the event is about. In general, entity callbacks occur on the normal priority so that
* plugins can jump in ahead but on parity with the general MoLang callbacks.
*
* @author Hiroku
* @since July 26th, 2025
*/
object EntityCallbackHandler {
fun setup() {
bindCallback(THROWN_POKEBALL_HIT, HIT_BY_POKEBALL, ThrownPokeballHitEvent::pokemon, ThrownPokeballHitEvent::functions)
}
fun <T> bindCallback(
observable: Observable<T>,
type: ResourceLocation,
entity: (T) -> MoLangScriptingEntity,
functions: (T) -> Map<String, (MoParams) -> MoValue>
) {
observable.subscribe(priority = Priority.NORMAL) { entity(it).callbacks.process(type, functions(it)) }
}
}
@@ -37,7 +37,7 @@ object FlowHandler {
CobblemonEvents.BATTLE_STARTED_POST.subscribe { CobblemonFlows.run(cobblemonResource("battle_started_post"), it.context) }
CobblemonEvents.APRICORN_HARVESTED.subscribe { CobblemonFlows.run(cobblemonResource("apricorn_harvested"), it.context) }
CobblemonEvents.BERRY_HARVEST.subscribe { CobblemonFlows.run(cobblemonResource("berry_harvested"), it.context) }
CobblemonEvents.THROWN_POKEBALL_HIT.subscribe { CobblemonFlows.run(cobblemonResource("thrown_pokeball_hit"), it.context) }
CobblemonEvents.THROWN_POKEBALL_HIT.subscribe { CobblemonFlows.run(cobblemonResource("thrown_pokeball_hit"), emptyMap(), functions = it.functions) }
CobblemonEvents.POKEMON_HEALED.subscribe { CobblemonFlows.run(cobblemonResource("pokemon_healed"), it.context, it.functions) }
CobblemonEvents.POKEMON_ASPECTS_CHANGED.subscribe { CobblemonFlows.run(cobblemonResource("pokemon_aspects_changed"), it.context) }
CobblemonEvents.LEVEL_UP_EVENT.subscribe { CobblemonFlows.run(cobblemonResource("level_up"), it.context, it.functions) }
@@ -140,7 +140,7 @@ class AprijuiceItem(val type: Apricorn): CobblemonItem(Properties().stacksTo(16)
val hasFlavour = stack.get(CobblemonItemComponents.FLAVOUR)?.flavours?.any { it.value > 0 } == true
if (!hasFlavour && user is Player && !world.isClientSide) {
user.foodData.eat(1, 0.1f)
user.foodData.eat(4, 1.2f)
stack.consume(1, user)
}
@@ -383,7 +383,6 @@ object CobblemonItemGroups {
private fun consumableEntries(displayContext: ItemDisplayParameters, entries: Output) {
entries.accept(CobblemonItems.ROASTED_LEEK)
entries.accept(CobblemonItems.LEEK_AND_POTATO_STEW)
entries.accept(CobblemonItems.BRAISED_VIVICHOKE)
entries.accept(CobblemonItems.VIVICHOKE_DIP)
entries.accept(CobblemonItems.TART_APPLE)
entries.accept(CobblemonItems.SWEET_APPLE)
@@ -399,8 +398,9 @@ object CobblemonItemGroups {
entries.accept(CobblemonItems.CANDIED_APPLE)
entries.accept(CobblemonItems.CANDIED_BERRY)
entries.accept(CobblemonItems.FRIED_RICE)
entries.accept(CobblemonItems.POTATO_MOCHI)
entries.accept(CobblemonItems.PONIGIRI)
entries.accept(CobblemonItems.SINISTER_TEA)
entries.accept(CobblemonItems.PEWTER_CRUNCHIES)
entries.accept(CobblemonItems.RAGE_CANDY_BAR)
@@ -409,7 +409,6 @@ object CobblemonItemGroups {
entries.accept(CobblemonItems.OLD_GATEAU)
entries.accept(CobblemonItems.CASTELIACONE)
entries.accept(CobblemonItems.LUMIOSE_GALETTE)
entries.accept(CobblemonItems.SHALOUR_SABLE)
entries.accept(CobblemonItems.BIG_MALASADA)
entries.accept(CobblemonItems.SMOKED_TAIL_CURRY)
entries.accept(CobblemonItems.OPEN_FACED_SANDWICH)
@@ -419,6 +418,7 @@ object CobblemonItemGroups {
entries.accept(CobblemonItems.FINE_REMEDY)
entries.accept(CobblemonItems.SUPERB_REMEDY)
entries.accept(CobblemonItems.HEAL_POWDER)
entries.accept(CobblemonItems.MOOMOO_MILK)
entries.accept(CobblemonItems.MEDICINAL_BREW)
entries.accept(CobblemonItems.POTION)
@@ -518,10 +518,23 @@ object CobblemonItemGroups {
entries.accept(CobblemonItems.JOLLY_MINT)
entries.accept(CobblemonItems.NAIVE_MINT)
entries.accept(CobblemonItems.SERIOUS_MINT)
entries.accept(CobblemonItems.MOOMOO_MILK)
entries.accept(CobblemonItems.ABILITY_CAPSULE)
entries.accept(CobblemonItems.ABILITY_PATCH)
entries.accept(CobblemonItems.POKE_BAIT)
entries.accept(CobblemonItems.LURE_CAKE)
entries.accept(CobblemonItems.POKE_CAKE)
entries.accept(CobblemonItems.POKE_PUFF)
entries.accept(CobblemonItems.APRIJUICE_RED)
entries.accept(CobblemonItems.APRIJUICE_YELLOW)
entries.accept(CobblemonItems.APRIJUICE_GREEN)
entries.accept(CobblemonItems.APRIJUICE_BLUE)
entries.accept(CobblemonItems.APRIJUICE_PINK)
entries.accept(CobblemonItems.APRIJUICE_BLACK)
entries.accept(CobblemonItems.APRIJUICE_WHITE)
}
private fun evolutionItemEntries(displayContext: ItemDisplayParameters, entries: Output) {
@@ -742,7 +755,6 @@ object CobblemonItemGroups {
injector.putAfter(CobblemonItems.MEDICINAL_LEEK, Items.POISONOUS_POTATO)
injector.putAfter(CobblemonItems.ROASTED_LEEK, CobblemonItems.MEDICINAL_LEEK)
injector.putAfter(CobblemonItems.BRAISED_VIVICHOKE, CobblemonItems.ROASTED_LEEK)
injector.putAfter(CobblemonItems.WHIPPED_DREAM, Items.PUMPKIN_PIE)
injector.putAfter(CobblemonItems.STRAWBERRY_SWEET, CobblemonItems.WHIPPED_DREAM)
@@ -755,24 +767,24 @@ object CobblemonItemGroups {
injector.putAfter(CobblemonItems.CANDIED_APPLE, CobblemonItems.RIBBON_SWEET)
injector.putAfter(CobblemonItems.CANDIED_BERRY, CobblemonItems.CANDIED_APPLE)
injector.putAfter(CobblemonItems.FRIED_RICE, CobblemonItems.CANDIED_BERRY)
injector.putAfter(CobblemonItems.POTATO_MOCHI, CobblemonItems.FRIED_RICE)
injector.putAfter(CobblemonItems.POTATO_MOCHI, CobblemonItems.CANDIED_BERRY)
injector.putAfter(CobblemonItems.PONIGIRI, CobblemonItems.POTATO_MOCHI)
injector.putAfter(CobblemonItems.PEWTER_CRUNCHIES, CobblemonItems.POTATO_MOCHI)
injector.putAfter(CobblemonItems.PEWTER_CRUNCHIES, CobblemonItems.PONIGIRI)
injector.putAfter(CobblemonItems.RAGE_CANDY_BAR, CobblemonItems.PEWTER_CRUNCHIES)
injector.putAfter(CobblemonItems.LAVA_COOKIE, CobblemonItems.RAGE_CANDY_BAR)
injector.putAfter(CobblemonItems.JUBILIFE_MUFFIN, CobblemonItems.LAVA_COOKIE)
injector.putAfter(CobblemonItems.OLD_GATEAU, CobblemonItems.JUBILIFE_MUFFIN)
injector.putAfter(CobblemonItems.CASTELIACONE, CobblemonItems.OLD_GATEAU)
injector.putAfter(CobblemonItems.LUMIOSE_GALETTE, CobblemonItems.CASTELIACONE)
injector.putAfter(CobblemonItems.SHALOUR_SABLE, CobblemonItems.LUMIOSE_GALETTE)
injector.putAfter(CobblemonItems.BIG_MALASADA, CobblemonItems.SHALOUR_SABLE)
injector.putAfter(CobblemonItems.BIG_MALASADA, CobblemonItems.LUMIOSE_GALETTE)
injector.putAfter(CobblemonItems.SMOKED_TAIL_CURRY, CobblemonItems.BIG_MALASADA)
injector.putAfter(CobblemonItems.OPEN_FACED_SANDWICH, CobblemonItems.SMOKED_TAIL_CURRY)
injector.putAfter(CobblemonItems.LEEK_AND_POTATO_STEW, Items.RABBIT_STEW)
injector.putAfter(CobblemonItems.VIVICHOKE_DIP, CobblemonItems.LEEK_AND_POTATO_STEW)
injector.putAfter(CobblemonItems.SINISTER_TEA, Items.MILK_BUCKET)
}
private fun toolsAndUtilitiesInjections(injector: Injector) {
@@ -25,8 +25,8 @@ class PonigiriItem : Item(
Properties().stacksTo(16)
.food(FoodProperties.Builder()
.alwaysEdible()
.nutrition(2)
.saturationModifier(0.55f)
.nutrition(4)
.saturationModifier(0.275f)
.build())
) {
override fun getName(stack: ItemStack): Component {
Loaded 100 of 301 files, more files were not shown because too many files have changed in this diff. Show more