Compare commits

..
Author SHA1 Message Date
JPAKx4 9eeeab340c Merge branch 'irl_date_time' into irl_date_time_spawns
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/api/spawning/condition/SpawningCondition.kt
2025-08-08 21:08:31 -05:00
JPAKx4 9163046b45 Merge branch 'main' into irl_date_time
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/api/molang/MoLangFunctions.kt
2025-08-08 21:03:49 -05:00
JPAK d5b5e1055b Merge branch 'irl_date_time_molang' into irl_date_time_spawns 2025-04-22 14:28:06 -05:00
JPAK 06732e89fd Update Changelog and error handling. 2025-04-22 14:27:40 -05:00
JPAK 304f368ae1 Merge branch 'irl_date_time_molang' into irl_date_time_spawns 2025-04-22 12:34:10 -05:00
JPAK 9e8d6a4f39 Add an option for String timezones to "date_of" and "date_local_time" functions. 2025-04-22 12:33:45 -05:00
JPAK e22eeafdea Merge branch 'irl_date_time_molang' into irl_date_time_spawns 2025-04-22 12:11:12 -05:00
JPAK 662efa55c6 Fix imports. 2025-04-22 12:10:39 -05:00
JPAK efb9e538f3 Merge branch 'main' into irl_date_time_molang
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/api/molang/MoLangFunctions.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/config/Category.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/config/CobblemonConfig.kt
2025-04-22 12:03:37 -05:00
JPAK 47e9491e4e Merge remote-tracking branch 'origin/irl_date_time_spawns' into irl_date_time_spawns 2025-02-07 14:36:32 -06:00
JPAK c4713b522b Add "irlDateRange" and "irlTimeRange" as a pokemon spawning condition. 2025-02-07 14:36:22 -06:00
JPAK 42c711cfc7 Fix config and add langs for config. 2025-02-07 14:35:59 -06:00
JPAK 1296acd151 Merge remote-tracking branch 'origin/irl_date_time_spawns' into irl_date_time_spawns 2025-02-07 12:28:39 -06:00
JPAK 1a5ef95879 Add "irlDateRange" and "irlTimeRange" as a pokemon spawning condition. 2025-02-07 12:28:21 -06:00
JPAK 681d5f8bcc Merge remote-tracking branch 'origin/irl_date_time' into irl_date_time_molang
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/config/Category.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/config/CobblemonConfig.kt
2025-02-07 12:27:49 -06:00
JordanandJPAK a52d8452b4 Revert "Add "irlDateRange" and "irlTimeRange" as a pokemon spawning condition."
This reverts commit b0be0b2dca
2025-02-07 12:23:13 -06:00
JPAK e65298b587 Add "irlDateRange" and "irlTimeRange" as a pokemon spawning condition. 2025-02-07 12:23:13 -06:00
JPAK f8c09b8e26 Add config for time zones and add an optional parameter to MoLangFunction's "date_local_time" and "date_of" to use the config time zone instead of the system time zone. 2025-02-07 12:23:11 -06:00
JPAK 5d1019afd5 Add config for time zones and add an optional parameter to MoLangFunction's "date_local_time" and "date_of" to use the config time zone instead of the system time zone. 2025-02-07 12:22:28 -06:00
JPAK f36f126a2b Merge branch 'irl_date_time_molang' into irl_date_time_spawns 2024-12-29 14:46:28 -06:00
JPAK eb9bd116b2 Merge remote-tracking branch 'origin/irl_date_time' into irl_date_time_molang
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/config/CobblemonConfig.kt
2024-12-29 14:28:06 -06:00
JPAK da40245fa6 Add "irlDateRange" and "irlTimeRange" as a pokemon spawning condition. 2024-12-29 14:16:04 -06:00
JPAK 70bf4d6654 Add config for time zones and add an optional parameter to MoLangFunction's "date_local_time" and "date_of" to use the config time zone instead of the system time zone. 2024-12-29 14:14:43 -06:00
Jordan 080a5b9c74 Revert "Add "irlDateRange" and "irlTimeRange" as a pokemon spawning condition."
This reverts commit b0be0b2dca
2024-12-27 19:45:08 +00:00
JPAK b0be0b2dca Add "irlDateRange" and "irlTimeRange" as a pokemon spawning condition. 2024-12-27 13:35:41 -06:00
JPAK d8300436c9 Add config for time zones and add an optional parameter to MoLangFunction's "date_local_time" and "date_of" to use the config time zone instead of the system time zone. 2024-12-26 22:25:15 -06:00
1623 changed files with 23533 additions and 138352 deletions
+26 -88
View File
@@ -40,33 +40,16 @@
- Ground type Pokémon are immune to lightning damage
- Added functionality to Everstone when held by a Pokémon; suppresses evolution notification and hides evolve button in summary interface.
- Added new optional property `attachment_options` for most EmitterShapes to be attached to the locator/entities scale, rotation, and/or position. Position is true by default.
- Galarica Nut bushes now generate on beaches.
- Some Pokémon now pitch their bodies in the direction they're moving.
- Added new advancements Didn't Stop To Think, We Need To Cook, and Pokémon Jockey!.
- 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.
- Galarica Nut bushes now generate on beaches
### Pokémon Added
#### Gen 2
- Marill
- Azumarill
- Hoppip
- Skiploom
- Jumpluff
- Dunsparce
#### Gen 3
- Azurill
- Meditite
- Medicham
- Electrike
- Manectric
- Spoink
- Grumpig
- Swablu
- Altaria
- Snorunt
- Glalie
- Latias
@@ -80,8 +63,6 @@
- Froslass
- Glameow
- Purugly
- Skorupi
- Drapion
#### Gen 5
- Pansage
@@ -104,20 +85,10 @@
- Solosis
- Duosion
- Reuniclus
- Tynamo
- Eelektrik
- Eelektross
- Mienfoo
- Mienshao
- Sewaddle
- Swadloon
- Leavanny
#### Gen 6
- Skiddo
- Gogoat
- Espurr
- Meowstic
- Inkay
- Malamar
- Hawlucha
@@ -126,25 +97,13 @@
- Noivern
#### Gen 7
- Yungoos
- Gumshoos
- Drampa
- Alolan Grimer
- Alolan Muk
- Togedemaru
#### Gen 8
- Skwovet
- Greedent
- Toxel
- Toxtricity
- Morpeko
- Silicobra
- Sandaconda
- Sinistea
- Polteageist
- Galarian Corsola
- Cursola
#### Gen 9
- Smoliv
@@ -157,12 +116,6 @@
- Cyclizar
- Poltchageist
- Sinistcha
- Capsakid
- Scovillain
- Tadbulb
- Bellibolt
- Toedscool
- Toedscruel
### Animation updates for the following Pokémon
- Garchomp
@@ -184,13 +137,6 @@
- Dratini
- Dragonair
- Dragonite
- G. Corsola
- Cursola
- Dunsparce
- Dudunsparce (both forms)
- Porygon
- Porygon2
- Porygon-Z
### Model updates for the following Pokémon
- Gyarados
@@ -238,25 +184,29 @@
- Cacturne
- Taillow
- Swellow
- Added Syrupy Apples.
- Seel
- Dewgong
- Honedge
- Doublade
- Aegislash
- Drowzee
- Hypno
- Mudkip
- Marshtomp
- Swampert
- Shelmet
- Escavalier
- Klink
- Klank
- Klinklang
### Changes
- Changed pokemon caught and seen count to update based on the current pokedex being looked
- Renamed `chargeGainedPerTick` config to `secondsToChargeHealingMachine`.
- Added config setting `maxPokedexScanningDetectionRange` to control from what distance the player can scan Pokémon using the Pokédex.
- Added the `is_standing_on_blocks(depth, blocks...)` MoLang function, which returns whether the specified entity is standing on a specific block or set of blocks. Example usage: `q.is_standing_on_blocks(2, minecraft:sand)`
- Added config setting `hideUnimplementedPokemonInThePokedex` which hides unimplemented Pokémon from the Pokédex when set to true.
- Added molang particle queries for getting distance to targeted entities
- Added debug renderer for poseable entity locators
- Added time zone as a config option
- Added optional boolean to use time zone for MoLang function "date_local_time" and "date_of"
- Added "irlDateRange" and "irlTimeRange" as a condition for spawning
- Added an optional MoValue to either use system timezone when set as true, or use the supplied string as a timezone for MoLang functions `date_local_time` and `date_of`. If the timezone supplied fails to parse this function now returns `0.0`.
### Additions
- Added time zone as a config option
- Added optional boolean to use time zone for MoLang function "date_local_time" and "date_of"
- Made Blocks of Gold count as Big Nuggets when held by a Pokémon (for Fling functionality)
- Players can now eat Sweet and Tart Apples, Whipped Dreams, and the Alcremie Sweets.
- Updated Sweet and Tart Apple sprites
@@ -303,12 +253,6 @@
- Offset in EmitterShape now ignores scale to be more like Blockbench by default. You can get this behaviour back by adding `"scale": true` in the `attachment_options` property in most EmitterShapes.
- Not specifying a dex in `/pokedex printcalculations {player} {dex}` will now print the National Dex statistics instead of showing all dexes. `/pokedex printcalculations {player} all` is how to view all dex statistics in one command.
- Removed Braised Vivichoke
- Updated Pokémon state icons shown in party interfaces
- MoLang `face` function can now be run on any `PosableEntity` (Like Pokémon!) instead of just NPCs.
- MoLang `run_action_effect` now works on Pokémon.
- Changed MoLang entity function `is_standing_on` to allow for block tags in the list.
- Changed MoLang entity function `is_standing_on` to allow for block tags in the list.
- Added entity (as `q.entity`) that executed the command to the `executemolangscript` command.
### Fixes
- Fixed game crashing when removing national pokedex using datapacks
@@ -352,8 +296,8 @@
- Fix issue where locator X axis was not aligned with blockbench.
- Fix issue where particle effects that play on frame 1 on sendout would never play (Gastly)
- The Wiglett line will now sink in water again.
- Fixed the Sprigatito line's portraits being zoomed in too far.
- Fixed Floragato's battle cry animation from breaking.
- Fixed the Sprigatito line's portraits being zoomed in too far.
- Fixed Floragato's battle cry animation from breaking.
- Fixed the block texture name for the Pep-Up Flower being inaccurate
- Fixed issue where the restoration tank would not accept valid items from a hopper.
- Fixed energy root not being shearable
@@ -371,12 +315,8 @@
- Improved flyers avoiding getting stuck on fence posts.
- Fixed air balloon battle text not correctly displaying the Pokémon or item name
- Fixed an issue where items retrieved from a Display case would disappear if a player's inventory is full
- Fixed Pokédex Scanner not respecting the "Invert Mouse" option
- Fixed recoil eyes on Shellder & Cloyster.
- Fixed Shroomish spinning at lightning speed when fainting (no longer allowed to let it rip.)
- Fixed Pokédex Scanner not respecting the "Invert Mouse" option.
- Fixed a crash due to a ConcurrentModificationException that could occur during world generation.
- Fixed Quaxly's animations showing hidden wing sets
- Fixed Moon Ball moon phase logic to actually work correctly
- Fixed `/pokedex printcalculations` to now show the correct percentage completed of the Pokedex
- Fixed mod incompatibility with the `Raised` mod
@@ -393,7 +333,7 @@
- Renamed Cobblemon's creative tabs to start with "Cobblemon: " to distinguish Cobblemon's tabs from tabs for other mods.
- Various items now have a rarity value.
- 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.
- 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.
@@ -424,7 +364,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
### 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)
- The `requiredContext` for an item interaction evolution
@@ -458,8 +398,6 @@
- Added Molang functions for Pokémon: `pokeball`, `held_item`, `remove_held_item`, `hyper_train_iv`, `validate_moveset`, `initialize_moveset`, and `add_exp`.
- Added Molang functions for Pokémon: `aspects`, `form_aspects`, `unlearn_move`, `teach_learnable_moves`, `cosmetic_item`, and `remove_cosmetic_item`.
- Added Molang functions for Pokémon: `ability`, `set_iv`, `set_ev`, `teach_move`, and `can_learn_move`.
- Added Molang function `q.delete_variable(<struct>, <variable_name>)` to delete a value from a variable structure in MoLang data.
- Added Molang function `q.delete_variables(<struct>)` to delete all values from a variable structure in MoLang data.
- Adds Flows for `STARTER_CHOSEN`, `SHOULDER_MOUNTED`, `EV_GAINED`, `POKEMON_RELEASED`, `POKEMON_NICKNAMED`, `HELD_ITEM`, and `TRADE_COMPLETED` events
- Adds Flows for `POKEMON_HEALED`, `POKEMON_SCANNED`, `BERRY_HARVEST`, `LOOT_DROPPED`, `POKEMON_SEEN`, `COLLECT_EGG`, `HATCH_EGG`, and `EXPERIENCE_GAINED`.
- Adds Flows for `POKEMON_CATCH_RATE`, `BAIT_SET`, `BAIT_SET_PRE`, `BAIT_CONSUMED`, `POKEROD_CAST_PRE`, `POKEROD_CAST_POST`, `POKEROD_REEL`, and `BOBBER_SPAWN_POKEMON_PRE`.
@@ -486,7 +424,7 @@
- Added optional box argument to the /pc command.
- Pokédex and Dialogue screens now close when the inventory keybind is pressed.
- Added config setting `maxPokedexScanningDetectionRange` to control from what distance the player can scan Pokémon using the Pokédex.
- Added config setting `hideUnimplementedPokemonInThePokedex` which hides unimplemented Pokémon from the Pokédex when set to true.
- Added config setting `hideUnimplementedPokemonInThePokedex` which hides unimplemented Pokémon from the Pokédex when set to true.
- Added debug renderer for posable entity locators.
- Added crossover paintings from Close Combat, Premonition, Altar, Slumber, and Nomad.
- Added optional box argument to the /pc command
@@ -640,7 +578,7 @@
- Fixed Vivichoke Dip and Leek & Potato Stew not returning a bowl upon consumption.
- Fixed Fossil Restoration Tank not accepting Hay Bales as organic material.
- Fixed Potion items applying double their intended healing value.
- Fixed Fast Ball capture bonuses applying to all Pokémon, making it the Best Ball instead of the Mediocre Edge-Case Ball.
- Fixed Fast Ball capture bonuses applying to all Pokémon, making it the Best Ball instead of the Mediocre Edge-Case Ball.
- Fixed "learned new move" messages appearing for already-known moves on Pokémon evolutions.
- Fixed Pokémon Item Models breaking shadows nearby when being placed in Display Cases or Item Frames.
- Fixed berries not giving bonus yields when planted in their preferred biomes. I'm sure we've fixed that 5 times now.
@@ -649,7 +587,7 @@
- Fixed players with shouldered Pokémon not being able to rejoin their 1.5.2 worlds using 1.6.
- Fixed `PokemonProperties` utilizing `ability=<some ability>` being treated as a forced ability even when it is a legal ability for the Pokémon.
- Fixed type formatting in Pokédex scanner mode when dual types require two lines.
- Fixed trading sometimes crashing the game or server.
- Fixed trading sometimes crashing the game or server.
- Fixed Wild shiny sounds not respecting the `shinyNoticeParticlesDistance` config setting.
- Fixed Pokémon being able to evolve mid-battle.
- Fixed NPC held items being able to be stolen by players. Don't be a thief!
@@ -743,7 +681,7 @@
- Stonjourner Henge Ruins, Luna Henge Ruins, and Sol Henge Ruins.
- Submerged Shipwreck Cove and Lush Shipwreck Cove.
- Pokémon Centers to all 5 village types.
### Pokémon Added
#### Gen 1
- Alola Bias Cubone (built-in resource pack)
@@ -1066,7 +1004,7 @@
- Big Roots and Energy Roots now share the same sounds as intended.
- Fixed sounds made by blocks playing at a lower pitch than intended.
- Fixed Chimchar and Monferno comically T posing whenever they sleep.
- Fixed Cetitan's cry breaking its walk and sleep animations.
- Fixed Cetitan's cry breaking its walk and sleep animations.
- Fixed the Magby line not having any placeholder walk animations.
- Fixed Duskull and Dusclops using skylight levels for their nether spawn data. There is no sun in the Nether!
- Fixed Hisuian Zoroark using base Zoroark stats.
@@ -1082,7 +1020,7 @@
- Fixed Trevenant T-posing after 8 seconds of sleeping. I also do that.
- Fixed Bewear T-posing for a bit if a wild one faints where you can see.
- Fixed Noctowl's placeholder fly animation.
- Corrected Lotad blinking animation.
- Corrected Lotad blinking animation.
- Fixed reviving items causing errors when used in battle.
- Fixed messages for Focus Sash, Confusion, Mummy, Ice Face, Own Tempo, and Revive.
- Improve error handling when loading spawn-sets to ensure invalid configurations don't crash the server.
@@ -1172,7 +1110,7 @@
- Added a new universal locator called "top".
- Added `eggs_collected` and `eggs_hatched` Advancement triggers.
- Added missing `minYaw` configuration to the `q.look()` function for JSON posers.
- Some pose condition names have been changed:
- Some pose condition names have been changed:
- `isTouchingWaterOrRain` has been changed to `isInWaterOrRain`
- `isSubermegedInWater` has been changed to `isUnderWater`
@@ -1,13 +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;
public interface DoubleJump {
boolean isDoubleJumping();
}
@@ -9,6 +9,15 @@
package com.cobblemon.mod.common;
import com.cobblemon.mod.common.api.orientation.OrientationController;
import net.minecraft.util.Mth;
import net.minecraft.util.SmoothDouble;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.Nullable;
import org.joml.Matrix3f;
import org.joml.Vector3f;
import java.util.function.Function;
public interface OrientationControllable {
OrientationController getOrientationController();
@@ -1,18 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.duck;
import org.joml.Vector3f;
public interface PlayerDuck {
void setDriverInput(Vector3f driverInput);
Vector3f getDriverInput();
void setLastSentDriverInput(Vector3f driverInput);
Vector3f getLastSentDriverInput();
}
@@ -8,44 +8,17 @@
package com.cobblemon.mod.common.mixin;
import com.cobblemon.mod.common.OrientationControllable;
import com.cobblemon.mod.common.api.orientation.OrientationController;
import com.cobblemon.mod.common.api.pokemon.effect.ShoulderEffectRegistry;
import com.cobblemon.mod.common.api.riding.Seat;
import com.cobblemon.mod.common.client.entity.PokemonClientDelegate;
import com.cobblemon.mod.common.client.render.MatrixWrapper;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.effect.MobEffectInstance;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.level.ClipContext;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.HitResult;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.NotNull;
import org.joml.Quaternionf;
import org.joml.Vector3f;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(LivingEntity.class)
public abstract class LivingEntityMixin extends Entity implements OrientationControllable {
public LivingEntityMixin(EntityType<?> entityType, Level level) {
super(entityType, level);
}
@Unique
private final OrientationController cobblemon$orientationController = new OrientationController((LivingEntity) (Object) this);
public OrientationController getOrientationController() {
return cobblemon$orientationController;
}
public abstract class LivingEntityMixin {
@Inject(method = "onEffectRemoved", at = @At(value = "TAIL"))
private void cobblemon$onEffectRemoved(MobEffectInstance effect, CallbackInfo ci) {
@@ -55,67 +28,4 @@ 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) {
int seatIndex = pokemonEntity.getPassengers().indexOf(this);
Seat seat = pokemonEntity.getSeats().get(seatIndex);
PokemonClientDelegate delegate = (PokemonClientDelegate) pokemonEntity.getDelegate();
MatrixWrapper locator = delegate.getLocatorStates().get(seat.getLocator());
Vec3 locatorOffset = new Vec3(locator.getMatrix().getTranslation(new Vector3f()));
Vec3 eyePosition = cobblemon$getEyePosition(partialTicks, pokemonEntity, locatorOffset);
Vec3 viewVector = this.getViewVector(partialTicks);
Vec3 viewDistanceVector = eyePosition.add(viewVector.x * hitDistance, viewVector.y * hitDistance, viewVector.z * hitDistance);
return this.level()
.clip(
new ClipContext(
eyePosition, viewDistanceVector, ClipContext.Block.OUTLINE, hitFluids ? net.minecraft.world.level.ClipContext.Fluid.ANY : net.minecraft.world.level.ClipContext.Fluid.NONE, this
)
);
}
return super.pick(hitDistance, partialTicks, hitFluids);
}
@Unique
private Vec3 cobblemon$getEyePosition(float partialTicks, PokemonEntity pokemonEntity, Vec3 locatorOffset) {
OrientationController controller = this.getOrientationController();
float currEyeHeight = this.getEyeHeight();
Vec3 offset = locatorOffset.add(pokemonEntity.position());
if (controller.isActive() && controller.getOrientation() != null) {
Quaternionf orientation = controller.getRenderOrientation(partialTicks);
Vec3 rotatedEyeHeight = new Vec3(orientation.transform(new Vector3f(0f, currEyeHeight - (this.getBbHeight() / 2), 0f)));
offset.add(rotatedEyeHeight);
}
return offset;
}
@Inject(method = "tick", at = @At("HEAD"))
private void cobblemon$updateRenderOrientation(CallbackInfo ci) {
this.cobblemon$orientationController.tick();
}
@Override
public void absMoveTo(double x, double y, double z, float yaw, float pitch) {
if (cobblemon$orientationController.getActive()) {
this.absMoveTo(x, y, z);
this.setYRot(yaw % 360.0f);
this.setXRot(pitch % 360.0f);
this.yRotO = this.getYRot();
this.xRotO = this.getXRot();
} else {
this.absMoveTo(x, y, z);
this.absRotateTo(yaw, pitch);
}
}
}
@@ -18,7 +18,6 @@ import com.cobblemon.mod.common.api.pokemon.PokemonSpecies;
import com.cobblemon.mod.common.api.storage.party.PartyStore;
import com.cobblemon.mod.common.api.storage.party.PlayerPartyStore;
import com.cobblemon.mod.common.api.tags.CobblemonItemTags;
import com.cobblemon.mod.common.duck.PlayerDuck;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import com.cobblemon.mod.common.pokedex.scanner.PokedexEntityData;
import com.cobblemon.mod.common.pokedex.scanner.ScannableEntity;
@@ -48,7 +47,6 @@ import net.minecraft.world.food.FoodProperties;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import org.jetbrains.annotations.Nullable;
import org.joml.Vector3f;
import org.objectweb.asm.Opcodes;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
@@ -63,12 +61,7 @@ import java.util.*;
import java.util.stream.Collectors;
@Mixin(Player.class)
public abstract class PlayerMixin extends LivingEntity implements ScannableEntity, OrientationControllable, PlayerDuck {
@Unique
private Vector3f cobblemon$driverInput;
@Unique
private Vector3f cobblemon$lastSentDriverInput;
public abstract class PlayerMixin extends LivingEntity implements ScannableEntity, OrientationControllable {
@Shadow public abstract CompoundTag getShoulderEntityLeft();
@@ -88,8 +81,8 @@ public abstract class PlayerMixin extends LivingEntity implements ScannableEntit
@Shadow @Final private static Map<Pose, EntityDimensions> POSES;
@Shadow @Final public static EntityDimensions STANDING_DIMENSIONS;
@Shadow protected int jumpTriggerTime;
@Unique private final OrientationController cobblemon$orientationController = new OrientationController(this);
@Unique
private final OrientationController cobblemon$orientationController = new OrientationController(this);
protected PlayerMixin(EntityType<? extends LivingEntity> p_20966_, Level p_20967_) {
super(p_20966_, p_20967_);
@@ -286,25 +279,4 @@ public abstract class PlayerMixin extends LivingEntity implements ScannableEntit
public OrientationController getOrientationController() {
return cobblemon$orientationController;
}
@Override
public void setDriverInput(Vector3f driverInput) {
cobblemon$driverInput = driverInput;
}
@Override
public Vector3f getDriverInput() {
return cobblemon$driverInput;
}
@Override
public void setLastSentDriverInput(Vector3f lastSentDriverInput) {
cobblemon$lastSentDriverInput = lastSentDriverInput;
}
@Override
public Vector3f getLastSentDriverInput() {
return cobblemon$lastSentDriverInput;
}
}
@@ -12,11 +12,9 @@ import com.cobblemon.mod.common.CobblemonNetwork;
import com.cobblemon.mod.common.OrientationControllable;
import com.cobblemon.mod.common.api.net.NetworkPacket;
import com.cobblemon.mod.common.api.orientation.OrientationController;
import com.cobblemon.mod.common.duck.PlayerDuck;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import com.cobblemon.mod.common.mixin.accessor.ChunkMapAccessor;
import com.cobblemon.mod.common.mixin.accessor.TrackedEntityAccessor;
import com.cobblemon.mod.common.net.messages.client.orientation.ClientboundUpdateDriverInputPacket;
import com.cobblemon.mod.common.net.messages.client.orientation.ClientboundUpdateOrientationPacket;
import com.cobblemon.mod.common.net.messages.client.pokemon.update.ClientboundUpdateRidingStatePacket;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
@@ -28,7 +26,6 @@ import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.network.ServerPlayerConnection;
import net.minecraft.world.entity.Entity;
import org.joml.Matrix3f;
import org.joml.Vector3f;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
@@ -58,20 +55,17 @@ public abstract class ServerEntityMixin {
private void cobblemon$sendChanges(CallbackInfo ci) {
cobblemon$sendOrientationChanges();
cobblemon$sendRidingStateChanges();
cobblemon$sendDriverInput();
}
private void cobblemon$sendRidingStateChanges() {
if (!(this.entity instanceof PokemonEntity pokemonEntity)) return;
if (pokemonEntity.getRidingController() == null) return;
var ridingController = pokemonEntity.getRidingController();
if (ridingController.getContext() == null) return;
var context = ridingController.getContext();
var ridingBehaviour = context.getBehaviour();
var ridingState = context.getState();
if (pokemonEntity.getRidingState() == null) return;
if (pokemonEntity.getRiding() == null) return;
var ridingBehaviour = pokemonEntity.getRiding();
var ridingState = pokemonEntity.getRidingState();
var previousRidingState = pokemonEntity.getPreviousRidingState();
if (previousRidingState != null && !ridingState.shouldSync(previousRidingState)) return;
cobblemon$broadcast(new ClientboundUpdateRidingStatePacket(pokemonEntity.getId(), ridingBehaviour, ridingState, null));
cobblemon$broadcast(new ClientboundUpdateRidingStatePacket(pokemonEntity.getId(), ridingBehaviour.getKey(), pokemonEntity.getRidingState(), null));
}
private void cobblemon$sendOrientationChanges() {
@@ -88,19 +82,6 @@ public abstract class ServerEntityMixin {
cobblemon$broadcast(new ClientboundUpdateOrientationPacket(currOrientation, currActive, entity.getId()));
}
private void cobblemon$sendDriverInput() {
if (!(this.entity instanceof ServerPlayer serverPlayer)) return;
if(!(serverPlayer.getVehicle() instanceof PokemonEntity)) return;
Vector3f driverInput = ((PlayerDuck)serverPlayer).getDriverInput();
if(driverInput == null) return;
// If no change in input is detected then don't send a new packet
Vector3f lastSentDriverInput = ((PlayerDuck)serverPlayer).getLastSentDriverInput();
if (driverInput.equals(lastSentDriverInput)) return;
((PlayerDuck)serverPlayer).setLastSentDriverInput(driverInput);
cobblemon$broadcast(new ClientboundUpdateDriverInputPacket(driverInput, entity.getId()));
}
private void cobblemon$broadcast(NetworkPacket<?> packet) {
if (!(entity.level() instanceof ServerLevel level)) return;
ChunkMap chunkMap = level.getChunkSource().chunkMap;
@@ -116,4 +97,5 @@ public abstract class ServerEntityMixin {
}
}
}
@@ -10,10 +10,7 @@ package com.cobblemon.mod.common.mixin;
import com.cobblemon.mod.common.OrientationControllable;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
import com.llamalad7.mixinextras.sugar.Local;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.Entity;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
@@ -46,10 +43,4 @@ public abstract class ServerPlayerMixin {
controllable.getOrientationController().setActive(false);
}
//TODO: Switch to sending out on load instead of preventing saving
@ModifyExpressionValue(method = "addAdditionalSaveData", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/Entity;hasExactlyOnePlayerPassenger()Z"))
private boolean cobblemon$cancelSavingPokemonMounts(boolean original, @Local(ordinal=0) Entity entity) {
return original && !(entity instanceof PokemonEntity);
}
}
@@ -8,7 +8,6 @@
package com.cobblemon.mod.common.mixin.accessor;
import net.minecraft.server.level.ServerEntity;
import net.minecraft.server.network.ServerPlayerConnection;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
@@ -19,7 +18,4 @@ import java.util.Set;
public interface TrackedEntityAccessor {
@Accessor("seenBy")
Set<ServerPlayerConnection> getSeenBy();
@Accessor("serverEntity")
ServerEntity getServerEntity();
}
@@ -8,7 +8,6 @@
package com.cobblemon.mod.common.mixin.client;
import com.cobblemon.mod.common.OrientationControllable;
import com.cobblemon.mod.common.client.render.player.MountedPlayerRenderer;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import net.minecraft.client.model.HumanoidModel;
@@ -60,9 +59,9 @@ public class HumanoidModelMixin {
}
Entity vehicle = entity.getVehicle();
if (vehicle instanceof PokemonEntity pokemonEntity && entity instanceof OrientationControllable) {
if (vehicle instanceof PokemonEntity pokemonEntity && entity instanceof AbstractClientPlayer) {
var shouldRotatePlayerHead = pokemonEntity.ifRidingAvailableSupply(false, (behaviour, settings, state) -> {
return behaviour.shouldRotateRiderHead(settings, state, pokemonEntity);
return behaviour.shouldRotatePlayerHead(settings, state, pokemonEntity);
});
if (!shouldRotatePlayerHead) {
@@ -74,9 +73,7 @@ public class HumanoidModelMixin {
}
}
if (!(entity instanceof AbstractClientPlayer player)) return;
MountedPlayerRenderer.INSTANCE.animate(pokemonEntity, player, relevantPartsByName, netHeadYaw, headPitch, ageInTicks, limbSwing, limbSwingAmount);
MountedPlayerRenderer.INSTANCE.animate(pokemonEntity, (AbstractClientPlayer) entity, relevantPartsByName, netHeadYaw, headPitch, ageInTicks, limbSwing, limbSwingAmount);
}
}
}
@@ -1,37 +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.entity.MountedLivingEntityRenderer;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.model.EntityModel;
import net.minecraft.client.renderer.entity.LivingEntityRenderer;
import net.minecraft.world.entity.LivingEntity;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(LivingEntityRenderer.class)
public class LivingEntityRendererMixin<T extends LivingEntity, M extends EntityModel<T>> {
@Inject(
method = "setupRotations",
at = @At("HEAD")
)
private void cobblemon$modifyRoll(LivingEntity entity, PoseStack poseStack, float bob, float yBodyRot, float partialTick, float scale, CallbackInfo ci) {
if (entity.isPassenger()) {
var vehicle = entity.getVehicle();
if (vehicle instanceof PokemonEntity pokemonEntity) {
MountedLivingEntityRenderer.INSTANCE.render(entity, pokemonEntity, poseStack, bob, yBodyRot, partialTick, scale);
}
}
}
}
@@ -9,7 +9,6 @@
package com.cobblemon.mod.common.mixin.client;
import com.cobblemon.mod.common.CobblemonNetwork;
import com.cobblemon.mod.common.DoubleJump;
import com.cobblemon.mod.common.OrientationControllable;
import com.cobblemon.mod.common.api.orientation.OrientationController;
import com.cobblemon.mod.common.api.riding.Seat;
@@ -18,21 +17,17 @@ import com.cobblemon.mod.common.client.render.MatrixWrapper;
import com.cobblemon.mod.common.client.render.models.blockbench.FloatingState;
import com.cobblemon.mod.common.client.render.models.blockbench.PosableModel;
import com.cobblemon.mod.common.client.render.models.blockbench.repository.VaryingModelRepository;
import com.cobblemon.mod.common.duck.PlayerDuck;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import com.cobblemon.mod.common.net.messages.server.orientation.ServerboundUpdateOrientationPacket;
import com.cobblemon.mod.common.net.messages.server.riding.ServerboundUpdateDriverInputPacket;
import com.mojang.authlib.GameProfile;
import net.minecraft.client.CameraType;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.player.AbstractClientPlayer;
import net.minecraft.client.player.Input;
import net.minecraft.client.player.LocalPlayer;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.ClipContext;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.HitResult;
@@ -51,23 +46,13 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.Map;
@Mixin(LocalPlayer.class)
public abstract class LocalPlayerMixin extends AbstractClientPlayer implements OrientationControllable, DoubleJump {
public abstract class LocalPlayerMixin extends AbstractClientPlayer implements OrientationControllable{
@Unique Matrix3f cobblemon$lastOrientation;
@Unique boolean cobblemon$isDoubleJumping = false;
@Shadow
private float jumpRidingScale;
@Shadow public Input input;
@Shadow private int autoJumpTime;
@Unique private int cobblemon$survivalJumpTriggerTime;
@Unique private boolean cobblemon$isJumping;
public LocalPlayerMixin(ClientLevel clientLevel, GameProfile gameProfile) {
super(clientLevel, gameProfile);
}
@@ -98,23 +83,6 @@ public abstract class LocalPlayerMixin extends AbstractClientPlayer implements O
controllable.getOrientationController().setActive(shouldUseCustomOrientation);
}
@Inject(method = "rideTick", at = @At("HEAD"))
private void cobblemon$updateDriverInputRideTick(CallbackInfo ci) {
if (Minecraft.getInstance().player != (Object)this) return;
if (!(this.getVehicle() instanceof PokemonEntity pokemonEntity)) return;
// Gather driver Input
float vertInput = this.jumping ? 1.0f : (((Player)this).isShiftKeyDown() ? -1.0f : 0.0f);
Vector3f driverInput = new Vector3f(Math.signum(this.xxa), vertInput, Math.signum(this.zza));
// Check if player input has changed. If it has then update the last sent and send it
Vector3f lastSentDriverInput = ((PlayerDuck)this).getLastSentDriverInput();
if (driverInput.equals(lastSentDriverInput)) return;
((PlayerDuck)this).setLastSentDriverInput(driverInput);
CobblemonNetwork.INSTANCE.sendToServer(new ServerboundUpdateDriverInputPacket(driverInput));
}
@Unique
private boolean cobblemon$shouldUseCustomOrientation(LocalPlayer player) {
var playerVehicle = player.getVehicle();
@@ -147,33 +115,6 @@ public abstract class LocalPlayerMixin extends AbstractClientPlayer implements O
}
}
@Inject(method = "aiStep", at = @At("HEAD"))
private void cobblemon$aiStep(CallbackInfo ci) {
this.cobblemon$isDoubleJumping = false;
this.cobblemon$isJumping = this.input.jumping;
}
@Inject(method = "aiStep", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/player/LocalPlayer;isSprinting()Z", shift = At.Shift.BEFORE))
private void cobblemon$updateDoubleJumping(CallbackInfo ci) {
if (!this.cobblemon$isJumping && this.input.jumping && this.autoJumpTime <= 0 && !this.isSwimming()) {
if (this.cobblemon$survivalJumpTriggerTime != 0) {
this.cobblemon$isDoubleJumping = true;
this.cobblemon$survivalJumpTriggerTime = 0;
}
else {
this.cobblemon$survivalJumpTriggerTime = 7;
}
}
else if (this.cobblemon$survivalJumpTriggerTime > 0) {
this.cobblemon$survivalJumpTriggerTime--;
}
}
@Override
public boolean isDoubleJumping() {
return this.cobblemon$isDoubleJumping;
}
@Override
public HitResult pick(double hitDistance, float partialTicks, boolean hitFluids) {
Entity vehicle = this.getVehicle();
@@ -186,7 +127,7 @@ public abstract class LocalPlayerMixin extends AbstractClientPlayer implements O
return super.pick(hitDistance, partialTicks, hitFluids);
}
Vec3 locatorOffset = locator != null ? new Vec3(locator.getMatrix().getTranslation(new Vector3f())) : Vec3.ZERO;
Vec3 locatorOffset = new Vec3(locator.getMatrix().getTranslation(new Vector3f()));
OrientationController controller = this.getOrientationController();
@@ -9,16 +9,20 @@
package com.cobblemon.mod.common.mixin.client;
import com.cobblemon.mod.common.client.render.ClientPlayerIcon;
import com.cobblemon.mod.common.client.render.player.MountedPlayerRenderer;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.model.EntityModel;
import net.minecraft.client.model.PlayerModel;
import net.minecraft.client.player.AbstractClientPlayer;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.entity.EntityRendererProvider;
import net.minecraft.client.renderer.entity.LivingEntityRenderer;
import net.minecraft.client.renderer.entity.player.PlayerRenderer;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
@@ -31,9 +35,9 @@ public abstract class PlayerRendererMixin extends LivingEntityRenderer<AbstractC
super(context, model, shadowRadius);
}
@Inject(method = "render", at = @At(value = "TAIL"))
public void renderPlayer(AbstractClientPlayer player, float f, float g, PoseStack poseStack, MultiBufferSource multiBufferSource, int i, CallbackInfo ci) {
ClientPlayerIcon.Companion.onRenderPlayer(player);
@Inject(method = "render(Lnet/minecraft/world/entity/LivingEntity;FFLcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/MultiBufferSource;I)V", at = @At(value = "TAIL"))
public void cobblemon$clientPlayerIcon(LivingEntity entity, float entityYaw, float partialTicks, PoseStack poseStack, MultiBufferSource buffer, int packedLight, CallbackInfo ci) {
ClientPlayerIcon.Companion.onRenderPlayer((Player) entity);
}
@WrapOperation(method = "getRenderOffset(Lnet/minecraft/client/player/AbstractClientPlayer;F)Lnet/minecraft/world/phys/Vec3;", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/player/AbstractClientPlayer;isCrouching()Z"))
@@ -51,4 +55,17 @@ public abstract class PlayerRendererMixin extends LivingEntityRenderer<AbstractC
playerModel.crouching = false;
}
}
@Inject(
method = "setupRotations(Lnet/minecraft/client/player/AbstractClientPlayer;Lcom/mojang/blaze3d/vertex/PoseStack;FFFF)V",
at = @At("HEAD")
)
private void cobblemon$modifyRoll(AbstractClientPlayer player, PoseStack poseStack, float a, float b, float partialTicks, float i, CallbackInfo ci) {
if (player.isPassenger()) {
var vehicle = player.getVehicle();
if (vehicle instanceof PokemonEntity entity) {
MountedPlayerRenderer.INSTANCE.render(player, entity, poseStack, a, b, partialTicks, i);
}
}
}
}
@@ -89,7 +89,6 @@ object CobblemonBehaviours : JsonDataRegistry<CobblemonBehaviour> {
override fun reload(manager: ResourceManager) {
autoPokemonBehaviours.clear()
autoNPCBehaviours.clear()
val data = mutableMapOf<ResourceLocation, CobblemonBehaviour>()
manager.listResources(resourcePath) { path -> path.endsWith(JSON_EXTENSION) }.forEach { (identifier, resource) ->
resource.open().use { stream ->
@@ -8,6 +8,7 @@
package com.cobblemon.mod.common
import com.cobblemon.mod.common.block.entity.PokeCakeBlockEntity
import com.cobblemon.mod.common.block.entity.BerryBlockEntity
import com.cobblemon.mod.common.block.entity.HealingMachineBlockEntity
import com.cobblemon.mod.common.block.entity.PCBlockEntity
@@ -87,23 +88,35 @@ object CobblemonBlockEntities : PlatformRegistry<Registry<BlockEntityType<*>>, R
BlockEntityType.Builder.of(::DisplayCaseBlockEntity, CobblemonBlocks.DISPLAY_CASE).build(null)
)
// TODO after 1.7
// @JvmField
// val INCENSE_SWEET: BlockEntityType<SweetIncenseBlockEntity> = this.create("incense_sweet",
// BlockEntityType.Builder.of(::SweetIncenseBlockEntity, CobblemonBlocks.INCENSE_SWEET).build(null)
// )
@JvmField
val INCENSE_SWEET: BlockEntityType<SweetIncenseBlockEntity> = this.create("incense_sweet",
BlockEntityType.Builder.of(::SweetIncenseBlockEntity, CobblemonBlocks.INCENSE_SWEET).build(null)
)
@JvmField
val CAMPFIRE: BlockEntityType<CampfireBlockEntity> = this.create("campfire_pot",
BlockEntityType.Builder.of(::CampfireBlockEntity,
CobblemonBlocks.CAMPFIRE,
CobblemonBlocks.SOUL_CAMPFIRE
).build(null)
BlockEntityType.Builder.of(::CampfireBlockEntity, CobblemonBlocks.CAMPFIRE).build(null)
)
/*@JvmField
val LURE_CAKE: BlockEntityType<LureCakeBlockEntity> =
BlockEntityType.Builder.of({ pos, state -> LureCakeBlockEntity(pos, state) },
CobblemonBlocks.LURE_CAKE
).build(null)*/
@JvmField
val POKE_SNACK: BlockEntityType<PokeSnackBlockEntity> = this.create(
"poke_snack",
BlockEntityType.Builder.of(::PokeSnackBlockEntity, CobblemonBlocks.POKE_SNACK, CobblemonBlocks.POKE_CAKE).build(null)
val LURE_CAKE: BlockEntityType<LureCakeBlockEntity> = this.create(
"lure_cake",
BlockEntityType.Builder.of(::LureCakeBlockEntity, CobblemonBlocks.LURE_CAKE).build(null)
)
@JvmField
val POKE_CAKE: BlockEntityType<PokeCakeBlockEntity> = this.create(
"poke_cake",
BlockEntityType.Builder.of(::PokeCakeBlockEntity, CobblemonBlocks.POKE_CAKE).build(null)
)
@JvmField
val CANDLE_POKE_CAKE: BlockEntityType<CandlePokeCakeBlockEntity> = this.create(
"candle_poke_cake",
BlockEntityType.Builder.of(::CandlePokeCakeBlockEntity, CobblemonBlocks.CANDLE_POKE_CAKE).build(null)
)
}
@@ -40,7 +40,6 @@ import net.minecraft.world.level.block.*
import net.minecraft.world.level.block.state.BlockBehaviour
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.properties.BlockSetType
import net.minecraft.world.level.block.state.properties.BlockStateProperties
import net.minecraft.world.level.block.state.properties.BlockStateProperties.LIT
import net.minecraft.world.level.block.state.properties.NoteBlockInstrument
import net.minecraft.world.level.block.state.properties.WoodType
@@ -231,10 +230,8 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
@JvmField
val MEDICINAL_LEEK = this.create("medicinal_leek", MedicinalLeekBlock(BlockBehaviour.Properties.of().pushReaction(PushReaction.DESTROY).ignitedByLava().mapColor(MapColor.CRIMSON_NYLIUM).noCollission().randomTicks().instabreak().sound(CobblemonSounds.MEDICINAL_LEEK_SOUNDS)))
// TODO after 1.7
// @JvmField
// val BUGWORT = this.create("bugwort", BugwortBlock(BlockBehaviour.Properties.of().pushReaction(PushReaction.DESTROY).ignitedByLava().mapColor(MapColor.COLOR_PURPLE).noCollission().randomTicks().instabreak().sound(CobblemonSounds.MEDICINAL_LEEK_SOUNDS)))
@JvmField
val BUGWORT = this.create("bugwort", BugwortBlock(BlockBehaviour.Properties.of().pushReaction(PushReaction.DESTROY).ignitedByLava().mapColor(MapColor.COLOR_PURPLE).noCollission().randomTicks().instabreak().sound(CobblemonSounds.MEDICINAL_LEEK_SOUNDS)))
@JvmField
val ENERGY_ROOT = this.create("energy_root", EnergyRootBlock(BlockBehaviour.Properties.of().pushReaction(PushReaction.DESTROY).ignitedByLava().mapColor(MapColor.DIRT).noCollission().randomTicks().instabreak().sound(CobblemonSounds.ENERGY_ROOT_SOUNDS)))
@JvmField
@@ -243,9 +240,11 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
val REVIVAL_HERB = this.create("revival_herb", RevivalHerbBlock(BlockBehaviour.Properties.of().pushReaction(PushReaction.DESTROY).mapColor(MapColor.PLANT).ignitedByLava().noCollission().instabreak().sound(CobblemonSounds.REVIVAL_HERB_SOUNDS)))
@JvmField
val POKE_CAKE = this.create("poke_cake", PokeSnackBlock(BlockBehaviour.Properties.of().forceSolidOn().strength(0.5F).sound(SoundType.WOOL).pushReaction(PushReaction.DESTROY).lightLevel { if (it.getValue(LIT)) 3 else 0 }, false))
val LURE_CAKE = this.create("lure_cake", LureCakeBlock(BlockBehaviour.Properties.of().forceSolidOn().strength(0.5F).sound(SoundType.WOOL).pushReaction(PushReaction.DESTROY)))
@JvmField
val POKE_SNACK = this.create("poke_snack", PokeSnackBlock(BlockBehaviour.Properties.of().forceSolidOn().strength(0.5F).sound(SoundType.WOOL).pushReaction(PushReaction.DESTROY).lightLevel { if (it.getValue(LIT)) 3 else 0 }, true))
val POKE_CAKE = this.create("poke_cake", PokeCakeBlock(BlockBehaviour.Properties.ofFullCopy(LURE_CAKE)))
@JvmField
val CANDLE_POKE_CAKE = this.create("candle_poke_cake", CandlePokeCakeBlock(BlockBehaviour.Properties.ofFullCopy(LURE_CAKE).lightLevel { blockState -> if (blockState.getValue(LIT) == true) 3 else 0 }))
@JvmField
val TUMBLESTONE_CLUSTER = tumblestoneBlock("tumblestone_cluster", GrowableStoneBlock.STAGE_3, 7, 3, null)
@@ -450,10 +449,7 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
)
@JvmField
val CAMPFIRE = create("campfire", CampfireBlock(BlockBehaviour.Properties.of().sound(SoundType.WOOD).noOcclusion().pushReaction(PushReaction.BLOCK).mapColor(MapColor.PODZOL).strength(2.0F).lightLevel{14}, false))
@JvmField
val SOUL_CAMPFIRE = create("soul_campfire", CampfireBlock(BlockBehaviour.Properties.of().sound(SoundType.WOOD).noOcclusion().pushReaction(PushReaction.BLOCK).mapColor(MapColor.PODZOL).strength(2.0F).lightLevel{9}, true))
val CAMPFIRE = create("campfire", CampfireBlock(BlockBehaviour.Properties.of().sound(SoundType.WOOD).noOcclusion().pushReaction(PushReaction.BLOCK).mapColor(MapColor.PODZOL).strength(2.0F).lightLevel { if ((it.getValue(CampfireBlock.SOUL) as Boolean)) 9 else 14 }))
@JvmField
val RELIC_COIN_POUCH = create("relic_coin_pouch", CoinPouchBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.RELIC_COIN_POUCH_SOUNDS).pushReaction(PushReaction.DESTROY).noOcclusion(), true))
@@ -496,9 +492,8 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
@JvmField
val DISPLAY_CASE = create("display_case", DisplayCaseBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.DISPLAY_CASE_SOUNDS).noOcclusion().pushReaction(PushReaction.BLOCK).mapColor(MapColor.STONE).strength(0.3F).requiresCorrectToolForDrops()))
// TODO after 1.7
// @JvmField
// val INCENSE_SWEET = create("incense_sweet", SweetIncenseBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.DISPLAY_CASE_SOUNDS).noOcclusion().pushReaction(PushReaction.BLOCK).mapColor(MapColor.STONE).strength(0.3f)))
@JvmField
val INCENSE_SWEET = create("incense_sweet", SweetIncenseBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.DISPLAY_CASE_SOUNDS).noOcclusion().pushReaction(PushReaction.BLOCK).mapColor(MapColor.STONE).strength(0.3f)))
@JvmField
val PASTURE = create("pasture", PastureBlock(BlockBehaviour.Properties.of().mapColor(MapColor.COLOR_BROWN).sound(SoundType.WOOD).strength(2F).noOcclusion().pushReaction(PushReaction.BLOCK).lightLevel { if ((it.getValue(PastureBlock.ON) as Boolean) && (it.getValue(PastureBlock.PART) == PastureBlock.PasturePart.TOP)) 13 else 0 }))
@@ -522,7 +517,7 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
@JvmField
val POTTED_PEP_UP_FLOWER = this.create("potted_pep_up_flower", BlocksInvoker.createFlowerPotBlock(PEP_UP_FLOWER))
@JvmField
val HEARTY_GRAINS = this.create("hearty_grains", HeartyGrainsBlock(BlockBehaviour.Properties.of().pushReaction(PushReaction.DESTROY).ignitedByLava().mapColor(MapColor.PLANT).noCollission().randomTicks().instabreak()))
val HEARTY_GRAINS = this.create("hearty_grains", HeartyGrainsBlock(BlockBehaviour.Properties.of().pushReaction(PushReaction.DESTROY).ignitedByLava().mapColor(MapColor.PLANT).noCollission().randomTicks().instabreak().sound(CobblemonSounds.VIVICHOKE_SOUNDS)))
@JvmField
val GALARICA_NUT_BUSH = this.create("galarica_nut_bush", NutBushBlock(BlockBehaviour.Properties.of().mapColor(MapColor.PLANT).randomTicks().noCollission().sound(SoundType.SWEET_BERRY_BUSH).pushReaction(PushReaction.DESTROY)))
@@ -545,11 +540,11 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
val POTTED_SACCHARINE_SAPLING = this.create("potted_saccharine_sapling", BlocksInvoker.createFlowerPotBlock(SACCHARINE_SAPLING))
@JvmField
val HEARTY_GRAIN_BALE = this.create("hearty_grain_bale", BaleBlock(BlockBehaviour.Properties.of().mapColor(MapColor.PLANT).instrument(NoteBlockInstrument.BANJO).strength(0.5F).sound(CobblemonSounds.HEARTY_GRAIN_BALE_SOUNDS)))
val HEARTY_GRAIN_BALE = this.create("hearty_grain_bale", BaleBlock(BlockBehaviour.Properties.of().mapColor(MapColor.PLANT).instrument(NoteBlockInstrument.BANJO).strength(0.5F).sound(SoundType.GRASS)))
@JvmField
val TATAMI_BLOCK = this.create("tatami_block", HorizontalRotationalBlock(BlockBehaviour.Properties.of().ignitedByLava().mapColor(MapColor.PLANT).strength(0.1F).sound(CobblemonSounds.TATAMI_BLOCK_SOUNDS)))
val TATAMI_BLOCK = this.create("tatami_block", HorizontalRotationalBlock(BlockBehaviour.Properties.of().ignitedByLava().mapColor(MapColor.PLANT).strength(0.1F).sound(SoundType.MOSS_CARPET)))
@JvmField
val TATAMI_MAT = this.create("tatami_mat", HorizontalRotationCarpetBlock(BlockBehaviour.Properties.of().ignitedByLava().mapColor(MapColor.PLANT).strength(0.1F).sound(CobblemonSounds.TATAMI_MAT_SOUNDS)))
val TATAMI_MAT = this.create("tatami_mat", HorizontalRotationCarpetBlock(BlockBehaviour.Properties.of().ignitedByLava().mapColor(MapColor.PLANT).strength(0.1F).sound(SoundType.MOSS_CARPET)))
/**
* Returns a map of all the blocks that can be stripped with an axe in the format of input - output.
@@ -59,7 +59,6 @@ object CobblemonCommands {
ApplyPlayerTextureCommand.register(dispatcher)
RenameBoxCommand.register(dispatcher)
ChangeBoxWallpaperCommand.register(dispatcher)
RunMolangCommand.register(dispatcher)
RunMolangScriptCommand.register(dispatcher)
SpectateBattleCommand.register(dispatcher)
CobblemonConfigCommand.register(dispatcher)
@@ -349,15 +349,15 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val SACCHARINE_SAPLING = compostableBlockItem("saccharine_sapling", CobblemonBlocks.SACCHARINE_SAPLING, 0.30f)
// @JvmField
// val BUGWORT = compostableBlockItem("bugwort", CobblemonBlocks.BUGWORT) // TODO after 1.7
@JvmField
val BUGWORT = compostableBlockItem("bugwort", CobblemonBlocks.BUGWORT)
@JvmField
val POKE_BAIT = noSettingsItem("poke_bait")
@JvmField
val POKE_CAKE = this.create("poke_cake", BlockItem(CobblemonBlocks.POKE_CAKE, Item.Properties().stacksTo(1)))
val LURE_CAKE = blockItem("lure_cake", CobblemonBlocks.LURE_CAKE)
@JvmField
val POKE_SNACK = this.create("poke_snack", BlockItem(CobblemonBlocks.POKE_SNACK, Item.Properties().stacksTo(16)))
val POKE_CAKE = blockItem("poke_cake", CobblemonBlocks.POKE_CAKE)
val aprijuices = mutableListOf<AprijuiceItem>()
@JvmField
@@ -386,31 +386,31 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
// FOODS
@JvmField
val SWEET_HEART = noSettingsItem("sweet_heart") // TODO make a SweetHeartItem class for breeding purposes
val SWEET_HEART = noSettingsItem("sweet_heart") // todo make a SweetHeartItem class for breeding purposes
@JvmField
val TASTY_TAIL = create("tasty_tail", foodItem(3, 0.3f))
@JvmField
val PEWTER_CRUNCHIES = regionalFoodItem("pewter_crunchies", 64, 7, 0.343f, false)
val PEWTER_CRUNCHIES = regionalFoodItem("pewter_crunchies", 16, 7, 0.343f, false)
@JvmField
val RAGE_CANDY_BAR = regionalFoodItem("rage_candy_bar", 64, 8, 0.2f, false)
val RAGE_CANDY_BAR = regionalFoodItem("rage_candy_bar", 16, 8, 0.2f, false)
@JvmField
val LAVA_COOKIE = regionalFoodItem("lava_cookie", 64, 2, 0.3f, false)
val LAVA_COOKIE = regionalFoodItem("lava_cookie", 16, 7, 0.343f, false)
@JvmField
val OLD_GATEAU = regionalFoodItem("old_gateau", 64, 9, 0.1335f, false)
val OLD_GATEAU = regionalFoodItem("old_gateau", 16, 9, 0.1335f, false)
@JvmField
val CASTELIACONE = regionalFoodItem("casteliacone", 64, 5, .28f, false)
val CASTELIACONE = regionalFoodItem("casteliacone", 16, 5, .28f, false)
@JvmField
val LUMIOSE_GALETTE = regionalFoodItem("lumiose_galette", 64, 4, 0.4f, false)
val LUMIOSE_GALETTE = regionalFoodItem("lumiose_galette", 16, 8, 0.2f, false)
@JvmField
val BIG_MALASADA = regionalFoodItem("big_malasada", 64, 7, 0.343f, false)
val BIG_MALASADA = regionalFoodItem("big_malasada", 16, 7, 0.343f, false)
@JvmField
val SMOKED_TAIL_CURRY = regionalFoodItem("smoked_tail_curry", 64, 10, 0.6f, false, ItemStack(Items.BOWL, 1))
val SMOKED_TAIL_CURRY = regionalFoodItem("smoked_tail_curry", 16, 10, 0.6f, false, ItemStack(Items.BOWL, 1))
@JvmField
val JUBILIFE_MUFFIN = regionalFoodItem("jubilife_muffin", 64, 8, 0.2f, false)
val JUBILIFE_MUFFIN = regionalFoodItem("jubilife_muffin", 16, 8, 0.2f, false)
@JvmField
val OPEN_FACED_SANDWICH = regionalFoodItem("open_faced_sandwich", 64, 13, 0.5f, false)
val OPEN_FACED_SANDWICH = regionalFoodItem("open_faced_sandwich", 16, 13, 0.5f, false)
// todo we might need to wait on these for later? These impact battles and may be harder to do
/*@JvmField
@@ -421,20 +421,20 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
val TWICE_SPICED_BEETROOT = noSettingsItem("twice_spiced_beetroot") // todo make a TwiceSpiceBeetrootItem class for battle purposes
*/
@JvmField
val POTATO_MOCHI = create("potato_mochi", CobblemonItem(Properties().stacksTo(64)
val POTATO_MOCHI = create("potato_mochi", CobblemonItem(Properties().stacksTo(16)
.food(FoodProperties.Builder()
.nutrition(4)
.saturationModifier(0.4F)
.nutrition(8)
.saturationModifier(0.2375F)
.build())))
@JvmField
val CANDIED_APPLE = create("candied_apple", CobblemonItem(Properties().stacksTo(64)
val CANDIED_APPLE = create("candied_apple", CobblemonItem(Properties().stacksTo(16)
.food(FoodProperties.Builder()
.nutrition(6)
.saturationModifier(0.2335F)
.usingConvertsTo(Items.STICK)
.build())))
@JvmField
val CANDIED_BERRY = create("candied_berry", CobblemonItem(Properties().stacksTo(64)
val CANDIED_BERRY = create("candied_berry", CobblemonItem(Properties().stacksTo(16)
.food(FoodProperties.Builder()
.nutrition(5)
.saturationModifier(0.22F)
@@ -460,8 +460,8 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
val PASTURE = blockItem("pasture", CobblemonBlocks.PASTURE)
@JvmField
val DISPLAY_CASE = blockItem("display_case", CobblemonBlocks.DISPLAY_CASE)
// @JvmField
// val INCENSE_SWEET = blockItem("incense_sweet", CobblemonBlocks.INCENSE_SWEET) // TODO after 1.7
@JvmField
val INCENSE_SWEET = blockItem("incense_sweet", CobblemonBlocks.INCENSE_SWEET)
// Evolution items
@@ -695,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)))
val MOOMOO_MILK = noSettingsItem("moomoo_milk")
@JvmField
val POTION = create("potion", PotionItem(PotionType.POTION))
@@ -23,7 +23,6 @@ import com.cobblemon.mod.common.client.net.data.DataRegistrySyncPacketHandler
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
import com.cobblemon.mod.common.client.net.effect.RunPosableMoLangHandler
import com.cobblemon.mod.common.client.net.effect.SaccharineLogBlockParticlesHandler
import com.cobblemon.mod.common.client.net.effect.SpawnSnowstormEntityParticleHandler
@@ -42,7 +41,6 @@ import com.cobblemon.mod.common.client.net.pasture.UpdatePastureConflictFlagHand
import com.cobblemon.mod.common.client.net.pokedex.ServerConfirmedRegisterHandler
import com.cobblemon.mod.common.client.net.pokemon.update.ClientboundUpdateRidingStateHandler
import com.cobblemon.mod.common.client.net.pokemon.update.PokemonUpdatePacketHandler
import com.cobblemon.mod.common.client.net.riding.ClientboundUpdateDriverInputHandler
import com.cobblemon.mod.common.client.net.settings.OpenCobblemonConfigEditorHandler
import com.cobblemon.mod.common.client.net.settings.ServerSettingsPacketHandler
import com.cobblemon.mod.common.client.net.sound.UnvalidatedPlaySoundS2CPacketHandler
@@ -79,14 +77,12 @@ import com.cobblemon.mod.common.net.messages.client.cooking.ToggleCookingPotLidP
import com.cobblemon.mod.common.net.messages.client.data.*
import com.cobblemon.mod.common.net.messages.client.dialogue.DialogueClosedPacket
import com.cobblemon.mod.common.net.messages.client.dialogue.DialogueOpenedPacket
import com.cobblemon.mod.common.net.messages.client.effect.PokeSnackBlockParticlesPacket
import com.cobblemon.mod.common.net.messages.client.effect.RunPosableMoLangPacket
import com.cobblemon.mod.common.net.messages.client.effect.SaccharineLogBlockParticlesPacket
import com.cobblemon.mod.common.net.messages.client.effect.SpawnSnowstormEntityParticlePacket
import com.cobblemon.mod.common.net.messages.client.effect.SpawnSnowstormParticlePacket
import com.cobblemon.mod.common.net.messages.client.npc.CloseNPCEditorPacket
import com.cobblemon.mod.common.net.messages.client.npc.OpenNPCEditorPacket
import com.cobblemon.mod.common.net.messages.client.orientation.ClientboundUpdateDriverInputPacket
import com.cobblemon.mod.common.net.messages.client.orientation.ClientboundUpdateOrientationPacket
import com.cobblemon.mod.common.net.messages.client.pasture.ClosePasturePacket
import com.cobblemon.mod.common.net.messages.client.pasture.OpenPasturePacket
@@ -177,7 +173,6 @@ import com.cobblemon.mod.common.net.messages.server.pokemon.update.SetMarkingsPa
import com.cobblemon.mod.common.net.messages.server.pokemon.update.ServerboundUpdateRidingStatePacket
import com.cobblemon.mod.common.net.messages.server.pokemon.update.evolution.AcceptEvolutionPacket
import com.cobblemon.mod.common.net.messages.server.riding.DismountPokemonPacket
import com.cobblemon.mod.common.net.messages.server.riding.ServerboundUpdateDriverInputPacket
import com.cobblemon.mod.common.net.messages.server.starter.RequestStarterScreenPacket
import com.cobblemon.mod.common.net.messages.server.storage.SwapPCPartyPokemonPacket
import com.cobblemon.mod.common.net.messages.server.storage.party.MovePartyPokemonPacket
@@ -228,7 +223,6 @@ import com.cobblemon.mod.common.net.serverhandling.pokemon.update.SetActiveMarkH
import com.cobblemon.mod.common.net.serverhandling.pokemon.update.SetMarkingsHandler
import com.cobblemon.mod.common.net.serverhandling.pokemon.update.ServerboundUpdateRidingStateHandler
import com.cobblemon.mod.common.net.serverhandling.riding.DismountPokemonPacketHandler
import com.cobblemon.mod.common.net.serverhandling.riding.DriverInputPacketHandler
import com.cobblemon.mod.common.net.serverhandling.starter.RequestStarterScreenHandler
import com.cobblemon.mod.common.net.serverhandling.starter.SelectStarterPacketHandler
import com.cobblemon.mod.common.net.serverhandling.storage.BenchMoveHandler
@@ -411,7 +405,6 @@ object CobblemonNetwork {
list.add(PacketRegisterInfo(SpawnSnowstormEntityParticlePacket.ID, SpawnSnowstormEntityParticlePacket::decode, SpawnSnowstormEntityParticleHandler))
list.add(PacketRegisterInfo(RunPosableMoLangPacket.ID, RunPosableMoLangPacket::decode, RunPosableMoLangHandler))
list.add(PacketRegisterInfo(SaccharineLogBlockParticlesPacket.ID, SaccharineLogBlockParticlesPacket::decode, SaccharineLogBlockParticlesHandler))
list.add(PacketRegisterInfo(PokeSnackBlockParticlesPacket.ID, PokeSnackBlockParticlesPacket::decode, PokeSnackBlockParticlesHandler))
// Hax
list.add(PacketRegisterInfo(UnvalidatedPlaySoundS2CPacket.ID, UnvalidatedPlaySoundS2CPacket::decode, UnvalidatedPlaySoundS2CPacketHandler))
@@ -472,7 +465,6 @@ object CobblemonNetwork {
// Riding
list.add(PacketRegisterInfo(ClientboundUpdateRidingStatePacket.ID, ClientboundUpdateRidingStatePacket::decode, ClientboundUpdateRidingStateHandler))
list.add(PacketRegisterInfo(ClientboundUpdateDriverInputPacket.ID, ClientboundUpdateDriverInputPacket::decode, ClientboundUpdateDriverInputHandler))
return list
}
@@ -579,7 +571,6 @@ object CobblemonNetwork {
list.add(PacketRegisterInfo(ServerboundUpdateRidingStatsPacket.ID, ServerboundUpdateRidingStatsPacket::decode, ServerboundUpdateRidingStatsHandler))
list.add(PacketRegisterInfo(ServerboundUpdateRidingStatRangePacket.ID, ServerboundUpdateRidingStatRangePacket::decode, ServerboundUpdateRidingStatRangeHandler))
list.add(PacketRegisterInfo(DismountPokemonPacket.ID, DismountPokemonPacket::decode, DismountPokemonPacketHandler))
list.add(PacketRegisterInfo(ServerboundUpdateDriverInputPacket.ID, ServerboundUpdateDriverInputPacket::decode, DriverInputPacketHandler))
// Cooking
list.add(PacketRegisterInfo(ToggleCookingPotLidPacket.ID, ToggleCookingPotLidPacket::decode, ToggleCookingPotLidHandler))
@@ -26,12 +26,11 @@ object CobblemonPoiTypes: PlatformRegistry<Registry<PoiType>, ResourceKey<Regist
@JvmField
val NURSE_KEY = createKey("nurse", CobblemonBlocks.HEALING_MACHINE, 1, 1)
@JvmField
val POKE_SNACK_KEY = createKey("poke_snack", CobblemonBlocks.POKE_SNACK, 0, 1)
val LURE_CAKE_KEY = createKey("lure_cake", CobblemonBlocks.LURE_CAKE, 0, 1)
@JvmField
val SACCHARINE_LOG_SLATHERED_KEY = createKey("saccharine_log_slathered", CobblemonBlocks.SACCHARINE_LOG_SLATHERED, 0, 1)
// TODO after 1.7
// @JvmField
// val INCENSE_SWEET_KEY = createKey("incense_sweet", CobblemonBlocks.INCENSE_SWEET, 0, 1)
@JvmField
val INCENSE_SWEET_KEY = createKey("incense_sweet", CobblemonBlocks.INCENSE_SWEET, 0, 1)
private fun createKey(string: String, block: Block, maxTickets: Int, validRange: Int ): ResourceKey<PoiType> =
ResourceKey.create(Registries.POINT_OF_INTEREST_TYPE, cobblemonResource(string)).also { resourceKey ->
@@ -204,19 +204,6 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, ResourceKey<Regi
@JvmField
val EVOLUTION_STONE_BLOCK_STEP = this.create("block.evolution_stone_block.step")
@JvmField
val TATAMI_BLOCK_BREAK = this.create("block.tatami.break")
@JvmField
val TATAMI_BLOCK_HIT = this.create("block.tatami.hit")
@JvmField
val TATAMI_BLOCK_PLACE = this.create("block.tatami.place")
@JvmField
val TATAMI_BLOCK_STEP = this.create("block.tatami.step")
@JvmField
val TATAMI_MAT_BREAK = this.create("block.tatami_mat.break")
@JvmField
val TATAMI_MAT_PLACE = this.create("block.tatami_mat.place")
@JvmField
val GIMMIGHOUL_GIVE_ITEM = this.create("pokemon.gimmighoul.give_item")
@JvmField
@@ -243,24 +230,6 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, ResourceKey<Regi
@JvmField
val VIVICHOKE_PLACE = this.create("block.vivichoke.place")
@JvmField
val HEARTY_GRAIN_BALE_BREAK = this.create("block.hearty_grain_bale.break")
@JvmField
val HEARTY_GRAIN_BALE_PLACE = this.create("block.hearty_grain_bale.place")
@JvmField
val HEARTY_GRAIN_BALE_HIT = this.create("block.hearty_grain_bale.hit")
@JvmField
val HEARTY_GRAIN_BALE_STEP = this.create("block.hearty_grain_bale.step")
@JvmField
val HEARTY_GRAINS_BREAK = this.create("block.hearty_grains.break")
@JvmField
val HEARTY_GRAINS_PLACE = this.create("block.hearty_grains.place")
@JvmField
val HEARTY_GRAINS_BREAK_WATER = this.create("block.hearty_grains.break_water")
@JvmField
val HEARTY_GRAINS_PLACE_WATER = this.create("block.hearty_grains.place_water")
@JvmField
val MINT_BREAK = this.create("block.mint.break")
@JvmField
@@ -355,8 +324,8 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, ResourceKey<Regi
@JvmField
val IMPACT_WATER = this.create("impact.water")
// Note to self or whoever's peeking, block sounds have to be at 1.1f pitch
// For some reason, the game pitches them down and compensating for it here returns them to normal
@JvmField
val TUMBLESTONE_SOUNDS = SoundType(1f, 1.1f,
TUMBLESTONE_BREAK,
@@ -374,7 +343,6 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, ResourceKey<Regi
TUMBLESTONE_HIT,
TUMBLESTONE_STEP
)
@JvmField
val EVOLUTION_STONE_BLOCK_SOUNDS = SoundType(1f, 1.1f,
EVOLUTION_STONE_BLOCK_BREAK,
@@ -384,23 +352,6 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, ResourceKey<Regi
EVOLUTION_STONE_BLOCK_STEP
)
@JvmField
val TATAMI_BLOCK_SOUNDS = SoundType(1f, 1.1f,
TATAMI_BLOCK_BREAK,
TATAMI_BLOCK_STEP,
TATAMI_BLOCK_PLACE,
TATAMI_BLOCK_HIT,
TATAMI_BLOCK_STEP
)
@JvmField
val TATAMI_MAT_SOUNDS = SoundType(1f, 1.1f,
TATAMI_MAT_BREAK,
TATAMI_BLOCK_STEP,
TATAMI_MAT_PLACE,
TATAMI_BLOCK_HIT,
TATAMI_BLOCK_STEP
)
@JvmField
val BERRY_BUSH_SOUNDS = SoundType(0.8f, 1.1f,
@@ -447,33 +398,6 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, ResourceKey<Regi
SoundEvents.GRASS_FALL
)
@JvmField
val HEARTY_GRAIN_BALE_SOUNDS = SoundType(0.6f, 1.1f,
HEARTY_GRAIN_BALE_BREAK,
HEARTY_GRAIN_BALE_STEP,
HEARTY_GRAIN_BALE_PLACE,
HEARTY_GRAIN_BALE_HIT,
HEARTY_GRAIN_BALE_STEP
)
@JvmField
val HEARTY_GRAINS_SOUNDS = SoundType(0.6f, 1.1f,
HEARTY_GRAINS_BREAK,
HEARTY_GRAIN_BALE_STEP,
HEARTY_GRAINS_PLACE,
HEARTY_GRAIN_BALE_HIT,
HEARTY_GRAIN_BALE_STEP
)
@JvmField
val HEARTY_GRAINS_WATER_SOUNDS = SoundType(0.6f, 1.1f,
HEARTY_GRAINS_BREAK_WATER,
SoundEvents.GRASS_STEP,
HEARTY_GRAINS_PLACE_WATER,
SoundEvents.GRASS_HIT,
SoundEvents.GRASS_FALL
)
@JvmField
val MINT_SOUNDS = SoundType(0.6f, 1.1f,
MINT_BREAK,
@@ -513,6 +437,7 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, ResourceKey<Regi
val DISPLAY_CASE_PLACE = this.create("block.display_case.place")
@JvmField
val DISPLAY_CASE_STEP = this.create("block.display_case.step")
@JvmField
val DISPLAY_CASE_SOUNDS = SoundType(1f, 1f,
DISPLAY_CASE_BREAK,
@@ -523,7 +448,7 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, ResourceKey<Regi
)
@JvmField
val CAMPFIRE_POT_SET = this.create("block.campfire_pot.set")
val CAMPFIRE_POT_PLACE_CAMPFIRE = this.create("block.campfire_pot.place_campfire")
@JvmField
val CAMPFIRE_POT_RETRIEVE = this.create("block.campfire_pot.retrieve")
@JvmField
@@ -531,14 +456,12 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, ResourceKey<Regi
@JvmField
val CAMPFIRE_POT_CLOSE = this.create("block.campfire_pot.close")
@JvmField
val CAMPFIRE_POT_TAKE_ITEM = this.create("block.campfire_pot.take_item")
@JvmField
val CAMPFIRE_POT_ACTIVE = this.create("block.campfire_pot.active")
@JvmField
val CAMPFIRE_POT_AMBIENT = this.create("block.campfire_pot.ambient")
val CAMPFIRE_POT_USE = this.create("block.campfire_pot.use")
@JvmField
val CAMPFIRE_POT_COOK = this.create("block.campfire_pot.cook")
@JvmField
val CAMPFIRE_POT_CRAFT = this.create("block.campfire_pot.craft")
@JvmField
val CAMPFIRE_POT_PLACE = this.create("block.campfire_pot.place")
@JvmField
val CAMPFIRE_POT_BREAK = this.create("block.campfire_pot.break")
@@ -15,12 +15,9 @@ import com.cobblemon.mod.common.battles.runner.ShowdownService
import com.cobblemon.mod.common.net.messages.client.data.AbilityRegistrySyncPacket
import com.cobblemon.mod.common.pokemon.abilities.HiddenAbilityType
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
import kotlin.collections.set
/**
* Registry for all known Abilities
@@ -34,31 +31,15 @@ object Abilities : DataRegistry {
val DUMMY = AbilityTemplate(name = "dummy")
private val abilityMap = mutableMapOf<String, AbilityTemplate>()
internal val abilityScripts = mutableMapOf<String, String>() // abilityId to JavaScript
override fun reload(manager: ResourceManager) {
PotentialAbility.types.clear()
PotentialAbility.types.add(CommonAbilityType)
PotentialAbility.types.add(HiddenAbilityType)
this.abilityMap.clear()
this.abilityScripts.clear()
ShowdownService.service.resetRegistryData("ability")
manager.listResources("abilities") { 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()
abilityScripts[resolvedIdentifier.path] = js
}
}
}
ShowdownService.service.sendRegistryData(abilityScripts, "ability")
val abilitiesJson = ShowdownService.service.getRegistryData("ability")
val abilitiesJson = ShowdownService.service.getAbilityIds()
for (i in 0 until abilitiesJson.size()) {
val jsonAbility = abilitiesJson[i].asJsonObject
val id = jsonAbility.get("id").asString
val id = abilitiesJson[i].asString
val ability = AbilityTemplate(id)
this.register(ability)
}
@@ -95,7 +95,6 @@ class Berry(
val fruitModelIdentifier: ResourceLocation,
val fruitTexture: ResourceLocation,
val stageOnePositioning: GrowthPoint,
val pokeSnackPositionings: Array<GrowthPoint>,
val weight: Float,
val boneMealChance: Float
) {
@@ -294,14 +293,6 @@ class Berry(
buffer.writeDouble(stageOnePositioning.rotation.x)
buffer.writeDouble(stageOnePositioning.rotation.y)
buffer.writeDouble(stageOnePositioning.rotation.z)
buffer.writeCollection(this.pokeSnackPositionings.toList()) { writer, value ->
writer.writeDouble(value.position.x)
writer.writeDouble(value.position.y)
writer.writeDouble(value.position.z)
writer.writeDouble(value.rotation.x)
writer.writeDouble(value.rotation.y)
writer.writeDouble(value.rotation.z)
}
buffer.writeFloat(boneMealChance)
}
@@ -369,12 +360,9 @@ class Berry(
val flowerTexture = buffer.readIdentifier()
val fruitModelIdentifier = buffer.readIdentifier()
val fruitTexture = buffer.readIdentifier()
val stageOnePos = GrowthPoint(Vec3(buffer.readDouble(), buffer.readDouble(), buffer.readDouble()), Vec3(buffer.readDouble(), buffer.readDouble(), buffer.readDouble()))
val pokeSnackPos = buffer.readList { reader ->
GrowthPoint(Vec3(reader.readDouble(), reader.readDouble(), reader.readDouble()), Vec3(reader.readDouble(), reader.readDouble(), reader.readDouble()))
}.toTypedArray()
val stageOneYPos = GrowthPoint(Vec3(buffer.readDouble(), buffer.readDouble(), buffer.readDouble()), Vec3(buffer.readDouble(), buffer.readDouble(), buffer.readDouble()))
val boneMealChance = buffer.readFloat()
return Berry(identifier, baseYield, emptyList(), growthTime, refreshRate, favMulchs, emptySet(), emptyList(), growthPoints, randomizedGrowthPoints, mutations, sproutShapeBoxes, matureShapeBoxes, flavors, colour, tintIndexes, flowerModelIdentifier, flowerTexture, fruitModelIdentifier, fruitTexture, stageOnePos, pokeSnackPos, 0F, boneMealChance)
return Berry(identifier, baseYield, emptyList(), growthTime, refreshRate, favMulchs, emptySet(), emptyList(), growthPoints, randomizedGrowthPoints, mutations, sproutShapeBoxes, matureShapeBoxes, flavors, colour, tintIndexes, flowerModelIdentifier, flowerTexture, fruitModelIdentifier, fruitTexture, stageOneYPos, 0F, boneMealChance)
}
}
@@ -12,7 +12,6 @@ import com.bedrockk.molang.runtime.MoLangEnvironment
import com.bedrockk.molang.runtime.MoLangRuntime
import com.bedrockk.molang.runtime.MoParams
import com.bedrockk.molang.runtime.struct.ArrayStruct
import com.bedrockk.molang.runtime.struct.MoStruct
import com.bedrockk.molang.runtime.struct.QueryStruct
import com.bedrockk.molang.runtime.struct.VariableStruct
import com.bedrockk.molang.runtime.value.DoubleValue
@@ -39,7 +38,6 @@ import com.cobblemon.mod.common.api.moves.Moves
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
import com.cobblemon.mod.common.api.moves.animations.ActionEffects
import com.cobblemon.mod.common.api.moves.animations.NPCProvider
import com.cobblemon.mod.common.api.moves.animations.TargetsProvider
import com.cobblemon.mod.common.api.npc.NPCClasses
import com.cobblemon.mod.common.api.npc.configuration.interaction.DialogueNPCInteractionConfiguration
import com.cobblemon.mod.common.api.npc.configuration.interaction.ScriptNPCInteractionConfiguration
@@ -145,6 +143,12 @@ 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 java.time.Instant
import java.time.LocalDateTime
import java.time.ZoneId
import java.time.ZoneOffset
import java.time.format.DateTimeFormatter
import java.util.*
/**
* Holds a bunch of useful MoLang trickery that can be used or extended in API
@@ -158,17 +162,6 @@ object MoLangFunctions {
val message = params.get<MoValue>(0).asString()
Cobblemon.LOGGER.info(message)
},
"delete_variable" to java.util.function.Function { params ->
val struct = params.get<VariableStruct>(0)
val variable = params.getString(1)
struct.map.remove(variable)
DoubleValue.ONE
},
"delete_variables" to java.util.function.Function { params ->
val struct = params.get<VariableStruct>(0)
struct.map.clear()
DoubleValue.ONE
},
"set_query" to java.util.function.Function { params ->
val variable = params.getString(0)
val value = params.get<MoValue>(1)
@@ -302,16 +295,44 @@ object MoLangFunctions {
DoubleValue(System.currentTimeMillis())
},
// the rest of the world use dd/MM/yyyy grow up america (this comment was generated by copilot)
"date_local_time" to java.util.function.Function { _ ->
val time = System.currentTimeMillis()
val date = java.util.Date(time)
val formatted = java.text.SimpleDateFormat("DD/MM/YYYY").format(date)
"date_local_time" to java.util.function.Function { params ->
val useTimeZone = params.get<MoValue>(0)
var timezone: ZoneId? = null
if (useTimeZone is DoubleValue){
timezone = if (useTimeZone.value == 1.0) Cobblemon.config.timeZoneId else null
} else if (useTimeZone is StringValue){
try {
timezone = ZoneId.of(useTimeZone.value)
} catch (_: Exception) {
Cobblemon.LOGGER.error("Error parsing timezone: ${useTimeZone.value}")
return@Function DoubleValue.ZERO
}
}
val date: LocalDateTime = if (timezone != null) LocalDateTime.now(timezone) else LocalDateTime.now()
val formatted = DateTimeFormatter.ofPattern("dd/MM/uuuu").format(date)
StringValue(formatted)
},
"date_of" to java.util.function.Function { params ->
val time = params.getDouble(0).toLong()
val useTimeZone = params.get<MoValue>(1)
var timezone: ZoneId? = null
if (useTimeZone is DoubleValue){
timezone = if (useTimeZone.value == 1.0) Cobblemon.config.timeZoneId else null
} else if (useTimeZone is StringValue){
try {
timezone = ZoneId.of(useTimeZone.value)
} catch (_: Exception) {
Cobblemon.LOGGER.error("Error parsing timezone: ${useTimeZone.value}")
return@Function DoubleValue.ZERO
}
}
val date = java.util.Date(time)
val formatted = java.text.SimpleDateFormat("DD/MM/YYYY").format(date)
val instant: Instant = date.toInstant()
val formatted = DateTimeFormatter.ofPattern("dd/MM/uuuu").format(LocalDateTime.ofInstant(instant,
timezone ?: ZoneOffset.systemDefault()
))
StringValue(formatted)
},
"date_is_after" to java.util.function.Function { params ->
@@ -673,7 +694,7 @@ object MoLangFunctions {
map
}
)
val itemStackFunctions = mutableListOf<(ItemStack, RegistryAccess) -> HashMap<String, java.util.function.Function<MoParams, Any>>>(
val itemStackFunctions = mutableListOf<(ItemStack, RegistryAccess) -> java.util.HashMap<String, java.util.function.Function<MoParams, Any>>>(
{ stack, registryAccess ->
val itemRegistry = registryAccess.registryOrThrow(Registries.ITEM)
val holder = itemRegistry.wrapAsHolder(stack.item)
@@ -692,7 +713,7 @@ object MoLangFunctions {
}
)
val entityFunctions: MutableList<(Entity) -> HashMap<String, java.util.function.Function<MoParams, Any>>> = mutableListOf(
val entityFunctions: MutableList<(Entity) -> java.util.HashMap<String, java.util.function.Function<MoParams, Any>>> = mutableListOf(
{ entity ->
val map = hashMapOf<String, java.util.function.Function<MoParams, Any>>()
map.put("uuid") { _ -> StringValue(entity.uuid.toString()) }
@@ -720,9 +741,6 @@ object MoLangFunctions {
val owner = entity.owner
return@put owner?.asMostSpecificMoLangValue() ?: DoubleValue.ZERO
}
map.put("delta_movement") {
return@put listOf(entity.deltaMovement.x, entity.deltaMovement.y, entity.deltaMovement.z).asArrayValue(::DoubleValue)
}
if (entity is MoLangScriptingEntity) {
map.put("add_callback") { params ->
@@ -867,12 +885,14 @@ object MoLangFunctions {
}
map.put("is_standing_on_blocks") { params ->
val depth = params.getDouble(0).toInt()
val blockStrings: MutableSet<String> = mutableSetOf()
val blocks: MutableSet<Block> = mutableSetOf()
for (blockIndex in 1..<params.params.size) {
blockStrings.add(params.getString(blockIndex))
val blockString = params.getString(blockIndex)
val block = BuiltInRegistries.BLOCK.get(blockString.asIdentifierDefaultingNamespace("minecraft"))
blocks.add(block)
}
return@put if (entity.isStandingOn(blockStrings, depth)) DoubleValue.ONE else DoubleValue.ZERO
return@put if (entity.isStandingOn(blocks, depth)) DoubleValue.ONE else DoubleValue.ZERO
}
if (entity is PosableEntity) {
map.put("play_animation") { params ->
@@ -898,10 +918,6 @@ object MoLangFunctions {
return@put DoubleValue.ONE
}
}
map.put("face") { params -> ObjectValue(ReferenceDialogueFaceProvider(entity.id, params.getBooleanOrNull(0) != false))
}
}
if (entity is Schedulable) {
map.put("run_molang_after") { params ->
@@ -935,7 +951,7 @@ object MoLangFunctions {
}
)
val livingEntityFunctions: MutableList<(LivingEntity) -> HashMap<String, java.util.function.Function<MoParams, Any>>> = mutableListOf<(LivingEntity) -> HashMap<String, java.util.function.Function<MoParams, Any>>>(
val livingEntityFunctions: MutableList<(LivingEntity) -> java.util.HashMap<String, java.util.function.Function<MoParams, Any>>> = mutableListOf<(LivingEntity) -> java.util.HashMap<String, java.util.function.Function<MoParams, Any>>>(
{ entity ->
val map = hashMapOf<String, java.util.function.Function<MoParams, Any>>()
map.put("is_living_entity") { DoubleValue.ONE }
@@ -1071,6 +1087,7 @@ object MoLangFunctions {
map.put("name") { StringValue(npc.name.string) }
map.put("level") { DoubleValue(npc.level) }
map.put("has_aspect") { params -> DoubleValue(npc.aspects.contains(params.getString(0))) }
map.put("face") { params -> ObjectValue(ReferenceDialogueFaceProvider(npc.id, params.getBooleanOrNull(0) != false)) }
map.put("in_battle") { DoubleValue(npc.isInBattle()) }
map.put("battles") { ArrayStruct(npc.battleIds.mapNotNull { BattleRegistry.getBattle(it)?.struct }.mapIndexed { index, value -> "$index" to value }.toMap()) }
map.put("stop_battles") { _ -> npc.battleIds.forEach { BattleRegistry.getBattle(it)?.stop() } }
@@ -1676,8 +1693,6 @@ object MoLangFunctions {
map.put("get_alt_pose") { StringValue(pokemonEntity.getAltPose()) }
map.put("is_gliding") { DoubleValue(pokemonEntity.isUsingAltPose(cobblemonResource("gliding"))) }
map.put("is_sprinting") { DoubleValue(pokemonEntity.isUsingAltPose(cobblemonResource("sprinting"))) }
map.put("is_drifting") { DoubleValue(pokemonEntity.isUsingAltPose(cobblemonResource("drifting"))) }
map.put("is_powered_drifting") { DoubleValue(pokemonEntity.isUsingAltPose(cobblemonResource("powered_drifting"))) }
map.put("in_air") { DoubleValue(pokemonEntity.isUsingAltPose(cobblemonResource("in_air"))) }
map.put("is_wild") { DoubleValue(pokemonEntity.ownerUUID == null) }
map.put("is_in_party") { DoubleValue(pokemonEntity.pokemon.storeCoordinates.get()?.store is PartyStore) }
@@ -1695,34 +1710,6 @@ object MoLangFunctions {
map.put("pasture_conflict_enabled") {
DoubleValue(pokemonEntity.getBehaviourFlag(PokemonBehaviourFlag.PASTURE_CONFLICT))
}
map.put("run_action_effect") { params ->
val runtime = MoLangRuntime().setup()
runtime.environment.cloneFrom(params.environment)
runtime.withQueryValue("entity", pokemonEntity.struct)
val actionEffect = ActionEffects.actionEffects[params.getString(0).asIdentifierDefaultingNamespace()]
if (actionEffect != null) {
val context = ActionEffectContext(
actionEffect = actionEffect,
providers = mutableListOf(TargetsProvider(pokemonEntity)),
runtime = runtime,
level = pokemonEntity.level()
)
// This was taken from NPC run_action_effect but this variable isn't used, so I just commented it out.
// pokemonEntity.actionEffect = context
pokemonEntity.brain.setMemory(CobblemonMemories.ACTIVE_ACTION_EFFECT, context)
pokemonEntity.brain.setActiveActivityIfPossible(CobblemonActivities.ACTION_EFFECT)
actionEffect.run(context).thenRun {
val pokemonActionEffect = pokemonEntity.brain.getMemory(CobblemonMemories.ACTIVE_ACTION_EFFECT).orElse(null)
if (pokemonActionEffect == context && pokemonEntity.brain.isActive(CobblemonActivities.ACTION_EFFECT)) {
pokemonEntity.brain.eraseMemory(CobblemonMemories.ACTIVE_ACTION_EFFECT)
//pokemonEntity.actionEffect = null
}
}
return@put DoubleValue.ONE
}
return@put DoubleValue.ZERO
}
map
}
)
@@ -2033,7 +2020,7 @@ object MoLangFunctions {
}
)
val pokemonPropertiesFunctions: MutableList<(PokemonProperties) -> HashMap<String, java.util.function.Function<MoParams, Any>>> = mutableListOf(
val pokemonPropertiesFunctions: MutableList<(PokemonProperties) -> java.util.HashMap<String, java.util.function.Function<MoParams, Any>>> = mutableListOf(
{ props ->
val map = hashMapOf<String, java.util.function.Function<MoParams, Any>>()
@@ -2091,7 +2078,7 @@ object MoLangFunctions {
}
)
val evolutionFunctions: MutableList<(Evolution) -> HashMap<String, java.util.function.Function<MoParams, Any>>> = mutableListOf(
val evolutionFunctions: MutableList<(Evolution) -> java.util.HashMap<String, java.util.function.Function<MoParams, Any>>> = mutableListOf(
{ evolution ->
val map = hashMapOf<String, java.util.function.Function<MoParams, Any>>()
@@ -21,12 +21,9 @@ import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import com.cobblemon.mod.common.util.cobblemonResource
import com.google.gson.JsonArray
import com.google.gson.JsonObject
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
import kotlin.collections.set
/**
* Registry for all known Moves
@@ -39,26 +36,11 @@ object Moves : DataRegistry {
private val allMoves = mutableMapOf<String, MoveTemplate>()
private val idMapping = mutableMapOf<Int, MoveTemplate>()
internal val moveScripts = mutableMapOf<String, String>() // moveId to JavaScript
override fun reload(manager: ResourceManager) {
this.allMoves.clear()
this.idMapping.clear()
this.moveScripts.clear()
ShowdownService.service.resetRegistryData("move")
manager.listResources("moves") { 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()
moveScripts[resolvedIdentifier.path] = js
}
}
}
ShowdownService.service.sendRegistryData(moveScripts, "move")
val movesJson = ShowdownService.service.getRegistryData("move")
val movesJson = ShowdownService.service.getMoves()
for (i in 0 until movesJson.size()) {
val jsMove = movesJson[i].asJsonObject
val id = jsMove.get("id").asString
@@ -10,12 +10,14 @@ package com.cobblemon.mod.common.api.orientation
import com.cobblemon.mod.common.util.math.geometry.toDegrees
import com.cobblemon.mod.common.util.math.geometry.toRadians
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.Mth
import net.minecraft.world.entity.LivingEntity
import org.joml.Matrix3f
import org.joml.Quaternionf
import org.joml.Vector3f
import kotlin.math.asin
import kotlin.math.sign
open class OrientationController(val entity: LivingEntity) {
@@ -132,8 +132,6 @@ object CobblemonPermissions {
@JvmStatic
val ABANDON_MULTITEAM = this.create("${COMMAND_PREFIX}abandonmultiteam", PermissionLevel.NONE)
@JvmStatic
val RUN_MOLANG = this.create("${COMMAND_PREFIX}runmolang", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
@JvmStatic
val RUN_MOLANG_SCRIPT = this.create("${COMMAND_PREFIX}runmolangscript", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
@JvmStatic
val COBBLEMON_CONFIG_RELOAD = this.create("${COMMAND_PREFIX}cobblemonconfig.reload", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
@@ -42,7 +42,6 @@ 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
@@ -77,7 +76,6 @@ import net.minecraft.world.item.Item
import net.minecraft.world.level.biome.Biome
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.levelgen.structure.Structure
import net.minecraft.world.level.material.Fluid
import net.minecraft.world.phys.AABB
object PokemonSpecies : JsonDataRegistry<Species> {
@@ -125,7 +123,6 @@ object PokemonSpecies : JsonDataRegistry<Species> {
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Block::class.java).type, BlockLikeConditionAdapter)
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Item::class.java).type, ItemLikeConditionAdapter)
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Structure::class.java).type, StructureLikeConditionAdapter)
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Fluid::class.java).type, FluidLikeConditionAdapter)
.registerTypeAdapter(EggGroup::class.java, EggGroupAdapter)
.registerTypeAdapter(MobEffect::class.java, RegistryElementAdapter<MobEffect>(BuiltInRegistries::MOB_EFFECT))
.registerTypeAdapter(ItemPredicate::class.java, LegacyItemConditionWrapperAdapter)
@@ -160,8 +157,7 @@ object PokemonSpecies : JsonDataRegistry<Species> {
}
this.species.forEach(Species::resolveEvolutionMoves)
Cobblemon.showdownThread.queue {
it.resetRegistryData("species")
it.sendRegistryData(allShowdownSpecies(), "species")
it.registerSpecies()
it.indicateSpeciesInitialized()
// Reload this with the mod
CobblemonHeldItemManager.load()
@@ -316,18 +312,4 @@ object PokemonSpecies : JsonDataRegistry<Species> {
return "${species.resourceIdentifier.namespace}:${species.name}"
}
internal fun allShowdownSpecies(): Map<String, String> {
val result = mutableMapOf<String, String>()
this.species.forEach {species ->
val baseSpecies = ShowdownSpecies(species, null)
result[baseSpecies.name] = this.gson.toJson(baseSpecies)
species.forms.forEach { form ->
if (form != species.standardForm) {
val formSpecies = ShowdownSpecies(species, form)
result[formSpecies.name] = this.gson.toJson(formSpecies)
}
}
}
return result
}
}
@@ -8,11 +8,8 @@
package com.cobblemon.mod.common.api.pokemon.aspect
import com.cobblemon.mod.common.CobblemonBlocks
import com.cobblemon.mod.common.api.spawning.influence.SaccharineLogSlatheredInfluence
import com.cobblemon.mod.common.api.spawning.prospecting.LurePokeSnackDetector
import com.cobblemon.mod.common.client.entity.PokemonClientDelegate
import net.minecraft.core.particles.BlockParticleOption
import net.minecraft.core.particles.ParticleOptions
import net.minecraft.core.particles.ParticleTypes
import net.minecraft.resources.ResourceLocation
@@ -21,12 +18,7 @@ import net.minecraft.resources.ResourceLocation
* Used by [PokemonClientDelegate.spawnAspectParticle] to spawn associated particles for aspects
*/
val aspectParticleMap: Map<String, ParticleData> = mapOf(
SaccharineLogSlatheredInfluence.HONEY_DRENCHED_ASPECT to ParticleData.MinecraftParticle(ParticleTypes.FALLING_HONEY, 0.075, 1),
LurePokeSnackDetector.POKE_SNACK_CRUMBED_ASPECT to ParticleData.MinecraftParticle(
BlockParticleOption(
ParticleTypes.BLOCK,
CobblemonBlocks.POKE_SNACK.defaultBlockState()
), 0.05, 3),
SaccharineLogSlatheredInfluence.HONEY_DRENCHED_ASPECT to ParticleData.MinecraftParticle(ParticleTypes.FALLING_HONEY, 0.075, 1)
)
sealed class ParticleData {
@@ -18,4 +18,11 @@ import net.minecraft.world.entity.PlayerRideableJumping
*
* @since 1.7.0
*/
interface Rideable : PlayerRideableJumping
interface Rideable : PlayerRideableJumping {
/**
* Denotes the manager responsible for handling any instance of riding taking place on an entity
*/
val riding: RidingBehaviour<RidingBehaviourSettings, RidingBehaviourState>?
}
@@ -18,41 +18,58 @@ import com.cobblemon.mod.common.util.adapters.RidingBehaviourSettingsAdapter
import net.minecraft.network.RegistryFriendlyByteBuf
class RidingProperties(
val stats: MutableMap<RidingStat, RidingStatDefinition> = mutableMapOf(),
val seats: List<Seat> = listOf(),
val conditions: List<Expression> = listOf(),
val behaviours: Map<RidingStyle, RidingBehaviourSettings>? = null
val behaviour: RidingBehaviourSettings? = null
) {
companion object {
fun decode(buffer: RegistryFriendlyByteBuf): RidingProperties {
val stats: MutableMap<RidingStat, RidingStatDefinition> = buffer.readMap(
{ buffer.readEnum(RidingStat::class.java) },
{ RidingStatDefinition.decode(buffer) }
).toMutableMap()
val seats: List<Seat> = buffer.readList { _ -> Seat.decode(buffer) }
val conditions = buffer.readList { buffer.readString().asExpression() }
val behaviours = buffer.readNullable { _ ->
buffer.readMap(
{ buffer.readEnumConstant(RidingStyle::class.java) },
{
val key = buffer.readResourceLocation()
val settings = RidingBehaviourSettingsAdapter.types[key]?.getConstructor()?.newInstance()
?: error("Unknown controller key: $key")
settings.decode(buffer)
settings
}
)
val behaviour = buffer.readNullable { _ ->
val key = buffer.readResourceLocation()
val settings = RidingBehaviourSettingsAdapter.types[key]?.getConstructor()?.newInstance() ?: error("Unknown controller key: $key")
settings.decode(buffer)
return@readNullable settings
}
return RidingProperties(seats = seats, conditions = conditions, behaviours = behaviours)
return RidingProperties(stats = stats, seats = seats, conditions = conditions, behaviour = behaviour)
}
}
internal fun updateStatRange(stat: RidingStat, style: RidingStyle, min: Int, max: Int) {
if (!Cobblemon.config.enableDebugKeys) return
if (stats[stat] == null) {
stats[stat] = RidingStatDefinition()
}
stats[stat]!!.ranges[style] = min..max
}
fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeMap(
stats,
{ _, stat -> buffer.writeEnum(stat) },
{ _, stat -> stat.encode(buffer) }
)
buffer.writeCollection(seats) { _, seat -> seat.encode(buffer) }
buffer.writeCollection(conditions) { _, condition -> buffer.writeString(condition.getString()) }
buffer.writeNullable(behaviours) { _, v ->
buffer.writeMap(
v,
{ _, style -> buffer.writeEnumConstant(style) },
{ _, behaviour -> behaviour.encode(buffer) }
)
buffer.writeNullable(behaviour) { _, behaviour ->
behaviour.encode(buffer)
}
}
fun calculate(stat: RidingStat, style: RidingStyle, boosts: Int): Float {
val definitions = stats[stat] ?: return 0F
return definitions.calculate(style, boosts)
}
fun hasStat(stat: RidingStat, style: RidingStyle): Boolean {
return stats[stat]?.ranges?.containsKey(style) == true
}
}
@@ -87,7 +87,7 @@ interface RidingBehaviour<Settings : RidingBehaviourSettings, State : RidingBeha
fun shouldRotatePokemonHead(settings: Settings, state: State, vehicle: PokemonEntity): Boolean
fun shouldRotateRiderHead(settings: Settings, state: State, vehicle: PokemonEntity): Boolean
fun shouldRotatePlayerHead(settings: Settings, state: State, vehicle: PokemonEntity): Boolean
fun getRideSounds(settings: Settings, state: State, vehicle: PokemonEntity): RideSoundSettingsList
@@ -101,19 +101,4 @@ interface RidingBehaviour<Settings : RidingBehaviourSettings, State : RidingBeha
* Calculate the damage a horizontal collision will do to a ridden Pokémon. This is only relevant to fast flying behaviours, fall damage is separate!
*/
fun damageOnCollision(settings: Settings, state: State, vehicle: PokemonEntity, impactVec: Vec3): Boolean = false
/**
* Internal helpers to help behaviour specific calculations
*/
companion object {
/**
* Scales a given value between a min and a max.
* The result is clamped between 0.0 and 1.0, where 0.0 represents x is at or below min
* and 1.0 represents x is at or above it.
*/
internal fun scaleToRange(x: Double, min: Double, max: Double): Double {
return ((x - min) / (max - min)).coerceIn(0.0, 1.0)
}
}
}
@@ -10,8 +10,6 @@ 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
@@ -25,13 +23,4 @@ import net.minecraft.world.phys.Vec3
*/
interface RidingBehaviourSettings: Encodable, Decodable {
val key: ResourceLocation
val stats: MutableMap<RidingStat, IntRange>
fun calculate(stat: RidingStat, boosts: Int): Float {
val range = stats[stat] ?: return 0F
val boostQuotient = boosts / 255F
return range.first + (range.last - range.first) * boostQuotient
}
fun hasStat(stat: RidingStat) = stats.containsKey(stat)
}
@@ -12,7 +12,6 @@ import com.cobblemon.mod.common.api.riding.behaviour.types.air.*
import com.cobblemon.mod.common.api.riding.behaviour.types.composite.CompositeBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.types.land.HorseBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.types.land.VehicleBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.types.land.MinekartBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.types.liquid.BoatBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.types.liquid.BurstBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.types.liquid.DolphinBehaviour
@@ -23,25 +22,21 @@ object RidingBehaviours {
init {
register(BirdBehaviour.KEY, BirdBehaviour())
register(BirdNoRollBehaviour.KEY, BirdNoRollBehaviour())
register(DolphinBehaviour.KEY, DolphinBehaviour())
register(HorseBehaviour.KEY, HorseBehaviour())
register(BoatBehaviour.KEY, BoatBehaviour())
register(GliderBehaviour.KEY, GliderBehaviour())
register(HelicopterBehaviour.KEY, HelicopterBehaviour())
register(JetBehaviour.KEY, JetBehaviour())
register(JetNoRollBehaviour.KEY, JetNoRollBehaviour())
register(BurstBehaviour.KEY, BurstBehaviour())
register(VehicleBehaviour.KEY, VehicleBehaviour())
register(MinekartBehaviour.KEY, MinekartBehaviour())
register(HoverBehaviour.KEY, HoverBehaviour())
register(RocketBehaviour.KEY, RocketBehaviour())
register(CompositeBehaviour.KEY, CompositeBehaviour())
}
fun register(key: ResourceLocation, behaviour: RidingBehaviour<out RidingBehaviourSettings, out RidingBehaviourState>) {
if (behaviours.contains(key)) error("Behaviour already registered to key $key")
behaviours[key] = behaviour as RidingBehaviour<RidingBehaviourSettings, RidingBehaviourState>
behaviours[key] = RidingController(behaviour) as RidingBehaviour<RidingBehaviourSettings, RidingBehaviourState>
}
fun get(key: ResourceLocation): RidingBehaviour<RidingBehaviourSettings, RidingBehaviourState> {
@@ -8,119 +8,206 @@
package com.cobblemon.mod.common.api.riding.behaviour
import com.cobblemon.mod.common.DoubleJump
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.resources.ResourceLocation
import net.minecraft.tags.FluidTags
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
import kotlin.collections.get
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
/**
* Small wrapper around a RidingBehaviour to provide sane defaults in the event that the behaviour is not active.
*
* @author landonjw
*/
class RidingController(
val entity: PokemonEntity,
val behaviours: Map<RidingStyle, RidingBehaviourSettings>) {
class RidingController<Settings : RidingBehaviourSettings, State : RidingBehaviourState>(val behaviour: RidingBehaviour<Settings, State>) : RidingBehaviour<Settings, State> {
var lastTransitionAge: Int = 0
private set
override val key = behaviour.key
var context: ActiveRidingContext? = null
private set
fun changeBehaviour(behaviour: ResourceLocation) {
if (entity.form.riding.behaviours == null) return
val style = entity.form.riding.behaviours!!.filter { it.value.key == behaviour }.keys.firstOrNull() ?: return
val behaviourSettings = entity.pokemon.riding.behaviours?.get(style) ?: return
val newState = RidingBehaviours.get(behaviourSettings.key).createDefaultState(behaviourSettings)
context?.let { newState.stamina.set(it.state.stamina.get(), forced = true) }
context = ActiveRidingContext(behaviourSettings.key, behaviourSettings, newState, style)
lastTransitionAge = entity.ticksLived
override fun getRidingStyle(settings: Settings, state: State): RidingStyle {
return behaviour.getRidingStyle(settings, state)
}
fun tick() {
if (entity.ticksLived - lastTransitionAge < 10) return
val newTransition = checkForNewTransition(entity.controllingPassenger)
val behaviourSettings = entity.pokemon.riding.behaviours?.get(newTransition)
if (newTransition != context?.style) {
if (newTransition != null && behaviourSettings != null) {
val newState = RidingBehaviours.get(behaviourSettings.key).createDefaultState(behaviourSettings)
context?.let { newState.stamina.set(it.state.stamina.get(), forced = true) }
context = ActiveRidingContext(behaviourSettings.key, behaviourSettings, newState, newTransition)
lastTransitionAge = entity.ticksLived
} else {
context = null
}
}
override fun isActive(settings: Settings, state: State, vehicle: PokemonEntity) =
behaviour.isActive(settings, state, vehicle)
if (context == null && !this.entity.level().isClientSide) {
entity.ejectPassengers()
}
override fun tick(settings: Settings, state: State, vehicle: PokemonEntity, driver: Player, input: Vec3) =
behaviour.tick(settings, state, vehicle, driver, input)
override fun pose(settings: Settings, state: State, vehicle: PokemonEntity) =
behaviour.pose(settings, state, vehicle)
override fun speed(settings: Settings, state: State, vehicle: PokemonEntity, driver: Player): Float {
if (!isActive(settings, state, vehicle)) return 0.0F
return behaviour.speed(settings, state, vehicle, driver)
}
fun getBehaviour(): RidingBehaviour<RidingBehaviourSettings, RidingBehaviourState>? {
if (context == null) return null
return RidingBehaviours.get(context!!.behaviour)
override fun updatePassengerRotation(
settings: Settings,
state: State,
vehicle: PokemonEntity,
driver: LivingEntity
) {
if (!isActive(settings, state, vehicle)) return
return behaviour.updatePassengerRotation(settings, state, vehicle, driver)
}
private fun checkForNewTransition(driver: LivingEntity?): RidingStyle? {
when (context?.style) {
RidingStyle.AIR -> {
if (canTransitionToLand()) return RidingStyle.LAND
if (canTransitionToLiquid()) return RidingStyle.LIQUID
return RidingStyle.AIR
}
RidingStyle.LIQUID -> {
if (canTransitionToAir(driver)) return RidingStyle.AIR
if (canTransitionToLand()) return RidingStyle.LAND
return RidingStyle.LIQUID
}
RidingStyle.LAND -> {
if (canTransitionToAir(driver)) return RidingStyle.AIR
if (canTransitionToLiquid()) return RidingStyle.LIQUID
return RidingStyle.LAND
}
null -> {
if (canTransitionToAir(driver)) return RidingStyle.AIR
if (canTransitionToLiquid()) return RidingStyle.LIQUID
if (canTransitionToLand()) return RidingStyle.LAND
return null
}
}
override fun clampPassengerRotation(settings: Settings, state: State, vehicle: PokemonEntity, driver: LivingEntity) {
if (!isActive(settings, state, vehicle)) return
return behaviour.clampPassengerRotation(settings, state, vehicle, driver)
}
private fun canTransitionToLand(): Boolean {
if (entity.isInLiquid || entity.isUnderWater) return false
return entity.onGround()
override fun rotation(settings: Settings, state: State, vehicle: PokemonEntity, driver: LivingEntity): Vec2 {
if (!isActive(settings, state, vehicle)) return driver.rotationVector
return behaviour.rotation(settings, state, vehicle, driver)
}
private fun canTransitionToLiquid(): Boolean {
return entity.isInLiquid || entity.isUnderWater
override fun velocity(
settings: Settings,
state: State,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
if (!isActive(settings, state, vehicle)) return Vec3.ZERO
return behaviour.velocity(settings, state, vehicle, driver, input)
}
private fun canTransitionToAir(driver: LivingEntity?): Boolean {
if (driver != null) {
if (driver !is DoubleJump) 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
return (driver as DoubleJump).isDoubleJumping
}
else {
return !entity.onGround() && !(entity.isInLiquid || entity.isUnderWater)
}
override fun angRollVel(
settings: Settings,
state: State,
vehicle: PokemonEntity,
driver: Player,
deltaTime: Double
): Vec3 {
if (!isActive(settings, state, vehicle)) return Vec3.ZERO
return behaviour.angRollVel(settings, state, vehicle, driver, deltaTime)
}
override fun rotationOnMouseXY(
settings: Settings,
state: State,
vehicle: PokemonEntity,
driver: Player,
mouseY: Double,
mouseX: Double,
mouseYSmoother: SmoothDouble,
mouseXSmoother: SmoothDouble,
sensitivity: Double,
deltaTime: Double
): Vec3 {
if (!isActive(settings, state, vehicle)) return Vec3.ZERO
return behaviour.rotationOnMouseXY(
settings,
state,
vehicle,
driver,
mouseY,
mouseX,
mouseYSmoother,
mouseXSmoother,
sensitivity,
deltaTime
)
}
override fun canJump(settings: Settings, state: State, vehicle: PokemonEntity, driver: Player): Boolean {
if (!isActive(settings, state, vehicle)) return false
return behaviour.canJump(settings, state, vehicle, driver)
}
override fun shouldRotatePlayerHead(settings: Settings, state: State, vehicle: PokemonEntity): Boolean {
if (!isActive(settings, state, vehicle)) return false
return behaviour.shouldRotatePlayerHead(settings, state, vehicle)
}
override fun shouldRotatePokemonHead(settings: Settings, state: State, vehicle: PokemonEntity): Boolean {
if (!isActive(settings, state, vehicle)) return false
return behaviour.shouldRotatePokemonHead(settings, state, vehicle)
}
override fun dismountOnShift(settings: Settings, state: State, vehicle: PokemonEntity): Boolean {
if (!isActive(settings, state, vehicle)) return false
return behaviour.dismountOnShift(settings, state, vehicle)
}
override fun turnOffOnGround(settings: Settings, state: State, vehicle: PokemonEntity): Boolean {
if (!isActive(settings, state, vehicle)) return false
return behaviour.turnOffOnGround(settings, state, vehicle)
}
override fun shouldRoll(settings: Settings, state: State, vehicle: PokemonEntity): Boolean {
if (!isActive(settings, state, vehicle)) return false
return behaviour.shouldRoll(settings, state, vehicle)
}
override fun inertia(settings: Settings, state: State, vehicle: PokemonEntity): Double {
if (!isActive(settings, state, vehicle)) return 0.5
return behaviour.inertia(settings, state, vehicle)
}
override fun useRidingAltPose(settings: Settings, state: State, vehicle: PokemonEntity, driver: Player): ResourceLocation {
if (!isActive(settings, state, vehicle)) return cobblemonResource("no_pose")
return behaviour.useRidingAltPose(settings, state, vehicle, driver)
}
override fun useAngVelSmoothing(settings: Settings, state: State, vehicle: PokemonEntity): Boolean {
if (!isActive(settings, state, vehicle)) return true
return behaviour.useAngVelSmoothing(settings, state, vehicle)
}
override fun rideFovMultiplier(settings: Settings, state: State, vehicle: PokemonEntity, driver: Player): Float {
if (!isActive(settings, state, vehicle)) return 1.0F
return behaviour.rideFovMultiplier(settings, state, vehicle, driver)
}
override fun gravity(settings: Settings, state: State, vehicle: PokemonEntity, regularGravity: Double): Double {
if (!isActive(settings, state, vehicle)) return regularGravity
return behaviour.gravity(settings, state, vehicle, regularGravity)
}
override fun jumpForce(
settings: Settings,
state: State,
vehicle: PokemonEntity,
driver: Player,
jumpStrength: Int
): Vec3 {
if (!isActive(settings, state, vehicle)) return Vec3.ZERO
return behaviour.jumpForce(settings, state, vehicle, driver, jumpStrength)
}
override fun setRideBar(settings: Settings, state: State, vehicle: PokemonEntity, driver: Player): Float {
if (!isActive(settings, state, vehicle)) return 0.0F
return behaviour.setRideBar(settings, state, vehicle, driver)
}
override fun maxUpStep(settings: Settings, state: State, vehicle: PokemonEntity): Float? {
if (!isActive(settings, state, vehicle)) return null
return behaviour.maxUpStep(settings, state, vehicle)
}
override fun getRideSounds(settings: Settings, state: State, vehicle: PokemonEntity): RideSoundSettingsList {
if (!isActive(settings, state, vehicle)) return RideSoundSettingsList()
return behaviour.getRideSounds(settings, state, vehicle)
}
override fun createDefaultState(settings: Settings): State {
return behaviour.createDefaultState(settings)
}
override fun damageOnCollision(
settings: Settings,
state: State,
vehicle: PokemonEntity,
impactVec: Vec3
): Boolean {
return behaviour.damageOnCollision(settings, state, vehicle, impactVec)
}
}
class ActiveRidingContext(
val behaviour: ResourceLocation,
val settings: RidingBehaviourSettings,
val state: RidingBehaviourState,
val style: RidingStyle
)
@@ -16,8 +16,8 @@ import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.*
import com.cobblemon.mod.common.api.riding.posing.PoseOption
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
import com.cobblemon.mod.common.api.riding.sound.RideLoopSound
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.*
@@ -25,6 +25,7 @@ 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.sounds.SoundEvent
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
@@ -106,6 +107,14 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
upForce -= 0.3
}
val altitudeLimit = vehicle.runtime.resolveDouble(settings.jumpExpr)
//Only limit altitude if altitude is not infinite
if (!vehicle.runtime.resolveBoolean(settings.infiniteAltitude)) {
//Provide a hard limit on altitude
upForce = if (vehicle.y >= altitudeLimit && upForce > 0) 0.0 else upForce
}
val velocity = Vec3(state.rideVelocity.get().x , upForce, forwardForce)
return velocity
@@ -120,7 +129,12 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
val glideTopSpeed = vehicle.runtime.resolveDouble(settings.glidespeedExpr)
val accel = vehicle.runtime.resolveDouble(settings.accelerationExpr)
val staminaStat = vehicle.runtime.resolveDouble(settings.staminaExpr)
var glideSpeedChange = 0.0
val currSpeed = state.rideVelocity.get().length()
//Flag for determining if player is actively inputting
var activeInput = false
var newVelocity = Vec3(state.rideVelocity.get().x, state.rideVelocity.get().y, state.rideVelocity.get().z)
@@ -243,7 +257,7 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
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 rotLimit = max(handling * sqrt(normalizeVal(state.rideVelocity.get().length(), 0.0, topSpeed)), rotMin)
var yawDeltaDeg = deltaTime * handling * sin(Math.toRadians(controller.roll.toDouble())) //sin(Math.toRadians(controller.roll.toDouble())) * abs(cos(Math.toRadians(controller.pitch.toDouble())))
val trueYawDelt = yawDeltaDeg * abs(cos(Math.toRadians(controller.pitch.toDouble())))
@@ -299,7 +313,7 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
//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)
val rotLimit = max(90 * sqrt(normalizeVal(state.rideVelocity.get().length(), 0.0, topSpeed)), rotMin)
//Limit roll by non linearly decreasing inputs towards
// a rotation limit based on the current distance from
@@ -315,7 +329,7 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
}
//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)))
val yawForce = xInput * ( 1.0 - sqrt(normalizeVal(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
@@ -343,6 +357,16 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
return Vec3(0.0, 0.0, rollForce)
}
/*
* Normalizes the given value between a min and a max.
* The result is clamped between 0.0 and 1.0, where 0.0 represents x is at or below min
* and 1.0 represents x is at or above it.
*/
private fun normalizeVal(x: Double, min: Double, max: Double): Double {
require(max > min) { "max must be greater than min" }
return ((x - min) / (max - min)).coerceIn(0.0, 1.0)
}
override fun canJump(
settings: BirdSettings,
state: BirdState,
@@ -390,7 +414,7 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
val glideTopSpeed = vehicle.runtime.resolveDouble(settings.glidespeedExpr)
//Must I ensure that topspeed is greater than minimum?
val normalizedGlideSpeed = RidingBehaviour.scaleToRange(state.rideVelocity.get().length(), topSpeed, glideTopSpeed)
val normalizedGlideSpeed = normalizeVal(state.rideVelocity.get().length(), topSpeed, glideTopSpeed)
//Only ever want the fov change to be a max of 0.2 and for it to have non linear scaling.
return 1.0f + normalizedGlideSpeed.pow(2).toFloat() * 0.2f
@@ -440,12 +464,12 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
return false
}
override fun shouldRotateRiderHead(
override fun shouldRotatePlayerHead(
settings: BirdSettings,
state: BirdState,
vehicle: PokemonEntity
): Boolean {
return false
return true
}
override fun getRideSounds(
@@ -461,7 +485,6 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
class BirdSettings : RidingBehaviourSettings {
override val key = BirdBehaviour.KEY
override val stats = mutableMapOf<RidingStat, IntRange>()
var infiniteAltitude: Expression = "false".asExpression()
private set
@@ -483,7 +506,6 @@ class BirdSettings : RidingBehaviourSettings {
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeRidingStats(stats)
rideSounds.encode(buffer)
buffer.writeExpression(infiniteAltitude)
buffer.writeExpression(infiniteStamina)
@@ -496,7 +518,6 @@ class BirdSettings : RidingBehaviourSettings {
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
stats.putAll(buffer.readRidingStats())
rideSounds = RideSoundSettingsList.decode(buffer)
infiniteAltitude = buffer.readExpression()
infiniteStamina = buffer.readExpression()
@@ -1,512 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.riding.behaviour.types.air
import com.bedrockk.molang.Expression
import com.bedrockk.molang.runtime.MoLangMath.lerp
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.*
import com.cobblemon.mod.common.api.riding.posing.PoseOption
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.*
import com.cobblemon.mod.common.util.math.geometry.toRadians
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
import org.joml.Vector3f
import kotlin.math.*
class BirdNoRollBehaviour : RidingBehaviour<BirdNoRollSettings, BirdNoRollState> {
companion object {
val KEY = cobblemonResource("air/birdnoroll")
}
override val key: ResourceLocation = KEY
override fun getRidingStyle(settings: BirdNoRollSettings, state: BirdNoRollState): RidingStyle {
return RidingStyle.AIR
}
val poseProvider = PoseProvider<BirdNoRollSettings, BirdNoRollState>(PoseType.HOVER)
.with(PoseOption(PoseType.FLY) { _, state, _ -> state.rideVelocity.get().z > 0.2 })
override fun isActive(settings: BirdNoRollSettings, state: BirdNoRollState, vehicle: PokemonEntity): Boolean {
return true
}
override fun pose(settings: BirdNoRollSettings, state: BirdNoRollState, vehicle: PokemonEntity): PoseType {
return poseProvider.select(settings, state, vehicle)
}
override fun speed(
settings: BirdNoRollSettings,
state: BirdNoRollState,
vehicle: PokemonEntity,
driver: Player
): Float {
return state.rideVelocity.get().length().toFloat()
}
override fun rotation(
settings: BirdNoRollSettings,
state: BirdNoRollState,
vehicle: PokemonEntity,
driver: LivingEntity
): Vec2 {
return Vec2(driver.xRot * 0.5f, driver.yRot)
}
override fun velocity(
settings: BirdNoRollSettings,
state: BirdNoRollState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
var yVel = 0.0
var leftForce = 0.0
var upForce = 0.0
var forwardForce = 0.0
//Perform ride velocity update
calculateRideSpaceVel(settings, state, vehicle, driver)
//Translate ride space velocity to world space velocity.
val controller = (driver as? OrientationControllable)?.orientationController
if (controller != null) {
upForce += -1.0 * sin(Math.toRadians(controller.pitch.toDouble())) * state.rideVelocity.get().z
forwardForce += cos(Math.toRadians(controller.pitch.toDouble())) * state.rideVelocity.get().z
upForce += cos(Math.toRadians(controller.pitch.toDouble())) * state.rideVelocity.get().y
forwardForce += sin(Math.toRadians(controller.pitch.toDouble())) * state.rideVelocity.get().y
}
//Bring the ride out of the sky when stamina is depleted.
if (state.stamina.get() <= 0.0) {
upForce -= 0.3
}
val velocity = Vec3(state.rideVelocity.get().x , upForce, forwardForce)
return velocity
}
/*
* Calculates the change in the ride space vector due to player input and ride state
*/
fun calculateRideSpaceVel(settings: BirdNoRollSettings, state: BirdNoRollState, vehicle: PokemonEntity, driver: Player) {
//retrieve stats
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
val glideTopSpeed = vehicle.runtime.resolveDouble(settings.glidespeedExpr)
val accel = vehicle.runtime.resolveDouble(settings.accelerationExpr)
val staminaStat = vehicle.runtime.resolveDouble(settings.staminaExpr)
var glideSpeedChange = 0.0
val currSpeed = state.rideVelocity.get().length()
//Flag for determining if player is actively inputting
var activeInput = false
var newVelocity = Vec3(state.rideVelocity.get().x, state.rideVelocity.get().y, state.rideVelocity.get().z)
//speed up and slow down based on input
if (driver.zza != 0.0f && state.stamina.get() > 0.0) {
//make sure it can't exceed top speed
val forwardInput = when {
driver.zza > 0 && newVelocity.z > topSpeed -> 0.0
driver.zza < 0 && newVelocity.z < (-topSpeed / 3.0) -> 0.0
else -> driver.zza.sign
}
newVelocity = Vec3(
newVelocity.x,
newVelocity.y,
(newVelocity.z + (accel * forwardInput.toDouble())))
activeInput = true
}
val controller = (driver as? OrientationControllable)?.orientationController
if (controller != null) {
//Base glide speed change on current pitch of the ride.
glideSpeedChange = sin(Math.toRadians(controller.pitch.toDouble()))
glideSpeedChange = glideSpeedChange * 0.25
//TODO: Possibly create a deadzone around parallel where glide doesn't affect speed?
if (glideSpeedChange <= 0.0) {
//Ensures that a propelling force is still able to be applied when
//climbing in height
if (driver.zza <= 0) {
//speed decrease should be 2x speed increase?
//state.currSpeed = max(state.currSpeed + (0.0166) * glideSpeedChange, 0.0 )
newVelocity = Vec3(
newVelocity.x,
newVelocity.y,
lerp( newVelocity.z, 0.0,glideSpeedChange * -0.0166 * 2 )
)
}
} else {
// only add to the speed if it hasn't exceeded the current
//glide angles maximum amount of speed that it can give.
//state.currSpeed = min(state.currSpeed + ((0.0166 * 2) * glideSpeedChange), maxGlideSpeed)
newVelocity = Vec3(
newVelocity.x,
newVelocity.y,
min(newVelocity.z + ((0.0166 * 2) * glideSpeedChange), glideTopSpeed)
)
}
}
//Vertical movement based on driver input.
val vertTopSpeed = topSpeed / 2.0
val vertInput = when {
driver.jumping && state.rideVelocity.get().length() < 0.3 -> 1.0
driver.isShiftKeyDown && state.rideVelocity.get().length() < 0.3 -> -1.0
else -> 0.0
}
if (vertInput != 0.0 && state.stamina.get() > 0.0) {
newVelocity = Vec3(
newVelocity.x,
(newVelocity.y + (accel * vertInput)).coerceIn(-vertTopSpeed, vertTopSpeed),
newVelocity.z)
activeInput = true
}
else {
newVelocity = Vec3(
newVelocity.x,
lerp(newVelocity.y, 0.0, vertTopSpeed / 20.0),
newVelocity.z)
}
//Check if the ride should be gliding
if (driver.isLocalPlayer) {
if (activeInput && state.stamina.get() > 0.0) {
state.gliding.set(false)
} else {
state.gliding.set(true)
}
}
//air resistance
if( abs(newVelocity.z) > 0) {
newVelocity = newVelocity.subtract(0.0, 0.0, min(0.0015 * newVelocity.z.sign, newVelocity.z))
}
state.rideVelocity.set(newVelocity)
}
override fun angRollVel(
settings: BirdNoRollSettings,
state: BirdNoRollState,
vehicle: PokemonEntity,
driver: Player,
deltaTime: Double
): Vec3 {
if (driver !is OrientationControllable) return Vec3.ZERO
val controller = (driver as OrientationControllable).orientationController
//limit rolling based on handling and current speed.
//modulated by speed so that when flapping idle in air you are ont wobbling around to look around
//TODO: Tie handling into yaw rate
val handling = vehicle.runtime.resolveDouble(settings.handlingExpr)
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
//This rotation limit will be used to know if the ride has exceeded it and its needs correcting.
val rotMin = 15.0
val rotLimit = max(handling * sqrt(RidingBehaviour.scaleToRange(state.rideVelocity.get().length(), 0.0, topSpeed)), rotMin)
var yawDeltaDeg = deltaTime * handling * sin(Math.toRadians(controller.roll.toDouble())) //sin(Math.toRadians(controller.roll.toDouble())) * abs(cos(Math.toRadians(controller.pitch.toDouble())))
val trueYawDelt = yawDeltaDeg * abs(cos(Math.toRadians(controller.pitch.toDouble())))
//val pitchedYawDelt = yawDeltaDeg - trueYawDelt
controller.applyGlobalYaw(trueYawDelt.toFloat())
//Calculate correcting roll force.
val correctionRate = 5.0
if(abs(controller.roll) > rotLimit && state.rideVelocity.get().length() < 0.2) {
state.currRollCorrectionForce.set(
state.currRollCorrectionForce.get() + ((abs(controller.roll) - rotLimit) / 180 - rotLimit) * controller.roll.sign * correctionRate * deltaTime * 0.01
)
} else {
state.currRollCorrectionForce.set(
//reduce rollCorrection gradually
lerp(state.currRollCorrectionForce.get(), 0.0, deltaTime * 0.98)
)
}
//yaw, pitch, roll
return Vec3(0.0, 0.0, state.currRollCorrectionForce.get())
}
override fun rotationOnMouseXY(
settings: BirdNoRollSettings,
state: BirdNoRollState,
vehicle: PokemonEntity,
driver: Player,
mouseY: Double,
mouseX: Double,
mouseYSmoother: SmoothDouble,
mouseXSmoother: SmoothDouble,
sensitivity: Double,
deltaTime: Double
): Vec3 {
if (driver !is OrientationControllable) return Vec3.ZERO
val controller = (driver as OrientationControllable).orientationController
//TODO: hook up handling in parity with normal bird
val handling = vehicle.runtime.resolveDouble(settings.handlingExpr)
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
//Smooth out mouse input.
val smoothingSpeed = 4.0
val invertRoll = if (Cobblemon.config.invertRoll) -1 else 1
val invertPitch = if (Cobblemon.config.invertPitch) -1 else 1
val mouseXc = (mouseX).coerceIn(-60.0, 60.0)
val mouseYc = (mouseY).coerceIn(-60.0, 60.0)
val xInput = mouseXSmoother.getNewDeltaValue(mouseXc * 0.1 * invertRoll, deltaTime * smoothingSpeed);
val yInput = mouseYSmoother.getNewDeltaValue(mouseYc * 0.1 * invertPitch, deltaTime * smoothingSpeed);
//Give the ability to yaw with x mouse input when at low speeds.
val yawForce = xInput * ( 1.0 - min(sqrt(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())
var pitchRot = yInput
// Pitch up globally
if (abs(controller.pitch + pitchRot) >= 89.5 ) {
pitchRot = 0.0
mouseYSmoother.reset()
} else {
controller.applyGlobalPitch(pitchRot.toFloat() * -1.0f)
}
//yaw, pitch, roll
return Vec3.ZERO
}
override fun canJump(
settings: BirdNoRollSettings,
state: BirdNoRollState,
vehicle: PokemonEntity,
driver: Player
): Boolean {
return false
}
override fun setRideBar(
settings: BirdNoRollSettings,
state: BirdNoRollState,
vehicle: PokemonEntity,
driver: Player
): Float {
return (state.stamina.get() / 1.0f)
}
override fun jumpForce(
settings: BirdNoRollSettings,
state: BirdNoRollState,
vehicle: PokemonEntity,
driver: Player,
jumpStrength: Int
): Vec3 {
return Vec3.ZERO
}
override fun gravity(
settings: BirdNoRollSettings,
state: BirdNoRollState,
vehicle: PokemonEntity,
regularGravity: Double
): Double {
return 0.0
}
override fun rideFovMultiplier(
settings: BirdNoRollSettings,
state: BirdNoRollState,
vehicle: PokemonEntity,
driver: Player
): Float {
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
val glideTopSpeed = vehicle.runtime.resolveDouble(settings.glidespeedExpr)
//Must I ensure that topspeed is greater than minimum?
val normalizedGlideSpeed = 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.
return 1.0f + normalizedGlideSpeed.pow(2).toFloat() * 0.2f
}
override fun useAngVelSmoothing(
settings: BirdNoRollSettings,
state: BirdNoRollState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun useRidingAltPose(
settings: BirdNoRollSettings,
state: BirdNoRollState,
vehicle: PokemonEntity,
driver: Player
): ResourceLocation {
if (state.gliding.get()) {
return cobblemonResource("gliding")
}
return cobblemonResource("no_pose")
}
override fun inertia(settings: BirdNoRollSettings, state: BirdNoRollState, vehicle: PokemonEntity): Double {
return 0.5
}
override fun shouldRoll(settings: BirdNoRollSettings, state: BirdNoRollState, vehicle: PokemonEntity): Boolean {
return true
}
override fun turnOffOnGround(settings: BirdNoRollSettings, state: BirdNoRollState, vehicle: PokemonEntity): Boolean {
return false
}
override fun dismountOnShift(settings: BirdNoRollSettings, state: BirdNoRollState, vehicle: PokemonEntity): Boolean {
return false
}
override fun shouldRotatePokemonHead(
settings: BirdNoRollSettings,
state: BirdNoRollState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun shouldRotateRiderHead(
settings: BirdNoRollSettings,
state: BirdNoRollState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun getRideSounds(
settings: BirdNoRollSettings,
state: BirdNoRollState,
vehicle: PokemonEntity
): RideSoundSettingsList {
return settings.rideSounds
}
override fun createDefaultState(settings: BirdNoRollSettings) = BirdNoRollState()
}
class BirdNoRollSettings : RidingBehaviourSettings {
override val key = BirdNoRollBehaviour.KEY
override val stats = mutableMapOf<RidingStat, IntRange>()
var infiniteAltitude: Expression = "false".asExpression()
private set
var infiniteStamina: Expression = "false".asExpression()
private set
//max y level for the ride
var jumpExpr: Expression = "q.get_ride_stats('JUMP', 'AIR', 200.0, 128.0)".asExpression()
var handlingExpr: Expression = "q.get_ride_stats('SKILL', 'AIR', 135.0, 45.0)".asExpression()
var speedExpr: Expression = "q.get_ride_stats('SPEED', 'AIR', 1.0, 0.35)".asExpression()
var accelerationExpr: Expression = "q.get_ride_stats('ACCELERATION', 'AIR', (1.0 / (20.0 * 3.0)), (1.0 / (20.0 * 8.0)))".asExpression()
var staminaExpr: Expression = "q.get_ride_stats('STAMINA', 'AIR', 120.0, 20.0)".asExpression()
var glidespeedExpr: Expression = "q.get_ride_stats('SPEED', 'AIR', 2.0, 1.0)".asExpression()
private set
var rideSounds: RideSoundSettingsList = RideSoundSettingsList()
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeRidingStats(stats)
rideSounds.encode(buffer)
buffer.writeExpression(infiniteAltitude)
buffer.writeExpression(infiniteStamina)
buffer.writeExpression(glidespeedExpr)
buffer.writeExpression(jumpExpr)
buffer.writeExpression(handlingExpr)
buffer.writeExpression(speedExpr)
buffer.writeExpression(accelerationExpr)
buffer.writeExpression(staminaExpr)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
stats.putAll(buffer.readRidingStats())
rideSounds = RideSoundSettingsList.decode(buffer)
infiniteAltitude = buffer.readExpression()
infiniteStamina = buffer.readExpression()
glidespeedExpr = buffer.readExpression()
jumpExpr = buffer.readExpression()
handlingExpr = buffer.readExpression()
speedExpr = buffer.readExpression()
accelerationExpr = buffer.readExpression()
staminaExpr = buffer.readExpression()
}
}
class BirdNoRollState : RidingBehaviourState() {
var gliding = ridingState(false, Side.CLIENT)
var lastGlide = ridingState(-100L, Side.CLIENT)
var currRollCorrectionForce = ridingState(0.0, Side.CLIENT)
override fun encode(buffer: FriendlyByteBuf) {
super.encode(buffer)
buffer.writeFloat(stamina.get())
buffer.writeBoolean(gliding.get())
}
override fun decode(buffer: FriendlyByteBuf) {
super.decode(buffer)
gliding.set(buffer.readBoolean(), forced = true)
}
override fun reset() {
super.reset()
gliding.set(false, forced = true)
lastGlide.set(-100L, forced = true)
currRollCorrectionForce.set(0.0, forced = true)
}
override fun toString(): String {
return "BirdNoRollState(rideVelocity=${rideVelocity.get()}, stamina=${stamina.get()}, gliding=${gliding.get()})"
}
override fun copy() = BirdNoRollState().also {
it.rideVelocity.set(this.rideVelocity.get(), forced = true)
it.stamina.set(this.stamina.get(), forced = true)
it.gliding.set(this.gliding.get(), forced = true)
it.lastGlide.set(this.lastGlide.get(), forced = true)
it.currRollCorrectionForce.set(this.currRollCorrectionForce.get(), forced = true)
}
override fun shouldSync(previous: RidingBehaviourState): Boolean {
if (previous !is BirdNoRollState) return false
if (previous.gliding.get() != gliding.get()) return true
return super.shouldSync(previous)
}
}
@@ -75,7 +75,8 @@ class GliderBehaviour : RidingBehaviour<GliderSettings, RidingBehaviourState> {
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)
val statSpeed = vehicle.rideProp.calculate(RidingStat.SPEED, RidingStyle.AIR, 0 )
return Vec3(xVector, yVector, zVector)
}
@@ -183,7 +184,7 @@ class GliderBehaviour : RidingBehaviour<GliderSettings, RidingBehaviourState> {
return false
}
override fun shouldRotateRiderHead(settings: GliderSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Boolean {
override fun shouldRotatePlayerHead(settings: GliderSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Boolean {
return false
}
@@ -196,7 +197,6 @@ class GliderBehaviour : RidingBehaviour<GliderSettings, RidingBehaviourState> {
class GliderSettings : RidingBehaviourSettings {
override val key = GliderBehaviour.KEY
override val stats = mutableMapOf<RidingStat, IntRange>()
var glideSpeed: Expression = "0.1".asExpression()
private set
@@ -211,7 +211,6 @@ class GliderSettings : RidingBehaviourSettings {
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeRidingStats(stats)
rideSounds.encode(buffer)
buffer.writeExpression(glideSpeed)
buffer.writeExpression(speed)
@@ -219,7 +218,6 @@ class GliderSettings : RidingBehaviourSettings {
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
stats.putAll(buffer.readRidingStats())
rideSounds = RideSoundSettingsList.decode(buffer)
glideSpeed = buffer.readExpression()
speed = buffer.readExpression()
@@ -12,13 +12,12 @@ import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourState
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourSettings
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourState
import com.cobblemon.mod.common.api.riding.posing.PoseOption
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
import com.cobblemon.mod.common.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.*
@@ -270,11 +269,7 @@ class HelicopterBehaviour : RidingBehaviour<HelicopterSettings, RidingBehaviourS
return false
}
override fun shouldRotateRiderHead(
settings: HelicopterSettings,
state: RidingBehaviourState,
vehicle: PokemonEntity
): Boolean {
override fun shouldRotatePlayerHead(settings: HelicopterSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Boolean {
return false
}
@@ -288,7 +283,6 @@ class HelicopterBehaviour : RidingBehaviour<HelicopterSettings, RidingBehaviourS
class HelicopterSettings : RidingBehaviourSettings {
override val key = HelicopterBehaviour.KEY
override val stats = mutableMapOf<RidingStat, IntRange>()
var gravity: Expression = "1.0".asExpression()
private set
@@ -306,7 +300,6 @@ class HelicopterSettings : RidingBehaviourSettings {
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeRidingStats(stats)
rideSounds.encode(buffer)
buffer.writeExpression(gravity)
buffer.writeExpression(horizontalAcceleration)
@@ -315,7 +308,6 @@ class HelicopterSettings : RidingBehaviourSettings {
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
stats.putAll(buffer.readRidingStats())
rideSounds = RideSoundSettingsList.decode(buffer)
gravity = buffer.readExpression()
horizontalAcceleration = buffer.readExpression()
@@ -13,8 +13,8 @@ import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.*
import com.cobblemon.mod.common.api.riding.posing.PoseOption
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
import com.cobblemon.mod.common.api.riding.sound.RideLoopSound
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.*
@@ -104,7 +104,7 @@ class HoverBehaviour : RidingBehaviour<HoverSettings, HoverState> {
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()
val normSpeed = 1.0f - 0.5f*normalizeVal(state.rideVelocity.get().length(), 0.0, topSpeed).toFloat()
//driver.yRot += (entity.riding.deltaRotation.y - turnAmount)
//driver.setYHeadRot(driver.yHeadRot + (entity.riding.deltaRotation.y) - turnAmount)
@@ -164,7 +164,7 @@ class HoverBehaviour : RidingBehaviour<HoverSettings, HoverState> {
val rotDiffMod = (sqrt(abs(rotDiffNorm)) * rotDiffNorm.sign)
//Take the inverse so that you turn less at higher speeds
val normSpeed = 1.0f // = 1.0f - 0.5f*RidingBehaviour.scaleToRange(state.rideVelocityocity.length(), 0.0, topSpeed).toFloat()
val normSpeed = 1.0f // = 1.0f - 0.5f*normalizeVal(state.rideVelocityocity.length(), 0.0, topSpeed).toFloat()
val turnRate = (handling.toFloat() / 20.0f)
@@ -308,6 +308,15 @@ class HoverBehaviour : RidingBehaviour<HoverSettings, HoverState> {
return newVelocity
}
/*
* Normalizes the current speed between minSpeed and maxSpeed.
* The result is clamped between 0.0 and 1.0, where 0.0 represents minSpeed and 1.0 represents maxSpeed.
*/
private fun normalizeVal(currSpeed: Double, minSpeed: Double, maxSpeed: Double): Double {
require(maxSpeed > minSpeed) { "maxSpeed must be greater than minSpeed" }
return ((currSpeed - minSpeed) / (maxSpeed - minSpeed)).coerceIn(0.0, 1.0)
}
override fun angRollVel(
settings: HoverSettings,
state: HoverState,
@@ -443,7 +452,7 @@ class HoverBehaviour : RidingBehaviour<HoverSettings, HoverState> {
return false
}
override fun shouldRotateRiderHead(
override fun shouldRotatePlayerHead(
settings: HoverSettings,
state: HoverState,
vehicle: PokemonEntity
@@ -464,7 +473,6 @@ class HoverBehaviour : RidingBehaviour<HoverSettings, HoverState> {
class HoverSettings : RidingBehaviourSettings {
override val key = HoverBehaviour.KEY
override val stats = mutableMapOf<RidingStat, IntRange>()
var rideSound: ResourceLocation = "ride.loop.saucer".asIdentifierDefaultingNamespace()
var volumeExpr: Expression = "math.pow(math.min(q.ride_velocity() / 0.8, 1.0),2)".asExpression()
@@ -496,7 +504,6 @@ class HoverSettings : RidingBehaviourSettings {
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeRidingStats(stats)
rideSounds.encode(buffer)
buffer.writeExpression(speedExpr)
buffer.writeExpression(accelerationExpr)
@@ -506,7 +513,6 @@ class HoverSettings : RidingBehaviourSettings {
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
stats.putAll(buffer.readRidingStats())
rideSounds = RideSoundSettingsList.decode(buffer)
speedExpr = buffer.readExpression()
accelerationExpr = buffer.readExpression()
@@ -21,8 +21,8 @@ import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.*
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.RegistryFriendlyByteBuf
import com.bedrockk.molang.runtime.MoLangMath.lerp
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
@@ -80,7 +80,7 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
// //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))
// var staminaRate = (normalizeSpeed(state.rideVelocity.get().length(), minSpeed, topSpeed))
//
// //interpolate between 0.25 and 1.0 so that you always have at least a min of 0.25 stam loss
// staminaRate = 0.25 + (0.75 * staminaRate.pow(3))
@@ -95,6 +95,16 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
return state.rideVelocity.get().length().toFloat()
}
//TODO: Move these functions to a riding util class.
/*
* Normalizes the current speed between minSpeed and maxSpeed.
* The result is clamped between 0.0 and 1.0, where 0.0 represents minSpeed and 1.0 represents maxSpeed.
*/
private fun normalizeSpeed(currSpeed: Double, minSpeed: Double, maxSpeed: Double): Double {
require(maxSpeed > minSpeed) { "maxSpeed must be greater than minSpeed" }
return ((currSpeed - minSpeed) / (maxSpeed - minSpeed)).coerceIn(0.0, 1.0)
}
override fun rotation(
settings: JetSettings,
state: JetState,
@@ -128,6 +138,14 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
//If stamina has run out then initiate forced glide down.
upForce = if (state.stamina.get() > 0.0) upForce else -0.7
val altitudeLimit = vehicle.runtime.resolveDouble(settings.jumpExpr)
//Only limit altitude if altitude is not infinite
if (!vehicle.runtime.resolveBoolean(settings.infiniteAltitude)) {
//Provide a hard limit on altitude
upForce = if (vehicle.y >= altitudeLimit && upForce > 0) 0.0 else upForce
}
val velocity = Vec3(0.0, upForce, forwardForce)
return velocity
@@ -144,13 +162,19 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
) {
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
val accel = vehicle.runtime.resolveDouble(settings.accelerationExpr)
val altitudeLimit = vehicle.runtime.resolveDouble(settings.jumpExpr)
val minSpeed = vehicle.runtime.resolveDouble(settings.minSpeed)
val speed = state.rideVelocity.get().length()
//Give no altitude limit if at max jump stat.
val pushingHeightLimit = if (vehicle.runtime.resolveBoolean(settings.infiniteStamina)) false
else (vehicle.y >= altitudeLimit && vehicle.xRot <= 0)
//speed up and slow down based on input
if (driver.zza > 0.0 && speed < topSpeed && state.stamina.get() > 0.0f) {
if (driver.zza > 0.0 && speed < topSpeed && state.stamina.get() > 0.0f && !pushingHeightLimit) {
//modify acceleration to be slower when at closer speeds to top speed
val accelMod = max(-(RidingBehaviour.scaleToRange(speed, minSpeed, topSpeed)) + 1, 0.0)
val accelMod = max(-(normalizeSpeed(speed, minSpeed, topSpeed)) + 1, 0.0)
state.rideVelocity.set(
Vec3(
state.rideVelocity.get().x,
@@ -158,7 +182,7 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
min(state.rideVelocity.get().z + (accel * accelMod), topSpeed)
)
)
} else if (driver.zza >= 0.0 && (state.stamina.get() == 0.0f)) {
} else if (driver.zza >= 0.0 && (state.stamina.get() == 0.0f || pushingHeightLimit)) {
state.rideVelocity.set(
Vec3(
state.rideVelocity.get().x,
@@ -168,7 +192,7 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
)
} 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)
val deccelMod = max((normalizeSpeed(speed, minSpeed, topSpeed) - 1).pow(2) * 4, 0.1)
//Decelerate currently always a constant half of max acceleration.
state.rideVelocity.set(
@@ -314,7 +338,7 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
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)
val normalizedSpeed = normalizeSpeed(state.rideVelocity.get().length(), minSpeed, topSpeed)
//TODO: Determine if this should be based on max possible speed instead of top speed.
//Only ever want the fov change to be a max of 0.2 and for it to have non linear scaling.
@@ -358,7 +382,7 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
return false
}
override fun shouldRotateRiderHead(
override fun shouldRotatePlayerHead(
settings: JetSettings,
state: JetState,
vehicle: PokemonEntity
@@ -389,7 +413,6 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
class JetSettings : RidingBehaviourSettings {
override val key = JetBehaviour.KEY
override val stats = mutableMapOf<RidingStat, IntRange>()
var gravity: Expression = "0".asExpression()
private set
@@ -404,6 +427,9 @@ class JetSettings : RidingBehaviourSettings {
var infiniteStamina: Expression = "false".asExpression()
private set
var infiniteAltitude: Expression = "false".asExpression()
private set
var jumpExpr: Expression = "q.get_ride_stats('JUMP', 'AIR', 300.0, 128.0)".asExpression()
private set
var handlingExpr: Expression = "q.get_ride_stats('SKILL', 'AIR', 140.0, 20.0)".asExpression()
@@ -420,12 +446,12 @@ class JetSettings : RidingBehaviourSettings {
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeRidingStats(stats)
rideSounds.encode(buffer)
buffer.writeExpression(gravity)
buffer.writeExpression(minSpeed)
buffer.writeExpression(handlingYawExpr)
buffer.writeExpression(infiniteStamina)
buffer.writeExpression(infiniteAltitude)
buffer.writeExpression(jumpExpr)
buffer.writeExpression(handlingExpr)
buffer.writeExpression(speedExpr)
@@ -434,12 +460,12 @@ class JetSettings : RidingBehaviourSettings {
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
stats.putAll(buffer.readRidingStats())
rideSounds = RideSoundSettingsList.decode(buffer)
gravity = buffer.readExpression()
minSpeed = buffer.readExpression()
handlingYawExpr = buffer.readExpression()
infiniteStamina = buffer.readExpression()
infiniteAltitude = buffer.readExpression()
jumpExpr = buffer.readExpression()
handlingExpr = buffer.readExpression()
speedExpr = buffer.readExpression()
@@ -475,6 +501,8 @@ class JetState : RidingBehaviourState() {
it.currSpeed.set(currSpeed.get(), forced = true)
it.stamina.set(stamina.get(), forced = true)
it.rideVelocity.set(rideVelocity.get(), forced = true)
// it.currMouseXForce.set(currMouseXForce.get(), forced = true)
// it.currMouseYForce.set(currMouseYForce.get(), forced = true)
}
override fun shouldSync(previous: RidingBehaviourState): Boolean {
@@ -1,471 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.riding.behaviour.types.air
import com.bedrockk.molang.Expression
import com.bedrockk.molang.runtime.MoLangMath.lerp
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.*
import com.cobblemon.mod.common.api.riding.posing.PoseOption
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.*
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.RegistryFriendlyByteBuf
import com.bedrockk.molang.runtime.MoLangMath.lerp
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
import net.minecraft.world.phys.shapes.Shapes
import kotlin.math.*
class JetNoRollBehaviour : RidingBehaviour<JetNoRollSettings, JetNoRollState> {
companion object {
val KEY = cobblemonResource("air/jetnoroll")
}
override val key = KEY
override fun getRidingStyle(settings: JetNoRollSettings, state: JetNoRollState): RidingStyle {
return RidingStyle.AIR
}
val poseProvider = PoseProvider<JetNoRollSettings, JetNoRollState>(PoseType.HOVER)
.with(PoseOption(PoseType.FLY) { _, state, _ -> state.rideVelocity.get().z > 0.1 })
override fun isActive(settings: JetNoRollSettings, state: JetNoRollState, vehicle: PokemonEntity): Boolean {
return Shapes.create(vehicle.boundingBox).blockPositionsAsListRounded().any {
//Need to check other fluids
if (vehicle.isInWater || vehicle.isUnderWater) {
return@any false
}
//This might not actually work, depending on what the yPos actually is. yPos of the middle of the entity? the feet?
if (it.y.toDouble() == (vehicle.position().y)) {
val blockState = vehicle.level().getBlockState(it.below())
return@any !(!blockState.isAir && blockState.fluidState.isEmpty)
}
true
}
}
override fun pose(settings: JetNoRollSettings, state: JetNoRollState, vehicle: PokemonEntity): PoseType {
return poseProvider.select(settings, state, vehicle)
}
override fun speed(settings: JetNoRollSettings, state: JetNoRollState, vehicle: PokemonEntity, driver: Player): Float {
//retrieve stats
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
val staminaStat = vehicle.runtime.resolveDouble(settings.staminaExpr)
//retrieve minSpeed
val minSpeed = vehicle.runtime.resolveDouble(settings.minSpeed)
//TODO: Reintroduce stamina drain once stats start to be polished and tweaked
state.stamina.set(1.0f)
return state.rideVelocity.get().length().toFloat()
}
override fun rotation(
settings: JetNoRollSettings,
state: JetNoRollState,
vehicle: PokemonEntity,
driver: LivingEntity
): Vec2 {
return Vec2(driver.xRot * 0.5f, driver.yRot)
}
override fun velocity(
settings: JetNoRollSettings,
state: JetNoRollState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
var upForce = 0.0
var forwardForce = 0.0
val controller = (driver as? OrientationControllable)?.orientationController
//Calculate ride space velocity
calculateRideSpaceVel(settings, state, vehicle, driver)
//Translate ride space velocity to world space velocity.
if (controller != null) {
upForce = -1.0 * sin(Math.toRadians(controller.pitch.toDouble())) * state.rideVelocity.get().z
forwardForce = cos(Math.toRadians(controller.pitch.toDouble())) * state.rideVelocity.get().z
}
//If stamina has run out then initiate forced glide down.
upForce = if (state.stamina.get() > 0.0) upForce else -0.7
val velocity = Vec3(0.0, upForce, forwardForce)
return velocity
}
/*
* Calculates the change in the ride space vector due to player input and ride state
*/
private fun calculateRideSpaceVel(
settings: JetNoRollSettings,
state: JetNoRollState,
vehicle: PokemonEntity,
driver: Player
) {
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
val accel = vehicle.runtime.resolveDouble(settings.accelerationExpr)
val minSpeed = vehicle.runtime.resolveDouble(settings.minSpeed)
val speed = state.rideVelocity.get().length()
//speed up and slow down based on input
if (driver.zza > 0.0 && speed < topSpeed && state.stamina.get() > 0.0f) {
//modify acceleration to be slower when at closer speeds to top speed
val accelMod = max(-(RidingBehaviour.scaleToRange(speed, minSpeed, topSpeed)) + 1, 0.0)
state.rideVelocity.set(
Vec3(
state.rideVelocity.get().x,
state.rideVelocity.get().y,
min(state.rideVelocity.get().z + (accel * accelMod), topSpeed)
)
)
} else if (driver.zza >= 0.0 && (state.stamina.get() == 0.0f)) {
state.rideVelocity.set(
Vec3(
state.rideVelocity.get().x,
state.rideVelocity.get().y,
max(state.rideVelocity.get().z - ((accel) / 4), minSpeed)
)
)
} else if (driver.zza < 0.0 && speed > minSpeed) {
//modify deccel to be slower when at closer speeds to minimum speed
val deccelMod = max((RidingBehaviour.scaleToRange(speed, minSpeed, topSpeed) - 1).pow(2) * 4, 0.1)
//Decelerate currently always a constant half of max acceleration.
state.rideVelocity.set(
Vec3(
state.rideVelocity.get().x,
state.rideVelocity.get().y,
max(state.rideVelocity.get().z - ((accel * deccelMod) / 2), minSpeed)
)
)
}
}
override fun angRollVel(
settings: JetNoRollSettings,
state: JetNoRollState,
vehicle: PokemonEntity,
driver: Player,
deltaTime: Double
): Vec3 {
// //Cap at a rate of 5fps so frame skips dont lead to huge jumps
// val cappedDeltaTime = min(deltaTime, 0.2)
//
// //Get handling in degrees per second
// val yawRotRate = vehicle.runtime.resolveDouble(settings.handlingYawExpr)
//
// //Base the change off of deltatime.
// var handlingYaw = yawRotRate * (cappedDeltaTime)
//
// //apply stamina debuff if applicable
// handlingYaw *= if (state.stamina.get() > 0.0) 1.0 else 0.5
//
// //A+D to yaw
// val yawForce = driver.xxa * handlingYaw * -1
return Vec3.ZERO
}
override fun rotationOnMouseXY(
settings: JetNoRollSettings,
state: JetNoRollState,
vehicle: PokemonEntity,
driver: Player,
mouseY: Double,
mouseX: Double,
mouseYSmoother: SmoothDouble,
mouseXSmoother: SmoothDouble,
sensitivity: Double,
deltaTime: Double
): Vec3 {
if (driver !is OrientationControllable) return Vec3.ZERO
//TODO: figure out a cleaner solution to this issue of large jumps when skipping frames or lagging
//Cap at a rate of 5fps so frame skips dont lead to huge jumps
val cappedDeltaTime = min(deltaTime, 0.2)
val invertRoll = if (Cobblemon.config.invertRoll) -1 else 1
val invertPitch = if (Cobblemon.config.invertPitch) -1 else 1
// Accumulate the mouse input
state.currMouseXForce.set((state.currMouseXForce.get() + (0.0015 * mouseX * invertRoll)).coerceIn(-1.0, 1.0))
state.currMouseYForce.set((state.currMouseYForce.get() + (0.0015 * mouseY * invertPitch)).coerceIn(-1.0, 1.0))
//Get handling in degrees per second
var handling = vehicle.runtime.resolveDouble(settings.handlingExpr)
//convert it to delta time
handling *= (cappedDeltaTime)
//apply stamina debuff if applicable
handling *= if (state.stamina.get() > 0.0) 1.0 else 0.5
var pitchRot = handling * state.currMouseYForce.get()
// Yaw
val yawRot = handling * 0.5 * state.currMouseXForce.get()
val controller = (driver as OrientationControllable).orientationController
controller.applyGlobalYaw(yawRot.toFloat())
if (abs(controller.pitch + pitchRot) >= 89.5 ) {
pitchRot = 0.0
state.currMouseYForce.set(0.0)
mouseYSmoother.reset()
} else {
controller.applyGlobalPitch(pitchRot.toFloat() * -1.0f)
}
val mouseRotation = Vec3(0.0, pitchRot, 0.0)
// Have accumulated input begin decay when no input detected
if (abs(mouseX) == 0.0) {
// Have decay on roll be much stronger.
state.currMouseXForce.set(lerp(state.currMouseXForce.get(), 0.0, 0.02))
}
if (mouseY == 0.0) {
state.currMouseYForce.set(lerp(state.currMouseYForce.get(), 0.0, 0.005))
}
//yaw, pitch, roll
return Vec3.ZERO
}
override fun canJump(
settings: JetNoRollSettings,
state: JetNoRollState,
vehicle: PokemonEntity,
driver: Player
): Boolean {
return false
}
override fun setRideBar(
settings: JetNoRollSettings,
state: JetNoRollState,
vehicle: PokemonEntity,
driver: Player
): Float {
return (state.stamina.get() / 1.0f)
}
override fun jumpForce(
settings: JetNoRollSettings,
state: JetNoRollState,
vehicle: PokemonEntity,
driver: Player,
jumpStrength: Int
): Vec3 {
return Vec3.ZERO
}
override fun gravity(
settings: JetNoRollSettings,
state: JetNoRollState,
vehicle: PokemonEntity,
regularGravity: Double
): Double {
return 0.0
}
override fun rideFovMultiplier(
settings: JetNoRollSettings,
state: JetNoRollState,
vehicle: PokemonEntity,
driver: Player
): Float {
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
val minSpeed = vehicle.runtime.resolveDouble(settings.minSpeed)
//Must I ensure that topspeed is greater than minimum?
val normalizedSpeed = 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: JetNoRollSettings, state: JetNoRollState, vehicle: PokemonEntity): Boolean {
return true
}
override fun useRidingAltPose(
settings: JetNoRollSettings,
state: JetNoRollState,
vehicle: PokemonEntity,
driver: Player
): ResourceLocation {
return cobblemonResource("no_pose")
}
override fun inertia(settings: JetNoRollSettings, state: JetNoRollState, vehicle: PokemonEntity): Double {
return 1.0
}
override fun shouldRoll(settings: JetNoRollSettings, state: JetNoRollState, vehicle: PokemonEntity): Boolean {
return true
}
override fun turnOffOnGround(settings: JetNoRollSettings, state: JetNoRollState, vehicle: PokemonEntity): Boolean {
return false
}
override fun dismountOnShift(settings: JetNoRollSettings, state: JetNoRollState, vehicle: PokemonEntity): Boolean {
return false
}
override fun shouldRotatePokemonHead(
settings: JetNoRollSettings,
state: JetNoRollState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun shouldRotateRiderHead(
settings: JetNoRollSettings,
state: JetNoRollState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun getRideSounds(
settings: JetNoRollSettings,
state: JetNoRollState,
vehicle: PokemonEntity
): RideSoundSettingsList {
return settings.rideSounds
}
override fun createDefaultState(settings: JetNoRollSettings) = JetNoRollState()
override fun damageOnCollision(
settings: JetNoRollSettings,
state: JetNoRollState,
vehicle: PokemonEntity,
impactVec: Vec3
): Boolean {
val impactSpeed = impactVec.horizontalDistance().toFloat() * 10f
return vehicle.causeFallDamage(impactSpeed, 1f, vehicle.damageSources().flyIntoWall())
}
}
class JetNoRollSettings : RidingBehaviourSettings {
override val key = JetNoRollBehaviour.KEY
override val stats = mutableMapOf<RidingStat, IntRange>()
var gravity: Expression = "0".asExpression()
private set
var minSpeed: Expression = "1.2".asExpression()
private set
var handlingYawExpr: Expression = "q.get_ride_stats('SKILL', 'AIR', 50.0, 25.0)".asExpression()
private set
// Make configurable by json
var infiniteStamina: Expression = "false".asExpression()
private set
var jumpExpr: Expression = "q.get_ride_stats('JUMP', 'AIR', 300.0, 128.0)".asExpression()
private set
var handlingExpr: Expression = "q.get_ride_stats('SKILL', 'AIR', 140.0, 20.0)".asExpression()
private set
var speedExpr: Expression = "q.get_ride_stats('SPEED', 'AIR', 1.8, 1.0)".asExpression()
private set
var accelerationExpr: Expression =
"q.get_ride_stats('ACCELERATION', 'AIR', (1.0 / (20.0 * 1.0)), (1.0 / (20.0 * 5.0)))".asExpression()
private set
var staminaExpr: Expression = "q.get_ride_stats('STAMINA', 'AIR', 60.0, 10.0)".asExpression()
private set
var rideSounds: RideSoundSettingsList = RideSoundSettingsList()
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeRidingStats(stats)
rideSounds.encode(buffer)
buffer.writeExpression(gravity)
buffer.writeExpression(minSpeed)
buffer.writeExpression(handlingYawExpr)
buffer.writeExpression(infiniteStamina)
buffer.writeExpression(jumpExpr)
buffer.writeExpression(handlingExpr)
buffer.writeExpression(speedExpr)
buffer.writeExpression(accelerationExpr)
buffer.writeExpression(staminaExpr)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
stats.putAll(buffer.readRidingStats())
rideSounds = RideSoundSettingsList.decode(buffer)
gravity = buffer.readExpression()
minSpeed = buffer.readExpression()
handlingYawExpr = buffer.readExpression()
infiniteStamina = buffer.readExpression()
jumpExpr = buffer.readExpression()
handlingExpr = buffer.readExpression()
speedExpr = buffer.readExpression()
accelerationExpr = buffer.readExpression()
staminaExpr = buffer.readExpression()
}
}
class JetNoRollState : RidingBehaviourState() {
var currSpeed = ridingState(0.0, Side.CLIENT)
var currMouseXForce = ridingState(0.0, Side.CLIENT)
var currMouseYForce = ridingState(0.0, Side.CLIENT)
override fun encode(buffer: FriendlyByteBuf) {
super.encode(buffer)
buffer.writeDouble(currSpeed.get())
buffer.writeFloat(stamina.get())
}
override fun decode(buffer: FriendlyByteBuf) {
super.decode(buffer)
currSpeed.set(buffer.readDouble(), forced = true)
}
override fun reset() {
super.reset()
currSpeed.set(0.0, forced = true)
currMouseXForce.set(0.0, forced = true)
currMouseYForce.set(0.0, forced = true)
}
override fun copy() = JetNoRollState().also {
it.currSpeed.set(currSpeed.get(), forced = true)
it.stamina.set(stamina.get(), forced = true)
it.rideVelocity.set(rideVelocity.get(), forced = true)
// it.currMouseXForce.set(currMouseXForce.get(), forced = true)
// it.currMouseYForce.set(currMouseYForce.get(), forced = true)
}
override fun shouldSync(previous: RidingBehaviourState): Boolean {
if (previous !is JetNoRollState) return false
if (previous.currSpeed != currSpeed) return true
return super.shouldSync(previous)
}
}
@@ -1,561 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.riding.behaviour.types.air
import com.bedrockk.molang.Expression
import com.bedrockk.molang.runtime.MoLangMath.lerp
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.*
import com.cobblemon.mod.common.api.riding.posing.PoseOption
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.*
import com.cobblemon.mod.common.util.math.geometry.toRadians
import net.minecraft.client.Minecraft
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.Mth
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
import net.minecraft.world.phys.shapes.Shapes
import org.joml.Matrix3f
import kotlin.math.*
class RocketBehaviour : RidingBehaviour<RocketSettings, RocketState> {
companion object {
val KEY = cobblemonResource("air/rocket")
}
override val key = KEY
override fun getRidingStyle(settings: RocketSettings, state: RocketState): RidingStyle {
return RidingStyle.AIR
}
val poseProvider = PoseProvider<RocketSettings, RocketState>(PoseType.HOVER)
.with(PoseOption(PoseType.FLY) { _, state, _ ->
return@PoseOption state.boosting.get()
})
override fun isActive(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity
): Boolean {
return Shapes.create(vehicle.boundingBox).blockPositionsAsListRounded().any {
//Need to check other fluids
if (vehicle.isInWater || vehicle.isUnderWater) {
return@any false
}
//This might not actually work, depending on what the yPos actually is. yPos of the middle of the entity? the feet?
if (it.y.toDouble() == (vehicle.position().y)) {
val blockState = vehicle.level().getBlockState(it.below())
return@any !blockState.isAir && blockState.fluidState.isEmpty
}
true
}
}
override fun pose(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity
): PoseType {
return this.poseProvider.select(settings, state, vehicle)
}
override fun speed(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity,
driver: Player
): Float {
// Use this as a "tick" function and check to see if the driver is "boosting"
if(vehicle.level().isClientSide) {
//If the forward key is not held then it cannot be boosting
if(Minecraft.getInstance().options.keyUp.isDown()) {
val boostKeyPressed = Minecraft.getInstance().options.keySprint.isDown()
//If on the previous tick the boost key was held then don't change if the ride is boosting
if(state.boostIsToggleable.get() && boostKeyPressed) {
//flip the boosting state if boost key is pressed
state.boosting.set(!state.boosting.get())
}
//If the boost key is not held then next tick boosting is toggleable
state.boostIsToggleable.set(!boostKeyPressed)
} else {
//Turn off boost and reset boost params
state.boostIsToggleable.set(true)
state.boosting.set(false)
state.canSpeedBurst.set(true)
}
}
return state.rideVelocity.get().length().toFloat()
}
override fun updatePassengerRotation(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity,
driver: LivingEntity
) {
return
}
override fun clampPassengerRotation(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity,
driver: LivingEntity
) {
val f = Mth.wrapDegrees(driver.yRot - vehicle.yRot)
val lookYawLimit = 90.0f
val g = Mth.clamp(f, -lookYawLimit, lookYawLimit)
val ticksBeforeCorrection = 40
if(state.prevCamYRot.get() == driver.yRot){
state.ticksNoInput.set(state.ticksNoInput.get() + 1)
} else {
state.ticksNoInput.set(0)
}
val correctionForce = if(state.ticksNoInput.get() >= ticksBeforeCorrection && f != 0.0f && state.boosting.get()) 1f * min(sqrt(lookYawLimit / (abs(f))), 10.0f) else 0.0f
driver.yRotO += g - f
driver.yRot = driver.yRot + g - f //- ( clamp(f.sign * correctionForce, -abs(f), abs(f)) )
//driver.yRot = driver.yRot + g - f - ( min )
state.prevCamYRot.set(driver.yRot)
driver.setYHeadRot(driver.yRot)
}
override fun rotation(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity,
driver: LivingEntity
): Vec2 {
var newMomentum = state.turnMomentum.get().toDouble()
// Grab turningAcceleration and divide 20 twice to get
val turningAcceleration = (vehicle.runtime.resolveDouble(settings.handlingExpr) / 20.0f) / 20.0f * 2
val turnInput = (driver.xxa *-1.0f) * turningAcceleration
val maxTurnMomentum = 80.0f / 20.0f
// Base boost stats off of normal turning stats
val boostMaxTurnMomentum = maxTurnMomentum * 0.1f
val boostTurnInput = turnInput * 0.15f
if(state.boosting.get()) {
if(driver.xxa != 0.0f && abs(newMomentum + turnInput) < (boostMaxTurnMomentum)) { //If max momentum will not be exceeded then modulate
newMomentum += boostTurnInput
} else {
newMomentum = lerp(newMomentum, 0.0, 0.05)
}
} else {
if(driver.xxa == 0.0f) { //If no turning input then lerp to 0
newMomentum = lerp(newMomentum, 0.0, 0.05)
} else if(abs(newMomentum + turnInput) < maxTurnMomentum) { //If max momentum will not be exceeded then modulate
newMomentum += turnInput
}
}
state.turnMomentum.set(newMomentum.toFloat())
driver.yRot += newMomentum.toFloat()
return Vec2(driver.xRot, vehicle.yRot + newMomentum.toFloat())
}
override fun velocity(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
val retVel = calculateRideSpaceVel(settings, state, vehicle, driver)
state.rideVelocity.set(retVel)
return retVel
}
private fun calculateRideSpaceVel(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity,
driver: Player
): Vec3 {
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr) * 2
val accel = vehicle.runtime.resolveDouble(settings.accelerationExpr) * 0.5 * 2
var newVelocity = vehicle.deltaMovement
// Align the direction of movement with the world coordinate space so as not to modify it through turning
val yawAligned = Matrix3f().rotateY(vehicle.yRot.toRadians())
newVelocity = (newVelocity.toVector3f().mul(yawAligned)).toVec3d()
//speed up and slow down based on input
if (driver.zza != 0.0f && state.stamina.get() > 0.0 && !state.boosting.get()) {
//make sure it can't exceed top speed
val forwardInput = when {
vehicle.deltaMovement.horizontalDistance() > topSpeed && (driver.zza.sign == newVelocity.z.sign.toFloat()) -> 0.0
else -> driver.zza.sign
}
newVelocity = Vec3(
newVelocity.x,
newVelocity.y,
(newVelocity.z + (accel * forwardInput.toDouble())))
} else if ((state.stamina.get() > 0.0 && state.boosting.get())) {
val boostSpeed = topSpeed * 5
val boostAccel = if(newVelocity.length() < boostSpeed) accel * 3 else 0.0
val forwardInput = 1.0f
var burst = 0.0f
if(state.canSpeedBurst.get()) {
burst = 0.25f
state.canSpeedBurst.set(false)
}
newVelocity = Vec3(
newVelocity.x,
newVelocity.y,
newVelocity.z + (boostAccel * forwardInput.toDouble()) + burst)
}
//Vertical movement based on driver input.
val maxVertSpeed = 0.5f
var vertInput = 0.0
//If boosting then don't lose altitude
//otherwise propel up or fall
if (state.boosting.get() && !driver.jumping && vehicle.deltaMovement.y > (-maxVertSpeed * 0.25)) {
vertInput = -0.7
newVelocity = Vec3(
newVelocity.x,
newVelocity.y + accel * vertInput,
newVelocity.z)
} else if(state.boosting.get() && vehicle.deltaMovement.y < maxVertSpeed) {
vertInput = when {
driver.jumping -> 2.0
else -> 0.0
}
newVelocity = Vec3(
newVelocity.x,
(newVelocity.y + (accel * 0.5 * vertInput)),
newVelocity.z)
} else {
if (driver.jumping && !(vehicle.deltaMovement.y > maxVertSpeed && (newVelocity.y.sign.toFloat() > 0.0))) {
vertInput = 1.0
// Reset falldistance if upward motion is detected
vehicle.resetFallDistance()
} else {
vertInput = -2.5
}
if (state.stamina.get() > 0.0) {
newVelocity = Vec3(
newVelocity.x,
(newVelocity.y + (accel * vertInput)),
newVelocity.z)
}
}
return newVelocity
}
override fun angRollVel(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity,
driver: Player,
deltaTime: Double
): Vec3 {
return Vec3.ZERO
}
override fun rotationOnMouseXY(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity,
driver: Player,
mouseY: Double,
mouseX: Double,
mouseYSmoother: SmoothDouble,
mouseXSmoother: SmoothDouble,
sensitivity: Double,
deltaTime: Double
): Vec3 {
//Smooth out mouse input.
val smoothingSpeed = 4
val xInput = mouseXSmoother.getNewDeltaValue(mouseX * 0.1, deltaTime * smoothingSpeed);
val yInput = mouseYSmoother.getNewDeltaValue(mouseY * 0.1, deltaTime * smoothingSpeed);
//yaw, pitch, roll
return Vec3(0.0, yInput, xInput)
}
override fun canJump(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity,
driver: Player
): Boolean {
return false
}
override fun setRideBar(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity,
driver: Player
): Float {
//Retrieve stamina from state and use it to set the "stamina bar"
return (state.stamina.get() / 1.0f)
}
override fun jumpForce(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity,
driver: Player,
jumpStrength: Int
): Vec3 {
return Vec3.ZERO
}
override fun gravity(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity,
regularGravity: Double
): Double {
return 0.0
}
override fun rideFovMultiplier(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity,
driver: Player
): Float {
if (state.boosting.get()) {
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
//val exceededSpeed = max(vehicle.deltaMovement.length() - topSpeed, 0.0)
//TODO: Remove this magic number and get a better comparison for boost top speed
val normalizedBoostSpeed = RidingBehaviour.scaleToRange(state.rideVelocity.get().length(), topSpeed, topSpeed * 3)
return 1.0f + normalizedBoostSpeed.pow(2).toFloat() * 0.2f
} else {
return 1.0f
}
}
override fun useAngVelSmoothing(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun useRidingAltPose(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity,
driver: Player
): ResourceLocation {
return cobblemonResource("no_pose")
}
override fun inertia(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity
): Double {
return 1.0
}
override fun shouldRoll(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun turnOffOnGround(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun dismountOnShift(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun shouldRotatePokemonHead(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun shouldRotateRiderHead(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun getRideSounds(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity
): RideSoundSettingsList {
return settings.rideSounds
}
override fun createDefaultState(settings: RocketSettings) = RocketState()
}
class RocketSettings : RidingBehaviourSettings {
override val key = RocketBehaviour.KEY
override val stats = mutableMapOf<RidingStat, IntRange>()
var speedExpr: Expression = "q.get_ride_stats('SPEED', 'AIR', 0.65, 0.3) * 0.25".asExpression()
private set
// Max accel is a whole 1.0 in 1 second. The conversion in the function below is to convert seconds to ticks
var accelerationExpr: Expression =
"q.get_ride_stats('ACCELERATION', 'AIR', (1.0 / (20.0 * 1.5)), (1.0 / (20.0 * 3.0))) * 0.5".asExpression()
private set
// Between 30 seconds and 10 seconds at the lowest when at full speed.
var staminaExpr: Expression = "q.get_ride_stats('STAMINA', 'AIR', 30.0, 10.0)".asExpression()
private set
//Between a one block jump and a ten block jump
var jumpExpr: Expression = "q.get_ride_stats('JUMP', 'AIR', 10.0, 1.0)".asExpression()
private set
// Controls the acceleration of the turning speed since the max turning speed is locked at 60 degrees/second
// So this is a range of 60 degrees/second per second to 20. Meaning at max stats when holding a direction it
// takes a full second to be turning at max speed.
var handlingExpr: Expression = "q.get_ride_stats('SKILL', 'AIR', 60.0, 20.0)".asExpression()
private set
var rideSounds: RideSoundSettingsList = RideSoundSettingsList()
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeRidingStats(stats)
rideSounds.encode(buffer)
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)
speedExpr = buffer.readExpression()
accelerationExpr = buffer.readExpression()
staminaExpr = buffer.readExpression()
jumpExpr = buffer.readExpression()
handlingExpr = buffer.readExpression()
}
}
class RocketState : RidingBehaviourState() {
var boosting = ridingState(false, Side.BOTH)
var boostIsToggleable = ridingState(false, Side.BOTH)
var canSpeedBurst = ridingState(false, Side.BOTH)
var prevCamYRot: SidedRidingState<Float> = ridingState(0.0f, Side.CLIENT)
var ticksNoInput: SidedRidingState<Int> = ridingState(0, Side.CLIENT)
val turnMomentum: SidedRidingState<Float> = ridingState(0.0f, Side.CLIENT)
override fun reset() {
super.reset()
boosting.set(false, forced = true)
boostIsToggleable.set(false, forced = true)
canSpeedBurst.set(true, forced = true)
prevCamYRot.set(0.0f, forced = true)
ticksNoInput.set(0, forced = true)
turnMomentum.set(0.0f, forced = true)
}
override fun copy() = RocketState().also {
it.rideVelocity.set(this.rideVelocity.get(), forced = true)
it.stamina.set(this.stamina.get(), forced = true)
it.boosting.set(this.boosting.get(), forced = true)
it.boostIsToggleable.set(this.boosting.get(), forced = true)
it.canSpeedBurst.set(this.canSpeedBurst.get(), forced = true)
it.prevCamYRot.set(this.prevCamYRot.get(), forced = true)
it.ticksNoInput.set(this.ticksNoInput.get(), forced = true)
it.turnMomentum.set(this.turnMomentum.get(), forced = true)
}
override fun encode(buffer: FriendlyByteBuf) {
super.encode(buffer)
buffer.writeBoolean(boosting.get())
buffer.writeBoolean(boostIsToggleable.get())
buffer.writeBoolean(canSpeedBurst.get())
buffer.writeFloat(prevCamYRot.get())
buffer.writeInt(ticksNoInput.get())
buffer.writeFloat(turnMomentum.get())
}
override fun decode(buffer: FriendlyByteBuf) {
super.decode(buffer)
boosting.set(buffer.readBoolean(), forced = true)
boostIsToggleable.set(buffer.readBoolean(), forced = true)
canSpeedBurst.set(buffer.readBoolean(), forced = true)
prevCamYRot.set(buffer.readFloat(), forced = true)
ticksNoInput.set(buffer.readInt(), forced = true)
turnMomentum.set(buffer.readFloat(), forced = true)
}
override fun shouldSync(previous: RidingBehaviourState): Boolean {
if (previous !is RocketState) return false
if (previous.boosting.get() != boosting.get()) return true
if (previous.boostIsToggleable.get() != boostIsToggleable.get()) return true
if (previous.canSpeedBurst.get() != canSpeedBurst.get()) return true
if (previous.prevCamYRot.get() != prevCamYRot.get()) return true
if (previous.ticksNoInput.get() != ticksNoInput.get()) return true
if (previous.turnMomentum.get() != turnMomentum.get()) return true
return super.shouldSync(previous)
}
}
@@ -12,13 +12,10 @@ import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.*
import com.cobblemon.mod.common.api.riding.behaviour.types.composite.strategies.CompositeRidingStrategies
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
import com.cobblemon.mod.common.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.adapters.RidingBehaviourSettingsAdapter
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.readRidingStats
import com.cobblemon.mod.common.util.writeRidingStats
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
@@ -110,13 +107,13 @@ class CompositeBehaviour : RidingBehaviour<CompositeSettings, CompositeState> {
}
}
override fun shouldRotateRiderHead(
override fun shouldRotatePlayerHead(
settings: CompositeSettings,
state: CompositeState,
vehicle: PokemonEntity
): Boolean {
return chooseBehaviour(settings, state) { behaviour, behaviourSettings, behaviourState ->
behaviour.shouldRotateRiderHead(behaviourSettings, behaviourState, vehicle)
behaviour.shouldRotatePlayerHead(behaviourSettings, behaviourState, vehicle)
}
}
@@ -383,10 +380,8 @@ open class CompositeSettings : RidingBehaviourSettings {
private set
override val key: ResourceLocation = CompositeBehaviour.KEY
override val stats = mutableMapOf<RidingStat, IntRange>()
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeRidingStats(stats)
buffer.writeResourceLocation(key)
buffer.writeResourceLocation(transitionStrategy)
defaultBehaviour.encode(buffer)
@@ -394,7 +389,6 @@ open class CompositeSettings : RidingBehaviourSettings {
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
stats.putAll(buffer.readRidingStats())
transitionStrategy = buffer.readResourceLocation()
val defaultBehaviourKey = buffer.readResourceLocation()
defaultBehaviour = RidingBehaviourSettingsAdapter.types[defaultBehaviourKey]?.getConstructor()?.newInstance()
@@ -45,7 +45,7 @@ object JumpStrategy : CompositeRidingStrategy<CompositeSettings> {
): Boolean {
if (state.activeBehaviour.get() == defaultSettings.key) return false
if (!vehicle.onGround()) return false
if (state.lastTransition.get() + 5 >= vehicle.level().gameTime) return false
if (state.lastTransition.get() + 20 >= vehicle.level().gameTime) return false
val defaultBehaviour = RidingBehaviours.get(defaultSettings.key)
return defaultBehaviour.isActive(defaultSettings, state.defaultBehaviourState, vehicle)
}
@@ -58,7 +58,7 @@ object JumpStrategy : CompositeRidingStrategy<CompositeSettings> {
): Boolean {
if (state.activeBehaviour.get() == alternativeSettings.key) return false
if (!driver.jumping) return false
if (state.lastTransition.get() + 5 >= vehicle.level().gameTime) return false
if (state.lastTransition.get() + 20 >= vehicle.level().gameTime) return false
val alternativeBehaviour = RidingBehaviours.get(alternativeSettings.key)
return alternativeBehaviour.isActive(alternativeSettings, state.alternateBehaviourState, vehicle)
}
@@ -14,7 +14,6 @@ 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.*
@@ -84,15 +83,7 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
// Use this as a "tick" function and calculate sprinting and inAir state here
state.sprinting.set(driver.isSprinting)
inAirCheck(state, vehicle)
return state.rideVelocity.get().length().toFloat()
}
fun inAirCheck(
state: HorseState,
vehicle: PokemonEntity,
) {
// Check both vertical movement and if there are blocks below.
val posBelow = vehicle.blockPosition().below()
val blockStateBelow = vehicle.level().getBlockState(posBelow)
@@ -101,10 +92,14 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
val canSupportEntity = blockStateBelow.isFaceSturdy(vehicle.level(), posBelow, Direction.UP)
val standingOnSolid = canSupportEntity && !isAirOrLiquid
// val level = vehicle.level()
// val toesBox = vehicle.boundingBox.move(0.0, -0.1, 0.0)
// inAir if not on the ground
val inAir = !(vehicle.deltaMovement.y == 0.0 || standingOnSolid)
state.inAir.set(inAir)
return state.rideVelocity.get().length().toFloat()
}
override fun updatePassengerRotation(
@@ -112,7 +107,26 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
state: HorseState,
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*normalizeVal(state.rideVelocity.get().length(), 0.0, topSpeed).toFloat()
//driver.yRot += (entity.riding.deltaRotation.y - turnAmount)
//driver.setYHeadRot(driver.yHeadRot + (entity.riding.deltaRotation.y) - turnAmount)
}
override fun clampPassengerRotation(
settings: HorseSettings,
@@ -135,9 +149,9 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
vehicle: PokemonEntity,
driver: LivingEntity
): Vec2 {
var turnAmount = calcRotAmount(settings, state, vehicle, driver)
val turnAmount = calcRotAmount(settings, state, vehicle, driver)
return Vec2(vehicle.xRot, vehicle.yRot + turnAmount * 0.5f )
return Vec2(vehicle.xRot, vehicle.yRot + turnAmount )
}
@@ -155,9 +169,9 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
): Float {
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
val handling = vehicle.runtime.resolveDouble(settings.handlingExpr) * 3
val walkHandlingBoost = 5
val walkHandling = handling * walkHandlingBoost
//In degrees per second
val walkHandling = 140.0 * 2
val handling = vehicle.runtime.resolveDouble(settings.handlingExpr) * 2
val maxYawDiff = 90.0f
@@ -171,14 +185,13 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
val rotDiffMod = (sqrt(abs(rotDiffNorm)) * rotDiffNorm.sign)
//Take the inverse so that you turn less at higher speeds
val normSpeed = 1.0f
val normSpeed = 1.0f // = 1.0f - 0.5f*normalizeVal(state.rideVelocityocity.length(), 0.0, topSpeed).toFloat()
// TurnRate should always be quick if not sprinting
val invRelSpeed = (RidingBehaviour.scaleToRange(vehicle.deltaMovement.horizontalDistance(), 0.0, topSpeed ).pow(0.5) - 1.0f) * -1.0f
val turnRate = ((handling.toFloat() / 20.0f) * max(walkHandlingBoost * invRelSpeed, 1.0)).toFloat()
val turnRate = if(state.sprinting.get()) (handling.toFloat() / 20.0f) else (walkHandling.toFloat() / 20.0f)
//Ensure you only ever rotate as much difference as there is between the angles.
val turnSpeed = turnRate * rotDiffMod * normSpeed
val turnSpeed = turnRate * rotDiffMod * normSpeed
val rotAmount = turnSpeed.coerceIn(-abs(rotDiff), abs(rotDiff))
return rotAmount
@@ -202,17 +215,19 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
vehicle: PokemonEntity,
driver: Player
): Vec3 {
val canSprint = vehicle.runtime.resolveBoolean(settings.canSprint)
val canJump = vehicle.runtime.resolveBoolean(settings.canJump)
val rideTopSpeed = vehicle.runtime.resolveDouble(settings.speedExpr) * 1.3
// Check to see if the ride should be walking or sprinting
val walkSpeed = getWalkSpeed(vehicle)
val topSpeed = if(canSprint && state.sprinting.get()) rideTopSpeed else walkSpeed
val accel = vehicle.runtime.resolveDouble(settings.accelerationExpr) * 2
val rideTopSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
val topSpeed = if(state.sprinting.get()) rideTopSpeed else walkSpeed
val accel = vehicle.runtime.resolveDouble(settings.accelerationExpr)
//Flag for determining if player is actively inputting
var activeInput = false
var newVelocity = Vec3(state.rideVelocity.get().x, state.rideVelocity.get().y, state.rideVelocity.get().z).normalize().scale(vehicle.deltaMovement.length())
var newVelocity = Vec3(state.rideVelocity.get().x, state.rideVelocity.get().y, state.rideVelocity.get().z)
//speed up and slow down based on input
if (driver.zza != 0.0f && state.stamina.get() > 0.0) {
@@ -231,11 +246,11 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
activeInput = true
}
// Gravity logic
//Gravity logic
if (vehicle.onGround()) {
newVelocity = Vec3(newVelocity.x, 0.0, newVelocity.z)
} else {
val gravity = (9.8 / ( 20.0)) * 0.2 * 0.8
val gravity = (9.8 / ( 20.0)) * 0.2
val terminalVel = 2.0
val fallingForce = gravity - ( newVelocity.z.sign *gravity *(abs(newVelocity.z) / 2.0))
@@ -247,21 +262,29 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
newVelocity = newVelocity.subtract(0.0, 0.0, min(0.03 * newVelocity.z.sign, newVelocity.z))
}
//TODO: Change this so its tied to a jumping stat and representative of the amount of jumps
//Jump the thang!
if (driver.jumping && vehicle.onGround() && canJump) {
if (driver.jumping && vehicle.onGround()) {
val jumpForce = 1.0
val velMag = newVelocity.length()
val horz = state.rideVelocity.get().horizontalDistance()
newVelocity = newVelocity.add(0.0, jumpForce, 0.0)
//Ensure this doesn't add unwanted forward velocity
//val mag = if(newVelocity.length() < rideTopSpeed) newVelocity.length() else rideTopSpeed
//newVelocity = newVelocity.normalize().scale(mag)
}
//Zero out lateral velocity possibly picked up from a controller transition
newVelocity = Vec3(0.0, newVelocity.y, newVelocity.z)
return newVelocity
}
/*
* Normalizes the current speed between minSpeed and maxSpeed.
* The result is clamped between 0.0 and 1.0, where 0.0 represents minSpeed and 1.0 represents maxSpeed.
*/
private fun normalizeVal(currSpeed: Double, minSpeed: Double, maxSpeed: Double): Double {
require(maxSpeed > minSpeed) { "maxSpeed must be greater than minSpeed" }
return ((currSpeed - minSpeed) / (maxSpeed - minSpeed)).coerceIn(0.0, 1.0)
}
private fun getWalkSpeed( vehicle: PokemonEntity ): Double {
val walkspeed = vehicle.runtime.resolveDouble(vehicle.behaviour.moving.walk.walkSpeed)
val movementSpeed = vehicle.attributes.getValue(Attributes.MOVEMENT_SPEED)
@@ -409,7 +432,7 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
return false
}
override fun shouldRotateRiderHead(
override fun shouldRotatePlayerHead(
settings: HorseSettings,
state: HorseState,
vehicle: PokemonEntity
@@ -430,14 +453,10 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
class HorseSettings : RidingBehaviourSettings {
override val key = HorseBehaviour.KEY
override val stats = mutableMapOf<RidingStat, IntRange>()
var canJump = "true".asExpression()
private set
var canSprint = "true".asExpression()
private set
var speedExpr: Expression = "q.get_ride_stats('SPEED', 'LAND', 1.0, 0.3)".asExpression()
private set
@@ -461,7 +480,6 @@ class HorseSettings : RidingBehaviourSettings {
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeRidingStats(stats)
rideSounds.encode(buffer)
buffer.writeExpression(speedExpr)
buffer.writeExpression(accelerationExpr)
@@ -471,7 +489,6 @@ class HorseSettings : RidingBehaviourSettings {
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
stats.putAll(buffer.readRidingStats())
rideSounds = RideSoundSettingsList.decode(buffer)
speedExpr = buffer.readExpression()
accelerationExpr = buffer.readExpression()
@@ -479,6 +496,7 @@ class HorseSettings : RidingBehaviourSettings {
jumpExpr = buffer.readExpression()
handlingExpr = buffer.readExpression()
}
}
class HorseState : RidingBehaviourState() {
@@ -1,558 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.riding.behaviour.types.land
import com.bedrockk.molang.Expression
import com.bedrockk.molang.runtime.MoLangMath.lerp
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.*
import com.cobblemon.mod.common.api.riding.posing.PoseOption
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.*
import com.cobblemon.mod.common.util.math.geometry.toRadians
import net.minecraft.core.Direction
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
import net.minecraft.world.phys.shapes.Shapes
import org.joml.Matrix3f
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
import kotlin.math.pow
import kotlin.math.sign
class MinekartBehaviour : RidingBehaviour<MinekartSettings, MinekartState> {
companion object {
val KEY = cobblemonResource("land/minekart")
}
override val key = KEY
override fun getRidingStyle(settings: MinekartSettings, state: MinekartState): RidingStyle {
return RidingStyle.LAND
}
val poseProvider = PoseProvider<MinekartSettings, MinekartState>(PoseType.STAND)
.with(PoseOption(PoseType.WALK) { _, state, _ ->
return@PoseOption abs(state.rideVelocity.get().horizontalDistance()) > 0.0
})
// val poseProvider = PoseProvider<MinekartSettings, MinekartState>(PoseType.STAND)
// .with(PoseOption(PoseType.WALK) { _, _, entity -> entity.entityData.get(PokemonEntity.MOVING) })
override fun isActive(settings: MinekartSettings, state: MinekartState, vehicle: PokemonEntity): Boolean {
return Shapes.create(vehicle.boundingBox).blockPositionsAsListRounded().any {
//Need to check other fluids
if (vehicle.isInWater || vehicle.isUnderWater) {
return@any false
}
//This might not actually work, depending on what the yPos actually is. yPos of the middle of the entity? the feet?
if (it.y.toDouble() == (vehicle.position().y)) {
val blockState = vehicle.level().getBlockState(it.below())
return@any !(!blockState.isAir && blockState.fluidState.isEmpty)
}
true
}
}
override fun pose(settings: MinekartSettings, state: MinekartState, vehicle: PokemonEntity): PoseType {
return poseProvider.select(settings, state, vehicle)
}
override fun speed(
settings: MinekartSettings,
state: MinekartState,
vehicle: PokemonEntity,
driver: Player
): Float {
val boostMax = vehicle.runtime.resolveFloat(settings.boostLimit)
//TODO: should this be by how much turn has accumulated or simply time turning... or both?
val boostPerSecond = 20.0f
val boostLoss = 40.0f
if (driver.jumping && driver.zza > 0.0f) {
if (!state.drifting.get()) {
if (driver.xxa != 0.0f) {
state.clockwiseDrift.set(driver.xxa.sign > 0.0)
state.turnMomentum.set(0.0f)
state.drifting.set(true)
state.boost.set(0.0f)
} else {
//TODO: Add jumping logic
}
} else {
state.boost.set((state.boost.get() + boostPerSecond / 20.0f).coerceIn(0.0f, boostMax))
}
} else {
if(driver.zza <= 0.0 && state.boost.get() != 0.0f){
state.boost.set(0.0f)
} else if( state.boost.get() > 0.0f ) {
state.boost.set(max(state.boost.get() - boostLoss, 0.0f))
}
state.drifting.set(false)
}
inAirCheck(state, vehicle)
return driver.deltaMovement.length().toFloat()
}
fun inAirCheck(
state: MinekartState,
vehicle: PokemonEntity,
) {
// Check both vertical movement and if there are blocks below.
val posBelow = vehicle.blockPosition().below()
val blockStateBelow = vehicle.level().getBlockState(posBelow)
val isAirOrLiquid = blockStateBelow.isAir || !blockStateBelow.fluidState.isEmpty
val canSupportEntity = blockStateBelow.isFaceSturdy(vehicle.level(), posBelow, Direction.UP)
val standingOnSolid = canSupportEntity && !isAirOrLiquid
// inAir if not on the ground
val inAir = !(vehicle.deltaMovement.y == 0.0 || standingOnSolid)
state.inAir.set(inAir)
}
override fun rotation(
settings: MinekartSettings,
state: MinekartState,
vehicle: PokemonEntity,
driver: LivingEntity
): Vec2 {
var newMomentum = state.turnMomentum.get().toDouble()
//TODO: maybe tie this to max turnspeeds rather than the turn acceleration
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
val turningAcceleration = (vehicle.runtime.resolveDouble(settings.handlingExpr) * 1.5 / 20.0f) / 20.0f * 4.0
val turnInput = (driver.xxa *-1.0f) * turningAcceleration
val driftInput = turnInput * 0.25f
val lowSpeedTurnBoost = (RidingBehaviour.scaleToRange(vehicle.deltaMovement.horizontalDistance(), 0.0, topSpeed ).pow(0.5) - 2.0f) * -1.0f
val forcedDriftTurnMomentum = (80.0f / 20.0f) * lowSpeedTurnBoost
val maxTurnMomentum = (60.0f / 20.0f) * lowSpeedTurnBoost
val maxDriftMomentum = forcedDriftTurnMomentum * 0.6f
if (!state.drifting.get()){
// perform non drift turning
if(abs(newMomentum) <= maxTurnMomentum && turnInput != 0.0 ) {
newMomentum += turnInput
newMomentum = min(max(newMomentum, maxTurnMomentum * -1.0), maxTurnMomentum.toDouble())
} else {
newMomentum = lerp(newMomentum, 0.0, 0.15)
}
state.turnMomentum.set(newMomentum.toFloat())
driver.yRot += newMomentum.toFloat()
return Vec2(driver.xRot, vehicle.yRot + newMomentum.toFloat())
} else {
// perform drift turning
if(abs(newMomentum) <= maxDriftMomentum && driftInput != 0.0 ) {
newMomentum += driftInput
newMomentum = min(max(newMomentum, maxDriftMomentum * -1.0), maxDriftMomentum.toDouble())
} else {
newMomentum = lerp(newMomentum, 0.0, 0.15)
}
val signedDriftTurn = if(state.clockwiseDrift.get()) forcedDriftTurnMomentum * -1.0f else forcedDriftTurnMomentum
state.turnMomentum.set(newMomentum.toFloat())
driver.yRot += newMomentum.toFloat() + signedDriftTurn.toFloat()
return Vec2(driver.xRot, vehicle.yRot + newMomentum.toFloat() + signedDriftTurn.toFloat())
}
}
override fun velocity(
settings: MinekartSettings,
state: MinekartState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
state.rideVelocity.set(calculateRideSpaceVel(settings, state, vehicle, driver))
return state.rideVelocity.get()
}
private fun calculateRideSpaceVel(
settings: MinekartSettings,
state: MinekartState,
vehicle: PokemonEntity,
driver: Player
): Vec3 {
// Check to see if the ride should be walking or sprinting
val rideTopSpeed = vehicle.runtime.resolveDouble(settings.speedExpr) * 0.6 * 2.0
val topSpeed = if (state.drifting.get()) rideTopSpeed * 0.7 else rideTopSpeed
val accel = vehicle.runtime.resolveDouble(settings.accelerationExpr) * 0.3 * 3 * 2.0
val driftAngle = 20.0f
//Align the direction of this vector with the vehicles current heading.
var newVelocity = vehicle.deltaMovement
newVelocity = newVelocity.yRot(vehicle.yRot.toRadians())
// rotate the angle of force if drifting
if (state.drifting.get()) {
val signedDriftAngle = if(state.clockwiseDrift.get()) driftAngle else driftAngle * -1.0f
newVelocity = newVelocity.yRot(signedDriftAngle.toRadians())
}
//speed up and slow down based on input
if (driver.zza != 0.0f && state.stamina.get() > 0.0 && !state.inAir.get()) {
//make sure it can't exceed top speed
var forwardInput = when {
driver.zza > 0 && newVelocity.z > topSpeed -> 0.0
driver.zza < 0 && newVelocity.z <= 0.0 -> 0.0
else -> driver.zza.sign
}
if (!state.drifting.get() && state.boost.get() != 0.0f) {
forwardInput = 2.0;
}
// If drifting we need more acceleration due to the constant turn
val driftAccelBoost = if (state.drifting.get()) 1.5 else 1.0
newVelocity = Vec3(
newVelocity.x,
newVelocity.y,
(newVelocity.z + (accel * forwardInput.toDouble() * driftAccelBoost)))
}
// Gravity logic
if (vehicle.onGround()) {
newVelocity = Vec3(newVelocity.x, 0.0, newVelocity.z)
} else {
//TODO: Should we just go back to standard minecraft gravity or do the lerp modifications prevent that?
//I think minecrafts gravity logic is also too harsh and isn't gamefied enough for mounts maybe? Need
//to do some testing and get other's opinions
val gravity = (9.8 / ( 20.0)) * 0.2 * 0.25 * 3.0
val terminalVel = 2.0
val fallingForce = gravity - ( newVelocity.z.sign *gravity *(abs(newVelocity.z) / 2.0))
newVelocity = Vec3(newVelocity.x, max(newVelocity.y - fallingForce, -terminalVel), newVelocity.z)
}
//ground Friction
var friction = 0.003 * 2.0
if (state.drifting.get()) {friction *= 0.5}
if (!state.inAir.get()) {
newVelocity = newVelocity.subtract(
min(friction , abs(newVelocity.x)) * newVelocity.x.sign,
0.0,
min(friction , abs(newVelocity.z)) * newVelocity.z.sign
)
// TEST FOR LATERAL SLIP OR WHATEVER
//TODO: calc lateral slip and friction better!
if (!state.drifting.get()) {
newVelocity = newVelocity.subtract(
min(friction * 10, abs(newVelocity.x)) * newVelocity.x.sign,
0.0,
0.0,
)
}
}
//TODO: Change this so its tied to a jumping stat and representative of the amount of jumps
// val canJump = vehicle.runtime.resolveBoolean(settings.canJump)
// //Jump the thang!
// if (driver.jumping && vehicle.onGround() && canJump) {
// val jumpForce = 0.5
//
// newVelocity = newVelocity.add(0.0, jumpForce, 0.0)
//
// }
// unrotate the angle of force if drifting
if (state.drifting.get()) {
val inverseSignedDriftAngle = if(state.clockwiseDrift.get()) driftAngle * -1.0f else driftAngle
newVelocity = newVelocity.yRot(inverseSignedDriftAngle.toRadians())
}
return newVelocity
}
override fun angRollVel(
settings: MinekartSettings,
state: MinekartState,
vehicle: PokemonEntity,
driver: Player,
deltaTime: Double
): Vec3 {
return Vec3.ZERO
}
override fun rotationOnMouseXY(
settings: MinekartSettings,
state: MinekartState,
vehicle: PokemonEntity,
driver: Player,
mouseY: Double,
mouseX: Double,
mouseYSmoother: SmoothDouble,
mouseXSmoother: SmoothDouble,
sensitivity: Double,
deltaTime: Double
): Vec3 {
if (driver !is OrientationControllable) return Vec3.ZERO
//Might need to add the smoothing here for default.
val invertRoll = if (Cobblemon.config.invertRoll) -1 else 1
val invertPitch = if (Cobblemon.config.invertPitch) -1 else 1
return Vec3(0.0, mouseY * invertPitch, mouseX * invertRoll)
}
override fun canJump(
settings: MinekartSettings,
state: MinekartState,
vehicle: PokemonEntity,
driver: Player
): Boolean {
return vehicle.runtime.resolveBoolean(settings.canJump)
}
override fun setRideBar(
settings: MinekartSettings,
state: MinekartState,
vehicle: PokemonEntity,
driver: Player
): Float {
return 0.0f
}
override fun jumpForce(
settings: MinekartSettings,
state: MinekartState,
vehicle: PokemonEntity,
driver: Player,
jumpStrength: Int
): Vec3 {
return Vec3.ZERO
}
override fun gravity(
settings: MinekartSettings,
state: MinekartState,
vehicle: PokemonEntity,
regularGravity: Double
): Double {
return 0.0
}
override fun rideFovMultiplier(
settings: MinekartSettings,
state: MinekartState,
vehicle: PokemonEntity,
driver: Player
): Float {
var fov = 1.0f
if(!state.drifting.get() && state.boost.get() != 0.0f) {
//TODO: is this too abrupt? Possibly caluclate differently?
fov = 1.2f
}
return fov
}
override fun useAngVelSmoothing(
settings: MinekartSettings,
state: MinekartState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun useRidingAltPose(
settings: MinekartSettings,
state: MinekartState,
vehicle: PokemonEntity,
driver: Player
): ResourceLocation {
when {
state.drifting.get() -> return cobblemonResource("drifting")
}
return cobblemonResource("no_pose")
}
override fun inertia(settings: MinekartSettings, state: MinekartState, vehicle: PokemonEntity): Double {
return 1.0
}
override fun shouldRoll(settings: MinekartSettings, state: MinekartState, vehicle: PokemonEntity): Boolean {
return false
}
override fun turnOffOnGround(
settings: MinekartSettings,
state: MinekartState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun dismountOnShift(
settings: MinekartSettings,
state: MinekartState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun shouldRotatePokemonHead(
settings: MinekartSettings,
state: MinekartState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun shouldRotateRiderHead(
settings: MinekartSettings,
state: MinekartState,
vehicle: PokemonEntity
): Boolean {
return true
}
override fun getRideSounds(
settings: MinekartSettings,
state: MinekartState,
vehicle: PokemonEntity
): RideSoundSettingsList {
return settings.rideSounds
}
override fun createDefaultState(settings: MinekartSettings) = MinekartState()
}
class MinekartSettings : RidingBehaviourSettings {
override val key = MinekartBehaviour.KEY
override val stats = mutableMapOf<RidingStat, IntRange>()
var canJump = "true".asExpression()
private set
var boostLimit = "100.0f".asExpression()
private set
var speedExpr: Expression = "q.get_ride_stats('SPEED', 'LAND', 1.0, 0.3)".asExpression()
private set
// Max accel is a whole 1.0 in 1 second. The conversion in the function below is to convert seconds to ticks
var accelerationExpr: Expression =
"q.get_ride_stats('ACCELERATION', 'LAND', (1.0 / (20.0 * 1.5)), (1.0 / (20.0 * 5.0)))".asExpression()
private set
var staminaExpr: Expression = "q.get_ride_stats('STAMINA', 'LAND', 30.0, 10.0)".asExpression()
private set
//Between a one block jump and a ten block jump
var jumpExpr: Expression = "q.get_ride_stats('JUMP', 'LAND', 10.0, 1.0)".asExpression()
private set
var handlingExpr: Expression = "q.get_ride_stats('SKILL', 'LAND', 140.0, 20.0)".asExpression()
private set
var rideSounds: RideSoundSettingsList = RideSoundSettingsList()
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeRidingStats(stats)
rideSounds.encode(buffer)
buffer.writeExpression(canJump)
buffer.writeExpression(boostLimit)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
stats.putAll(buffer.readRidingStats())
rideSounds = RideSoundSettingsList.decode(buffer)
canJump = buffer.readExpression()
boostLimit = buffer.readExpression()
}
}
class MinekartState : RidingBehaviourState() {
var currSpeed = ridingState(0.0, Side.BOTH)
var deltaRotation = ridingState(Vec2.ZERO, Side.BOTH)
var drifting = ridingState(false, Side.CLIENT)
var clockwiseDrift = ridingState(false, Side.CLIENT)
val boost: SidedRidingState<Float> = ridingState(0.0f, Side.CLIENT)
val turnMomentum: SidedRidingState<Float> = ridingState(0.0f, Side.CLIENT)
var inAir = ridingState(false, Side.CLIENT)
override fun reset() {
super.reset()
currSpeed.set(0.0, forced = true)
deltaRotation.set(Vec2.ZERO, forced = true)
drifting.set(false, forced = true)
clockwiseDrift.set(false, forced = true)
boost.set(0.0f, forced = true)
turnMomentum.set(0.0f, forced = true)
inAir.set(false, forced = true)
}
override fun encode(buffer: FriendlyByteBuf) {
super.encode(buffer)
buffer.writeBoolean(drifting.get())
buffer.writeBoolean(clockwiseDrift.get())
buffer.writeFloat(boost.get())
buffer.writeFloat(turnMomentum.get())
buffer.writeBoolean(inAir.get())
}
override fun decode(buffer: FriendlyByteBuf) {
super.decode(buffer)
drifting.set(buffer.readBoolean(), forced = true)
clockwiseDrift.set(buffer.readBoolean(), forced = true)
boost.set(buffer.readFloat(), forced = true)
turnMomentum.set(buffer.readFloat(), forced = true)
inAir.set(buffer.readBoolean(), forced = true)
}
override fun toString(): String {
return "MinekartState(currSpeed=${currSpeed.get()}, deltaRotation=${deltaRotation.get()})"
}
override fun copy() = MinekartState().also {
it.rideVelocity.set(rideVelocity.get(), forced = true)
it.stamina.set(stamina.get(), forced = true)
it.currSpeed.set(currSpeed.get(), forced = true)
it.deltaRotation.set(deltaRotation.get(), forced = true)
it.drifting.set(drifting.get(), forced = true)
it.clockwiseDrift.set(clockwiseDrift.get(), forced = true)
it.boost.set(boost.get(), forced = true)
it.turnMomentum.set(turnMomentum.get(), forced = true)
it.inAir.set(inAir.get(), forced = true)
}
override fun shouldSync(previous: RidingBehaviourState): Boolean {
if (previous !is MinekartState) return false
if (previous.drifting.get() != drifting.get()) return true
if (previous.clockwiseDrift.get() != clockwiseDrift.get()) return true
if (previous.boost.get() != boost.get()) return true
if (previous.turnMomentum.get() != turnMomentum.get()) return true
if (previous.inAir.get() != inAir.get()) return true
return super.shouldSync(previous)
}
}
@@ -8,8 +8,7 @@
package com.cobblemon.mod.common.api.riding.behaviour.types.land
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.OrientationControllable
import com.cobblemon.mod.common.api.riding.RidingStyle
@@ -21,9 +20,6 @@ import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.*
import com.cobblemon.mod.common.util.math.geometry.toRadians
import net.minecraft.core.Direction
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.SmoothDouble
@@ -32,15 +28,29 @@ 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.absoluteValue
import kotlin.math.max
import kotlin.math.min
import kotlin.math.sign
class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
companion object {
val KEY = cobblemonResource("land/vehicle")
val MAX_TOP_SPEED = 1.0 // 20 bl/s
val MIN_TOP_SPEED = 0.35 // 7 bl/s
//val MIN_SPEED = 0.25 // 5 bl/s
//Accel will lie between 1.0 second and 5.0 seconds
val MAX_ACCEL = (MAX_TOP_SPEED) / (20*3) //3.0 second to max speed
val MIN_ACCEL = (MAX_TOP_SPEED) / (20*8) // 8 seconds to max speed
//Can rotate 90 degrees
val MAX_HANDLING = 90.0
val MIN_HANDLING = 30.0
val MAX_YAW_HANDLING = 16.0
val MIN_YAW_HANDLING = 8.0
}
override val key = KEY
@@ -50,12 +60,7 @@ class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
}
val poseProvider = PoseProvider<VehicleSettings, VehicleState>(PoseType.STAND)
.with(PoseOption(PoseType.WALK) { _, state, _ ->
return@PoseOption abs(state.rideVelocity.get().horizontalDistance()) > 0.0
})
// val poseProvider = PoseProvider<VehicleSettings, VehicleState>(PoseType.STAND)
// .with(PoseOption(PoseType.WALK) { _, _, entity -> entity.entityData.get(PokemonEntity.MOVING) })
.with(PoseOption(PoseType.WALK) { _, _, entity -> entity.entityData.get(PokemonEntity.MOVING) })
override fun isActive(settings: VehicleSettings, state: VehicleState, vehicle: PokemonEntity): Boolean {
return Shapes.create(vehicle.boundingBox).blockPositionsAsListRounded().any {
@@ -82,38 +87,18 @@ class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
vehicle: PokemonEntity,
driver: Player
): Float {
val topSpeed = vehicle.getRideStat(RidingStat.SPEED, RidingStyle.AIR, MIN_TOP_SPEED, MAX_TOP_SPEED)
val accel = vehicle.getRideStat(RidingStat.ACCELERATION, RidingStyle.AIR, MIN_ACCEL, MAX_ACCEL)
if (driver.isShiftKeyDown) {
state.drifting.set(true)
if(driver.zza != 0.0f) {
state.poweredDrifting.set(true)
} else {
state.poweredDrifting.set(false)
}
} else {
state.drifting.set(false)
state.poweredDrifting.set(false)
//speed up and slow down based on input
if (driver.zza > 0.0 && state.currSpeed.get() < topSpeed) {
state.currSpeed.set(min(state.currSpeed.get() + accel , topSpeed))
} else if (driver.zza < 0.0 && state.currSpeed.get() > 0.0) {
//Decelerate is now always a constant half of max acceleration.
state.currSpeed.set(max(state.currSpeed.get() - (MAX_ACCEL / 2), 0.0))
}
inAirCheck(state, vehicle)
return driver.deltaMovement.length().toFloat()
}
fun inAirCheck(
state: VehicleState,
vehicle: PokemonEntity,
) {
// Check both vertical movement and if there are blocks below.
val posBelow = vehicle.blockPosition().below()
val blockStateBelow = vehicle.level().getBlockState(posBelow)
val isAirOrLiquid = blockStateBelow.isAir || !blockStateBelow.fluidState.isEmpty
val canSupportEntity = blockStateBelow.isFaceSturdy(vehicle.level(), posBelow, Direction.UP)
val standingOnSolid = canSupportEntity && !isAirOrLiquid
// inAir if not on the ground
val inAir = !(vehicle.deltaMovement.y == 0.0 || standingOnSolid)
state.inAir.set(inAir)
return state.currSpeed.get().toFloat()
}
override fun rotation(
@@ -122,38 +107,10 @@ class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
vehicle: PokemonEntity,
driver: LivingEntity
): Vec2 {
var newMomentum = state.turnMomentum.get().toDouble()
// Grab turningAcceleration and divide 20 twice to get
val turningAcceleration = (vehicle.runtime.resolveDouble(settings.handlingExpr) * 1.5 / 20.0f) / 20.0f * 4.0
val turnInput = (driver.xxa *-1.0f) * turningAcceleration
val maxTurnMomentum = 80.0f / 20.0f
// Base boost stats off of normal turning stats
val driftMaxTurnMomentum = maxTurnMomentum * 3.0f
var driftTurnInput = turnInput * 0.4f
if(state.drifting.get() || state.inAir.get()) {
if( newMomentum < driftMaxTurnMomentum) {
driftTurnInput = driftTurnInput * (vehicle.deltaMovement.horizontalDistance() / vehicle.runtime.resolveDouble(settings.speedExpr))
newMomentum += driftTurnInput
}
newMomentum = lerp(newMomentum, 0.0, 0.03)
if( abs(newMomentum) < 0.05 ) {newMomentum = 0.0}
} else {
if(abs(newMomentum) <= maxTurnMomentum && turnInput != 0.0 ) {
newMomentum += turnInput
newMomentum.coerceIn(-maxTurnMomentum.toDouble(), maxTurnMomentum.toDouble())
} else {
newMomentum = lerp(newMomentum, 0.0, 0.15)
}
}
state.turnMomentum.set(newMomentum.toFloat())
driver.yRot += newMomentum.toFloat()
return Vec2(driver.xRot, vehicle.yRot + newMomentum.toFloat())
val rotationDegrees = driver.xxa * vehicle.runtime.resolveFloat(settings.rotationSpeed)
val rotation = Vec2(driver.xRot, vehicle.yRot - rotationDegrees)
state.deltaRotation.set(Vec2(rotation.x - driver.rotationVector.x, rotation.y - vehicle.rotationVector.y))
return rotation
}
override fun velocity(
@@ -163,103 +120,18 @@ class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
driver: Player,
input: Vec3
): Vec3 {
state.rideVelocity.set(calculateRideSpaceVel(settings, state, vehicle, driver))
return state.rideVelocity.get()
}
private fun calculateRideSpaceVel(
settings: VehicleSettings,
state: VehicleState,
vehicle: PokemonEntity,
driver: Player
): Vec3 {
// Check to see if the ride should be walking or sprinting
//val walkSpeed = getWalkSpeed(vehicle)
val rideTopSpeed = vehicle.runtime.resolveDouble(settings.speedExpr) * 0.6 * 2.0
val topSpeed = if (state.drifting.get()) rideTopSpeed * 0.5 else rideTopSpeed
val accel = vehicle.runtime.resolveDouble(settings.accelerationExpr) * 0.3 * 3 * 2.0
//Flag for determining if player is actively inputting
var activeInput = false
var newVelocity = Vec3.ZERO
if (!state.drifting.get()) {
newVelocity = vehicle.deltaMovement
newVelocity = newVelocity.yRot(vehicle.yRot.toRadians())
} else {
state
newVelocity = vehicle.deltaMovement
// Align the direction of movement with the world coordinate space so as not to modify it through turning
val yawAligned = Matrix3f().rotateY(vehicle.yRot.toRadians())
newVelocity = (newVelocity.toVector3f().mul(yawAligned)).toVec3d()
val runtime = vehicle.runtime
val driveFactor = runtime.resolveFloat(settings.driveFactor)
var g = driver.zza * driveFactor
if (g <= 0.0f) {
g *= runtime.resolveFloat(settings.reverseDriveFactor)
}
//speed up and slow down based on input
if (driver.zza != 0.0f && state.stamina.get() > 0.0 && !state.inAir.get()) {
//make sure it can't exceed top speed
val forwardInput = when {
driver.zza > 0 && newVelocity.z > topSpeed -> 0.0
driver.zza < 0 && newVelocity.z <= 0.0 -> 0.0
else -> driver.zza.sign
}
newVelocity = Vec3(
newVelocity.x,
newVelocity.y,
(newVelocity.z + (accel * forwardInput.toDouble())))
activeInput = true
if (driver.xxa != 0F && g.absoluteValue < runtime.resolveFloat(settings.minimumSpeedToTurn)) {
driver.xxa = 0F
}
val velocity = Vec3(0.0 /* Maybe do drifting here later */, 0.0, g.toDouble() * state.currSpeed.get())
// Gravity logic
if (vehicle.onGround()) {
newVelocity = Vec3(newVelocity.x, 0.0, newVelocity.z)
} else {
val gravity = (9.8 / ( 20.0)) * 0.2 * 0.25 * 3.0
val terminalVel = 2.0
val fallingForce = gravity - ( newVelocity.z.sign *gravity *(abs(newVelocity.z) / 2.0))
newVelocity = Vec3(newVelocity.x, max(newVelocity.y - fallingForce, -terminalVel), newVelocity.z)
}
//ground Friction
var friction = 0.003 * 2.0
if (state.drifting.get()) {friction *= 0.1}
if (!state.inAir.get()) {
newVelocity = newVelocity.subtract(
min(friction , abs(newVelocity.x)) * newVelocity.x.sign,
0.0,
min(friction , abs(newVelocity.z)) * newVelocity.z.sign
)
// TEST FOR LATERAL SLIP OR WHATEVER
//TODO: calc lateral slip and friction better!
if (!state.drifting.get()) {
newVelocity = newVelocity.subtract(
min(friction * 10, abs(newVelocity.x)) * newVelocity.x.sign,
0.0,
0.0,
)
}
}
//TODO: Change this so its tied to a jumping stat and representative of the amount of jumps
val canJump = vehicle.runtime.resolveBoolean(settings.canJump)
//Jump the thang!
if (driver.jumping && vehicle.onGround() && canJump) {
val jumpForce = 0.5
newVelocity = newVelocity.add(0.0, jumpForce, 0.0)
}
return newVelocity
return velocity
}
override fun angRollVel(
@@ -317,7 +189,10 @@ class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
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(
@@ -326,7 +201,7 @@ class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
vehicle: PokemonEntity,
regularGravity: Double
): Double {
return 0.0
return regularGravity
}
override fun rideFovMultiplier(
@@ -352,15 +227,11 @@ class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
vehicle: PokemonEntity,
driver: Player
): ResourceLocation {
when {
state.poweredDrifting.get() -> return cobblemonResource("powered_drifting")
state.drifting.get() -> return cobblemonResource("drifting")
}
return cobblemonResource("no_pose")
}
override fun inertia(settings: VehicleSettings, state: VehicleState, vehicle: PokemonEntity): Double {
return 1.0
return 0.5
}
override fun shouldRoll(settings: VehicleSettings, state: VehicleState, vehicle: PokemonEntity): Boolean {
@@ -391,12 +262,12 @@ class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
return false
}
override fun shouldRotateRiderHead(
override fun shouldRotatePlayerHead(
settings: VehicleSettings,
state: VehicleState,
vehicle: PokemonEntity
): Boolean {
return true
return false
}
override fun getRideSounds(
@@ -412,11 +283,13 @@ class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
class VehicleSettings : RidingBehaviourSettings {
override val key = VehicleBehaviour.KEY
override val stats = mutableMapOf<RidingStat, IntRange>()
var canJump = "true".asExpression()
private set
var jumpVector = listOf("0".asExpression(), "0.3".asExpression(), "0".asExpression())
private set
var speed = "0.3".asExpression()
private set
@@ -426,52 +299,44 @@ class VehicleSettings : RidingBehaviourSettings {
var reverseDriveFactor = "0.25".asExpression()
private set
var minimumSpeedToTurn = "0.1".asExpression()
private set
var rotationSpeed = "45/20".asExpression()
private set
var lookYawLimit = "101".asExpression()
private set
var speedExpr: Expression = "q.get_ride_stats('SPEED', 'LAND', 1.0, 0.3)".asExpression()
private set
// Max accel is a whole 1.0 in 1 second. The conversion in the function below is to convert seconds to ticks
var accelerationExpr: Expression =
"q.get_ride_stats('ACCELERATION', 'LAND', (1.0 / (20.0 * 1.5)), (1.0 / (20.0 * 5.0)))".asExpression()
private set
// Between 30 seconds and 10 seconds at the lowest when at full speed.
var staminaExpr: Expression = "q.get_ride_stats('STAMINA', 'LAND', 30.0, 10.0)".asExpression()
private set
//Between a one block jump and a ten block jump
var jumpExpr: Expression = "q.get_ride_stats('JUMP', 'LAND', 10.0, 1.0)".asExpression()
private set
var handlingExpr: Expression = "q.get_ride_stats('SKILL', 'LAND', 140.0, 20.0)".asExpression()
private set
var rideSounds: RideSoundSettingsList = RideSoundSettingsList()
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeRidingStats(stats)
rideSounds.encode(buffer)
buffer.writeExpression(canJump)
buffer.writeExpression(jumpVector[0])
buffer.writeExpression(jumpVector[1])
buffer.writeExpression(jumpVector[2])
buffer.writeExpression(speed)
buffer.writeExpression(driveFactor)
buffer.writeExpression(reverseDriveFactor)
buffer.writeExpression(minimumSpeedToTurn)
buffer.writeExpression(rotationSpeed)
buffer.writeExpression(lookYawLimit)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
stats.putAll(buffer.readRidingStats())
rideSounds = RideSoundSettingsList.decode(buffer)
canJump = buffer.readExpression()
jumpVector = listOf(
buffer.readExpression(),
buffer.readExpression(),
buffer.readExpression()
)
speed = buffer.readExpression()
driveFactor = buffer.readExpression()
reverseDriveFactor = buffer.readExpression()
minimumSpeedToTurn = buffer.readExpression()
rotationSpeed = buffer.readExpression()
lookYawLimit = buffer.readExpression()
}
@@ -480,36 +345,11 @@ class VehicleSettings : RidingBehaviourSettings {
class VehicleState : RidingBehaviourState() {
var currSpeed = ridingState(0.0, Side.BOTH)
var deltaRotation = ridingState(Vec2.ZERO, Side.BOTH)
var drifting = ridingState(false, Side.CLIENT)
var poweredDrifting = ridingState(false, Side.CLIENT)
val turnMomentum: SidedRidingState<Float> = ridingState(0.0f, Side.CLIENT)
var inAir = ridingState(false, Side.CLIENT)
override fun reset() {
super.reset()
currSpeed.set(0.0, forced = true)
deltaRotation.set(Vec2.ZERO, forced = true)
drifting.set(false, forced = true)
poweredDrifting.set(false, forced = true)
turnMomentum.set(0.0f, forced = true)
inAir.set(false, forced = true)
}
override fun encode(buffer: FriendlyByteBuf) {
super.encode(buffer)
buffer.writeBoolean(drifting.get())
buffer.writeBoolean(poweredDrifting.get())
buffer.writeFloat(turnMomentum.get())
buffer.writeBoolean(inAir.get())
}
override fun decode(buffer: FriendlyByteBuf) {
super.decode(buffer)
drifting.set(buffer.readBoolean(), forced = true)
poweredDrifting.set(buffer.readBoolean(), forced = true)
turnMomentum.set(buffer.readFloat(), forced = true)
inAir.set(buffer.readBoolean(), forced = true)
}
override fun toString(): String {
@@ -521,18 +361,5 @@ class VehicleState : RidingBehaviourState() {
it.stamina.set(stamina.get(), forced = true)
it.currSpeed.set(currSpeed.get(), forced = true)
it.deltaRotation.set(deltaRotation.get(), forced = true)
it.drifting.set(drifting.get(), forced = true)
it.poweredDrifting.set(poweredDrifting.get(), forced = true)
it.turnMomentum.set(turnMomentum.get(), forced = true)
it.inAir.set(inAir.get(), forced = true)
}
override fun shouldSync(previous: RidingBehaviourState): Boolean {
if (previous !is VehicleState) return false
if (previous.drifting.get() != drifting.get()) return true
if (previous.poweredDrifting.get() != poweredDrifting.get()) return true
if (previous.turnMomentum.get() != turnMomentum.get()) return true
if (previous.inAir.get() != inAir.get()) return true
return super.shouldSync(previous)
}
}
@@ -330,7 +330,7 @@ class BoatBehaviour : RidingBehaviour<BoatSettings, BoatState> {
return vehicle.runtime.resolveBoolean(settings.rotatePokemonHead)
}
override fun shouldRotateRiderHead(
override fun shouldRotatePlayerHead(
settings: BoatSettings,
state: BoatState,
vehicle: PokemonEntity
@@ -354,7 +354,6 @@ class BoatBehaviour : RidingBehaviour<BoatSettings, BoatState> {
class BoatSettings : RidingBehaviourSettings {
override val key = BoatBehaviour.KEY
override val stats = mutableMapOf<RidingStat, IntRange>()
var rideSounds: RideSoundSettingsList = RideSoundSettingsList()
var terminalVelocity = "-2.0".asExpression()
@@ -365,13 +364,11 @@ class BoatSettings : RidingBehaviourSettings {
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeRidingStats(stats)
rideSounds.encode(buffer)
buffer.writeExpression(terminalVelocity)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
stats.putAll(buffer.readRidingStats())
rideSounds = RideSoundSettingsList.decode(buffer)
terminalVelocity = buffer.readExpression()
}
@@ -15,13 +15,10 @@ 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.readRidingStats
import com.cobblemon.mod.common.util.writeRidingStats
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.SmoothDouble
@@ -218,7 +215,7 @@ class BurstBehaviour : RidingBehaviour<BurstSettings, BurstState> {
return false
}
override fun shouldRotateRiderHead(
override fun shouldRotatePlayerHead(
settings: BurstSettings,
state: BurstState,
vehicle: PokemonEntity
@@ -239,7 +236,6 @@ class BurstBehaviour : RidingBehaviour<BurstSettings, BurstState> {
class BurstSettings : RidingBehaviourSettings {
override val key = BurstBehaviour.KEY
override val stats = mutableMapOf<RidingStat, IntRange>()
var dashSpeed = 1F
private set
@@ -248,13 +244,11 @@ class BurstSettings : RidingBehaviourSettings {
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeRidingStats(stats)
rideSounds.encode(buffer)
buffer.writeFloat(dashSpeed)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
stats.putAll(buffer.readRidingStats())
rideSounds = RideSoundSettingsList.decode(buffer)
dashSpeed = buffer.readFloat()
}
@@ -8,7 +8,6 @@
package com.cobblemon.mod.common.api.riding.behaviour.types.liquid
import com.bedrockk.molang.Expression
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.OrientationControllable
@@ -17,11 +16,9 @@ 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.*
import com.cobblemon.mod.common.util.math.geometry.toRadians
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.SmoothDouble
@@ -32,9 +29,7 @@ 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 {
@@ -48,7 +43,7 @@ class DolphinBehaviour : RidingBehaviour<DolphinSettings, DolphinState> {
}
val poseProvider = PoseProvider<DolphinSettings, DolphinState>(PoseType.FLOAT)
.with(PoseOption(PoseType.SWIM) { _, _, entity -> entity.deltaMovement.length() > 0.1 })
.with(PoseOption(PoseType.SWIM) { _, _, entity -> entity.entityData.get(PokemonEntity.MOVING) })
override fun isActive(settings: DolphinSettings, state: DolphinState, vehicle: PokemonEntity): Boolean {
return Shapes.create(vehicle.boundingBox).blockPositionsAsListRounded().any {
@@ -166,49 +161,11 @@ class DolphinBehaviour : RidingBehaviour<DolphinSettings, DolphinState> {
deltaTime: Double
): Vec3 {
if (driver !is OrientationControllable) return Vec3.ZERO
val controller = (driver as OrientationControllable).orientationController
//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
//Might need to add the smoothing here for default.
val invertRoll = if (Cobblemon.config.invertRoll) -1 else 1
val invertPitch = if (Cobblemon.config.invertPitch) -1 else 1
val mouseXc = (mouseX).coerceIn(-60.0, 60.0)
val mouseYc = (mouseY).coerceIn(-60.0, 60.0)
val xInput = mouseXSmoother.getNewDeltaValue(mouseXc * 0.1 * invertRoll, deltaTime * smoothingSpeed);
val yInput = mouseYSmoother.getNewDeltaValue(mouseYc * 0.1 * invertPitch, deltaTime * smoothingSpeed);
val rollRot = xInput
val pitchRot = yInput
// Roll
// if (abs(controller.pitch + rollRot) >= 89.5 ) {
// mouseXSmoother.reset()
// } else {
//
// }
controller.rotateRoll(rollRot.toFloat())
// Yaw
// TODO:move all magic numbers to handling expression
val yawSpeed = handling * deltaTime * 0.25
val yawForce = sin(controller.roll.toRadians()) * yawSpeed * ( 1.0 - min(sqrt(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.ZERO
return Vec3(0.0, mouseY * invertPitch, mouseX * invertRoll)
}
override fun canJump(
@@ -297,7 +254,7 @@ class DolphinBehaviour : RidingBehaviour<DolphinSettings, DolphinState> {
return false
}
override fun shouldRotateRiderHead(
override fun shouldRotatePlayerHead(
settings: DolphinSettings,
state: DolphinState,
vehicle: PokemonEntity
@@ -319,7 +276,6 @@ class DolphinBehaviour : RidingBehaviour<DolphinSettings, DolphinState> {
class DolphinSettings : RidingBehaviourSettings {
override val key = DolphinBehaviour.KEY
override val stats = mutableMapOf<RidingStat, IntRange>()
var canJump = "true".asExpression()
private set
@@ -339,17 +295,10 @@ class DolphinSettings : RidingBehaviourSettings {
var strafeFactor = "0.2".asExpression()
private set
var jumpExpr: Expression = "q.get_ride_stats('JUMP', 'AIR', 200.0, 128.0)".asExpression()
var handlingExpr: Expression = "q.get_ride_stats('SKILL', 'AIR', 135.0, 45.0)".asExpression()
var speedExpr: Expression = "q.get_ride_stats('SPEED', 'AIR', 1.0, 0.35)".asExpression()
var accelerationExpr: Expression = "q.get_ride_stats('ACCELERATION', 'AIR', (1.0 / (20.0 * 3.0)), (1.0 / (20.0 * 8.0)))".asExpression()
var staminaExpr: Expression = "q.get_ride_stats('STAMINA', 'AIR', 120.0, 20.0)".asExpression()
var rideSounds: RideSoundSettingsList = RideSoundSettingsList()
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeRidingStats(stats)
rideSounds.encode(buffer)
buffer.writeExpression(canJump)
buffer.writeExpression(jumpVector[0])
@@ -362,7 +311,6 @@ class DolphinSettings : RidingBehaviourSettings {
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
stats.putAll(buffer.readRidingStats())
rideSounds = RideSoundSettingsList.decode(buffer)
canJump = buffer.readExpression()
jumpVector = listOf(
@@ -50,7 +50,7 @@ class RideLoopSound(val ride: PokemonEntity, val soundSettings: RideSoundSetting
this.looping = true
this.delay = 0
this.volume = 0.1f
this.attenuation = SoundInstance.Attenuation.LINEAR // This class performs its own attenuation
this.attenuation = SoundInstance.Attenuation.NONE // This class performs its own attenuation
if (this.isPassenger) this.relative = true
}
@@ -63,9 +63,9 @@ class RideSoundManager(
}
fun getActiveRideKey(): ResourceLocation {
val key = ride.ridingController?.context?.settings?.key ?: cobblemonResource("no_key")
val key = ride.ridingBehaviourSettings?.key ?: cobblemonResource("no_key")
if (key == CompositeBehaviour.KEY) {
return (ride.ridingController?.context?.state as? CompositeState)?.activeBehaviour?.get() ?: cobblemonResource("no_key")
return (ride.ridingState as? CompositeState)?.activeBehaviour?.get() ?: cobblemonResource("no_key")
} else {
return key
}
@@ -16,7 +16,6 @@ import net.minecraft.world.phys.Vec3
import org.joml.Matrix3f
import org.joml.Quaternionf
import org.joml.Vector3f
import kotlin.math.sign
/**
* Class to handle the updating and containment of ride data used in animation.
@@ -34,7 +33,6 @@ class RidingAnimationData(val ride: PokemonEntity)
var rotSpring: Vec3Spring = Vec3Spring()
var rotDeltaSpring: Vec3Spring = Vec3Spring()
var localVelocitySpring: Vec3Spring = Vec3Spring()
var driverInputSpring: Vec3Spring = Vec3Spring()
fun update() {
val activeRide = ride.hasControllingPassenger()
@@ -138,14 +136,6 @@ class RidingAnimationData(val ride: PokemonEntity)
/******************************************************
* Update springs
*****************************************************/
val driver = ride.controllingPassenger
val vertInput = when {
driver != null && driver.jumping-> 1.0
driver != null && driver.isShiftKeyDown -> -1.0
else -> 0.0
}
val input = if(driver != null) Vec3(driver.xxa.toDouble() * -1, vertInput, driver.zza.toDouble()) else Vec3.ZERO
driverInputSpring.update(input, stiffness * 2, damping)
rotDeltaSpring.update(angDelta, stiffness, damping)
velocitySpring.update(currentVelocity, stiffness, damping)
rotSpring.update(currRot, stiffness, damping)
@@ -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.spawning
import java.time.LocalDate
open class IrlDateRange (
val startDate: LocalDate,
val stopDate: LocalDate? = null
) {
operator fun contains(date: LocalDate): Boolean {
return if(stopDate != null) {
(startDate.isEqual(date) || startDate.isBefore(date)) && (stopDate.isAfter(date) || stopDate.isEqual(date))
} else {
startDate.isEqual(date)
}
}
}
@@ -0,0 +1,20 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.spawning
import java.time.LocalTime
open class IrlTimeRange(
val startTime: LocalTime,
val stopTime: LocalTime
) {
operator fun contains(time: LocalTime): Boolean {
return (time.isAfter(startTime) || time == startTime) && time.isBefore(stopTime)
}
}
@@ -74,6 +74,8 @@ object SpawnLoader {
.registerTypeAdapter(NPCClass::class.java, NPCClassReferenceAdapter)
.registerTypeAdapter(Expression::class.java, ExpressionAdapter)
.registerTypeAdapter(ExpressionLike::class.java, ExpressionLikeAdapter)
.registerTypeAdapter(IrlTimeRange::class.java, IrlTimeRangeAdapter)
.registerTypeAdapter(IrlDateRange::class.java, IrlDateRangeAdapter)
.create()
var deserializingConditionClass: Class<out SpawningCondition<*>>? = null
@@ -44,12 +44,10 @@ class FishingSpawningCondition: SpawningCondition<FishingSpawnablePosition>() {
if (minLureLevel != null) { // check for the lureLevel of the rod
val pokerodStack = spawnablePosition.rodStack
val lureLevel = spawnablePosition.enchantmentRegistry.getHolder(Enchantments.LURE).map {
EnchantmentHelper.getItemEnchantmentLevel(
it,
pokerodStack
)
}.orElse(0)
val lureLevel = EnchantmentHelper.getItemEnchantmentLevel(
spawnablePosition.enchantmentRegistry.getHolder(Enchantments.LURE).get(),
pokerodStack
)
if (lureLevel < minLureLevel!!) {
return false
} else if (maxLureLevel != null && lureLevel > maxLureLevel!!) {
@@ -8,7 +8,10 @@
package com.cobblemon.mod.common.api.spawning.condition
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.conditional.RegistryLikeCondition
import com.cobblemon.mod.common.api.spawning.IrlDateRange
import com.cobblemon.mod.common.api.spawning.IrlTimeRange
import com.cobblemon.mod.common.api.spawning.MoonPhaseRange
import com.cobblemon.mod.common.api.spawning.TimeRange
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
@@ -21,6 +24,8 @@ import net.minecraft.tags.TagKey
import net.minecraft.world.level.biome.Biome
import net.minecraft.world.level.levelgen.WorldgenRandom
import net.minecraft.world.level.levelgen.structure.Structure
import java.time.LocalDate
import java.time.LocalTime
/**
* The root of spawning conditions that can be applied to a spawnable position. What type
@@ -58,6 +63,8 @@ abstract class SpawningCondition<T : SpawnablePosition> {
var timeRange: TimeRange? = null
var structures: MutableList<Either<ResourceLocation, TagKey<Structure>>>? = null
var isSlimeChunk: Boolean? = null
var irlTimeRange: IrlTimeRange? = null
var irlDateRange: IrlDateRange? = null
var markers: MutableList<String>? = null
@Transient
@@ -129,6 +136,10 @@ abstract class SpawningCondition<T : SpawnablePosition> {
val random = Random(seed)
return random.nextInt(10) == 0*/
} else if (irlTimeRange != null && !irlTimeRange!!.contains(LocalTime.now(Cobblemon.config.timeZoneId))){
return false
} else if (irlDateRange != null && !irlDateRange!!.contains(LocalDate.now(Cobblemon.config.timeZoneId))){
return false
}
return true
@@ -160,6 +171,10 @@ abstract class SpawningCondition<T : SpawnablePosition> {
return false
if (structures != null && structures!!.any { it == null })
return false
if(irlTimeRange != null && irlTimeRange!!.stopTime.isBefore(irlTimeRange!!.startTime))
return false
if(irlDateRange != null && irlDateRange!!.stopDate != null && irlDateRange!!.stopDate!!.isBefore(irlDateRange!!.startDate))
return false
return true
}
}
@@ -29,13 +29,13 @@ import net.minecraft.world.entity.Entity
* @author Hiroku, Plastered_Crab
* @since March 18th, 2025
*/
open class SpawnBaitInfluence(val effects: List<SpawnBait.Effect>, val onUsed: (time: Int, entity: PokemonEntity?) -> Unit = { _, _ -> }) : SpawningInfluence {
class SpawnBaitInfluence(val effects: List<SpawnBait.Effect>, val onUsed: (time: Int) -> Unit = {}) : SpawningInfluence {
var usedTimes = 0
private fun markUsed(entity: PokemonEntity? = null) {
private fun markUsed() {
usedTimes++
onUsed(usedTimes, entity)
onUsed(usedTimes)
}
override fun affectSpawn(action: SpawnAction<*>, entity: Entity) {
@@ -43,7 +43,7 @@ open class SpawnBaitInfluence(val effects: List<SpawnBait.Effect>, val onUsed: (
val merged = SpawnBaitUtils.mergeEffects(effects)
merged.forEach { effect ->
if (Math.random() <= effect.chance) {
markUsed(entity)
markUsed()
Effects.getEffectFunction(effect.type)?.invoke(entity, effect)
}
}
@@ -17,7 +17,7 @@ import net.minecraft.server.level.ServerLevel
import net.minecraft.world.level.block.state.BlockState
import com.cobblemon.mod.common.api.spawning.SpawningZoneGenerator
import com.cobblemon.mod.common.api.spawning.prospecting.IncenseSweetDetector
import com.cobblemon.mod.common.api.spawning.prospecting.LurePokeSnackDetector
import com.cobblemon.mod.common.api.spawning.prospecting.LureCakeDetector
import com.cobblemon.mod.common.api.spawning.prospecting.SaccharineLogSlatheredDetector
/**
@@ -31,7 +31,7 @@ interface SpawningInfluenceDetector {
companion object {
@JvmStatic
val detectors = mutableSetOf<SpawningInfluenceDetector>(
LurePokeSnackDetector,
LureCakeDetector,
SaccharineLogSlatheredDetector,
IncenseSweetDetector
)
@@ -37,19 +37,18 @@ object IncenseSweetDetector : SpawningInfluenceDetector {
val searchRange = RANGE + ceil(sqrt(((input.length pow 2) + (input.width pow 2)).toDouble())).toInt()
val centerPos = input.getCenter().toBlockPos()
// TODO after 1.7
// val sweetIncensePositions = world.poiManager.findAll(
// { holder: Holder<PoiType> -> holder.`is`(CobblemonPoiTypes.INCENSE_SWEET_KEY) },
// { true },
// centerPos,
// searchRange,
// PoiManager.Occupancy.ANY
// ).toList()
//
// for (pos in sweetIncensePositions) {
// val influence = SpatialSpawningZoneInfluence(pos, radius = RANGE.toFloat(), IncenseSweetInfluence(pos))
// listOfInfluences.add(influence)
// }
val sweetIncensePositions = world.poiManager.findAll(
{ holder: Holder<PoiType> -> holder.`is`(CobblemonPoiTypes.INCENSE_SWEET_KEY) },
{ true },
centerPos,
searchRange,
PoiManager.Occupancy.ANY
).toList()
for (pos in sweetIncensePositions) {
val influence = SpatialSpawningZoneInfluence(pos, radius = RANGE.toFloat(), IncenseSweetInfluence(pos))
listOfInfluences.add(influence)
}
return listOfInfluences
}
@@ -0,0 +1,104 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.spawning.prospecting
import com.cobblemon.mod.common.CobblemonPoiTypes
import com.cobblemon.mod.common.api.fishing.SpawnBait
import com.cobblemon.mod.common.api.spawning.influence.BucketNormalizingInfluence
import com.cobblemon.mod.common.api.spawning.influence.SpatialSpawningZoneInfluence
import com.cobblemon.mod.common.api.spawning.influence.SpawnBaitInfluence
import com.cobblemon.mod.common.api.spawning.influence.SpawningZoneInfluence
import com.cobblemon.mod.common.api.spawning.influence.UnconditionalSpawningZoneInfluence
import com.cobblemon.mod.common.api.spawning.influence.detector.SpawningInfluenceDetector
import com.cobblemon.mod.common.api.spawning.spawner.Spawner
import com.cobblemon.mod.common.api.spawning.spawner.SpawningZoneInput
import com.cobblemon.mod.common.block.LureCakeBlock
import com.cobblemon.mod.common.block.entity.LureCakeBlockEntity
import com.cobblemon.mod.common.util.math.pow
import com.cobblemon.mod.common.util.toBlockPos
import kotlin.math.ceil
import kotlin.math.sqrt
import net.minecraft.core.BlockPos
import net.minecraft.core.Holder
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.entity.ai.village.poi.PoiManager
import net.minecraft.world.entity.ai.village.poi.PoiType
import net.minecraft.world.level.block.state.BlockState
object LureCakeDetector : SpawningInfluenceDetector {
@JvmField
val RANGE: Int = 48
override fun detectFromInput(spawner: Spawner, input: SpawningZoneInput): MutableList<SpawningZoneInfluence> {
val world = input.world
val listOfInfluences = mutableListOf<SpawningZoneInfluence>()
val searchRange = RANGE + ceil(sqrt(((input.length pow 2) + (input.width pow 2)).toDouble())).toInt()
val lureCakePositions = world.poiManager.findAll(
{ holder: Holder<PoiType> -> holder.`is`(CobblemonPoiTypes.LURE_CAKE_KEY) },
{ true },
input.getCenter().toBlockPos(),
searchRange,
PoiManager.Occupancy.ANY
).toList()
var highestLureTier = 0
for (lureCakePos in lureCakePositions) {
val blockState = world.getBlockState(lureCakePos)
if (blockState.block !is LureCakeBlock) continue
val blockEntity = world.getBlockEntity(lureCakePos) as? LureCakeBlockEntity ?: continue
val baitEffects = blockEntity.getBaitEffectsFromLureCake()
highestLureTier = maxOf(highestLureTier, baitEffects.filter { it.type == SpawnBait.Effects.RARITY_BUCKET }.maxOfOrNull { it.value }?.toInt() ?: 0)
listOfInfluences.add(
SpatialSpawningZoneInfluence(
pos = lureCakePos,
radius = RANGE.toFloat(),
influence = SpawnBaitInfluence(
effects = baitEffects,
onUsed = { time ->
if (time == 1) {
tryBiteCake(world, lureCakePos)
}
}
)
)
)
}
if (highestLureTier > 0) {
listOfInfluences.add(UnconditionalSpawningZoneInfluence(influence = BucketNormalizingInfluence(tier = highestLureTier)))
}
return listOfInfluences
}
private fun tryBiteCake(world: ServerLevel, lureCakePos: BlockPos) {
if (world.isLoaded(lureCakePos)) {
val blockEntity = world.getBlockEntity(lureCakePos) as? LureCakeBlockEntity ?: return
blockEntity.bites++
if (blockEntity.bites >= blockEntity.maxBites) {
world.removeBlock(lureCakePos, false)
} else {
blockEntity.setChanged()
world.sendBlockUpdated(lureCakePos, blockEntity.blockState, blockEntity.blockState, 3)
}
}
}
override fun detectFromBlock(
world: ServerLevel,
pos: BlockPos,
blockState: BlockState
) = emptyList<SpawningZoneInfluence>()
}
@@ -1,258 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.spawning.prospecting
import com.cobblemon.mod.common.CobblemonPoiTypes
import com.cobblemon.mod.common.api.fishing.SpawnBait
import com.cobblemon.mod.common.api.spawning.influence.BucketNormalizingInfluence
import com.cobblemon.mod.common.api.spawning.influence.SpatialSpawningZoneInfluence
import com.cobblemon.mod.common.api.spawning.influence.SpawnBaitInfluence
import com.cobblemon.mod.common.api.spawning.influence.SpawningZoneInfluence
import com.cobblemon.mod.common.api.spawning.influence.UnconditionalSpawningZoneInfluence
import com.cobblemon.mod.common.api.spawning.influence.detector.SpawningInfluenceDetector
import com.cobblemon.mod.common.api.spawning.spawner.Spawner
import com.cobblemon.mod.common.api.spawning.spawner.SpawningZoneInput
import com.cobblemon.mod.common.block.PokeSnackBlock
import com.cobblemon.mod.common.block.entity.PokeSnackBlockEntity
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.net.messages.client.effect.PokeSnackBlockParticlesPacket
import com.cobblemon.mod.common.util.math.pow
import com.cobblemon.mod.common.util.toBlockPos
import com.cobblemon.mod.common.util.toVec3d
import kotlin.math.ceil
import kotlin.math.sqrt
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.core.Holder
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.entity.Entity
import net.minecraft.world.entity.ai.village.poi.PoiManager
import net.minecraft.world.entity.ai.village.poi.PoiType
import net.minecraft.world.level.block.Blocks
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.phys.Vec3
import kotlin.math.abs
object LurePokeSnackDetector : SpawningInfluenceDetector {
@JvmField
val RANGE: Int = 48
@JvmField
val POKE_SNACK_CRUMBED_ASPECT = "poke_snack_crumbed"
override fun detectFromInput(spawner: Spawner, input: SpawningZoneInput): MutableList<SpawningZoneInfluence> {
val world = input.world
val listOfInfluences = mutableListOf<SpawningZoneInfluence>()
val searchRange = RANGE + ceil(sqrt(((input.length pow 2) + (input.width pow 2)).toDouble())).toInt()
val lurePokeSnackPositions = world.poiManager.findAll(
{ holder: Holder<PoiType> -> holder.`is`(CobblemonPoiTypes.POKE_SNACK_KEY) },
{ true },
input.getCenter().toBlockPos(),
searchRange,
PoiManager.Occupancy.ANY
).toList()
var highestLureTier = 0
for (lurePokeSnackPos in lurePokeSnackPositions) {
val blockState = world.getBlockState(lurePokeSnackPos)
if (blockState.block !is PokeSnackBlock) continue
val blockEntity = world.getBlockEntity(lurePokeSnackPos) as? PokeSnackBlockEntity ?: continue
val baitEffects = blockEntity.getBaitEffects()
highestLureTier = maxOf(highestLureTier, baitEffects.filter { it.type == SpawnBait.Effects.RARITY_BUCKET }.maxOfOrNull { it.value }?.toInt() ?: 0)
listOfInfluences.add(
SpatialSpawningZoneInfluence(
pos = lurePokeSnackPos,
radius = RANGE.toFloat(),
influence = SpawnBaitInfluence(
effects = baitEffects,
onUsed = { usedAmount, entity ->
entity?.let {
// Prevent triggering twice on same entity
if (usedAmount % 2 == 0) {
tryBite(world, lurePokeSnackPos)
moveAndApplyAspect(world, lurePokeSnackPos, entity)
}
}
}
)
)
)
}
if (highestLureTier > 0) {
listOfInfluences.add(UnconditionalSpawningZoneInfluence(influence = BucketNormalizingInfluence(tier = highestLureTier)))
}
return listOfInfluences
}
private fun tryBite(level: ServerLevel, blockPos: BlockPos) {
if (level.isLoaded(blockPos)) {
val blockState = level.getBlockState(blockPos)
val block = blockState.block as? PokeSnackBlock ?: return
block.eat(level, blockPos, blockState, null)
}
}
private fun moveAndApplyAspect(level: ServerLevel, blockPos: BlockPos, entity: PokemonEntity?) {
entity?.let {
entity.pokemon.forcedAspects += POKE_SNACK_CRUMBED_ASPECT
attemptSafeMove(level, blockPos, entity)
}
}
fun attemptSafeMove2(level: ServerLevel, startPos: BlockPos, entity: PokemonEntity, ) {
val inWater = entity.isInWater
// Random offset in a cardinal direction (north, south, east, west)
val randomDirection = listOf(Direction.NORTH, Direction.SOUTH, Direction.EAST, Direction.WEST).random()
val initialOffsetPos = startPos.relative(randomDirection)
// Variables to track the best (closest) safe position
var safePos: BlockPos? = null
var bestDistanceSq = Double.MAX_VALUE
val maxRadius = 10
// Search within a sphere around the initial offset position
for (dx in -maxRadius..maxRadius) {
for (dy in -maxRadius..maxRadius) {
for (dz in -maxRadius..maxRadius) {
val candidatePos = initialOffsetPos.offset(dx, dy, dz)
val distanceSq = startPos.distSqr(candidatePos)
// Check if within sphere radius
if (dx*dx + dy*dy + dz*dz <= maxRadius*maxRadius) {
// Check collision at candidate position
val currentBox = entity.boundingBox.move(
candidatePos.x - entity.x,
candidatePos.y - entity.y,
candidatePos.z - entity.z
)
if (level.noCollision(entity, currentBox)) {
val blockBelowPos = candidatePos.below()
val blockState = level.getBlockState(blockBelowPos)
val isWater = blockState.block == Blocks.WATER
if (inWater) {
if (isWater) {
if (distanceSq < bestDistanceSq) {
bestDistanceSq = distanceSq
safePos = candidatePos
}
}
} else {
if (!blockState.isAir) {
if (distanceSq < bestDistanceSq) {
bestDistanceSq = distanceSq
safePos = candidatePos
}
}
}
}
}
}
}
}
if (safePos != null) {
entity.moveTo(safePos.toVec3d())
PokeSnackBlockParticlesPacket(startPos, safePos).sendToPlayersAround(
startPos.x.toDouble(),
startPos.y.toDouble(),
startPos.z.toDouble(),
64.0,
level.dimension()
)
}
}
fun attemptSafeMove(level: ServerLevel, startPos: BlockPos, entity: PokemonEntity) {
val inWater = entity.isInWater
val maxDistance = 10 // maximum search radius
val maxPositions = 10 // maximum number of positions at the closest distance
var foundPositions: List<BlockPos> = emptyList()
// Search layer by layer, starting from the closest
for (distance in 1..maxDistance) {
val positionsAtThisDistance = mutableListOf<BlockPos>()
for (dx in -distance..distance) {
for (dy in -distance..distance) {
for (dz in -distance..distance) {
// Consider only positions on the surface of the cube at current distance
if (listOf(dx, dy, dz).count { abs(it) != 0 } == distance) {
val candidatePos = startPos.offset(dx, dy, dz)
if (!candidatePos.equals(startPos.relative(Direction.UP))) {
// Check collision
val currentBox = entity.boundingBox.move(
candidatePos.x - entity.x,
candidatePos.y - entity.y,
candidatePos.z - entity.z
)
if (level.noCollision(entity, currentBox)) {
val blockBelowPos = candidatePos.below()
val blockState = level.getBlockState(blockBelowPos)
val isWater = blockState.block == Blocks.WATER
if (inWater) {
if (isWater) {
positionsAtThisDistance.add(candidatePos)
}
} else {
if (!blockState.isAir) {
positionsAtThisDistance.add(candidatePos)
}
}
}
}
}
}
}
}
if (positionsAtThisDistance.isNotEmpty()) {
// Limit to 10 nearest positions (all at this distance)
foundPositions = if (positionsAtThisDistance.size > maxPositions) {
positionsAtThisDistance.shuffled().take(maxPositions)
} else {
positionsAtThisDistance
}
break // Found the nearest layer with safe positions, stop expanding
}
}
if (foundPositions.isNotEmpty()) {
val chosenPos = foundPositions.random()
val centerX = chosenPos.x + 0.5
val centerZ = chosenPos.z + 0.5
entity.moveTo(Vec3(centerX, chosenPos.y.toDouble(), centerZ))
PokeSnackBlockParticlesPacket(startPos, chosenPos).sendToPlayersAround(
startPos.x.toDouble(),
startPos.y.toDouble(),
startPos.z.toDouble(),
64.0,
level.dimension()
)
}
}
override fun detectFromBlock(level: ServerLevel, pos: BlockPos, blockState: BlockState) = emptyList<SpawningZoneInfluence>()
}
@@ -25,6 +25,7 @@ import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.DataKeys
import com.cobblemon.mod.common.util.server
import com.google.gson.JsonObject
import java.util.Collections
import java.util.UUID
import net.minecraft.core.RegistryAccess
import net.minecraft.nbt.CompoundTag
@@ -258,15 +259,9 @@ open class PartyStore(override val uuid: UUID) : PokemonStore<PartyPosition>() {
BattlePokemon.playerOwned(it)
}.also { if (healPokemon) it.effectedPokemon.heal() }
}.toMutableList()
// reposition lead to front of the party
if (leadingPokemon != null && result.first().uuid != leadingPokemon) {
result.find { it.uuid == leadingPokemon }?.let { lead ->
result.remove(lead)
result.add(0, lead)
}
if (leadingPokemon != null) {
Collections.rotate(result, result.size - this.indexOfFirst { it.uuid == leadingPokemon })
}
return result
}
fun clearParty() {
@@ -38,8 +38,6 @@ class Search(
filter = filter.substring(1)
}
filter = filter.lowercase(Locale.ROOT)
val pokemonFilter: PokemonFilter = when (filter) {
"holding", "helditem", "held_item" -> PokemonFilter { pokemon -> !pokemon.heldItem().isEmpty }
"fainted" -> PokemonFilter { pokemon -> pokemon.isFainted() }
@@ -47,16 +45,14 @@ class Search(
"mythical" -> PokemonFilter { pokemon -> pokemon.isMythical() }
"ultrabeast", "ultra_beast" -> PokemonFilter { pokemon -> pokemon.isUltraBeast() }
else -> {
val nameFilter = filter.lowercase(Locale.ROOT)
PokemonFilter { pokemon ->
if (filter.isEmpty()) {
true
} else {
val species = pokemon.species.translatedName.string.lowercase(Locale.ROOT)
val name = pokemon.getDisplayName().string.lowercase(Locale.ROOT)
val props = PokemonProperties.parse(filter)
species.contains(filter) || name.contains(filter) || (props.matches(pokemon) && props.asString().isNotEmpty())
// search by partial name
if (pokemon.species.resourceIdentifier.path.contains(nameFilter)) {
return@PokemonFilter true
}
// search by exactly match in different props
return@PokemonFilter PokemonProperties.parse(filter).matches(pokemon)
}
}
}
@@ -8,6 +8,7 @@
package com.cobblemon.mod.common.battles
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.PrioritizedList
import com.cobblemon.mod.common.api.data.DataRegistry
import com.cobblemon.mod.common.api.reactive.SimpleObservable
@@ -39,13 +40,17 @@ object BagItems : DataRegistry {
val bagItems = PrioritizedList<BagItemLike>()
internal val bagItemsScripts = mutableMapOf<String, String>() // itemId to JavaScript
init {
observable.subscribe {
Cobblemon.showdownThread.queue(ShowdownService::registerBagItems)
}
}
fun getConvertibleForStack(stack: ItemStack): BagItemLike? {
return bagItems.firstOrNull { it.getBagItem(stack) != null }
}
override fun reload(manager: ResourceManager) {
this.bagItemsScripts.clear()
ShowdownService.service.resetRegistryData("bagItem")
manager.listResources("bag_items") { it.path.endsWith(".js") }.forEach { (identifier, resource) ->
resource.open().use { stream ->
stream.bufferedReader().use { reader ->
@@ -55,8 +60,6 @@ object BagItems : DataRegistry {
}
}
}
ShowdownService.service.sendRegistryData(bagItemsScripts, "bagItem")
this.observable.emit(this)
}
}
@@ -127,8 +127,7 @@ data class MoveActionResponse(var moveName: String, var targetPnx: String? = nul
// No PP or disabled or something
return false
}
val availableTargets = (if (gimmickID != null && validGimmickMove) gimmickMove.target else move.target)
.targetList(activeBattlePokemon)?.takeIf { it.isNotEmpty() } ?: return true
val availableTargets = (gimmickMove?.target ?: move.target).targetList(activeBattlePokemon)?.takeIf { it.isNotEmpty() } ?: return true
val pnx = targetPnx ?: return false // If the targets list is non-null then they need to have specified a target
val (_, targetPokemon) = activeBattlePokemon.actor.battle.getActorAndActiveSlotFromPNX(pnx)
@@ -16,9 +16,6 @@ import com.cobblemon.mod.common.api.battles.interpreter.BattleContext
* @author Segfault Guy
* @since April 10th, 2023
*/
@Deprecated(message =
"This was designed before Doubles and Multi battle formats were introduce and has not been maintained since." +
"Accurate context for formats other than Singles is not guaranteed. This will be replaced in the near future.")
class ContextManager {
/** [BattleContext.Type] buckets containing [BattleContexts] of that type. */
@@ -10,7 +10,6 @@ package com.cobblemon.mod.common.battles.runner
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.battles.runner.graal.GraalShowdownService
import com.cobblemon.mod.common.battles.runner.socket.SocketShowdownService
import com.google.gson.JsonArray
import java.util.UUID
@@ -28,13 +27,14 @@ interface ShowdownService {
fun closeConnection()
fun startBattle(battle: PokemonBattle, messages: Array<String>)
fun send(battleId: UUID, messages: Array<String>)
fun getRegistryData(type: String): JsonArray
fun sendRegistryData(data: Map<String, String>, type: String)
fun sendRegistryEntry(data: String, type: String)
fun resetRegistryData(type: String)
fun resetAllRegistries()
fun getAbilityIds(): JsonArray
fun getMoves(): JsonArray
fun getItemIds(): JsonArray
fun registerSpecies()
fun registerBagItems()
fun indicateSpeciesInitialized() {}
companion object {
val service: ShowdownService by lazy { GraalShowdownService() }
}
@@ -10,6 +10,8 @@ package com.cobblemon.mod.common.battles.runner.graal
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.battles.BagItems
import com.cobblemon.mod.common.battles.ShowdownInterpreter
import com.cobblemon.mod.common.battles.runner.ShowdownService
import com.google.gson.Gson
@@ -31,7 +33,6 @@ import org.graalvm.polyglot.HostAccess.Export
import org.graalvm.polyglot.PolyglotAccess
import org.graalvm.polyglot.Value
import org.graalvm.polyglot.io.FileSystem
import kotlin.text.replace
/**
* Mediator service for communicating between the Cobblemon Minecraft mod and Cobblemon showdown service via
@@ -133,38 +134,49 @@ class GraalShowdownService : ShowdownService {
sendToShowdown(battleId, messages)
}
override fun getRegistryData(type: String): JsonArray {
val func = context.getBindings("js").getMember("getData")
val result = func.execute(type).asString()
return gson.fromJson(result, JsonArray::class.java)
override fun getAbilityIds(): JsonArray {
val getCobbledAbilityIdsFn = context.getBindings("js").getMember("getCobbledAbilityIds")
val arrayResult = getCobbledAbilityIdsFn.execute().asString()
return gson.fromJson(arrayResult, JsonArray::class.java)
}
override fun sendRegistryData(data: Map<String, String>, type: String) {
val payload = data.entries.joinToString(prefix = "{", postfix = "}") { (k, v) ->
val newV = v.replace(Regex("[\r\n]+"), " ")
"\"$k\": $newV"
override fun getMoves(): JsonArray {
val getCobbledMovesFn = context.getBindings("js").getMember("getCobbledMoves")
val arrayResult = getCobbledMovesFn.execute().asString()
return gson.fromJson(arrayResult, JsonArray::class.java)
}
override fun getItemIds(): JsonArray {
val getCobbledItemIdsFn = context.getBindings("js").getMember("getCobbledItemIds")
val arrayResult = getCobbledItemIdsFn.execute().asString()
return gson.fromJson(arrayResult, JsonArray::class.java)
}
override fun registerSpecies() {
val receiveSpeciesDataFn = this.context.getBindings("js").getMember("receiveSpeciesData")
val jsArray = this.context.eval("js", "new Array();")
var index = 0L
PokemonSpecies.species.forEach { species ->
jsArray.setArrayElement(index++, this.gson.toJson(PokemonSpecies.ShowdownSpecies(species, null)))
species.forms.forEach { form ->
if (form != species.standardForm) {
jsArray.setArrayElement(index++, this.gson.toJson(PokemonSpecies.ShowdownSpecies(species, form)))
}
}
}
val func = this.context.getBindings("js").getMember("receiveData")
func.execute(payload, type)
}
override fun sendRegistryEntry(data: String, type: String) {
val payload = data.replace(Regex("[\r\n]+"), " ")
val func = this.context.getBindings("js").getMember("receiveData")
func.execute(payload, type)
}
override fun resetRegistryData(type: String) {
this.context.getBindings("js").getMember("resetData").execute(type)
}
override fun resetAllRegistries() {
this.context.getBindings("js").getMember("resetAll").execute()
receiveSpeciesDataFn.execute(jsArray)
}
override fun indicateSpeciesInitialized() {
val func = this.context.getBindings("js").getMember("afterSpeciesInit")
func.execute()
val afterCobbledSpeciesInitFn = this.context.getBindings("js").getMember("afterCobbledSpeciesInit")
afterCobbledSpeciesInitFn.execute()
}
override fun registerBagItems() {
val receiveBagItemDataFn = this.context.getBindings("js").getMember("receiveBagItemData")
for ((itemId, js) in BagItems.bagItemsScripts) {
receiveBagItemDataFn.execute(itemId, js.replace("\n", " "))
}
}
private fun sendToShowdown(battleId: UUID, messages: Array<String>) {
@@ -11,6 +11,7 @@ package com.cobblemon.mod.common.battles.runner.socket
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.battles.BagItems
import com.cobblemon.mod.common.battles.ShowdownInterpreter
import com.cobblemon.mod.common.battles.runner.ShowdownService
import com.cobblemon.mod.common.pokemon.FormData
@@ -101,44 +102,48 @@ class SocketShowdownService(val host: String = "localhost", val port: Int = 1846
ShowdownInterpreter.interpretMessage(battleId, message)
}
override fun sendRegistryData(data: Map<String, String>, type: String) {
data.forEach { (_, value) -> sendRegistryEntry(value, type) }
// The code for sending bulk data is commented out below, because:
// 1) while debugging it's useful to have individual entries, and socket is only used for debug atm
// 2) the species JSONs are way too big to be handled as one line on the JS side (maybe both sides?)
// this can eventually be remedied if socket is intended to be used for any actual servers/etc.
// alternatively, you could get around this limitation by just having ONLY PokemonSpecies send individually,
// but it's likely that with enough custom data for moves/abilities/etc. you'd run into the same issue
/*val payload = data.entries.joinToString(prefix = "{", postfix = "}") { (k, v) ->
val newV = v.replace(Regex("[\r\n]+"), " ")
"\"$k\": $newV"
}
writer.write(">receiveData $type payload")
acknowledge { Cobblemon.LOGGER.error("Failed to send $type data to Showdown: $data") }*/
}
override fun sendRegistryEntry(data: String, type: String) {
val payload = data.replace(Regex("[\r\n]+"), " ")
writer.write(">receiveEntry $type $payload")
acknowledge { Cobblemon.LOGGER.error("Failed to send $type data to Showdown: $payload") }
}
override fun getRegistryData(type: String): JsonArray {
writer.write(">getData $type")
override fun getAbilityIds(): JsonArray {
writer.write(">getCobbledAbilityIds")
writer.flush()
val response = readMessage()
return gson.fromJson(response, JsonArray::class.java)
}
override fun resetRegistryData(type: String) {
writer.write(">resetData $type")
override fun getMoves(): JsonArray {
writer.write(">getCobbledMoves\n")
writer.flush()
val response = readMessage()
return gson.fromJson(response, JsonArray::class.java)
}
override fun getItemIds(): JsonArray {
writer.write(">getCobbledItemIds")
writer.flush()
val response = readMessage()
return gson.fromJson(response, JsonArray::class.java)
}
private fun sendSpeciesData(species: Species, form: FormData?) {
writer.write(">receiveSpeciesData ${gson.toJson(PokemonSpecies.ShowdownSpecies(species, form))}\n")
acknowledge()
}
override fun resetAllRegistries() {
writer.write(">resetAll")
private fun sendBagItem(itemId: String, js: String) {
writer.write(">receiveBagItemData $itemId $js")
acknowledge { Cobblemon.LOGGER.error("Failed to send bag item to Showdown: $itemId") }
}
override fun registerSpecies() {
writer.write(">resetSpeciesData\n")
acknowledge()
PokemonSpecies.species.forEach { species ->
sendSpeciesData(species, null)
species.forms.forEach { form ->
if (form != species.standardForm) {
sendSpeciesData(species, form)
}
}
}
}
fun acknowledge(ifFails: () -> Unit = {}) {
@@ -150,8 +155,14 @@ class SocketShowdownService(val host: String = "localhost", val port: Int = 1846
}
}
override fun registerBagItems() {
for ((itemId, js) in BagItems.bagItemsScripts) {
sendBagItem(itemId, js.replace("\n", " "))
}
}
override fun indicateSpeciesInitialized() {
writer.write(">afterSpeciesInit")
writer.write(">afterCobbledSpeciesInit")
acknowledge()
}
@@ -226,99 +226,115 @@ class ApricornBlock(settings: Properties, val apricorn: Apricorn) : HorizontalDi
const val MIN_AGE = 0
// North
// Stage 0
private val NORTH_STAGE_0 = Shapes.box(0.40625, 0.5, 0.0, 0.59375, 0.6875, 0.1875)
private val NORTH_CAP_STAGE_0 = Shapes.box(0.4375, 0.65625, 0.03125, 0.5625, 0.71875, 0.15625)
// Stage 1
private val NORTH_STAGE_1 = Shapes.box(0.375, 0.40625, 0.0, 0.625, 0.65625, 0.25)
private val NORTH_CAP_STAGE_1 = Shapes.box(0.40625, 0.625, 0.03125, 0.59375, 0.6875, 0.21875)
private val NORTH_TOP_STAGE_1 = Block.box(7.0, 11.0, 0.5, 9.0, 11.5, 2.5)
private val NORTH_BODY_STAGE_1 = Block.box(6.5, 9.0, 0.0, 9.5, 11.0, 3.0)
private val NORTH_BOTTOM_STAGE_1 = Block.box(7.0, 8.5, 0.5, 9.0, 9.0, 2.5)
// Stage 2
private val NORTH_STAGE_2 = Shapes.box(0.34375, 0.296875, 0.0, 0.65625, 0.609375, 0.3125)
private val NORTH_CAP_STAGE_2 = Shapes.box(0.375, 0.59375, 0.03125, 0.625, 0.65625, 0.28125)
private val NORTH_TOP_STAGE_2 = Block.box(6.5, 10.5, 0.5, 9.5, 11.0, 3.5)
private val NORTH_BODY_STAGE_2 = Block.box(6.0, 7.5, 0.0, 10.0, 10.5, 4.0)
private val NORTH_BOTTOM_STAGE_2 = Block.box(6.5, 7.0, 0.5, 9.5, 7.5, 3.5)
// Stage 3 - Full Fruit
private val NORTH_STAGE_3 = Shapes.box(0.3125, 0.1875, 0.0, 0.6875, 0.5625, 0.375)
private val NORTH_CAP_STAGE_3 = Shapes.box(0.375, 0.5625, 0.0625, 0.625, 0.625, 0.3125)
// Stage 3
private val NORTH_TOP_STAGE_3 = Block.box(6.0, 9.75, 0.5, 10.0, 10.5, 4.5)
private val NORTH_BODY_STAGE_3 = Block.box(5.5, 5.75, 0.0, 10.5, 9.75, 5.0)
private val NORTH_BOTTOM_STAGE_3 = Block.box(6.0, 5.0, 0.5, 10.0, 5.75, 4.5)
// Full fruit
private val NORTH_TOP_FRUIT = Block.box(6.0, 9.0, 1.0, 10.0, 10.0, 5.0)
private val NORTH_BODY_FRUIT = Block.box(5.0, 4.0, 0.0, 11.0, 9.0, 6.0)
private val NORTH_BOTTOM_FRUIT = Block.box(5.5, 3.0, 0.5, 10.5, 4.0, 5.5)
private val NORTH_AABB = arrayOf(
Shapes.or(NORTH_STAGE_0, NORTH_CAP_STAGE_0),
Shapes.or(NORTH_STAGE_1, NORTH_CAP_STAGE_1),
Shapes.or(NORTH_STAGE_2, NORTH_CAP_STAGE_2),
Shapes.or(NORTH_STAGE_3, NORTH_CAP_STAGE_3)
Shapes.or(NORTH_BODY_STAGE_1, NORTH_TOP_STAGE_1, NORTH_BOTTOM_STAGE_1),
Shapes.or(NORTH_BODY_STAGE_2, NORTH_TOP_STAGE_2, NORTH_BOTTOM_STAGE_2),
Shapes.or(NORTH_BODY_STAGE_3, NORTH_TOP_STAGE_3, NORTH_BOTTOM_STAGE_3),
Shapes.or(NORTH_BODY_FRUIT, NORTH_TOP_FRUIT, NORTH_BOTTOM_FRUIT)
)
// South
// Stage 0
private val SOUTH_STAGE_0 = Shapes.box(0.40625, 0.5, 0.8125, 0.59375, 0.6875, 1.0)
private val SOUTH_CAP_STAGE_0 = Shapes.box(0.4375, 0.65625, 0.84375, 0.5625, 0.71875, 0.96875)
// Stage 1
private val SOUTH_STAGE_1 = Shapes.box(0.375, 0.40625, 0.75, 0.625, 0.65625, 1.0)
private val SOUTH_CAP_STAGE_1 = Shapes.box(0.40625, 0.625, 0.78125, 0.59375, 0.6875, 0.96875)
private val SOUTH_TOP_STAGE_1 = Block.box(7.0, 11.0, 13.5, 9.0, 11.5, 15.5)
private val SOUTH_BODY_STAGE_1 = Block.box(6.5, 9.0, 13.0, 9.5, 11.0, 16.0)
private val SOUTH_BOTTOM_STAGE_1 = Block.box(7.0, 8.5, 13.5, 9.0, 9.0, 15.5)
// Stage 2
private val SOUTH_STAGE_2 = Shapes.box(0.34375, 0.296875, 0.6875, 0.65625, 0.609375, 1.0)
private val SOUTH_CAP_STAGE_2 = Shapes.box(0.375, 0.59375, 0.71875, 0.625, 0.65625, 0.96875)
private val SOUTH_TOP_STAGE_2 = Block.box(6.5, 10.5, 12.5, 9.5, 11.0, 15.5)
private val SOUTH_BODY_STAGE_2 = Block.box(6.0, 7.5, 12.0, 10.0, 10.5, 16.0)
private val SOUTH_BOTTOM_STAGE_2 = Block.box(6.5, 7.0, 12.5, 9.5, 7.5, 15.5)
// Stage 3 - Full Fruit
private val SOUTH_STAGE_3 = Shapes.box(0.3125, 0.1875, 0.625, 0.6875, 0.5625, 1.0)
private val SOUTH_CAP_STAGE_3 = Shapes.box(0.375, 0.5625, 0.6875, 0.625, 0.625, 0.9375)
// Stage 3
private val SOUTH_TOP_STAGE_3 = Block.box(6.0, 9.75, 11.5, 10.0, 10.5, 15.5)
private val SOUTH_BODY_STAGE_3 = Block.box(5.5, 5.75, 11.0, 10.5, 9.75, 16.0)
private val SOUTH_BOTTOM_STAGE_3 = Block.box(6.0, 5.0, 11.5, 10.0, 5.75, 15.5)
// Full fruit
private val SOUTH_TOP_FRUIT = Block.box(6.0, 9.0, 11.0, 10.0, 10.0, 15.0)
private val SOUTH_BODY_FRUIT = Block.box(5.0, 4.0, 10.0, 11.0, 9.0, 16.0)
private val SOUTH_BOTTOM_FRUIT = Block.box(5.5, 3.0, 10.5, 10.5, 4.0, 15.5)
private val SOUTH_AABB = arrayOf(
Shapes.or(SOUTH_STAGE_0, SOUTH_CAP_STAGE_0),
Shapes.or(SOUTH_STAGE_1, SOUTH_CAP_STAGE_1),
Shapes.or(SOUTH_STAGE_2, SOUTH_CAP_STAGE_2),
Shapes.or(SOUTH_STAGE_3, SOUTH_CAP_STAGE_3)
Shapes.or(SOUTH_BODY_STAGE_1, SOUTH_TOP_STAGE_1, SOUTH_BOTTOM_STAGE_1),
Shapes.or(SOUTH_BODY_STAGE_2, SOUTH_TOP_STAGE_2, SOUTH_BOTTOM_STAGE_2),
Shapes.or(SOUTH_BODY_STAGE_3, SOUTH_TOP_STAGE_3, SOUTH_BOTTOM_STAGE_3),
Shapes.or(SOUTH_BODY_FRUIT, SOUTH_TOP_FRUIT, SOUTH_BOTTOM_FRUIT)
)
// East
// Stage 0
private val EAST_STAGE_0 = Shapes.box(0.8125, 0.5, 0.40625, 1.0, 0.6875, 0.59375)
private val EAST_CAP_STAGE_0 = Shapes.box(0.84375, 0.65625, 0.4375, 0.96875, 0.71875, 0.5625)
// Stage 1
private val EAST_STAGE_1 = Shapes.box(0.75, 0.40625, 0.375, 1.0, 0.65625, 0.625)
private val EAST_CAP_STAGE_1 = Shapes.box(0.78125, 0.625, 0.40625, 0.96875, 0.6875, 0.59375)
private val EAST_TOP_STAGE_1 = Block.box(13.5, 11.0, 7.0, 15.5, 11.5, 9.0)
private val EAST_BODY_STAGE_1 = Block.box(13.0, 9.0, 6.5, 16.0, 11.0, 9.5)
private val EAST_BOTTOM_STAGE_1 = Block.box(13.5, 8.5, 7.0, 15.5, 9.0, 9.0)
// Stage 2
private val EAST_STAGE_2 = Shapes.box(0.6875, 0.296875, 0.34375, 1.0, 0.609375, 0.65625)
private val EAST_CAP_STAGE_2 = Shapes.box(0.71875, 0.59375, 0.375, 0.96875, 0.65625, 0.625)
private val EAST_TOP_STAGE_2 = Block.box(12.5, 10.5, 6.5, 15.5, 11.0, 9.5)
private val EAST_BODY_STAGE_2 = Block.box(12.0, 7.5, 6.0, 16.0, 10.5, 10.0)
private val EAST_BOTTOM_STAGE_2 = Block.box(12.5, 7.0, 6.5, 15.5, 7.5, 9.5)
// Stage 3 - Full Fruit
private val EAST_STAGE_3 = Shapes.box(0.625, 0.1875, 0.3125, 1.0, 0.5625, 0.6875)
private val EAST_CAP_STAGE_3 = Shapes.box(0.6875, 0.5625, 0.375, 0.9375, 0.625, 0.625)
// Stage 3
private val EAST_TOP_STAGE_3 = Block.box(11.5, 9.75, 6.0, 15.5, 10.5, 10.0)
private val EAST_BODY_STAGE_3 = Block.box(11.0, 5.75, 5.5, 16.0, 9.75, 10.5)
private val EAST_BOTTOM_STAGE_3 = Block.box(11.5, 5.0, 6.0, 15.5, 5.75, 10.0)
// Full fruit
private val EAST_TOP_FRUIT = Block.box(11.0, 9.0, 6.0, 15.0, 10.0, 10.0)
private val EAST_BODY_FRUIT = Block.box(10.0, 4.0, 5.0, 16.0, 9.0, 11.0)
private val EAST_BOTTOM_FRUIT = Block.box(10.5, 3.0, 5.5, 15.5, 4.0, 10.5)
private val EAST_AABB = arrayOf(
Shapes.or(EAST_STAGE_0, EAST_CAP_STAGE_0),
Shapes.or(EAST_STAGE_1, EAST_CAP_STAGE_1),
Shapes.or(EAST_STAGE_2, EAST_CAP_STAGE_2),
Shapes.or(EAST_STAGE_3, EAST_CAP_STAGE_3)
Shapes.or(EAST_BODY_STAGE_1, EAST_TOP_STAGE_1, EAST_BOTTOM_STAGE_1),
Shapes.or(EAST_BODY_STAGE_2, EAST_TOP_STAGE_2, EAST_BOTTOM_STAGE_2),
Shapes.or(EAST_BODY_STAGE_3, EAST_TOP_STAGE_3, EAST_BOTTOM_STAGE_3),
Shapes.or(EAST_BODY_FRUIT, EAST_TOP_FRUIT, EAST_BOTTOM_FRUIT)
)
// West
// Stage 0
private val WEST_STAGE_0 = Shapes.box(0.0, 0.5, 0.40625, 0.1875, 0.6875, 0.59375)
private val WEST_CAP_STAGE_0 = Shapes.box(0.03125, 0.65625, 0.4375, 0.15625, 0.71875, 0.5625)
// Stage 1
private val WEST_STAGE_1 = Shapes.box(0.0, 0.40625, 0.375, 0.25, 0.65625, 0.625)
private val WEST_CAP_STAGE_1 = Shapes.box(0.03125, 0.625, 0.40625, 0.21875, 0.6875, 0.59375)
private val WEST_TOP_STAGE_1 = Block.box(0.5, 11.0, 7.0, 2.5, 11.5, 9.0)
private val WEST_BODY_STAGE_1 = Block.box(0.0, 9.0, 6.5, 3.0, 11.0, 9.5)
private val WEST_BOTTOM_STAGE_1 = Block.box(0.5, 8.5, 7.0, 2.5, 9.0, 9.0)
// Stage 2
private val WEST_STAGE_2 = Shapes.box(0.0, 0.296875, 0.34375, 0.3125, 0.609375, 0.65625)
private val WEST_CAP_STAGE_2 = Shapes.box(0.03125, 0.59375, 0.375, 0.28125, 0.65625, 0.625)
private val WEST_TOP_STAGE_2 = Block.box(0.5, 10.5, 6.5, 3.5, 11.0, 9.5)
private val WEST_BODY_STAGE_2 = Block.box(0.0, 7.5, 6.0, 4.0, 10.5, 10.0)
private val WEST_BOTTOM_STAGE_2 = Block.box(0.5, 7.0, 6.5, 3.5, 7.5, 9.5)
// Stage 3 - Full Fruit
private val WEST_STAGE_3 = Shapes.box(0.0, 0.1875, 0.3125, 0.375, 0.5625, 0.6875)
private val WEST_CAP_STAGE_3 = Shapes.box(0.0625, 0.5625, 0.375, 0.3125, 0.625, 0.625)
// Stage 3
private val WEST_TOP_STAGE_3 = Block.box(0.5, 9.75, 6.0, 4.5, 10.5, 10.0)
private val WEST_BODY_STAGE_3 = Block.box(0.0, 5.75, 5.5, 5.0, 9.75, 10.5)
private val WEST_BOTTOM_STAGE_3 = Block.box(0.5, 5.0, 6.0, 4.5, 5.75, 10.0)
// Full fruit
private val WEST_TOP_FRUIT = Block.box(1.0, 9.0, 6.0, 5.0, 10.0, 10.0)
private val WEST_BODY_FRUIT = Block.box(0.0, 4.0, 5.0, 6.0, 9.0, 11.0)
private val WEST_BOTTOM_FRUIT = Block.box(0.5, 3.0, 5.5, 5.5, 4.0, 10.5)
private val WEST_AABB = arrayOf(
Shapes.or(WEST_STAGE_0, WEST_CAP_STAGE_0),
Shapes.or(WEST_STAGE_1, WEST_CAP_STAGE_1),
Shapes.or(WEST_STAGE_2, WEST_CAP_STAGE_2),
Shapes.or(WEST_STAGE_3, WEST_CAP_STAGE_3)
Shapes.or(WEST_BODY_STAGE_1, WEST_TOP_STAGE_1, WEST_BOTTOM_STAGE_1),
Shapes.or(WEST_BODY_STAGE_2, WEST_TOP_STAGE_2, WEST_BOTTOM_STAGE_2),
Shapes.or(WEST_BODY_STAGE_3, WEST_TOP_STAGE_3, WEST_BOTTOM_STAGE_3),
Shapes.or(WEST_BODY_FRUIT, WEST_TOP_FRUIT, WEST_BOTTOM_FRUIT)
)
}
@@ -57,8 +57,7 @@ class BugwortBlock(settings: Properties) : CropBlock(settings), BonemealableBloc
builder.add(IS_WILD)
}
// TODO after 1.7
// override fun getBaseSeedId(): ItemLike = CobblemonItems.BUGWORT
override fun getBaseSeedId(): ItemLike = CobblemonItems.BUGWORT
override fun getShape(state: BlockState, world: BlockGetter, pos: BlockPos, context: CollisionContext): VoxelShape = AGE_TO_SHAPE[this.getAge(state)]
@@ -94,27 +93,26 @@ class BugwortBlock(settings: Properties) : CropBlock(settings), BonemealableBloc
return floor.`is`(BlockTags.DIRT) || floor.`is`(Blocks.FARMLAND)
}
// TODO after 1.7
// override fun useWithoutItem(
// state: BlockState,
// world: Level,
// pos: BlockPos,
// player: Player,
// hit: BlockHitResult
// ): InteractionResult {
// if (state != null && world != null && pos != null) {
// // if bugwort is at max age then revert age to 0 and drop items
// if (player.getItemInHand(InteractionHand.MAIN_HAND).`is`(Items.BONE_MEAL) && !this.isMaxAge(state)) {
// return InteractionResult.PASS
// } else if (this.isMaxAge(state)) {
// world.playSound(null, pos, CobblemonSounds.BERRY_HARVEST, SoundSource.BLOCKS, 0.4F, 1F)
// world.setBlock(pos, state.setValue(this.ageProperty, (0).coerceAtMost(this.maxAge)), UPDATE_CLIENTS)
// Block.popResource(world, pos, ItemStack(CobblemonItems.BUGWORT, Random.nextInt(2, 4)))
// return InteractionResult.sidedSuccess(world.isClientSide)
// }
// }
// return super.useWithoutItem(state, world, pos, player, hit)
// }
override fun useWithoutItem(
state: BlockState,
world: Level,
pos: BlockPos,
player: Player,
hit: BlockHitResult
): InteractionResult {
if (state != null && world != null && pos != null) {
// if bugwort is at max age then revert age to 0 and drop items
if (player.getItemInHand(InteractionHand.MAIN_HAND).`is`(Items.BONE_MEAL) && !this.isMaxAge(state)) {
return InteractionResult.PASS
} else if (this.isMaxAge(state)) {
world.playSound(null, pos, CobblemonSounds.BERRY_HARVEST, SoundSource.BLOCKS, 0.4F, 1F)
world.setBlock(pos, state.setValue(this.ageProperty, (0).coerceAtMost(this.maxAge)), UPDATE_CLIENTS)
Block.popResource(world, pos, ItemStack(CobblemonItems.BUGWORT, Random.nextInt(2, 4)))
return InteractionResult.sidedSuccess(world.isClientSide)
}
}
return super.useWithoutItem(state, world, pos, player, hit)
}
override fun getBonemealAgeIncrease(world: Level): Int = 1
@@ -0,0 +1,168 @@
/*
* 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.block
import com.cobblemon.mod.common.block.entity.CakeBlockEntity
import com.cobblemon.mod.common.item.components.FoodColourComponent
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.DyeColor
import net.minecraft.world.item.ItemStack
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.Level
import net.minecraft.world.level.LevelReader
import net.minecraft.world.level.block.BaseEntityBlock
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.Blocks
import net.minecraft.world.level.block.Rotation
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.StateDefinition
import net.minecraft.world.level.block.state.properties.DirectionProperty
import net.minecraft.world.phys.shapes.CollisionContext
import net.minecraft.world.phys.shapes.VoxelShape
abstract class CakeBlock(settings: Properties): BaseEntityBlock(settings) {
companion object {
val FACING: DirectionProperty = DirectionProperty.create("facing", Direction.Plane.HORIZONTAL)
val SHAPE_BY_BITE = arrayOf(
box(1.0, 0.0, 1.0, 15.0, 9.0, 15.0),
box(3.0, 0.0, 1.0, 15.0, 9.0, 15.0),
box(5.0, 0.0, 1.0, 15.0, 9.0, 15.0),
box(7.0, 0.0, 1.0, 15.0, 9.0, 15.0),
box(9.0, 0.0, 1.0, 15.0, 9.0, 15.0),
box(11.0, 0.0, 1.0, 15.0, 9.0, 15.0),
box(13.0, 0.0, 1.0, 15.0, 9.0, 15.0)
)
enum class Rotation {
NONE,
CLOCKWISE_90,
CLOCKWISE_180,
COUNTERCLOCKWISE_90
}
}
init {
registerDefaultState(stateDefinition.any().setValue(FACING, Direction.NORTH))
}
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
super.createBlockStateDefinition(builder)
builder.add(FACING)
}
override fun setPlacedBy(level: Level, pos: BlockPos, state: BlockState, placer: LivingEntity?, stack: ItemStack) {
super.setPlacedBy(level, pos, state, placer, stack)
if (placer != null) {
level.setBlock(pos, state.setValue(FACING, placer.direction.opposite), 2)
}
val blockEntity = level.getBlockEntity(pos) as? CakeBlockEntity
blockEntity?.initializeFromItemStack(stack)
}
override fun getCloneItemStack(level: LevelReader, pos: BlockPos, state: BlockState): ItemStack {
val blockEntity = level.getBlockEntity(pos) as? CakeBlockEntity ?: return ItemStack.EMPTY
return blockEntity.toItemStack()
}
override fun getShape(
state: BlockState,
level: BlockGetter,
pos: BlockPos,
context: CollisionContext
): VoxelShape {
val bites = getBites(level, pos)
val blockEntity = level.getBlockEntity(pos) as? CakeBlockEntity
val maxBites = blockEntity?.maxBites ?: 6
val visualStage = ((bites * SHAPE_BY_BITE.size) / (maxBites + 1))
.coerceIn(0, SHAPE_BY_BITE.lastIndex)
val baseShape = SHAPE_BY_BITE[visualStage]
return when (state.getValue(FACING)) {
Direction.NORTH -> baseShape
Direction.SOUTH -> rotateShape(baseShape, Rotation.CLOCKWISE_180)
Direction.WEST -> rotateShape(baseShape, Rotation.COUNTERCLOCKWISE_90)
Direction.EAST -> rotateShape(baseShape, Rotation.CLOCKWISE_90)
else -> baseShape
}
}
private fun rotateShape(shape: VoxelShape, rotation: Rotation): VoxelShape {
var newShape = net.minecraft.world.phys.shapes.Shapes.empty()
for (box in shape.toAabbs()) {
val minX = box.minX
val minY = box.minY
val minZ = box.minZ
val maxX = box.maxX
val maxY = box.maxY
val maxZ = box.maxZ
val (rMinX, rMinZ, rMaxX, rMaxZ) = when (rotation) {
Rotation.NONE -> arrayOf(minX, minZ, maxX, maxZ)
Rotation.CLOCKWISE_90 -> arrayOf(minZ, 1.0 - maxX, maxZ, 1.0 - minX)
Rotation.CLOCKWISE_180 -> arrayOf(1.0 - maxX, 1.0 - maxZ, 1.0 - minX, 1.0 - minZ)
Rotation.COUNTERCLOCKWISE_90 -> arrayOf(1.0 - maxZ, minX, 1.0 - minZ, maxX)
}
val rotatedVoxel = net.minecraft.world.phys.shapes.Shapes.box(rMinX, minY, rMinZ, rMaxX, maxY, rMaxZ)
newShape = net.minecraft.world.phys.shapes.Shapes.or(newShape, rotatedVoxel)
}
return newShape
}
override fun spawnDestroyParticles(
level: Level,
player: Player,
pos: BlockPos,
state: BlockState
) {
val blockEntity = level.getBlockEntity(pos) as? CakeBlockEntity
val color = blockEntity?.foodColourComponent?.colours?.lastOrNull()
val woolBlock = when (color) {
DyeColor.RED -> Blocks.RED_WOOL
DyeColor.ORANGE -> Blocks.ORANGE_WOOL
DyeColor.YELLOW -> Blocks.YELLOW_WOOL
DyeColor.LIME -> Blocks.LIME_WOOL
DyeColor.GREEN -> Blocks.GREEN_WOOL
DyeColor.CYAN -> Blocks.CYAN_WOOL
DyeColor.LIGHT_BLUE -> Blocks.LIGHT_BLUE_WOOL
DyeColor.BLUE -> Blocks.BLUE_WOOL
DyeColor.PURPLE -> Blocks.PURPLE_WOOL
DyeColor.MAGENTA -> Blocks.MAGENTA_WOOL
DyeColor.PINK -> Blocks.PINK_WOOL
else -> Blocks.WHITE_WOOL
}
level.levelEvent(player, 2001, pos, getId(woolBlock.defaultBlockState()))
}
fun getFoodColourComponent(level: BlockGetter, pos: BlockPos): FoodColourComponent? {
return (level.getBlockEntity(pos) as? CakeBlockEntity)?.foodColourComponent
}
fun setFoodColourComponent(level: BlockGetter, pos: BlockPos, foodColourComponent: FoodColourComponent) {
(level.getBlockEntity(pos) as? CakeBlockEntity)?.foodColourComponent = foodColourComponent
}
fun getBites(level: BlockGetter, pos: BlockPos): Int = (level.getBlockEntity(pos) as? CakeBlockEntity)?.bites ?: 0
fun setBites(level: BlockGetter, pos: BlockPos, bites: Int) {
(level.getBlockEntity(pos) as? CakeBlockEntity)?.bites = bites
}
}
@@ -0,0 +1,178 @@
/*
* 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.block
import com.cobblemon.mod.common.CobblemonBlocks
import com.cobblemon.mod.common.block.entity.CandlePokeCakeBlockEntity
import com.cobblemon.mod.common.client.render.block.CakeBlockEntityRenderer.Companion.TOP_LAYER_HEIGHT
import com.cobblemon.mod.common.util.toEquipmentSlot
import com.mojang.serialization.MapCodec
import net.minecraft.core.BlockPos
import net.minecraft.core.particles.ParticleTypes
import net.minecraft.sounds.SoundEvents
import net.minecraft.sounds.SoundSource
import net.minecraft.util.RandomSource
import net.minecraft.world.InteractionHand
import net.minecraft.world.InteractionResult
import net.minecraft.world.ItemInteractionResult
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Items
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.AbstractCandleBlock
import net.minecraft.world.level.block.AbstractCandleBlock.extinguish
import net.minecraft.world.level.block.BaseEntityBlock
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.Blocks.*
import net.minecraft.world.level.block.CandleBlock
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.StateDefinition
import net.minecraft.world.level.block.state.properties.BlockStateProperties
import net.minecraft.world.level.block.state.properties.IntegerProperty
import net.minecraft.world.level.gameevent.GameEvent
import net.minecraft.world.level.material.MapColor
import net.minecraft.world.phys.BlockHitResult
import net.minecraft.world.phys.Vec3
import net.minecraft.world.phys.shapes.CollisionContext
import net.minecraft.world.phys.shapes.Shapes
import net.minecraft.world.phys.shapes.VoxelShape
class CandlePokeCakeBlock(settings: Properties) : CakeBlock(settings) {
companion object {
val LIT = BlockStateProperties.LIT
val CANDLE_COLOR = IntegerProperty.create("candle_color", 0, MapColor.MATERIAL_COLORS.size - 1)
fun getCandleByMapColorId(id: Int): CandleBlock {
return when (id) {
MapColor.SAND.id -> CANDLE
MapColor.WOOL.id -> WHITE_CANDLE
MapColor.COLOR_ORANGE.id -> ORANGE_CANDLE
MapColor.COLOR_MAGENTA.id -> MAGENTA_CANDLE
MapColor.COLOR_LIGHT_BLUE.id -> LIGHT_BLUE_CANDLE
MapColor.COLOR_YELLOW.id -> YELLOW_CANDLE
MapColor.COLOR_LIGHT_GREEN.id -> LIME_CANDLE
MapColor.COLOR_PINK.id -> PINK_CANDLE
MapColor.COLOR_GRAY.id -> GRAY_CANDLE
MapColor.COLOR_LIGHT_GRAY.id -> LIGHT_GRAY_CANDLE
MapColor.COLOR_CYAN.id -> CYAN_CANDLE
MapColor.COLOR_PURPLE.id -> PURPLE_CANDLE
MapColor.COLOR_BLUE.id -> BLUE_CANDLE
MapColor.COLOR_BROWN.id -> BROWN_CANDLE
MapColor.COLOR_GREEN.id -> GREEN_CANDLE
MapColor.COLOR_RED.id -> RED_CANDLE
MapColor.COLOR_BLACK.id -> BLACK_CANDLE
else -> CANDLE
} as CandleBlock
}
val CANDLE_SHAPE: VoxelShape = box(7.0, 8.0, 7.0, 9.0, 15.0, 9.0)
val SHAPE: VoxelShape = Shapes.or(SHAPE_BY_BITE[0], CANDLE_SHAPE)
val PARTICLE_OFFSET = Vec3(0.5, 0.5 + TOP_LAYER_HEIGHT, 0.5)
}
init {
registerDefaultState(stateDefinition.any()
.setValue(LIT, false)
.setValue(CANDLE_COLOR, MapColor.SAND.id)
)
}
override fun codec(): MapCodec<out BaseEntityBlock?> {
return simpleCodec(::CandlePokeCakeBlock)
}
override fun newBlockEntity(pos: BlockPos, state: BlockState): BlockEntity {
return CandlePokeCakeBlockEntity(pos, state)
}
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
super.createBlockStateDefinition(builder)
builder.add(LIT, CANDLE_COLOR)
}
override fun useItemOn(
stack: ItemStack,
state: BlockState,
level: Level,
pos: BlockPos,
player: Player,
hand: InteractionHand,
hitResult: BlockHitResult
): ItemInteractionResult {
return if (!stack.`is`(Items.FLINT_AND_STEEL) && !stack.`is`(Items.FIRE_CHARGE)) {
if (candleHit(hitResult) && stack.isEmpty && state.getValue(LIT) == true) {
extinguish(player, state, level, pos)
level.addParticle(ParticleTypes.SMOKE, PARTICLE_OFFSET.x + pos.x, pos.y + PARTICLE_OFFSET.y, pos.z + PARTICLE_OFFSET.z, 0.0, 0.1, 0.0)
ItemInteractionResult.sidedSuccess(level.isClientSide)
} else {
super.useItemOn(stack, state, level, pos, player, hand, hitResult)
}
} else if (state.getValue(LIT) == false) {
level.playSound(player, pos, SoundEvents.FLINTANDSTEEL_USE, SoundSource.BLOCKS, 1.0f, level.random.nextFloat() * 0.4f + 0.8f)
level.setBlock(pos, state.setValue(BlockStateProperties.LIT, true), UPDATE_ALL_IMMEDIATE)
level.gameEvent(player, GameEvent.BLOCK_CHANGE, pos)
player.getItemInHand(hand).hurtAndBreak(1, player, hand.toEquipmentSlot())
ItemInteractionResult.sidedSuccess(level.isClientSide)
} else {
ItemInteractionResult.SKIP_DEFAULT_BLOCK_INTERACTION
}
}
override fun useWithoutItem(
state: BlockState,
level: Level,
pos: BlockPos,
player: Player,
hitResult: BlockHitResult
): InteractionResult {
val oldCookingComponent = getFoodColourComponent(level, pos)
val candle = getCandleByMapColorId(state.getValue(CANDLE_COLOR))
val newBlockState = CobblemonBlocks.POKE_CAKE.defaultBlockState()
level.setBlockAndUpdate(pos, newBlockState)
level.gameEvent(player, GameEvent.BLOCK_CHANGE, pos)
val pokeCakeBlock = newBlockState.block as PokeCakeBlock
oldCookingComponent?.let {
pokeCakeBlock.setFoodColourComponent(level, pos, it)
}
val interactionResult = pokeCakeBlock.eat(level, pos, player)
if (interactionResult.consumesAction()) {
popResource(level, pos, ItemStack(candle.asItem()))
}
return interactionResult
}
private fun candleHit(hit: BlockHitResult): Boolean {
return hit.location.y - hit.blockPos.y.toDouble() > TOP_LAYER_HEIGHT
}
override fun animateTick(
state: BlockState,
level: Level,
pos: BlockPos,
random: RandomSource
) {
if (state.getValue(LIT)) {
AbstractCandleBlock.addParticlesAndSound(level, Vec3(PARTICLE_OFFSET.x + pos.x, PARTICLE_OFFSET.y + pos.y, PARTICLE_OFFSET.z + pos.z), random)
}
}
override fun getShape(
state: BlockState,
level: BlockGetter,
pos: BlockPos,
context: CollisionContext
): VoxelShape = SHAPE
}
@@ -10,23 +10,24 @@ package com.cobblemon.mod.common.block
import com.cobblemon.mod.common.CobblemonBlocks
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.tags.CobblemonBlockTags
import com.mojang.serialization.MapCodec
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.server.level.ServerLevel
import net.minecraft.tags.FluidTags
import net.minecraft.util.Mth
import net.minecraft.util.RandomSource
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.context.BlockPlaceContext
import net.minecraft.world.level.*
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.ItemLike
import net.minecraft.world.level.Level
import net.minecraft.world.level.LevelAccessor
import net.minecraft.world.level.LevelReader
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.Blocks
import net.minecraft.world.level.block.CropBlock
import net.minecraft.world.level.block.SimpleWaterloggedBlock
import net.minecraft.world.level.block.SoundType
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.StateDefinition
import net.minecraft.world.level.block.state.properties.BlockStateProperties
@@ -115,7 +116,7 @@ class HeartyGrainsBlock(settings: Properties) : CropBlock(settings), SimpleWater
}
private fun canGrow(world: LevelReader, pos: BlockPos, state: BlockState): Boolean {
return !this.isMaxAge(state) && canSurvive(state, world, pos) && !(!canGrowInto(world, pos.above()) && state.getValue(AGE) >= AGE_AFTER_HARVEST)
return !this.isMaxAge(state) && canSurvive(state, world, pos)
}
private fun canGrowInto(world: LevelReader, pos: BlockPos): Boolean {
@@ -123,6 +124,28 @@ class HeartyGrainsBlock(settings: Properties) : CropBlock(settings), SimpleWater
return blockState.isAir || blockState.`is`(CobblemonBlocks.HEARTY_GRAINS)
}
override fun playerWillDestroy(world: Level, pos: BlockPos, state: BlockState, player: Player): BlockState {
if (!world.isClientSide) {
if (state.getValue(HALF) == DoubleBlockHalf.UPPER) {
val belowPos = pos.below()
val belowState = world.getBlockState(belowPos)
world.destroyBlock(pos, true) // Break the lower block without drops
world.setBlock(
belowPos,
belowState.setValue(AGE, AGE_AFTER_HARVEST), // Reset age to simulate regrowth preparation
UPDATE_CLIENTS
)
} else if (state.getValue(HALF) == DoubleBlockHalf.LOWER) {
val abovePos = pos.above()
val aboveState = world.getBlockState(abovePos)
if (aboveState.block is HeartyGrainsBlock && aboveState.getValue(HALF) == DoubleBlockHalf.UPPER) {
world.destroyBlock(abovePos, true) // Break the upper block without drops
}
}
}
return super.playerWillDestroy(world, pos, state, player)
}
override fun updateShape(
state: BlockState,
direction: Direction,
@@ -134,12 +157,15 @@ class HeartyGrainsBlock(settings: Properties) : CropBlock(settings), SimpleWater
if (state.getValue(WATERLOGGED)) {
world.scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(world))
}
if (!state.canSurvive(world, pos)) return Blocks.AIR.defaultBlockState()
val doubleBlockHalf = state.getValue(HALF)
if (doubleBlockHalf == DoubleBlockHalf.LOWER && direction == Direction.UP) {
if (state.getValue(AGE) > AGE_AFTER_HARVEST && !neighborState.`is`(CobblemonBlocks.HEARTY_GRAINS)) return state.setValue(AGE, AGE_AFTER_HARVEST)
return if (direction.axis !== Direction.Axis.Y || doubleBlockHalf == DoubleBlockHalf.LOWER != (direction == Direction.UP) ||
neighborState.`is`(this) && neighborState.getValue(HALF) != doubleBlockHalf) {
if (doubleBlockHalf == DoubleBlockHalf.LOWER && direction == Direction.DOWN && !state.canSurvive(world, pos))
Blocks.AIR.defaultBlockState()
else super.updateShape(state, direction, neighborState, world, pos, neighborPos)
} else {
Blocks.AIR.defaultBlockState()
}
return state
}
override fun isValidBonemealTarget(level: LevelReader, pos: BlockPos, state: BlockState): Boolean {
@@ -185,9 +211,7 @@ class HeartyGrainsBlock(settings: Properties) : CropBlock(settings), SimpleWater
}
}
override fun getBonemealAgeIncrease(level: Level): Int {
return Mth.nextInt(level.random, 1, 3)
}
override fun getBonemealAgeIncrease(world: Level): Int = 1
override fun canSurvive(state: BlockState, world: LevelReader, pos: BlockPos): Boolean {
return when (state.getValue(HALF)) {
@@ -198,9 +222,9 @@ class HeartyGrainsBlock(settings: Properties) : CropBlock(settings), SimpleWater
// Check if bottom part is submerged in water
val fluidState = world.getFluidState(pos)
if (fluidState.`is`(FluidTags.WATER) && fluidState.isSource)
return floor.`is`(CobblemonBlockTags.HEARTY_GRAINS_WATER_PLANTABLE)
return floor.`is`(CobblemonBlockTags.HEARTY_GRAINS_WATER_PLANTABLE) && canGrowInto(world, pos.above())
floor.`is`(CobblemonBlockTags.HEARTY_GRAINS_LAND_PLANTABLE)
floor.`is`(CobblemonBlockTags.HEARTY_GRAINS_LAND_PLANTABLE) && canGrowInto(world, pos.above())
}
DoubleBlockHalf.UPPER -> {
getLowerHalf(world, pos, state) != null
@@ -224,8 +248,4 @@ class HeartyGrainsBlock(settings: Properties) : CropBlock(settings), SimpleWater
private fun isLower(state: BlockState): Boolean =
state.`is`(CobblemonBlocks.HEARTY_GRAINS) && state.getValue(HALF) == DoubleBlockHalf.LOWER
override fun getSoundType(state: BlockState): SoundType? {
return if (state.getValue(WATERLOGGED)) CobblemonSounds.HEARTY_GRAINS_WATER_SOUNDS else CobblemonSounds.HEARTY_GRAINS_SOUNDS
}
}
@@ -0,0 +1,25 @@
/*
* 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.block
import com.cobblemon.mod.common.block.entity.LureCakeBlockEntity
import com.mojang.serialization.MapCodec
import net.minecraft.core.BlockPos
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.state.BlockState
class LureCakeBlock(settings: Properties): CakeBlock(settings) {
override fun codec(): MapCodec<LureCakeBlock> {
return simpleCodec(::LureCakeBlock)
}
override fun newBlockEntity(pos: BlockPos, state: BlockState): BlockEntity {
return LureCakeBlockEntity(pos, state)
}
}
@@ -0,0 +1,134 @@
/*
* 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.block
import com.cobblemon.mod.common.CobblemonBlocks
import com.cobblemon.mod.common.block.CandlePokeCakeBlock.Companion.CANDLE_COLOR
import com.cobblemon.mod.common.block.entity.PokeCakeBlockEntity
import com.mojang.serialization.MapCodec
import net.minecraft.core.BlockPos
import net.minecraft.sounds.SoundEvents
import net.minecraft.sounds.SoundSource
import net.minecraft.stats.Stats
import net.minecraft.tags.ItemTags
import net.minecraft.world.InteractionHand
import net.minecraft.world.InteractionHand.MAIN_HAND
import net.minecraft.world.InteractionResult
import net.minecraft.world.ItemInteractionResult
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.BlockItem
import net.minecraft.world.item.ItemStack
import net.minecraft.world.level.Level
import net.minecraft.world.level.LevelAccessor
import net.minecraft.world.level.block.BaseEntityBlock
import net.minecraft.world.level.block.CandleBlock
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.gameevent.GameEvent
import net.minecraft.world.phys.BlockHitResult
class PokeCakeBlock(settings: Properties): CakeBlock(settings) {
override fun codec(): MapCodec<out BaseEntityBlock?> {
return simpleCodec(::PokeCakeBlock)
}
override fun newBlockEntity(pos: BlockPos, state: BlockState): BlockEntity? {
return PokeCakeBlockEntity(pos, state)
}
override fun setPlacedBy(
level: Level,
pos: BlockPos,
state: BlockState,
placer: LivingEntity?,
stack: ItemStack
) {
super.setPlacedBy(level, pos, state, placer, stack)
val blockEntity = level.getBlockEntity(pos) as? PokeCakeBlockEntity
blockEntity?.initializeFromItemStack(stack)
}
override fun useWithoutItem(
state: BlockState,
level: Level,
pos: BlockPos,
player: Player,
hitResult: BlockHitResult
): InteractionResult {
if (level.isClientSide) {
if (eat(level, pos, player).consumesAction()) {
return InteractionResult.SUCCESS
}
if (player.getItemInHand(MAIN_HAND).isEmpty) {
return InteractionResult.CONSUME
}
}
return eat(level, pos, player)
}
override fun useItemOn(
stack: ItemStack,
state: BlockState,
level: Level,
pos: BlockPos,
player: Player,
hand: InteractionHand,
hitResult: BlockHitResult
): ItemInteractionResult {
val item = stack.item
val block = state.block as? PokeCakeBlock
if (stack.`is`(ItemTags.CANDLES) && block?.getBites(level, pos) == 0) {
val candleBlock = byItem(item)
if (candleBlock is CandleBlock) {
stack.consume(1, player)
level.playSound(null, pos, SoundEvents.CAKE_ADD_CANDLE, SoundSource.BLOCKS, 1.0f, 1.0f)
val oldCookingComponent = block.getFoodColourComponent(level, pos)
val newBlockState = CobblemonBlocks.CANDLE_POKE_CAKE.defaultBlockState()
.setValue(CANDLE_COLOR, (item as BlockItem).block.defaultMapColor().id)
level.setBlockAndUpdate(pos, newBlockState)
level.gameEvent(player, GameEvent.BLOCK_CHANGE, pos)
oldCookingComponent?.let {
(newBlockState.block as CakeBlock).setFoodColourComponent(level, pos, it)
}
player.awardStat(Stats.ITEM_USED[item])
return ItemInteractionResult.SUCCESS
}
}
return ItemInteractionResult.PASS_TO_DEFAULT_BLOCK_INTERACTION
}
fun eat(level: LevelAccessor, pos: BlockPos, player: Player): InteractionResult {
if (!player.canEat(false)) {
return InteractionResult.PASS
}
player.getFoodData().eat(2, 0.1F)
level.gameEvent(player, GameEvent.EAT, pos)
var bites = getBites(level, pos)
if (bites < 6) {
setBites(level, pos, bites + 1)
} else {
level.removeBlock(pos, false)
level.gameEvent(player, GameEvent.BLOCK_DESTROY, pos)
}
return InteractionResult.SUCCESS
}
}
@@ -1,327 +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.block
import com.cobblemon.mod.common.CobblemonBlocks
import com.cobblemon.mod.common.block.entity.PokeSnackBlockEntity
import com.cobblemon.mod.common.util.toEquipmentSlot
import com.mojang.serialization.MapCodec
import com.mojang.serialization.codecs.PrimitiveCodec
import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.core.BlockPos
import net.minecraft.core.particles.BlockParticleOption
import net.minecraft.core.particles.ParticleTypes
import net.minecraft.sounds.SoundEvents
import net.minecraft.sounds.SoundSource
import net.minecraft.stats.Stats
import net.minecraft.tags.ItemTags
import net.minecraft.util.RandomSource
import net.minecraft.world.InteractionHand
import net.minecraft.world.InteractionResult
import net.minecraft.world.ItemInteractionResult
import net.minecraft.world.entity.EquipmentSlot
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.item.ItemEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.entity.projectile.Projectile
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Items
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.Level
import net.minecraft.world.level.LevelAccessor
import net.minecraft.world.level.LevelReader
import net.minecraft.world.level.block.*
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.StateDefinition
import net.minecraft.world.level.block.state.properties.BlockStateProperties
import net.minecraft.world.level.block.state.properties.IntegerProperty
import net.minecraft.world.level.gameevent.GameEvent
import net.minecraft.world.level.material.MapColor
import net.minecraft.world.phys.BlockHitResult
import net.minecraft.world.phys.Vec3
import net.minecraft.world.phys.shapes.CollisionContext
import net.minecraft.world.phys.shapes.Shapes
import net.minecraft.world.phys.shapes.VoxelShape
class PokeSnackBlock(settings: Properties, val isLure: Boolean): BaseEntityBlock(settings) {
companion object {
val MAX_BITES = 8
val CAKE_HEIGHT = 0.4375
val CANDLE_PARTICLE_POSITION = Vec3(0.5, CAKE_HEIGHT + 0.5, 0.5)
val BITES: IntegerProperty = IntegerProperty.create("bites", 0, MAX_BITES)
val CANDLE: IntegerProperty = IntegerProperty.create("candle", 0, MapColor.MATERIAL_COLORS.size - 1)
val idToCandleBlock: Map<Int, Block> = mapOf(
MapColor.WOOL.id to Blocks.WHITE_CANDLE,
MapColor.COLOR_ORANGE.id to Blocks.ORANGE_CANDLE,
MapColor.COLOR_MAGENTA.id to Blocks.MAGENTA_CANDLE,
MapColor.COLOR_LIGHT_BLUE.id to Blocks.LIGHT_BLUE_CANDLE,
MapColor.COLOR_YELLOW.id to Blocks.YELLOW_CANDLE,
MapColor.COLOR_LIGHT_GREEN.id to Blocks.LIME_CANDLE,
MapColor.COLOR_PINK.id to Blocks.PINK_CANDLE,
MapColor.COLOR_GRAY.id to Blocks.GRAY_CANDLE,
MapColor.COLOR_LIGHT_GRAY.id to Blocks.LIGHT_GRAY_CANDLE,
MapColor.COLOR_CYAN.id to Blocks.CYAN_CANDLE,
MapColor.COLOR_PURPLE.id to Blocks.PURPLE_CANDLE,
MapColor.COLOR_BLUE.id to Blocks.BLUE_CANDLE,
MapColor.COLOR_BROWN.id to Blocks.BROWN_CANDLE,
MapColor.COLOR_GREEN.id to Blocks.GREEN_CANDLE,
MapColor.COLOR_RED.id to Blocks.RED_CANDLE,
MapColor.COLOR_BLACK.id to Blocks.BLACK_CANDLE
)
fun getCandleById(id: Int): Block = idToCandleBlock[id] ?: Blocks.CANDLE
fun getIdByCandle(candle: CandleBlock): Int = (idToCandleBlock.entries.associate { (id, block) -> block to id })[candle] ?: MapColor.SAND.id
private val CANDLE_SHAPE = Shapes.box(0.4375, CAKE_HEIGHT, 0.4375, 0.5625, 0.8125, 0.5625)
private val SHAPES = listOf(
Shapes.box(0.0625, 0.0, 0.0625, 0.9375, CAKE_HEIGHT, 0.9375),
Shapes.or(
Shapes.box(0.3125, 0.0, 0.0625, 0.9375, CAKE_HEIGHT, 0.9375),
Shapes.box(0.0625, 0.0, 0.3125, 0.9375, CAKE_HEIGHT, 0.9375)
),
Shapes.or(
Shapes.box(0.625, 0.0, 0.0625, 0.9375, CAKE_HEIGHT, 0.3125),
Shapes.box(0.0625, 0.0, 0.3125, 0.9375, CAKE_HEIGHT, 0.9375)
),
Shapes.box(0.0625, 0.0, 0.3125, 0.9375, CAKE_HEIGHT, 0.9375),
Shapes.or(
Shapes.box(0.6875, 0.0, 0.625, 0.9375, CAKE_HEIGHT, 0.9375),
Shapes.box(0.0625, 0.0, 0.3125, 0.6875, CAKE_HEIGHT, 0.9375)
),
Shapes.box(0.0625, 0.0, 0.3125, 0.6875, CAKE_HEIGHT, 0.9375),
Shapes.or(
Shapes.box(0.0625, 0.0, 0.6875, 0.375, CAKE_HEIGHT, 0.9375),
Shapes.box(0.0625, 0.0, 0.3125, 0.6875, CAKE_HEIGHT, 0.6875)
),
Shapes.box(0.0625, 0.0, 0.3125, 0.6875, CAKE_HEIGHT, 0.6875),
Shapes.box(0.3125, 0.0, 0.3125, 0.6875, CAKE_HEIGHT, 0.6875)
)
}
init {
registerDefaultState(stateDefinition.any()
.setValue(BITES, 0)
.setValue(CANDLE, 0)
.setValue(BlockStateProperties.LIT, false)
)
}
override fun codec(): MapCodec<PokeSnackBlock> = RecordCodecBuilder.mapCodec { it.group(
propertiesCodec(),
PrimitiveCodec.BOOL.fieldOf("isLure").forGetter { it.isLure }
).apply(it, ::PokeSnackBlock) }
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block?, BlockState?>) {
builder.add(BITES, CANDLE, BlockStateProperties.LIT)
}
override fun newBlockEntity(pos: BlockPos, state: BlockState): BlockEntity = PokeSnackBlockEntity(pos, state)
override fun getShape(state: BlockState, level: BlockGetter, pos: BlockPos, context: CollisionContext): VoxelShape? {
if (hasCandle(state)) {
return Shapes.or(SHAPES[state.getValue(BITES)], CANDLE_SHAPE)
}
return SHAPES[state.getValue(BITES)]
}
override fun getRenderShape(blockState: BlockState?) = RenderShape.MODEL
override fun setPlacedBy(level: Level, pos: BlockPos, state: BlockState, placer: LivingEntity?, stack: ItemStack) {
super.setPlacedBy(level, pos, state, placer, stack)
val blockEntity = level.getBlockEntity(pos) as? PokeSnackBlockEntity
blockEntity?.initializeFromItemStack(stack)
}
override fun getCloneItemStack(level: LevelReader, pos: BlockPos, state: BlockState): ItemStack {
val blockEntity = level.getBlockEntity(pos) as? PokeSnackBlockEntity ?: return ItemStack.EMPTY
return blockEntity.toItemStack()
}
override fun useWithoutItem(state: BlockState, level: Level, pos: BlockPos, player: Player, hitResult: BlockHitResult): InteractionResult {
if (!isLure) {
if (!candleHit(hitResult)) {
if (level.isClientSide) {
if (playerEat(level, pos, state, player).consumesAction()) {
return InteractionResult.SUCCESS
}
if (player.getItemInHand(InteractionHand.MAIN_HAND).isEmpty) {
return InteractionResult.CONSUME
}
}
return playerEat(level, pos, state, player)
} else if (canBeLit(state) && player.mainHandItem.isEmpty && player.isCrouching()) {
player.setItemSlot(EquipmentSlot.MAINHAND, ItemStack(getCandleById(state.getValue(CANDLE))))
level.playSound(null, pos, SoundEvents.ITEM_PICKUP, SoundSource.PLAYERS, 0.5F, level.random.nextFloat() * 0.4F + 0.8F)
level.setBlockAndUpdate(pos, state.setValue(CANDLE, 0))
level.gameEvent(player, GameEvent.BLOCK_CHANGE, pos)
}
}
return super.useWithoutItem(state, level, pos, player, hitResult)
}
override fun useItemOn(stack: ItemStack, state: BlockState, level: Level, pos: BlockPos, player: Player, hand: InteractionHand, hitResult: BlockHitResult): ItemInteractionResult {
if (!isLure) {
if (stack.`is`(ItemTags.CANDLES) && !hasCandle(state)) {
val item = stack.getItem()
val candle = byItem(item)
if (candle is CandleBlock) {
stack.consume(1, player)
level.playSound(null as Player?, pos, SoundEvents.CAKE_ADD_CANDLE, SoundSource.BLOCKS, 1.0F, 1.0F)
level.setBlockAndUpdate(pos, state.setValue(CANDLE, getIdByCandle(candle)))
level.gameEvent(player, GameEvent.BLOCK_CHANGE, pos)
player.awardStat(Stats.ITEM_USED.get(item))
return ItemInteractionResult.SUCCESS
}
}
if (candleHit(hitResult) && hasCandle(state)) {
val isFlintAndSteel = stack.`is`(Items.FLINT_AND_STEEL)
if (canBeLit(state) && (isFlintAndSteel || stack.`is`(Items.FIRE_CHARGE))) {
setCandleLit(level, state, pos, true)
level.playSound(
player,
pos,
if (isFlintAndSteel) SoundEvents.FLINTANDSTEEL_USE else SoundEvents.FIRECHARGE_USE,
SoundSource.BLOCKS,
1.0F,
level.random.nextFloat() * 0.4F + 0.8F
)
if (isFlintAndSteel) stack.hurtAndBreak(1, player, hand.toEquipmentSlot())
else stack.consume(1, player)
level.gameEvent(player, GameEvent.BLOCK_CHANGE, pos)
return ItemInteractionResult.sidedSuccess(level.isClientSide)
} else if (stack.isEmpty() && isCandleLit(state)) {
extinguishCandle(player, state, level, pos)
return ItemInteractionResult.sidedSuccess(level.isClientSide)
}
}
}
return super.useItemOn(stack, state, level, pos, player, hand, hitResult)
}
override fun animateTick(state: BlockState, level: Level, pos: BlockPos, random: RandomSource) {
if (isCandleLit(state)) {
AbstractCandleBlock.addParticlesAndSound(
level,
Vec3(
pos.x + CANDLE_PARTICLE_POSITION.x,
pos.y + CANDLE_PARTICLE_POSITION.y,
pos.z + CANDLE_PARTICLE_POSITION.z
),
random
)
}
}
override fun onProjectileHit(level: Level, state: BlockState, hit: BlockHitResult, projectile: Projectile) {
if (!level.isClientSide && projectile.isOnFire() && canBeLit(state)) {
setCandleLit(level, state, hit.getBlockPos(), true)
}
}
override fun playerWillDestroy(level: Level, blockPos: BlockPos, blockState: BlockState, player: Player): BlockState {
dropCandle(level, blockPos, blockState, player)
return super.playerWillDestroy(level, blockPos, blockState, player)
}
private fun dropCandle(level: Level, blockPos: BlockPos, blockState: BlockState, player: Player?) {
if (hasCandle(blockState) && !level.isClientSide && (!(player?.isCreative ?: false))) {
val itemEntity = ItemEntity(
level,
blockPos.x + 0.5,
blockPos.y + CAKE_HEIGHT,
blockPos.z + 0.5,
ItemStack(getCandleById(blockState.getValue(CANDLE)))
)
itemEntity.setDefaultPickUpDelay()
level.addFreshEntity(itemEntity)
}
}
private fun playerEat(level: Level, pos: BlockPos, state: BlockState, player: Player): InteractionResult {
if (!player.canEat(false)) {
return InteractionResult.PASS
} else {
player.awardStat(Stats.EAT_CAKE_SLICE)
player.getFoodData().eat(2, 0.1F)
level.gameEvent(player, GameEvent.EAT, pos)
eat(level, pos, state, player)
return InteractionResult.SUCCESS
}
}
fun eat(level: Level, pos: BlockPos, state: BlockState, player: Player?) {
val bites = state.getValue(BITES) as Int
if (bites < MAX_BITES) {
level.setBlock(pos, state.setValue(BITES, bites + 1) as BlockState, UPDATE_ALL)
} else {
dropCandle(level, pos, state, player)
level.removeBlock(pos, false)
level.removeBlockEntity(pos)
}
level.playSound(null, player?.blockPosition() ?: pos, SoundEvents.GENERIC_EAT, if (player != null) SoundSource.PLAYERS else SoundSource.NEUTRAL)
spawnEatParticles(level, pos)
player?.let {
level.gameEvent(it, GameEvent.BLOCK_DESTROY, pos)
}
}
private fun hasCandle(state: BlockState) = state.getValue(CANDLE) > 0
private fun isCandleLit(state: BlockState) = state.getValue(BlockStateProperties.LIT)
private fun canBeLit(state: BlockState) = hasCandle(state) && !isCandleLit(state)
private fun setCandleLit(level: LevelAccessor, state: BlockState, pos: BlockPos, lit: Boolean) {
level.setBlock(pos, state.setValue(BlockStateProperties.LIT, lit) as BlockState, UPDATE_ALL_IMMEDIATE)
}
private fun extinguishCandle(player: Player?, state: BlockState, level: LevelAccessor, pos: BlockPos) {
setCandleLit(level, state, pos, false)
level.addParticle(
ParticleTypes.SMOKE,
pos.getX().toDouble() + CANDLE_PARTICLE_POSITION.x(),
pos.getY().toDouble() + CANDLE_PARTICLE_POSITION.y(),
pos.getZ().toDouble() + CANDLE_PARTICLE_POSITION.z(),
0.0,
0.1,
0.0
)
level.playSound(null as Player?, pos, SoundEvents.CANDLE_EXTINGUISH, SoundSource.BLOCKS, 1.0F, 1.0F)
level.gameEvent(player, GameEvent.BLOCK_CHANGE, pos)
}
private fun candleHit(hit: BlockHitResult): Boolean {
return hit.getLocation().y - hit.getBlockPos().getY().toDouble() > CAKE_HEIGHT
}
fun spawnEatParticles(level: Level, pos: BlockPos) {
val random = level.random
repeat(5) {
level.addParticle(
BlockParticleOption(ParticleTypes.BLOCK, CobblemonBlocks.POKE_SNACK.defaultBlockState()),
pos.x + random.nextDouble(),
pos.y + random.nextDouble() * CAKE_HEIGHT,
pos.z + random.nextDouble(),
0.0,
0.0,
0.0
)
}
}
}
@@ -11,12 +11,11 @@ package com.cobblemon.mod.common.block.campfirepot
import com.cobblemon.mod.common.CobblemonBlockEntities
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.block.entity.CampfireBlockEntity
import com.cobblemon.mod.common.block.entity.CampfireBlockEntity.Companion.PREVIEW_ITEM_SLOT
import com.cobblemon.mod.common.item.CampfirePotItem
import com.cobblemon.mod.common.util.playSoundServer
import com.cobblemon.mod.common.util.toVec3d
import com.mojang.serialization.Codec
import com.mojang.serialization.MapCodec
import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.server.level.ServerLevel
@@ -56,16 +55,12 @@ import org.jetbrains.annotations.Nullable
import net.minecraft.world.level.block.CampfireBlock as MCCampfireBlock
@Suppress("OVERRIDE_DEPRECATION")
class CampfireBlock(settings: Properties, val isSoul: Boolean) : BaseEntityBlock(settings), SimpleWaterloggedBlock {
class CampfireBlock(settings: Properties) : BaseEntityBlock(settings), SimpleWaterloggedBlock {
companion object {
val CODEC: MapCodec<CampfireBlock> = RecordCodecBuilder.mapCodec {
it.group(
propertiesCodec(),
Codec.BOOL.fieldOf("isSoul").forGetter(CampfireBlock::isSoul)
).apply(it, ::CampfireBlock)
}
val CODEC = simpleCodec(::CampfireBlock)
val ITEM_DIRECTION = DirectionProperty.create("item_facing")
val LIT = BlockStateProperties.LIT
var SOUL = BooleanProperty.create("soul")
val POWERED = BlockStateProperties.POWERED
private val campfireAABB = Shapes.box(0.0, 0.0, 0.0, 1.0, 0.4375, 1.0)
@@ -83,6 +78,7 @@ class CampfireBlock(settings: Properties, val isSoul: Boolean) : BaseEntityBlock
registerDefaultState(stateDefinition.any()
.setValue(FACING, Direction.NORTH)
.setValue(LIT, true)
.setValue(SOUL, false)
.setValue(ITEM_DIRECTION, Direction.NORTH)
.setValue(POWERED, false))
}
@@ -186,9 +182,11 @@ class CampfireBlock(settings: Properties, val isSoul: Boolean) : BaseEntityBlock
blockEntity.setPotItem(ItemStack.EMPTY)
level.playSoundServer(blockPos.bottomCenter, CobblemonSounds.CAMPFIRE_POT_RETRIEVE)
blockEntity.setItem(PREVIEW_ITEM_SLOT, ItemStack.EMPTY)
Containers.dropContents(level, blockPos, blockEntity)
val facing = blockState.getValue(FACING)
val isSoul = blockState.getValue(SOUL)
blockEntity.setRemoved()
val newBlockState = if (isSoul) Blocks.SOUL_CAMPFIRE.defaultBlockState().setValue(FACING, facing)
@@ -203,7 +201,7 @@ class CampfireBlock(settings: Properties, val isSoul: Boolean) : BaseEntityBlock
}
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
builder.add(FACING, ITEM_DIRECTION, LIT, POWERED)
builder.add(FACING, ITEM_DIRECTION, LIT, SOUL, POWERED)
}
override fun updateShape(
@@ -273,7 +271,7 @@ class CampfireBlock(settings: Properties, val isSoul: Boolean) : BaseEntityBlock
}
override fun getCloneItemStack(level: LevelReader, pos: BlockPos, state: BlockState): ItemStack {
return if (isSoul) ItemStack(Blocks.SOUL_CAMPFIRE) else ItemStack(Blocks.CAMPFIRE)
return if (state.getValue(SOUL)) ItemStack(Blocks.SOUL_CAMPFIRE) else ItemStack(Blocks.CAMPFIRE)
}
override fun neighborChanged(
@@ -36,7 +36,6 @@ import net.minecraft.world.item.crafting.RecipeHolder
import net.minecraft.world.item.crafting.RecipeManager
import net.minecraft.world.item.crafting.RecipeType
import net.minecraft.world.level.Level
import java.util.*
class CookingPotMenu : RecipeBookMenu<CraftingInput, CookingPotRecipeBase>, ContainerListener {
private val player: Player
@@ -47,8 +46,6 @@ class CookingPotMenu : RecipeBookMenu<CraftingInput, CookingPotRecipeBase>, Cont
val containerData: ContainerData
private val recipeType: RecipeType<CookingPotRecipe> = CobblemonRecipeTypes.COOKING_POT_COOKING
private val quickCheck = RecipeManager.createCheck(CobblemonRecipeTypes.COOKING_POT_COOKING)
var currentActiveRecipe: RecipeHolder<CookingPotRecipeBase>? = null
var previewItem = ItemStack.EMPTY
constructor(containerId: Int, playerInventory: Inventory) : super(CobblemonMenuType.COOKING_POT, containerId) {
this.playerInventory = playerInventory
@@ -62,7 +59,6 @@ class CookingPotMenu : RecipeBookMenu<CraftingInput, CookingPotRecipeBase>, Cont
this.player = playerInventory.player
this.level = playerInventory.player.level()
initializeSlots(playerInventory)
this.addSlotListener(this)
}
constructor(containerId: Int, playerInventory: Inventory, container: CraftingContainer, containerData: ContainerData) : super(
@@ -95,6 +91,9 @@ class CookingPotMenu : RecipeBookMenu<CraftingInput, CookingPotRecipeBase>, Cont
addSlot(SeasoningSlot(this.container, slotIndex, 110 + index * 18, 18))
}
addSlot(CookingPotPreviewSlot(this.container,
CampfireBlockEntity.Companion.PREVIEW_ITEM_SLOT, resultSlotX, resultSlotY))
for ((index, _) in CampfireBlockEntity.Companion.PLAYER_INVENTORY_SLOTS.withIndex()) {
val i = index / CampfireBlockEntity.Companion.PLAYER_INVENTORY_WIDTH
val j = index % CampfireBlockEntity.Companion.PLAYER_INVENTORY_WIDTH
@@ -149,40 +148,14 @@ class CookingPotMenu : RecipeBookMenu<CraftingInput, CookingPotRecipeBase>, Cont
}
override fun recipeMatches(recipe: RecipeHolder<CookingPotRecipeBase>): Boolean {
val craftInput = CraftingInput.of(3,3, container.items.subList(1,10))
val recipeValue = recipe.value()
return if (recipeValue is CookingPotRecipeBase) {
recipeValue.matches(craftInput, level)
recipeValue.matches(container.asCraftInput(), level)
} else {
false
}
}
private fun recalculateRecipe() {
val craftInput = CraftingInput.of(3,3, container.items.subList(1,10))
fun <T : CookingPotRecipeBase> fetchRecipe(
recipeType: RecipeType<T>
): Optional<RecipeHolder<CookingPotRecipeBase>> {
val optional = level.recipeManager.getRecipeFor(recipeType, craftInput, level)
@Suppress("UNCHECKED_CAST")
return optional.map { it as RecipeHolder<CookingPotRecipeBase> }
}
val recipe = fetchRecipe(CobblemonRecipeTypes.COOKING_POT_COOKING)
.orElseGet { fetchRecipe(CobblemonRecipeTypes.COOKING_POT_SHAPELESS).orElse(null) }
currentActiveRecipe = recipe
if (recipe != null) {
previewItem = recipe.value.assemble(craftInput, level.registryAccess())
recipe.value.applySeasoning(
previewItem,
container.items.subList(CampfireBlockEntity.SEASONING_SLOTS.first,
CampfireBlockEntity.SEASONING_SLOTS.last + 1)
.filterNotNull()
.filter { !it.isEmpty })
} else previewItem = ItemStack.EMPTY
}
override fun getResultSlotIndex(): Int {
return CampfireBlockEntity.Companion.RESULT_SLOT
}
@@ -214,7 +187,7 @@ class CookingPotMenu : RecipeBookMenu<CraftingInput, CookingPotRecipeBase>, Cont
}
override fun shouldMoveToInventory(slotIndex: Int): Boolean {
return !CampfireBlockEntity.SEASONING_SLOTS.contains(slotIndex)
return slotIndex != CampfireBlockEntity.Companion.PREVIEW_ITEM_SLOT && !CampfireBlockEntity.SEASONING_SLOTS.contains(slotIndex)
}
override fun quickMoveStack(
@@ -236,8 +209,8 @@ class CookingPotMenu : RecipeBookMenu<CraftingInput, CookingPotRecipeBase>, Cont
slot.onQuickCraft(slotItemStack, itemStack);
} else if (index in CampfireBlockEntity.Companion.PLAYER_INVENTORY_SLOTS || index in CampfireBlockEntity.Companion.PLAYER_HOTBAR_SLOTS) {
if (Seasonings.isSeasoning(slotItemStack)) {
if (!this.moveItemStackTo(slotItemStack, CampfireBlockEntity.Companion.CRAFTING_GRID_SLOTS.first, CampfireBlockEntity.Companion.CRAFTING_GRID_SLOTS.last + 1, false) &&
!this.moveItemStackTo(slotItemStack, CampfireBlockEntity.Companion.SEASONING_SLOTS.first, CampfireBlockEntity.Companion.SEASONING_SLOTS.last + 1, false)
if (!this.moveItemStackTo(slotItemStack, CampfireBlockEntity.Companion.SEASONING_SLOTS.first, CampfireBlockEntity.Companion.SEASONING_SLOTS.last + 1, false) &&
!this.moveItemStackTo(slotItemStack, CampfireBlockEntity.Companion.CRAFTING_GRID_SLOTS.first, CampfireBlockEntity.Companion.CRAFTING_GRID_SLOTS.last + 1, false)
) {
return ItemStack.EMPTY
}
@@ -271,7 +244,6 @@ class CookingPotMenu : RecipeBookMenu<CraftingInput, CookingPotRecipeBase>, Cont
}
override fun slotChanged(containerToSend: AbstractContainerMenu, dataSlotIndex: Int, stack: ItemStack) {
if (CampfireBlockEntity.CRAFTING_GRID_SLOTS.contains(dataSlotIndex) || CampfireBlockEntity.SEASONING_SLOTS.contains(dataSlotIndex)) recalculateRecipe()
broadcastChanges()
}
@@ -0,0 +1,31 @@
/*
* 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.block.campfirepot
import com.cobblemon.mod.common.block.entity.CampfireBlockEntity.Companion.RESULT_SLOT
import net.minecraft.world.entity.player.Player
import net.minecraft.world.inventory.CraftingContainer
import net.minecraft.world.inventory.Slot
import net.minecraft.world.item.ItemStack
class CookingPotPreviewSlot(
container: CraftingContainer,
index: Int,
x: Int,
y: Int
) : Slot(container, index, x, y) {
override fun isActive(): Boolean {
return container.getItem(RESULT_SLOT).isEmpty
}
override fun isFake(): Boolean = true
override fun mayPlace(stack: ItemStack): Boolean = false
override fun mayPickup(player: Player): Boolean = false
override fun isHighlightable() = false
}
@@ -31,11 +31,12 @@ class CookingPotResultSlot(
val menu = player.containerMenu
if (menu is CookingPotMenu) {
menu.broadcastChanges()
player.playNotifySound(CobblemonSounds.CAMPFIRE_POT_TAKE_ITEM, SoundSource.MASTER, 1.0f, 1.0f)
player.playNotifySound(CobblemonSounds.CAMPFIRE_POT_USE, SoundSource.MASTER, 1.0f, 1.0f)
}
super.onTake(player, stack)
}
override fun mayPlace(stack: ItemStack): Boolean = false
override fun isActive(): Boolean = hasItem()
}
@@ -8,127 +8,130 @@
package com.cobblemon.mod.common.block.entity
import com.cobblemon.mod.common.CobblemonBlockEntities
import com.cobblemon.mod.common.CobblemonItemComponents
import com.cobblemon.mod.common.api.cooking.getColourMixFromFlavours
import com.cobblemon.mod.common.api.fishing.SpawnBait
import com.cobblemon.mod.common.api.fishing.SpawnBait.Effect
import com.cobblemon.mod.common.api.fishing.SpawnBaitEffects
import com.cobblemon.mod.common.block.PokeSnackBlock
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.item.components.BaitEffectsComponent
import com.cobblemon.mod.common.item.components.FlavourComponent
import com.cobblemon.mod.common.item.components.IngredientComponent
import com.cobblemon.mod.common.item.components.FoodColourComponent
import com.cobblemon.mod.common.util.DataKeys
import net.minecraft.core.BlockPos
import net.minecraft.core.HolderLookup
import net.minecraft.nbt.*
import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.NbtOps
import net.minecraft.network.protocol.Packet
import net.minecraft.network.protocol.game.ClientGamePacketListener
import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket
import net.minecraft.world.item.ItemStack
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.entity.BlockEntityType
import net.minecraft.world.level.block.state.BlockState
import kotlin.collections.orEmpty
open class PokeSnackBlockEntity(pos: BlockPos, state: BlockState) : TintBlockEntity(CobblemonBlockEntities.POKE_SNACK, pos, state) {
open class CakeBlockEntity(
type: BlockEntityType<*>,
pos: BlockPos,
state: BlockState
) : BlockEntity(type, pos, state) {
open val maxBites: Int
get() = 6
var flavourComponent: FlavourComponent? = null
var baitEffectsComponent: BaitEffectsComponent? = null
var ingredientComponent: IngredientComponent? = null
var foodColourComponent: FoodColourComponent? = null
var bites: Int = 0
fun initializeFromItemStack(itemStack: ItemStack) {
flavourComponent = itemStack.get(CobblemonItemComponents.FLAVOUR)
ingredientComponent = itemStack.get(CobblemonItemComponents.INGREDIENT)
if (isLure()) baitEffectsComponent = itemStack.get(CobblemonItemComponents.BAIT_EFFECTS)
flavourComponent?.let {
getColourMixFromFlavours(it.getDominantFlavours())?.let {
setTint(it)
}
}
baitEffectsComponent = itemStack.get(CobblemonItemComponents.BAIT_EFFECTS)
foodColourComponent = itemStack.get(CobblemonItemComponents.FOOD_COLOUR)
}
fun isLure(): Boolean {
val block = blockState.block
if (block is PokeSnackBlock) {
return block.isLure
}
return false
}
fun toItemStack(): ItemStack {
val stack = ItemStack(this.blockState.block)
if (isLure() && baitEffectsComponent != null) {
stack.set(CobblemonItemComponents.BAIT_EFFECTS, baitEffectsComponent)
}
if (flavourComponent != null) {
stack.set(CobblemonItemComponents.FLAVOUR, flavourComponent)
}
return stack
}
/**
* Combine all the [SpawnBait.Effect] values from the [baitEffectsComponent] data.
*/
fun getBaitEffects(): List<Effect> {
return baitEffectsComponent?.effects?.mapNotNull(SpawnBaitEffects::getFromIdentifier)?.flatMap { it.effects }.orEmpty()
}
override fun saveAdditional(tag: CompoundTag, registries: HolderLookup.Provider) {
override fun saveAdditional(
tag: CompoundTag,
registries: HolderLookup.Provider
) {
super.saveAdditional(tag, registries)
tag.putInt(DataKeys.CAKE_BITES, bites)
flavourComponent?.let { component ->
CobblemonItemComponents.FLAVOUR.codec()!!
.encodeStart(NbtOps.INSTANCE, component)
.result()
.ifPresent { encodedTag ->
tag.put(DataKeys.FLAVOUR, encodedTag)
tag.put(DataKeys.CAKE_FLAVOUR, encodedTag)
}
}
baitEffectsComponent?.let { component ->
CobblemonItemComponents.BAIT_EFFECTS.codec()!!
.encodeStart(NbtOps.INSTANCE, component)
.result()
.ifPresent { encodedTag ->
tag.put(DataKeys.BAIT_EFFECTS, encodedTag)
tag.put(DataKeys.CAKE_BAIT_EFFECTS, encodedTag)
}
}
ingredientComponent?.let { component ->
CobblemonItemComponents.INGREDIENT.codec()!!
foodColourComponent?.let { component ->
CobblemonItemComponents.FOOD_COLOUR.codec()!!
.encodeStart(NbtOps.INSTANCE, component)
.result()
.ifPresent { encodedTag ->
tag.put(DataKeys.INGREDIENTS, encodedTag)
tag.put(DataKeys.CAKE_FOOD_COLOUR, encodedTag)
}
}
}
override fun loadAdditional(tag: CompoundTag, registries: HolderLookup.Provider) {
override fun loadAdditional(
tag: CompoundTag,
registries: HolderLookup.Provider
) {
super.loadAdditional(tag, registries)
if (tag.contains(DataKeys.FLAVOUR)) {
bites = tag.getInt(DataKeys.CAKE_BITES)
if (tag.contains(DataKeys.CAKE_FLAVOUR)) {
CobblemonItemComponents.FLAVOUR.codec()
?.parse(NbtOps.INSTANCE, tag.get(DataKeys.FLAVOUR))
?.parse(NbtOps.INSTANCE, tag.get(DataKeys.CAKE_FLAVOUR))
?.result()
?.ifPresent { component ->
flavourComponent = component
}
}
if (tag.contains(DataKeys.BAIT_EFFECTS)) {
if (tag.contains(DataKeys.CAKE_BAIT_EFFECTS)) {
CobblemonItemComponents.BAIT_EFFECTS.codec()
?.parse(NbtOps.INSTANCE, tag.get(DataKeys.BAIT_EFFECTS))
?.parse(NbtOps.INSTANCE, tag.get(DataKeys.CAKE_BAIT_EFFECTS))
?.result()
?.ifPresent { component ->
baitEffectsComponent = component
}
}
if (tag.contains(DataKeys.INGREDIENTS)) {
CobblemonItemComponents.INGREDIENT.codec()
?.parse(NbtOps.INSTANCE, tag.get(DataKeys.INGREDIENTS))
if (tag.contains(DataKeys.CAKE_FOOD_COLOUR)) {
CobblemonItemComponents.FOOD_COLOUR.codec()
?.parse(NbtOps.INSTANCE, tag.get(DataKeys.CAKE_FOOD_COLOUR))
?.result()
?.ifPresent { component ->
ingredientComponent = component
foodColourComponent = component
}
}
}
}
override fun getUpdatePacket(): Packet<ClientGamePacketListener?>? {
return ClientboundBlockEntityDataPacket.create(this)
}
override fun getUpdateTag(registries: HolderLookup.Provider): CompoundTag {
val tag = CompoundTag()
saveAdditional(tag, registries)
return tag
}
fun toItemStack(): ItemStack {
val stack = ItemStack(this.blockState.block)
if (stack.item == CobblemonItems.LURE_CAKE && baitEffectsComponent != null) {
stack.set(CobblemonItemComponents.BAIT_EFFECTS, baitEffectsComponent)
}
if (flavourComponent != null) {
stack.set(CobblemonItemComponents.FLAVOUR, flavourComponent)
}
if (foodColourComponent != null) {
stack.set(CobblemonItemComponents.FOOD_COLOUR, foodColourComponent)
}
return stack
}
}
@@ -12,7 +12,6 @@ import com.cobblemon.mod.common.CobblemonBlockEntities
import com.cobblemon.mod.common.CobblemonRecipeTypes
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.cooking.getColourMixFromSeasonings
import com.cobblemon.mod.common.block.campfirepot.CampfireBlock
import com.cobblemon.mod.common.item.components.PotComponent
import com.cobblemon.mod.common.block.campfirepot.CookingPotMenu
import com.cobblemon.mod.common.client.particle.BedrockParticleOptionsRepository
@@ -40,12 +39,11 @@ import net.minecraft.resources.ResourceLocation
import net.minecraft.util.FastColor
import net.minecraft.world.ContainerHelper
import net.minecraft.world.WorldlyContainer
import net.minecraft.world.entity.item.ItemEntity
import net.minecraft.world.entity.player.Inventory
import net.minecraft.world.entity.player.StackedContents
import net.minecraft.world.inventory.*
import net.minecraft.world.item.Item
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Items
import net.minecraft.world.item.crafting.CraftingInput
import net.minecraft.world.item.crafting.RecipeHolder
import net.minecraft.world.item.crafting.RecipeManager
@@ -53,7 +51,6 @@ import net.minecraft.world.item.crafting.RecipeType
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.entity.BaseContainerBlockEntity
import net.minecraft.world.level.block.entity.HopperBlockEntity
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.gameevent.GameEvent
import net.minecraft.world.phys.Vec3
@@ -70,10 +67,11 @@ class CampfireBlockEntity(pos: BlockPos, state: BlockState) : BaseContainerBlock
const val RESULT_SLOT = 0
val CRAFTING_GRID_SLOTS = 1..9
val SEASONING_SLOTS = 10..12
const val ITEMS_SIZE = 13
const val PREVIEW_ITEM_SLOT = 13
const val ITEMS_SIZE = 14
val PLAYER_INVENTORY_SLOTS = 13..39
val PLAYER_HOTBAR_SLOTS = 40..48
val PLAYER_INVENTORY_SLOTS = 14..40
val PLAYER_HOTBAR_SLOTS = 41..49
const val CRAFTING_GRID_WIDTH = 3
const val PLAYER_INVENTORY_WIDTH = 9
@@ -91,21 +89,15 @@ class CampfireBlockEntity(pos: BlockPos, state: BlockState) : BaseContainerBlock
if (!level.isClientSide) return
val isLit = campfireBlockEntity.dataAccess.get(COOKING_PROGRESS_INDEX) > 0
val isRunningSoundActive = BlockEntitySoundTracker.isActive(pos, campfireBlockEntity.runningSound.location)
val isAmbientSoundActive = BlockEntitySoundTracker.isActive(pos, campfireBlockEntity.ambientSound.location)
val containsItems = campfireBlockEntity.getSeasonings().isNotEmpty() || campfireBlockEntity.getIngredients().isNotEmpty()
val isSoundActive = BlockEntitySoundTracker.isActive(pos, campfireBlockEntity.runningSound.location)
if (containsItems) {
if (isLit) {
BlockEntitySoundTracker.stop(pos, campfireBlockEntity.ambientSound.location)
if (!isRunningSoundActive) BlockEntitySoundTracker.play(pos, CancellableSoundInstance(campfireBlockEntity.runningSound, pos, true, 1.0f, 1.0f))
} else {
BlockEntitySoundTracker.stop(pos, campfireBlockEntity.runningSound.location)
if (!isAmbientSoundActive) BlockEntitySoundTracker.play(pos, CancellableSoundInstance(campfireBlockEntity.ambientSound, pos, true, 1.0f, 1.0f))
}
} else {
if (isLit && !isSoundActive) {
BlockEntitySoundTracker.play(
pos,
CancellableSoundInstance(campfireBlockEntity.runningSound, pos, true, 0.8f, 1.0f)
)
} else if (!isLit && isSoundActive) {
BlockEntitySoundTracker.stop(pos, campfireBlockEntity.runningSound.location)
BlockEntitySoundTracker.stop(pos, campfireBlockEntity.ambientSound.location)
}
campfireBlockEntity.brothColor =
@@ -157,6 +149,7 @@ class CampfireBlockEntity(pos: BlockPos, state: BlockState) : BaseContainerBlock
if (optionalRecipe == null) {
campfireBlockEntity.cookingProgress = 0
campfireBlockEntity.setItem(PREVIEW_ITEM_SLOT, ItemStack.EMPTY)
return
}
@@ -167,6 +160,8 @@ class CampfireBlockEntity(pos: BlockPos, state: BlockState) : BaseContainerBlock
recipe.applySeasoning(cookedItem, campfireBlockEntity.getSeasonings())
campfireBlockEntity.setItem(PREVIEW_ITEM_SLOT, cookedItem)
if (campfireBlockEntity.isLidOpen) {
campfireBlockEntity.cookingProgress = 0
return
@@ -176,7 +171,7 @@ class CampfireBlockEntity(pos: BlockPos, state: BlockState) : BaseContainerBlock
if (!ItemStack.isSameItemSameComponents(
resultSlotItem,
cookedItem
) || resultSlotItem.count + cookedItem.count > resultSlotItem.maxStackSize
) || resultSlotItem.count >= resultSlotItem.maxStackSize
) {
campfireBlockEntity.cookingProgress = 0
return
@@ -196,11 +191,11 @@ class CampfireBlockEntity(pos: BlockPos, state: BlockState) : BaseContainerBlock
resultSlotItem.grow(cookedItem.count)
}
campfireBlockEntity.consumeCraftingIngredients(recipe, level, pos, state, campfireBlockEntity)
campfireBlockEntity.consumeCraftingIngredients(recipe)
level.playSoundServer(
position = pos.bottomCenter,
sound = CobblemonSounds.CAMPFIRE_POT_COOK,
sound = CobblemonSounds.CAMPFIRE_POT_CRAFT,
)
setChanged(level, pos, state);
@@ -209,8 +204,7 @@ class CampfireBlockEntity(pos: BlockPos, state: BlockState) : BaseContainerBlock
}
}
private val runningSound = CobblemonSounds.CAMPFIRE_POT_ACTIVE
private val ambientSound = CobblemonSounds.CAMPFIRE_POT_AMBIENT
private val runningSound = CobblemonSounds.CAMPFIRE_POT_COOK
private var cookingProgress: Int = 0
private var cookingTotalTime: Int = COOKING_TOTAL_TIME
private var isLidOpen: Boolean = true
@@ -276,17 +270,32 @@ class CampfireBlockEntity(pos: BlockPos, state: BlockState) : BaseContainerBlock
return particleStorm
}
fun consumeCraftingIngredients(recipe: CookingPotRecipeBase, level: Level, pos: BlockPos, state: BlockState, campfireBlockEntity: CampfireBlockEntity) {
val remainderItems = mutableMapOf<Item, Int>() //This is so we don't spawn multiple entities for buckets
fun consumeCraftingIngredients(recipe: CookingPotRecipeBase) {
fun consumeItem(slot: Int) {
val itemInSlot = getItem(slot)
if (!itemInSlot.isEmpty) {
if (itemInSlot.item.hasCraftingRemainingItem()) {
remainderItems[itemInSlot.item.craftingRemainingItem!!] = (remainderItems[itemInSlot.item.craftingRemainingItem!!] ?: 0) + 1
when (itemInSlot.item) {
Items.LAVA_BUCKET, Items.WATER_BUCKET, Items.MILK_BUCKET -> {
// Replace with empty bucket
setItem(slot, ItemStack(Items.BUCKET))
}
Items.HONEY_BOTTLE -> {
// TODO: Currently eats the empty bottles until the honey bottle stack is empty, replace with better system later.
itemInSlot.shrink(1)
if (itemInSlot.count <= 0) {
setItem(slot, ItemStack.EMPTY)
}
}
else -> {
// Decrease the stack size by 1
itemInSlot.shrink(1)
if (itemInSlot.count <= 0) {
setItem(slot, ItemStack.EMPTY) // Clear the slot if empty
}
}
}
itemInSlot.shrink(1)
if (itemInSlot.count <= 0) setItem(slot, ItemStack.EMPTY)
}
}
@@ -296,24 +305,6 @@ class CampfireBlockEntity(pos: BlockPos, state: BlockState) : BaseContainerBlock
for (i in SEASONING_SLOTS.first..SEASONING_SLOTS.last) {
if (recipe.seasoningProcessors.any { it.consumesItem(getItem(i)) }) consumeItem(i)
}
val direction = state.getValue(CampfireBlock.ITEM_DIRECTION)
val container = HopperBlockEntity.getContainerAt(level, pos.relative(direction))
for (remainder in remainderItems) {
var remainderItem = ItemStack(remainder.key, remainder.value)
if (container != null) {
remainderItem = HopperBlockEntity.addItem(campfireBlockEntity, container, remainderItem, direction.opposite)
}
if (!remainderItem.isEmpty) {
val spawnPos = Vec3.atCenterOf(pos).relative(direction, 0.7)
val itemEntity = ItemEntity(level, spawnPos.x, spawnPos.y, spawnPos.z, remainderItem)
itemEntity.setDeltaMovement(direction.stepX * 0.05, 0.0, direction.stepZ * 0.05)
level.addFreshEntity(itemEntity) //Wanted to use default dispenser behavior but its too much speed
}
}
}
fun getSeasonings(): List<ItemStack> =
@@ -438,7 +429,6 @@ class CampfireBlockEntity(pos: BlockPos, state: BlockState) : BaseContainerBlock
if (level?.isClientSide == true) {
BlockEntitySoundTracker.stop(blockPos, runningSound.location)
BlockEntitySoundTracker.stop(blockPos, ambientSound.location)
}
}
@@ -0,0 +1,18 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.block.entity
import com.cobblemon.mod.common.CobblemonBlockEntities
import net.minecraft.core.BlockPos
import net.minecraft.world.level.block.state.BlockState
class CandlePokeCakeBlockEntity(
pos: BlockPos,
state: BlockState
) : CakeBlockEntity(CobblemonBlockEntities.CANDLE_POKE_CAKE, pos, state)
@@ -0,0 +1,30 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.block.entity
import com.cobblemon.mod.common.CobblemonBlockEntities
import com.cobblemon.mod.common.api.fishing.SpawnBait
import com.cobblemon.mod.common.api.fishing.SpawnBaitEffects
import net.minecraft.core.BlockPos
import net.minecraft.world.level.block.state.BlockState
class LureCakeBlockEntity(
pos: BlockPos,
state: BlockState
) : CakeBlockEntity(CobblemonBlockEntities.LURE_CAKE, pos, state) {
override val maxBites: Int
get() = 20
/**
* Combine all the [SpawnBait.Effect] values from the [baitEffectsComponent] data.
*/
fun getBaitEffectsFromLureCake(): List<SpawnBait.Effect> {
return baitEffectsComponent?.effects?.mapNotNull(SpawnBaitEffects::getFromIdentifier)?.flatMap { it.effects }.orEmpty()
}
}
@@ -0,0 +1,18 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.block.entity
import com.cobblemon.mod.common.CobblemonBlockEntities
import net.minecraft.core.BlockPos
import net.minecraft.world.level.block.state.BlockState
class PokeCakeBlockEntity(
pos: BlockPos,
state: BlockState
) : CakeBlockEntity(CobblemonBlockEntities.POKE_CAKE, pos, state)
@@ -13,6 +13,4 @@ import net.minecraft.core.BlockPos
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.state.BlockState
class SweetIncenseBlockEntity(pos: BlockPos, state: BlockState) : BlockEntity(
null // CobblemonBlockEntities.INCENSE_SWEET // TODO after 1.7
, pos, state)
class SweetIncenseBlockEntity(pos: BlockPos, state: BlockState) : BlockEntity(CobblemonBlockEntities.INCENSE_SWEET, pos, state)
@@ -1,51 +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.block.entity
import net.minecraft.core.BlockPos
import net.minecraft.core.HolderLookup
import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.IntTag
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.entity.BlockEntityType
import net.minecraft.world.level.block.state.BlockState
open class TintBlockEntity(type: BlockEntityType<*>?, blockPos: BlockPos, blockState: BlockState) : BlockEntity(type, blockPos, blockState) {
companion object {
const val TINT = "tint"
}
var tint: Int? = null
override fun saveAdditional(tag: CompoundTag, registries: HolderLookup.Provider) {
super.saveAdditional(tag, registries)
tint?.let { tag.put(TINT, IntTag.valueOf(it)) }
}
override fun loadAdditional(tag: CompoundTag, registries: HolderLookup.Provider) {
super.loadAdditional(tag, registries)
if (tag.contains(TINT)) tint = tag.getInt(TINT)
}
override fun getUpdateTag(registryLookup: HolderLookup.Provider): CompoundTag {
return this.saveWithoutMetadata(registryLookup)
}
fun getTint() = tint ?: 0xFFFFFF
fun setTint(tintValue: Int) {
level?.let {
tint = tintValue
setChanged()
it.blockEntityChanged(blockPos)
it.sendBlockUpdated(blockPos, blockState, blockState, Block.UPDATE_ALL)
}
}
}
@@ -19,15 +19,7 @@ import com.cobblemon.mod.common.api.molang.ObjectValue
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.client.render.models.blockbench.ExcludedLabels
import com.cobblemon.mod.common.client.render.models.blockbench.PosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.animation.ActiveAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.animation.BimanualSwingAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.animation.BipedWalkAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.animation.PitchTiltAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.animation.PrimaryAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.animation.PunchAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.animation.QuadrupedWalkAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.animation.SingleBoneLookAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.animation.WingFlapIdleAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.animation.*
import com.cobblemon.mod.common.client.render.models.blockbench.pose.ModelPartTransformation
import com.cobblemon.mod.common.client.render.models.blockbench.wavefunction.WaveFunction
import com.cobblemon.mod.common.client.render.models.blockbench.wavefunction.sineFunction
@@ -125,22 +117,6 @@ object ClientMoLangFunctions {
ObjectValue(anim)
}
},
"pitch_tilt" to Function {
model -> Function { params ->
val bone = params.getStringOrNull(0) ?: "root"
val maxChangePerTick = params.getDoubleOrNull(1) ?: 1.5F
val minPitch = params.getDoubleOrNull(2) ?: -45F
val maxPitch = params.getDoubleOrNull(3) ?: 45F
ObjectValue(
PitchTiltAnimation(
bone = model.getPart(bone),
minPitch = minPitch.toFloat(),
maxPitch = maxPitch.toFloat(),
maxChangePerTick = maxChangePerTick.toFloat()
)
)
}
},
"look" to Function {
model -> Function { params ->
val boneName = params.getString(0)
@@ -20,20 +20,19 @@ import com.cobblemon.mod.common.api.scheduling.ClientTaskTracker
import com.cobblemon.mod.common.api.storage.player.client.ClientGeneralPlayerData
import com.cobblemon.mod.common.api.storage.player.client.ClientPokedexManager
import com.cobblemon.mod.common.api.tags.CobblemonItemTags
import com.cobblemon.mod.common.block.entity.TintBlockEntity
import com.cobblemon.mod.common.client.battle.ClientBattle
import com.cobblemon.mod.common.client.gui.PartyOverlay
import com.cobblemon.mod.common.client.gui.RideControlsOverlay
import com.cobblemon.mod.common.client.gui.battle.BattleOverlay
import com.cobblemon.mod.common.client.gui.cookingpot.CookingPotScreen
import com.cobblemon.mod.common.client.particle.BedrockParticleOptionsRepository
import com.cobblemon.mod.common.client.render.block.CandlePokeCakeBlockEntityRenderer
import com.cobblemon.mod.common.client.render.ClientPlayerIcon
import com.cobblemon.mod.common.client.render.DeferredRenderer
import com.cobblemon.mod.common.client.render.block.*
import com.cobblemon.mod.common.client.render.boat.CobblemonBoatRenderer
import com.cobblemon.mod.common.client.render.color.AprijuiceItemColorProvider
import com.cobblemon.mod.common.client.render.color.PokeBaitItemColorProvider
import com.cobblemon.mod.common.client.render.color.PokeSnackItemColorProvider
import com.cobblemon.mod.common.client.render.color.PonigiriItemColorProvider
import com.cobblemon.mod.common.client.render.color.SinisterTeaItemColorProvider
import com.cobblemon.mod.common.client.render.entity.PokeBobberEntityRenderer
@@ -60,8 +59,6 @@ import com.cobblemon.mod.common.platform.events.PlatformEvents
import com.cobblemon.mod.common.pokedex.scanner.PokedexUsageContext
import com.cobblemon.mod.common.util.isLookingAt
import net.minecraft.client.Minecraft
import net.minecraft.client.color.block.BlockColor
import net.minecraft.client.color.item.ItemColor
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.screens.MenuScreens
import net.minecraft.client.gui.screens.Screen
@@ -127,7 +124,7 @@ object CobblemonClient {
this.registerBlockEntityRenderers()
registerBlockRenderTypes()
registerColors()
//registerColors()
registerFlywheelRenderers()
this.registerEntityRenderers()
this.registerItemColors()
@@ -199,16 +196,16 @@ object CobblemonClient {
// .apply()
}
/*
fun registerColors() {
this.implementation.registerBlockColors(BlockColor { blockState, view, blockPos, tintIndex ->
blockPos?.let { pos ->
view?.getBlockEntity(pos)?.let { blockEntity ->
if (blockEntity is TintBlockEntity) return@BlockColor blockEntity.getTint()
}
}
return@BlockColor 0xFFFFFF
}, CobblemonBlocks.POKE_SNACK, CobblemonBlocks.POKE_CAKE)
this.implementation.registerBlockColors(BlockColorProvider { _, _, _, _ ->
return@BlockColorProvider 0xE0A33A
}, CobblemonBlocks.APRICORN_LEAVES)
this.implementation.registerItemColors(ItemColorProvider { _, _ ->
return@ItemColorProvider 0xE0A33A
}, CobblemonItems.APRICORN_LEAVES)
}
*/
private fun registerBlockRenderTypes() {
@@ -292,10 +289,10 @@ object CobblemonClient {
CobblemonBlocks.SACCHARINE_WALL_HANGING_SIGN,
CobblemonBlocks.SACCHARINE_SAPLING,
CobblemonBlocks.POTTED_SACCHARINE_SAPLING,
CobblemonBlocks.POKE_SNACK,
CobblemonBlocks.LURE_CAKE,
CobblemonBlocks.POKE_CAKE,
CobblemonBlocks.LECTERN,
CobblemonBlocks.CAMPFIRE,
CobblemonBlocks.SOUL_CAMPFIRE,
CobblemonBlocks.BLACK_CAMPFIRE_POT,
CobblemonBlocks.BLUE_CAMPFIRE_POT,
CobblemonBlocks.GREEN_CAMPFIRE_POT,
@@ -352,7 +349,12 @@ object CobblemonClient {
this.implementation.registerBlockEntityRenderer(CobblemonBlockEntities.DISPLAY_CASE, ::DisplayCaseRenderer)
this.implementation.registerBlockEntityRenderer(CobblemonBlockEntities.LECTERN, ::LecternBlockEntityRenderer)
this.implementation.registerBlockEntityRenderer(CobblemonBlockEntities.CAMPFIRE, ::CampfireBlockEntityRenderer)
this.implementation.registerBlockEntityRenderer(CobblemonBlockEntities.POKE_SNACK, ::PokeSnackBlockEntityRenderer)
this.implementation.registerBlockEntityRenderer(CobblemonBlockEntities.LURE_CAKE, ::CakeBlockEntityRenderer)
this.implementation.registerBlockEntityRenderer(CobblemonBlockEntities.POKE_CAKE, ::CakeBlockEntityRenderer)
this.implementation.registerBlockEntityRenderer(
CobblemonBlockEntities.CANDLE_POKE_CAKE,
::CandlePokeCakeBlockEntityRenderer
)
}
private fun registerEntityRenderers() {
@@ -381,8 +383,9 @@ object CobblemonClient {
private fun registerItemColors() {
implementation.registerItemColors(AprijuiceItemColorProvider, *CobblemonItems.aprijuices.toTypedArray())
implementation.registerItemColors(PokeSnackItemColorProvider, CobblemonItems.POKE_SNACK, CobblemonItems.POKE_CAKE)
implementation.registerItemColors(PokeBaitItemColorProvider, CobblemonItems.POKE_BAIT)
implementation.registerItemColors(PokeBaitItemColorProvider, CobblemonItems.LURE_CAKE)
implementation.registerItemColors(PokeBaitItemColorProvider, CobblemonItems.POKE_CAKE)
implementation.registerItemColors(PonigiriItemColorProvider, CobblemonItems.PONIGIRI)
implementation.registerItemColors(SinisterTeaItemColorProvider, CobblemonItems.SINISTER_TEA)
}
@@ -20,8 +20,6 @@ import com.cobblemon.mod.common.api.pokeball.PokeBalls
import com.cobblemon.mod.common.api.pokemon.aspect.aspectParticleMap
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.api.pokemon.aspect.ParticleData
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourState
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.api.scheduling.ScheduledTask
import com.cobblemon.mod.common.api.scheduling.SchedulingTracker
import com.cobblemon.mod.common.api.scheduling.afterOnClient
@@ -14,7 +14,6 @@ import com.cobblemon.mod.common.api.riding.behaviour.types.air.*
import com.cobblemon.mod.common.api.riding.behaviour.types.composite.CompositeBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.types.composite.CompositeState
import com.cobblemon.mod.common.api.riding.behaviour.types.land.HorseBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.types.land.MinekartBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.types.land.VehicleBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.types.liquid.BoatBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.types.liquid.BurstBehaviour
@@ -100,14 +99,13 @@ class RideControlsOverlay : Gui(Minecraft.getInstance()) {
// Don't render if duration config set to 0 or less
if (maxDurationFrames <= 0F) return
val rideBehaviourSettings = riddenEntity.ridingController?.context?.settings ?: return
val ridingBehaviourState = riddenEntity.ridingController?.context?.state ?: return
val rideBehaviourKey = rideBehaviourSettings.key
val rideBehaviourSettings = riddenEntity.ridingBehaviourSettings
val rideBehaviourKey = rideBehaviourSettings?.key
// Update current ride behaviour if changed
// If composite, get active behaviour instead
if (rideBehaviourKey == CompositeBehaviour.KEY) {
val activeBehaviour = (ridingBehaviourState as CompositeState).activeBehaviour.get()
val activeBehaviour = (riddenEntity.ridingState as CompositeState).activeBehaviour.get()
if (currentBehaviourKey != activeBehaviour) {
currentBehaviourKey = activeBehaviour
resetOverlayState()
@@ -135,34 +133,25 @@ class RideControlsOverlay : Gui(Minecraft.getInstance()) {
// Configure what controls to show for each behaviour
when (currentBehaviourKey) {
BirdBehaviour.KEY -> {}
BoatBehaviour.KEY -> {}
BurstBehaviour.KEY -> {}
DolphinBehaviour.KEY -> {}
GliderBehaviour.KEY -> {}
HelicopterBehaviour.KEY -> {}
HorseBehaviour.KEY -> {
showVerticalMouse = false
disableHorizontalMovementKeys = true
showSneakKey = false
}
HoverBehaviour.KEY -> {
showVerticalMouse = false
}
BoatBehaviour.KEY -> {}
GliderBehaviour.KEY -> {}
HelicopterBehaviour.KEY -> {}
JetBehaviour.KEY -> {
showMovementKeys = false
showSneakKey = false
showJumpKey = false
}
MinekartBehaviour.KEY -> {
showVerticalMouse = false
showHorizontalMouse = false
showSneakKey = false
}
RocketBehaviour.KEY -> {
showVerticalMouse = false
showHorizontalMouse = false
}
BurstBehaviour.KEY -> {}
VehicleBehaviour.KEY -> {}
HoverBehaviour.KEY -> {
showVerticalMouse = false
}
}
val centerX = minecraft.window.guiScaledWidth / 2
@@ -201,10 +201,10 @@ class BattleMoveSelection(
val move = moveTiles.find { it.isHovered(mouseX, mouseY) }
val gimmick = gimmickButtons.find { it.isHovered(mouseX, mouseY) }
if (move != null) {
if(request.activePokemon.getFormat().battleType.pokemonPerSide == 1) {
if(this.request.activePokemon.getFormat().battleType.pokemonPerSide == 1) {
move.onClick()
} else {
battleGUI.changeActionSelection(BattleTargetSelection(battleGUI, request, move.move, move.response.gimmickID, move.move.gimmickMove))
battleGUI.changeActionSelection(BattleTargetSelection(battleGUI, request, move.move))
playDownSound(Minecraft.getInstance().soundManager)
}
return true
@@ -14,7 +14,6 @@ import com.cobblemon.mod.common.api.moves.MoveTemplate
import com.cobblemon.mod.common.api.text.bold
import com.cobblemon.mod.common.api.text.font
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.battles.InBattleGimmickMove
import com.cobblemon.mod.common.battles.InBattleMove
import com.cobblemon.mod.common.battles.MoveActionResponse
import com.cobblemon.mod.common.client.CobblemonClient
@@ -44,9 +43,7 @@ import kotlin.math.sin
class BattleTargetSelection(
battleGUI: BattleGUI,
request: SingleActionRequest,
val move: InBattleMove,
val gimmickID: String?,
gimmickMove: InBattleGimmickMove?
val move: InBattleMove
) : BattleActionSelection(
battleGUI = battleGUI,
request = request,
@@ -84,10 +81,9 @@ class BattleTargetSelection(
val targets = request.activePokemon.getAllActivePokemon()
val targetType = if (gimmickID != null && gimmickMove != null) gimmickMove.target else move.target
val backButton = BattleBackButton(x + 9F, Minecraft.getInstance().window.guiScaledHeight - 22F)
val selectableTargetList = targetType.targetList(request.activePokemon)
val multiTargetList = if(selectableTargetList == null) request.activePokemon.getMultiTargetList(targetType) else null
val selectableTargetList = move.target.targetList(request.activePokemon)
val multiTargetList = if(selectableTargetList == null) request.activePokemon.getMultiTargetList(move.target) else null
val baseTiles = targets.mapIndexed { index, target ->
val isAlly = target.isAllied(request.activePokemon)
@@ -11,8 +11,8 @@ package com.cobblemon.mod.common.client.gui.cookingpot
import com.cobblemon.mod.common.CobblemonNetwork.sendToServer
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.block.campfirepot.CookingPotMenu
import com.cobblemon.mod.common.block.campfirepot.SeasoningSlot
import com.cobblemon.mod.common.block.entity.CampfireBlockEntity.Companion.IS_LID_OPEN_INDEX
import com.cobblemon.mod.common.block.entity.CampfireBlockEntity.Companion.PREVIEW_ITEM_SLOT
import com.cobblemon.mod.common.integration.jei.CobblemonJeiPlugin
import com.cobblemon.mod.common.integration.jei.cooking.CampfirePotRecipeCategory
import com.cobblemon.mod.common.item.crafting.CookingPotRecipe
@@ -39,7 +39,6 @@ import net.minecraft.world.inventory.ClickType
import net.minecraft.world.inventory.RecipeBookMenu
import net.minecraft.world.inventory.Slot
import net.minecraft.world.item.crafting.CraftingInput
import java.util.*
import kotlin.math.ceil
@Environment(EnvType.CLIENT)
@@ -175,36 +174,24 @@ class CookingPotScreen(
this.recipeBookComponent.renderGhostRecipe(context, this.leftPos, this.topPos, true, partialTicks)
}
val resultSlot = menu.slots[menu.resultSlotIndex]
val optionalRecipe = menu.currentActiveRecipe
if (optionalRecipe != null && !optionalRecipe.value.result.isEmpty && !resultSlot.hasItem()) {
val resultItem = menu.previewItem
RenderSystem.enableBlend()
RenderSystem.setShaderColor(1F, 1F, 1F, 0.5F)
context.renderFakeItem(resultItem, leftPos + resultSlot.x, topPos + resultSlot.y);
context.renderItemDecorations(font, resultItem, leftPos + resultSlot.x, topPos + resultSlot.y)
RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F)
RenderSystem.disableBlend()
if (isHovering(resultSlot.x, resultSlot.y, 16, 16, mouseX.toDouble(), mouseY.toDouble()) && menu.carried.isEmpty) {
context.renderTooltip(font, getTooltipFromContainerItem(resultItem), resultItem.tooltipImage, mouseX, mouseY)
}
}
this.renderTooltip(context, mouseX, mouseY)
this.recipeBookComponent.renderTooltip(context, this.leftPos, this.topPos, mouseX, mouseY)
}
override fun renderSlot(guiGraphics: GuiGraphics, slot: Slot) {
if (slot is SeasoningSlot && !menu.carried.isEmpty && !slot.mayPlace(menu.carried)) {
val x = slot.x
val y = slot.y
guiGraphics.fill(x,y,x+16,y+16,822018048)
if (slot.index != PREVIEW_ITEM_SLOT) {
super.renderSlot(guiGraphics, slot)
return
}
super.renderSlot(guiGraphics, slot)
RenderSystem.enableBlend()
RenderSystem.setShaderColor(1F, 1F, 1F, 0.5F)
guiGraphics.renderFakeItem(slot.item, slot.x, slot.y);
guiGraphics.renderItemDecorations(minecraft!!.font, slot.item, slot.x, slot.y)
RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F)
RenderSystem.disableBlend()
}
override fun recipesUpdated() {
@@ -146,19 +146,19 @@ class RidingStatsDebugGUI(val vehicle: PokemonEntity) : Screen(lang("ui.debug.ri
val maxStamina = maxStaminaInput.value.toIntOrNull() ?: getRidingStatRange(RidingStat.STAMINA).last
if (minSpeed < maxSpeed) {
vehicle.rideProp.behaviours?.get(ridingStyle)?.stats?.put(RidingStat.SPEED, minSpeed..maxSpeed)
vehicle.rideProp.stats[RidingStat.SPEED]?.ranges?.put(ridingStyle, minSpeed..maxSpeed)
}
if (minAcceleration < maxAcceleration) {
vehicle.rideProp.behaviours?.get(ridingStyle)?.stats?.put(RidingStat.ACCELERATION, minSpeed..maxSpeed)
vehicle.rideProp.stats[RidingStat.ACCELERATION]?.ranges?.put(ridingStyle, minAcceleration..maxAcceleration)
}
if (minSkill < maxSkill) {
vehicle.rideProp.behaviours?.get(ridingStyle)?.stats?.put(RidingStat.SKILL, minSpeed..maxSpeed)
vehicle.rideProp.stats[RidingStat.SKILL]?.ranges?.put(ridingStyle, minSkill..maxSkill)
}
if (minJump < maxJump) {
vehicle.rideProp.behaviours?.get(ridingStyle)?.stats?.put(RidingStat.JUMP, minSpeed..maxSpeed)
vehicle.rideProp.stats[RidingStat.JUMP]?.ranges?.put(ridingStyle, minJump..maxJump)
}
if (minStamina < maxStamina) {
vehicle.rideProp.behaviours?.get(ridingStyle)?.stats?.put(RidingStat.STAMINA, minSpeed..maxSpeed)
vehicle.rideProp.stats[RidingStat.STAMINA]?.ranges?.put(ridingStyle, minStamina..maxStamina)
}
CobblemonNetwork.sendToServer(
@@ -198,9 +198,7 @@ class RidingStatsDebugGUI(val vehicle: PokemonEntity) : Screen(lang("ui.debug.ri
}
fun getRidingStatRange(ridingStat: RidingStat): IntRange {
// TODO
return 0..0
// return vehicle.rideProp.stats[ridingStat]?.ranges?.get(ridingStyle) ?: 0..0
return vehicle.rideProp.stats[ridingStat]?.ranges?.get(ridingStyle) ?: 0..0
}
fun getScaledWidth() = Minecraft.getInstance().window.guiScaledWidth
@@ -719,7 +719,6 @@ class PCGUI(
if (pasture.pastureScrollList.isHovered) pasture.pastureScrollList.mouseScrolled(mouseX, mouseY, amount, verticalAmount)
}
if (storageWidget.isHovered && mouseX < (storageWidget.x + StorageWidget.SCREEN_WIDTH)) this.storageWidget.box -= verticalAmount.toInt()
if (wallpaperWidget.isHovered) wallpaperWidget.mouseScrolled(mouseX, mouseY, amount, verticalAmount)
return super.mouseScrolled(mouseX, mouseY, amount, verticalAmount)
}

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