Compare commits

..
2530 changed files with 71517 additions and 512215 deletions

No files matched your search

-3
View File
@@ -15,9 +15,6 @@
- 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(...)`
### Fixes
- Fixed Pokémon with a moving walk target frequently spinning in place as they re-calculate their path.
### 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.
+1 -2
View File
@@ -1,7 +1,6 @@
### Additions
- Added functionality for marks.
- Added data for all marks and ribbons from the mainline Pokémon games.
- Implemented fishing, personality, weather, and time related marks, that can rarely be granted to a wild-spawned Pokémon.
- Added data for all marks and all title ribbons from Pokémon.
- Added marks screen within the summary UI.
- Added `/givemark <player> <partySlot> <mark>` command to give a mark to a Pokémon.
- Added `/takemark <player> <partySlot> <mark>` command to remove a mark from a Pokémon.
+2 -11
View File
@@ -1,28 +1,19 @@
### Additions
- Added ability to rename PC Boxes by clicking on the name of a box.
- Added box options buttons for boxes, toggleable by clicking the right icon button in the bottom bar.
- The option buttons on the left side allow for sorting the box by name, level, Pokédex number, gender, and type. Shift clicking allows for sorting in reverse order.
- The button on the right allows for changing the box wallpaper.
- 10 additional color-based wallpapers have been added.
- 5 unlockable biome-based wallpapers have been added, which can be unlocked by visiting:
- Forest biome
- Ocean biome
- Cave Biome
- The End
- The Nether
- The option buttons on the left side allow for sorting the box by name, level, Pokédex number, gender, and type. Shift clicking allows for sorting in reverse order.
- Added filter functionality in PC UI, which supports `PokemonProperties` (e.g. `shiny=yes` shows all shiny Pokémon).
- Names can be filtered by exact or partial matches, e.g. entering "cha" will show Charmander, Charmeleon, etc.
- Added `/changewallpaper <player> <boxNumber> <wallpaper>` command to change a box wallpaper through commands.
- Added `/renamebox <player> <boxNumber> <name>` command to rename a PC box through commands.
- Added IVs and EVs stat displays in PC.
- The displays can be cycled through by scrolling when the display is hovered over.
- Added ability to cycle through boxes by scrolling when the box is hovered over.
- Added ability to change boxes by scrolling.
- PC will now open to last box viewed within a session.
### Changes
- Slots in pastures will now have darker backgrounds and italicised Pokémon names if the Pokémon is not owned by the player.
- The pasture slot will show the name of the Pokémon owner when hovered if the Pokémon is not owned.
- Increased the default PC box count from 30 to 40 to accommodate the fact that we have way more Pokémon now. If you've changed this config option then you'll need to edit it again after starting up with 1.7!
### Developer
- Added `RenamePCBoxEvent.Pre` and `RenamePCBoxEvent.Post` events to prevent players from renaming a box or changing their input.
-1
View File
@@ -4,7 +4,6 @@
### Fixes
- Fixed the "enabled" property in spawn files not actually being respected. Where do they find these devs?
- Fixed Terralith's shrubland not counting as plains for spawning purposes the way it was intended to.
### Developer
- Spawning Influences now have the context of what the other buckets are when adjusting bucket weights. This will break existing influences that do bucket weight adjustment.
+12 -312
View File
@@ -2,7 +2,6 @@
## [1.7.0 (Month xth, 2025)](#1-7-0)
### Additions
- Added `/npcdelete` uuid parameter support and usuable to console
- Added `/spectateBattle <player>` command to spectate battles without having to manually walk up to the target.
- Added an in-game configuration screen, allowing all settings from `main.json` to be edited directly in-game.
- Added `/cobblemonconfig reload` command to reload `main.json` configuration. **Note:** Some settings require a server restart to take effect; use this command cautiously.
@@ -12,11 +11,11 @@
- Pokémon's held items can now be rendered, with a visibility toggle in the Summary screen.
- Added cosmetic item functionality for Pokémon. Certain cosmetic items can be given to applicable Pokémon via the interact menu.
- Added the various log blocks as cosmetic items for Timburr and Komala.
- Added cosmetics for Spoink, Gurdurr, Conkeldurr, Squirtle Line, Sneasler, Sandile line, Treecko line, Braixen, Delphox, and Dragonite.
- Added Gilded Chest cosmetics for Chest Gimmighoul and Gholdengo
- Added `visibility/hidden`, `visibility/hat` and `visibility/face` tags to control where and how certain items are rendered.
- Added Pokémon markings, toggleable within the summary.
- Added `/boxcount` command to change PC boxes amount
- Added cosmetics for Gurdurr, Conkeldurr, Squirtle Line, Sneasler, Sandile line, Treecko line, Braixen, Delphox, and Dragonite.
- Added cosmetics for Gurdurr, Conkeldurr, Squirtle Line, Sneasler, Sandle line, Treecko line, Braixen, Delphox, and Dragonite.
- Added `/transformmodelpart (position|rotation|scale) <modelPart> <transform: x y z>` command that can add transformations to a pokemon's model part.
- The player executing the command must be facing the target pokemon entity. Transformations are not persistent and will revert when resources are reloaded.
- Added `legacy` and `special` sourced moves to Pokémon.
@@ -24,7 +23,7 @@
- Added `translucent_cull` boolean option into resolver's layer to allow for translucent textures with culling
- Added [LambDynamicLights](https://modrinth.com/mod/lambdynamiclights) support for items held by Pokémon, evolution stone blocks, evolution stone items, Pokédex, Luminous Moss, Flame Orb, and Magmarizer.
- Added the Clear Amulet, Grip Claw, Lagging Tail, Luminous Moss, Metal Alloy, Scroll of Darkness, Scroll of Waters
- Added Recipes for Masterpiece Cup, Eject Pack.
- Added Recipes for Masterpiece Cup, Eject Pack
- Added modification to Minecraft Creative Inventory search to account for item names that contain `poké` when input contains `poke`.
- Added Campfire Pot as well as loads of new food items (Poke Puffs, Ponigiri, Sinister Tea, etc)
- Added Hearty Grains, a new crop used in the new cooking mechanic
@@ -40,7 +39,6 @@
- Pokémon with the ability Volt Absorb are immune to lightning damage and receive Regeneration II for a short duration
- 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 optional `filePath` parameter to MoLang functions to allow for global stores (ex. `../global/`). Path must end in a forward slash.
- 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.
- Some Pokémon now pitch their bodies in the direction they're moving.
@@ -48,11 +46,9 @@
- Berries can now be smelted into their respective dyes.
- Added Syrupy Apples.
- Added `/runmolang <molang> [<npc>|<player>|<pokemon>]` command that executes a MoLang expression with the provided options as environment variables, as well as the entity (as `q.entity`) that executed the command.
- Added bubble quirk to Kingler; like Krabby, Kingler will blows bubbles during dusk.
- Added `.Pre` and `.Post` to the following events:
- `PokemonRecallEvent`
- `TradeEvent`
- Added a new gamerule, 'healerHealsPC', when set to true a successful use of a healer will also heal all of the Pokemon in that player's PC.
- Added [LambDynamicLights](https://modrinth.com/mod/lambdynamiclights) support for items held by Pokémon.
- Fixed Moon Ball moon phase logic to actually work correctly
- Added the Moves tab to the Pokédex, allowing players to view all of the move categories a form can obtain.
### Pokémon Added
@@ -68,9 +64,6 @@
- Unown
- Houndour
- Houndoom
- Ho-Oh
- Lugia
- Alola Bias Pichu (built-in resourcepack)
#### Gen 3
- Azurill
@@ -86,6 +79,8 @@
- Glalie
- Latias
- Latios
- Swablu
- Altaria
#### Gen 4
- Bronzor
@@ -144,8 +139,6 @@
- Dedenne
- Noibat
- Noivern
- Furfrou
- You can change Furfrou's form by using shears on it while it holds a certain dye in its cosmetic slot.
#### Gen 7
- Yungoos
@@ -200,10 +193,9 @@
### Animation updates for the following Pokémon
- Garchomp
- Tropius
- Torpius
- Nosepass
- Probopass
- Sneasel
- Weavile
- Sneasler
- Braixen
- Delphox
@@ -225,79 +217,8 @@
- Porygon
- Porygon2
- Porygon-Z
- Wattrel
- Kilowattrel
- Golurk
- Spinarak
- Ariados
- Wyrdeer
- Tyrantrum
- Pidove
- Tranquill
- Unfezant
- Beldum
- Metang
- Metagross
- Plusle
- Minun
- Murkrow
- Honchkrow
- Larvesta
- Volcarona
- Dwebble
- Crustle
- Mr. Mime
- Flygon
- Pichu
- Pikachu
- Raichu
- Alolan Raichu
- Espathra
- Abra
- Kadabra
- Alakazam
- Deino
- Zweilous
- Hydreigon
- Slaking
- Klink
- Klang
- Klinklang
- Baltoy
- Claydol
- Mamoswine
- Rufflet
- Braviary
- Girafarig
- Farigiraf
- Rookidee
- Corvisquire
- Corviknight
- Venipede
- Whirlipede
- Scolipede
- Venusaur
- Parasect
- Bastiodon
- Amaura
- Varoom
- Revavroom
- Bouffalant
- Lickilicky
- Blaziken
- Rhyhorn
- Rhydon
- Rhyperior
- Tauros
- Tauros - Paldea Aqua
- Tauros - Paldea Blaze
- Tauros - Paldea Combat
- Dewgong
### Model updates for the following Pokémon
- Cleffa
- Clefairy
- Clefable
- Gyarados
- Dragonite
- Eevee
@@ -358,51 +279,6 @@
- Klink
- Klank
- Klinklang
- Spinarak
- Ariados
- Pidove
- Tranquill
- Unfezant
- Sobble
- Drizzile
- Inteleon
- Plusle
- Minun
- Murkrow
- Zorua
- Zoroark
- Mime.Jr
- Mr. Mime
- Pichu
- Pikachu
- Raichu
- Alolan Raichu
- Dusknoir
- Deino
- Zweilous
- Hydreigon
- Dreepy
- Drakloak
- Dragapult
- Mamoswine
- Rookidee
- Corvisquire
- Corviknight
- Venipede
- Whirlipede
- Scolipede
- Farigiraf
- Staryu
- Starmie
- Gimmighoul
- Gholdengo
- Starly
- Staravia
- Staraptor
- Varoom
- Revavroom
- Nickit
- Thievul
### Changes
- Changed pokemon caught and seen count to update based on the current pokedex being looked
@@ -456,17 +332,9 @@
- 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.
- 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.
- Refactored the following events to `.Pre` and `.Post` for consistency:
- `PokemonSentEvent`
- `ExperienceGainedEvent`
- `BattleStartedEvent`
- Updated `PokemonSentEvent` parameters to include the Position and Level of the Pokémon being sent out.
- Updated `EvolutionCompleteEvent` parameters to include the Source Pokemon that evolved.
- Updated `HatchEggEvent.Post` to include the Pokemon that hatched.
- Roseli Berry Trees now naturally generate in their preferred biomes.
- Refactored dynamic lights compat to be more future proof. The dynamic light support is only tested with LambDynamicLights, on NeoForged use that mod in combination with Sinytra Connector
### Fixes
- Fixed game crashing when removing national pokedex using datapacks
@@ -541,16 +409,6 @@
- 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.
- Fixed BotanyPots built-in integration
- Fixed shading of berries on berry trees.
- Fixed static text cursor for nickname input in summary.
- Fixed Cobblemon brewing recipes placing result into all slots, and not just slots with correct ingredients inside the brewing stand.
- Fixed an issue where hoppers and brewing stands were not recognizing Cobblemon brewing recipes.
- Fixed global species features... not working. Since they were created. Oops.
- Fixed Pokémon with alternate forms being created with an incorrect 'forced' tag on their ability.
- Fixed busted abilities and moves in Pokémon data due to removed datapacks etc. causing storage corruption. It now just rerolls their ability / uses Tackle.
- Fixed singular Pokémon corruption causing entire storage corruption. Storages will now skip corrupted Pokémon and print an error to console.
- Fixed species additions not being able to properly mark a species as implemented.
- Fixed Pokémon item models not showing a glint when enchanted.
### Developer
- A finished battle now has winners and losers set inside of `PokemonBattle` instead of them always being empty.
@@ -564,6 +422,7 @@
- Reworked observable handling in `Pokemon.kt` to cut down on RAM usage and clarify the file.
- Note: This will break mods that used our observable functionality there or in MoveSet, IVs, EVs, or BenchedMoves.
- Using `Pokemon#onChange()` is now the way to mark a Pokémon as needing a save.
- Using `[Pokemon].changeObservable` is now the way to get an `Observable` for any save-worthy changes.
- Updated NPCEntity beam positioning to properly account for the baseScale property.
- Updated NPCEntity pokeball throw positioning to properly account for the baseScale property.
- Fixed `[Pokemon].copyFrom` error causing forms, IVs, and EVs to not be applied properly when using `[Pokemon].loadFromJSON` or `[Pokemon].loadFromNBT`
@@ -592,6 +451,7 @@
- Added `Pokemon#hyperTrainIV()` and `IVs#setHyperTrainedIV(Stat, Int)`
- `ElementalType` now implements `ShowdownIdentifiable` to ensure the communcation with showdown stays consistent (also in regards to TeraTypes)
- Pokemon no longer have a change observable
- Fixed `[SpawnPool].observable` not emitting when the spawn pool is reloaded.
### 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)
@@ -644,166 +504,6 @@
- Added `is_included`, `to_lower`, `to_upper`, and `string_length` as available Molang functions.
- Fixed a crash that would occur during battles if the opponent wild Pokémon species comes from a namespace other than cobblemon
- Fixed `clientActions` inside Dialogue pages being executed twice
- Added `q.split_string(<text>, <delimiter/comma>)` Outputs an array with the divided text.
- Added `q.spawn_npc(<x>, <y>, <z>, <npc>)` Spawns an NPC at the given coordinates and returns the NPC.
- Added `q.player.seen_credits` Returns whether the player has seen the end credits.
- Added `q.player.is_in_dialogue` Returns whether the player is currently in a dialogue.
- Added `q.player.active_dialogue` Returns the active dialogue of the player, or null if none.
- Added `q.player.is_spectator` Returns whether the player is in spectator mode.
- Added `q.player.is_adventure` Returns whether the player is in adventure mode.
- Added `q.player.is_creative` Returns whether the player is in creative mode.
- Added `q.player.is_survival` Returns whether the player is in survival mode.
- Added `q.player.set_battle_theme(<sound resource>)` Sets the player's battle theme to the given theme.
- Added `q.player.battle_music(<sound_resource>, <volume>, <pitch>)` Plays the given battle music for the player.
- Added `q.player.stop_battle_music()` Stops any currently playing battle music for the player. (With a fade out).
- Added `q.player.riding_pokemon` Returns the Pokémon the player is currently riding.
- Added `q.entity.is_player` Returns whether the entity is a player.
- Added `q.entity.is_pokemon` Returns whether the entity is a Pokémon.
- Added `q.entity.is_npc` Returns whether the entity is an NPC.
- Added `q.entity.is_mob` Returns whether the entity is a vanilla mob.
- Added `q.entity.is_animal` Returns whether the entity is an animal.
- Added `q.entity.is_hostile` Returns whether the entity is a hostile mob.
- Added `q.entity.is_baby` Returns whether the entity is a baby.
- Added `q.entity.is_adult` Returns whether the entity is an adult.
- Added `q.entity.is_tamable` Returns whether the entity is tamable.
- Added `q.entity.is_tamed` Returns whether the entity is tamed.
- Added `q.entity.add_effect(<effect>, <duration>, <amplifier>, <ambient> <show_particles>)` Adds a status effect to the entity.
- Added `q.entity.remove_effect(<effect>)` Removes a status effect from the entity.
- Added `q.entity.has_effect(<effect>)` Returns whether the entity has the given status effect.
- Added `q.entity.is_looking_at(<entity>)` Returns whether the entity is looking at the given entity.
- Added `q.entity.tags` Returns an array of the entity's tags.
- Added `q.entity.add_tag(<tag>)` Adds the given tag to the entity.
- Added `q.entity.remove_tag(<tag>)` Removes the given tag from the entity.
- Added `q.entity.has_tag(<tag>)` Returns whether the entity has the given tag.
- Added `q.entity.make_intangible(<true/false>)` Makes the entity intangible.
- Added `q.pokemon.force_evolve(<index>)` Forces the Pokémon at the given index.
- Added `q.pokemon.can_evolve` Returns whether the Pokémon at the given index can evolve.
- Added `q.pokemon.is_busy` Returns whether the Pokémon is busy.
- Added `q.pokemon.is_rideable` Returns whether the Pokémon is rideable.
- Fixed `background` field and added `textColor` field for dialogues.
- Added support to reload some data registries:
- MoLang scripts
- Callbacks
- Spawn Detail Presets
- Spawn Pools
- Spawn Rules
- Cosmetic Items
- Dialogues
- Fossils
- Natural Materials
- Action Effects
- Mechanics
- Unlockable Wallpapers
- Starter Data
### Particles
Added new/updated particles for the following moves:
- Absorb
- Acid
- Acid Spray
- Aerial Ace
- Amnesia
- Aurora Beam
- Bite
- Body Slam
- Bubble
- Bubble Beam
- Bulldoze
- Bullet Punch
- Close Combat
- Confusion
- Crunch
- Cotton Guard
- Double Team
- Dragon Claw
- Egg Bomb
- Ember
- Eruption
- Explosion
- Fire Blast
- Fire Punch
- Fire Spin
- Flame Burst
- Flame Charge
- Flamethrower
- Flame Wheel
- Frost Breath
- Frustration
- Giga Drain
- Growl
- Haze
- Hidden Power
- Horn Attack
- Hyper Fang
- Hypnosis
- Ice Beam
- Ice Punch
- Ice Shard
- Icy Wind
- Infestation
- Kinesis
- Lava Plume
- Leech Life
- Leech Seed
- Leer
- Lick
- Magical Leaf
- Mega Drain
- Minimize
- Mist
- Mud Bomb
- MudSport
- Mystical Fire
- Nasty Plot
- Poison Fang
- Poison Gas
- Poison Powder
- Population Bomb
- Pound
- Powder
- Powder Snow
- Present
- Protect
- Psybeam
- Psychic
- Pursuit
- Quick Attack
- Rage Powder
- Razor Leaf
- Return
- Rock Throw
- Sand Attack
- Scary Face
- Scratch
- Seed Bomb
- Seismic Toss
- Self Destruct
- Shadow Ball
- Shadow Claw
- Sing
- Sleep Powder
- Sludge Bomb
- Smokescreen
- Spike
- Spore
- Stomp
- String Shot
- Stun Spore
- Super Fang
- Swords Dance
- Tackle
- Tail Whip
- Thunder
- Thunder Punch
- Thunderbolt
- Thunder Shock
- Thunder Wave
- Toxic
- Water Gun
- Water Pulse
- Water Sport
- Will-O-Wisp
- Withdraw
## [1.6.1 (January 26th, 2025)](#1-6-1)
+1 -8
View File
@@ -31,7 +31,6 @@ repositories {
maven(url = "${rootProject.projectDir}/deps")
maven(url = "https://api.modrinth.com/maven")
maven(url = "https://maven.neoforged.net/releases")
maven(url = "https://maven.gegy.dev")
mavenLocal()
}
@@ -45,12 +44,6 @@ dependencies {
modCompileOnly(libs.bundles.common.integrations.compileOnly) {
isTransitive = false
}
// LambDynamicLights is handled differently because we need the Mojang-mappings version of it.
modCompileOnly(libs.lambDynamicLights) {
capabilities {
requireCapability("dev.lambdaurora.lambdynamiclights:api-mojmap")
}
}
// Flywheel has no common dep so just pick one and don't use any platform specific code in common
// modCompileOnly(libs.flywheelFabric)
@@ -101,4 +94,4 @@ sourceSets {
}
}
}
}
}
@@ -0,0 +1,102 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.compat;
import com.cobblemon.mod.common.CobblemonEntities;
import com.cobblemon.mod.common.client.render.layer.PokemonOnShoulderRenderer;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import com.cobblemon.mod.common.item.PokedexItem;
import com.cobblemon.mod.common.pokemon.FormData;
import com.cobblemon.mod.common.pokemon.lighthing.LightingData;
import dev.lambdaurora.lambdynlights.LambDynLights;
import dev.lambdaurora.lambdynlights.api.DynamicLightHandler;
import dev.lambdaurora.lambdynlights.api.DynamicLightHandlers;
import net.minecraft.core.BlockPos;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.item.ItemStack;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Optional;
import net.minecraft.world.entity.player.Player;
// Java class due to relying on entrypoint on Fabric side
public class LambDynamicLightsCompat {
// This is all called on client side
// These things are checked every tick so try to keep it as free as possible
// That being said it's unlikely school Toshiba person is running dynamic lighting and shaders to enjoy this
// Make sure not to call anything Fabric specific :)
public static void hookCompat() {
// Pokémon entities
DynamicLightHandlers.registerDynamicLightHandler(
CobblemonEntities.POKEMON,
DynamicLightHandler.makeHandler(
LambDynamicLightsCompat::resolvedPokemonLightLevel,
pokemon -> false
)
);
// Shouldered Pokémon
DynamicLightHandlers.registerDynamicLightHandler(
EntityType.PLAYER,
DynamicLightHandler.makeHandler(
LambDynamicLightsCompat::resolvedPlayerLightLevel,
player -> false
)
);
}
private static int resolvedPokemonLightLevel(PokemonEntity pokemon) {
var underwater = !pokemon.level().getFluidState(BlockPos.containing(pokemon.getX(), pokemon.getEyeY(), pokemon.getZ())).isEmpty();
ItemStack item = pokemon.getShownItem();
int itemLightLevel = item.isEmpty()?0:LambDynLights.getLuminanceFromItemStack(item, underwater);
int formLightLevel = extractFormLightLevel(pokemon.getForm(), underwater).orElse(0);
return Math.max(itemLightLevel, formLightLevel);
}
private static int resolvedPlayerLightLevel(Player player) {
var underwater = !player.level().getFluidState(BlockPos.containing(player.getX(), player.getEyeY(), player.getZ())).isEmpty();
final int leftShoulderLightLevel = extractShoulderLightLevel(PokemonOnShoulderRenderer.shoulderDataFrom(player.getShoulderEntityLeft()), underwater);
final int rightShoulderLightLevel = extractShoulderLightLevel(PokemonOnShoulderRenderer.shoulderDataFrom(player.getShoulderEntityRight()), underwater);
final int itemLightLevel = customHeldItemLightLevel(player.getMainHandItem(), player.getOffhandItem());
return Math.max(itemLightLevel, Math.max(leftShoulderLightLevel, rightShoulderLightLevel));
}
private static Optional<Integer> extractFormLightLevel(@NotNull FormData form, boolean underwater) {
if (form.getLightingData() == null || !liquidGlowModeSupport(form.getLightingData().getLiquidGlowMode(), underwater)) {
return Optional.empty();
}
return Optional.of(form.getLightingData().getLightLevel());
}
private static int extractShoulderLightLevel(@Nullable PokemonOnShoulderRenderer.ShoulderData shoulderData, boolean underwater) {
if (shoulderData == null) {
return 0;
}
ItemStack item = shoulderData.getShownItem();
int itemLightLevel = item.isEmpty() ? 0 :LambDynLights.getLuminanceFromItemStack(item, underwater);
int formLightLevel = extractFormLightLevel(shoulderData.getForm(), underwater).orElse(0);
return Math.max(itemLightLevel, formLightLevel);
}
private static int customHeldItemLightLevel(ItemStack mainhand, ItemStack offhand) {
return (mainhand.getItem() instanceof PokedexItem || offhand.getItem() instanceof PokedexItem) ? 13 : 0;
}
private static boolean liquidGlowModeSupport(@NotNull LightingData.LiquidGlowMode liquidGlowMode, boolean underwater) {
return underwater ? liquidGlowMode.getGlowsUnderwater() : liquidGlowMode.getGlowsInLand();
}
}
@@ -1,28 +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.compat.lambdynamiclights;
import com.cobblemon.mod.common.pokemon.FormData;
import com.cobblemon.mod.common.pokemon.lighthing.LightingData;
import org.jetbrains.annotations.NotNull;
import java.util.Optional;
public abstract class CustomLuminance {
protected static Optional<Integer> extractFormLightLevel(@NotNull FormData form, boolean underwater) {
if (form.getLightingData() == null || !liquidGlowModeSupport(form.getLightingData().getLiquidGlowMode(), underwater)) {
return Optional.empty();
}
return Optional.of(form.getLightingData().getLightLevel());
}
protected static boolean liquidGlowModeSupport(@NotNull LightingData.LiquidGlowMode liquidGlowMode, boolean underwater) {
return underwater ? liquidGlowMode.getGlowsUnderwater() : liquidGlowMode.getGlowsInLand();
}
}
@@ -1,42 +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.compat.lambdynamiclights;
import com.cobblemon.mod.common.Cobblemon;
import dev.lambdaurora.lambdynlights.api.DynamicLightsContext;
import dev.lambdaurora.lambdynlights.api.DynamicLightsInitializer;
import dev.lambdaurora.lambdynlights.api.entity.luminance.EntityLuminance;
import dev.lambdaurora.lambdynlights.api.item.ItemLightSourceManager;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.EntityType;
public class LambDynamicLightsInitializer implements DynamicLightsInitializer {
public static final EntityLuminance.Type POKEMON_LUMINANCE = EntityLuminance.Type.registerSimple(
ResourceLocation.fromNamespaceAndPath(Cobblemon.MODID, "pokemon"),
PokemonLuminance.INSTANCE
);
public static final EntityLuminance.Type PLAYER_LUMINANCE = EntityLuminance.Type.registerSimple(
ResourceLocation.fromNamespaceAndPath(Cobblemon.MODID, "shoulder"),
PlayerLuminance.INSTANCE
);
@Override
public void onInitializeDynamicLights(DynamicLightsContext context) {
Cobblemon.LOGGER.info("Lamb Dynamic Lights compatibility enabled");
context.entityLightSourceManager().onRegisterEvent().register(ctx -> {
ctx.register(EntityType.PLAYER, PlayerLuminance.INSTANCE);
});
}
@SuppressWarnings({"removal", "UnstableApiUsage"})
@Override
public void onInitializeDynamicLights(ItemLightSourceManager itemLightSourceManager) {}
}
@@ -1,72 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.compat.lambdynamiclights;
import com.cobblemon.mod.common.Cobblemon;
import com.cobblemon.mod.common.client.render.layer.PokemonOnShoulderRenderer;
import dev.lambdaurora.lambdynlights.api.entity.luminance.EntityLuminance;
import dev.lambdaurora.lambdynlights.api.item.ItemLightSourceManager;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.TagKey;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.Range;
public final class PlayerLuminance extends CustomLuminance implements EntityLuminance {
public static final PlayerLuminance INSTANCE = new PlayerLuminance();
private static final TagKey<Item> POKEDEX = TagKey.create(Registries.ITEM, ResourceLocation.fromNamespaceAndPath(Cobblemon.MODID, "pokedex"));
@Override
public @NotNull Type type() {
return LambDynamicLightsInitializer.PLAYER_LUMINANCE;
}
@Override
public @Range(from = 0L, to = 15L) int getLuminance(@NotNull ItemLightSourceManager itemLightSourceManager, @NotNull Entity entity) {
if (entity instanceof Player player) {
final int shoulderLightLevel = shoulderLuminance(itemLightSourceManager, player);
final int itemLightLevel = player.isHolding(stack -> stack.is(POKEDEX)) ? 13 : 0;
return Math.max(shoulderLightLevel, itemLightLevel);
} else {
return 0;
}
}
private @Range(from = 0L, to = 15L) static int shoulderLuminance(@NotNull ItemLightSourceManager itemLightSourceManager, @NotNull Player player) {
final var leftShoulder = PokemonOnShoulderRenderer.shoulderDataFrom(player.getShoulderEntityLeft());
final var rightShoulder = PokemonOnShoulderRenderer.shoulderDataFrom(player.getShoulderEntityRight());
if (leftShoulder == null && rightShoulder == null) {
return 0;
}
final int leftShoulderLightLevel = extractShoulderLightLevel(leftShoulder, itemLightSourceManager, player.isUnderWater());
final int rightShoulderLightLevel = extractShoulderLightLevel(rightShoulder, itemLightSourceManager, player.isUnderWater());
return Math.max(leftShoulderLightLevel, rightShoulderLightLevel);
}
private static int extractShoulderLightLevel(@Nullable PokemonOnShoulderRenderer.ShoulderData shoulderData, @NotNull ItemLightSourceManager itemLightSourceManager, boolean underwater) {
if (shoulderData == null) {
return 0;
}
ItemStack item = shoulderData.getShownItem();
int itemLightLevel = item.isEmpty() ? 0 : itemLightSourceManager.getLuminance(item, underwater);
int formLightLevel = extractFormLightLevel(shoulderData.getForm(), underwater).orElse(0);
return Math.max(itemLightLevel, formLightLevel);
}
}
@@ -1,45 +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.compat.lambdynamiclights;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import dev.lambdaurora.lambdynlights.api.entity.luminance.EntityLuminance;
import dev.lambdaurora.lambdynlights.api.item.ItemLightSourceManager;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.item.ItemStack;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Range;
public final class PokemonLuminance extends CustomLuminance implements EntityLuminance {
public static final PokemonLuminance INSTANCE = new PokemonLuminance();
private PokemonLuminance() {}
@Override
public @NotNull Type type() {
return LambDynamicLightsInitializer.POKEMON_LUMINANCE;
}
@Override
public @Range(from = 0L, to = 15L) int getLuminance(
@NotNull ItemLightSourceManager itemLightSourceManager,
@NotNull Entity entity
) {
if (entity instanceof PokemonEntity pokemon) {
boolean underwater = pokemon.isUnderWater();
ItemStack item = pokemon.getShownItem();
int itemLightLevel = item.isEmpty() ? 0 : itemLightSourceManager.getLuminance(item, underwater);
int formLightLevel = extractFormLightLevel(pokemon.getForm(), underwater).orElse(0);
return Math.max(itemLightLevel, formLightLevel);
} else {
return 0;
}
}
}
@@ -1,62 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.mixin;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResultHolder;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.EnderpearlItem;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.Comparator;
@Mixin(EnderpearlItem.class)
public abstract class EnderPearlItemMixin {
@Inject(method = "use", at = @At(value = "HEAD"), cancellable = true)
private void cobblemon$use(Level level, Player player, InteractionHand usedHand, CallbackInfoReturnable<InteractionResultHolder<ItemStack>> cir) {
double range = player.entityInteractionRange();
Entity closestEntity = level.getEntities(player, AABB.ofSize(player.position(), range, range, range)).stream()
.filter(entity -> isLookingAt(player, entity))
.min(Comparator.comparingDouble(entity -> entity.distanceTo(player)))
.orElse(null);
if (closestEntity instanceof PokemonEntity pokemonEntity) {
if (player.isCrouching() && pokemonEntity.getOwnerUUID() == player.getUUID()) {
cir.cancel();
}
}
}
public boolean isLookingAt(Player player, Entity other) {
float maxDistance = 10F;
float stepDistance = 0.1F;
double step = stepDistance;
Vec3 startPos = player.getEyePosition();
Vec3 direction = player.getLookAngle();
while (step <= maxDistance) {
Vec3 location = startPos.add(direction.scale(step));
step += stepDistance;
if (other.getBoundingBox().contains(location)) {
return true;
}
}
return false;
}
}
@@ -58,9 +58,11 @@ public abstract class LivingEntityMixin extends Entity implements OrientationCon
@Override
public @NotNull HitResult pick(double hitDistance, float partialTicks, boolean hitFluids) {
Entity vehicle = this.getVehicle();
if (vehicle instanceof PokemonEntity pokemonEntity && pokemonEntity.getDelegate() instanceof PokemonClientDelegate delegate) {
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()));
@@ -25,9 +25,9 @@ public class TargetingConditionsMixin {
@Inject(method = "test", at = @At("HEAD"), cancellable = true)
public void test(@Nullable LivingEntity baseEntity, LivingEntity targetEntity, CallbackInfoReturnable<Boolean> ci) {
if (targetEntity instanceof ServerPlayer player) {
if (player.level().getGameRules().getBoolean(CobblemonGameRules.MOB_TARGET_IN_BATTLE)) return;
boolean canTargetInBattle = player.level().getGameRules().getBoolean(CobblemonGameRules.MOB_TARGET_IN_BATTLE);
boolean targetInBattle = Cobblemon.INSTANCE.getBattleRegistry().getBattleByParticipatingPlayer(player) != null;
if (targetInBattle) ci.setReturnValue(false);
if (!canTargetInBattle && targetInBattle) ci.setReturnValue(false);
}
}
}
@@ -15,11 +15,9 @@ import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import com.llamalad7.mixinextras.sugar.Local;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.NonNullList;
import net.minecraft.world.Containers;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.alchemy.PotionBrewing;
import net.minecraft.world.item.crafting.RecipeHolder;
import net.minecraft.world.item.crafting.RecipeManager;
@@ -29,8 +27,6 @@ import net.minecraft.world.level.block.entity.BrewingStandBlockEntity;
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.CallbackInfoReturnable;
import java.util.List;
import java.util.Optional;
@@ -47,9 +43,7 @@ public class BrewingStandBlockEntityMixin {
//copied from vanilla doBrew logic, but slightly tweaked to work for cobblemon, this was needed as replacing the loop was not quite possible inside doBrew
ItemStack itemStack = slots.get(3);
for(int i = 0; i < 3; ++i) {
if (!slots.get(i).isEmpty()){
slots.set(i, recipe.getResult().copy());
}
slots.set(i, recipe.getResult().copy());
}
itemStack.shrink(1);
@@ -97,35 +91,4 @@ public class BrewingStandBlockEntityMixin {
return recipeHolder.map(RecipeHolder::value).orElse(null);
}
@Inject(method = "canPlaceItem", at = @At("HEAD"), cancellable = true)
private void cobblemon$canPlaceItem(int slot, ItemStack stack, CallbackInfoReturnable<Boolean> cir) {
if (stack == null || stack.isEmpty()) return;
BrewingStandBlockEntity self = (BrewingStandBlockEntity) (Object) this;
Level level = self.getLevel();
if (level != null) {
var rm = level.getRecipeManager();
//Input slot
if (slot == 3) {
if (BrewingStandRecipe.Companion.isInput(stack, rm)) {
cir.setReturnValue(true);
return;
}
}
// Output slots
if (slot >= 0 && slot < 3) {
if (BrewingStandRecipe.Companion.isBottle(stack, rm)) {
if (self.getItem(slot).isEmpty()) {
cir.setReturnValue(true);
} else {
cir.setReturnValue(false);
}
}
}
}
}
}
@@ -31,15 +31,14 @@ public class AbstractClientPlayerMixin {
PokedexUsageContext usageContext = CobblemonClient.INSTANCE.getPokedexUsageContext();
if (usageContext.getScanningGuiOpen()) {
cir.setReturnValue(usageContext.getFovMultiplier());
} else {
// Only modify fov through riding if the pokedex is not open
LocalPlayer player = (LocalPlayer) (Object) this;
if (player != null && player.isPassenger() && player.getVehicle() instanceof PokemonEntity) {
PokemonEntity ride = (PokemonEntity) player.getVehicle();
}
// Return custom fov mult for riding
cir.setReturnValue(ride.rideFovMult());
}
LocalPlayer player = (LocalPlayer) (Object) this;
if (player != null && player.isPassenger() && player.getVehicle() instanceof PokemonEntity) {
PokemonEntity ride = (PokemonEntity) player.getVehicle();
//Return custom fov mult
cir.setReturnValue(ride.rideFovMult());
}
}
}
@@ -1,46 +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.mixin.client;
import com.cobblemon.mod.common.client.render.player.MountedPlayerRenderer;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
import net.minecraft.client.model.AgeableListModel;
import net.minecraft.client.model.HumanoidModel;
import net.minecraft.client.model.Model;
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;
/**
* This mixin adds to the head of the renderToBuffer function and allows us to detect if it is a HumanoidModel and
* is being rendered as part of a player in the world who is riding a PokemonEntity. If so we need to obey any
* root animations that have been applied earlier.
*
* @author Hiroku
* @since May 16, 2025
*/
@Mixin(AgeableListModel.class)
public class AgeableListModelMixin {
@Inject(method = "renderToBuffer", at = @At("HEAD"))
private void cobblemon$renderToBuffer(
PoseStack poseStack,
VertexConsumer vertexConsumer,
int packedLight,
int packedOverlay,
int colour,
CallbackInfo ci
) {
Model model = (Model) (Object) this;
if (model instanceof HumanoidModel<?> && MountedPlayerRenderer.shouldApplyRootAnimation) {
MountedPlayerRenderer.INSTANCE.animateRoot(poseStack);
}
}
}
@@ -19,17 +19,20 @@ import net.minecraft.world.entity.LivingEntity;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.HashMap;
@Mixin(HumanoidModel.class)
public class HumanoidModelMixin {
@Unique HashMap<String, ModelPart> relevantPartsByName = new HashMap<>();
@Shadow @Final
public ModelPart head;
@Shadow @Final
public ModelPart hat;
@Shadow @Final
public ModelPart rightArm;
@Shadow @Final
public ModelPart leftArm;
@@ -37,8 +40,6 @@ public class HumanoidModelMixin {
public ModelPart rightLeg;
@Shadow @Final
public ModelPart leftLeg;
@Shadow @Final
public ModelPart body;
@Inject(method = "setupAnim*", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/model/geom/ModelPart;copyFrom(Lnet/minecraft/client/model/geom/ModelPart;)V", shift = At.Shift.BEFORE))
public void cobblemon$animateRiders(
@@ -50,23 +51,19 @@ public class HumanoidModelMixin {
float headPitch,
CallbackInfo ci
) {
MountedPlayerRenderer.shouldApplyRootAnimation = false;
if (MountedPlayerRenderer.relevantPartsByName.size() <= 1) {
MountedPlayerRenderer.relevantPartsByName.put("arm_left", leftArm);
MountedPlayerRenderer.relevantPartsByName.put("arm_right", rightArm);
MountedPlayerRenderer.relevantPartsByName.put("leg_left", leftLeg);
MountedPlayerRenderer.relevantPartsByName.put("leg_right", rightLeg);
MountedPlayerRenderer.relevantPartsByName.put("head", head);
MountedPlayerRenderer.relevantPartsByName.put("hat", hat);
MountedPlayerRenderer.relevantPartsByName.put("torso", body);
if (relevantPartsByName.isEmpty()) {
relevantPartsByName.put("arm_left", leftArm);
relevantPartsByName.put("arm_right", rightArm);
relevantPartsByName.put("leg_left", leftLeg);
relevantPartsByName.put("leg_right", rightLeg);
relevantPartsByName.put("head", head);
}
Entity vehicle = entity.getVehicle();
if (vehicle instanceof PokemonEntity pokemonEntity && entity instanceof OrientationControllable) {
var shouldRotatePlayerHead = pokemonEntity.ifRidingAvailableSupply(false, (behaviour, settings, state) ->
behaviour.shouldRotateRiderHead(settings, state, pokemonEntity)
);
var shouldRotatePlayerHead = pokemonEntity.ifRidingAvailableSupply(false, (behaviour, settings, state) -> {
return behaviour.shouldRotateRiderHead(settings, state, pokemonEntity);
});
if (!shouldRotatePlayerHead) {
netHeadYaw = 0f;
@@ -79,8 +76,7 @@ public class HumanoidModelMixin {
if (!(entity instanceof AbstractClientPlayer player)) return;
MountedPlayerRenderer.shouldApplyRootAnimation = true;
MountedPlayerRenderer.INSTANCE.animate((HumanoidModel<AbstractClientPlayer>)(Object)this, pokemonEntity, player, netHeadYaw, headPitch, ageInTicks, limbSwing, limbSwingAmount);
MountedPlayerRenderer.INSTANCE.animate(pokemonEntity, player, relevantPartsByName, netHeadYaw, headPitch, ageInTicks, limbSwing, limbSwingAmount);
}
}
}
@@ -174,16 +174,6 @@ public abstract class LocalPlayerMixin extends AbstractClientPlayer implements O
return this.cobblemon$isDoubleJumping;
}
@Inject(method = "isHandsBusy", at = @At("HEAD"), cancellable = true)
private void cobblemon$isHandsBusy(CallbackInfoReturnable<Boolean> cir) {
Entity vehicle = this.getVehicle();
if (vehicle instanceof PokemonEntity pokemonEntity) {
int seatIndex = pokemonEntity.getPassengers().indexOf(this);
Seat seat = pokemonEntity.getSeats().get(seatIndex);
cir.setReturnValue(seat.getHandsBusy());
}
}
@Override
public HitResult pick(double hitDistance, float partialTicks, boolean hitFluids) {
Entity vehicle = this.getVehicle();
@@ -117,8 +117,6 @@ public abstract class MouseHandlerMixin {
@Local(argsOnly = true) double d,
@Local(argsOnly = true) double movementTime
) {
boolean returnValue = true;
PokedexUsageContext usageContext = CobblemonClient.INSTANCE.getPokedexUsageContext();
if (usageContext.getScanningGuiOpen()) {
this.smoothTurnY.reset();
@@ -129,7 +127,7 @@ public abstract class MouseHandlerMixin {
var sensitivity = Mth.lerp(usageContext.getFovMultiplier(), spyglassSensitivity, lookSensitivity);
var yRotationFlip = this.minecraft.options.invertYMouse().get() ? -1 : 1;
player.turn(this.accumulatedDX * sensitivity, (this.accumulatedDY * sensitivity * yRotationFlip));
returnValue = false;
return false;
}
// Clamp player rotation if riding and the vehicle demands it
@@ -145,33 +143,35 @@ public abstract class MouseHandlerMixin {
pitchSmoother.reset();
rollSmoother.reset();
yawSmoother.reset();
return returnValue;
return true;
}
// Send mouse input to be interpreted into rotation
// deltas by the ride controller
//Send mouse input to be interpreted into rotation
//deltas by the ride controller
Vec3 angVecMouse = cobblemon$getRideMouseRotation(cursorDeltaX, cursorDeltaY, movementTime);
// Perform Rotation using mouse influenced rotation deltas.
//Perform Rotation using mouse influenced rotation deltas.
controllable.getOrientationController().rotate(
(float) angVecMouse.x,
(float) angVecMouse.y,
(float) angVecMouse.z
);
// Gather and apply the current rotation deltas
//Gather and apply the current rotation deltas
var angRot = cobblemon$getAngularVelocity(movementTime);
// Apply smoothing if requested by the controller.
// This Might be best if done by the controller itself?
if(cobblemon$shouldUseAngVelSmoothing()) {
//Apply smoothing if requested by the controller.
//This Might be best if done by the controller itself?
if(cobblemon$shouldUseAngVelSmoothing())
{
var yaw = yawSmoother.getNewDeltaValue(angRot.x * 0.5f, d);
var pitch = pitchSmoother.getNewDeltaValue(angRot.y * 0.5f, d);
var roll = rollSmoother.getNewDeltaValue(angRot.z * 0.5f, d);
controllable.getOrientationController().rotate((float) yaw, (float) pitch, (float) roll);
}
// Otherwise simply apply the smoothing
else {
//Otherwise simply apply the smoothing
else
{
controllable.getOrientationController().rotate((float) (angRot.x * 10 * d), (float) (angRot.y * 10 * d), (float) (angRot.z * 10 * d));
}
return false;
@@ -10,7 +10,8 @@ package com.cobblemon.mod.common.mixin.client.snapshots;
import com.cobblemon.mod.common.CobblemonBuildDetails;
import com.cobblemon.mod.common.client.gui.snapshots.SnapshotWarningScreen;
import com.cobblemon.mod.common.client.persisted.ClientPersistedData;
import com.cobblemon.mod.common.client.snapshots.SnapshotAcknowledgementData;
import com.cobblemon.mod.common.client.snapshots.SnapshotAcknowledgementReader;
import kotlin.Unit;
import net.minecraft.SharedConstants;
import net.minecraft.client.Minecraft;
@@ -41,7 +42,7 @@ public final class SnapshotWarningMixin {
return Unit.INSTANCE;
}
ClientPersistedData.INSTANCE.writeSnapshotsData(new ClientPersistedData.SnapshotAcknowledgementData(CobblemonBuildDetails.VERSION, dsa));
SnapshotAcknowledgementReader.INSTANCE.write(new SnapshotAcknowledgementData(CobblemonBuildDetails.VERSION, dsa));
runnable.run();
return Unit.INSTANCE;
@@ -53,7 +54,7 @@ public final class SnapshotWarningMixin {
@Unique
@SuppressWarnings("ResultOfMethodCallIgnored")
private boolean cobblemon$isAcknowledged() {
ClientPersistedData.SnapshotAcknowledgementData data = ClientPersistedData.INSTANCE.readSnapshotsData();
SnapshotAcknowledgementData data = SnapshotAcknowledgementReader.INSTANCE.read();
if (data == null) {
return false;
}
@@ -11,10 +11,7 @@ package com.cobblemon.mod.common.mixin.entity;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.Level;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
@@ -42,17 +39,10 @@ public abstract class EntityMixin {
target = "Lnet/minecraft/world/entity/Entity;onGround()Z"
)
)
public boolean cobblemon$forceOnGroundForStepUp(Entity entity, Operation<Boolean> original) {
public boolean cobblemon$forceOnGroundForStepUp(Entity entity, Operation<Boolean> original)
{
if (entity instanceof PokemonEntity && entity.hasControllingPassenger()) {
BlockPos below = entity.blockPosition().below();
Level level = entity.level();
var blockStateBelow = level.getBlockState(below);
boolean isAirOrLiquid = blockStateBelow.isAir() || !blockStateBelow.getFluidState().isEmpty();
boolean canSupportEntity = blockStateBelow.isFaceSturdy(level, below, Direction.UP);
boolean standingOnSolid = canSupportEntity && !isAirOrLiquid;
if (standingOnSolid) {
return true;
}
return true;
}
return original.call(entity);
}
@@ -76,7 +76,7 @@ import com.cobblemon.mod.common.config.starter.StarterConfig
import com.cobblemon.mod.common.data.CobblemonDataProvider
import com.cobblemon.mod.common.events.AdvancementHandler
import com.cobblemon.mod.common.events.EntityCallbackHandler
import com.cobblemon.mod.common.events.CallbackHandler
import com.cobblemon.mod.common.events.FlowHandler
import com.cobblemon.mod.common.events.PokedexHandler
import com.cobblemon.mod.common.events.ServerTickHandler
import com.cobblemon.mod.common.net.messages.client.settings.ServerSettingsPacket
@@ -313,7 +313,7 @@ object Cobblemon {
CobblemonEvents.POKEMON_PROPERTY_INITIALISED.emit(Unit)
CallbackHandler.setup()
FlowHandler.setup()
EntityCallbackHandler.setup()
ifDedicatedServer {
@@ -128,7 +128,7 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
@JvmField
val APRICORN_FENCE = this.create("apricorn_fence", FenceBlock(BlockBehaviour.Properties.of().mapColor(APRICORN_PLANKS.defaultMapColor()).instrument(NoteBlockInstrument.BASS).strength(2.0f, 3.0f).sound(SoundType.WOOD)))
@JvmField
val APRICORN_FENCE_GATE = this.create("apricorn_fence_gate", FenceGateBlock(APRICORN_WOOD_TYPE, BlockBehaviour.Properties.of().mapColor(APRICORN_PLANKS.defaultMapColor()).strength(2.0f, 3.0f).sound(SoundType.WOOD).ignitedByLava().forceSolidOn()))
val APRICORN_FENCE_GATE = this.create("apricorn_fence_gate", FenceGateBlock(APRICORN_WOOD_TYPE, BlockBehaviour.Properties.of().mapColor(APRICORN_PLANKS.defaultMapColor()).strength(2.0f, 3.0f).sound(SoundType.WOOD)))
@JvmField
val APRICORN_BUTTON = this.create("apricorn_button", BlocksInvoker.createWoodenButtonBlock(APRICORN_BLOCK_SET_TYPE))
@JvmField
@@ -205,7 +205,7 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
@JvmField
val SACCHARINE_FENCE = this.create("saccharine_fence", FenceBlock(BlockBehaviour.Properties.of().mapColor(SACCHARINE_PLANKS.defaultMapColor()).instrument(NoteBlockInstrument.BASS).strength(2.0f, 3.0f).sound(SoundType.WOOD)))
@JvmField
val SACCHARINE_FENCE_GATE = this.create("saccharine_fence_gate", FenceGateBlock(SACCHARINE_WOOD_TYPE, BlockBehaviour.Properties.of().mapColor(SACCHARINE_PLANKS.defaultMapColor()).instrument(NoteBlockInstrument.BASS).strength(2.0f, 3.0f).sound(SoundType.WOOD).ignitedByLava().forceSolidOn()))
val SACCHARINE_FENCE_GATE = this.create("saccharine_fence_gate", FenceGateBlock(SACCHARINE_WOOD_TYPE, BlockBehaviour.Properties.of().mapColor(SACCHARINE_PLANKS.defaultMapColor()).instrument(NoteBlockInstrument.BASS).strength(2.0f, 3.0f).sound(SoundType.WOOD)))
@JvmField
val SACCHARINE_BUTTON = this.create("saccharine_button", BlocksInvoker.createWoodenButtonBlock(BlockSetType.OAK))
@JvmField
@@ -213,7 +213,7 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
@JvmField
val SACCHARINE_SIGN = this.create("saccharine_sign", CobblemonSignBlock(BlockBehaviour.Properties.ofFullCopy(Blocks.OAK_SIGN), SACCHARINE_WOOD_TYPE))
@JvmField
val SACCHARINE_WALL_SIGN = this.create("saccharine_wall_sign", CobblemonWallSignBlock(BlockBehaviour.Properties.ofFullCopy(Blocks.OAK_SIGN), SACCHARINE_WOOD_TYPE))
val SACCHARINE_WALL_SIGN = this.create("saccharine_wall_sign", CobblemonWallSignBlock(BlockBehaviour.Properties.ofFullCopy(Blocks.OAK_WALL_SIGN), SACCHARINE_WOOD_TYPE))
@JvmField
val SACCHARINE_HANGING_SIGN = this.create("saccharine_hanging_sign", CobblemonHangingSignBlock(BlockBehaviour.Properties.ofFullCopy(Blocks.OAK_WALL_HANGING_SIGN), SACCHARINE_WOOD_TYPE))
@JvmField
@@ -18,78 +18,72 @@ import com.cobblemon.mod.common.api.molang.ExpressionLike
import com.cobblemon.mod.common.api.molang.MoLangFunctions.setup
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.api.scripting.CobblemonScripts
import com.cobblemon.mod.common.net.messages.client.data.CallbackRegistrySyncPacket
import com.cobblemon.mod.common.net.messages.client.data.FlowRegistrySyncPacket
import com.cobblemon.mod.common.util.asExpressionLike
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.endsWith
import java.util.concurrent.ExecutionException
import java.util.function.Predicate
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
import net.minecraft.server.packs.PackType
import net.minecraft.server.packs.resources.ResourceManager
/**
* Holds all the callbacks that are loaded from the server's data packs. A callback is foldered under the event identifier,
* Holds all the flows that are loaded from the server's data packs. A flow is foldered under the event identifier,
* which can be Cobblemon's namespace but can also be minecraft or any other namespace someone has added hooks for.
* The value of the map is a list of MoLang scripts that will execute when that event occurs.
*
* @author Hiroku
* @since February 24th, 2024
*/
object CobblemonCallbacks : DataRegistry {
override val id = cobblemonResource("callbacks")
override val observable = SimpleObservable<CobblemonCallbacks>()
object CobblemonFlows : DataRegistry {
override val id = cobblemonResource("flows")
override val observable = SimpleObservable<CobblemonFlows>()
override val type = PackType.SERVER_DATA
override fun sync(player: ServerPlayer) {
player.sendPacket(CallbackRegistrySyncPacket(clientCallbacks.entries))
player.sendPacket(FlowRegistrySyncPacket(clientFlows.entries))
}
val runtime by lazy { MoLangRuntime().setup() } // Lazy for if someone adds to generalFunctions in MoLangFunctions
val clientCallbacks = hashMapOf<ResourceLocation, MutableList<ExpressionLike>>()
val callbacks = hashMapOf<ResourceLocation, MutableList<ExpressionLike>>()
val clientFlows = hashMapOf<ResourceLocation, MutableList<ExpressionLike>>()
val flows = hashMapOf<ResourceLocation, MutableList<ExpressionLike>>()
override fun reload(manager: ResourceManager) {
clientCallbacks.clear()
callbacks.clear()
clientFlows.clear()
flows.clear()
val unsortedCallbacks = mutableMapOf<ResourceLocation, MutableList<Pair<String, ExpressionLike>>>()
val unsortedFlows = mutableMapOf<ResourceLocation, MutableList<Pair<String, ExpressionLike>>>()
val folderBeforeNameRegex = ".*\\/([^\\/]+)\\/[^\\/]+\$".toRegex()
val predicate: Predicate<ResourceLocation> = Predicate { path -> path.endsWith(CobblemonScripts.MOLANG_EXTENSION) }
manager.listResources("flows") { path -> path.endsWith(CobblemonScripts.MOLANG_EXTENSION) }.forEach { (identifier, resource) ->
resource.openAsReader().use { stream ->
stream.buffered().use { reader ->
try {
val expression = reader.readText().asExpressionLike()
manager.listResources("callbacks", predicate)
.plus(manager.listResources("flows", predicate)) // old name
.forEach { (identifier, resource) ->
resource.openAsReader().use { stream ->
stream.buffered().use { reader ->
try {
val expression = reader.readText().asExpressionLike()
val event = folderBeforeNameRegex.find(identifier.path)?.groupValues?.get(1)
?: throw IllegalArgumentException("Invalid flow path: $identifier. Should have a folder structure that includes the name of the event being flowed.")
val event = folderBeforeNameRegex.find(identifier.path)?.groupValues?.get(1)
?: throw IllegalArgumentException("Invalid callback path: $identifier. Should have a folder structure that includes the name of the event to callback.")
val callbackKey = ResourceLocation.fromNamespaceAndPath(identifier.namespace, event)
unsortedCallbacks.putIfAbsent(callbackKey, mutableListOf())
unsortedCallbacks[callbackKey]!!.add(identifier.path to expression)
} catch (exception: Exception) {
throw ExecutionException("Error loading MoLang script for callback: $identifier", exception)
}
val flowKey = ResourceLocation.fromNamespaceAndPath(identifier.namespace, event)
unsortedFlows.putIfAbsent(flowKey, mutableListOf())
unsortedFlows[flowKey]!!.add(identifier.path to expression)
} catch (exception: Exception) {
throw ExecutionException("Error loading MoLang script for flow: $identifier", exception)
}
}
}
unsortedCallbacks.forEach { (key, value) ->
value.sortBy { it.first }
if (key.path.startsWith("flows/client/") || key.path.startsWith("callbacks/client/")) {
clientCallbacks.getOrPut(key) { mutableListOf() }.addAll(value.map { it.second })
} else {
callbacks.getOrPut(key) { mutableListOf() }.addAll(value.map { it.second })
}
}
Cobblemon.LOGGER.info("Loaded ${callbacks.size} callbacks and ${clientCallbacks.size} client callbacks")
unsortedFlows.forEach { (key, value) ->
value.sortBy { it.first }
if (key.path.startsWith("flows/client/")) {
clientFlows.getOrPut(key) { mutableListOf() }.addAll(value.map { it.second })
} else {
flows.getOrPut(key) { mutableListOf() }.addAll(value.map { it.second })
}
}
Cobblemon.LOGGER.info("Loaded ${flows.size} flows and ${clientFlows.size} client flows")
observable.emit(this)
}
@@ -99,7 +93,7 @@ object CobblemonCallbacks : DataRegistry {
functions: Map<String, (MoParams) -> Any> = emptyMap(),
cancelable: Cancelable? = null
) {
if (eventResourceLocation !in callbacks) return
if (eventResourceLocation !in flows) return
if (cancelable == null) {
runtime.environment.query.functions.remove("cancel")
@@ -112,7 +106,7 @@ object CobblemonCallbacks : DataRegistry {
functions.forEach { (name, function) -> runtime.environment.query.addFunction(name, function) }
callbacks[eventResourceLocation]?.forEach {
flows[eventResourceLocation]?.forEach {
if (cancelable != null && cancelable.isCanceled) {
return
}
@@ -26,10 +26,10 @@ object CobblemonItemComponents : PlatformRegistry<Registry<DataComponentType<*>>
.networkSynchronized(PokemonItemComponent.PACKET_CODEC)
.build())
val HELD_ITEM_EFFECT: DataComponentType<HeldItemEffectComponent> = create("held_item_effect", DataComponentType.builder<HeldItemEffectComponent>()
.persistent(HeldItemEffectComponent.CODEC)
.networkSynchronized(HeldItemEffectComponent.PACKET_CODEC)
.build())
val HELD_ITEM_REP: DataComponentType<HeldItemCapableComponent> = DataComponentType.builder<HeldItemCapableComponent>()
.persistent(HeldItemCapableComponent.CODEC)
.networkSynchronized(HeldItemCapableComponent.PACKET_CODEC)
.build()
val BAIT: DataComponentType<RodBaitComponent> = create("bait", DataComponentType.builder<RodBaitComponent>()
.persistent(RodBaitComponent.CODEC)
@@ -86,7 +86,6 @@ object CobblemonItemComponents : PlatformRegistry<Registry<DataComponentType<*>>
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:ingredient"), INGREDIENT)
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:food"), FOOD)
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:mob_effects"), MOB_EFFECTS)
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:held_item_effect"), HELD_ITEM_EFFECT)
}
override val registry = BuiltInRegistries.DATA_COMPONENT_TYPE
@@ -22,7 +22,6 @@ import com.cobblemon.mod.common.block.BerryBlock
import com.cobblemon.mod.common.block.MintBlock
import com.cobblemon.mod.common.block.MintBlock.MintType
import com.cobblemon.mod.common.client.pokedex.PokedexType
import com.cobblemon.mod.common.block.campfirepot.CampfirePotColor
import com.cobblemon.mod.common.entity.boat.CobblemonBoatType
import com.cobblemon.mod.common.item.*
import com.cobblemon.mod.common.item.armor.CobblemonArmorTrims
@@ -193,19 +192,19 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
val campfire_pots = mutableListOf<CampfirePotItem>()
@JvmField
val CAMPFIRE_POT_RED = campfirePotItem(CobblemonBlocks.RED_CAMPFIRE_POT, CampfirePotColor.RED)
val CAMPFIRE_POT_RED = campfirePotItem(CobblemonBlocks.RED_CAMPFIRE_POT, "red")
@JvmField
val CAMPFIRE_POT_YELLOW = campfirePotItem(CobblemonBlocks.YELLOW_CAMPFIRE_POT, CampfirePotColor.YELLOW)
val CAMPFIRE_POT_YELLOW = campfirePotItem(CobblemonBlocks.YELLOW_CAMPFIRE_POT, "yellow")
@JvmField
val CAMPFIRE_POT_GREEN = campfirePotItem(CobblemonBlocks.GREEN_CAMPFIRE_POT, CampfirePotColor.GREEN)
val CAMPFIRE_POT_GREEN = campfirePotItem(CobblemonBlocks.GREEN_CAMPFIRE_POT, "green")
@JvmField
val CAMPFIRE_POT_BLUE = campfirePotItem(CobblemonBlocks.BLUE_CAMPFIRE_POT, CampfirePotColor.BLUE)
val CAMPFIRE_POT_BLUE = campfirePotItem(CobblemonBlocks.BLUE_CAMPFIRE_POT, "blue")
@JvmField
val CAMPFIRE_POT_PINK = campfirePotItem(CobblemonBlocks.PINK_CAMPFIRE_POT, CampfirePotColor.PINK)
val CAMPFIRE_POT_PINK = campfirePotItem(CobblemonBlocks.PINK_CAMPFIRE_POT, "pink")
@JvmField
val CAMPFIRE_POT_BLACK = campfirePotItem(CobblemonBlocks.BLACK_CAMPFIRE_POT, CampfirePotColor.BLACK)
val CAMPFIRE_POT_BLACK = campfirePotItem(CobblemonBlocks.BLACK_CAMPFIRE_POT, "black")
@JvmField
val CAMPFIRE_POT_WHITE = campfirePotItem(CobblemonBlocks.WHITE_CAMPFIRE_POT, CampfirePotColor.WHITE)
val CAMPFIRE_POT_WHITE = campfirePotItem(CobblemonBlocks.WHITE_CAMPFIRE_POT, "white")
@JvmField
val HEARTY_GRAINS = compostableItem("hearty_grains", ItemNameBlockItem(CobblemonBlocks.HEARTY_GRAINS, Properties().rarity(Rarity.COMMON)))
@@ -401,7 +400,7 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val OLD_GATEAU = regionalFoodItem("old_gateau", 64, 9, 0.1335f, false)
@JvmField
val CASTELIACONE = regionalFoodItem("casteliacone", 64, 5, 0.28f, false)
val CASTELIACONE = regionalFoodItem("casteliacone", 64, 5, .28f, false)
@JvmField
val LUMIOSE_GALETTE = regionalFoodItem("lumiose_galette", 64, 4, 0.4f, false)
@JvmField
@@ -696,7 +695,7 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val SUPERB_REMEDY = compostableItem("superb_remedy", RemedyItem(RemedyItem.SUPERB), 0.85f)
@JvmField
val MOOMOO_MILK = create("moomoo_milk", CobblemonItem(Item.Properties().craftRemainder(Items.GLASS_BOTTLE).stacksTo((16))))
val MOOMOO_MILK = create("moomoo_milk", CobblemonItem(Item.Properties().craftRemainder(Items.GLASS_BOTTLE)))
@JvmField
val POTION = create("potion", PotionItem(PotionType.POTION))
@@ -1501,8 +1500,8 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
}
)
private fun campfirePotItem(block: Block, color: CampfirePotColor): CampfirePotItem {
val item = create("campfire_pot_${color.suffix}", CampfirePotItem(block, color))
private fun campfirePotItem(block: Block, type: String): CampfirePotItem {
val item = create("campfire_pot_${type}", CampfirePotItem(block))
campfire_pots.add(item)
return item
}
@@ -1649,4 +1648,4 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
private fun foodItem(nutrition: Int, saturationModifier: Float): Item {
return CobblemonItem(Item.Properties().food(FoodProperties.Builder().nutrition(nutrition).saturationModifier(saturationModifier).build()))
}
}
}
@@ -39,7 +39,6 @@ object CobblemonMechanics : DataRegistry {
var potions = PotionsMechanic()
override fun sync(player: ServerPlayer) {}
override fun reload(manager: ResourceManager) {
remedies = loadMechanic(manager, "remedies", RemediesMechanic::class.java)
berries = loadMechanic(manager, "berries", BerriesMechanic::class.java)
@@ -9,8 +9,8 @@
package com.cobblemon.mod.common
import com.cobblemon.mod.common.api.net.NetworkPacket
import com.cobblemon.mod.common.client.net.CalculateSeatPositionsHandler
import com.cobblemon.mod.common.client.net.OpenBehaviourEditorHandler
import com.cobblemon.mod.common.client.net.CalculateSeatPositionsHandler
import com.cobblemon.mod.common.client.net.PlayerInteractOptionsHandler
import com.cobblemon.mod.common.client.net.SetClientPlayerDataHandler
import com.cobblemon.mod.common.client.net.animation.PlayPosableAnimationHandler
@@ -20,7 +20,7 @@ import com.cobblemon.mod.common.client.net.callback.party.OpenPartyCallbackHandl
import com.cobblemon.mod.common.client.net.callback.partymove.OpenPartyMoveCallbackHandler
import com.cobblemon.mod.common.client.net.cooking.ToggleCookingPotLidHandler
import com.cobblemon.mod.common.client.net.data.DataRegistrySyncPacketHandler
import com.cobblemon.mod.common.client.net.data.RideSettingsSyncHandler
import com.cobblemon.mod.common.client.net.data.UnlockReloadPacketHandler
import com.cobblemon.mod.common.client.net.dialogue.DialogueClosedHandler
import com.cobblemon.mod.common.client.net.dialogue.DialogueOpenedHandler
import com.cobblemon.mod.common.client.net.effect.PokeSnackBlockParticlesHandler
@@ -65,8 +65,8 @@ import com.cobblemon.mod.common.client.net.trade.TradeProcessStartedHandler
import com.cobblemon.mod.common.client.net.trade.TradeStartedHandler
import com.cobblemon.mod.common.client.net.trade.TradeUpdatedHandler
import com.cobblemon.mod.common.net.PacketRegisterInfo
import com.cobblemon.mod.common.net.messages.client.CalculateSeatPositionsPacket
import com.cobblemon.mod.common.net.messages.client.OpenBehaviourEditorPacket
import com.cobblemon.mod.common.net.messages.client.CalculateSeatPositionsPacket
import com.cobblemon.mod.common.net.messages.client.PlayerInteractOptionsPacket
import com.cobblemon.mod.common.net.messages.client.SetClientPlayerDataPacket
import com.cobblemon.mod.common.net.messages.client.animation.PlayPosableAnimationPacket
@@ -157,7 +157,6 @@ import com.cobblemon.mod.common.net.messages.server.callback.party.PartyPokemonS
import com.cobblemon.mod.common.net.messages.server.callback.party.PartySelectCancelledPacket
import com.cobblemon.mod.common.net.messages.server.callback.partymove.PartyMoveSelectCancelledPacket
import com.cobblemon.mod.common.net.messages.server.callback.partymove.PartyPokemonMoveSelectedPacket
import com.cobblemon.mod.common.net.messages.server.debug.ServerboundUpdateRidingSettingsPacket
import com.cobblemon.mod.common.net.messages.server.debug.ServerboundUpdateRidingStatRangePacket
import com.cobblemon.mod.common.net.messages.server.debug.ServerboundUpdateRidingStatsPacket
import com.cobblemon.mod.common.net.messages.server.dialogue.EscapeDialoguePacket
@@ -171,11 +170,11 @@ import com.cobblemon.mod.common.net.messages.server.pasture.UnpasturePokemonPack
import com.cobblemon.mod.common.net.messages.server.pokedex.scanner.FinishScanningPacket
import com.cobblemon.mod.common.net.messages.server.pokedex.scanner.StartScanningPacket
import com.cobblemon.mod.common.net.messages.server.pokemon.interact.InteractPokemonPacket
import com.cobblemon.mod.common.net.messages.server.pokemon.update.ServerboundUpdateRidingStatePacket
import com.cobblemon.mod.common.net.messages.server.pokemon.update.SetActiveMarkPacket
import com.cobblemon.mod.common.net.messages.server.pokemon.update.SetItemHiddenPacket
import com.cobblemon.mod.common.net.messages.server.pokemon.update.SetMarkingsPacket
import com.cobblemon.mod.common.net.messages.server.pokemon.update.SetNicknamePacket
import com.cobblemon.mod.common.net.messages.server.pokemon.update.SetActiveMarkPacket
import com.cobblemon.mod.common.net.messages.server.pokemon.update.SetMarkingsPacket
import com.cobblemon.mod.common.net.messages.server.pokemon.update.ServerboundUpdateRidingStatePacket
import com.cobblemon.mod.common.net.messages.server.pokemon.update.evolution.AcceptEvolutionPacket
import com.cobblemon.mod.common.net.messages.server.riding.DismountPokemonPacket
import com.cobblemon.mod.common.net.messages.server.riding.ServerboundUpdateDriverInputPacket
@@ -209,7 +208,6 @@ import com.cobblemon.mod.common.net.serverhandling.callback.party.PartyPokemonSe
import com.cobblemon.mod.common.net.serverhandling.callback.party.PartySelectCancelledHandler
import com.cobblemon.mod.common.net.serverhandling.callback.partymove.PartyMoveSelectCancelledHandler
import com.cobblemon.mod.common.net.serverhandling.callback.partymove.PartyPokemonMoveSelectedHandler
import com.cobblemon.mod.common.net.serverhandling.debug.ServerboundUpdateRidingSettingsHandler
import com.cobblemon.mod.common.net.serverhandling.debug.ServerboundUpdateRidingStatRangeHandler
import com.cobblemon.mod.common.net.serverhandling.debug.ServerboundUpdateRidingStatsHandler
import com.cobblemon.mod.common.net.serverhandling.dialogue.EscapeDialogueHandler
@@ -224,11 +222,11 @@ import com.cobblemon.mod.common.net.serverhandling.pasture.UnpasturePokemonHandl
import com.cobblemon.mod.common.net.serverhandling.pokedex.scanner.FinishScanningHandler
import com.cobblemon.mod.common.net.serverhandling.pokedex.scanner.StartScanningHandler
import com.cobblemon.mod.common.net.serverhandling.pokemon.interact.InteractPokemonHandler
import com.cobblemon.mod.common.net.serverhandling.pokemon.update.ServerboundUpdateRidingStateHandler
import com.cobblemon.mod.common.net.serverhandling.pokemon.update.SetActiveMarkHandler
import com.cobblemon.mod.common.net.serverhandling.pokemon.update.SetItemHiddenHandler
import com.cobblemon.mod.common.net.serverhandling.pokemon.update.SetMarkingsHandler
import com.cobblemon.mod.common.net.serverhandling.pokemon.update.SetNicknameHandler
import com.cobblemon.mod.common.net.serverhandling.pokemon.update.SetActiveMarkHandler
import com.cobblemon.mod.common.net.serverhandling.pokemon.update.SetMarkingsHandler
import com.cobblemon.mod.common.net.serverhandling.pokemon.update.ServerboundUpdateRidingStateHandler
import com.cobblemon.mod.common.net.serverhandling.riding.DismountPokemonPacketHandler
import com.cobblemon.mod.common.net.serverhandling.riding.DriverInputPacketHandler
import com.cobblemon.mod.common.net.serverhandling.starter.RequestStarterScreenHandler
@@ -312,7 +310,6 @@ object CobblemonNetwork {
list.add(PacketRegisterInfo(MarksPotentialUpdatePacket.ID, MarksPotentialUpdatePacket::decode, PokemonUpdatePacketHandler()))
list.add(PacketRegisterInfo(MarkingsUpdatePacket.ID, MarkingsUpdatePacket::decode, PokemonUpdatePacketHandler()))
list.add(PacketRegisterInfo(RideBoostsUpdatePacket.ID, RideBoostsUpdatePacket::decode, PokemonUpdatePacketHandler()))
list.add(PacketRegisterInfo(RideStaminaUpdatePacket.ID, RideStaminaUpdatePacket::decode, PokemonUpdatePacketHandler()))
// Evolution start
list.add(PacketRegisterInfo(AddEvolutionPacket.ID, AddEvolutionPacket::decode, PokemonUpdatePacketHandler()))
@@ -391,6 +388,7 @@ object CobblemonNetwork {
list.add(PacketRegisterInfo(BerryRegistrySyncPacket.ID, BerryRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(SpeciesRegistrySyncPacket.ID, SpeciesRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(PropertiesCompletionRegistrySyncPacket.ID, PropertiesCompletionRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(UnlockReloadPacket.ID, UnlockReloadPacket::decode, UnlockReloadPacketHandler))
list.add(PacketRegisterInfo(StandardSpeciesFeatureSyncPacket.ID, StandardSpeciesFeatureSyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(GlobalSpeciesFeatureSyncPacket.ID, GlobalSpeciesFeatureSyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(SpeciesFeatureAssignmentSyncPacket.ID, SpeciesFeatureAssignmentSyncPacket::decode, DataRegistrySyncPacketHandler()))
@@ -403,11 +401,10 @@ object CobblemonNetwork {
list.add(PacketRegisterInfo(DexEntrySyncPacket.ID, DexEntrySyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(SpawnBaitRegistrySyncPacket.ID, SpawnBaitRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(SeasoningRegistrySyncPacket.ID, SeasoningRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(CallbackRegistrySyncPacket.ID, CallbackRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(FlowRegistrySyncPacket.ID, FlowRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(CosmeticItemAssignmentSyncPacket.ID, CosmeticItemAssignmentSyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(BehaviourSyncPacket.ID, BehaviourSyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(MarkRegistrySyncPacket.ID, MarkRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(RideSettingsSyncPacket.ID, RideSettingsSyncPacket::decode, RideSettingsSyncHandler))
// Effects
list.add(PacketRegisterInfo(SpawnSnowstormParticlePacket.ID, SpawnSnowstormParticlePacket::decode, SpawnSnowstormParticleHandler))
@@ -581,7 +578,6 @@ object CobblemonNetwork {
list.add(PacketRegisterInfo(ServerboundUpdateRidingStatePacket.ID, ServerboundUpdateRidingStatePacket::decode, ServerboundUpdateRidingStateHandler))
list.add(PacketRegisterInfo(ServerboundUpdateRidingStatsPacket.ID, ServerboundUpdateRidingStatsPacket::decode, ServerboundUpdateRidingStatsHandler))
list.add(PacketRegisterInfo(ServerboundUpdateRidingStatRangePacket.ID, ServerboundUpdateRidingStatRangePacket::decode, ServerboundUpdateRidingStatRangeHandler))
list.add(PacketRegisterInfo(ServerboundUpdateRidingSettingsPacket.ID, ServerboundUpdateRidingSettingsPacket::decode, ServerboundUpdateRidingSettingsHandler))
list.add(PacketRegisterInfo(DismountPokemonPacket.ID, DismountPokemonPacket::decode, DismountPokemonPacketHandler))
list.add(PacketRegisterInfo(ServerboundUpdateDriverInputPacket.ID, ServerboundUpdateDriverInputPacket::decode, DriverInputPacketHandler))
@@ -1,106 +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
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.CobblemonNetwork.sendPacket
import com.cobblemon.mod.common.api.data.DataRegistry
import com.cobblemon.mod.common.api.molang.ExpressionLike
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourSettings
import com.cobblemon.mod.common.api.riding.behaviour.types.air.BirdSettings
import com.cobblemon.mod.common.api.riding.behaviour.types.air.GliderSettings
import com.cobblemon.mod.common.api.riding.behaviour.types.air.HelicopterSettings
import com.cobblemon.mod.common.api.riding.behaviour.types.air.HoverSettings
import com.cobblemon.mod.common.api.riding.behaviour.types.air.JetSettings
import com.cobblemon.mod.common.api.riding.behaviour.types.air.RocketSettings
import com.cobblemon.mod.common.api.riding.behaviour.types.land.HorseSettings
import com.cobblemon.mod.common.api.riding.behaviour.types.land.MinekartSettings
import com.cobblemon.mod.common.api.riding.behaviour.types.land.VehicleSettings
import com.cobblemon.mod.common.api.riding.behaviour.types.liquid.BoatSettings
import com.cobblemon.mod.common.api.riding.behaviour.types.liquid.BurstSettings
import com.cobblemon.mod.common.api.riding.behaviour.types.liquid.DolphinSettings
import com.cobblemon.mod.common.net.messages.client.data.RideSettingsSyncPacket
import com.cobblemon.mod.common.util.adapters.ExpressionAdapter
import com.cobblemon.mod.common.util.adapters.ExpressionLikeAdapter
import com.cobblemon.mod.common.util.adapters.FloatNumberRangeAdapter
import com.cobblemon.mod.common.util.cobblemonResource
import com.google.gson.GsonBuilder
import net.minecraft.advancements.critereon.MinMaxBounds
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
import net.minecraft.server.packs.PackType
import net.minecraft.server.packs.resources.ResourceManager
object CobblemonRideSettings : DataRegistry {
override val id: ResourceLocation = cobblemonResource("ride_settings")
override val type = PackType.SERVER_DATA
override val observable = SimpleObservable<CobblemonRideSettings>()
val gson = GsonBuilder()
.setPrettyPrinting()
.registerTypeAdapter(Expression::class.java, ExpressionAdapter)
.registerTypeAdapter(ExpressionLike::class.java, ExpressionLikeAdapter)
.registerTypeAdapter(MinMaxBounds.Doubles::class.java, FloatNumberRangeAdapter)
.create()
var bird = BirdSettings()
var glider = GliderSettings()
var helicopter = HelicopterSettings()
var hover = HoverSettings()
var jet = JetSettings()
var rocket = RocketSettings()
var horse = HorseSettings()
var minekart = MinekartSettings()
var vehicle = VehicleSettings()
var boat = BoatSettings()
var burst = BurstSettings()
var dolphin = DolphinSettings()
override fun sync(player: ServerPlayer) {
player.sendPacket(
RideSettingsSyncPacket(
bird = bird,
glider = glider,
helicopter = helicopter,
hover = hover,
jet = jet,
rocket = rocket,
horse = horse,
minekart = minekart,
vehicle = vehicle,
boat = boat,
burst = burst,
dolphin = dolphin
)
)
}
override fun reload(manager: ResourceManager) {
bird = loadStyle(manager, "bird", BirdSettings::class.java)
glider = loadStyle(manager, "glider", GliderSettings::class.java)
helicopter = loadStyle(manager, "helicopter", HelicopterSettings::class.java)
hover = loadStyle(manager, "hover", HoverSettings::class.java)
jet = loadStyle(manager, "jet", JetSettings::class.java)
rocket = loadStyle(manager, "rocket", RocketSettings::class.java)
horse = loadStyle(manager, "horse", HorseSettings::class.java)
minekart = loadStyle(manager, "minekart", MinekartSettings::class.java)
vehicle = loadStyle(manager, "vehicle", VehicleSettings::class.java)
boat = loadStyle(manager, "boat", BoatSettings::class.java)
burst = loadStyle(manager, "burst", BurstSettings::class.java)
dolphin = loadStyle(manager, "dolphin", DolphinSettings::class.java)
}
private fun <T : RidingBehaviourSettings> loadStyle(manager: ResourceManager, name: String, clazz: Class<T>): T {
manager.getResourceOrThrow(cobblemonResource("ride_settings/$name.json")).open().use {
return gson.fromJson(it.reader(), clazz)
}
}
}
@@ -78,7 +78,6 @@ object Abilities : DataRegistry {
fun all() = this.abilityMap.values.toList()
fun first() = this.abilityMap.values.first()
fun get(name: String) = abilityMap[name.lowercase()]
fun getOrDummy(name: String) = get(name) ?: DUMMY
fun getOrException(name: String) = get(name) ?: throw IllegalArgumentException("Unable to find ability of name: $name")
fun count() = this.abilityMap.size
@@ -49,9 +49,10 @@ class AbilityTemplate(
@JvmStatic
val CODEC: Codec<AbilityTemplate> = CodecUtils.createByStringCodec(
Abilities::getOrDummy,
Abilities::get,
AbilityTemplate::name
) { id -> "No ability for ID $id" }
}
}
@@ -117,7 +117,7 @@ Example behaviour (`uses_healing_machine.json`):
"configurations": [
{
"type": "add_tasks_to_activity",
"activity": "minecraft:idle",
"activity": "idle",
"tasksByPriority": {
"1": [
"heal_using_healing_machine",
@@ -12,9 +12,11 @@ import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.withQueryValue
import com.mojang.datafixers.util.Either
import kotlin.jvm.optionals.getOrNull
import net.minecraft.world.entity.LivingEntity
@@ -25,9 +27,7 @@ import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.entity.ai.sensing.SensorType
/**
* Checks nearby entities and counts how many have nominated this entity as their leader. We normally update this
* as people join and leave the herd, but this is a fallback to make sure we don't get stuck with an incorrect value
* like if the entities die or whatever.
* Checks nearby entities and counts how many have nominated this entity as their leader.
*
* @author Hiroku
* @since June 17th, 2025
@@ -53,7 +53,7 @@ class FindAirTaskConfig : SingleTaskConfig {
it.absent(CobblemonMemories.PATH_COOLDOWN)
).apply(it) { walkTarget, pathCooldown ->
Trigger { world, entity, time ->
if (!entity.getIsSubmerged() || entity.isInLava) {
if (!entity.getIsSubmerged()) {
return@Trigger false
}
@@ -8,16 +8,17 @@
package com.cobblemon.mod.common.api.ai.config.task
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
import com.cobblemon.mod.common.api.storage.party.PartyStore
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.pokemon.ai.ToleratedHerdLeader
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.withQueryValue
import com.mojang.datafixers.util.Either
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.behavior.BehaviorControl
@@ -52,25 +53,24 @@ class FindHerdLeaderTaskConfig : SingleTaskConfig {
it is PokemonEntity &&
it != currentLeader &&
it.pokemon.storeCoordinates.get()?.store !is PartyStore &&
it.getHerdSize() < it.behaviour.herd.maxSize &&
!it.brain.hasMemoryValue(CobblemonMemories.POKEMON_DROWSY)
it.getHerdSize() < it.behaviour.herd.maxSize
}.forEach { possibleLeader ->
possibleLeader as PokemonEntity
val bestMatchingHerdLeader = entity.behaviour.herd.bestMatchLeader(entity, possibleLeader) ?: return@forEach
val possibleLeaderIsAlreadyALeader = possibleLeader.brain.hasMemoryValue(CobblemonMemories.HERD_SIZE)
val currentLeaderHerdTier = currentLeaderEntity?.getHerdTier() ?: entity.getHerdTier()
if (currentLeaderHerdTier >= bestMatchingHerdLeader.tier) {
val matchingHerdLeader = entity.behaviour.herd.bestMatchLeader(entity, possibleLeader) ?: return@forEach
val alreadyIsALeader = possibleLeader.brain.hasMemoryValue(CobblemonMemories.HERD_SIZE)
val nestedHerdTier = currentLeaderEntity?.getHerdTier() ?: 0
if (nestedHerdTier >= matchingHerdLeader.tier) {
return@forEach // This leader is following a leader of a higher tier, we can't replace them
}
val distance = entity.distanceTo(possibleLeader)
if (bestMatchingHerdLeader.tier < bestTier) {
if (matchingHerdLeader.tier < bestTier) {
return@forEach // No need to check further if this leader is worse than the best found so far
} else if ((distance > bestDistance && !possibleLeaderIsAlreadyALeader) && bestMatchingHerdLeader.tier == bestTier) {
} else if ((distance > bestDistance && !alreadyIsALeader) && matchingHerdLeader.tier == bestTier) {
return@forEach // No need to check further if this leader is farther than the best found so far, unless they are already a leader - I'd prefer to join a pack
} else {
bestLeader = possibleLeader
bestDistance = distance
bestTier = bestMatchingHerdLeader.tier
bestTier = matchingHerdLeader.tier
}
}
return bestLeader ?: currentLeaderEntity
@@ -118,7 +118,6 @@ class FindHerdLeaderTaskConfig : SingleTaskConfig {
herdLeader.set(bestLeader.uuid.toString())
bestLeader.brain.eraseMemory(CobblemonMemories.HERD_LEADER)
bestLeader.adjustHerdSize(1)
return@Trigger true
}
}
@@ -14,19 +14,15 @@ import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
import com.cobblemon.mod.common.api.ai.WrapperLivingEntityTask
import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.api.molang.ExpressionLike
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.entity.pokemon.ai.tasks.FindRestingPlaceTask
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.asExpressionLike
import com.cobblemon.mod.common.util.resolveBoolean
import com.mojang.datafixers.util.Either
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.behavior.BehaviorControl
import net.minecraft.world.entity.ai.memory.MemoryModuleType
class FindRestingPlaceTaskConfig : SingleTaskConfig {
val condition: ExpressionLike = "true".asExpressionLike()
val horizontalSearchDistance: ExpressionOrEntityVariable = Either.left("16".asExpression())
val verticalSearchDistance: ExpressionOrEntityVariable = Either.left("5".asExpression())
@@ -35,13 +31,6 @@ class FindRestingPlaceTaskConfig : SingleTaskConfig {
entity: LivingEntity,
behaviourConfigurationContext: BehaviourConfigurationContext
): BehaviorControl<in LivingEntity>? {
if (entity !is PokemonEntity) {
return null
}
if (!behaviourConfigurationContext.runtime.resolveBoolean(condition)) {
return null
}
behaviourConfigurationContext.addMemories(
CobblemonMemories.PATH_COOLDOWN,
CobblemonMemories.POKEMON_DROWSY,
@@ -9,36 +9,44 @@
package com.cobblemon.mod.common.api.ai.config.task
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
import com.cobblemon.mod.common.api.ai.WrapperLivingEntityTask
import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
import com.cobblemon.mod.common.entity.ai.FleeFromAttackerTask
import com.cobblemon.mod.common.util.asExpression
import com.mojang.datafixers.util.Either
import com.cobblemon.mod.common.api.ai.config.task.SharedEntityVariables.FLEE_DESIRED_DISTANCE
import com.cobblemon.mod.common.api.ai.config.task.SharedEntityVariables.FLEE_SPEED_MULTIPLIER
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
import com.cobblemon.mod.common.util.withQueryValue
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.PathfinderMob
import net.minecraft.world.entity.ai.behavior.BehaviorControl
import net.minecraft.world.entity.ai.behavior.SetWalkTargetAwayFrom
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.entity.ai.sensing.SensorType
class FleeAttackerTaskConfig : SingleTaskConfig {
val avoidDurationTicks: ExpressionOrEntityVariable = Either.left("600".asExpression())
var condition = booleanVariable(SharedEntityVariables.FEAR_CATEGORY, "flee_attacker", true).asExpressible()
var speedMultiplier = numberVariable(SharedEntityVariables.FEAR_CATEGORY, FLEE_SPEED_MULTIPLIER, 0.5).asExpressible()
var desiredDistance = numberVariable(SharedEntityVariables.FEAR_CATEGORY, FLEE_DESIRED_DISTANCE, 9).asExpressible()
override fun getVariables(
entity: LivingEntity,
behaviourConfigurationContext: BehaviourConfigurationContext
): List<MoLangConfigVariable> {
return listOf(avoidDurationTicks).asVariables()
}
override fun getVariables(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext) = listOf(
condition,
speedMultiplier,
desiredDistance
).asVariables()
override fun createTask(
entity: LivingEntity,
behaviourConfigurationContext: BehaviourConfigurationContext
): BehaviorControl<in LivingEntity> {
behaviourConfigurationContext.addMemories(
MemoryModuleType.HURT_BY_ENTITY,
MemoryModuleType.AVOID_TARGET
)
): BehaviorControl<in LivingEntity>? {
if (!condition.resolveBoolean(behaviourConfigurationContext.runtime) || entity !is PathfinderMob) return null
behaviourConfigurationContext.addMemories(MemoryModuleType.HURT_BY_ENTITY, MemoryModuleType.WALK_TARGET)
behaviourConfigurationContext.addSensors(SensorType.HURT_BY)
return FleeFromAttackerTask.create(avoidDurationTicks.asExpression())
val speedMultiplier = speedMultiplier.resolveFloat(behaviourConfigurationContext.runtime)
val desiredDistance = desiredDistance.resolveInt(behaviourConfigurationContext.runtime)
return WrapperLivingEntityTask(
SetWalkTargetAwayFrom.entity(MemoryModuleType.HURT_BY_ENTITY, speedMultiplier, desiredDistance, false),
PathfinderMob::class.java
)
}
}
@@ -57,7 +57,7 @@ class GoToLandTaskConfig : SingleTaskConfig {
it.absent(CobblemonMemories.PATH_COOLDOWN)
).apply(it) { walkTarget, pathCooldown ->
Trigger { world, entity, time ->
if (!entity.isInWater) {
if (!entity.isInLiquid) {
return@Trigger false
}
@@ -11,20 +11,15 @@ package com.cobblemon.mod.common.api.ai.config.task
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.CobblemonSensors
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
import com.cobblemon.mod.common.api.ai.WrapperLivingEntityTask
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.entity.pokemon.ai.tasks.GoToSleepTask
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.resolveBoolean
import com.mojang.datafixers.util.Either
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.behavior.BehaviorControl
import net.minecraft.world.entity.ai.memory.MemoryModuleType
class GoToSleepTaskConfig : SingleTaskConfig {
var onlyFromStatus: ExpressionOrEntityVariable = Either.left("false".asExpression())
override fun getVariables(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext) = emptyList<MoLangConfigVariable>()
override fun createTask(
entity: LivingEntity,
@@ -39,6 +34,6 @@ class GoToSleepTaskConfig : SingleTaskConfig {
CobblemonMemories.POKEMON_SLEEPING
)
behaviourConfigurationContext.addSensors(CobblemonSensors.POKEMON_DROWSY)
return WrapperLivingEntityTask(GoToSleepTask.create(onlyFromStatus.resolveBoolean(behaviourConfigurationContext.runtime)), PokemonEntity::class.java)
return WrapperLivingEntityTask(GoToSleepTask.create(), PokemonEntity::class.java)
}
}
@@ -10,28 +10,17 @@ package com.cobblemon.mod.common.api.ai.config.task
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
import com.cobblemon.mod.common.entity.npc.ai.LookAtSpeakerTask
import com.cobblemon.mod.common.util.asExpression
import com.mojang.datafixers.util.Either
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.behavior.BehaviorControl
import net.minecraft.world.entity.ai.memory.MemoryModuleType
class LookAtSpeakerTaskConfig : SingleTaskConfig {
val condition: ExpressionOrEntityVariable = Either.left("true".asExpression())
override fun getVariables(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext) = emptyList<MoLangConfigVariable>()
override fun createTask(
entity: LivingEntity,
behaviourConfigurationContext: BehaviourConfigurationContext
): BehaviorControl<in LivingEntity>? {
if (!condition.resolveBoolean(behaviourConfigurationContext.runtime)) {
return null
}
return LookAtSpeakerTask.create().also {
behaviourConfigurationContext.addMemories(MemoryModuleType.LOOK_TARGET, CobblemonMemories.DIALOGUES)
}
) = LookAtSpeakerTask.create().also {
behaviourConfigurationContext.addMemories(MemoryModuleType.LOOK_TARGET, CobblemonMemories.DIALOGUES)
}
}
@@ -8,14 +8,15 @@
package com.cobblemon.mod.common.api.ai.config.task
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.withQueryValue
import com.mojang.datafixers.util.Either
import java.util.UUID
import net.minecraft.world.entity.LivingEntity
@@ -81,8 +82,6 @@ class MaintainHerdLeaderTaskConfig : SingleTaskConfig {
if (bestLeader != null && bestLeader != leader) {
bestLeader.brain.eraseMemory(CobblemonMemories.HERD_LEADER)
entity.brain.setMemory(CobblemonMemories.HERD_LEADER, bestLeader.uuid.toString())
bestLeader.adjustHerdSize(1)
leader.adjustHerdSize(-1)
}
}
return@Trigger true
@@ -162,11 +162,11 @@ on the entity type and this piggy-backs off the Minecraft list of aggressive mob
The 'calm_down' type creates a task that will make the entity calm down when it is no longer being hurt and is far from hostiles and its attacker.
- `condition`: A MoLang expression (with `q.entity` as the entity) that determines if this entire task should exist on the entity or not. This runs at the time of entity creation, not on tick.
### walk_away_from_avoid_target
The 'flee_attacker' type creates a task that will make the entity flee from its avoid target.
### flee_attacker
The 'flee_attacker' type creates a task that will make the entity flee from its attacker when it is hurt.
- `condition`: A MoLang expression (with `q.entity` as the entity) that determines if this entire task should exist on the entity or not. This runs at the time of entity creation, not on tick.
- `speedMultiplier`: A MoLang expression that determines the speed multiplier for fleeing from the avoid target. Defaults to 0.5 which is an elevated speed to usual.
- `desiredDistance`: A MoLang expression that determines the distance from the target that the entity should try to maintain. Defaults to 9.
- `speedMultiplier`: A MoLang expression that determines the speed multiplier for fleeing from the attacker. Defaults to 0.5 which is an elevated speed to usual.
- `desiredDistance`: A MoLang expression that determines the distance from the attacker that the entity should try to maintain. Defaults to 9.
### flee_nearest_hostile
The 'flee_nearest_hostile' type creates a task that will make the entity flee from the nearest hostile entity when it is hurt.
@@ -78,9 +78,8 @@ interface TaskConfig {
cobblemonResource("switch_to_panic_when_hurt") to SwitchToPanicWhenHurtTaskConfig::class.java,
cobblemonResource("switch_to_panic_when_hostiles_nearby") to SwitchToPanicWhenHostilesNearbyTaskConfig::class.java,
cobblemonResource("calm_down") to CalmDownTaskConfig::class.java,
cobblemonResource("walk_away_from_avoid_target") to WalkAwayFromAvoidTargetTaskConfig::class.java,
cobblemonResource("flee_nearest_hostile") to FleeNearestHostileTaskConfig::class.java,
cobblemonResource("flee_attacker") to FleeAttackerTaskConfig::class.java,
cobblemonResource("flee_nearest_hostile") to FleeNearestHostileTaskConfig::class.java,
cobblemonResource("fly_in_circles") to FlyInCirclesTaskConfig::class.java,
cobblemonResource("run_script") to RunScript::class.java,
cobblemonResource("look_in_direction") to LookInDirectionTaskConfig::class.java,
@@ -1,53 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.ai.config.task
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
import com.cobblemon.mod.common.api.ai.WrapperLivingEntityTask
import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.api.ai.config.task.SharedEntityVariables.FLEE_DESIRED_DISTANCE
import com.cobblemon.mod.common.api.ai.config.task.SharedEntityVariables.FLEE_SPEED_MULTIPLIER
import com.cobblemon.mod.common.util.asExpression
import com.mojang.datafixers.util.Either
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.PathfinderMob
import net.minecraft.world.entity.ai.behavior.BehaviorControl
import net.minecraft.world.entity.ai.behavior.SetWalkTargetAwayFrom
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.entity.ai.sensing.SensorType
class WalkAwayFromAvoidTargetTaskConfig : SingleTaskConfig {
var condition: ExpressionOrEntityVariable = Either.left("true".asExpression())
var speedMultiplier = numberVariable(SharedEntityVariables.FEAR_CATEGORY, FLEE_SPEED_MULTIPLIER, 0.7).asExpressible()
var desiredDistance = numberVariable(SharedEntityVariables.FEAR_CATEGORY, FLEE_DESIRED_DISTANCE, 9).asExpressible()
override fun getVariables(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext) = listOf(
condition,
speedMultiplier,
desiredDistance
).asVariables()
override fun createTask(
entity: LivingEntity,
behaviourConfigurationContext: BehaviourConfigurationContext
): BehaviorControl<in LivingEntity>? {
if (!condition.resolveBoolean(behaviourConfigurationContext.runtime) || entity !is PathfinderMob) return null
behaviourConfigurationContext.addMemories(MemoryModuleType.HURT_BY_ENTITY, MemoryModuleType.WALK_TARGET)
behaviourConfigurationContext.addSensors(SensorType.HURT_BY)
val speedMultiplier = speedMultiplier.resolveFloat(behaviourConfigurationContext.runtime)
val desiredDistance = desiredDistance.resolveInt(behaviourConfigurationContext.runtime)
return WrapperLivingEntityTask(
SetWalkTargetAwayFrom.entity(MemoryModuleType.HURT_BY_ENTITY, speedMultiplier, desiredDistance, false),
PathfinderMob::class.java
)
}
}
@@ -14,8 +14,6 @@ import com.cobblemon.mod.common.api.ai.CobblemonWanderControl
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
import com.cobblemon.mod.common.entity.OmniPathingEntity
import com.cobblemon.mod.common.entity.ai.CobblemonRandomSurfacePos
import com.cobblemon.mod.common.entity.ai.CobblemonWalkTarget
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.mainThreadRuntime
@@ -28,7 +26,6 @@ import net.minecraft.server.level.ServerLevel
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.PathfinderMob
import net.minecraft.world.entity.ai.behavior.BehaviorControl
import net.minecraft.world.entity.ai.behavior.BehaviorUtils
import net.minecraft.world.entity.ai.behavior.BlockPosTracker
import net.minecraft.world.entity.ai.behavior.declarative.BehaviorBuilder
import net.minecraft.world.entity.ai.behavior.declarative.Trigger
@@ -40,31 +37,27 @@ import net.minecraft.world.level.pathfinder.PathType
class WanderTaskConfig : SingleTaskConfig {
companion object {
const val WANDER = "wander" // Category
const val MAX_LOOK_DOWN_DISTANCE = 64
}
val condition = booleanVariable(WANDER, "wanders", true).asExpressible()
val wanderChance = numberVariable(WANDER, "wander_chance", 1 / (20 * 6F)).asExpressible()
val wanderChance = numberVariable(WANDER, "wander_chance", 1/(20 * 6F)).asExpressible()
val horizontalRange = numberVariable(WANDER, "horizontal_wander_range", 10).asExpressible()
val verticalRange = numberVariable(WANDER, "vertical_wander_range", 5).asExpressible()
val speedMultiplier =
numberVariable(SharedEntityVariables.MOVEMENT_CATEGORY, SharedEntityVariables.WALK_SPEED, 0.35).asExpressible()
val avoidTargetingAir: ExpressionOrEntityVariable =
Either.left("true".asExpression()) // Whether to avoid air blocks when wandering
val speedMultiplier = numberVariable(SharedEntityVariables.MOVEMENT_CATEGORY, SharedEntityVariables.WALK_SPEED, 0.35).asExpressible()
val avoidTargetingAir: ExpressionOrEntityVariable = Either.left("true".asExpression()) // Whether to avoid air blocks when wandering
val minimumHeight: ExpressionOrEntityVariable = Either.left("0".asExpression()) // Height off the ground
val maximumHeight: ExpressionOrEntityVariable = Either.left("-1".asExpression()) // Height off the ground
override fun getVariables(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext) =
listOf(
condition,
wanderChance,
horizontalRange,
verticalRange,
speedMultiplier,
avoidTargetingAir,
minimumHeight,
maximumHeight
).asVariables()
override fun getVariables(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext) = listOf(
condition,
wanderChance,
horizontalRange,
verticalRange,
speedMultiplier,
avoidTargetingAir,
minimumHeight,
maximumHeight
).asVariables()
private fun applyHeightConstraints(
pos: BlockPos,
@@ -80,18 +73,10 @@ class WanderTaskConfig : SingleTaskConfig {
val altitude = if (!block.isAir) {
0
} else {
(1..MAX_LOOK_DOWN_DISTANCE).firstOrNull {
(1..64).firstOrNull {
val newPos = pos.below(it)
if (!world.getBlockState(newPos).isAir) {
return@firstOrNull true
} else if (newPos.y <= world.minBuildHeight) {
// Don't adjust downward into the void (Mostly if we're in the end)
// This doesn't stop fliers from wandering off end islands,
// but it does stop them from diving directly into the void.
return pos
}
return@firstOrNull false
} ?: MAX_LOOK_DOWN_DISTANCE
return@firstOrNull !world.getBlockState(newPos).isAir
} ?: Int.MAX_VALUE
}
if (altitude > maximumHeight && maximumHeight >= 0) {
@@ -132,7 +117,7 @@ class WanderTaskConfig : SingleTaskConfig {
it.registered(CobblemonMemories.WANDER_CONTROL)
).apply(it) { walkTarget, lookTarget, pathCooldown, wanderControl ->
Trigger { world, entity, time ->
if (entity !is PathfinderMob || (entity.isUnderWater && !(entity.canBreatheUnderwater() && !((entity as OmniPathingEntity).canSwimInWater())))) {
if (entity !is PathfinderMob || entity.isUnderWater) {
return@Trigger false
}
@@ -140,9 +125,10 @@ class WanderTaskConfig : SingleTaskConfig {
mainThreadRuntime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
val avoidsTargetingAir = avoidTargetingAir.resolveBoolean(mainThreadRuntime)
val wanderChance = mainThreadRuntime.resolveFloat(wanderChanceExpression)
if (wanderChance <= 0 || world.random.nextFloat() > wanderChance) {
if (wanderChance <= 0 || world.random.nextFloat() > wanderChance || !wanderControl.allowLand) {
return@Trigger false
}
pathCooldown.setWithExpiry(true, wanderControl.pathCooldownTicks.toLong())
val minimumHeight = minimumHeight.resolveInt(mainThreadRuntime)
val maximumHeight = maximumHeight.resolveInt(mainThreadRuntime)
@@ -155,35 +141,21 @@ class WanderTaskConfig : SingleTaskConfig {
HoverRandomPos.getPos(
entity,
horizontalRange.resolveInt(mainThreadRuntime),
verticalRange.resolveInt(mainThreadRuntime),
maxOf(verticalRange.resolveInt(mainThreadRuntime), maximumHeight), // In case they fly up pretty high and need to find a way down
entity.random.nextFloat() - 0.5,
entity.random.nextFloat() - 0.5,
Math.PI.toFloat(),
maximumHeight,
minimumHeight
)
} else if (entity.isUnderWater && entity.canBreatheUnderwater() && !((entity as OmniPathingEntity).canSwimInWater())) {
BehaviorUtils.getRandomSwimmablePos(
entity,
horizontalRange.resolveInt(mainThreadRuntime),
verticalRange.resolveInt(mainThreadRuntime)
)
} else if (entity is OmniPathingEntity && (entity.canWalkOnWater() || entity.canWalkOnLava())) {
CobblemonRandomSurfacePos.getPos(
entity,
horizontalRange.resolveInt(mainThreadRuntime),
verticalRange.resolveInt(mainThreadRuntime)
)
} else {
LandRandomPos.getPos(
entity,
horizontalRange.resolveInt(mainThreadRuntime),
verticalRange.resolveInt(mainThreadRuntime)
)
LandRandomPos.getPos(
entity,
horizontalRange.resolveInt(mainThreadRuntime),
verticalRange.resolveInt(mainThreadRuntime)
)
} ?: continue
if (targetVec == null) continue
pos = applyHeightConstraints(
pos = BlockPos.containing(targetVec),
minimumHeight = minimumHeight,
@@ -201,17 +173,8 @@ class WanderTaskConfig : SingleTaskConfig {
pos = pos,
speedModifier = speedMultiplier.resolveFloat(mainThreadRuntime),
completionRange = 0,
nodeTypeFilter = { nodeType ->
entity.canBreatheUnderwater() || nodeType !in listOf(
PathType.WATER,
PathType.WATER_BORDER
)
},
destinationNodeTypeFilter = { nodeType ->
!avoidsTargetingAir || nodeType !in listOf(
PathType.OPEN
)
}
nodeTypeFilter = { nodeType -> nodeType !in listOf(PathType.WATER, PathType.WATER_BORDER) },
destinationNodeTypeFilter = { nodeType -> !avoidsTargetingAir || nodeType !in listOf(PathType.OPEN) }
)
)
lookTarget.set(BlockPosTracker(pos.toVec3d().add(0.0, entity.eyeHeight.toDouble(), 0.0)))
@@ -31,17 +31,15 @@ import net.minecraft.world.entity.ai.behavior.BlockPosTracker
import net.minecraft.world.entity.ai.behavior.declarative.BehaviorBuilder
import net.minecraft.world.entity.ai.behavior.declarative.Trigger
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.level.material.Fluids
import net.minecraft.world.level.pathfinder.PathType
import net.minecraft.world.phys.Vec3
import kotlin.math.ceil
class WaterWanderTaskConfig : SingleTaskConfig {
val condition = booleanVariable(WANDER, "water_wanders", true).asExpressible()
val wanderChance = numberVariable(WANDER, "water_wander_chance", 1/(20 * 3F)).asExpressible()
val speedMultiplier = numberVariable(SharedEntityVariables.MOVEMENT_CATEGORY, SharedEntityVariables.WALK_SPEED, 0.35).asExpressible()
val horizontalRange: ExpressionOrEntityVariable = Either.left("10.0".asExpression())
val verticalRange: ExpressionOrEntityVariable = Either.left("7.0".asExpression())
val verticalRange: ExpressionOrEntityVariable = Either.left("3.0".asExpression())
override fun getVariables(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): List<MoLangConfigVariable> {
return listOf(condition, wanderChance, speedMultiplier).asVariables()
@@ -102,33 +100,6 @@ class WaterWanderTaskConfig : SingleTaskConfig {
return@Trigger false
}
// Move target pos down so that we don't move out of water too much
val requiredDepth = ceil(0.5 + entity.eyeHeight)
// Get the current depth of the block
var depth = 1
var testPos = pos.above()
var blockState = entity.level().getBlockState(testPos)
while (blockState.fluidState.type == Fluids.WATER) {
depth++
testPos = testPos!!.above()
blockState = entity.level().getBlockState(testPos)
}
if (depth < requiredDepth) {
// Iterate down until we reach the desired min depth or we reach a non water block
pos = pos.below()
blockState = entity.level().getBlockState(pos)
while (blockState.fluidState.type == Fluids.WATER && depth < requiredDepth) {
depth ++
pos = pos!!.below()
blockState = entity.level().getBlockState(pos)
}
}
if (depth < requiredDepth) {
return@Trigger false
}
walkTarget.set(
CobblemonWalkTarget(
pos = pos,
@@ -8,6 +8,7 @@
package com.cobblemon.mod.common.api.cooking
import net.minecraft.ChatFormatting
import net.minecraft.util.StringRepresentable
/**
@@ -17,16 +18,16 @@ import net.minecraft.util.StringRepresentable
* @author Licious
* @since November 28th, 2022
*/
enum class Flavour(val colour: Int): StringRepresentable {
SPICY(0xFA795E),
DRY(0x65AFF3),
SWEET(0xFF82CF),
BITTER(0x77C96A),
SOUR(0xF3D465),
MILD(0x9D99BF);
enum class Flavour(val colour: Int, val chatFormatting: ChatFormatting): StringRepresentable {
SPICY(0x00FF00, ChatFormatting.RED),
DRY(0xFF0000, ChatFormatting.DARK_AQUA),
SWEET(0x0000FF, ChatFormatting.LIGHT_PURPLE),
BITTER(0xFFFF00, ChatFormatting.GREEN),
SOUR(0xFF00FF, ChatFormatting.YELLOW),
MILD(0xFF00FF, ChatFormatting.DARK_PURPLE);
override fun getSerializedName() = name
companion object {
val CODEC = StringRepresentable.fromEnum<Flavour> { Flavour.entries.toTypedArray() }
}
}
}
@@ -9,11 +9,13 @@
package com.cobblemon.mod.common.api.cooking
import com.cobblemon.mod.common.CobblemonItemComponents
import com.cobblemon.mod.common.item.PokePuffItem
import com.cobblemon.mod.common.item.PokePuffItem.Companion.DISLIKED_FLAVOR_MULTIPLIER
import com.cobblemon.mod.common.item.PokePuffItem.Companion.LIKED_FLAVOR_MULTIPLIER
import com.cobblemon.mod.common.pokemon.Nature
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.item.ItemStack
import net.minecraft.core.registries.BuiltInRegistries
object PokePuffUtils {
@@ -23,12 +25,11 @@ object PokePuffUtils {
val (baseFriendship) = calculateBaseFriendship(stack)
val multiplier = if (
dominantFlavour == nature.favouriteFlavour ||
(dominantFlavour == Flavour.MILD && isNeutralNature)
) LIKED_FLAVOR_MULTIPLIER else -DISLIKED_FLAVOR_MULTIPLIER
return (baseFriendship / multiplier).toInt()
return when {
dominantFlavour == nature.favouriteFlavour -> (baseFriendship / LIKED_FLAVOR_MULTIPLIER).toInt()
dominantFlavour == Flavour.MILD && isNeutralNature -> (baseFriendship / 2.0).coerceAtLeast(1.0).toInt()
else -> (-baseFriendship / DISLIKED_FLAVOR_MULTIPLIER).toInt()
}
}
fun calculateBaseFriendship(stack: ItemStack): Pair<Double, Double> {
@@ -76,8 +77,8 @@ object PokePuffUtils {
Flavour.SPICY -> "attack"
Flavour.DRY -> "special_attack"
Flavour.SWEET -> "speed"
Flavour.BITTER -> "special_defence"
Flavour.SOUR -> "defence"
Flavour.BITTER -> "special_defense"
Flavour.SOUR -> "defense"
Flavour.MILD -> "neutral"
}
}
@@ -18,13 +18,14 @@ import net.minecraft.resources.ResourceLocation
* @since August 5th, 2022
*/
interface DataProvider {
/**
* Registers a [DataRegistry] to listen for updates.
* The updates will automatically happen on the correct sides based on [DataRegistry.type].
*
* @param registry The [DataRegistry] being registered.
*/
fun <T : DataRegistry> register(registry: T, reloadable: Boolean): T
fun <T : DataRegistry> register(registry: T): T
/**
* Attempts to find a [DataRegistry] with the given [ResourceLocation].
@@ -29,7 +29,6 @@ class DialoguePage(
var id: String = "",
var speaker: String? = null,
var lines: MutableList<DialogueText> = mutableListOf(),
var textColor: String? = null,
var input: DialogueInput = DialogueNoInput(),
var background: ResourceLocation? = null,
var gibber: DialogueGibber? = null,
@@ -43,7 +42,6 @@ class DialoguePage(
id: String = "",
speaker: String? = null,
lines: Iterable<MutableComponent>,
textColor: String? = null,
input: DialogueInput = DialogueNoInput(),
gibber: DialogueGibber? = null,
background: ResourceLocation? = null,
@@ -55,7 +53,6 @@ class DialoguePage(
id = id,
speaker = speaker,
lines = lines.map { WrappedDialogueText(it) }.toMutableList(),
textColor = textColor,
input = input,
gibber = gibber,
background = background,
@@ -78,4 +75,4 @@ class DialoguePage(
)
)
}
}
}
@@ -10,7 +10,8 @@ package com.cobblemon.mod.common.api.events
import com.cobblemon.mod.common.api.events.battles.BattleFaintedEvent
import com.cobblemon.mod.common.api.events.battles.BattleFledEvent
import com.cobblemon.mod.common.api.events.battles.BattleStartedEvent
import com.cobblemon.mod.common.api.events.battles.BattleStartedPostEvent
import com.cobblemon.mod.common.api.events.battles.BattleStartedPreEvent
import com.cobblemon.mod.common.api.events.battles.BattleVictoryEvent
import com.cobblemon.mod.common.api.events.battles.instruction.FormeChangeEvent
import com.cobblemon.mod.common.api.events.battles.instruction.MegaEvolutionEvent
@@ -21,7 +22,11 @@ import com.cobblemon.mod.common.api.events.berry.BerryMutationOfferEvent
import com.cobblemon.mod.common.api.events.berry.BerryMutationResultEvent
import com.cobblemon.mod.common.api.events.berry.BerryYieldCalculationEvent
import com.cobblemon.mod.common.api.events.drops.LootDroppedEvent
import com.cobblemon.mod.common.api.events.entity.*
import com.cobblemon.mod.common.api.events.entity.PokemonEntityLoadEvent
import com.cobblemon.mod.common.api.events.entity.PokemonEntitySaveEvent
import com.cobblemon.mod.common.api.events.entity.PokemonEntitySaveToWorldEvent
import com.cobblemon.mod.common.api.events.entity.SpawnBucketChosenEvent
import com.cobblemon.mod.common.api.events.entity.SpawnEvent
import com.cobblemon.mod.common.api.events.farming.ApricornHarvestEvent
import com.cobblemon.mod.common.api.events.fishing.*
import com.cobblemon.mod.common.api.events.item.LeftoversCreatedEvent
@@ -38,7 +43,11 @@ import com.cobblemon.mod.common.api.events.pokemon.healing.PokemonHealedEvent
import com.cobblemon.mod.common.api.events.pokemon.interaction.ExperienceCandyUseEvent
import com.cobblemon.mod.common.api.events.pokemon.interaction.PokemonInteractionGUICreationEvent
import com.cobblemon.mod.common.api.events.starter.StarterChosenEvent
import com.cobblemon.mod.common.api.events.storage.*
import com.cobblemon.mod.common.api.events.storage.ChangePCBoxWallpaperEvent
import com.cobblemon.mod.common.api.events.storage.ReleasePokemonEvent
import com.cobblemon.mod.common.api.events.storage.RenamePCBoxEvent
import com.cobblemon.mod.common.api.events.storage.WallpaperCollectionEvent
import com.cobblemon.mod.common.api.events.storage.WallpaperUnlockedEvent
import com.cobblemon.mod.common.api.events.world.BigRootPropagatedEvent
import com.cobblemon.mod.common.api.reactive.CancelableObservable
import com.cobblemon.mod.common.api.reactive.EventObservable
@@ -54,109 +63,69 @@ object CobblemonEvents {
@JvmField
val POKEMON_PROPERTY_INITIALISED = SimpleObservable<Unit>()
@JvmField
val COBBLEMON_INITIALISED = SimpleObservable<Unit>()
@JvmField
val DATA_SYNCHRONIZED = SimpleObservable<ServerPlayer>()
@JvmField
val SHOULDER_MOUNT = CancelableObservable<ShoulderMountEvent>()
@JvmField
val FRIENDSHIP_UPDATED = EventObservable<FriendshipUpdatedEvent>()
@JvmField
val FULLNESS_UPDATED = EventObservable<FullnessUpdatedEvent>()
@JvmField
val POKEMON_FAINTED = EventObservable<PokemonFaintedEvent>()
@JvmField
val EVOLUTION_ACCEPTED = CancelableObservable<EvolutionAcceptedEvent>()
@JvmField
val EVOLUTION_DISPLAY = EventObservable<EvolutionDisplayEvent>()
@JvmField
val EVOLUTION_TESTED = EventObservable<EvolutionTestedEvent>()
@JvmField
val EVOLUTION_COMPLETE = EventObservable<EvolutionCompleteEvent>()
@JvmField
val POKEMON_NICKNAMED = CancelableObservable<PokemonNicknamedEvent>()
@JvmField
val THROWN_POKEBALL_HIT = CancelableObservable<ThrownPokeballHitEvent>()
@JvmField
val POKEMON_CATCH_RATE = EventObservable<PokemonCatchRateEvent>()
@JvmField
val POKE_BALL_CAPTURE_CALCULATED = EventObservable<PokeBallCaptureCalculatedEvent>()
@JvmField
val POKEMON_CAPTURED = EventObservable<PokemonCapturedEvent>()
@JvmField
val FOSSIL_REVIVED = EventObservable<FossilRevivedEvent>()
@JvmField
val BATTLE_STARTED_PRE = CancelableObservable<BattleStartedEvent.Pre>()
val BATTLE_STARTED_PRE = CancelableObservable<BattleStartedPreEvent>()
@JvmField
val BATTLE_STARTED_POST = EventObservable<BattleStartedEvent.Post>()
val BATTLE_STARTED_POST = EventObservable<BattleStartedPostEvent>()
@JvmField
val BATTLE_FLED = EventObservable<BattleFledEvent>()
@JvmField
val BATTLE_VICTORY = EventObservable<BattleVictoryEvent>()
@JvmField
val BATTLE_FAINTED = EventObservable<BattleFaintedEvent>()
// instructions
@JvmField
val MEGA_EVOLUTION = EventObservable<MegaEvolutionEvent>()
@JvmField
val TERASTALLIZATION = EventObservable<TerastallizationEvent>()
@JvmField
val ZPOWER_USED = EventObservable<ZMoveUsedEvent>()
@JvmField
val FORME_CHANGE = EventObservable<FormeChangeEvent>()
@JvmField
val POKEMON_SENT_PRE = CancelableObservable<PokemonSentEvent.Pre>()
val POKEMON_SENT_PRE = CancelableObservable<PokemonSentPreEvent>()
@JvmField
val POKEMON_SENT_POST = EventObservable<PokemonSentPostEvent>()
@JvmField
val POKEMON_RECALLED = EventObservable<PokemonRecalledEvent>()
@JvmField
val POKEMON_SENT_POST = EventObservable<PokemonSentEvent.Post>()
@JvmField
val POKEMON_RECALL_PRE = CancelableObservable<PokemonRecallEvent.Pre>()
@JvmField
val POKEMON_RECALL_POST = EventObservable<PokemonRecallEvent.Post>()
@JvmField
val TRADE_EVENT_PRE = CancelableObservable<TradeEvent.Pre>()
@JvmField
val TRADE_EVENT_POST = EventObservable<TradeEvent.Post>()
@JvmField
val RIDE_EVENT_PRE = CancelableObservable<RidePokemonEvent.Pre>()
@JvmField
val RIDE_EVENT_APPLY_STAMINA = EventObservable<RidePokemonEvent.ApplyStamina>()
@JvmField
val RIDE_EVENT_POST = EventObservable<RidePokemonEvent.Post>()
val TRADE_COMPLETED = EventObservable<TradeCompletedEvent>()
@JvmField
val LEVEL_UP_EVENT = EventObservable<LevelUpEvent>()
@@ -165,21 +134,16 @@ object CobblemonEvents {
val POKEMON_HEALED = CancelableObservable<PokemonHealedEvent>()
@JvmField
/** CLIENT ONLY! */
/** CLIENT ONLY! */
val POKEMON_INTERACTION_GUI_CREATION = EventObservable<PokemonInteractionGUICreationEvent>()
@JvmField
val POKEMON_ENTITY_SAVE = EventObservable<PokemonEntitySaveEvent>()
@JvmField
val POKEMON_ENTITY_LOAD = CancelableObservable<PokemonEntityLoadEvent>()
@JvmField
val POKEMON_ENTITY_SAVE_TO_WORLD = CancelableObservable<PokemonEntitySaveToWorldEvent>()
@JvmField
val ENTITY_SPAWN = CancelableObservable<SpawnEvent<*>>()
@JvmField
val SHINY_CHANCE_CALCULATION = EventObservable<ShinyChanceCalculationEvent>()
@@ -194,56 +158,46 @@ object CobblemonEvents {
)
@JvmField
val EXPERIENCE_GAINED_EVENT_PRE = CancelableObservable<ExperienceGainedEvent.Pre>()
val EXPERIENCE_GAINED_EVENT_PRE = CancelableObservable<ExperienceGainedPreEvent>()
@JvmField
val EXPERIENCE_GAINED_EVENT_POST = EventObservable<ExperienceGainedEvent.Post>()
val EXPERIENCE_GAINED_EVENT_POST = EventObservable<ExperienceGainedPostEvent>()
@JvmField
val EXPERIENCE_CANDY_USE_PRE = CancelableObservable<ExperienceCandyUseEvent.Pre>()
@JvmField
val EXPERIENCE_CANDY_USE_POST = EventObservable<ExperienceCandyUseEvent.Post>()
@JvmField
val EV_GAINED_EVENT_PRE = CancelableObservable<EvGainedEvent.Pre>()
@JvmField
val EV_GAINED_EVENT_POST = EventObservable<EvGainedEvent.Post>()
@JvmField
val HYPER_TRAINED_IV_PRE = CancelableObservable<HyperTrainedIvEvent.Pre>()
@JvmField
val HYPER_TRAINED_IV_POST = EventObservable<HyperTrainedIvEvent.Post>()
@JvmField
val POKEMON_RELEASED_EVENT_PRE = CancelableObservable<ReleasePokemonEvent.Pre>()
@JvmField
val POKEMON_RELEASED_EVENT_POST = EventObservable<ReleasePokemonEvent.Post>()
@JvmField
val RENAME_PC_BOX_EVENT_PRE = CancelableObservable<RenamePCBoxEvent.Pre>()
@JvmField
val RENAME_PC_BOX_EVENT_POST = EventObservable<RenamePCBoxEvent.Post>()
@JvmField
val WALLPAPER_COLLECTION_EVENT = EventObservable<WallpaperCollectionEvent>()
@JvmField
val WALLPAPER_UNLOCKED_EVENT = CancelableObservable<WallpaperUnlockedEvent>()
@JvmField
val CHANGE_PC_BOX_WALLPAPER_EVENT_PRE = CancelableObservable<ChangePCBoxWallpaperEvent.Pre>()
@JvmField
val CHANGE_PC_BOX_WALLPAPER_EVENT_POST = EventObservable<ChangePCBoxWallpaperEvent.Post>()
@JvmField
val LOOT_DROPPED = CancelableObservable<LootDroppedEvent>()
@JvmField
val STARTER_CHOSEN = CancelableObservable<StarterChosenEvent>()
@@ -252,81 +206,58 @@ object CobblemonEvents {
@JvmField
val APRICORN_HARVESTED = EventObservable<ApricornHarvestEvent>()
// Berries
@JvmField
val BERRY_HARVEST = EventObservable<BerryHarvestEvent>()
@JvmField
val BERRY_MUTATION_OFFER = EventObservable<BerryMutationOfferEvent>()
@JvmField
val BERRY_MUTATION_RESULT = EventObservable<BerryMutationResultEvent>()
@JvmField
val BERRY_YIELD = EventObservable<BerryYieldCalculationEvent>()
@JvmField
val LEFTOVERS_CREATED = CancelableObservable<LeftoversCreatedEvent>()
@JvmField
val BIG_ROOT_PROPAGATED = CancelableObservable<BigRootPropagatedEvent>()
@JvmField
val HELD_ITEM_PRE = CancelableObservable<HeldItemEvent.Pre>()
@JvmField
val HELD_ITEM_POST = EventObservable<HeldItemEvent.Post>()
@JvmField
val COSMETIC_ITEM_PRE = CancelableObservable<HeldItemEvent.Pre>()
@JvmField
val COSMETIC_ITEM_POST = EventObservable<HeldItemEvent.Post>()
@JvmField
val POKEMON_GAINED = EventObservable<PokemonGainedEvent>()
@JvmField
val POKEMON_SEEN = CancelableObservable<PokemonSeenEvent>()
@JvmField
val POKEMON_ASPECTS_CHANGED = EventObservable<PokemonAspectsChangedEvent>()
@JvmField
val POKEDEX_DATA_CHANGED_PRE = CancelableObservable<PokedexDataChangedEvent.Pre>()
@JvmField
val POKEDEX_DATA_CHANGED_POST = EventObservable<PokedexDataChangedEvent.Post>()
// Fishing
@JvmField
val BAIT_SET = CancelableObservable<BaitSetEvent>()
@JvmField
val BAIT_SET_PRE = CancelableObservable<BaitSetEvent>()
@JvmField
val BAIT_CONSUMED = CancelableObservable<BaitConsumedEvent>()
@JvmField
val POKEROD_CAST_PRE = CancelableObservable<PokerodCastEvent.Pre>()
@JvmField
val POKEROD_CAST_POST = EventObservable<PokerodCastEvent.Post>()
@JvmField
val POKEROD_REEL = CancelableObservable<PokerodReelEvent>()
@JvmField
val BOBBER_SPAWN_POKEMON_PRE = CancelableObservable<BobberSpawnPokemonEvent.Pre>()
@JvmField
val BOBBER_SPAWN_POKEMON_MODIFY = EventObservable<BobberSpawnPokemonEvent.Modify>()
@JvmField
val BOBBER_SPAWN_POKEMON_POST = EventObservable<BobberSpawnPokemonEvent.Post>()
@JvmField
val BAIT_EFFECT_REGISTRATION = EventObservable<BaitEffectFunctionRegistryEvent>()
@@ -340,10 +271,8 @@ object CobblemonEvents {
@JvmField
val COLLECT_EGG = CancelableObservable<CollectEggEvent>()
@JvmField
val HATCH_EGG_PRE = CancelableObservable<HatchEggEvent.Pre>()
@JvmField
val HATCH_EGG_POST = EventObservable<HatchEggEvent.Post>()
@@ -12,75 +12,40 @@ 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.events.Cancelable
import com.cobblemon.mod.common.api.molang.MoLangFunctions.moLangFunctionMap
import com.cobblemon.mod.common.battles.ActiveBattlePokemon
import com.cobblemon.mod.common.battles.BattleSide
import com.cobblemon.mod.common.util.asArrayValue
import net.minecraft.network.chat.MutableComponent
/**
* Events regarding the starting of a [PokemonBattle].
* Event fired before a [PokemonBattle] is started. Canceling this event prevents the battle from being
* created and launched.
*
* @author Segfault Guy/MeAlam
* @property reason The text used to inform the participants why the battle was canceled. Keep null for a default error.
*
* @author Segfault Guy
* @since March 26th 2023
*/
interface BattleStartedEvent : BattleEvent {
/**
* The battle that is starting.
*/
override val battle: PokemonBattle
data class BattleStartedPreEvent (override val battle: PokemonBattle, var reason: MutableComponent? = null) : BattleEvent, Cancelable() {
val context = mutableMapOf(
"battle" to battle.struct,
"players" to battle.actors.filter { it.type == ActorType.PLAYER }.asArrayValue { it.struct },
"npcs" to battle.actors.filter { it.type == ActorType.NPC }.asArrayValue { it.struct },
"wild_pokemon" to battle.actors.filter { it.type == ActorType.WILD }.asArrayValue { it.struct }
)
/**
* Event fired before a [PokemonBattle] is started. Canceling this event prevents the battle from being
* created and launched.
*
* @property reason The text used to inform the participants why the battle was canceled. Keep null for a default error.
*
* @author Segfault Guy/MeAlam
* @since March 26th 2023
*/
data class Pre(
override val battle: PokemonBattle,
var reason: MutableComponent? = null
) : BattleStartedEvent, Cancelable() {
val context = mutableMapOf(
"battle" to battle.struct,
"players" to battle.actors.filter { it.type == ActorType.PLAYER }.asArrayValue { it.struct },
"npcs" to battle.actors.filter { it.type == ActorType.NPC }.asArrayValue { it.struct },
"wild_pokemon" to battle.actors.filter { it.type == ActorType.WILD }.asArrayValue { it.struct }
)
val functions = moLangFunctionMap(cancelFunc)
fun getBattleSide(side: Int) : BattleSide? {
return when (side) {
1 -> battle.side1
2 -> battle.side2
else -> null
}
}
fun getPokemonOnSide(side: Int) : List<ActiveBattlePokemon>? {
val battleSide = getBattleSide(side)
val activePokemon = battleSide?.activePokemon
return activePokemon
}
}
/**
* Event fired after a [PokemonBattle] starts.
*
* @author Segfault Guy/MeAlam
* @since March 26th 2023
*/
data class Post(
override val battle: PokemonBattle
) : BattleStartedEvent {
val context = mutableMapOf(
"battle" to battle.struct,
"players" to battle.actors.filter { it.type == ActorType.PLAYER }.asArrayValue { it.struct },
"npcs" to battle.actors.filter { it.type == ActorType.NPC }.asArrayValue { it.struct },
"wild_pokemon" to battle.actors.filter { it.type == ActorType.WILD }.asArrayValue { it.struct }
)
}
val functions = moLangFunctionMap(cancelFunc)
}
/**
* Event fired after a [PokemonBattle] starts.
*
* @author Segfault Guy
* @since March 26th 2023
*/
data class BattleStartedPostEvent (override val battle: PokemonBattle) : BattleEvent {
val context = mutableMapOf(
"battle" to battle.struct,
"players" to battle.actors.filter { it.type == ActorType.PLAYER }.asArrayValue { it.struct },
"npcs" to battle.actors.filter { it.type == ActorType.NPC }.asArrayValue { it.struct },
"wild_pokemon" to battle.actors.filter { it.type == ActorType.WILD }.asArrayValue { it.struct }
)
}
@@ -8,9 +8,11 @@
package com.cobblemon.mod.common.api.events.fishing
import com.bedrockk.molang.runtime.value.StringValue
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.api.reactive.CancelableObservable
import com.cobblemon.mod.common.util.server
import net.minecraft.world.item.ItemStack
@@ -20,12 +22,10 @@ import net.minecraft.world.item.ItemStack
* @param bait The ItemStack of the bait that is set on the rod.
*/
class BaitSetEvent(val rod: ItemStack, val bait: ItemStack) : Cancelable() {
val context = server()?.let { server ->
mutableMapOf(
"rod" to rod.asMoLangValue(server.registryAccess()),
"bait" to bait.asMoLangValue(server.registryAccess())
)
} ?: emptyMap()
val context = mutableMapOf(
"rod" to rod.asMoLangValue(server()!!.registryAccess()),
"bait" to bait.asMoLangValue(server()!!.registryAccess())
)
val functions = moLangFunctionMap(
cancelFunc
)
@@ -17,88 +17,67 @@ import com.cobblemon.mod.common.api.pokemon.experience.*
import com.cobblemon.mod.common.pokemon.Pokemon
/**
* Event fired when experience will be gained.
* Event fired when experience is about to be gained. Cancelling this event prevents
* any experience being added, and the amount of experience can be changed from [experience].
*
* @author Hiroku
* @since August 5th, 2022
*/
interface ExperienceGainedEvent {
/**
* The [Pokemon] gaining experience.
*/
val pokemon: Pokemon
/**
* The source of the experience being gained.
*/
val source: ExperienceSource
/**
* The amount of experience being gained.
*/
class ExperienceGainedPreEvent(
val pokemon: Pokemon,
val source: ExperienceSource,
var experience: Int
/**
* Event fired when experience is about to be gained. Cancelling this event prevents
* any experience being added, and the amount of experience can be changed from [experience].
*
* @author Hiroku/MeAlam
* @since August 5th, 2022
*/
data class Pre(
override val pokemon: Pokemon,
override val source: ExperienceSource,
override var experience: Int
) : ExperienceGainedEvent, Cancelable() {
val context = mutableMapOf(
"pokemon" to pokemon.struct,
"source" to StringValue(
when (source) {
is BattleExperienceSource -> "battle"
is CandyExperienceSource -> "interaction"
is CommandExperienceSource -> "command"
is SidemodExperienceSource -> source.sidemodId
else -> "unknown"
}
),
"experience" to DoubleValue(experience.toDouble())
)
val functions = moLangFunctionMap(
cancelFunc,
"set_amount" to {
experience = it.getInt(0)
DoubleValue.ONE
) : Cancelable() {
val context = mutableMapOf(
"pokemon" to pokemon.struct,
"source" to StringValue(
when (source) {
is BattleExperienceSource -> "battle"
is CandyExperienceSource -> "interaction"
is CommandExperienceSource -> "command"
is SidemodExperienceSource -> source.sidemodId
else -> "unknown"
}
)
}
),
"experience" to DoubleValue(experience.toDouble())
)
val functions = moLangFunctionMap(
cancelFunc,
"set_amount" to {
experience = it.getInt(0)
DoubleValue.ONE
}
)
}
/**
* Event fired when experience has been gained. Information about whether it leveled up or not is
* available.
*
* @author Hiroku/MeAlam
* @since August 5th, 2022
*/
data class Post(
override val pokemon: Pokemon,
override val source: ExperienceSource,
override var experience: Int,
val previousLevel: Int,
val currentLevel: Int,
val learnedMoves: MutableSet<MoveTemplate>
) : ExperienceGainedEvent {
val context = mutableMapOf(
"pokemon" to pokemon.struct,
"source" to StringValue(
when (source) {
is BattleExperienceSource -> "battle"
is CandyExperienceSource -> "interaction"
is CommandExperienceSource -> "command"
is SidemodExperienceSource -> source.sidemodId
else -> "unknown"
}
),
"experience" to DoubleValue(experience.toDouble()),
"previous_level" to DoubleValue(previousLevel.toDouble()),
"current_level" to DoubleValue(currentLevel.toDouble())
)
}
/**
* Event fired when experience has been gained. Information about whether it leveled up or not is
* available.
*
* @author Hiroku
* @since August 5th, 2022
*/
class ExperienceGainedPostEvent(
val pokemon: Pokemon,
val source: ExperienceSource,
val experience: Int,
val previousLevel: Int,
val currentLevel: Int,
val learnedMoves: MutableSet<MoveTemplate>
) {
val context = mutableMapOf(
"pokemon" to pokemon.struct,
"source" to StringValue(
when (source) {
is BattleExperienceSource -> "battle"
is CandyExperienceSource -> "interaction"
is CommandExperienceSource -> "command"
is SidemodExperienceSource -> source.sidemodId
else -> "unknown"
}
),
"experience" to DoubleValue(experience.toDouble()),
"previous_level" to DoubleValue(previousLevel.toDouble()),
"current_level" to DoubleValue(currentLevel.toDouble())
)
}
@@ -13,14 +13,14 @@ 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.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.getPlayer
import net.minecraft.server.level.ServerPlayer
interface HatchEggEvent {
val egg: PokemonProperties
val player: ServerPlayer
data class Pre(var egg : PokemonProperties, override val player: ServerPlayer) : HatchEggEvent, Cancelable() {
data class Pre(override var egg : PokemonProperties, override var player: ServerPlayer) : HatchEggEvent, Cancelable() {
val context = mutableMapOf(
"player" to (player.uuid.getPlayer()?.asMoLangValue() ?: DoubleValue.ZERO)
)
@@ -29,7 +29,7 @@ interface HatchEggEvent {
)
}
data class Post(override var player: ServerPlayer, val pokemon : Pokemon) : HatchEggEvent {
data class Post(override var egg : PokemonProperties, override var player: ServerPlayer) : HatchEggEvent {
val context = mutableMapOf(
"player" to (player.uuid.getPlayer()?.asMoLangValue() ?: DoubleValue.ZERO)
)
@@ -1,55 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.events.pokemon
import com.bedrockk.molang.runtime.value.StringValue
import com.cobblemon.mod.common.api.events.Cancelable
import com.cobblemon.mod.common.api.molang.MoLangFunctions.moLangFunctionMap
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.pokemon.Pokemon
/**
* Event fired when a [PokemonEntity] is recalled.
*
* @author Segfault Guy
* @since March 25th, 2023
*/
interface PokemonRecallEvent {
val pokemon: Pokemon
val oldEntity: PokemonEntity?
/**
* Event fired before a [PokemonEntity] is recalled.
*/
data class Pre(
override val pokemon: Pokemon,
override val oldEntity: PokemonEntity?
) : PokemonRecallEvent, Cancelable() {
val context = mutableMapOf(
"pokemon" to pokemon.struct,
"old_entity" to (oldEntity?.struct ?: StringValue("null"))
)
val functions = moLangFunctionMap(
cancelFunc
)
}
data class Post(
override val pokemon: Pokemon,
override val oldEntity: PokemonEntity?
) : PokemonRecallEvent {
val context = mutableMapOf(
"pokemon" to pokemon.struct,
"old_entity" to (oldEntity?.struct ?: StringValue("null"))
)
}
}
@@ -0,0 +1,23 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.events.pokemon
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.pokemon.Pokemon
/**
* Event fired when a [PokemonEntity] is recalled.
*
* @author Segfault Guy
* @since March 25th, 2023
*/
data class PokemonRecalledEvent (
val pokemon: Pokemon,
val oldEntity: PokemonEntity?
)
@@ -17,64 +17,39 @@ import net.minecraft.server.level.ServerLevel
import net.minecraft.world.phys.Vec3
/**
* Event fired when a party [Pokemon] is sent out.
* Event fired when a party [Pokemon] is sent out. Cancelling this event prevents a corresponding
* [PokemonEntity] from being instantiated and spawned into the world.
*
* @author Segfault Guy
* @since March 25th, 2023
*/
interface PokemonSentEvent {
/**
* The [Pokemon] being sent out.
*/
val pokemon: Pokemon
/**
* The [ServerLevel] the [Pokemon] is being sent out into.
*/
val level: ServerLevel
/**
* The [Vec3] position the [Pokemon] is being sent out at.
*/
data class PokemonSentPreEvent(
val pokemon: Pokemon,
val level: ServerLevel,
val position: Vec3
) : Cancelable() {
val context = mutableMapOf(
"pokemon" to pokemon.struct,
"position" to StringValue(position.toString())
)
val functions = moLangFunctionMap(
cancelFunc
)
}
/**
* Event fired when a party [Pokemon] is sent out. Cancelling this event prevents a corresponding
* [PokemonEntity] from being instantiated and spawned into the world.
*
* @author Segfault Guy/MeAlam
* @since March 25th, 2023
*/
data class Pre(
override val pokemon: Pokemon,
override val level: ServerLevel,
override val position: Vec3
) : PokemonSentEvent, Cancelable() {
val context = mutableMapOf(
"pokemon" to pokemon.struct,
"position" to StringValue(position.toString())
)
val functions = moLangFunctionMap(
cancelFunc
)
}
/**
* Event fired after a [PokemonEntity] is spawned from a player's party and after its animations are finished.
* Only fired for party [Pokemon] sent out with animations.
*
* @author Segfault Guy/MeAlam
* @since March 25th, 2023
*/
data class Post(
override val pokemon: Pokemon,
override val level: ServerLevel,
override val position: Vec3,
val pokemonEntity: PokemonEntity
) : PokemonSentEvent {
val context = mutableMapOf(
"pokemon" to pokemon.struct,
"pokemon_entity" to pokemonEntity.struct
)
}
/**
* Event fired after a [PokemonEntity] is spawned from a player's party and after its animations are finished.
* Only fired for party [Pokemon] sent out with animations.
*
* @author Segfault Guy
* @since March 25th, 2023
*/
data class PokemonSentPostEvent(
val pokemon: Pokemon,
val pokemonEntity: PokemonEntity
) {
val context = mutableMapOf(
"pokemon" to pokemon.struct,
"pokemon_entity" to pokemonEntity.struct
)
}
@@ -1,82 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.events.pokemon
import com.bedrockk.molang.runtime.value.DoubleValue
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.pokemon.PokemonEntity
import net.minecraft.server.level.ServerPlayer
/**
* The base for all events related to riding a Pokemon.
*
* @see [Pre]
* @see [Post]
*/
interface RidePokemonEvent {
val player: ServerPlayer
val pokemon: PokemonEntity
class Pre(
override val player: ServerPlayer,
override val pokemon: PokemonEntity
) : RidePokemonEvent, Cancelable() {
val context = mutableMapOf(
"player" to player.asMoLangValue(),
"pokemon" to pokemon.struct
)
val functions = moLangFunctionMap(
cancelFunc
)
}
class Post(
override val player: ServerPlayer,
override val pokemon: PokemonEntity
) : RidePokemonEvent {
val context = mutableMapOf(
"player" to player.asMoLangValue(),
"pokemon" to pokemon.struct
)
}
/**
* Event published when stamina is being applied. At this point you can modify the stamina that will get used on the
* mount or you can make the stamina infinite. Handy hey.
*/
class ApplyStamina(
override val player: ServerPlayer,
override val pokemon: PokemonEntity,
var rideStamina: Float
) : RidePokemonEvent {
val context = mutableMapOf(
"player" to player.asMoLangValue(),
"pokemon" to pokemon.struct,
"ride_stamina" to DoubleValue(rideStamina)
)
val functions = moLangFunctionMap(
"set_ride_stamina" to { params ->
rideStamina = params.getDouble(0).toFloat().coerceIn(0F, 1F)
DoubleValue.ONE
},
"set_infinite_stamina" to { _ ->
rideStamina = -1F
DoubleValue.ONE
},
)
fun setInfiniteStamina() {
rideStamina = -1F
}
fun isInfiniteStamina() = rideStamina == -1F
}
}
@@ -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.events.pokemon
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.trade.TradeParticipant
import com.cobblemon.mod.common.util.getPlayer
class TradeCompletedEvent(val tradeParticipant1 : TradeParticipant, val tradeParticipant1Pokemon : Pokemon, val tradeParticipant2 : TradeParticipant, val tradeParticipant2Pokemon : Pokemon) {
val context = mutableMapOf(
"trade_participant_1" to (tradeParticipant1.uuid.getPlayer()?.asMoLangValue() ?: DoubleValue.ZERO),
"trade_participant_2" to (tradeParticipant2.uuid.getPlayer()?.asMoLangValue() ?: DoubleValue.ZERO),
"trade_participant_1_pokemon" to tradeParticipant1Pokemon.struct,
"trade_participant_2_pokemon" to tradeParticipant2Pokemon.struct
)
}
@@ -1,91 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.events.pokemon
import com.bedrockk.molang.runtime.value.DoubleValue
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.pokemon.PokemonEntity
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.trade.TradeParticipant
import com.cobblemon.mod.common.util.getPlayer
/**
* Event fired when a trade is initiated or completed.
*
* @author MeAlam
* @since August 23th, 2025
*/
interface TradeEvent {
/**
* The first [TradeParticipant].
*/
val tradeParticipant1: TradeParticipant
/**
* The [Pokemon] being traded by the first participant.
*/
val tradeParticipant1Pokemon: Pokemon
/**
* The second [TradeParticipant].
*/
val tradeParticipant2: TradeParticipant
/**
* The [Pokemon] being traded by the second participant.
*/
val tradeParticipant2Pokemon: Pokemon
/**
* Event fired when a trade is about to happen. Cancelling this event prevents the trade from occurring.
*
* @author MeAlam
* @since August 23th, 2025
*/
data class Pre(
override val tradeParticipant1: TradeParticipant,
override val tradeParticipant1Pokemon: Pokemon,
override val tradeParticipant2: TradeParticipant,
override val tradeParticipant2Pokemon: Pokemon
) : TradeEvent, Cancelable() {
val context = mutableMapOf(
"trade_participant_1" to (tradeParticipant1.uuid.getPlayer()?.asMoLangValue() ?: DoubleValue.ZERO),
"trade_participant_2" to (tradeParticipant2.uuid.getPlayer()?.asMoLangValue() ?: DoubleValue.ZERO),
"trade_participant_1_pokemon" to tradeParticipant1Pokemon.struct,
"trade_participant_2_pokemon" to tradeParticipant2Pokemon.struct
)
val functions = moLangFunctionMap(
cancelFunc
)
}
/**
* Event fired after a trade has occurred.
*
* @author MeAlam
* @since August 23th, 2025
*/
data class Post(
override val tradeParticipant1: TradeParticipant,
override val tradeParticipant1Pokemon: Pokemon,
override val tradeParticipant2: TradeParticipant,
override val tradeParticipant2Pokemon: Pokemon
) : TradeEvent {
val context = mutableMapOf(
"trade_participant_1" to (tradeParticipant1.uuid.getPlayer()?.asMoLangValue() ?: DoubleValue.ZERO),
"trade_participant_2" to (tradeParticipant2.uuid.getPlayer()?.asMoLangValue() ?: DoubleValue.ZERO),
"trade_participant_1_pokemon" to tradeParticipant1Pokemon.struct,
"trade_participant_2_pokemon" to tradeParticipant2Pokemon.struct
)
}
}
@@ -23,22 +23,11 @@ import com.cobblemon.mod.common.pokemon.Pokemon
* @since October 2nd, 2022
*/
data class EvolutionCompleteEvent(
/**
* The [Pokemon] resulting from the evolution.
*/
override val pokemon: Pokemon,
/**
* The [Pokemon] that was the source of the evolution.
*/
val sourcePokemon: Pokemon,
/**
* The [Evolution] that was used.
*/
override val evolution: Evolution
) : EvolutionEvent {
val context = mutableMapOf<String, MoValue>(
"pokemon" to pokemon.struct,
"source_pokemon" to sourcePokemon.struct,
"evolution" to evolution.asMoLangValue()
)
}
@@ -37,28 +37,19 @@ interface ChangePCBoxWallpaperEvent {
/**
* The location of the wallpaper that is being changed to. Can be modified in the [Pre] event.
* NOTE: Changing this to a wallpaper that does not exist on the client, will result in a default fallback texture being displayed.
* NOTE: Changing this to a wallpaper that does not exist on the client, will result in a purple/black fallback texture being displayed.
*/
val wallpaper: ResourceLocation
/**
* The location of the alternative wallpaper that is being changed to if available. Can be modified in the [Pre] event.
* NOTE: Changing this to a wallpaper that does not exist on the client, will result in a default fallback texture being displayed.
*/
val altWallpaper: ResourceLocation?
class Pre(
override val player: ServerPlayer,
override val box: PCBox,
override var wallpaper: ResourceLocation,
override var altWallpaper: ResourceLocation?
override var wallpaper: ResourceLocation
) : ChangePCBoxWallpaperEvent, Cancelable() {
val context = mutableMapOf(
"player" to player.asMoLangValue(),
"box" to StringValue(box.toString()),
"wallpaper" to StringValue(wallpaper.toString()),
"altWallpaper" to StringValue(altWallpaper?.toString())
"wallpaper" to StringValue(wallpaper.toString())
)
val functions = mapOf(
cancelFunc
@@ -68,14 +59,12 @@ interface ChangePCBoxWallpaperEvent {
class Post(
override val player: ServerPlayer,
override val box: PCBox,
override val wallpaper: ResourceLocation,
override val altWallpaper: ResourceLocation?
override val wallpaper: ResourceLocation
) : ChangePCBoxWallpaperEvent {
val context = mutableMapOf(
"player" to player.asMoLangValue(),
"box" to StringValue(box.toString()),
"wallpaper" to StringValue(wallpaper.toString()),
"altWallpaper" to StringValue(altWallpaper?.toString())
"wallpaper" to StringValue(wallpaper.toString())
)
}
}
@@ -227,7 +227,7 @@ fun drawPosablePortrait(
matrixStack.scale(scale, scale, -scale)
matrixStack.translate(0.0, -PORTRAIT_DIAMETER / 18.0, 0.0)
val sprite = VaryingModelRepository.getSprite(identifier, state, SpriteType.PORTRAIT)
val sprite = VaryingModelRepository.getSprite(identifier, state, SpriteType.PORTRAIT);
if (sprite == null) {
val model = VaryingModelRepository.getPoser(identifier, state)
@@ -1,57 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.item
import com.cobblemon.mod.common.api.data.DataRegistry
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.battles.runner.ShowdownService
import com.cobblemon.mod.common.pokemon.helditem.CobblemonHeldItemManager
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
import net.minecraft.server.packs.PackType
import net.minecraft.server.packs.resources.ResourceManager
import java.io.File
object HeldItems : DataRegistry {
override val id = cobblemonResource("held_items")
override val type = PackType.SERVER_DATA
override val observable = SimpleObservable<HeldItems>()
override fun sync(player: ServerPlayer) {}
// val heldItems = PrioritizedList<BagItemLike>()
internal val heldItemsScripts = mutableMapOf<String, String>() // showdown item ID to JavaScript
val showdownItems = hashSetOf<String>()
override fun reload(manager: ResourceManager) {
heldItemsScripts.clear()
ShowdownService.service.resetRegistryData("heldItem")
manager.listResources("held_items") { it.path.endsWith(".js") }.forEach { (identifier, resource) ->
resource.open().use { stream ->
stream.bufferedReader().use { reader ->
val resolvedIdentifier = ResourceLocation.fromNamespaceAndPath(identifier.namespace, File(identifier.path).nameWithoutExtension)
val js = reader.readText()
heldItemsScripts[resolvedIdentifier.path] = js
}
}
}
ShowdownService.service.sendRegistryData(heldItemsScripts, "heldItem")
val itemsJson = ShowdownService.service.getRegistryData("heldItem")
for (i in 0 until itemsJson.size()) {
val jsItem = itemsJson[i].asJsonObject
showdownItems += jsItem.get("id").asString
}
CobblemonHeldItemManager.load()
this.observable.emit(this)
}
}
@@ -40,7 +40,6 @@ import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
import com.cobblemon.mod.common.api.moves.animations.ActionEffects
import com.cobblemon.mod.common.api.moves.animations.NPCProvider
import com.cobblemon.mod.common.api.moves.animations.TargetsProvider
import com.cobblemon.mod.common.api.npc.NPCClass
import com.cobblemon.mod.common.api.npc.NPCClasses
import com.cobblemon.mod.common.api.npc.configuration.interaction.DialogueNPCInteractionConfiguration
import com.cobblemon.mod.common.api.npc.configuration.interaction.ScriptNPCInteractionConfiguration
@@ -57,8 +56,6 @@ import com.cobblemon.mod.common.api.pokemon.evolution.Evolution
import com.cobblemon.mod.common.api.pokemon.experience.SidemodExperienceSource
import com.cobblemon.mod.common.api.pokemon.moves.LearnsetQuery
import com.cobblemon.mod.common.api.pokemon.stats.Stats
import com.cobblemon.mod.common.api.riding.Rideable
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.api.scheduling.ClientTaskTracker
import com.cobblemon.mod.common.api.scheduling.Schedulable
import com.cobblemon.mod.common.api.scheduling.ServerTaskTracker
@@ -87,7 +84,6 @@ import com.cobblemon.mod.common.entity.pokemon.PokemonBehaviourFlag
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.entity.pokemon.ai.PokemonMoveControl
import com.cobblemon.mod.common.net.messages.client.animation.PlayPosableAnimationPacket
import com.cobblemon.mod.common.net.messages.client.battle.BattleMusicPacket
import com.cobblemon.mod.common.net.messages.client.effect.RunPosableMoLangPacket
import com.cobblemon.mod.common.net.messages.client.effect.SpawnSnowstormEntityParticlePacket
import com.cobblemon.mod.common.net.messages.client.effect.SpawnSnowstormParticlePacket
@@ -129,28 +125,21 @@ import net.minecraft.sounds.SoundSource
import net.minecraft.tags.TagKey
import net.minecraft.world.damagesource.DamageSource
import net.minecraft.world.damagesource.DamageTypes
import net.minecraft.world.effect.MobEffectInstance
import net.minecraft.world.entity.Entity
import net.minecraft.world.entity.EntityDimensions
import net.minecraft.world.entity.EntityType
import net.minecraft.world.entity.LightningBolt
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.Mob
import net.minecraft.world.entity.PathfinderMob
import net.minecraft.world.entity.PlayerRideable
import net.minecraft.world.entity.TamableAnimal
import net.minecraft.world.entity.ai.behavior.BlockPosTracker
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.entity.ai.memory.MemoryStatus
import net.minecraft.world.entity.ai.memory.WalkTarget
import net.minecraft.world.entity.animal.Animal
import net.minecraft.world.entity.item.ItemEntity
import net.minecraft.world.entity.monster.Enemy
import net.minecraft.world.entity.monster.Monster
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.ItemStack
import net.minecraft.world.level.ClipContext
import net.minecraft.world.level.GameType
import net.minecraft.world.level.Level
import net.minecraft.world.level.Level.ExplosionInteraction
import net.minecraft.world.level.biome.Biome
@@ -160,9 +149,6 @@ import net.minecraft.world.level.levelgen.Heightmap
import net.minecraft.world.level.pathfinder.PathType
import net.minecraft.world.phys.AABB
import net.minecraft.world.phys.Vec3
import kotlin.text.get
import kotlin.text.toInt
import kotlin.toString
/**
* Holds a bunch of useful MoLang trickery that can be used or extended in API
@@ -213,14 +199,6 @@ object MoLangFunctions {
"string_length" to java.util.function.Function { params ->
return@Function DoubleValue(params.getString(0).length)
},
"split_string" to java.util.function.Function { params ->
val text = params.getString(0)
val delimiter = params.getString(1)
val parts = text.split(delimiter).map { StringValue(it) }
val struct = ArrayStruct(hashMapOf())
parts.forEachIndexed { index, moValue -> struct.setDirectly("$index", moValue) }
return@Function struct
},
"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))
@@ -488,22 +466,6 @@ object MoLangFunctions {
return@put DoubleValue.ZERO
}
}
map.put("spawn_npc") { params ->
val x = params.getDouble(0)
val y = params.getDouble(1)
val z = params.getDouble(2)
val npcClass = params.getStringOrNull(3)?.let { NPCClasses.getByIdentifier(it.asIdentifierDefaultingNamespace()) }
val level = params.getInt(4)
if(npcClass == null) return@put DoubleValue.ZERO
val npc = NPCEntity(world)
npc.moveTo(x, y, z, npc.yRot, npc.xRot)
npc.npc = npcClass
npc.initialize(level)
if (world.addFreshEntity(npc)) {
return@put npc.asMoLangValue()
}
return@put DoubleValue.ZERO
}
map.put("play_sound_on_server") { params ->
val sound = params.getString(0).asResource()
val soundSource = params.getString(1).uppercase()
@@ -588,62 +550,11 @@ object MoLangFunctions {
environment
}
map.put("is_player") { DoubleValue.ONE }
map.put("riding_pokemon") {
val vehicle = player.vehicle
if (vehicle is PokemonEntity) {
return@put vehicle.struct
} else {
return@put DoubleValue.ZERO
}
}
if (player is ServerPlayer) {
map.put("seen_credits") { _ ->
DoubleValue( DoubleValue(player.seenCredits) )
}
map.put("is_in_dialogue") { _ ->
DoubleValue( DoubleValue(player.isInDialogue) )
}
map.put("active_dialogue") { _ ->
if (player.isInDialogue) {
player.activeDialogue?.dialogueId.toString()
return@put DoubleValue.ONE
} else {
DoubleValue.ZERO
}
}
map.put("is_spectator") { DoubleValue(player.isSpectator) }
map.put("is_creative") { DoubleValue(player.isCreative) }
map.put("is_survival") { DoubleValue(player.gameMode.isSurvival) }
map.put("is_adventure") { DoubleValue(player.gameMode.getGameModeForPlayer() == GameType.ADVENTURE) }
map.put("run_command") { params ->
val command = params.getString(0)
player.server.commands.performPrefixedCommand(player.createCommandSourceStack(), command)
}
map.put("set_battle_theme") { params ->
val soundId = params.getString(0).asResource()
Cobblemon.playerDataManager.getGenericData(player).battleTheme = soundId
return@put DoubleValue.ONE
}
map.put("battle_music") { params ->
val soundId = params.getString(0).asResource()
val volume = params.getDoubleOrNull(1)?.toFloat() ?: 1.0f
val pitch = params.getDoubleOrNull(2)?.toFloat() ?: 1.0f
val restart = params.getBooleanOrNull(3) ?: true
val music = soundId
if (music != null) {
val packet = BattleMusicPacket(music, volume, pitch, restart)
packet.sendToPlayer(player)
return@put DoubleValue.ONE
} else {
return@put DoubleValue.ZERO
}
}
map.put("stop_battle_music") { _ ->
val packet = BattleMusicPacket(null)
packet.sendToPlayer(player)
return@put DoubleValue.ONE
}
map.put("play_sound_on_server") { params ->
val sound = params.getString(0).asResource()
val soundSource = SoundSource.valueOf(params.getString(1).uppercase())
@@ -676,13 +587,13 @@ object MoLangFunctions {
map.put("party") { player.party().struct }
map.put("pc") { player.pc().struct }
map.put("has_permission") { params -> DoubleValue(Cobblemon.permissionValidator.hasPermission(player, params.getString(0), params.getIntOrNull(1) ?: 4)) }
map.put("data") { params -> Cobblemon.molangData.load(player.uuid, params.getStringOrNull(0)) }
map.put("save_data") { params -> Cobblemon.molangData.save(player.uuid, params.getStringOrNull(0)) }
map.put("data") { _ -> Cobblemon.molangData.load(player.uuid) }
map.put("save_data") { _ -> Cobblemon.molangData.save(player.uuid) }
map.put("in_battle") { DoubleValue(player.isInBattle()) }
map.put("battle") { player.getBattleState()?.first?.struct ?: DoubleValue.ZERO }
map.put("get_npc_data") { params ->
val npcId = ((params.get<MoValue>(0) as? ObjectValue<*>)?.obj as? NPCEntity)?.stringUUID ?: params.getString(0)
val data = Cobblemon.molangData.load(player.uuid, params.getStringOrNull(1))
val npcId = (params.get<MoValue>(0) as? ObjectValue<NPCEntity>)?.obj?.stringUUID ?: params.getString(0)
val data = Cobblemon.molangData.load(player.uuid)
if (data.map.containsKey(npcId)) {
return@put data.map[npcId]!!
} else {
@@ -692,9 +603,9 @@ object MoLangFunctions {
}
}
map.put("get_npc_variable") { params ->
val npcId = ((params.get<MoValue>(0) as? ObjectValue<*>)?.obj as? NPCEntity)?.stringUUID ?: params.getString(0)
val npcId = (params.get<MoValue>(0) as? ObjectValue<NPCEntity>)?.obj?.stringUUID ?: params.getString(0)
val variable = params.getString(1)
val data = Cobblemon.molangData.load(player.uuid, params.getStringOrNull(2))
val data = Cobblemon.molangData.load(player.uuid)
if (data.map.containsKey(npcId)) {
return@put (data.map[npcId] as VariableStruct).map[variable] ?: DoubleValue.ZERO
} else {
@@ -702,16 +613,15 @@ object MoLangFunctions {
}
}
map.put("set_npc_variable") { params ->
val npcId = ((params.get<MoValue>(0) as? ObjectValue<*>)?.obj as? NPCEntity)?.stringUUID ?: params.getString(0)
val npcId = (params.get<MoValue>(0) as? ObjectValue<NPCEntity>)?.obj?.stringUUID ?: params.getString(0)
val variable = params.getString(1)
val value = params.get<MoValue>(2)
val saveAfterwards = params.getBooleanOrNull(3) != false
val path = params.getStringOrNull(4)
val data = Cobblemon.molangData.load(player.uuid, path)
val data = Cobblemon.molangData.load(player.uuid)
val npcData = data.map.getOrPut(npcId) { VariableStruct() } as VariableStruct
npcData.map[variable] = value
if (saveAfterwards) {
Cobblemon.molangData.save(player.uuid, path)
Cobblemon.molangData.save(player.uuid)
}
return@put DoubleValue.ONE
}
@@ -824,26 +734,7 @@ object MoLangFunctions {
map.put("delta_movement") {
return@put listOf(entity.deltaMovement.x, entity.deltaMovement.y, entity.deltaMovement.z).asArrayValue(::DoubleValue)
}
map.put("tags") {
val tags = entity.tags
val array = ArrayStruct(hashMapOf())
tags.forEachIndexed { index, tag -> array.setDirectly("$index", StringValue(tag)) }
return@put array
}
map.put("add_tag") { params ->
val tag = params.getString(0)
entity.addTag(tag)
return@put DoubleValue.ONE
}
map.put("remove_tag") { params ->
val tag = params.getString(0)
entity.removeTag(tag)
return@put DoubleValue.ONE
}
map.put("has_tag") { params ->
val tag = params.getString(0)
return@put DoubleValue(entity.tags.contains(tag))
}
if (entity is MoLangScriptingEntity) {
map.put("add_callback") { params ->
val type = params.getString(0).asIdentifierDefaultingNamespace()
@@ -994,9 +885,6 @@ object MoLangFunctions {
return@put if (entity.isStandingOn(blockStrings, depth)) DoubleValue.ONE else DoubleValue.ZERO
}
map.put("discard") {
entity.discard()
}
if (entity is PosableEntity) {
map.put("play_animation") { params ->
val animation = params.getString(0)
@@ -1054,11 +942,6 @@ object MoLangFunctions {
packet.sendToPlayer(player)
}
}
map.put("make_intangible") { params ->
val intangible = params.getBooleanOrNull(0) ?: true
entity.noPhysics = intangible
return@put DoubleValue.ONE
}
return@mutableListOf map
}
)
@@ -1066,57 +949,10 @@ object MoLangFunctions {
val livingEntityFunctions: MutableList<(LivingEntity) -> HashMap<String, java.util.function.Function<MoParams, Any>>> = mutableListOf<(LivingEntity) -> HashMap<String, java.util.function.Function<MoParams, Any>>>(
{ entity ->
val map = hashMapOf<String, java.util.function.Function<MoParams, Any>>()
map.put("is_player") { _ -> DoubleValue(entity is Player) }
map.put("is_npc") { _ -> DoubleValue(entity is NPCEntity) }
map.put("is_mob") { _ -> DoubleValue(entity is Mob) }
map.put("is_pokemon") { _ -> DoubleValue(entity is PokemonEntity) }
map.put("is_animal") { _ -> DoubleValue(entity is Animal) }
map.put("is_tamable") { _ -> DoubleValue(entity is TamableAnimal) }
map.put("is_tamed") { _ -> DoubleValue(entity is TamableAnimal && entity.isTame) }
map.put("is_hostile") { _ -> DoubleValue(entity is Monster) }
map.put("is_baby") { _ -> DoubleValue(entity.isBaby) }
map.put("is_adult") { _ -> DoubleValue(!entity.isBaby) }
map.put("remove_effect") { params ->
val effectId = params.getString(0).asIdentifierDefaultingNamespace()
val effectHolder = BuiltInRegistries.MOB_EFFECT.getHolder(effectId).orElse(null)
if (effectHolder != null) {
entity.removeEffect(effectHolder)
return@put DoubleValue.ONE
}
}
map.put("add_effect") { params ->
val effectId = params.getString(0).asIdentifierDefaultingNamespace()
val duration = params.getInt(1)
val amplifier = params.getIntOrNull(2) ?: 0
val ambient = params.getBooleanOrNull(3) ?: false
val visible = params.getBooleanOrNull(4) ?: true
val effectHolder = BuiltInRegistries.MOB_EFFECT.getHolder(effectId).orElse(null)
if (effectHolder != null) {
entity.addEffect(MobEffectInstance(effectHolder, duration, amplifier, ambient, visible))
return@put DoubleValue.ONE
}
}
map.put("has_effect") { params ->
val effectId = params.getString(0).asIdentifierDefaultingNamespace()
val effectHolder = BuiltInRegistries.MOB_EFFECT.getHolder(effectId).orElse(null)
return@put DoubleValue(if (effectHolder != null) entity.hasEffect(effectHolder) else false)
}
map.put("heal") { params ->
val amount = params.getDouble(0)
entity.heal(amount.toFloat())
}
map.put("is_looking_at") { params ->
val targetEntity = params.get<MoValue>(0)
val maxDistance = params.getDoubleOrNull(1)?.toFloat() ?: 2.0F
val entity = if (targetEntity is ObjectValue<*> && targetEntity.obj is Entity) {
targetEntity.obj as Entity
} else {
return@put DoubleValue.ZERO
}
return@put DoubleValue(entity.isLookingAt(entity, maxDistance))
}
map.put("is_living_entity") { DoubleValue.ONE }
map.put("is_flying") { _ -> DoubleValue(entity.isFallFlying) }
map.put("is_sleeping") { _ -> DoubleValue(entity.isSleeping) }
@@ -1236,9 +1072,10 @@ object MoLangFunctions {
}
}
map.put("has_walk_target") { _ ->
DoubleValue(entity.brain.getMemorySafely(MemoryModuleType.WALK_TARGET).isPresent || entity.isPathFinding)
DoubleValue(entity.brain.getMemory(MemoryModuleType.WALK_TARGET).isPresent || entity.isPathFinding)
}
}
map
}
)
@@ -1459,11 +1296,6 @@ object MoLangFunctions {
map.put("has_party") { DoubleValue(npc.party != null) }
map.put("is_npc") { DoubleValue.ONE }
map.put("can_battle") { DoubleValue(npc.party?.any { it.currentHealth > 0 } == true || npc.npc.party?.isStatic == false) }
map.put("set_battle_theme") { params ->
val soundId = params.getString(0).asResource()
npc.npc.battleTheme = soundId
return@put DoubleValue.ONE
}
map
}
)
@@ -1583,18 +1415,11 @@ object MoLangFunctions {
}
struct
}
map.put("ride_boosts") {
val struct = QueryStruct(hashMapOf())
for (stat in RidingStat.entries) {
struct.addFunction(stat.name.lowercase()) { DoubleValue(pokemon.getRideBoost(stat) ?: 0.0) }
}
struct
}
map.put("natdex_number") {
DoubleValue(pokemon.species.nationalPokedexNumber.toDouble())
}
map.put("types") {
pokemon.form.types.map { it.toString() }.asArrayValue(::StringValue)
pokemon.form.types.map { it.toString() }.asArrayValue(::StringValue)
}
map.put("gender_ratio") {
DoubleValue(pokemon.form.maleRatio.toDouble())
@@ -1795,21 +1620,6 @@ object MoLangFunctions {
return@put DoubleValue.ZERO
}
}
map.put("set_ride_boost") { params ->
val statName = params.getString(0).uppercase()
val stat = RidingStat.entries.find { it.name.equals(statName, ignoreCase = true) } ?: return@put DoubleValue.ZERO
val value = params.getDoubleOrNull(1)?.toFloat() ?: return@put DoubleValue.ZERO
pokemon.setRideBoost(stat, value)
return@put DoubleValue.ONE
}
map.put("add_ride_boost") { params ->
val statName = params.getString(0).uppercase()
val stat = RidingStat.entries.find { it.name.equals(statName, ignoreCase = true) } ?: return@put DoubleValue.ZERO
val value = params.getDoubleOrNull(1)?.toFloat() ?: return@put DoubleValue.ZERO
return@put DoubleValue(if (pokemon.addRideBoost(stat, value)) 1.0 else 0.0)
}
map.put("initialize_moveset") { params ->
val preferLatest = params.getBooleanOrNull(0) ?: true
pokemon.initializeMoveset(preferLatest)
@@ -1854,11 +1664,7 @@ object MoLangFunctions {
val moveTemplate = Moves.getByName(moveName) ?: return@put DoubleValue.ZERO
val includeLegacy = params.getBooleanOrNull(1) ?: true
val canLearn = if (includeLegacy) {
LearnsetQuery.ANY.canLearn(moveTemplate, pokemon.form.moves)
} else {
LearnsetQuery.LEGAL.canLearn(moveTemplate, pokemon.form.moves)
}
val canLearn = LearnsetQuery.ANY.canLearn(moveTemplate, pokemon.form.moves)
return@put DoubleValue(if (canLearn) 1.0 else 0.0)
}
map.put("unlearn_move") { params ->
@@ -1871,15 +1677,6 @@ object MoLangFunctions {
return@put DoubleValue.ZERO
}
}
map.put("can_evolve") { _ ->
DoubleValue(pokemon.evolutions.any())
}
map.put("force_evolve") { params ->
val idx = params.getInt(0)
val evolution = if (idx >= 0) pokemon.evolutions.elementAtOrNull(idx) else return@put DoubleValue.ZERO
evolution?.forceEvolve(pokemon)
StringValue(evolution.toString())
}
map
}
)
@@ -1887,10 +1684,10 @@ object MoLangFunctions {
val pokemonEntityFunctions = mutableListOf<(PokemonEntity) -> HashMap<String, java.util.function.Function<MoParams, Any>>>(
{ pokemonEntity ->
val map = hashMapOf<String, java.util.function.Function<MoParams, Any>>()
map.put("is_busy") { DoubleValue(pokemonEntity.isBusy) }
map.put("in_battle") { DoubleValue(pokemonEntity.isBattling) }
map.put("is_moving") { DoubleValue((pokemonEntity.moveControl as? PokemonMoveControl)?.hasWanted() == true) }
map.put("is_flying") { DoubleValue(pokemonEntity.getBehaviourFlag(PokemonBehaviourFlag.FLYING)) }
map.put("is_flying") { DoubleValue(pokemonEntity.getBehaviourFlag(PokemonBehaviourFlag.FLYING)) }
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"))) }
@@ -2144,17 +1941,12 @@ object MoLangFunctions {
}
map.put("data") { params ->
Cobblemon.molangData.load(
UUID(0L, 0L),
params.getStringOrNull(0)
)
val data = Cobblemon.molangData.load(UUID(0L, 0L))
return@put data
}
map.put("save_data") { params ->
Cobblemon.molangData.save(
UUID(0L, 0L),
params.getStringOrNull(index = 0)
)
map.put("save_data") {
Cobblemon.molangData.save(UUID(0L, 0L))
return@put DoubleValue.ONE
}
@@ -2658,4 +2450,3 @@ object MoLangFunctions {
}
fun Either<ResourceLocation, ExpressionLike>.runScript(runtime: MoLangRuntime) = map({ CobblemonScripts.run(it, runtime) }, { it.resolve(runtime) })
@@ -117,7 +117,7 @@ class BenchedMoves : Iterable<BenchedMove> {
.xmap(
{ moveList ->
val benchedMoves = BenchedMoves()
benchedMoves.addAll(moveList.filter { it.moveTemplate !is MoveTemplate.Dummy })
benchedMoves.addAll(moveList)
return@xmap benchedMoves
},
BenchedMoves::toList
@@ -213,10 +213,7 @@ class MoveSet : Iterable<Move> {
.xmap(
{ moveList ->
val moveSet = MoveSet()
moveList.filter { it.template !is MoveTemplate.Dummy }.forEach(moveSet::add)
if (moveSet.moves.all { it == null }) {
moveSet.add(Moves.getExceptional().create())
}
moveList.forEach(moveSet::add)
return@xmap moveSet
},
{ moveSet -> moveSet.toList() }
@@ -96,7 +96,7 @@ open class MoveTemplate(
@JvmStatic
val BY_STRING_CODEC: Codec<MoveTemplate> = CodecUtils.createByStringCodec(
Moves::getByNameOrDummy,
Moves::getByName,
MoveTemplate::name
) { id -> "No MoveTemplate for ID $id" }
}
@@ -15,9 +15,7 @@ import net.minecraft.world.entity.LivingEntity
import org.joml.Matrix3f
import org.joml.Quaternionf
import org.joml.Vector3f
import kotlin.math.abs
import kotlin.math.asin
import kotlin.math.sign
open class OrientationController(val entity: LivingEntity) {
@@ -114,11 +112,8 @@ open class OrientationController(val entity: LivingEntity) {
fun applyGlobalPitch(deltaPitchDegrees: Float) = updateOrientation { original ->
val currQuat = Quaternionf().setFromUnnormalized(original)
val horzLeftVector = Vector3f(0.0f, -abs(this.upVector.y.sign), 0.0f).cross(this.forwardVector)
// Avoid NaN issue when normalizing and the forwardVector and upVector are equal
val pitchAxis = if (horzLeftVector.lengthSquared() < 0.01) this.leftVector else horzLeftVector
val globalPitch = Quaternionf().fromAxisAngleRad(pitchAxis.normalize(), -deltaPitchDegrees.toRadians())
val horzLeftVector = Vector3f(this.leftVector.x, 0.0f, this.leftVector.z)
val globalPitch = Quaternionf().fromAxisAngleRad(horzLeftVector.normalize(), -deltaPitchDegrees.toRadians())
val resultQuat = globalPitch.mul(currQuat)
return@updateOrientation Matrix3f().set(resultQuat)
}
@@ -131,7 +131,6 @@ open class PokemonProperties {
props.species = parseSpeciesIdentifier(keyPairs)
props.form = parseForm(keyPairs)
props.friendship = parseIntProperty(keyPairs, listOf("friendship"))?.coerceIn(0, Cobblemon.config.maxPokemonFriendship)
props.fullness = parseIntProperty(keyPairs, listOf("fullness"))
props.pokeball = parseIdentifierOfRegistry(keyPairs, listOf("pokeball")) { identifier -> PokeBalls.getPokeBall(identifier)?.name?.toString() }
props.nature = parseIdentifierOfRegistry(keyPairs, listOf("nature")) { identifier -> Natures.getNature(identifier)?.name?.toString() }
props.ability = parseStringOfRegistry(keyPairs, listOf("ability")) { Abilities.get(it)?.name }
@@ -327,7 +326,6 @@ open class PokemonProperties {
var gender: Gender? = null
var level: Int? = null
var friendship: Int? = null
var fullness: Int? = null
var pokeball: String? = null
var nature: String? = null
var ability: String? = null
@@ -399,12 +397,11 @@ open class PokemonProperties {
pokemon.setFriendship(pokemon.form.baseFriendship)
}
}
fullness?.let { pokemon.currentFullness = it }
pokeball?.let { PokeBalls.getPokeBall(it.asIdentifierDefaultingNamespace())?.let { pokeball -> pokemon.caughtBall = pokeball } }
nature?.let { Natures.getNature(it.asIdentifierDefaultingNamespace())?.let { nature -> pokemon.nature = nature } }
customProperties.forEach { it.apply(pokemon) }
ability?.let { this.createAbility(it, pokemon.form)?.let(pokemon::updateAbility) }
status?.let { Statuses.getStatus(it)?.let { status -> if (status is PersistentStatus) pokemon.applyStatus(status) } }
customProperties.forEach { it.apply(pokemon) }
ivs?.let { ivs ->
ivs.forEach { stat ->
pokemon.setIV(stat.key, stat.value)
@@ -467,7 +464,7 @@ open class PokemonProperties {
val stack = ItemStack(result.item)
stack.applyComponents(result.components)
if (stack.isEmpty) return@let
pokemon.swapHeldItem(stack, decrement = true, aiCanDrop = false)
pokemon.swapHeldItem(stack)
}
pokemon.updateAspects()
}
@@ -507,7 +504,6 @@ open class PokemonProperties {
nickname?.takeIf { it.string != pokemon.nickname?.string }?.let { return false }
form?.takeIf { !it.equals(pokemon.form.name, true) }?.let { return false }
friendship?.takeIf { it != pokemon.friendship }?.let { return false }
fullness?.takeIf { it != pokemon.currentFullness }?.let { return false }
pokeball?.takeIf { it != pokemon.caughtBall.name.toString() }?.let { return false }
nature?.takeIf { it != pokemon.nature.name.toString() }?.let { return false }
ability?.takeIf { it != pokemon.ability.name }?.let { return false }
@@ -592,7 +588,6 @@ open class PokemonProperties {
originalTrainer?.takeIf { it != properties.originalTrainer }?.let{ return false }
originalTrainerType?.takeIf { it != properties.originalTrainerType }?.let{ return false }
moves?.takeIf { it.any { move -> properties.moves?.none { it == move } == true } }?.let { return false }
fullness?.takeIf { it != properties.fullness }?.let { return false }
return true
}
@@ -641,7 +636,6 @@ open class PokemonProperties {
nickname?.let { nbt.putString(DataKeys.POKEMON_NICKNAME, Component.Serializer.toJson(it, registryLookup)) }
form?.let { nbt.putString(DataKeys.POKEMON_FORM_ID, it) }
friendship?.let { nbt.putInt(DataKeys.POKEMON_FRIENDSHIP, it) }
fullness?.let { nbt.putInt(DataKeys.POKEMON_FULLNESS, it) }
pokeball?.let { nbt.putString(DataKeys.POKEMON_CAUGHT_BALL, it) }
nature?.let { nbt.putString(DataKeys.POKEMON_NATURE, it) }
ability?.let { nbt.putString(DataKeys.POKEMON_ABILITY, it) }
@@ -673,7 +667,6 @@ open class PokemonProperties {
nickname = if (tag.contains(DataKeys.POKEMON_NICKNAME)) Component.Serializer.fromJson(tag.getString(DataKeys.POKEMON_NICKNAME), registryLookup) else null
form = if (tag.contains(DataKeys.POKEMON_FORM_ID)) tag.getString(DataKeys.POKEMON_FORM_ID) else null
friendship = if (tag.contains(DataKeys.POKEMON_FRIENDSHIP)) tag.getInt(DataKeys.POKEMON_FRIENDSHIP) else null
fullness = if (tag.contains(DataKeys.POKEMON_FULLNESS)) tag.getInt(DataKeys.POKEMON_FULLNESS) else null
pokeball = if (tag.contains(DataKeys.POKEMON_CAUGHT_BALL)) tag.getString(DataKeys.POKEMON_CAUGHT_BALL) else null
nature = if (tag.contains(DataKeys.POKEMON_NATURE)) tag.getString(DataKeys.POKEMON_NATURE) else null
ability = if (tag.contains(DataKeys.POKEMON_ABILITY)) tag.getString(DataKeys.POKEMON_ABILITY) else null
@@ -707,7 +700,6 @@ open class PokemonProperties {
//nickname?.let { json.addProperty(DataKeys.POKEMON_NICKNAME, Text.Serialization.toJsonString(it)) }
form?.let { json.addProperty(DataKeys.POKEMON_FORM_ID, it) }
friendship?.let { json.addProperty(DataKeys.POKEMON_FRIENDSHIP, it) }
fullness?.let {json.addProperty(DataKeys.POKEMON_FULLNESS, it)}
pokeball?.let { json.addProperty(DataKeys.POKEMON_CAUGHT_BALL, it) }
nature?.let { json.addProperty(DataKeys.POKEMON_NATURE, it) }
ability?.let { json.addProperty(DataKeys.POKEMON_ABILITY, it) }
@@ -740,7 +732,6 @@ open class PokemonProperties {
//nickname = json.get(DataKeys.POKEMON_NICKNAME)?.asString?.let { Text.Serialization.fromJson(it) }
form = json.get(DataKeys.POKEMON_FORM_ID)?.asString
friendship = json.get(DataKeys.POKEMON_FRIENDSHIP)?.asInt
fullness = json.get(DataKeys.POKEMON_FULLNESS)?.asInt
pokeball = json.get(DataKeys.POKEMON_CAUGHT_BALL)?.asString
nature = json.get(DataKeys.POKEMON_NATURE)?.asString
ability = json.get(DataKeys.POKEMON_ABILITY)?.asString
@@ -772,7 +763,6 @@ open class PokemonProperties {
shiny?.let { pieces.add("shiny=$it") }
gender?.let { pieces.add("gender=$it")}
friendship?.let { pieces.add("friendship=$it") }
fullness?.let {pieces.add("fullness=$it")}
pokeball?.let { pieces.add("pokeball=$it") }
nature?.let { pieces.add("nature=$it") }
ability?.let { pieces.add("ability=$it") }
@@ -43,6 +43,7 @@ import com.cobblemon.mod.common.api.riding.stats.RidingStatDefinition
import com.cobblemon.mod.common.api.spawning.TimeRange
import com.cobblemon.mod.common.api.types.ElementalType
import com.cobblemon.mod.common.api.types.adapters.ElementalTypeAdapter
import com.cobblemon.mod.common.battles.runner.ShowdownService
import com.cobblemon.mod.common.net.messages.client.data.SpeciesRegistrySyncPacket
import com.cobblemon.mod.common.pokemon.FormData
import com.cobblemon.mod.common.pokemon.Species
@@ -53,8 +54,10 @@ import com.cobblemon.mod.common.pokemon.ai.ObtainableItemConditionAdapter
import com.cobblemon.mod.common.pokemon.evolution.adapters.CobblemonEvolutionAdapter
import com.cobblemon.mod.common.pokemon.evolution.adapters.CobblemonPreEvolutionAdapter
import com.cobblemon.mod.common.pokemon.evolution.adapters.LegacyItemConditionWrapperAdapter
import com.cobblemon.mod.common.pokemon.helditem.CobblemonHeldItemManager
import com.cobblemon.mod.common.util.adapters.*
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.ifClient
import com.google.common.collect.HashBasedTable
import com.google.gson.Gson
import com.google.gson.GsonBuilder
@@ -62,11 +65,13 @@ import com.google.gson.reflect.TypeToken
import com.mojang.datafixers.util.Either
import net.minecraft.advancements.critereon.ItemPredicate
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.core.registries.Registries
import net.minecraft.nbt.CompoundTag
import net.minecraft.network.chat.Component
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
import net.minecraft.server.packs.PackType
import net.minecraft.tags.TagKey
import net.minecraft.world.effect.MobEffect
import net.minecraft.world.entity.EntityDimensions
import net.minecraft.world.entity.ai.memory.MemoryModuleType
@@ -146,17 +151,24 @@ object PokemonSpecies : JsonDataRegistry<Species> {
val species: Collection<Species>
get() = this.speciesByIdentifier.values
val implemented: List<Species>
get() = this.species.filter { it.implemented }
val implemented = mutableListOf<Species>()
init {
SpeciesAdditions.observable.subscribe {
implemented.clear()
this.species.forEach(Species::initialize)
this.species.forEach {
if (it.implemented) {
this.implemented.add(it)
}
}
this.species.forEach(Species::resolveEvolutionMoves)
Cobblemon.showdownThread.queue {
it.resetRegistryData("species")
it.sendRegistryData(allShowdownSpecies(), "species")
it.indicateSpeciesInitialized()
// Reload this with the mod
CobblemonHeldItemManager.load()
Cobblemon.LOGGER.info("Loaded {} Pokémon species", this.speciesByIdentifier.size)
this.observable.emit(this)
}
@@ -221,6 +233,7 @@ object PokemonSpecies : JsonDataRegistry<Species> {
override fun reload(data: Map<ResourceLocation, Species>) {
this.speciesByIdentifier.clear()
this.implemented.clear()
this.speciesByDex.clear()
data.forEach { (identifier, species) ->
species.resourceIdentifier = identifier
@@ -228,6 +241,11 @@ object PokemonSpecies : JsonDataRegistry<Species> {
this.speciesByDex.remove(old.resourceIdentifier.namespace, old.nationalPokedexNumber)
}
this.speciesByDex.put(species.resourceIdentifier.namespace, species.nationalPokedexNumber, species)
ifClient {
if (species.implemented) {
this.implemented.add(species)
}
}
}
}
@@ -199,8 +199,6 @@ interface Evolution : EvolutionLike {
// (e.g., Oshawott evolving at level 17 knowing Razor Shell, while Dewott only learns it at level 18).
val previousSpeciesLearnableMoves = pokemon.relearnableMoves
val sourcePokemon = pokemon.clone()
this.result.apply(pokemon)
val movesToLearn = previousSpeciesLearnableMoves + this.learnableMoves
@@ -240,7 +238,7 @@ interface Evolution : EvolutionLike {
)
}
CobblemonEvents.EVOLUTION_COMPLETE.post(EvolutionCompleteEvent(pokemon, sourcePokemon, this))
CobblemonEvents.EVOLUTION_COMPLETE.post(EvolutionCompleteEvent(pokemon, this))
CobblemonEvents.POKEMON_GAINED.post(PokemonGainedEvent(pokemon.getOwnerUUID()!!, pokemon))
}
@@ -70,7 +70,7 @@ object SpeciesFeatures : JsonDataRegistry<SpeciesFeatureProvider<*>> {
fun getCodeFeature(name: String) = codeFeatures[name]
fun getResourceFeature(name: String) = resourceFeatures[name]
fun getFeature(name: String) = getCodeFeature(name) ?: getResourceFeature(name) ?: GlobalSpeciesFeatures.getFeature(name)
fun getFeature(name: String) = getCodeFeature(name) ?: getResourceFeature(name)
fun loadOnClient(entries: Collection<Map.Entry<String, SpeciesFeatureProvider<*>>>) {
codeFeatures.putAll(entries.map { it.toPair() })
}
@@ -109,7 +109,17 @@ open class Learnset : ClientDataSynchronizer<Learnset> {
}
// We only sync level up moves atm
override fun shouldSynchronize(other: Learnset) = other.levelUpMoves != this.levelUpMoves
override fun shouldSynchronize(other: Learnset): Boolean {
return when {
other.levelUpMoves.size != this.levelUpMoves.size -> true
other.eggMoves.size != this.eggMoves.size -> true
other.tutorMoves.size != this.tutorMoves.size -> true
other.tmMoves.size != this.tmMoves.size -> true
other.evolutionMoves.size != this.evolutionMoves.size -> true
other.formChangeMoves.size != this.formChangeMoves.size -> true
else -> false
}
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
this.levelUpMoves.clear()
@@ -121,6 +131,16 @@ open class Learnset : ClientDataSynchronizer<Learnset> {
}
levelUpMoves[level] = moves
}
this.eggMoves.clear()
this.eggMoves.addAll(readMoves(buffer))
this.tutorMoves.clear()
this.tutorMoves.addAll(readMoves(buffer))
this.tmMoves.clear()
this.tmMoves.addAll(readMoves(buffer))
this.evolutionMoves.clear()
this.evolutionMoves.addAll(readMoves(buffer))
this.formChangeMoves.clear()
this.formChangeMoves.addAll(readMoves(buffer))
}
override fun encode(buffer: RegistryFriendlyByteBuf) {
@@ -132,5 +152,25 @@ open class Learnset : ClientDataSynchronizer<Learnset> {
buffer.writeInt(move.num)
}
}
writeMoves(buffer, this.eggMoves)
writeMoves(buffer, this.tutorMoves)
writeMoves(buffer, this.tmMoves)
writeMoves(buffer, this.evolutionMoves)
writeMoves(buffer, this.formChangeMoves)
}
private fun writeMoves(buffer: RegistryFriendlyByteBuf, moves: Collection<MoveTemplate>) {
buffer.writeSizedInt(IntSize.U_SHORT, moves.size)
for (move in moves) {
buffer.writeInt(move.num)
}
}
private fun readMoves(buffer: RegistryFriendlyByteBuf): MutableList<MoveTemplate> {
val moves = mutableListOf<MoveTemplate>()
repeat(times = buffer.readSizedInt(IntSize.U_SHORT)) {
Moves.getByNumericalId(buffer.readInt())?.let(moves::add)
}
return moves
}
}
@@ -8,6 +8,9 @@
package com.cobblemon.mod.common.api.riding
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourSettings
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourState
import net.minecraft.world.entity.PlayerRideableJumping
/**
@@ -9,7 +9,10 @@
package com.cobblemon.mod.common.api.riding
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourSettings
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.api.riding.stats.RidingStatDefinition
import com.cobblemon.mod.common.util.*
import com.cobblemon.mod.common.util.adapters.RidingBehaviourSettingsAdapter
import net.minecraft.network.RegistryFriendlyByteBuf
@@ -29,10 +32,10 @@ class RidingProperties(
{ buffer.readEnumConstant(RidingStyle::class.java) },
{
val key = buffer.readResourceLocation()
val behaviour = RidingBehaviourSettingsAdapter.types[key]?.getConstructor()?.newInstance()
val settings = RidingBehaviourSettingsAdapter.types[key]?.getConstructor()?.newInstance()
?: error("Unknown controller key: $key")
behaviour.decode(buffer)
behaviour
settings.decode(buffer)
settings
}
)
}
@@ -48,10 +51,7 @@ class RidingProperties(
buffer.writeMap(
v,
{ _, style -> buffer.writeEnumConstant(style) },
{ _, behaviour ->
buffer.writeResourceLocation(behaviour.key)
behaviour.encode(buffer)
}
{ _, behaviour -> behaviour.encode(buffer) }
)
}
}
@@ -25,8 +25,7 @@ data class Seat(
val locator: String?,
val offset: Vec3?,
val poseOffsets: MutableList<SeatPoseOffset>?,
val poseAnimations: MutableList<SeatPoseAnimations>?,
val handsBusy: Boolean = false,
val poseAnimations: MutableList<SeatPoseAnimations>?
) : Encodable {
fun getOffset(poseType: PoseType) : Vec3 {
return poseOffsets?.firstOrNull { poseType in it.poseTypes }?.offset ?: Vec3.ZERO
@@ -41,7 +40,6 @@ data class Seat(
buffer.writeNullable(poseAnimations) { _, v ->
buffer.writeCollection(v) { _, animations -> animations.encode(buffer) }
}
buffer.writeBoolean(handsBusy)
}
companion object {
@@ -50,8 +48,7 @@ data class Seat(
buffer.readNullable { buffer.readString() },
buffer.readNullable { buffer.readVec3() },
buffer.readNullable { buffer.readList { SeatPoseOffset.decode(buffer) } },
buffer.readNullable { buffer.readList { SeatPoseAnimations.decode(buffer) } },
buffer.readBoolean()
buffer.readNullable { buffer.readList { SeatPoseAnimations.decode(buffer) } }
)
}
}
@@ -18,7 +18,6 @@ import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
import kotlin.math.abs
/**
* Represents the behaviour of a Pokemon when being ridden.
@@ -114,7 +113,7 @@ interface RidingBehaviour<Settings : RidingBehaviourSettings, State : RidingBeha
* and 1.0 represents x is at or above it.
*/
internal fun scaleToRange(x: Double, min: Double, max: Double): Double {
return if ((max - min) < 0.01) 0.0 else ((x - min) / (max - min)).coerceIn(0.0, 1.0)
return ((x - min) / (max - min)).coerceIn(0.0, 1.0)
}
}
}
@@ -10,8 +10,10 @@ package com.cobblemon.mod.common.api.riding.behaviour
import com.cobblemon.mod.common.api.net.Decodable
import com.cobblemon.mod.common.api.net.Encodable
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.phys.Vec3
/**
* Represents static settings of a riding behaviour.
@@ -19,17 +21,17 @@ import net.minecraft.resources.ResourceLocation
* Typically this will be initialized for each pokemon form during deserialization
* to determine how they should ride.
*
* These also exist in a datapacked folder, ride_settings, which are used for the fallback
* values if non-stat settings are omitted in the pokemon form JSON.
*
* @author landonjw
*/
interface RidingBehaviourSettings: Encodable, Decodable {
val key: ResourceLocation
val stats: MutableMap<RidingStat, IntRange>
fun calculate(stat: RidingStat, boostAmount: Float): Float {
fun calculate(stat: RidingStat, boosts: Int): Float {
val range = stats[stat] ?: return 0F
return range.first + boostAmount
val boostQuotient = boosts / 255F
return range.first + (range.last - range.first) * boostQuotient
}
fun hasStat(stat: RidingStat) = stats.containsKey(stat)
}
@@ -18,7 +18,7 @@ import net.minecraft.world.phys.Vec3
* This is intended to contain mutable state that is passed between the client and server
* or temporary state that is used during the riding process.
*
* This will be destroyed when the main rider dismounts the Pokemon.
* This will be destroyed when the a user dismounts the Pokemon.
*
* @author landonjw
*/
@@ -8,17 +8,13 @@
package com.cobblemon.mod.common.api.riding.behaviour
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.DoubleJump
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.client.MountedCameraTypeHandler
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import net.minecraft.resources.ResourceLocation
import net.minecraft.tags.FluidTags
import net.minecraft.world.entity.LivingEntity
import kotlin.collections.get
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.entity.player.Player
/**
* Small wrapper around a RidingBehaviour to provide sane defaults in the event that the behaviour is not active.
@@ -53,15 +49,6 @@ class RidingController(
if (newTransition != null && behaviourSettings != null) {
val newState = RidingBehaviours.get(behaviourSettings.key).createDefaultState(behaviourSettings)
context?.let { newState.stamina.set(it.state.stamina.get(), forced = true) }
context?.let { newState.rideVelocity.set(it.state.rideVelocity.get(), forced = true) }
val previousKey = context?.behaviour
val newKey = behaviourSettings.key
for (passenger in entity.passengers) {
if (passenger is Player && passenger !is ServerPlayer) {
MountedCameraTypeHandler.handleTransition(previousKey ?: continue, newKey)
}
}
context = ActiveRidingContext(behaviourSettings.key, behaviourSettings, newState, newTransition)
lastTransitionAge = entity.ticksLived
} else {
@@ -120,7 +107,6 @@ class RidingController(
private fun canTransitionToAir(driver: LivingEntity?): Boolean {
if (driver != null) {
if (driver !is DoubleJump) return false
if (behaviours[RidingStyle.AIR] == null) return false
// check if style is water and player is submerged
val isEyeInLiquid = entity.isEyeInFluid(FluidTags.WATER) || entity.isEyeInFluid(FluidTags.LAVA)
if (context?.style == RidingStyle.LIQUID && isEyeInLiquid) return false
@@ -11,9 +11,7 @@ package com.cobblemon.mod.common.api.riding.behaviour.types.air
import com.bedrockk.molang.Expression
import com.bedrockk.molang.runtime.MoLangMath.lerp
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonRideSettings
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.api.orientation.OrientationController
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.*
import com.cobblemon.mod.common.api.riding.posing.PoseOption
@@ -27,13 +25,12 @@ 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.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.Vector3f
import kotlin.math.*
class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
@@ -50,22 +47,8 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
val poseProvider = PoseProvider<BirdSettings, BirdState>(PoseType.HOVER)
.with(PoseOption(PoseType.FLY) { _, state, _ -> state.rideVelocity.get().z > 0.2 })
val globalBird: BirdSettings
get() = CobblemonRideSettings.bird
override fun isActive(settings: BirdSettings, state: BirdState, vehicle: PokemonEntity): Boolean {
return Shapes.create(vehicle.boundingBox).blockPositionsAsListRounded().any {
//Need to check other fluids
if (vehicle.isInWater || vehicle.isUnderWater) {
return@any false
}
//This might not actually work, depending on what the yPos actually is. yPos of the middle of the entity? the feet?
if (it.y.toDouble() == (vehicle.position().y)) {
val blockState = vehicle.level().getBlockState(it.below())
return@any !(!blockState.isAir && blockState.fluidState.isEmpty)
}
true
}
return true
}
override fun pose(settings: BirdSettings, state: BirdState, vehicle: PokemonEntity): PoseType {
@@ -81,57 +64,6 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
return state.rideVelocity.get().length().toFloat()
}
override fun tick(
settings: BirdSettings,
state: BirdState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
) {
if(vehicle.level().isClientSide) {
tickStamina(settings, state, vehicle, driver)
checkUpsideDown(state, vehicle, driver)
}
}
private fun checkUpsideDown(
state: BirdState,
vehicle: PokemonEntity,
driver: Player,
) {
val controller = (driver as OrientationControllable).orientationController
if (cos(controller.roll.toRadians()) < 0.0) {
val upsideDownRate = 1.0 / (2.0 * 20.0) // 2 seconds to max out upsideDown force
state.currUpsideDownForce.set( min(1.0, state.currUpsideDownForce.get() + upsideDownRate * abs(cos(controller.roll.toRadians()))))
} else if (state.stamina.get() == 0.0f) { // checks stamina depletion and forces dive if depleted
val upsideDownRate = 1.0 / (2.0 * 20.0) // 1 seconds to max out
state.currUpsideDownForce.set( min(1.0, state.currUpsideDownForce.get() + upsideDownRate))
} else { // Its right side up or has stamina so reset it
state.currUpsideDownForce.set(0.0)
}
}
fun tickStamina(
settings: BirdSettings,
state: BirdState,
vehicle: PokemonEntity,
driver: Player
) {
val stam = state.stamina.get()
// Grab the boost time in seconds and convert to ticks. Then calculate the drain rate as inversely
// proportional to the number of ticks of boost thus making a full boost take x ticks
// in short: "Stamina drains slower at higher values and also replenishes slower"
val totalStam = vehicle.runtime.resolveDouble(settings.staminaExpr ?: globalBird.staminaExpr!!) * 20.0f
val stamDrainRate = (1.0f / totalStam).toFloat()
val newStam = if (driver.zza > 0.0) max(0.0f,stam - stamDrainRate)
else if (!state.gliding.get()) max(0.0f,stam - stamDrainRate * 0.5f) // if hovering half the stam drain
else min(1.0f,stam + stamDrainRate * 0.2f)
state.stamina.set(newStam)
}
override fun rotation(
settings: BirdSettings,
state: BirdState,
@@ -148,6 +80,8 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
driver: Player,
input: Vec3
): Vec3 {
var yVel = 0.0
var leftForce = 0.0
var upForce = 0.0
var forwardForce = 0.0
@@ -166,10 +100,14 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
forwardForce += sin(Math.toRadians(controller.pitch.toDouble())) * state.rideVelocity.get().y
}
// The downward force used to encourage players to stop flying upside down.
val extraDownwardForce = state.currUpsideDownForce.get().pow(2) * -0.3 // 6 blocks a second downward
val velocity = Vec3(state.rideVelocity.get().x , upForce + extraDownwardForce, forwardForce)
//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
}
@@ -177,25 +115,20 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
* Calculates the change in the ride space vector due to player input and ride state
*/
fun calculateRideSpaceVel(settings: BirdSettings, state: BirdState, vehicle: PokemonEntity, driver: Player) {
// retrieve stats
val topSpeed = (vehicle.runtime.resolveDouble(settings.speedExpr ?: globalBird.speedExpr!!) / 20.0)
val accel = topSpeed / (vehicle.runtime.resolveDouble(settings.accelerationExpr ?: globalBird.accelerationExpr!!) * 20.0)
val glideTopSpeed = topSpeed * vehicle.runtime.resolveDouble(settings.glideSpeedExpr ?: globalBird.glideSpeedExpr!!)
//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
var activeInput = false
var newVelocity = Vec3(state.rideVelocity.get().x, state.rideVelocity.get().y, state.rideVelocity.get().z)
// Account for collision
if (vehicle.verticalCollision || vehicle.horizontalCollision) {
newVelocity = newVelocity.normalize().scale(vehicle.deltaMovement.length())
}
//speed up and slow down based on input
if (driver.zza != 0.0f && state.stamina.get() > 0.0) {
//make sure it can't exceed top speed
val forwardInput = when {
state.stamina.get() == 0.0f -> 0.0
driver.zza > 0 && newVelocity.z > topSpeed -> 0.0
driver.zza < 0 && newVelocity.z < (-topSpeed / 3.0) -> 0.0
else -> driver.zza.sign
@@ -215,6 +148,7 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
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
@@ -242,7 +176,6 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
//Vertical movement based on driver input.
val vertTopSpeed = topSpeed / 2.0
val vertInput = when {
state.stamina.get() == 0.0f -> 0.0
driver.jumping && state.rideVelocity.get().length() < 0.3 -> 1.0
driver.isShiftKeyDown && state.rideVelocity.get().length() < 0.3 -> -1.0
else -> 0.0
@@ -264,13 +197,28 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
//Check if the ride should be gliding
if (driver.isLocalPlayer) {
if ((activeInput && state.stamina.get() > 0.0) || state.rideVelocity.get().z < 0.2) {
if (activeInput && state.stamina.get() > 0.0) {
state.gliding.set(false)
} else {
state.gliding.set(true)
}
}
//TODO: Reintroduce stamina drain once stats start to be polished and tweaked
// //Only perform stamina logic if the ride does not have infinite stamina
// if (!vehicle.runtime.resolveBoolean(settings.infiniteStamina)) {
// if (activeInput) {
// state.stamina.set(state.stamina.get() - (0.05 / staminaStat).toFloat())
// }
//
// //Lose a base amount of stamina just for being airborne
// state.stamina.set(state.stamina.get() - (0.01 / staminaStat).toFloat())
// }
// else
// {
// state.stamina.set(1.0f)
// }
//air resistance
if( abs(newVelocity.z) > 0) {
newVelocity = newVelocity.subtract(0.0, 0.0, min(0.0015 * newVelocity.z.sign, newVelocity.z))
@@ -285,94 +233,41 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
driver: Player,
deltaTime: Double
): Vec3 {
if (Cobblemon.config.disableRoll) return Vec3.ZERO
if (driver !is OrientationControllable) return Vec3.ZERO
val controller = (driver as OrientationControllable).orientationController
val handling = vehicle.runtime.resolveDouble(settings.handlingExpr ?: globalBird.handlingExpr!!)
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr ?: globalBird.speedExpr!!) / 20.0
//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(RidingBehaviour.scaleToRange(state.rideVelocity.get().length(), 0.0, topSpeed)), rotMin)
val yawDeltaDeg = deltaTime * handling * sin(Math.toRadians(controller.roll.toDouble())) //* sin(Math.toRadians(controller.roll.toDouble())).sign
val trueYawDelt = yawDeltaDeg * abs(cos(Math.toRadians(controller.pitch.toDouble())))// * sqrt(RidingBehaviour.scaleToRange(state.rideVelocity.get().length(), 0.0, topSpeed))
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
// Dampen yaw when upside down
val yawDampen = (1 - abs(min(cos(controller.roll.toRadians()),0.0f)).pow(2))
controller.applyGlobalYaw(trueYawDelt.toFloat() * yawDampen)
controller.applyGlobalYaw(trueYawDelt.toFloat())
// Correct orientation when at low speeds
correctOrientation(settings, state, vehicle, controller, deltaTime)
controller.applyGlobalPitch(state.currPitchCorrectionForce.get().toRadians())
//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())
}
private fun correctOrientation(
settings: BirdSettings,
state: BirdState,
vehicle: PokemonEntity,
controller: OrientationController,
deltaTime: Double
) {
// Calculate correcting roll force.
// Mult by cos(pitch) to ensure that it doesn't roll correct at when diving or climbing
val rollCorrectionTimer = vehicle.runtime.resolveDouble(settings.timeToRollCorrect ?: globalBird.timeToRollCorrect!!)
val maxRollCorrectionRate = if (state.rideVelocity.get().length() < 0.1) 15.0f else 8.0f * (cos(controller.pitch.toRadians())).pow(2) * ((state.noInputTimeRoll.get() - rollCorrectionTimer)).coerceIn(0.0, 1.0).toFloat()
val maxRollForce = maxRollCorrectionRate / 40.0
val rollArrivalDeg = 90.0
if(state.rideVelocity.get().length() < 0.1 || state.noInputTimeRoll.get() > rollCorrectionTimer) {
val arrivalInfluence = (min(rollArrivalDeg,abs(controller.roll).toDouble()) / rollArrivalDeg)
val desiredRollForce = (0 - controller.roll).sign * maxRollCorrectionRate * arrivalInfluence
var steeredRollForce = desiredRollForce - state.currRollCorrectionForce.get()
// Ensure the 'steering' correction force doesn't exceed our maxForce
if (abs(steeredRollForce) > maxRollForce) {
steeredRollForce = steeredRollForce.sign * maxRollForce
}
// Apply the 'steered' force (its really not a steering force I need a better name)
state.currRollCorrectionForce.set(
state.currRollCorrectionForce.get() + steeredRollForce
)
} else {
state.currRollCorrectionForce.set(
//reduce rollCorrection gradually
lerp(state.currRollCorrectionForce.get(), 0.0, deltaTime * 0.96)
)
}
//Calculate correcting pitch force.
val maxPitchCorrectionRate = if (state.currUpsideDownForce.get() != 0.0) 8f * state.currUpsideDownForce.get() else 15f
val maxPitchForce = maxPitchCorrectionRate.toFloat() / 40.0f
val pitchArrivalDeg = 45.0f
var desiredPitch = if (state.currUpsideDownForce.get() != 0.0) 90.0 else 0.0
// if (cos(controller.roll.toRadians()) < 0.0) {
// desiredPitch *= -1 // If not rolled over then flip the pitch force. There are better ways to do this :(
// }
if((state.rideVelocity.get().length() < 0.1 && controller.pitch < 0.0) || state.currUpsideDownForce.get() != 0.0) {
val arrivalInfluence = (min(pitchArrivalDeg,abs(Mth.wrapDegrees(desiredPitch.toFloat().angleDifference(controller.pitch)))) / pitchArrivalDeg)
val desiredPitchForce = maxPitchCorrectionRate.toFloat() * arrivalInfluence
var steeredPitchForce = (desiredPitchForce - state.currPitchCorrectionForce.get())
// Ensure the 'steering' correction force doesn't exceed our maxForce
if (abs(steeredPitchForce) > maxPitchForce) {
steeredPitchForce = steeredPitchForce.sign * maxPitchForce
}
// Apply the 'steered' force (its really not a steering force I need a better name)
state.currPitchCorrectionForce.set(
state.currPitchCorrectionForce.get() + steeredPitchForce
)
} else {
state.currPitchCorrectionForce.set(
//reduce rollCorrection gradually
lerp(state.currPitchCorrectionForce.get(), 0.0, deltaTime * 0.98)
)
}
}
override fun rotationOnMouseXY(
settings: BirdSettings,
state: BirdState,
@@ -385,25 +280,6 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
sensitivity: Double,
deltaTime: Double
): Vec3 {
if (driver !is OrientationControllable) return Vec3.ZERO
val controller = (driver as OrientationControllable).orientationController
// Begin the roll correction time counter if not enough mouse input
// is detected
if (mouseX < 1 && abs(mouseY) < 1) {
state.noInputTimeRoll.set(state.noInputTimeRoll.get() + deltaTime)
} else if (cos(controller.roll.toRadians()) < 0.1) {
state.noInputTimeRoll.set(state.noInputTimeRoll.get() + deltaTime * 0.5)
}
else {
state.noInputTimeRoll.set(0.0)
}
if (abs(mouseY) < 1) {
state.noInputTimePitch.set(state.noInputTimePitch.get() + deltaTime)
} else {
state.noInputTimePitch.set(0.0)
}
return when {
Cobblemon.config.disableRoll -> noRollRotation(
settings,
@@ -450,23 +326,24 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
// Set roll to zero if transitioning to noroll config
controller.rotateRoll(controller.roll * -1.0f)
val handling = vehicle.runtime.resolveDouble(settings.handlingExpr ?: globalBird.handlingExpr!!)
val mouseInputMult = vehicle.runtime.resolveDouble(settings.mouseInputMult ?: globalBird.mouseInputMult!!)
//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 mouseXc = ((mouseX).coerceIn(-60.0, 60.0) / 60.0) * mouseInputMult
val mouseYc = ((mouseY).coerceIn(-60.0, 60.0) / 60.0) * mouseInputMult
val mouseXc = (mouseX).coerceIn(-60.0, 60.0)
val mouseYc = (mouseY).coerceIn(-60.0, 60.0)
val xInput = mouseXSmoother.getNewDeltaValue(mouseXc * 0.1, deltaTime * smoothingSpeed);
val yInput = mouseYSmoother.getNewDeltaValue(mouseYc * 0.1, deltaTime * smoothingSpeed);
//Give the ability to yaw with x mouse input when at low speeds.
val yawForce = xInput * handling //* ( 1.0 - min(sqrt(RidingBehaviour.scaleToRange(state.rideVelocity.get().length(), 0.0, topSpeed)), 0.5))
val yawForce = xInput * ( 1.0 - min(sqrt(RidingBehaviour.scaleToRange(state.rideVelocity.get().length(), 0.0, topSpeed)), 0.5))
//Apply yaw globally as we don't want roll or pitch changes due to local yaw when looking up or down.
controller.applyGlobalYaw(yawForce.toFloat() * abs(cos(controller.pitch.toRadians())).pow(2))
controller.applyGlobalYaw(yawForce.toFloat())
var pitchRot = yInput * handling
var pitchRot = yInput
// Pitch up globally
if (abs(controller.pitch + pitchRot) >= 89.5 ) {
@@ -495,48 +372,62 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
if (driver !is OrientationControllable) return Vec3.ZERO
val controller = (driver as OrientationControllable).orientationController
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr ?: globalBird.speedExpr!!) / 20.0
val mouseInputMult = vehicle.runtime.resolveDouble(settings.mouseInputMult ?: globalBird.mouseInputMult!!)
val handling = vehicle.runtime.resolveDouble(settings.handlingExpr)
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
//Smooth out mouse input.
val smoothingSpeed = 2.0
val mouseXc = (mouseX).coerceIn(-45.0, 45.0) * mouseInputMult
// When upside down apply a pitching force to nudge the player towards a dive. This reduces the 'weirdness'
// of being upside down
val rolledOverPitchCorrection = -8*sqrt(abs(min(cos(controller.roll.toRadians()),0.0f))) * (cos(controller.pitch.toRadians())).pow(2)
val mouseYc = ((mouseY).coerceIn(-45.0, 45.0) * mouseInputMult) + rolledOverPitchCorrection
val smoothingSpeed = 4.0
val mouseXc = (mouseX).coerceIn(-60.0, 60.0)
val mouseYc = (mouseY).coerceIn(-60.0, 60.0)
val xInput = mouseXSmoother.getNewDeltaValue(mouseXc * 0.1, deltaTime * smoothingSpeed);
val yInput = mouseYSmoother.getNewDeltaValue(mouseYc * 0.1, deltaTime * smoothingSpeed);
val currSpeed = state.rideVelocity.get().length()
val currHorzSpeed = state.rideVelocity.get().horizontalDistance()
val hoverSpeed = 0.1
// Yaw locally when at or below hoverSpeed but not when pitched up or rolled over
val yawDampen = abs(cos(controller.pitch.toRadians())).pow(2) * abs(cos(controller.roll.toRadians())).pow(2)
val localYaw = if (currHorzSpeed < hoverSpeed) xInput * (1 - sqrt(RidingBehaviour.scaleToRange(currHorzSpeed, hoverSpeed, topSpeed))).coerceIn(0.0, 1.0)
else 0.0 //xInput * 0.4 * cos(controller.roll.toRadians()).coerceIn(0.0f, 1.0f) * yawDampen
//limit rolling based on handling and current speed.
//modulated by speed so that when flapping idle in air you are ont wobbling around to look around
val rotMin = 15.0
var rollForce = xInput
val rotLimit = max(90 * sqrt(RidingBehaviour.scaleToRange(state.rideVelocity.get().length(), 0.0, topSpeed)), rotMin)
// Pitch locally up or down depending upon a number of factors:
// - Reduce pitch substantially when rolled and in a steep yaw. This prevents the player from ignoring a slow handling stat
// - Do not pitch at all when at or below hover speed
// - pitch faster at higher speeds
val p = 1.0 - vehicle.runtime.resolveDouble(settings.horizontalPitchExpr ?: globalBird.horizontalPitchExpr!!)
var localPitch = yInput * (1 - abs(sin(controller.roll.toRadians())).pow(2) * p * hypot(controller.forwardVector.x.toDouble(), controller.forwardVector.z.toDouble()).toFloat())
localPitch *= if (currSpeed > hoverSpeed || yInput > 0.0) RidingBehaviour.scaleToRange(currSpeed, 0.0, topSpeed).coerceIn(0.2, 1.0)
else 0.0
//Limit roll by non linearly decreasing inputs towards
// a rotation limit based on the current distance from
// that rotation limit
if (abs(controller.roll + rollForce) < rotLimit) {
if (sign(rollForce) == sign(controller.roll).toDouble()) {
//Grab how far the current roll is away from the roll limit
val d = abs(abs(controller.roll) - rotLimit)
rollForce *= (d.pow(2)) / (rotLimit.pow(2))
}
} else { //if (sign(rollForce) == sign(controller.roll).toDouble()) {
rollForce *= max(abs(sin(controller.pitch.toRadians())),0.1f)
}
// Roll less when slow and not at all when at hoverSpeed or lower.
// Apply a roll righting force when trying to pitch hard while rolled sideways. This nudges the player towards
// pitching globally
val pitchInfluencedRollCorrection = 0.0
val howRolledAmI = (( cos(controller.roll.toRadians()) - 1).coerceIn(-1.0f, 0.0f) * -2).coerceIn(0.0f, 0.7f) //Coerce into how much roll you want to let in in the deadzone
val inputInRolledDir = if (xInput.sign.toFloat() == controller.roll.sign) 1.0 else -cos(controller.roll.toRadians()).coerceIn(-1.0f, 0.0f).toDouble()
val rollDampen = (1 - howRolledAmI * abs(cos(controller.pitch.toRadians())).pow(2) * inputInRolledDir)
val rollForce = if (currHorzSpeed > hoverSpeed) xInput * sqrt(abs(RidingBehaviour.scaleToRange(currHorzSpeed, hoverSpeed, topSpeed)).coerceIn(0.0,1.0)) * rollDampen + pitchInfluencedRollCorrection
else 0.0
//Give the ability to yaw with x mouse input when at low speeds.
val yawForce = xInput * ( 1.0 - sqrt(RidingBehaviour.scaleToRange(state.rideVelocity.get().length(), 0.0, topSpeed)))
// //Yaw locally a bit when up or down so that its more intuitive to make it out of a dive or a straight vertical
// //climb
// val yawForcePitched = xInput * sin(Math.toRadians(abs(controller.pitch.toDouble()))) * 0.25
//Apply yaw globally as we don't want roll or pitch changes due to local yaw when looking up or down.
if (controller.upVector.dot(Vector3f(0f,1f,0f)) > 0) {
controller.applyGlobalYaw(yawForce.toFloat())
}
// Pitch up globally
controller.applyGlobalPitch(-1 * yInput.toFloat())
// roll to 0 if pitching and not upside down. This is to prevent odd back and forth wobbling when
// pitching globally and experiencing axis changes
if (controller.upVector.dot(Vector3f(0f,1f,0f)) > 0) {
// correct roll equal to the amount the ride is pitching (yInput)
// correct roll only when near to horizontal (cos(pitch))
// correct roll proportional to how rolled the ride currently is
rollForce += controller.roll * -0.01 * abs(yInput) * abs(cos(controller.pitch.toRadians())) * abs(sin(controller.roll.toRadians()))
}
//yaw, pitch, roll
return Vec3(localYaw, localPitch, rollForce)
return Vec3(0.0, 0.0, rollForce)
}
@@ -583,14 +474,13 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
vehicle: PokemonEntity,
driver: Player
): Float {
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr ?: globalBird.speedExpr!!) / 20.0
val glideTopSpeed = topSpeed * vehicle.runtime.resolveDouble(settings.glideSpeedExpr ?: globalBird.glideSpeedExpr!!)
val currSpeed = state.rideVelocity.get().length()
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 = RidingBehaviour.scaleToRange(currSpeed, topSpeed, glideTopSpeed)
//Must I ensure that topspeed is greater than minimum?
val normalizedGlideSpeed = RidingBehaviour.scaleToRange(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.
//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
}
@@ -646,17 +536,6 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
return false
}
override fun damageOnCollision(
settings: BirdSettings,
state: BirdState,
vehicle: PokemonEntity,
impactVec: Vec3
): Boolean {
if (!state.gliding.get()) return false
val impactSpeed = impactVec.horizontalDistance().toFloat() * 10f
return vehicle.causeFallDamage(impactSpeed, 1f, vehicle.damageSources().flyIntoWall())
}
override fun getRideSounds(
settings: BirdSettings,
state: BirdState,
@@ -672,70 +551,60 @@ class BirdSettings : RidingBehaviourSettings {
override val key = BirdBehaviour.KEY
override val stats = mutableMapOf<RidingStat, IntRange>()
var infiniteAltitude: Expression? = null
var infiniteAltitude: Expression = "false".asExpression()
private set
var infiniteStamina: Expression? = null
var infiniteStamina: Expression = "false".asExpression()
private set
var timeToRollCorrect: Expression? = null
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 handlingExpr: Expression? = null
var horizontalPitchExpr: Expression? = null
var mouseInputMult: Expression? = null
var speedExpr: Expression? = null
// Seconds from stationary to top speed
var accelerationExpr: Expression? = null
var staminaExpr: Expression? = null
var glideSpeedExpr: Expression? = null
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)
buffer.writeRidingStats(stats)
rideSounds.encode(buffer)
buffer.writeNullableExpression(infiniteAltitude)
buffer.writeNullableExpression(infiniteStamina)
buffer.writeNullableExpression(glideSpeedExpr)
buffer.writeNullableExpression(handlingExpr)
buffer.writeNullableExpression(horizontalPitchExpr)
buffer.writeNullableExpression(mouseInputMult)
buffer.writeNullableExpression(speedExpr)
buffer.writeNullableExpression(accelerationExpr)
buffer.writeNullableExpression(staminaExpr)
buffer.writeNullableExpression(timeToRollCorrect)
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) {
stats.putAll(buffer.readRidingStats())
rideSounds = RideSoundSettingsList.decode(buffer)
infiniteAltitude = buffer.readNullableExpression()
infiniteStamina = buffer.readNullableExpression()
glideSpeedExpr = buffer.readNullableExpression()
handlingExpr = buffer.readNullableExpression()
horizontalPitchExpr = buffer.readNullableExpression()
mouseInputMult = buffer.readNullableExpression()
speedExpr = buffer.readNullableExpression()
accelerationExpr = buffer.readNullableExpression()
staminaExpr = buffer.readNullableExpression()
timeToRollCorrect = buffer.readNullableExpression()
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 BirdState : RidingBehaviourState() {
var gliding = ridingState(false, Side.BOTH)
var gliding = ridingState(false, Side.CLIENT)
var lastGlide = ridingState(-100L, Side.CLIENT)
var currRollCorrectionForce = ridingState(0.0, Side.CLIENT)
var currPitchCorrectionForce = ridingState(0.0, Side.CLIENT)
var noInputTimeRoll = ridingState(0.0, Side.CLIENT)
var noInputTimePitch = ridingState(0.0, Side.CLIENT)
var currUpsideDownForce = ridingState(0.0, Side.CLIENT)
override fun encode(buffer: FriendlyByteBuf) {
super.encode(buffer)
buffer.writeFloat(stamina.get())
buffer.writeBoolean(gliding.get())
}
@@ -749,9 +618,6 @@ class BirdState : RidingBehaviourState() {
gliding.set(false, forced = true)
lastGlide.set(-100L, forced = true)
currRollCorrectionForce.set(0.0, forced = true)
currPitchCorrectionForce.set(0.0, forced = true)
noInputTimeRoll.set(0.0, forced = true)
noInputTimePitch.set(0.0, forced = true)
}
override fun toString(): String {
@@ -764,9 +630,6 @@ class BirdState : RidingBehaviourState() {
it.gliding.set(this.gliding.get(), forced = true)
it.lastGlide.set(this.lastGlide.get(), forced = true)
it.currRollCorrectionForce.set(this.currRollCorrectionForce.get(), forced = true)
it.currPitchCorrectionForce.set(this.currPitchCorrectionForce.get(), forced = true)
it.noInputTimeRoll.set(this.noInputTimeRoll.get(), forced = true)
it.noInputTimePitch.set(this.noInputTimePitch.get(), forced = true)
}
override fun shouldSync(previous: RidingBehaviourState): Boolean {
@@ -775,16 +638,3 @@ class BirdState : RidingBehaviourState() {
return super.shouldSync(previous)
}
}
/**
* Calculates the shortest difference between two angles.
* The result will be in the range [-180, 180].
* A positive result indicates a clockwise direction from this angle to the other,
* a negative result indicates a counter-clockwise direction.
*/
fun Float.angleDifference(other: Float): Float {
var diff = other - this
while (diff <= -180F) diff += 360F
while (diff > 180F) diff -= 360F
return diff
}
@@ -10,7 +10,6 @@ package com.cobblemon.mod.common.api.riding.behaviour.types.air
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonRideSettings
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourState
@@ -38,9 +37,6 @@ class GliderBehaviour : RidingBehaviour<GliderSettings, RidingBehaviourState> {
override val key = KEY
val globalGlider: GliderSettings
get() = CobblemonRideSettings.glider
override fun getRidingStyle(settings: GliderSettings, state: RidingBehaviourState): RidingStyle {
return RidingStyle.AIR
}
@@ -57,7 +53,7 @@ class GliderBehaviour : RidingBehaviour<GliderSettings, RidingBehaviourState> {
}
override fun speed(settings: GliderSettings, state: RidingBehaviourState, vehicle: PokemonEntity, driver: Player): Float {
return vehicle.runtime.resolveFloat(settings.speed ?: globalGlider.speed!!)
return vehicle.runtime.resolveFloat(settings.speed)
}
override fun rotation(
@@ -76,9 +72,10 @@ class GliderBehaviour : RidingBehaviour<GliderSettings, RidingBehaviourState> {
driver: Player,
input: Vec3
): Vec3 {
val xVector = if (vehicle.runtime.resolveBoolean(settings.canStrafe ?: globalGlider.canStrafe!!)) driver.xxa.toDouble() else 0.0
val yVector = -vehicle.runtime.resolveDouble(settings.glideSpeed ?: globalGlider.glideSpeed!!)
val xVector = if (vehicle.runtime.resolveBoolean(settings.canStrafe)) driver.xxa.toDouble() else 0.0
val yVector = -vehicle.runtime.resolveDouble(settings.glideSpeed)
val zVector = driver.zza.toDouble()
val speedStat = settings.calculate(RidingStat.SPEED, 0)
return Vec3(xVector, yVector, zVector)
}
@@ -199,30 +196,31 @@ class GliderSettings : RidingBehaviourSettings {
override val key = GliderBehaviour.KEY
override val stats = mutableMapOf<RidingStat, IntRange>()
var glideSpeed: Expression? = null
var glideSpeed: Expression = "0.1".asExpression()
private set
var speed: Expression? = null
var speed: Expression = "1.0".asExpression()
private set
var canStrafe: Expression? = null
var canStrafe: Expression = "false".asExpression()
private set
var rideSounds: RideSoundSettingsList = RideSoundSettingsList()
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeRidingStats(stats)
rideSounds.encode(buffer)
buffer.writeNullableExpression(glideSpeed)
buffer.writeNullableExpression(speed)
buffer.writeNullableExpression(canStrafe)
buffer.writeExpression(glideSpeed)
buffer.writeExpression(speed)
buffer.writeExpression(canStrafe)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
stats.putAll(buffer.readRidingStats())
rideSounds = RideSoundSettingsList.decode(buffer)
glideSpeed = buffer.readNullableExpression()
speed = buffer.readNullableExpression()
canStrafe = buffer.readNullableExpression()
glideSpeed = buffer.readExpression()
speed = buffer.readExpression()
canStrafe = buffer.readExpression()
}
}
@@ -10,7 +10,6 @@ package com.cobblemon.mod.common.api.riding.behaviour.types.air
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonRideSettings
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviour
@@ -22,16 +21,7 @@ import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.blockPositionsAsListRounded
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.readNullableExpression
import com.cobblemon.mod.common.util.readRidingStats
import com.cobblemon.mod.common.util.resolveDouble
import com.cobblemon.mod.common.util.writeNullableExpression
import com.cobblemon.mod.common.util.writeRidingStats
import kotlin.math.abs
import kotlin.math.pow
import kotlin.math.sin
import com.cobblemon.mod.common.util.*
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.SmoothDouble
@@ -40,6 +30,9 @@ import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
import net.minecraft.world.phys.shapes.Shapes
import kotlin.math.abs
import kotlin.math.pow
import kotlin.math.sin
class HelicopterBehaviour : RidingBehaviour<HelicopterSettings, RidingBehaviourState> {
companion object {
@@ -48,8 +41,6 @@ class HelicopterBehaviour : RidingBehaviour<HelicopterSettings, RidingBehaviourS
}
override val key = KEY
val globalHelicopter: HelicopterSettings
get() = CobblemonRideSettings.helicopter
override fun getRidingStyle(settings: HelicopterSettings, state: RidingBehaviourState): RidingStyle {
return RidingStyle.AIR
@@ -102,8 +93,8 @@ class HelicopterBehaviour : RidingBehaviour<HelicopterSettings, RidingBehaviourS
val runtime = vehicle.runtime
//If the player is not modulating height then hover
var yVel = if (driver.jumping) runtime.resolveDouble(settings.verticalVelocity ?: globalHelicopter.verticalVelocity!!)
else if (driver.isShiftKeyDown) -runtime.resolveDouble(settings.verticalVelocity ?: globalHelicopter.verticalVelocity!!)
var yVel = if (driver.jumping) runtime.resolveDouble(settings.verticalVelocity)
else if (driver.isShiftKeyDown) -runtime.resolveDouble(settings.verticalVelocity)
else (0.0)
yVel *= 0.25
@@ -299,35 +290,36 @@ class HelicopterSettings : RidingBehaviourSettings {
override val key = HelicopterBehaviour.KEY
override val stats = mutableMapOf<RidingStat, IntRange>()
var gravity: Expression? = null
var gravity: Expression = "1.0".asExpression()
private set
var horizontalAcceleration: Expression? = null
var horizontalAcceleration: Expression = "0.1".asExpression()
private set
var verticalVelocity: Expression? = null
var verticalVelocity: Expression = "1.0".asExpression()
private set
var speed: Expression? = null
var speed: Expression = "1.0".asExpression()
private set
var rideSounds: RideSoundSettingsList = RideSoundSettingsList()
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeRidingStats(stats)
rideSounds.encode(buffer)
buffer.writeNullableExpression(gravity)
buffer.writeNullableExpression(horizontalAcceleration)
buffer.writeNullableExpression(verticalVelocity)
buffer.writeNullableExpression(speed)
buffer.writeExpression(gravity)
buffer.writeExpression(horizontalAcceleration)
buffer.writeExpression(verticalVelocity)
buffer.writeExpression(speed)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
stats.putAll(buffer.readRidingStats())
rideSounds = RideSoundSettingsList.decode(buffer)
gravity = buffer.readNullableExpression()
horizontalAcceleration = buffer.readNullableExpression()
verticalVelocity = buffer.readNullableExpression()
speed = buffer.readNullableExpression()
gravity = buffer.readExpression()
horizontalAcceleration = buffer.readExpression()
verticalVelocity = buffer.readExpression()
speed = buffer.readExpression()
}
}
@@ -9,7 +9,6 @@
package com.cobblemon.mod.common.api.riding.behaviour.types.air
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.CobblemonRideSettings
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.*
import com.cobblemon.mod.common.api.riding.posing.PoseOption
@@ -20,16 +19,13 @@ 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.Entity
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.level.ClipContext
import net.minecraft.world.phys.HitResult
import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
import net.minecraft.world.phys.shapes.Shapes
@@ -42,8 +38,6 @@ class HoverBehaviour : RidingBehaviour<HoverSettings, HoverState> {
}
override val key = KEY
val globalHover: HoverSettings
get() = CobblemonRideSettings.hover
override fun getRidingStyle(settings: HoverSettings, state: HoverState): RidingStyle {
return RidingStyle.AIR
@@ -87,57 +81,7 @@ class HoverBehaviour : RidingBehaviour<HoverSettings, HoverState> {
vehicle: PokemonEntity,
driver: Player
): Float {
return state.speed.get().toFloat()
}
override fun tick(
settings: HoverSettings,
state: HoverState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
) {
if(vehicle.level().isClientSide) {
tickStamina(settings, state, vehicle)
checkTooHigh(settings, state, vehicle)
}
}
fun checkTooHigh(
settings: HoverSettings,
state: HoverState,
vehicle: PokemonEntity,
) {
val heightLimit = vehicle.runtime.resolveDouble(settings.jumpExpr ?: globalHover.jumpExpr!!)
val pos = vehicle.position()
val level = Minecraft.getInstance().player?.level() ?: return
val hit = level.clip(
ClipContext(
pos,
pos.subtract(0.0, heightLimit, 0.0),
ClipContext.Block.COLLIDER,
ClipContext.Fluid.NONE,
vehicle
)
)
state.tooHigh.set(hit.type == HitResult.Type.MISS)
}
fun tickStamina(
settings: HoverSettings,
state: HoverState,
vehicle: PokemonEntity
) {
if (vehicle.runtime.resolveBoolean(settings.infiniteStamina ?: globalHover.infiniteStamina!!)) {
return
}
val stam = state.stamina.get()
val stamDrainRate = (1.0f / vehicle.runtime.resolveDouble(settings.staminaExpr ?: globalHover.staminaExpr!!)).toFloat() / 20.0f
val stamReplenishRate = (1.0f / vehicle.runtime.resolveDouble(settings.stamReplenishTimeSeconds ?: globalHover.stamReplenishTimeSeconds!!) ).toFloat() / 20.0f // 2 seconds to replenish a full bar of stamina
val newStam = if (state.tooHigh.get()) max(0.0f, stam - stamDrainRate) else min(1.0f,stam + stamReplenishRate)
state.stamina.set(newStam)
return state.rideVelocity.get().length().toFloat()
}
override fun updatePassengerRotation(
@@ -146,7 +90,24 @@ class HoverBehaviour : RidingBehaviour<HoverSettings, HoverState> {
vehicle: PokemonEntity,
driver: LivingEntity
) {
return
//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*RidingBehaviour.scaleToRange(state.rideVelocity.get().length(), 0.0, topSpeed).toFloat()
//driver.yRot += (entity.riding.deltaRotation.y - turnAmount)
//driver.setYHeadRot(driver.yHeadRot + (entity.riding.deltaRotation.y) - turnAmount)
}
override fun clampPassengerRotation(
@@ -155,7 +116,12 @@ class HoverBehaviour : RidingBehaviour<HoverSettings, HoverState> {
vehicle: PokemonEntity,
driver: LivingEntity
) {
return
val f = Mth.wrapDegrees(driver.yRot - vehicle.yRot)
val lookYawLimit = 90.0f
val g = Mth.clamp(f, -lookYawLimit, lookYawLimit)
driver.yRotO += g - f
driver.yRot = driver.yRot + g - f
driver.setYHeadRot(driver.yRot)
}
@@ -166,7 +132,9 @@ class HoverBehaviour : RidingBehaviour<HoverSettings, HoverState> {
driver: LivingEntity
): Vec2 {
val turnAmount = calcRotAmount(settings, state, vehicle, driver)
return Vec2(driver.xRot, vehicle.yRot + turnAmount )
}
/*
@@ -181,19 +149,27 @@ class HoverBehaviour : RidingBehaviour<HoverSettings, HoverState> {
vehicle: PokemonEntity,
driver: LivingEntity
): Float {
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr ?: globalHover.speedExpr!!) / 20.0
val handling = vehicle.runtime.resolveDouble(settings.handlingExpr ?: globalHover.handlingExpr!!) / 20.0
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
//In degrees per second
val handling = 220.0
val maxYawDiff = 90.0f
// Normalize the current rotation diff
val rotDiff = Mth.wrapDegrees(driver.yRot - vehicle.yRot).coerceIn(-maxYawDiff,maxYawDiff)
val rotDiffInfl = sqrt(abs(rotDiff / maxYawDiff)) * rotDiff.sign
//Normalize the current rotation diff
val rotDiff = Mth.wrapDegrees(driver.yRot - vehicle.yRot)
val rotDiffNorm = rotDiff / maxYawDiff
var turnRate = (handling.toFloat()) * rotDiffInfl
turnRate -= (turnRate * 0.5f) * (RidingBehaviour.scaleToRange(vehicle.deltaMovement.length(), 0.0, topSpeed).coerceIn(0.0, 1.0)).pow(2).toFloat()
//Take the square root so that the ride levels out quicker when at lower differences between entity
//y and driver y
//This influences the speed of the turn based on how far in one direction you're looking
val rotDiffMod = (sqrt(abs(rotDiffNorm)) * rotDiffNorm.sign)
// Ensure you only ever rotate as much difference as there is between the angles.
val turnSpeed = turnRate
//Take the inverse so that you turn less at higher speeds
val normSpeed = 1.0f // = 1.0f - 0.5f*RidingBehaviour.scaleToRange(state.rideVelocityocity.length(), 0.0, topSpeed).toFloat()
val turnRate = (handling.toFloat() / 20.0f)
//Ensure you only ever rotate as much difference as there is between the angles.
val turnSpeed = turnRate * rotDiffMod * normSpeed
val rotAmount = turnSpeed.coerceIn(-abs(rotDiff), abs(rotDiff))
return rotAmount
@@ -206,77 +182,128 @@ class HoverBehaviour : RidingBehaviour<HoverSettings, HoverState> {
driver: Player,
input: Vec3
): Vec3 {
// if (Minecraft.getInstance().options.keySprint.isDown) {
// val belowBox = vehicle.boundingBox.expandTowards(0.0,-1.5, 0.0).inflate(5.0, 10.0,5.0)
//
// val level = vehicle.level()
// val toBeSucked = level.getEntities(vehicle, belowBox)
// for (suckee in toBeSucked) {
// if (suckee != null) {
// abductify(suckee, vehicle.position().add(0.0,-2.0,0.0))
// suckee.deltaMovement = suckee.deltaMovement.add(0.0, 0.5,0.0)
//
// }
// }
// }
//
// if (firstEntity != null) {
// // Example action: print the entity name
// println("Found nearby entity: ${firstEntity.name.string}")
//
// firstEntity.deltaMovement = firstEntity.deltaMovement.add(0.0, 0.1,0.0)
// }
val retVel = calculateRideSpaceVel(settings, state, vehicle, driver)
//state.rideVelocity.set(retVel)
return retVel
}
private fun abductify(abductee: Entity, pos: Vec3) {
val diff = pos.subtract(abductee.position()).scale(1.0)
abductee.deltaMovement = abductee.deltaMovement.add(diff)
}
private fun calculateRideSpaceVel(
settings: HoverSettings,
state: HoverState,
vehicle: PokemonEntity,
driver: Player
): Vec3 {
// Convert these to their tick values
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr ?: globalHover.speedExpr!!) / 20.0
val accel = topSpeed / (vehicle.runtime.resolveDouble(settings.accelerationExpr ?: globalHover.accelerationExpr!!) * 20.0)
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
val accel = vehicle.runtime.resolveDouble(settings.accelerationExpr)
val speed = vehicle.deltaMovement.length()
var newVelocity = state.rideVelocity.get()
//Flag for determining if player is actively inputting
var activeInput = false
// Check for collisions and if found, update the speed to reflect it
if (vehicle.verticalCollision || vehicle.horizontalCollision) {
state.speed.set(vehicle.deltaMovement.length())
}
val minSpeed = 0.0
var newVelocity = vehicle.deltaMovement
// align the velocity vector to be in local vehicle space
val yawAligned = Matrix3f().rotateY(vehicle.yRot.toRadians())
newVelocity = (newVelocity.toVector3f().mul(yawAligned)).toVec3d()
// Vertical movement based on driver input.
//air Resistance
//newVelocity = newVelocity.lerp(Vec3.ZERO,(((topSpeed - speed) / topSpeed))*0.001)
//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 {
speed > 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 {
// newVelocity = Vec3(
// newVelocity.x,
// newVelocity.y,
// newVelocity.z - (((topSpeed - speed) / topSpeed)*0.001)
// )
}
//speed up and slow down based on input
if (driver.xxa != 0.0f && state.stamina.get() > 0.0) {
//make sure it can't exceed top speed
val lateralInput = when {
speed > topSpeed && (driver.xxa.sign == newVelocity.x.sign.toFloat()) -> 0.0
else -> driver.xxa.sign
}
newVelocity = Vec3(
newVelocity.x + (accel * lateralInput.toDouble()),
newVelocity.y,
(newVelocity.z ))
//air resistance
// if (lateralInput == 0.0) {
// newVelocity = Vec3(
// newVelocity.x - (((topSpeed - speed) / topSpeed)*0.001),
// newVelocity.y,
// newVelocity.z
// )
// }
}
//Vertical movement based on driver input.
val vertInput = when {
state.stamina.get() == 0.0f -> -1.0
driver.jumping -> 1.0
driver.isShiftKeyDown -> -1.0
driver.jumping && !(speed > topSpeed && (newVelocity.y.sign.toFloat() > 0.0))-> 1.0
driver.isShiftKeyDown && !(speed > topSpeed && (newVelocity.y.sign.toFloat() < 0.0))-> -1.0
else -> 0.0
}
// Normalize and scale to accel so that diagonal movement
// doesn't speed up the ride more than input in one direction
var inputVel = Vec3(
driver.xxa.sign.toDouble(),
vertInput,
driver.zza.sign.toDouble()
).normalize()
//air resistance
// if (vertInput == 0.0) {
// newVelocity = Vec3(
// newVelocity.x,
// newVelocity.y - (((topSpeed - speed) / topSpeed)*0.001),
// newVelocity.z
// )
// }
// Air Friction/Resistance
// When the dot between friction and input is -1 we want to apply none of it.
// In short we want to avoid applying friction that opposes our input force.
// otherwise we would not be able to reach our top speed when acceleration is
// low.
val frictionConst = 0.02
var frictionForce = newVelocity.scale(-1.0).scale(frictionConst)
val frictionApplied = if(inputVel.lengthSqr() != 0.0) 1 - (0.5*(1 + -1.0 * (inputVel.normalize().dot(frictionForce.normalize()))))
else 1.0
frictionForce = frictionForce.scale((frictionApplied))
newVelocity = newVelocity.add(frictionForce)
// Ensure that if you're not applying force where the ride is facing then you apply a portion of debuff
val strafeDebuff = 0.5
val vehicleForwardVec = Vec3(0.0, 0.0, 1.0)
val playerForwardVec = inputVel //.yRot(vehicle.yRot)
val inputStrength = accel - (accel * strafeDebuff*(1 - vehicleForwardVec.dot(playerForwardVec).coerceIn(0.0, 1.0)))
inputVel = inputVel.scale(inputStrength)
newVelocity = newVelocity.add(inputVel)
// Set speed to new magnitude to avoid the horrible things minecraft friction
// does to our vectors :(
state.speed.set(min(topSpeed, newVelocity.length()))
// Check to see if this new velocity will exceed top speed and if it will then cap it
newVelocity = if (newVelocity.length() > topSpeed) newVelocity.normalize().scale(topSpeed) else newVelocity
val revertYawAligned = Matrix3f().rotateY(-vehicle.yRot.toRadians())
state.rideVelocity.set((newVelocity.toVector3f().mul(revertYawAligned)).toVec3d())
if (state.stamina.get() > 0.0) {
newVelocity = Vec3(
newVelocity.x,
(newVelocity.y + (accel * vertInput)), //.coerceIn(-vertTopSpeed, vertTopSpeed),
newVelocity.z)
}
return newVelocity
}
@@ -303,7 +330,13 @@ class HoverBehaviour : RidingBehaviour<HoverSettings, HoverState> {
sensitivity: Double,
deltaTime: Double
): Vec3 {
return Vec3.ZERO
//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(
@@ -350,7 +383,7 @@ class HoverBehaviour : RidingBehaviour<HoverSettings, HoverState> {
vehicle: PokemonEntity,
driver: Player
): Float {
return if (state.isBoosting.get()) 1.1f else 1.0f
return 1.0f
}
override fun useAngVelSmoothing(
@@ -433,107 +466,66 @@ class HoverSettings : RidingBehaviourSettings {
override val key = HoverBehaviour.KEY
override val stats = mutableMapOf<RidingStat, IntRange>()
var infiniteStamina: Expression? = null
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
var canJump: Expression? = null
var speedExpr: Expression = "q.get_ride_stats('SPEED', 'AIR', 0.65, 0.3)".asExpression()
private set
var stamReplenishTimeSeconds: Expression? = null
// 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)))".asExpression()
private set
// Speed in block per second
var speedExpr: Expression? = null
// 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
// Acceleration in number of seconds to top speed
var accelerationExpr: Expression? = null
//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
// Amount of seconds between boosts
var staminaExpr: Expression? = null
private set
// Blocks before stamina drain
var jumpExpr: Expression? = null
private set
// Turn rate in degrees per second when stationary
var handlingExpr: Expression? = null
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)
buffer.writeRidingStats(stats)
rideSounds.encode(buffer)
buffer.writeNullableExpression(infiniteStamina)
buffer.writeNullableExpression(canJump)
buffer.writeNullableExpression(stamReplenishTimeSeconds)
buffer.writeNullableExpression(speedExpr)
buffer.writeNullableExpression(accelerationExpr)
buffer.writeNullableExpression(staminaExpr)
buffer.writeNullableExpression(jumpExpr)
buffer.writeNullableExpression(handlingExpr)
buffer.writeExpression(speedExpr)
buffer.writeExpression(accelerationExpr)
buffer.writeExpression(staminaExpr)
buffer.writeExpression(jumpExpr)
buffer.writeExpression(handlingExpr)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
stats.putAll(buffer.readRidingStats())
rideSounds = RideSoundSettingsList.decode(buffer)
infiniteStamina = buffer.readNullableExpression()
canJump = buffer.readNullableExpression()
stamReplenishTimeSeconds = buffer.readNullableExpression()
speedExpr = buffer.readNullableExpression()
accelerationExpr = buffer.readNullableExpression()
staminaExpr = buffer.readNullableExpression()
jumpExpr = buffer.readNullableExpression()
handlingExpr = buffer.readNullableExpression()
speedExpr = buffer.readExpression()
accelerationExpr = buffer.readExpression()
staminaExpr = buffer.readExpression()
jumpExpr = buffer.readExpression()
handlingExpr = buffer.readExpression()
}
}
class HoverState : RidingBehaviourState() {
var speed = ridingState(0.0, Side.CLIENT)
var isBoosting = ridingState(false, Side.BOTH)
var boostVec = ridingState(Vec3.ZERO, Side.CLIENT)
var boostTicks = ridingState(0, Side.CLIENT)
var tooHigh = ridingState(false, Side.CLIENT)
override fun encode(buffer: FriendlyByteBuf) {
super.encode(buffer)
buffer.writeDouble(speed.get())
buffer.writeBoolean(isBoosting.get())
buffer.writeBoolean(tooHigh.get())
}
override fun decode(buffer: FriendlyByteBuf) {
super.decode(buffer)
speed.set(buffer.readDouble(), forced = true)
isBoosting.set(buffer.readBoolean(), forced = true)
tooHigh.set(buffer.readBoolean(), forced = true)
}
override fun reset() {
super.reset()
speed.set(0.0, forced = true)
isBoosting.set(false, forced = true)
boostVec.set(Vec3.ZERO, forced = true)
boostTicks.set(0, forced = true)
tooHigh.set(false, forced = true)
}
override fun copy() = HoverState().also {
it.rideVelocity.set(this.rideVelocity.get(), forced = true)
it.stamina.set(this.stamina.get(), forced = true)
it.isBoosting.set(this.isBoosting.get(), forced = true)
it.boostVec.set(this.boostVec.get(), forced = true)
it.boostTicks.set(this.boostTicks.get(), forced = true)
it.tooHigh.set(this.tooHigh.get(), forced = true)
}
override fun shouldSync(previous: RidingBehaviourState): Boolean {
if (previous !is HoverState) return false
if (previous.isBoosting.get() != isBoosting.get()) return true
return super.shouldSync(previous)
}
}
@@ -11,35 +11,19 @@ package com.cobblemon.mod.common.api.riding.behaviour.types.air
import com.bedrockk.molang.Expression
import com.bedrockk.molang.runtime.MoLangMath.lerp
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonRideSettings
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourSettings
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourState
import com.cobblemon.mod.common.api.riding.behaviour.Side
import com.cobblemon.mod.common.api.riding.behaviour.ridingState
import com.cobblemon.mod.common.api.riding.behaviour.*
import com.cobblemon.mod.common.api.riding.posing.PoseOption
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.blockPositionsAsListRounded
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.math.geometry.toRadians
import com.cobblemon.mod.common.util.readNullableExpression
import com.cobblemon.mod.common.util.readRidingStats
import com.cobblemon.mod.common.util.resolveDouble
import com.cobblemon.mod.common.util.toVec3d
import com.cobblemon.mod.common.util.writeNullableExpression
import com.cobblemon.mod.common.util.writeRidingStats
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
import net.minecraft.client.Minecraft
import com.cobblemon.mod.common.util.*
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.RegistryFriendlyByteBuf
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.config.CobblemonConfig
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
@@ -47,7 +31,7 @@ import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
import net.minecraft.world.phys.shapes.Shapes
import org.joml.Vector3f
import kotlin.math.*
class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
companion object {
@@ -60,9 +44,6 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
return RidingStyle.AIR
}
val globalJet: JetSettings
get() = CobblemonRideSettings.jet
val poseProvider = PoseProvider<JetSettings, JetState>(PoseType.HOVER)
.with(PoseOption(PoseType.FLY) { _, state, _ -> state.rideVelocity.get().z > 0.1 })
@@ -86,65 +67,35 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
}
override fun speed(settings: JetSettings, state: JetState, 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)
// //Reduce stamina unless stamina is infinite
// if (!vehicle.runtime.resolveBoolean(settings.infiniteStamina)) {
// //Calculate stamina loss due to speed
// //At max speed it will tick down 0.1 a second so the stamina will last ten seconds
// //There has got to be a better way to express this equation. It interpolates between 0.5 and 1.0
// var staminaRate = (RidingBehaviour.scaleToRange(state.rideVelocity.get().length(), minSpeed, topSpeed))
//
// //interpolate between 0.25 and 1.0 so that you always have at least a min of 0.25 stam loss
// staminaRate = 0.25 + (0.75 * staminaRate.pow(3))
//
// //Calculate stamina loss in seconds achievable at top speed
// val staminaLoss = staminaRate * (1.0 / (20.0 * staminaStat))
// state.stamina.set(max(state.stamina.get() - staminaLoss, 0.0).toFloat())
// } else {
// state.stamina.set(1.0f)
// }
return state.rideVelocity.get().length().toFloat()
}
override fun tick(settings: JetSettings, state: JetState, vehicle: PokemonEntity, driver: Player, input: Vec3) {
if(vehicle.level().isClientSide) {
handleBoosting(state)
tickStamina(settings, state, vehicle)
}
}
fun handleBoosting(
state: JetState,
) {
//If the forward key is not held then it cannot be boosting
val boostKeyPressed = Minecraft.getInstance().options.keySprint.isDown()
if(state.stamina.get() != 0.0f && boostKeyPressed) {
if (state.stamina.get() >= 0.0f) {
//If on the previous tick the boost key was held then don't change if the ride is boosting
if(state.boostIsToggleable.get()) {
//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(false)
}
} else {
//Turn off boost and reset boost params
state.boostIsToggleable.set(true)
state.boosting.set(false)
state.canSpeedBurst.set(true)
}
}
fun tickStamina(
settings: JetSettings,
state: JetState,
vehicle: PokemonEntity,
) {
val stam = state.stamina.get()
var newStam = stam
val stamDrainRate = (1.0f / vehicle.runtime.resolveDouble(settings.staminaExpr ?: globalJet.staminaExpr!!)).toFloat() / 20.0f
if (state.boosting.get()) {
newStam = max(0.0f,stam - stamDrainRate * 1.5f)
} else {
newStam = max(0.0f,stam - stamDrainRate)
}
// If out of stamina then increase noStamTickCnt
if (newStam == 0.0f) {
state.noStamTickCnt.set(state.noStamTickCnt.get() + 1)
} else {
state.noStamTickCnt.get()
}
state.stamina.set(newStam)
}
override fun rotation(
settings: JetSettings,
state: JetState,
@@ -161,26 +112,26 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
driver: Player,
input: Vec3
): Vec3 {
var upForce = 0.0
var forwardForce = 0.0
val controller = (driver as? OrientationControllable)?.orientationController
if (controller == null || controller.orientation == null) return Vec3.ZERO
//Calculate ride space velocity
calculateRideSpaceVel(settings, state, vehicle, driver)
// The downward force used to encourage players to stop flying upside down.
val maxDownwardForceTickCnt = 20.0
val extraDownwardForce = if(state.stamina.get() == 0.0f) -0.3 * (state.noStamTickCnt.get() / maxDownwardForceTickCnt).coerceIn(0.0, 1.0) else 0.0 // 6 blocks a second downward
//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
}
// Convert the local velocity vector into a world vector
val localVelVec = Vector3f(
state.rideVelocity.get().x.toFloat(),
(state.rideVelocity.get().y).toFloat(),
state.rideVelocity.get().z.toFloat() * -1.0f // Flip the z axis to make this left handed? orr.. right handed? idk, flip it though
)
var worldVelVec = localVelVec.mul(controller.orientation).toVec3d().yRot(vehicle.yRot.toRadians()) // Unrotate preemptively as this vector gets rotate later down the line in MC logic.
worldVelVec = worldVelVec.add(0.0, extraDownwardForce, 0.0) // Add the stamina depletion force to bring the ride down.
//If stamina has run out then initiate forced glide down.
upForce = if (state.stamina.get() > 0.0) upForce else -0.7
return worldVelVec
val velocity = Vec3(0.0, upForce, forwardForce)
return velocity
}
/*
@@ -192,36 +143,13 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
vehicle: PokemonEntity,
driver: Player
) {
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr ?: globalJet.speedExpr!!) / 20.0
val accel = topSpeed / (vehicle.runtime.resolveDouble(settings.accelerationExpr ?: globalJet.accelerationExpr!!) * 20.0)
val deccel = vehicle.runtime.resolveDouble(settings.deccelRate ?: globalJet.deccelRate!!)//0.005
val minSpeed = topSpeed * vehicle.runtime.resolveDouble(settings.minSpeedFactor ?: globalJet.minSpeedFactor!!)
val speed = state.rideVelocity.get().z
val boostMult = vehicle.runtime.resolveDouble(settings.jumpExpr ?: globalJet.jumpExpr!!)
val boostTopSpeed = topSpeed * boostMult
val boostAccel = accel * boostMult
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 (state.stamina.get() == 0.0f) {
// 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 - (deccel * 1.3), 0.3 * 0.5)
)
)
}
else if (state.boosting.get() && speed < boostTopSpeed) {
state.rideVelocity.set(
Vec3(
state.rideVelocity.get().x,
state.rideVelocity.get().y,
min(state.rideVelocity.get().z + (boostAccel), boostTopSpeed)
)
)
} else if ((speed < minSpeed) || (driver.zza > 0.0 && speed < topSpeed)) {
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(-(RidingBehaviour.scaleToRange(speed, minSpeed, topSpeed)) + 1, 0.0)
state.rideVelocity.set(
@@ -231,31 +159,27 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
min(state.rideVelocity.get().z + (accel * accelMod), topSpeed)
)
)
} else if (driver.zza < 0.0 && speed > minSpeed) {
// Decelerate currently always a constant half of max acceleration.
} 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 - (deccel), minSpeed)
max(state.rideVelocity.get().z - ((accel) / 4), minSpeed)
)
)
} else if (speed > topSpeed) {
} else if (driver.zza < 0.0 && speed > minSpeed) {
//modify deccel to be slower when at closer speeds to minimum speed
val deccelMod = max((RidingBehaviour.scaleToRange(speed, minSpeed, topSpeed) - 1).pow(2) * 4, 0.1)
//Decelerate currently always a constant half of max acceleration.
state.rideVelocity.set(
state.rideVelocity.get().scale(0.98)
Vec3(
state.rideVelocity.get().x,
state.rideVelocity.get().y,
max(state.rideVelocity.get().z - ((accel * deccelMod) / 2), minSpeed)
)
)
}
/****************************************************************************
* Kill strafing velocities carried over from controller transition. Don't
* kill immediately to give some momentum to transitions.
***************************************************************************/
state.rideVelocity.set(Vec3(
lerp(state.rideVelocity.get().x, 0.0, 0.03),
lerp(state.rideVelocity.get().y, 0.0, 0.03),
state.rideVelocity.get().z
))
}
override fun angRollVel(
@@ -272,7 +196,7 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
val cappedDeltaTime = min(deltaTime, 0.2)
//Get handling in degrees per second
val yawRotRate = vehicle.runtime.resolveDouble(settings.handlingYawExpr ?: globalJet.handlingYawExpr!!)
val yawRotRate = vehicle.runtime.resolveDouble(settings.handlingYawExpr)
//Base the change off of deltatime.
var handlingYaw = yawRotRate * (cappedDeltaTime)
@@ -352,10 +276,11 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
state.currMouseYForce.set((state.currMouseYForce.get() + (0.0015 * mouseY)).coerceIn(-1.0, 1.0))
//Get handling in degrees per second
val handlingDebuff = if(state.stamina.get() == 0.0f) 0.5 else 1.0
var handling = vehicle.runtime.resolveDouble(settings.handlingExpr ?: globalJet.handlingExpr!!) * handlingDebuff
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()
@@ -406,12 +331,13 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
state.currMouseYForce.set((state.currMouseYForce.get() + (0.0015 * mouseY)).coerceIn(-1.0, 1.0))
//Get handling in degrees per second
val handlingDebuff = if(state.stamina.get() == 0.0f) 0.5 else 1.0
var handling = vehicle.runtime.resolveDouble(settings.handlingExpr ?: globalJet.handlingExpr!!) * handlingDebuff
var handling = vehicle.runtime.resolveDouble(settings.handlingExpr)
//convert it to delta time
handling *= (cappedDeltaTime)
//apply stamina debuff if applicable
handling *= if (state.stamina.get() > 0.0) 1.0 else 0.5
val poke = driver.vehicle as? PokemonEntity
@@ -483,12 +409,15 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
vehicle: PokemonEntity,
driver: Player
): Float {
val maxDownwardForceTickCnt = 20.0f
return when {
state.noStamTickCnt.get() != 0 -> 1f - 0.2f*(state.noStamTickCnt.get() / maxDownwardForceTickCnt).coerceIn(0.0f,1.0f)
state.boosting.get() -> 1.2f
else -> 1.0f
}
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 = RidingBehaviour.scaleToRange(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: JetSettings, state: JetState, vehicle: PokemonEntity): Boolean {
@@ -561,70 +490,60 @@ class JetSettings : RidingBehaviourSettings {
override val key = JetBehaviour.KEY
override val stats = mutableMapOf<RidingStat, IntRange>()
var gravity: Expression? = null
var gravity: Expression = "0".asExpression()
private set
var deccelRate: Expression? = null
var minSpeed: Expression = "1.2".asExpression()
private set
// Mult to top speed in order to derive minSpeed
var minSpeedFactor: Expression? = null
private set
var handlingYawExpr: Expression? = null
var handlingYawExpr: Expression = "q.get_ride_stats('SKILL', 'AIR', 50.0, 25.0)".asExpression()
private set
// Make configurable by json
var infiniteStamina: Expression? = null
var infiniteStamina: Expression = "false".asExpression()
private set
// Boost power. Mult for top speed and accel while boosting
var jumpExpr: Expression? = null
var jumpExpr: Expression = "q.get_ride_stats('JUMP', 'AIR', 300.0, 128.0)".asExpression()
private set
// Turn rate in degrees per second
var handlingExpr: Expression? = null
var handlingExpr: Expression = "q.get_ride_stats('SKILL', 'AIR', 140.0, 20.0)".asExpression()
private set
// Top Speed in blocks per second
var speedExpr: Expression? = null
var speedExpr: Expression = "q.get_ride_stats('SPEED', 'AIR', 1.8, 1.0)".asExpression()
private set
// Seconds to get to top speed
var accelerationExpr: Expression? = null
var accelerationExpr: Expression =
"q.get_ride_stats('ACCELERATION', 'AIR', (1.0 / (20.0 * 1.0)), (1.0 / (20.0 * 5.0)))".asExpression()
private set
// Time in seconds to drain full bar of stamina flying
var staminaExpr: Expression? = null
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)
buffer.writeRidingStats(stats)
rideSounds.encode(buffer)
buffer.writeNullableExpression(gravity)
buffer.writeNullableExpression(deccelRate)
buffer.writeNullableExpression(minSpeedFactor)
buffer.writeNullableExpression(handlingYawExpr)
buffer.writeNullableExpression(infiniteStamina)
buffer.writeNullableExpression(jumpExpr)
buffer.writeNullableExpression(handlingExpr)
buffer.writeNullableExpression(speedExpr)
buffer.writeNullableExpression(accelerationExpr)
buffer.writeNullableExpression(staminaExpr)
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) {
stats.putAll(buffer.readRidingStats())
rideSounds = RideSoundSettingsList.decode(buffer)
gravity = buffer.readNullableExpression()
deccelRate = buffer.readNullableExpression()
minSpeedFactor = buffer.readNullableExpression()
handlingYawExpr = buffer.readNullableExpression()
infiniteStamina = buffer.readNullableExpression()
jumpExpr = buffer.readNullableExpression()
handlingExpr = buffer.readNullableExpression()
speedExpr = buffer.readNullableExpression()
accelerationExpr = buffer.readNullableExpression()
staminaExpr = buffer.readNullableExpression()
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()
}
}
@@ -632,26 +551,16 @@ class JetState : RidingBehaviourState() {
var currSpeed = ridingState(0.0, Side.CLIENT)
var currMouseXForce = ridingState(0.0, Side.CLIENT)
var currMouseYForce = ridingState(0.0, Side.CLIENT)
var boosting = ridingState(false, Side.BOTH)
var boostIsToggleable = ridingState(false, Side.BOTH)
var canSpeedBurst = ridingState(false, Side.BOTH)
var noStamTickCnt = ridingState(0, Side.CLIENT) // Value that is increased for every tick you are out of stamina
override fun encode(buffer: FriendlyByteBuf) {
super.encode(buffer)
buffer.writeDouble(currSpeed.get())
buffer.writeFloat(stamina.get())
buffer.writeBoolean(boosting.get())
buffer.writeBoolean(boostIsToggleable.get())
buffer.writeBoolean(canSpeedBurst.get())
}
override fun decode(buffer: FriendlyByteBuf) {
super.decode(buffer)
currSpeed.set(buffer.readDouble(), forced = true)
boosting.set(buffer.readBoolean(), forced = true)
boostIsToggleable.set(buffer.readBoolean(), forced = true)
canSpeedBurst.set(buffer.readBoolean(), forced = true)
}
override fun reset() {
@@ -659,19 +568,12 @@ class JetState : RidingBehaviourState() {
currSpeed.set(0.0, forced = true)
currMouseXForce.set(0.0, forced = true)
currMouseYForce.set(0.0, forced = true)
boosting.set(false, forced = true)
boostIsToggleable.set(false, forced = true)
canSpeedBurst.set(true, forced = true)
noStamTickCnt.set(0, forced = true)
}
override fun copy() = JetState().also {
it.currSpeed.set(currSpeed.get(), forced = true)
it.stamina.set(stamina.get(), forced = true)
it.rideVelocity.set(rideVelocity.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)
}
override fun shouldSync(previous: RidingBehaviourState): Boolean {
@@ -10,7 +10,6 @@ package com.cobblemon.mod.common.api.riding.behaviour.types.air
import com.bedrockk.molang.Expression
import com.bedrockk.molang.runtime.MoLangMath.lerp
import com.cobblemon.mod.common.CobblemonRideSettings
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.*
import com.cobblemon.mod.common.api.riding.posing.PoseOption
@@ -41,8 +40,6 @@ class RocketBehaviour : RidingBehaviour<RocketSettings, RocketState> {
}
override val key = KEY
val globalRocket: RocketSettings
get() = CobblemonRideSettings.rocket
override fun getRidingStyle(settings: RocketSettings, state: RocketState): RidingStyle {
return RidingStyle.AIR
@@ -87,59 +84,30 @@ class RocketBehaviour : RidingBehaviour<RocketSettings, RocketState> {
driver: Player
): Float {
// Use this as a "tick" function and check to see if the driver is "boosting"
if (vehicle.level().isClientSide) {
handleBoosting(settings, state, vehicle, driver)
tickStamina(settings, state, vehicle, driver)
}
if(vehicle.level().isClientSide) {
return state.rideVelocity.get().length().toFloat()
}
//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()
fun handleBoosting(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity,
driver: Player
) {
//If the forward key is not held then it cannot be boosting
if(Minecraft.getInstance().options.keyUp.isDown() && state.stamina.get() != 0.0f) {
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)
}
}
} else {
//Turn off boost and reset boost params
state.boostIsToggleable.set(true)
state.boosting.set(false)
state.canSpeedBurst.set(true)
}
fun tickStamina(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity,
driver: Player
) {
val stam = state.stamina.get()
if (vehicle.runtime.resolveBoolean(settings.infiniteStamina ?: globalRocket.infiniteStamina!!)) {
return
}
// Grab the boost time in seconds and convert to ticks. Then calculate the drain rate as inversely
// proportional to the number of ticks of boost thus making a full boost take x ticks
// in short: "Stamina drains slower at higher values and also replenishes slower"
val boostTime = vehicle.runtime.resolveDouble(settings.staminaExpr ?: globalRocket.staminaExpr!!) * 20.0f
val stamDrainRate = (1.0f / boostTime).toFloat()
val newStam = if(state.boosting.get() || driver.jumping) max(0.0f,stam - stamDrainRate) else stam
state.stamina.set(newStam)
return state.rideVelocity.get().length().toFloat()
}
override fun updatePassengerRotation(
@@ -185,23 +153,22 @@ class RocketBehaviour : RidingBehaviour<RocketSettings, RocketState> {
): Vec2 {
var newMomentum = state.turnMomentum.get().toDouble()
// Degrees per tick
val maxTurnMomentum = vehicle.runtime.resolveDouble(settings.maxTurnRate ?: globalRocket.maxTurnRate!!) / 20.0f
// Turn rate increase per tick. Based off number of seconds to get to max turn rate
val turningAcceleration = maxTurnMomentum / (vehicle.runtime.resolveDouble(settings.handlingExpr ?: globalRocket.handlingExpr!!) * 20.0f)
// 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 boostHandlingMod = vehicle.runtime.resolveDouble(settings.boostHandlingMod ?: globalRocket.boostHandlingMod!!)
val boostMaxTurnMomentum = maxTurnMomentum * boostHandlingMod
val boostTurnInput = turnInput * boostHandlingMod
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)
newMomentum = newMomentum.coerceIn(-boostMaxTurnMomentum, boostMaxTurnMomentum)
}
} else {
if(driver.xxa == 0.0f) { //If no turning input then lerp to 0
@@ -237,9 +204,8 @@ class RocketBehaviour : RidingBehaviour<RocketSettings, RocketState> {
vehicle: PokemonEntity,
driver: Player
): Vec3 {
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr ?: globalRocket.speedExpr!!)
val accel = vehicle.runtime.resolveDouble(settings.accelerationExpr ?: globalRocket.accelerationExpr!!)
val jump = vehicle.runtime.resolveDouble(settings.jumpExpr ?: globalRocket.jumpExpr!!)
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr) * 2
val accel = vehicle.runtime.resolveDouble(settings.accelerationExpr) * 0.5 * 2
var newVelocity = vehicle.deltaMovement
@@ -247,10 +213,8 @@ class RocketBehaviour : RidingBehaviour<RocketSettings, RocketState> {
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.boosting.get()) {
//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
@@ -262,16 +226,14 @@ class RocketBehaviour : RidingBehaviour<RocketSettings, RocketState> {
newVelocity.y,
(newVelocity.z + (accel * forwardInput.toDouble())))
} else if ((state.boosting.get())) {
val boostMod = vehicle.runtime.resolveDouble(settings.boostSpeedMod ?: globalRocket.boostSpeedMod!!)
val boostSpeed = topSpeed * boostMod
val boostAccel = if(newVelocity.length() < boostSpeed) accel * (boostMod/1.5) else 0.0
} 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.5f
state.stamina.set(max(0.0f,state.stamina.get() - 0.05f))
burst = 0.25f
state.canSpeedBurst.set(false)
}
@@ -282,13 +244,12 @@ class RocketBehaviour : RidingBehaviour<RocketSettings, RocketState> {
}
//Vertical movement based on driver input.
val maxVertSpeed = jump
val boostMaxVertSpeed = 0.5f * 0.25f
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 > (-boostMaxVertSpeed)) {
if (state.boosting.get() && !driver.jumping && vehicle.deltaMovement.y > (-maxVertSpeed * 0.25)) {
vertInput = -0.7
newVelocity = Vec3(
newVelocity.x,
@@ -296,33 +257,28 @@ class RocketBehaviour : RidingBehaviour<RocketSettings, RocketState> {
newVelocity.z)
} else if(state.boosting.get() && vehicle.deltaMovement.y < maxVertSpeed) {
vertInput = when {
driver.jumping -> 1.0
driver.jumping -> 2.0
else -> 0.0
}
newVelocity = Vec3(
newVelocity.x,
(newVelocity.y + (accel * vertInput)),
(newVelocity.y + (accel * 0.5 * vertInput)),
newVelocity.z)
} else {
if (driver.jumping && !(vehicle.deltaMovement.y > maxVertSpeed && (newVelocity.y.sign.toFloat() > 0.0))) {
// More force if traveling downwards to allow for quicker fall stops
vertInput = (if (state.stamina.get() == 0.0f) -1.0f else if(newVelocity.y < 0) jump.toFloat() * 3.0f else jump.toFloat()).toDouble()
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)
} else {
/******************************************************
* Gravity logic
*****************************************************/
val gravity = (9.8 / ( 20.0)) * 0.2 * 0.6
val terminalVel = 2.0
val fallingForce = gravity
newVelocity = Vec3(newVelocity.x, max(newVelocity.y - fallingForce, -terminalVel), newVelocity.z)
}
}
@@ -405,9 +361,11 @@ class RocketBehaviour : RidingBehaviour<RocketSettings, RocketState> {
driver: Player
): Float {
if (state.boosting.get()) {
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr ?: globalRocket.speedExpr!!)
val boostMod = vehicle.runtime.resolveDouble(settings.boostSpeedMod ?: globalRocket.boostSpeedMod!!)
val normalizedBoostSpeed = RidingBehaviour.scaleToRange(state.rideVelocity.get().length(), topSpeed, topSpeed * boostMod)
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 = RidingBehaviour.scaleToRange(state.rideVelocity.get().length(), topSpeed, topSpeed * 3)
return 1.0f + normalizedBoostSpeed.pow(2).toFloat() * 0.2f
} else {
return 1.0f
@@ -479,18 +437,6 @@ class RocketBehaviour : RidingBehaviour<RocketSettings, RocketState> {
return false
}
override fun damageOnCollision(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity,
impactVec: Vec3
): Boolean {
if (!state.boosting.get()) return false
val impactSpeed = impactVec.horizontalDistance().toFloat() * 10f
return vehicle.causeFallDamage(impactSpeed, 1f, vehicle.damageSources().flyIntoWall())
}
override fun getRideSounds(
settings: RocketSettings,
state: RocketState,
@@ -506,67 +452,51 @@ class RocketSettings : RidingBehaviourSettings {
override val key = RocketBehaviour.KEY
override val stats = mutableMapOf<RidingStat, IntRange>()
var infiniteStamina: Expression? = null
private set
// Boost multiplier for speed
var boostSpeedMod: Expression? = null
private set
// Boost multiplier for handling
var boostHandlingMod: Expression? = null
private set
// Max turn rate in degrees per second
var maxTurnRate: Expression? = null
private set
var speedExpr: Expression? = null
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? = null
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
// air time in seconds
var staminaExpr: Expression? = null
// 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
var jumpExpr: Expression? = null
//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
// How long it takes to get to max turn rate from rest (in seconds)
var handlingExpr: Expression? = null
// 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)
buffer.writeRidingStats(stats)
rideSounds.encode(buffer)
buffer.writeNullableExpression(infiniteStamina)
buffer.writeNullableExpression(boostSpeedMod)
buffer.writeNullableExpression(boostHandlingMod)
buffer.writeNullableExpression(maxTurnRate)
buffer.writeNullableExpression(speedExpr)
buffer.writeNullableExpression(accelerationExpr)
buffer.writeNullableExpression(staminaExpr)
buffer.writeNullableExpression(jumpExpr)
buffer.writeNullableExpression(handlingExpr)
buffer.writeExpression(speedExpr)
buffer.writeExpression(accelerationExpr)
buffer.writeExpression(staminaExpr)
buffer.writeExpression(jumpExpr)
buffer.writeExpression(handlingExpr)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
stats.putAll(buffer.readRidingStats())
rideSounds = RideSoundSettingsList.decode(buffer)
infiniteStamina = buffer.readNullableExpression()
boostSpeedMod = buffer.readNullableExpression()
boostHandlingMod = buffer.readNullableExpression()
maxTurnRate = buffer.readNullableExpression()
speedExpr = buffer.readNullableExpression()
accelerationExpr = buffer.readNullableExpression()
staminaExpr = buffer.readNullableExpression()
jumpExpr = buffer.readNullableExpression()
handlingExpr = buffer.readNullableExpression()
speedExpr = buffer.readExpression()
accelerationExpr = buffer.readExpression()
staminaExpr = buffer.readExpression()
jumpExpr = buffer.readExpression()
handlingExpr = buffer.readExpression()
}
}
class RocketState : RidingBehaviourState() {
@@ -387,10 +387,9 @@ open class CompositeSettings : RidingBehaviourSettings {
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeRidingStats(stats)
buffer.writeResourceLocation(key)
buffer.writeResourceLocation(transitionStrategy)
buffer.writeResourceLocation(defaultBehaviour.key)
defaultBehaviour.encode(buffer)
buffer.writeResourceLocation(alternateBehaviour.key)
alternateBehaviour.encode(buffer)
}
@@ -41,10 +41,6 @@ interface CompositeRidingStrategy<T : CompositeSettings> {
toState: RidingBehaviourState,
toSettings: RidingBehaviourSettings
) {
if (vehicle.level().isClientSide) {
val fromSettings = if (settings.defaultBehaviour == toSettings) settings.alternateBehaviour else settings.defaultBehaviour
}
toState.stamina.set(fromState.stamina.get())
toState.rideVelocity.set(fromState.rideVelocity.get())
fromState.reset()
@@ -9,34 +9,15 @@
package com.cobblemon.mod.common.api.riding.behaviour.types.land
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.CobblemonRideSettings
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourSettings
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourState
import com.cobblemon.mod.common.api.riding.behaviour.Side
import com.cobblemon.mod.common.api.riding.behaviour.ridingState
import com.cobblemon.mod.common.api.riding.behaviour.*
import com.cobblemon.mod.common.api.riding.posing.PoseOption
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.blockPositionsAsListRounded
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.readNullableExpression
import com.cobblemon.mod.common.util.readRidingStats
import com.cobblemon.mod.common.util.resolveBoolean
import com.cobblemon.mod.common.util.resolveDouble
import com.cobblemon.mod.common.util.resolveFloat
import com.cobblemon.mod.common.util.writeNullableExpression
import com.cobblemon.mod.common.util.writeRidingStats
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
import kotlin.math.pow
import kotlin.math.sign
import kotlin.math.sqrt
import com.cobblemon.mod.common.util.*
import net.minecraft.core.Direction
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.RegistryFriendlyByteBuf
@@ -49,6 +30,7 @@ import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
import net.minecraft.world.phys.shapes.Shapes
import kotlin.math.*
class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
companion object {
@@ -56,8 +38,6 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
}
override val key = KEY
val globalHorse: HorseSettings
get() = CobblemonRideSettings.horse
override fun getRidingStyle(settings: HorseSettings, state: HorseState): RidingStyle {
return RidingStyle.LAND
@@ -101,19 +81,11 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
vehicle: PokemonEntity,
driver: Player
): Float {
return state.rideVelocity.get().length().toFloat()
}
override fun tick(
settings: HorseSettings,
state: HorseState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
) {
handleSprinting(state, driver)
// Use this as a "tick" function and calculate sprinting and inAir state here
handleSprinting(settings, state, vehicle, driver)
inAirCheck(state, vehicle)
tickStamina(settings, state, vehicle)
tickStamina(settings, state, vehicle, driver)
return state.rideVelocity.get().length().toFloat()
}
fun inAirCheck(
@@ -128,13 +100,16 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
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)
}
fun handleSprinting(
settings: HorseSettings,
state: HorseState,
vehicle: PokemonEntity,
driver: Player
) {
if (state.sprinting.get()) {
@@ -150,15 +125,11 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
settings: HorseSettings,
state: HorseState,
vehicle: PokemonEntity,
driver: Player
) {
val stam = state.stamina.get()
if (vehicle.runtime.resolveBoolean(settings.infiniteStamina ?: globalHorse.infiniteStamina!!)) {
return
}
var newStam = stam
val stamDrainRate = (1.0f / vehicle.runtime.resolveDouble(settings.staminaExpr ?: globalHorse.staminaExpr!!)).toFloat() / 20.0f
val stamDrainRate = (1.0f / vehicle.runtime.resolveDouble(settings.staminaExpr)).toFloat() / 20.0f
if (state.sprinting.get()) {
newStam = max(0.0f,stam - stamDrainRate)
@@ -208,10 +179,10 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
vehicle: PokemonEntity,
driver: LivingEntity
): Float {
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr ?: globalHorse.speedExpr!!)
val handling = vehicle.runtime.resolveDouble(settings.handlingExpr ?: globalHorse.handlingExpr!!)
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
val handling = vehicle.runtime.resolveDouble(settings.handlingExpr)
val walkHandlingBoost = 5
val maxYawDiff = vehicle.runtime.resolveFloat(settings.lookYawLimit ?: globalHorse.lookYawLimit!!)
val maxYawDiff = vehicle.runtime.resolveFloat(settings.lookYawLimit)
// Normalize the current rotation diff
val rotDiff = Mth.wrapDegrees(driver.yRot - vehicle.yRot).coerceIn(-maxYawDiff,maxYawDiff)
@@ -255,37 +226,24 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
vehicle: PokemonEntity,
driver: Player
): Vec3 {
val canSprint = vehicle.runtime.resolveBoolean(settings.canSprint ?: globalHorse.canSprint!!)
val canJump = vehicle.runtime.resolveBoolean(settings.canJump ?: globalHorse.canJump!!)
val jumpForce = vehicle.runtime.resolveDouble(settings.jumpExpr ?: globalHorse.jumpExpr!!) * 0.75
val rideTopSpeed = vehicle.runtime.resolveDouble(settings.speedExpr ?: globalHorse.speedExpr!!)
val canSprint = vehicle.runtime.resolveBoolean(settings.canSprint)
val canJump = vehicle.runtime.resolveBoolean(settings.canJump)
val jumpForce = vehicle.runtime.resolveDouble(settings.jumpExpr)
val rideTopSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
val walkSpeed = getWalkSpeed(vehicle)
val topSpeed = if(canSprint && state.sprinting.get()) rideTopSpeed else walkSpeed
val accel = topSpeed / (vehicle.runtime.resolveDouble(settings.accelerationExpr ?: globalHorse.accelerationExpr!!) * 20.0)
val accel = vehicle.runtime.resolveDouble(settings.accelerationExpr)
var activeInput = false
/******************************************************
* Gather the previous velocity and check for horizontal
* collisions
* Gather the previous velocity and set the horizontal
* fields of the velocity vector to the magnitude of the
* ones from deltaMovement. This is important as when a
* collision happens the Deltamovement will reflect it
* but rideVelocity won't.
*****************************************************/
val dmSpeed = vehicle.deltaMovement.length()
val rvSpeed = state.rideVelocity.get().length()
var newVelocity = state.rideVelocity.get() //.normalize().scale(vehicle.deltaMovement.horizontalDistance())
/******************************************************
* Gather the previous velocity and check for horizontal
* collisions
*****************************************************/
// if (dmSpeed > rvSpeed) {
// // align the velocity vector to be in local vehicle space
// newVelocity = vehicle.deltaMovement
// val yawAligned = Matrix3f().rotateY(-vehicle.yRot.toRadians())
// newVelocity = (newVelocity.toVector3f().mul(yawAligned)).toVec3d()
// }
if (vehicle.horizontalCollision) {
newVelocity = newVelocity.normalize().scale(vehicle.deltaMovement.length())
}
var newVelocity = Vec3(state.rideVelocity.get().x, 0.0, state.rideVelocity.get().z).normalize().scale(vehicle.deltaMovement.horizontalDistance())
newVelocity = newVelocity.add(0.0, state.rideVelocity.get().y, 0.0)
/******************************************************
* Speed up and slow down based on input
@@ -294,7 +252,7 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
// If on a tight turn then do not speed up past half of top speed in order to turn quicker
// Also determine how fast to be slowing down based on how far turned you are
val lookYawLimit = vehicle.runtime.resolveFloat(settings.lookYawLimit ?: globalHorse.lookYawLimit!!)
val lookYawLimit = vehicle.runtime.resolveFloat(settings.lookYawLimit)
val percOfMaxTurnSpeed = abs(Mth.wrapDegrees(driver.yRot - vehicle.yRot) / lookYawLimit) * 100.0f
val turnPercThresh = 0.0f
val s = min(((percOfMaxTurnSpeed - turnPercThresh) / (100.0f - turnPercThresh)).pow(1),1.0f)
@@ -324,14 +282,12 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
/******************************************************
* Gravity logic
*****************************************************/
if (!vehicle.onGround() && state.jumpTicks.get() <= 0) {
val gravity = (9.8 / ( 20.0)) * 0.2 * 0.6
val terminalVel = 2.0
newVelocity = Vec3(newVelocity.x, max(newVelocity.y - gravity, -terminalVel), newVelocity.z)
} else if(vehicle.onGround()) {
newVelocity = Vec3(newVelocity.x, 0.0, newVelocity.z)
}
if (vehicle.onGround()) { newVelocity = Vec3(newVelocity.x, 0.0, newVelocity.z) }
val gravity = (9.8 / ( 20.0)) * 0.2 * 0.6
val terminalVel = 2.0
val fallingForce = gravity
newVelocity = Vec3(newVelocity.x, max(newVelocity.y - fallingForce, -terminalVel), newVelocity.z)
/******************************************************
* Ground Friction
@@ -343,21 +299,8 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
/******************************************************
* Jump Logic
*****************************************************/
if (state.jumpTicks.get() > 0 || (state.jumpTicks.get() >= 0 && driver.jumping && vehicle.onGround() && canJump && driver.deltaMovement.y <= 0.1)) {
// Spread out jumpforce so that variable height jumps are possible
val jumpInputTicks = 6
if (driver.jumping && (state.jumpTicks.get() >= 0 && state.jumpTicks.get() < jumpInputTicks)) {
val appliedJumpForce = ((jumpForce*1.5) / jumpInputTicks) //* (1 - state.jumpTicks.get() / jumpInputTicks)
newVelocity = Vec3(newVelocity.x, newVelocity.y + appliedJumpForce, newVelocity.z)
state.jumpTicks.set(state.jumpTicks.get() + 1)
} else {
// Set delay before next jump is possible after hitting ground
val tickJumpDelay = 3
state.jumpTicks.set(-tickJumpDelay)
}
} else if (vehicle.onGround() && state.jumpTicks.get() < 0) {
// Tick off the delay once reaching the ground
state.jumpTicks.set(state.jumpTicks.get() + 1)
if (driver.jumping && vehicle.onGround() && canJump && driver.deltaMovement.y <= 0.1) {
newVelocity = Vec3(newVelocity.x, jumpForce, newVelocity.z)
}
//Zero out lateral velocity possibly picked up from a controller transition
@@ -536,94 +479,78 @@ class HorseSettings : RidingBehaviourSettings {
override val key = HorseBehaviour.KEY
override val stats = mutableMapOf<RidingStat, IntRange>()
var infiniteStamina: Expression? = null
private set
var canJump: Expression? = null
var canJump = "true".asExpression()
private set
var canSprint: Expression? = null
var canSprint = "true".asExpression()
private set
var lookYawLimit: Expression? = null
var lookYawLimit = "90.0f".asExpression()
private set
var speedExpr: Expression? = null
var speedExpr: Expression = "q.get_ride_stats('SPEED', 'LAND', 1.0, 0.4)".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? = null
var accelerationExpr: Expression =
"q.get_ride_stats('ACCELERATION', 'LAND', (1.0 / (20.0 * 0.8)), (1.0 / (20.0 * 3.0)))".asExpression()
private set
// Between 30 seconds and 10 seconds at the lowest when at full speed.
var staminaExpr: Expression? = null
var staminaExpr: Expression = "q.get_ride_stats('STAMINA', 'LAND', 30.0, 10.0)".asExpression()
private set
//Between a one block jump and a six block jump
var jumpExpr: Expression? = null
//Between a one block jump and a ten block jump
var jumpExpr: Expression = "q.get_ride_stats('JUMP', 'LAND', 1.5, 0.8)".asExpression()
private set
var handlingExpr: Expression? = null
var handlingExpr: Expression = "q.get_ride_stats('SKILL', 'LAND', 180.0, 40.0)".asExpression()
private set
var rideSounds: RideSoundSettingsList = RideSoundSettingsList()
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeRidingStats(stats)
rideSounds.encode(buffer)
buffer.writeNullableExpression(infiniteStamina)
buffer.writeNullableExpression(canJump)
buffer.writeNullableExpression(canSprint)
buffer.writeNullableExpression(lookYawLimit)
buffer.writeNullableExpression(speedExpr)
buffer.writeNullableExpression(accelerationExpr)
buffer.writeNullableExpression(staminaExpr)
buffer.writeNullableExpression(jumpExpr)
buffer.writeNullableExpression(handlingExpr)
buffer.writeNullableExpression(canJump)
buffer.writeNullableExpression(canSprint)
buffer.writeExpression(speedExpr)
buffer.writeExpression(accelerationExpr)
buffer.writeExpression(staminaExpr)
buffer.writeExpression(jumpExpr)
buffer.writeExpression(handlingExpr)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
stats.putAll(buffer.readRidingStats())
rideSounds = RideSoundSettingsList.decode(buffer)
infiniteStamina = buffer.readNullableExpression()
canJump = buffer.readNullableExpression()
canSprint = buffer.readNullableExpression()
lookYawLimit = buffer.readNullableExpression()
speedExpr = buffer.readNullableExpression()
accelerationExpr = buffer.readNullableExpression()
staminaExpr = buffer.readNullableExpression()
jumpExpr = buffer.readNullableExpression()
handlingExpr = buffer.readNullableExpression()
canJump = buffer.readNullableExpression()
canSprint = buffer.readNullableExpression()
speedExpr = buffer.readExpression()
accelerationExpr = buffer.readExpression()
staminaExpr = buffer.readExpression()
jumpExpr = buffer.readExpression()
handlingExpr = buffer.readExpression()
}
}
class HorseState : RidingBehaviourState() {
var sprinting = ridingState(false, Side.CLIENT)
var inAir = ridingState(false, Side.CLIENT)
var jumpTicks = ridingState(0, Side.CLIENT)
override fun encode(buffer: FriendlyByteBuf) {
super.encode(buffer)
buffer.writeBoolean(sprinting.get())
buffer.writeBoolean(inAir.get())
buffer.writeInt(jumpTicks.get())
}
override fun decode(buffer: FriendlyByteBuf) {
super.decode(buffer)
sprinting.set(buffer.readBoolean(), forced = true)
inAir.set(buffer.readBoolean(), forced = true)
jumpTicks.set(buffer.readInt(), forced = true)
}
override fun reset() {
super.reset()
sprinting.set(false, forced = true)
inAir.set(false, forced = true)
jumpTicks.set(0, forced = true)
}
override fun copy() = HorseState().also {
@@ -631,7 +558,6 @@ class HorseState : RidingBehaviourState() {
it.stamina.set(this.stamina.get(), forced = true)
it.sprinting.set(this.sprinting.get(), forced = true)
it.inAir.set(this.inAir.get(), forced = true)
it.jumpTicks.set(this.jumpTicks.get(), forced = true)
}
override fun shouldSync(previous: RidingBehaviourState): Boolean {
@@ -10,36 +10,18 @@ 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.CobblemonRideSettings
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.RidingBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourSettings
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourState
import com.cobblemon.mod.common.api.riding.behaviour.Side
import com.cobblemon.mod.common.api.riding.behaviour.SidedRidingState
import com.cobblemon.mod.common.api.riding.behaviour.ridingState
import com.cobblemon.mod.common.api.riding.behaviour.*
import com.cobblemon.mod.common.api.riding.posing.PoseOption
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.blockPositionsAsListRounded
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.*
import com.cobblemon.mod.common.util.math.geometry.toRadians
import com.cobblemon.mod.common.util.readNullableExpression
import com.cobblemon.mod.common.util.readRidingStats
import com.cobblemon.mod.common.util.resolveBoolean
import com.cobblemon.mod.common.util.resolveDouble
import com.cobblemon.mod.common.util.resolveFloat
import com.cobblemon.mod.common.util.writeNullableExpression
import com.cobblemon.mod.common.util.writeRidingStats
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
import kotlin.math.pow
import kotlin.math.sign
import net.minecraft.core.Direction
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.RegistryFriendlyByteBuf
@@ -50,6 +32,12 @@ 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 {
@@ -57,8 +45,6 @@ class MinekartBehaviour : RidingBehaviour<MinekartSettings, MinekartState> {
}
override val key = KEY
val globalMinekart: MinekartSettings
get() = CobblemonRideSettings.minekart
override fun getRidingStyle(settings: MinekartSettings, state: MinekartState): RidingStyle {
return RidingStyle.LAND
@@ -98,7 +84,7 @@ class MinekartBehaviour : RidingBehaviour<MinekartSettings, MinekartState> {
driver: Player
): Float {
val boostMax = vehicle.runtime.resolveFloat(settings.boostLimit ?: globalMinekart.boostLimit!!)
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
@@ -156,8 +142,8 @@ class MinekartBehaviour : RidingBehaviour<MinekartSettings, MinekartState> {
var newMomentum = state.turnMomentum.get().toDouble()
//TODO: maybe tie this to max turnspeeds rather than the turn acceleration
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr ?: globalMinekart.speedExpr!!)
val turningAcceleration = (vehicle.runtime.resolveDouble(settings.handlingExpr ?: globalMinekart.handlingExpr!!) * 1.5 / 20.0f) / 20.0f * 4.0
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
@@ -216,9 +202,9 @@ class MinekartBehaviour : RidingBehaviour<MinekartSettings, MinekartState> {
): Vec3 {
// Check to see if the ride should be walking or sprinting
val rideTopSpeed = vehicle.runtime.resolveDouble(settings.speedExpr ?: globalMinekart.speedExpr!!) * 0.6 * 2.0
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 ?: globalMinekart.accelerationExpr!!) * 0.3 * 3 * 2.0
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.
@@ -341,7 +327,7 @@ class MinekartBehaviour : RidingBehaviour<MinekartSettings, MinekartState> {
vehicle: PokemonEntity,
driver: Player
): Boolean {
return vehicle.runtime.resolveBoolean(settings.canJump ?: globalMinekart.canJump!!)
return vehicle.runtime.resolveBoolean(settings.canJump)
}
override fun setRideBar(
@@ -461,57 +447,45 @@ class MinekartSettings : RidingBehaviourSettings {
override val key = MinekartBehaviour.KEY
override val stats = mutableMapOf<RidingStat, IntRange>()
var infiniteStamina: Expression? = null
private set
var canJump: Expression? = null
var canJump = "true".asExpression()
private set
var boostLimit: Expression? = null
var boostLimit = "100.0f".asExpression()
private set
var speedExpr: Expression? = null
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? = null
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? = null
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? = null
var jumpExpr: Expression = "q.get_ride_stats('JUMP', 'LAND', 10.0, 1.0)".asExpression()
private set
var handlingExpr: Expression? = null
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)
buffer.writeRidingStats(stats)
rideSounds.encode(buffer)
buffer.writeNullableExpression(infiniteStamina)
buffer.writeNullableExpression(canJump)
buffer.writeNullableExpression(boostLimit)
buffer.writeNullableExpression(speedExpr)
buffer.writeNullableExpression(accelerationExpr)
buffer.writeNullableExpression(staminaExpr)
buffer.writeNullableExpression(jumpExpr)
buffer.writeNullableExpression(handlingExpr)
buffer.writeExpression(canJump)
buffer.writeExpression(boostLimit)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
stats.putAll(buffer.readRidingStats())
rideSounds = RideSoundSettingsList.decode(buffer)
infiniteStamina = buffer.readNullableExpression()
canJump = buffer.readNullableExpression()
boostLimit = buffer.readNullableExpression()
speedExpr = buffer.readNullableExpression()
accelerationExpr = buffer.readNullableExpression()
staminaExpr = buffer.readNullableExpression()
jumpExpr = buffer.readNullableExpression()
handlingExpr = buffer.readNullableExpression()
canJump = buffer.readExpression()
boostLimit = buffer.readExpression()
}
}
@@ -10,35 +10,18 @@ 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.CobblemonRideSettings
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.RidingBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourSettings
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourState
import com.cobblemon.mod.common.api.riding.behaviour.Side
import com.cobblemon.mod.common.api.riding.behaviour.SidedRidingState
import com.cobblemon.mod.common.api.riding.behaviour.ridingState
import com.cobblemon.mod.common.api.riding.behaviour.*
import com.cobblemon.mod.common.api.riding.posing.PoseOption
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.blockPositionsAsListRounded
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.*
import com.cobblemon.mod.common.util.math.geometry.toRadians
import com.cobblemon.mod.common.util.readNullableExpression
import com.cobblemon.mod.common.util.readRidingStats
import com.cobblemon.mod.common.util.resolveBoolean
import com.cobblemon.mod.common.util.resolveDouble
import com.cobblemon.mod.common.util.toVec3d
import com.cobblemon.mod.common.util.writeNullableExpression
import com.cobblemon.mod.common.util.writeRidingStats
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
import kotlin.math.sign
import net.minecraft.core.Direction
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.RegistryFriendlyByteBuf
@@ -50,6 +33,10 @@ import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
import net.minecraft.world.phys.shapes.Shapes
import org.joml.Matrix3f
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
import kotlin.math.sign
class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
companion object {
@@ -57,8 +44,6 @@ class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
}
override val key = KEY
val globalVehicle: VehicleSettings
get() = CobblemonRideSettings.vehicle
override fun getRidingStyle(settings: VehicleSettings, state: VehicleState): RidingStyle {
return RidingStyle.LAND
@@ -141,7 +126,7 @@ class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
var newMomentum = state.turnMomentum.get().toDouble()
// Grab turningAcceleration and divide 20 twice to get
val turningAcceleration = (vehicle.runtime.resolveDouble(settings.handlingExpr ?: globalVehicle.handlingExpr!!) * 1.5 / 20.0f) / 20.0f * 4.0
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
@@ -152,7 +137,7 @@ class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
if(state.drifting.get() || state.inAir.get()) {
if( newMomentum < driftMaxTurnMomentum) {
driftTurnInput = driftTurnInput * (vehicle.deltaMovement.horizontalDistance() / vehicle.runtime.resolveDouble(settings.speedExpr ?: globalVehicle.speedExpr!!))
driftTurnInput = driftTurnInput * (vehicle.deltaMovement.horizontalDistance() / vehicle.runtime.resolveDouble(settings.speedExpr))
newMomentum += driftTurnInput
}
newMomentum = lerp(newMomentum, 0.0, 0.03)
@@ -192,10 +177,10 @@ class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
// Check to see if the ride should be walking or sprinting
//val walkSpeed = getWalkSpeed(vehicle)
val rideTopSpeed = vehicle.runtime.resolveDouble(settings.speedExpr ?: globalVehicle.speedExpr!!) * 0.6 * 2.0
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 ?: globalVehicle.accelerationExpr!!) * 0.3 * 3 * 2.0
val accel = vehicle.runtime.resolveDouble(settings.accelerationExpr) * 0.3 * 3 * 2.0
//Flag for determining if player is actively inputting
var activeInput = false
@@ -265,7 +250,7 @@ class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
}
//TODO: Change this so its tied to a jumping stat and representative of the amount of jumps
val canJump = vehicle.runtime.resolveBoolean(settings.canJump ?: globalVehicle.canJump!!)
val canJump = vehicle.runtime.resolveBoolean(settings.canJump)
//Jump the thang!
if (driver.jumping && vehicle.onGround() && canJump) {
val jumpForce = 0.5
@@ -311,7 +296,7 @@ class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
vehicle: PokemonEntity,
driver: Player
): Boolean {
return vehicle.runtime.resolveBoolean(settings.canJump ?: globalVehicle.canJump!!)
return vehicle.runtime.resolveBoolean(settings.canJump)
}
override fun setRideBar(
@@ -427,79 +412,66 @@ class VehicleSettings : RidingBehaviourSettings {
override val key = VehicleBehaviour.KEY
override val stats = mutableMapOf<RidingStat, IntRange>()
var infiniteStamina: Expression? = null
var canJump = "true".asExpression()
private set
var canJump: Expression? = null
var speed = "0.3".asExpression()
private set
var speed: Expression? = null
var driveFactor = "1.0".asExpression()
private set
var driveFactor: Expression? = null
var reverseDriveFactor = "0.25".asExpression()
private set
var reverseDriveFactor: Expression? = null
var rotationSpeed = "45/20".asExpression()
private set
var rotationSpeed: Expression? = null
var lookYawLimit = "101".asExpression()
private set
var lookYawLimit: Expression? = null
private set
var speedExpr: Expression? = null
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? = null
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? = null
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? = null
var jumpExpr: Expression = "q.get_ride_stats('JUMP', 'LAND', 10.0, 1.0)".asExpression()
private set
var handlingExpr: Expression? = null
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)
buffer.writeRidingStats(stats)
rideSounds.encode(buffer)
buffer.writeNullableExpression(infiniteStamina)
buffer.writeNullableExpression(canJump)
buffer.writeNullableExpression(speed)
buffer.writeNullableExpression(driveFactor)
buffer.writeNullableExpression(reverseDriveFactor)
buffer.writeNullableExpression(rotationSpeed)
buffer.writeNullableExpression(lookYawLimit)
buffer.writeNullableExpression(speedExpr)
buffer.writeNullableExpression(accelerationExpr)
buffer.writeNullableExpression(staminaExpr)
buffer.writeNullableExpression(jumpExpr)
buffer.writeNullableExpression(handlingExpr)
buffer.writeExpression(canJump)
buffer.writeExpression(speed)
buffer.writeExpression(driveFactor)
buffer.writeExpression(reverseDriveFactor)
buffer.writeExpression(rotationSpeed)
buffer.writeExpression(lookYawLimit)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
stats.putAll(buffer.readRidingStats())
rideSounds = RideSoundSettingsList.decode(buffer)
infiniteStamina = buffer.readNullableExpression()
canJump = buffer.readNullableExpression()
speed = buffer.readNullableExpression()
driveFactor = buffer.readNullableExpression()
reverseDriveFactor = buffer.readNullableExpression()
rotationSpeed = buffer.readNullableExpression()
lookYawLimit = buffer.readNullableExpression()
speedExpr = buffer.readNullableExpression()
accelerationExpr = buffer.readNullableExpression()
staminaExpr = buffer.readNullableExpression()
jumpExpr = buffer.readNullableExpression()
handlingExpr = buffer.readNullableExpression()
canJump = buffer.readExpression()
speed = buffer.readExpression()
driveFactor = buffer.readExpression()
reverseDriveFactor = buffer.readExpression()
rotationSpeed = buffer.readExpression()
lookYawLimit = buffer.readExpression()
}
}
@@ -8,34 +8,17 @@
package com.cobblemon.mod.common.api.riding.behaviour.types.liquid
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.CobblemonRideSettings
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourSettings
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourState
import com.cobblemon.mod.common.api.riding.behaviour.Side
import com.cobblemon.mod.common.api.riding.behaviour.ridingState
import com.cobblemon.mod.common.api.riding.behaviour.*
import com.cobblemon.mod.common.api.riding.posing.PoseOption
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.blockPositionsAsListRounded
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.readNullableExpression
import com.cobblemon.mod.common.util.readRidingStats
import com.cobblemon.mod.common.util.resolveBoolean
import com.cobblemon.mod.common.util.resolveDouble
import com.cobblemon.mod.common.util.resolveFloat
import com.cobblemon.mod.common.util.writeNullableExpression
import com.cobblemon.mod.common.util.writeRidingStats
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
import net.minecraft.core.BlockPos
import com.cobblemon.mod.common.util.*
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.Mth
@@ -45,24 +28,24 @@ import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
import net.minecraft.world.phys.shapes.Shapes
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
class BoatBehaviour : RidingBehaviour<BoatSettings, BoatState> {
companion object {
val KEY = cobblemonResource("liquid/boat")
val TOP_SPEED = 2.0
}
override val key = KEY
val globalBoat: BoatSettings
get() = CobblemonRideSettings.boat
override fun getRidingStyle(settings: BoatSettings, state: BoatState): RidingStyle {
return RidingStyle.LIQUID
}
val poseProvider = PoseProvider<BoatSettings, BoatState>(PoseType.STAND)
.with(PoseOption(PoseType.WALK) { _, state, _ ->
abs(state.rideVelocity.get().horizontalDistance()) > 0.2
})
val poseProvider = PoseProvider<BoatSettings, BoatState>(PoseType.FLOAT)
.with(PoseOption(PoseType.SWIM) { _, _, entity -> entity.isSwimming && entity.entityData.get(PokemonEntity.MOVING) })
override fun isActive(settings: BoatSettings, state: BoatState, vehicle: PokemonEntity): Boolean {
if (state.jumpBuffer.get() != -1) {
@@ -93,22 +76,14 @@ class BoatBehaviour : RidingBehaviour<BoatSettings, BoatState> {
state.jumpBuffer.set(state.jumpBuffer.get() + 1)
}
// Only start sprinting on vehicle if the state has changed and the pokemon has atleast 1/3 stamina
if (driver.isSprinting && !state.isVehicleSprinting.get()) {
state.isVehicleSprinting.set(state.stamina.get() > 0.33f)
}
else if (!driver.isSprinting) {
state.isVehicleSprinting.set(false)
}
if (!state.isVehicleSprinting.get()) {
if (driver.zza == 0f && (vehicle.isInWater || vehicle.isUnderWater)) {
if (state.staminaBuffer.get() >= 20) {
val staminaIncrease = vehicle.runtime.resolveDouble(settings.staminaIncreaseExpr ?: globalBoat.staminaIncreaseExpr!!)
val staminaIncrease = vehicle.getRideStat(RidingStat.STAMINA, RidingStyle.LIQUID, 1.0, 2.0)
state.stamina.set(min(1.0f, state.stamina.get() + 0.01f * staminaIncrease.toFloat()))
}
}
else if (abs(state.rideVelocity.get().z) > 0.1 && state.isVehicleSprinting.get()) {
consumeStamina(vehicle, driver, settings, state, 0.01f)
else if (abs(state.rideVelocity.get().z) > 0.1) {
consumeStamina(vehicle, state, 0.01f)
}
state.staminaBuffer.set(state.staminaBuffer.get() + 1)
super.tick(settings, state, vehicle, driver, input)
@@ -139,79 +114,67 @@ class BoatBehaviour : RidingBehaviour<BoatSettings, BoatState> {
driver: Player,
input: Vec3
): Vec3 {
applyStrafeRotation(vehicle, driver, settings, state)
applyStrafeRotation(vehicle, driver, state)
var velocity = state.rideVelocity.get()
velocity = applyVelocityFromInput(velocity, vehicle, driver, settings, state)
velocity = applyVelocityFromInput(velocity, vehicle, driver, state)
velocity = applyGravity(velocity, vehicle, settings, state)
velocity = applyJump(velocity, vehicle, driver, settings, state)
velocity = applyJump(velocity, vehicle, driver, state)
state.rideVelocity.set(velocity)
return state.rideVelocity.get()
}
private fun applyVelocityFromInput(velocity: Vec3, vehicle: PokemonEntity, driver: Player, settings: BoatSettings, state: BoatState): Vec3 {
val speed = vehicle.runtime.resolveDouble(settings.speedExpr ?: globalBoat.speedExpr!!)
val acceleration = speed / (vehicle.runtime.resolveDouble(settings.accelerationExpr ?: globalBoat.accelerationExpr!!) * 20)
private fun applyVelocityFromInput(velocity: Vec3, vehicle: PokemonEntity, driver: Player, state: BoatState): Vec3 {
val speed = vehicle.getRideStat(RidingStat.SPEED, RidingStyle.LIQUID, 0.8, TOP_SPEED)
val acceleration = vehicle.getRideStat(RidingStat.ACCELERATION, RidingStyle.LIQUID, 0.05, 0.5)
if (state.jumpBuffer.get() != -1 || !(vehicle.isInWater || vehicle.isUnderWater)) {
return velocity
}
val sprintModifier = vehicle.runtime.resolveDouble(settings.sprintSpeedModifier ?: globalBoat.sprintSpeedModifier!!)
val activeSprintModifier = if (state.isVehicleSprinting.get() && hasStamina(state)) sprintModifier else 1.0
val forwardInput = driver.zza.toDouble()
val delta = when {
forwardInput == 0.0 -> -velocity.z * 0.04
!hasStamina(state) || forwardInput == 0.0 -> -velocity.z * 0.04
forwardInput < 0.0 -> when {
velocity.z < 0.0 -> forwardInput * acceleration * 0.5
else -> min(forwardInput * acceleration * 0.5, -velocity.z * 0.04) // We never want it to be slower to reverse than no input
}
else -> forwardInput * acceleration * activeSprintModifier
else -> forwardInput * acceleration
}
return Vec3(
velocity.x,
velocity.y,
Mth.clamp(velocity.z + delta, -speed * 0.25, speed * activeSprintModifier)
Mth.clamp(velocity.z + delta, -speed * 0.25, speed)
)
}
private fun applyJump(velocity: Vec3, vehicle: PokemonEntity, driver: Player, settings: BoatSettings, state: BoatState): Vec3 {
private fun applyJump(velocity: Vec3, vehicle: PokemonEntity, driver: Player, state: BoatState): Vec3 {
if (!driver.jumping) return velocity // Not jumping
if (state.jumpBuffer.get() != -1) return velocity // Already jumped very recently
if (!hasStamina(state)) return velocity // Has no stamina
val jumpStrength = vehicle.runtime.resolveDouble(settings.jumpStrengthExpr ?: globalBoat.jumpStrengthExpr!!)
val jumpStrength = vehicle.getRideStat(RidingStat.JUMP, RidingStyle.LIQUID, 1.0, 2.5)
state.jumpBuffer.set(0)
consumeStamina(vehicle, state, 0.05f)
return Vec3(velocity.x, velocity.y + jumpStrength, velocity.z)
}
private fun applyGravity(velocity: Vec3, vehicle: PokemonEntity, settings: BoatSettings, state: BoatState): Vec3 {
if (state.jumpBuffer.get() == -1 && (vehicle.isInWater || vehicle.isUnderWater)) {
if (shouldFloatHigher(vehicle, settings)) {
return Vec3(velocity.x, 0.5, velocity.z)
}
else {
return Vec3(velocity.x, 0.0, velocity.z)
}
if (state.jumpBuffer.get() == -1 && vehicle.isInWater || vehicle.isUnderWater) {
return Vec3(velocity.x, -0.05, velocity.z)
}
val terminalVelocity = vehicle.runtime.resolveDouble(settings.terminalVelocity ?: globalBoat.terminalVelocity!!)
val terminalVelocity = vehicle.runtime.resolveDouble(settings.terminalVelocity)
val gravity = (9.8 / ( 20.0)) * 0.2
return Vec3(velocity.x, max(velocity.y - gravity, terminalVelocity), velocity.z)
}
private fun shouldFloatHigher(vehicle: PokemonEntity, settings: BoatSettings): Boolean {
val surfaceOffset = vehicle.runtime.resolveFloat(settings.surfaceLevelOffset ?: globalBoat.surfaceLevelOffset!!)
val blockPos = BlockPos.containing(vehicle.x, vehicle.eyeY + surfaceOffset, vehicle.z)
val fluidState = vehicle.level().getFluidState(blockPos)
return !fluidState.isEmpty
}
private fun applyStrafeRotation(vehicle: PokemonEntity, driver: Player, settings: BoatSettings, state: BoatState) {
val skillModifier = vehicle.runtime.resolveDouble(settings.rotationSpeedModifierExpr ?: globalBoat.rotationSpeedModifierExpr!!)
private fun applyStrafeRotation(vehicle: PokemonEntity, driver: Player, state: BoatState) {
val skill = vehicle.getRideStat(RidingStat.SKILL, RidingStyle.LIQUID, 0.5, 1.5)
val strafe = driver.xxa
if (abs(strafe) > 0) {
val increase = if (strafe > 0) -1 else 1
state.deltaRotation.set(state.deltaRotation.get() + increase * skillModifier)
state.deltaRotation.set(state.deltaRotation.get() + increase * skill)
}
}
@@ -219,16 +182,9 @@ class BoatBehaviour : RidingBehaviour<BoatSettings, BoatState> {
return state.stamina.get() != 0f
}
private fun consumeStamina(vehicle: PokemonEntity, driver: Player, settings: BoatSettings, state: BoatState, drain: Float) {
val staminaModifier = vehicle.runtime.resolveFloat(settings.staminaDecreaseExpr ?: globalBoat.staminaDecreaseExpr!!)
if (staminaModifier == 0F) {
return
}
state.stamina.set(max(0f, state.stamina.get() - drain / staminaModifier))
if (state.stamina.get() == 0f) {
state.isVehicleSprinting.set(false)
driver.isSprinting = false
}
private fun consumeStamina(vehicle: PokemonEntity, state: BoatState, drain: Float) {
val stamina = vehicle.getRideStat(RidingStat.STAMINA, RidingStyle.LIQUID, 1.0, 5.0)
state.stamina.set(max(0f, state.stamina.get() - drain / stamina.toFloat()))
state.staminaBuffer.set(0)
}
@@ -329,8 +285,10 @@ class BoatBehaviour : RidingBehaviour<BoatSettings, BoatState> {
vehicle: PokemonEntity,
driver: Player
): Float {
val sprintFov = vehicle.runtime.resolveFloat(settings.sprintFovModifier ?: globalBoat.sprintFovModifier!!)
return if (state.isVehicleSprinting.get()) sprintFov else 1.0f
if (state.rideVelocity.get().z > TOP_SPEED * 0.8) {
return Mth.lerp((state.rideVelocity.get().z - (TOP_SPEED * 0.8)) / (TOP_SPEED * 0.2), 1.0, 1.2).toFloat()
}
return 1.0f
}
override fun useAngVelSmoothing(settings: BoatSettings, state: BoatState, vehicle: PokemonEntity): Boolean {
@@ -343,14 +301,7 @@ class BoatBehaviour : RidingBehaviour<BoatSettings, BoatState> {
vehicle: PokemonEntity,
driver: Player
): ResourceLocation {
val isInWater = Shapes.create(vehicle.boundingBox).blockPositionsAsListRounded().any {
if (vehicle.isInWater || vehicle.isUnderWater) {
return@any true
}
val blockState = vehicle.level().getBlockState(it)
return@any !blockState.fluidState.isEmpty
}
return if (!isInWater) cobblemonResource("in_air") else cobblemonResource("no_pose")
return cobblemonResource("no_pose")
}
override fun inertia(settings: BoatSettings, state: BoatState, vehicle: PokemonEntity): Double {
@@ -374,7 +325,7 @@ class BoatBehaviour : RidingBehaviour<BoatSettings, BoatState> {
state: BoatState,
vehicle: PokemonEntity
): Boolean {
return vehicle.runtime.resolveBoolean(settings.rotatePokemonHead ?: globalBoat.rotatePokemonHead!!)
return vehicle.runtime.resolveBoolean(settings.rotatePokemonHead)
}
override fun shouldRotateRiderHead(
@@ -404,67 +355,23 @@ class BoatSettings : RidingBehaviourSettings {
override val stats = mutableMapOf<RidingStat, IntRange>()
var rideSounds: RideSoundSettingsList = RideSoundSettingsList()
var terminalVelocity: Expression? = null
var terminalVelocity = "-2.0".asExpression()
private set
var rotatePokemonHead: Expression? = null
var rotatePokemonHead = "true".asExpression()
private set
var staminaIncreaseExpr: Expression? = null
private set
var staminaDecreaseExpr: Expression? = null
private set
var rotationSpeedModifierExpr: Expression? = null
private set
var jumpStrengthExpr: Expression? = null
private set
var speedExpr: Expression? = null
private set
var accelerationExpr: Expression? = null
private set
var sprintSpeedModifier: Expression? = null
private set
var sprintFovModifier: Expression? = null
var surfaceLevelOffset: Expression? = null
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeRidingStats(stats)
rideSounds.encode(buffer)
buffer.writeNullableExpression(terminalVelocity)
buffer.writeNullableExpression(rotatePokemonHead)
buffer.writeNullableExpression(staminaIncreaseExpr)
buffer.writeNullableExpression(staminaDecreaseExpr)
buffer.writeNullableExpression(rotationSpeedModifierExpr)
buffer.writeNullableExpression(jumpStrengthExpr)
buffer.writeNullableExpression(speedExpr)
buffer.writeNullableExpression(accelerationExpr)
buffer.writeNullableExpression(sprintSpeedModifier)
buffer.writeNullableExpression(sprintFovModifier)
buffer.writeNullableExpression(surfaceLevelOffset)
buffer.writeExpression(terminalVelocity)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
stats.putAll(buffer.readRidingStats())
rideSounds = RideSoundSettingsList.decode(buffer)
terminalVelocity = buffer.readNullableExpression()
rotatePokemonHead = buffer.readNullableExpression()
staminaIncreaseExpr = buffer.readNullableExpression()
staminaDecreaseExpr = buffer.readNullableExpression()
rotationSpeedModifierExpr = buffer.readNullableExpression()
jumpStrengthExpr = buffer.readNullableExpression()
speedExpr = buffer.readNullableExpression()
accelerationExpr = buffer.readNullableExpression()
sprintSpeedModifier = buffer.readNullableExpression()
sprintFovModifier = buffer.readNullableExpression()
surfaceLevelOffset = buffer.readNullableExpression()
terminalVelocity = buffer.readExpression()
}
}
@@ -472,7 +379,6 @@ class BoatState : RidingBehaviourState() {
val deltaRotation = ridingState(0.0, Side.CLIENT)
val jumpBuffer = ridingState(-1, Side.CLIENT)
val staminaBuffer = ridingState(0, Side.CLIENT)
val isVehicleSprinting = ridingState(false, Side.CLIENT)
override fun copy() = BoatState().also {
it.deltaRotation.set(deltaRotation.get(), true)
@@ -480,7 +386,6 @@ class BoatState : RidingBehaviourState() {
it.stamina.set(stamina.get(), true)
it.jumpBuffer.set(jumpBuffer.get(), true)
it.staminaBuffer.set(staminaBuffer.get(), true)
it.isVehicleSprinting.set(isVehicleSprinting.get(), true)
}
override fun shouldSync(previous: RidingBehaviourState): Boolean {
@@ -8,14 +8,10 @@
package com.cobblemon.mod.common.api.riding.behaviour.types.liquid
import com.cobblemon.mod.common.CobblemonRideSettings
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.RidingBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourSettings
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourState
import com.cobblemon.mod.common.api.riding.behaviour.Side
import com.cobblemon.mod.common.api.riding.behaviour.ridingState
import com.cobblemon.mod.common.api.riding.behaviour.*
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
@@ -25,7 +21,6 @@ import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.blockPositionsAsListRounded
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.readRidingStats
import com.cobblemon.mod.common.util.writeNullable
import com.cobblemon.mod.common.util.writeRidingStats
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
@@ -43,8 +38,6 @@ class BurstBehaviour : RidingBehaviour<BurstSettings, BurstState> {
}
override val key = KEY
val globalBurst: BurstSettings
get() = CobblemonRideSettings.burst
override fun getRidingStyle(settings: BurstSettings, state: BurstState): RidingStyle {
return RidingStyle.LIQUID
@@ -74,7 +67,7 @@ class BurstBehaviour : RidingBehaviour<BurstSettings, BurstState> {
vehicle: PokemonEntity,
driver: Player
): Float {
if (state.dashing.get()) {
if(state.dashing.get()) {
state.ticks.set(state.ticks.get() + 1)
if(state.ticks.get() >= DASH_TICKS) {
state.dashing.set(false)
@@ -84,7 +77,7 @@ class BurstBehaviour : RidingBehaviour<BurstSettings, BurstState> {
}
state.dashing.set(true)
return settings.dashSpeed ?: globalBurst.dashSpeed!!
return settings.dashSpeed
}
override fun rotation(
@@ -246,21 +239,22 @@ class BurstSettings : RidingBehaviourSettings {
override val key = BurstBehaviour.KEY
override val stats = mutableMapOf<RidingStat, IntRange>()
var dashSpeed: Float? = null
var dashSpeed = 1F
private set
var rideSounds: RideSoundSettingsList = RideSoundSettingsList()
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeRidingStats(stats)
rideSounds.encode(buffer)
buffer.writeNullable(dashSpeed) { buf, speed -> buf.writeFloat(speed) }
buffer.writeFloat(dashSpeed)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
stats.putAll(buffer.readRidingStats())
rideSounds = RideSoundSettingsList.decode(buffer)
dashSpeed = buffer.readNullable { buf -> buf.readFloat() }
dashSpeed = buffer.readFloat()
}
}
@@ -9,44 +9,34 @@
package com.cobblemon.mod.common.api.riding.behaviour.types.liquid
import com.bedrockk.molang.Expression
import com.bedrockk.molang.runtime.MoLangMath.lerp
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonRideSettings
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.api.orientation.OrientationController
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourSettings
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourState
import com.cobblemon.mod.common.api.riding.behaviour.Side
import com.cobblemon.mod.common.api.riding.behaviour.ridingState
import com.cobblemon.mod.common.api.riding.behaviour.*
import com.cobblemon.mod.common.api.riding.behaviour.types.air.JetSettings
import com.cobblemon.mod.common.api.riding.behaviour.types.air.JetState
import com.cobblemon.mod.common.api.riding.posing.PoseOption
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.math.geometry.toDegrees
import com.cobblemon.mod.common.util.*
import com.cobblemon.mod.common.util.math.geometry.toRadians
import com.cobblemon.mod.common.util.readNullableExpression
import com.cobblemon.mod.common.util.readRidingStats
import com.cobblemon.mod.common.util.resolveBoolean
import com.cobblemon.mod.common.util.resolveDouble
import com.cobblemon.mod.common.util.toVec3d
import com.cobblemon.mod.common.util.writeNullableExpression
import com.cobblemon.mod.common.util.writeRidingStats
import kotlin.math.*
import net.minecraft.client.Minecraft
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.RegistryFriendlyByteBuf
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 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 {
@@ -54,8 +44,6 @@ class DolphinBehaviour : RidingBehaviour<DolphinSettings, DolphinState> {
}
override val key = KEY
val globalDolphin: DolphinSettings
get() = CobblemonRideSettings.dolphin
override fun getRidingStyle(settings: DolphinSettings, state: DolphinState): RidingStyle {
return RidingStyle.LIQUID
@@ -65,7 +53,14 @@ class DolphinBehaviour : RidingBehaviour<DolphinSettings, DolphinState> {
.with(PoseOption(PoseType.SWIM) { _, _, entity -> entity.deltaMovement.length() > 0.1 })
override fun isActive(settings: DolphinSettings, state: DolphinState, vehicle: PokemonEntity): Boolean {
return vehicle.isInWater || !vehicle.onGround()
return Shapes.create(vehicle.boundingBox).blockPositionsAsListRounded().any {
if (it.y.toDouble() == (vehicle.position().y)) {
val blockState = vehicle.level().getBlockState(it.below())
return@any (blockState.isAir || !blockState.fluidState.isEmpty ) ||
(vehicle.isInWater || vehicle.isUnderWater)
}
true
}
}
override fun pose(settings: DolphinSettings, state: DolphinState, vehicle: PokemonEntity): PoseType {
@@ -73,66 +68,7 @@ class DolphinBehaviour : RidingBehaviour<DolphinSettings, DolphinState> {
}
override fun speed(settings: DolphinSettings, state: DolphinState, vehicle: PokemonEntity, driver: Player): Float {
return state.speed.get().toFloat()
}
override fun tick(
settings: DolphinSettings,
state: DolphinState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
) {
if(vehicle.level().isClientSide) {
handleBoosting(state, vehicle)
tickStamina(settings, state, vehicle)
}
}
fun handleBoosting(
state: DolphinState,
vehicle: PokemonEntity
) {
//If the forward key is not held then it cannot be boosting
if(Minecraft.getInstance().options.keyUp.isDown() && state.stamina.get() != 0.0f && (vehicle.isInWater || vehicle.isUnderWater)) {
val boostKeyPressed = Minecraft.getInstance().options.keySprint.isDown()
if (state.stamina.get() >= 0.25f) {
//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)
}
}
fun tickStamina(
settings: DolphinSettings,
state: DolphinState,
vehicle: PokemonEntity
) {
val stam = state.stamina.get()
if (vehicle.runtime.resolveBoolean(settings.infiniteStamina ?: globalDolphin.infiniteStamina!!)) {
return
}
// Grab the boost time in seconds and convert to ticks. Then calculate the drain rate as inversely
// proportional to the number of ticks of boost thus making a full boost take x ticks
// in short: "Stamina drains slower at higher values and also replenishes slower"
val boostTime = vehicle.runtime.resolveDouble(settings.staminaExpr ?: globalDolphin.staminaExpr!!) * 20.0f
val stamDrainRate = (1.0f / boostTime).toFloat()
val newStam = if (state.boosting.get()) max(0.0f,stam - stamDrainRate)
else min(1.0f,stam + stamDrainRate)
state.stamina.set(newStam)
return vehicle.runtime.resolveFloat(settings.speed)
}
override fun rotation(
@@ -151,71 +87,39 @@ class DolphinBehaviour : RidingBehaviour<DolphinSettings, DolphinState> {
driver: Player,
input: Vec3
): Vec3 {
if (driver !is OrientationControllable) return Vec3.ZERO
val controller = (driver as OrientationControllable).orientationController
val runtime = vehicle.runtime
val boostMod = if (state.boosting.get()) runtime.resolveDouble(settings.jumpExpr ?: globalDolphin.jumpExpr!!) else 1.0
val topSpeed = (vehicle.runtime.resolveDouble(settings.speedExpr ?: globalDolphin.speedExpr!!) / 20.0) * boostMod
val accel = (topSpeed / (vehicle.runtime.resolveDouble(settings.accelerationExpr ?: globalDolphin.accelerationExpr!!) * 20.0)) * boostMod
/*********************************************
* Breaching logic
*********************************************/
if (!vehicle.isInWater && !vehicle.isUnderWater)
{
val currVel = state.lastVelocity.get()
val breachBoost = 1.5
if (!state.hasBreached.get()) {
// Apply an upwards breaching force if boosting. This will enhance jumping out of water to feel more
// fun
if (state.boosting.get()) {
state.lastVelocity.set(Vec3(currVel.x, currVel.y + breachBoost*sin(-controller.pitch.toRadians()).coerceIn(0.0f,0.5f), currVel.z))
}
state.hasBreached.set(true)
} else {
val gravity = (9.8 / ( 20.0)) * 0.2 * 0.4
val terminalVel = 2.0
state.lastVelocity.set(Vec3(currVel.x, max(-terminalVel, currVel.y - gravity), currVel.z)) //.yRot(vehicle.yRot.toRadians()))
}
state.lastVelocity.set(Vec3(state.lastVelocity.get().x, state.lastVelocity.get().y - 0.035, state.lastVelocity.get().z))
return state.lastVelocity.get()
} else {
state.lastVelocity.set(vehicle.deltaMovement.yRot(vehicle.yRot.toRadians()))
state.hasBreached.set(false)
}
/*********************************************
* Handle collisions
*********************************************/
if (vehicle.horizontalCollision || vehicle.verticalCollision) {
// not actually friction
val frictionLimit = 0.1
val postColSpeed = min(max(vehicle.deltaMovement.length(), frictionLimit), state.speed.get())
state.speed.set(postColSpeed)
val driveFactor = runtime.resolveFloat(settings.driveFactor)
val strafeFactor = runtime.resolveFloat(settings.strafeFactor)
val f = driver.xxa * strafeFactor
var g = driver.zza * driveFactor
if (g <= 0.0f) {
g *= runtime.resolveFloat(settings.reverseDriveFactor)
}
val currSpeed = state.speed.get()
val strafeFactor = runtime.resolveDouble(settings.strafeFactor ?: globalDolphin.strafeFactor!!)
val reverseDriveFactor = runtime.resolveDouble(settings.reverseDriveFactor ?: globalDolphin.reverseDriveFactor!!)
val f = -driver.xxa * strafeFactor
when {
driver.zza > 0.0 -> state.speed.set(min(topSpeed, currSpeed + accel))
driver.zza < 0.0 -> state.speed.set(max( -topSpeed * reverseDriveFactor, currSpeed - accel))
else -> state.speed.set( lerp(currSpeed, 0.0, 0.05))
var yComp = 0.0
//Get roll to add a left and right strafe during roll
//Not sure if this is desired? Will need to mess around with this
//a bit more
var zComp = 0.0
val controller = (driver as? OrientationControllable)?.orientationController
if (controller != null)
{
//Can be used again if I better figure out how to deadzone it near the tops?
zComp = -1.0 * g.toDouble() * sin(Math.toRadians(controller.roll.toDouble()))
yComp = -1.0 * g.toDouble() * sin(Math.toRadians(controller.pitch.toDouble()))
}
val vertInput = when {
driver.jumping && state.stamina.get() != 0.0f -> 1.0
driver.isShiftKeyDown -> -1.0
else -> 0.0
} * strafeFactor
var newVelocity = Vec3(f, vertInput,-state.speed.get())
// Align to the ride
newVelocity = controller.orientation?.transform(newVelocity.toVector3f())?.toVec3d() ?: Vec3.ZERO
newVelocity = newVelocity.yRot(vehicle.yRot.toRadians())
val desiredVec = newVelocity.subtract(vehicle.deltaMovement)
return vehicle.deltaMovement.add(desiredVec)
val currVelocity = Vec3(0.0, yComp , g.toDouble())
state.lastVelocity.set(currVelocity)
return currVelocity
}
override fun angRollVel(
@@ -225,93 +129,30 @@ class DolphinBehaviour : RidingBehaviour<DolphinSettings, DolphinState> {
driver: Player,
deltaTime: Double
): Vec3 {
if (Cobblemon.config.disableRoll) return Vec3.ZERO
if (driver !is OrientationControllable) return Vec3.ZERO
val controller = (driver as OrientationControllable).orientationController
val currSpeed = vehicle.deltaMovement.length()
val handling = vehicle.runtime.resolveDouble(settings.handlingExpr ?: globalDolphin.handlingExpr!!)
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr ?: globalDolphin.speedExpr!!) / 20.0
val yawDeltaDeg = deltaTime * handling * sin(Math.toRadians(controller.roll.toDouble())).pow(2) * sin(Math.toRadians(controller.roll.toDouble())).sign
val trueYawDelt = yawDeltaDeg * abs(cos(Math.toRadians(controller.pitch.toDouble()))) * sqrt(RidingBehaviour.scaleToRange(currSpeed, 0.0, topSpeed))
// Dampen yaw when upside down
val yawDampen = (1 - abs(min(cos(controller.roll.toRadians()),0.0f)))
controller.applyGlobalYaw(trueYawDelt.toFloat() * yawDampen)
// Correct orientation when at low speeds
correctOrientation(settings, state, vehicle, controller, driver, deltaTime)
//yaw, pitch, roll
return Vec3(0.0, state.currPitchCorrectionForce.get(), state.currRollCorrectionForce.get())
}
private fun correctOrientation(
settings: DolphinSettings,
state: DolphinState,
vehicle: PokemonEntity,
controller: OrientationController,
driver: Player,
deltaTime: Double
) {
// Calculate correcting roll force.
// Mult by cos(pitch) to ensure that it doesn't roll correct at when diving or climbing
val currSpeed = vehicle.deltaMovement.length()
val rollCorrectionTimer = vehicle.runtime.resolveDouble(settings.timeToRollCorrect ?: globalDolphin.timeToRollCorrect!!)
val maxRollCorrectionRate = if (!(vehicle.isInWater || vehicle.isUnderWater)) 15.0f
else 10.0f * (cos(controller.pitch.toRadians())).pow(2) * ((state.noInputTime.get() - rollCorrectionTimer) * 2).coerceIn(0.0, 1.0).toFloat()
val maxRollForce = maxRollCorrectionRate / 40.0
val rollArrivalDeg = 90.0
if((state.speed.get() < 0.1 && driver.zza.toDouble() == 0.0) || state.noInputTime.get() > rollCorrectionTimer || !(vehicle.isInWater || vehicle.isUnderWater)) {
val arrivalInfluence = (min(rollArrivalDeg,abs(controller.roll).toDouble()) / rollArrivalDeg)
val desiredRollForce = (0 - controller.roll).sign * maxRollCorrectionRate * arrivalInfluence
var steeredRollForce = desiredRollForce - state.currRollCorrectionForce.get()
// Ensure the 'steering' correction force doesn't exceed our maxForce
if (abs(steeredRollForce) > maxRollForce) {
steeredRollForce = steeredRollForce.sign * maxRollForce
}
// Apply the 'steered' force (its really not a steering force I need a better name)
state.currRollCorrectionForce.set(
state.currRollCorrectionForce.get() + steeredRollForce
)
} else {
state.currRollCorrectionForce.set(
//reduce rollCorrection gradually
lerp(state.currRollCorrectionForce.get(), 0.0, deltaTime * 0.96)
)
if (!vehicle.isInWater && !vehicle.isUnderWater)
{
return Vec3(0.0, 0.0, 0.0)
}
//Calculate correcting pitch force.
val maxPitchCorrectionRate = if (!(vehicle.isInWater || vehicle.isUnderWater)) 15.0 else 5.0f * ((state.noInputTime.get() - rollCorrectionTimer) * 2).coerceIn(0.0, 1.0).toFloat() * (1 - (currSpeed * 0.5).coerceIn(0.0, 1.0))
val maxPitchForce = maxPitchCorrectionRate / 40.0
val pitchArrivalDeg = 45.0
val targetPitch = if ((vehicle.isInWater || vehicle.isUnderWater)) 0.0f else (-asin(vehicle.deltaMovement.y / vehicle.deltaMovement.length()).toDegrees()).coerceIn(-45.0f, 45.0f)
val controller = (driver as? OrientationControllable)?.orientationController
if((state.noInputTime.get() > rollCorrectionTimer && driver.zza.toDouble() == 0.0) || !(vehicle.isInWater || vehicle.isUnderWater)) {
val arrivalInfluence = min(rollArrivalDeg, abs(Mth.wrapDegrees(targetPitch - controller.pitch.toDouble()))) / pitchArrivalDeg
val desiredPitchForce = (targetPitch - controller.pitch).sign * maxPitchCorrectionRate * arrivalInfluence
var steeredPitchForce = desiredPitchForce - state.currPitchCorrectionForce.get()
//this should be changed to be speed maybe?
val movingForce = driver.zza
if (controller != null) {
var yawAngVel = 3 * sin(Math.toRadians(controller.roll.toDouble())).toFloat()
var pitchAngVel = -1 * Math.abs(sin(Math.toRadians(controller.roll.toDouble())).toFloat())
// Ensure the 'steering' correction force doesn't exceed our maxForce
if (abs(steeredPitchForce) > maxPitchForce) {
steeredPitchForce = steeredPitchForce.sign * maxPitchForce
}
// Apply the 'steered' force (its really not a steering force I need a better name)
state.currPitchCorrectionForce.set(
state.currPitchCorrectionForce.get() + steeredPitchForce
)
} else {
state.currPitchCorrectionForce.set(
//reduce rollCorrection gradually
lerp(state.currPitchCorrectionForce.get(), 0.0, deltaTime * 0.98)
)
//limit rotation modulation when pitched up heavily or pitched down heavily
yawAngVel *= (abs(cos(Math.toRadians(controller.pitch.toDouble())))).toFloat()
pitchAngVel *= (abs(cos(Math.toRadians(controller.pitch.toDouble())))).toFloat()
//if you are not pressing forward then don't turn
//yawAngVel *= movingForce
//Ignore x,y,z its angular velocity:
//yaw, pitch, roll
return Vec3(yawAngVel.toDouble(), pitchAngVel.toDouble(), 0.0)
}
return Vec3(0.0, 0.0, 0.0)
}
override fun rotationOnMouseXY(
@@ -326,13 +167,7 @@ class DolphinBehaviour : RidingBehaviour<DolphinSettings, DolphinState> {
sensitivity: Double,
deltaTime: Double
): Vec3 {
// Begin the roll correction time counter if not enough mouse input
// is detected
if (abs(mouseX) < 1 && abs(mouseY) < 1 && (vehicle.isInWater || vehicle.isUnderWater)) {
state.noInputTime.set(state.noInputTime.get() + deltaTime)
} else {
state.noInputTime.set(0.0)
}
return when {
Cobblemon.config.disableRoll -> noRollRotation(
settings,
@@ -376,34 +211,41 @@ class DolphinBehaviour : RidingBehaviour<DolphinSettings, DolphinState> {
if (driver !is OrientationControllable) return Vec3.ZERO
val controller = (driver as OrientationControllable).orientationController
// Set roll to zero if transitioning to noroll config
controller.rotateRoll(controller.roll * -1.0f)
//TODO:hook up handling
val handling = vehicle.runtime.resolveDouble(settings.handlingExpr)
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
val boostingHandleDebuff = if (state.boosting.get()) 0.2 else 1.0
val handling = vehicle.runtime.resolveDouble(settings.handlingExpr ?: globalDolphin.handlingExpr!!) * boostingHandleDebuff
val mouseInputMult = vehicle.runtime.resolveDouble(settings.mouseInputMult ?: globalDolphin.mouseInputMult!!)
//Smooth out mouse input.
//Interpret mouse input
val smoothingSpeed = 4.0
val mouseXc = ((mouseX).coerceIn(-60.0, 60.0) / 60.0) * mouseInputMult
val mouseYc = ((mouseY).coerceIn(-60.0, 60.0) / 60.0) * mouseInputMult
val mouseXc = (mouseX).coerceIn(-60.0, 60.0)
val mouseYc = (mouseY).coerceIn(-60.0, 60.0)
val xInput = mouseXSmoother.getNewDeltaValue(mouseXc * 0.1, deltaTime * smoothingSpeed);
val yInput = mouseYSmoother.getNewDeltaValue(mouseYc * 0.1, deltaTime * smoothingSpeed);
val rollRot = xInput
val pitchRot = yInput
//Give the ability to yaw with x mouse input when at low speeds.
val yawForce = xInput * handling
// 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(RidingBehaviour.scaleToRange(state.rideVelocity.get().length(), 0.0, topSpeed)), 0.5))
//Apply yaw globally as we don't want roll or pitch changes due to local yaw when looking up or down.
controller.applyGlobalYaw(yawForce.toFloat() * abs(cos(controller.pitch.toRadians())).pow(2))
controller.applyGlobalYaw(yawForce.toFloat())
var pitchRot = yInput * handling
// Pitch up globally
// Pitch
if (abs(controller.pitch + pitchRot) >= 89.5 ) {
pitchRot = 0.0
mouseYSmoother.reset()
} else {
controller.applyGlobalPitch(pitchRot.toFloat() * -1.0f)
//controller.applyGlobalPitch(pitchRot.toFloat() * -1.0f)
controller.rotatePitch(pitchRot.toFloat())
}
//yaw, pitch, roll
@@ -424,39 +266,46 @@ class DolphinBehaviour : RidingBehaviour<DolphinSettings, DolphinState> {
): Vec3 {
if (driver !is OrientationControllable) return Vec3.ZERO
val controller = (driver as OrientationControllable).orientationController
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr ?: globalDolphin.speedExpr!!) / 20.0
val boostingHandleDebuff = if (state.boosting.get()) 0.2 else 1.0
val mouseInputMult = vehicle.runtime.resolveDouble(settings.mouseInputMult ?: globalDolphin.mouseInputMult!!) * boostingHandleDebuff
//Smooth out mouse input.
//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 mouseXc = (mouseX).coerceIn(-45.0, 45.0) * mouseInputMult
val mouseYc = (mouseY).coerceIn(-45.0, 45.0) * mouseInputMult
val mouseXc = (mouseX).coerceIn(-60.0, 60.0)
val mouseYc = (mouseY).coerceIn(-60.0, 60.0)
val xInput = mouseXSmoother.getNewDeltaValue(mouseXc * 0.1, deltaTime * smoothingSpeed);
val yInput = mouseYSmoother.getNewDeltaValue(mouseYc * 0.1, deltaTime * smoothingSpeed);
val currSpeed = vehicle.deltaMovement.length()
val rollRot = xInput
val pitchRot = yInput
// Yaw locally
// Dampen yaw when rolled or pitched substantially
val yawDampen = abs(cos(controller.pitch.toRadians())).pow(2) * abs(cos(controller.roll.toRadians())).pow(2)
val localYaw = xInput * (1 - (RidingBehaviour.scaleToRange(currSpeed, 0.0, topSpeed))).coerceIn(0.4, 1.0) * yawDampen
// Roll
// if (abs(controller.pitch + rollRot) >= 89.5 ) {
// mouseXSmoother.reset()
// } else {
//
// }
// Pitch locally up or down depending upon a number of factors:
// - Reduce pitch substantially when rolled and in a steep yaw. This prevents the player from ignoring a slow handling stat
// - Do not pitch at all when at or below hover speed
// - pitch faster at higher speeds
val p = 1.0 - vehicle.runtime.resolveDouble(settings.horizontalPitchExpr ?: globalDolphin.horizontalPitchExpr!!)
val localPitch = yInput * (1 - abs(sin(controller.roll.toRadians())) * p * hypot(controller.forwardVector.x.toDouble(), controller.forwardVector.z.toDouble()).toFloat())
// localPitch *= if (currSpeed > hoverSpeed) RidingBehaviour.scaleToRange(currSpeed, hoverSpeed, topSpeed)
// else 0.0
controller.rotateRoll(rollRot.toFloat())
// Roll less when slow and not at all when at hoverSpeed or lower.
// Apply a roll righting force when trying to pitch hard while rolled sideways. This nudges the player towards
// pitching globally
val rollForce = xInput * ((RidingBehaviour.scaleToRange(currSpeed, 0.0, topSpeed))).coerceIn(0.2, 1.0) //+ pitchInfluencedRollCorrection
// Yaw
// 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(RidingBehaviour.scaleToRange(state.rideVelocity.get().length(), 0.0, topSpeed)), 0.5))
//Apply yaw globally as we don't want roll or pitch changes due to local yaw when looking up or down.
controller.applyGlobalYaw(yawForce.toFloat())
// 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(localYaw, localPitch, rollForce)
return Vec3.ZERO
}
override fun canJump(
@@ -465,7 +314,7 @@ class DolphinBehaviour : RidingBehaviour<DolphinSettings, DolphinState> {
vehicle: PokemonEntity,
driver: Player
): Boolean {
return false
return vehicle.runtime.resolveBoolean(settings.canJump)
}
override fun setRideBar(
@@ -474,7 +323,7 @@ class DolphinBehaviour : RidingBehaviour<DolphinSettings, DolphinState> {
vehicle: PokemonEntity,
driver: Player
): Float {
return (state.stamina.get() / 1.0f)
return 0.0f
}
override fun jumpForce(
@@ -484,7 +333,10 @@ class DolphinBehaviour : RidingBehaviour<DolphinSettings, DolphinState> {
driver: Player,
jumpStrength: Int
): Vec3 {
return Vec3.ZERO
val runtime = vehicle.runtime
runtime.environment.query.addFunction("jump_strength") { DoubleValue(jumpStrength.toDouble()) }
val jumpVector = settings.jumpVector.map { runtime.resolveFloat(it) }
return Vec3(jumpVector[0].toDouble(), jumpVector[1].toDouble(), jumpVector[2].toDouble())
}
override fun gravity(
@@ -493,7 +345,7 @@ class DolphinBehaviour : RidingBehaviour<DolphinSettings, DolphinState> {
vehicle: PokemonEntity,
regularGravity: Double
): Double {
return 0.0 //if (!vehicle.isInWater && !vehicle.isUnderWater) 1.0 else 0.0
return 0.0
}
override fun rideFovMultiplier(
@@ -502,7 +354,7 @@ class DolphinBehaviour : RidingBehaviour<DolphinSettings, DolphinState> {
vehicle: PokemonEntity,
driver: Player
): Float {
return if (state.boosting.get()) 1.2f else 1.0f
return 1.0f
}
override fun useAngVelSmoothing(settings: DolphinSettings, state: DolphinState, vehicle: PokemonEntity): Boolean {
@@ -519,7 +371,7 @@ class DolphinBehaviour : RidingBehaviour<DolphinSettings, DolphinState> {
}
override fun inertia(settings: DolphinSettings, state: DolphinState, vehicle: PokemonEntity): Double {
return if (!vehicle.isInWater && !vehicle.isUnderWater) 1.0 else 0.1
return 0.05
}
override fun shouldRoll(settings: DolphinSettings, state: DolphinState, vehicle: PokemonEntity): Boolean {
@@ -527,7 +379,7 @@ class DolphinBehaviour : RidingBehaviour<DolphinSettings, DolphinState> {
}
override fun turnOffOnGround(settings: DolphinSettings, state: DolphinState, vehicle: PokemonEntity): Boolean {
return false
return true
}
override fun dismountOnShift(settings: DolphinSettings, state: DolphinState, vehicle: PokemonEntity): Boolean {
@@ -566,99 +418,68 @@ class DolphinSettings : RidingBehaviourSettings {
override val key = DolphinBehaviour.KEY
override val stats = mutableMapOf<RidingStat, IntRange>()
var infiniteStamina: Expression? = null
var canJump = "true".asExpression()
private set
var canJump: Expression? = null
var jumpVector = listOf("0".asExpression(), "0.3".asExpression(), "0".asExpression())
private set
var reverseDriveFactor: Expression? = null
var speed = "1.0".asExpression()
private set
var strafeFactor: Expression? = null
var driveFactor = "1.0".asExpression()
private set
var timeToRollCorrect: Expression? = null
var reverseDriveFactor = "0.25".asExpression()
private set
// Boost influence when boosting
var jumpExpr: Expression? = null
// Yaw rate in degrees per second
var handlingExpr: Expression? = null
var horizontalPitchExpr: Expression? = null
var mouseInputMult: Expression? = null
// Top speed in Bl/s
var speedExpr: Expression? = null
// Seconds to get to top speed
var accelerationExpr: Expression? = null
// Seconds of boost
var staminaExpr: Expression? = null
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)
buffer.writeRidingStats(stats)
rideSounds.encode(buffer)
buffer.writeNullableExpression(infiniteStamina)
buffer.writeNullableExpression(canJump)
buffer.writeNullableExpression(reverseDriveFactor)
buffer.writeNullableExpression(strafeFactor)
buffer.writeNullableExpression(jumpExpr)
buffer.writeNullableExpression(handlingExpr)
buffer.writeNullableExpression(horizontalPitchExpr)
buffer.writeNullableExpression(mouseInputMult)
buffer.writeNullableExpression(speedExpr)
buffer.writeNullableExpression(accelerationExpr)
buffer.writeNullableExpression(staminaExpr)
buffer.writeExpression(canJump)
buffer.writeExpression(jumpVector[0])
buffer.writeExpression(jumpVector[1])
buffer.writeExpression(jumpVector[2])
buffer.writeExpression(speed)
buffer.writeExpression(driveFactor)
buffer.writeExpression(reverseDriveFactor)
buffer.writeExpression(strafeFactor)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
stats.putAll(buffer.readRidingStats())
rideSounds = RideSoundSettingsList.decode(buffer)
infiniteStamina = buffer.readNullableExpression()
canJump = buffer.readNullableExpression()
reverseDriveFactor = buffer.readNullableExpression()
strafeFactor = buffer.readNullableExpression()
jumpExpr = buffer.readNullableExpression()
handlingExpr = buffer.readNullableExpression()
horizontalPitchExpr = buffer.readNullableExpression()
mouseInputMult = buffer.readNullableExpression()
speedExpr = buffer.readNullableExpression()
accelerationExpr = buffer.readNullableExpression()
staminaExpr = buffer.readNullableExpression()
canJump = buffer.readExpression()
jumpVector = listOf(
buffer.readExpression(),
buffer.readExpression(),
buffer.readExpression()
)
speed = buffer.readExpression()
driveFactor = buffer.readExpression()
reverseDriveFactor = buffer.readExpression()
strafeFactor = buffer.readExpression()
}
}
class DolphinState : RidingBehaviourState() {
var lastVelocity = ridingState(Vec3.ZERO, Side.BOTH)
var currRollCorrectionForce = ridingState(0.0, Side.CLIENT)
var currPitchCorrectionForce = ridingState(0.0, Side.CLIENT)
var noInputTime = ridingState(0.0, Side.CLIENT)
var boosting = ridingState(false, Side.BOTH)
var boostIsToggleable = ridingState(false, Side.CLIENT)
var speed = ridingState(0.0, Side.CLIENT)
var hasBreached = ridingState(false, Side.CLIENT)
override fun encode(buffer: FriendlyByteBuf) {
super.encode(buffer)
buffer.writeBoolean(boosting.get())
}
override fun decode(buffer: FriendlyByteBuf) {
super.decode(buffer)
boosting.set(buffer.readBoolean(), forced = true)
}
override fun reset() {
super.reset()
lastVelocity.set(Vec3.ZERO, forced = true)
currRollCorrectionForce.set(0.0, forced = true)
currPitchCorrectionForce.set(0.0, forced = true)
noInputTime.set(0.0, forced = true)
boosting.set(false, forced = true)
boostIsToggleable.set(true, forced = true)
speed.set(0.0, forced = true)
hasBreached.set(false, forced = true)
}
override fun toString(): String {
@@ -669,18 +490,5 @@ class DolphinState : RidingBehaviourState() {
it.stamina.set(stamina.get(), forced = true)
it.rideVelocity.set(rideVelocity.get(), forced = true)
it.lastVelocity.set(lastVelocity.get(), forced = true)
it.currRollCorrectionForce.set(this.currRollCorrectionForce.get(), forced = true)
it.currPitchCorrectionForce.set(this.currPitchCorrectionForce.get(), forced = true)
it.noInputTime.set(this.noInputTime.get(), forced = true)
it.boosting.set(this.boosting.get(), forced = true)
it.boostIsToggleable.set(this.boosting.get(), forced = true)
it.speed.set(this.speed.get(), forced = true)
it.hasBreached.set(this.hasBreached.get(), forced = true)
}
override fun shouldSync(previous: RidingBehaviourState): Boolean {
if (previous !is DolphinState) return false
if (previous.boosting.get() != boosting.get()) return true
return super.shouldSync(previous)
}
}
@@ -42,10 +42,9 @@ class RideLoopSound(val ride: PokemonEntity, val soundSettings: RideSoundSetting
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 no muffle and 0.0 is full hFgain muting
var isPassenger: Boolean = ride.passengers.any { passenger ->
Minecraft.getInstance().player?.id == passenger.id
}
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
@@ -6,16 +6,16 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.riding.sound
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 com.google.gson.annotations.SerializedName
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import net.minecraft.resources.ResourceLocation;
/**
* Class to store data for looping sounds played during riding.
@@ -25,41 +25,31 @@ import net.minecraft.resources.ResourceLocation
*/
data class RideSoundSettings(
val soundLocation: ResourceLocation,
@SerializedName("volumeExpr") private val _volumeExpr: Expression? = "1.0".asExpression(),
@SerializedName("pitchExpr") private val _pitchExpr: Expression? = "1.0".asExpression(),
val volumeExpr: Expression = "1.0".asExpression(),
val pitchExpr: Expression = "1.0".asExpression(),
val playForNonPassengers: Boolean = false,
val muffleEnabled: Boolean = false,
@SerializedName("attenuationModel") private val _attenuationModel: RideAttenuationModel? = RideAttenuationModel.NONE
val attenuationModel: RideAttenuationModel = RideAttenuationModel.NONE
): Encodable {
//this is needed because GSON does NOT support default constructor parameters and will just happily throw nulls all over the place
val volumeExpr: Expression
get() = _volumeExpr ?: "1.0".asExpression()
val pitchExpr: Expression
get() = _pitchExpr ?: "1.0".asExpression()
val attenuationModel: RideAttenuationModel
get() = _attenuationModel ?: RideAttenuationModel.NONE
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(soundLocation)
buffer.writeExpression(volumeExpr)
buffer.writeExpression(pitchExpr)
buffer.writeBoolean(playForNonPassengers)
buffer.writeBoolean(muffleEnabled)
buffer.writeEnum(attenuationModel)
}
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 = buffer.readEnum(RideAttenuationModel::class.java)
val attenuationModel = RideAttenuationModel.EXPONENTIAL
return RideSoundSettings(
soundLocation,
@@ -21,11 +21,11 @@ import net.minecraft.network.chat.Component
enum class RidingStat(
val flavour: Flavour
) {
SPEED(Flavour.SWEET),
ACCELERATION(Flavour.SPICY),
SKILL(Flavour.DRY),
SPEED(Flavour.SWEET),
STAMINA(Flavour.SOUR),
JUMP(Flavour.BITTER);
JUMP(Flavour.BITTER),
STAMINA(Flavour.SOUR);
companion object {
fun getByName(name: String) = RidingStat.entries.firstOrNull { it.name.equals(name, ignoreCase = true) }
Loaded 100 of 2530 files, more files were not shown because too many files have changed in this diff. Show more