Compare commits

..
Author SHA1 Message Date
Segfault Guy 5661ae2cb0 Command and showdown service update for testing team validation. 2025-09-22 10:09:56 -04:00
Segfault Guy 9e9603d60d Merge remote-tracking branch 'origin/main' into experimental_showdown_changes 2025-08-20 23:39:03 -04:00
Polymeta 5b90a4d894 prevent clientActions inside dialogues being executed twice
Signed-off-by: Polymeta <40173118+Polymeta@users.noreply.github.com>
2025-08-21 00:06:31 +01:00
Polymeta 69d3ef23ef fix pokedex lookup during battle causing crashes with namespaced species
Signed-off-by: Polymeta <40173118+Polymeta@users.noreply.github.com>
2025-08-20 21:50:03 +01:00
Landon ca8bcd966f Merge branch 'playericonfix' into 'main'
don't touch my mixins if you're not going to bother testing your changes

See merge request cable-mc/cobblemon!1789
2025-08-19 19:06:49 +00:00
Segfault Guy 12c7318625 don't touch my mixins if you're not going to bother testing your changes 2025-08-19 09:42:44 -04:00
Hiroku 4f917b94e8 Merge branch 'narrator-dev-fix' into 'main'
Fixed narrator-related GUI crashes on NeoForge

See merge request cable-mc/cobblemon!1788
2025-08-19 12:03:57 +00:00
gruxor 9e82cb2516 Fixed narrator-related GUI crashes on NeoForge 2025-08-19 07:37:27 -04:00
Polymeta bbf85d5804 Merge branch 'riding-fix-failing-asserts' into 'main'
Move and rename the scaleToRange function and place it in the RidingBehaviour...

See merge request cable-mc/cobblemon!1783
2025-08-19 09:20:17 +00:00
Jackson BowesandPolymeta 9daa85750b Move and rename the scaleToRange function and place it in the RidingBehaviour... 2025-08-19 09:20:17 +00:00
Polymeta 6f7c3b0681 Merge branch 'riding-species-sprint-and-jet-crash-fix' into 'main'
Add ability to disable sprinting per species for horse and fix unneeded assert in a Jet function.

See merge request cable-mc/cobblemon!1780
2025-08-19 08:38:00 +00:00
Polymeta d262bb6136 Merge branch 'sound' into 'main'
Tatami, Hearty Grains and cooking sounds

See merge request cable-mc/cobblemon!1784
2025-08-19 08:37:18 +00:00
Polymeta 7509934394 Merge branch 'cooking-pot-ui-tweaks' into 'main'
Cooking Pot UI Tweaks + Bugfix

See merge request cable-mc/cobblemon!1785
2025-08-19 08:35:32 +00:00
GertimoshkaandPolymeta a0ae226506 Cooking Pot UI Tweaks + Bugfix 2025-08-19 08:35:32 +00:00
Hiroku 0dde8d27e6 Merge branch 'ihatebugtypes' into 'main'
kill all bug type pokemon

Closes #1541

See merge request cable-mc/cobblemon!1786
2025-08-18 05:32:06 +00:00
Segfault Guy 559e728bc3 Deprecate ContextManager. 2025-08-18 00:34:12 -04:00
Segfault Guy dcea1bc67f Merge remote-tracking branch 'origin/main' into ihatebugtypes 2025-08-18 00:29:52 -04:00
Sterrezo 7dc451f344 Ambient sound now stops when broken, volume adjustments 2025-08-17 23:28:53 +02:00
Sterrezo b6ea6a6850 Merge branch 'main' into sound 2025-08-17 22:37:13 +02:00
Segfault Guy 7fe91f7ad0 Original natures for flavor-bias berries 2025-08-17 14:29:18 -04:00
Hiroku 768a6df247 Merge branch 'target-selection-fixes' into 'main'
Fix target selection for gimmick moves

See merge request cable-mc/cobblemon!1781
2025-08-17 08:14:39 +00:00
Landon Wills 5c9c8b0b3b Fix target selection for gimmick moves 2025-08-17 03:34:20 -04:00
jackowes 085972f2d3 Add ability to disable sprinting per species for horse and fix unneeded assert in a Jet function. 2025-08-17 01:51:35 -05:00
Landon ddefd1aa24 Merge branch 'booooooo' into 'main'
Fix ghost pokemon that occur due to riding

See merge request cable-mc/cobblemon!1779
2025-08-17 06:04:58 +00:00
JPAKx4 53f441884e Fix ghost pokemon that occur due to riding 2025-08-16 23:47:46 -05:00
Landon 954c5a1027 Merge branch 'riding-transition-changes' into 'main'
Riding transition changes

See merge request cable-mc/cobblemon!1778
2025-08-17 04:35:10 +00:00
Landon Wills c26ab8ff60 Add license 2025-08-17 00:12:39 -04:00
Landon Wills 9ea9f9b307 Fix issue with passenger ejection and survival double jump 2025-08-16 23:29:24 -04:00
Paul fc5ba24e76 removed some debug printlns 2025-08-17 10:07:59 +10:00
Landon Wills 0fe496a290 Fix up outstanding TODOs 2025-08-16 04:43:06 -04:00
Landon Wills df0eb21a9e Change some of the json files to new system 2025-08-16 04:36:52 -04:00
Landon Wills 79ed7da316 Sync behavours between client and server 2025-08-16 04:15:30 -04:00
Landon Wills 908a8b89a6 Add logic for if pokemon is always hovering 2025-08-16 03:34:35 -04:00
Landon Wills 83d4d6f7e9 Change logic for transitions 2025-08-16 03:20:56 -04:00
Hiroku a588906f43 Merge branch 'showdown-datapack' into 'main'
Datapackable abilities/moves; refactoring

See merge request cable-mc/cobblemon!1686
2025-08-15 22:10:03 +00:00
Polymeta e890fd3e9e adjust mochi EV increase per previous discussion
Signed-off-by: Polymeta <40173118+Polymeta@users.noreply.github.com>
2025-08-15 21:16:46 +01:00
Test e7c95ac72c Merge branch 'main' of https://gitlab.com/cable-mc/cobblemon 2025-08-15 13:15:08 -04:00
Test 245765c6af fix partial match filtering, +lang support in PC 2025-08-15 13:14:51 -04:00
Paul f8ae4dcecd Removed battler_cooldown stuff since it was not really fully implemented, fixed NPCs getting autoconfigured with pokemon behaviours 2025-08-15 21:12:54 +10:00
Test f694c7c821 Merge branch 'whispering-whismur' into 'main'
Implement a vibration-dampening behaviour for Pokémon living in the Deep Dark

See merge request cable-mc/cobblemon!1745
2025-08-15 03:26:36 +00:00
Test a4824b6766 Merge branch 'riding-fix-reverse-animation-input' into 'main'
amend what has been broken :(

See merge request cable-mc/cobblemon!1775
2025-08-15 03:22:15 +00:00
Test 834533aa6a Merge branch 'main' of https://gitlab.com/cable-mc/cobblemon 2025-08-14 23:04:05 -04:00
Test 0c9cf2c83d update snack POI key 2025-08-14 23:03:33 -04:00
jackowes fbffeff8dc amend what has been broken :( 2025-08-14 21:39:51 -05:00
Paul 8532ca2d38 Added fluid sleeping, fixed non-water sleepers sleeping in water 2025-08-15 11:38:27 +10:00
Paul 722bf5ba7b Added q.delete_variable(struct, name) and q.delete_variables(struct) 2025-08-15 10:07:21 +10:00
gruxor ca1cf72d86 semicolon 2025-08-14 08:24:52 -04:00
gruxor e12936fb66 Refactoring; review resolution 2025-08-14 08:24:52 -04:00
gruxor 9b85742c00 Datapackable abilities/moves; refactoring 2025-08-14 08:24:52 -04:00
Hiroku aceaef14c5 Merge branch 'tag-im-it' into 'main'
Errors Be Gone

See merge request cable-mc/cobblemon!1774
2025-08-14 11:28:17 +00:00
dhi_holo c168364ca4 Errors Be Gone
Fixed an error I made in a previous branch
Fixed some tag names erroring on NeoForge
Fixed some items not being properly scrubbed from the mod
Fixed some tags not having translations, causing errors in the log
2025-08-14 20:45:45 +10:00
Polymeta db6533795b Merge branch 'Adjust-Generation-of-New-Flora-1.7' into 'main'
Adjusted generation chances/rates for all new 1.7 flora

See merge request cable-mc/cobblemon!1773
2025-08-13 22:45:52 +00:00
Slippy 596c0cd8c3 Adjusted generation chances/rates for all new 1.7 flora 2025-08-13 15:55:40 -05:00
Polymeta b65cec2916 fix codec error
Signed-off-by: Polymeta <40173118+Polymeta@users.noreply.github.com>
2025-08-13 16:39:42 +01:00
Polymeta 63ef406058 Merge branch 'cooking-still-needs-fixes' into 'main'
Even More Cooking Fixes

See merge request cable-mc/cobblemon!1771
2025-08-13 13:42:14 +00:00
GertimoshkaandPolymeta 4856e9feee Even More Cooking Fixes 2025-08-13 13:42:14 +00:00
Polymeta e839813c2a Merge branch 'food-check' into 'main'
Food Balancing Tweaks and Asset Updates

See merge request cable-mc/cobblemon!1772
2025-08-13 11:05:29 +00:00
dhi_holoandPolymeta c70f2a6684 Food Balancing Tweaks and Asset Updates 2025-08-13 11:05:28 +00:00
Paul 860ec50071 Fixed the lerping between poses for pitch tilting 2025-08-13 20:44:54 +10:00
Polymeta 93e26fb31f Merge branch 'snack' into 'main'
🍰

See merge request cable-mc/cobblemon!1768
2025-08-13 06:09:45 +00:00
TestandPolymeta 23e2e18019 🍰 2025-08-13 06:09:45 +00:00
Polymeta a0d5a1fbad Merge branch 'flx-the-big-oops' into 'main'
Fix the oopsie

See merge request cable-mc/cobblemon!1770
2025-08-13 06:08:27 +00:00
JPAKx4 b1b5ef7236 Fix the oopsie 2025-08-12 22:30:00 -05:00
Polymeta 6ab2aa3413 fix game crashes if lure enchantment is not present
Closes #1646

Signed-off-by: Polymeta <40173118+Polymeta@users.noreply.github.com>
2025-08-12 21:51:33 +01:00
Polymeta 16819bb0e7 * Updated Ponigiri Textures
* Updated Sinister Tea Textures, as well as added alternate recipes with altered component outputs (does encounter a slight bug with Minecraft's inherent behaviour, will be fixed naturally when we upgrade versions)
* Cleaned up a lot of recipe files
* Tweaked some food colours
* Made berries smeltable into their various dyes
* Updated the Apricorn crop models and hitboxes
* New Campfire Pot advancement
* Hearty Rice now has variable growth from bone meal
2025-08-12 20:17:45 +00:00
dhi_holoandPolymeta 0bf3488a01 Foods 2025-08-12 20:17:44 +00:00
Polymeta 1f3d3e90cf Merge branch 'even-more-cooking-fixes' into 'main'
Additional Fixes to Cooking Pots

See merge request cable-mc/cobblemon!1763
2025-08-12 20:16:09 +00:00
Polymeta d141e6b46d Merge branch 'riding-controllers-expanded' into 'main'
Controller additions as well as many controller fixes and polish

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

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

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

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

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

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

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

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

See merge request cable-mc/cobblemon!1765
2025-08-08 21:40:50 +00:00
Slippy cd03e6f8c4 Replaced the Phantoms with Breezes in the Lush Shipwreck Cove 2025-08-08 00:48:19 -05:00
Slippy a1b53f90ad Adjusting the loot table to trial spawners in shipwreck coves a little bit. 2025-08-08 00:40:54 -05:00
Josh c0677c6353 Fixed item rendering being flipped for shoulder mounts and model widgets 2025-08-05 13:11:14 -04:00
Gertimoshka ea49ea7818 Made Cooking pot drop/put crafting remainder items from pots facing direction, as well as unhardcoded the remainder item calculation 2025-08-04 21:18:54 +03:00
Gertimoshka 725b8bc614 Made client not calculate preview item every render, and instead on crafting slot changes 2025-08-01 20:58:12 +03:00
Gertimoshka 875cd2c1b9 Remove Depriciated CookingPotPreviewSlot class 2025-07-31 22:26:18 +03:00
Gertimoshka db972aba4a Fixed Preview Slot being dropped on breaking and showing by removing the fake slot and remaking how fake item render works 2025-07-31 21:36:24 +03:00
Nadja Reitzenstein 714412246b Implement a vibration-dampening behaviour for Pokémon living in the Deep Dark 2025-07-17 20:01:27 +02:00
Sterrezo db37721e9f Merge branch 'main' into sound 2025-07-08 17:18:03 +02:00
Sterrezo c373d24fe1 Campfire Pot now plays the break sound when the campfire is broken 2025-07-08 17:17:50 +02:00
Sterrezo 2770cbbe35 Added Campfire Pot sounds 2025-07-08 17:09:26 +02:00
Sterrezo 699a47c633 Updated Hearty Grains sounds and added Hearty Grain Bale sounds 2025-07-06 17:51:45 +02:00
Sterrezo 47ebc6c6ca Merge branch 'main' into sound 2025-07-06 17:37:57 +02:00
Sterrezo bdb47532b9 let's not import everything there is 2025-07-05 18:50:31 +02:00
Sterrezo b5a40adcdf Merge branch 'main' into sound 2025-07-05 18:12:18 +02:00
Sterrezo 955422aab7 Updated Hearty Grains sounds and added water variation 2025-07-05 17:55:30 +02:00
Sterrezo cfa55095aa Added sounds for Hearty Grains, updated Tatami sounds 2025-06-30 19:41:26 +02:00
Sterrezo bcac005fb1 Merge branch 'main' into sound 2025-06-23 14:03:31 +02:00
Sterrezo aa6536b57c Added sounds for Tatami blocks and mats 2025-06-23 12:22:06 +02:00
Segfault Guy 58a5412602 Shift arbitrary battle lead to front of party while preserving order. 2025-05-02 00:48:05 -04:00
940 changed files with 22308 additions and 13945 deletions
+14 -1
View File
@@ -40,7 +40,11 @@
- Ground type Pokémon are immune to lightning damage
- Added functionality to Everstone when held by a Pokémon; suppresses evolution notification and hides evolve button in summary interface.
- Added new optional property `attachment_options` for most EmitterShapes to be attached to the locator/entities scale, rotation, and/or position. Position is true by default.
- Galarica Nut bushes now generate on beaches
- Galarica Nut bushes now generate on beaches.
- Some Pokémon now pitch their bodies in the direction they're moving.
- Added 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 `/runmolang <molang> [<npc>|<player>|<pokemon>]` command that executes a MoLang expression with the provided options as environment variables, as well as the entity (as `q.entity`) that executed the command.
### Pokémon Added
@@ -241,6 +245,10 @@
- Not specifying a dex in `/pokedex printcalculations {player} {dex}` will now print the National Dex statistics instead of showing all dexes. `/pokedex printcalculations {player} all` is how to view all dex statistics in one command.
- Removed Braised Vivichoke
- Updated Pokémon state icons shown in party interfaces
- MoLang `face` function can now be run on any `PosableEntity` (Like Pokémon!) instead of just NPCs.
- MoLang `run_action_effect` now works on Pokémon.
- Changed MoLang entity function `is_standing_on` to allow for block tags in the list.
- Added entity (as `q.entity`) that executed the command to the `executemolangscript` command.
### Fixes
- Fixed game crashing when removing national pokedex using datapacks
@@ -386,6 +394,8 @@
- 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`.
@@ -399,6 +409,9 @@
- Added `q.has_argument(<argument_name>, [argument_value])` MoLang function to several battle-related action effect contexts.
- Added `q.has_argument_at(<index>, [argument_value])` MoLang function to several battle-related action effect contexts.
- Added `q.hit_count` MoLang function to move action effect contexts.
- Added `is_included`, `to_lower`, `to_upper`, and `string_length` as available Molang functions.
- Fixed a crash that would occur during battles if the opponent wild Pokémon species comes from a namespace other than cobblemon
- Fixed `clientActions` inside Dialogue pages being executed twice
## [1.6.1 (January 26th, 2025)](#1-6-1)
@@ -0,0 +1,13 @@
/*
* 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,15 +9,6 @@
package com.cobblemon.mod.common;
import com.cobblemon.mod.common.api.orientation.OrientationController;
import net.minecraft.util.Mth;
import net.minecraft.util.SmoothDouble;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.Nullable;
import org.joml.Matrix3f;
import org.joml.Vector3f;
import java.util.function.Function;
public interface OrientationControllable {
OrientationController getOrientationController();
@@ -10,6 +10,9 @@ package com.cobblemon.mod.common.duck;
import org.joml.Vector3f;
public interface ServerEntityDuck {
public interface PlayerDuck {
void setDriverInput(Vector3f driverInput);
Vector3f getDriverInput();
void setLastSentDriverInput(Vector3f driverInput);
Vector3f getLastSentDriverInput();
}
@@ -8,17 +8,44 @@
package com.cobblemon.mod.common.mixin;
import com.cobblemon.mod.common.OrientationControllable;
import com.cobblemon.mod.common.api.orientation.OrientationController;
import com.cobblemon.mod.common.api.pokemon.effect.ShoulderEffectRegistry;
import com.cobblemon.mod.common.api.riding.Seat;
import com.cobblemon.mod.common.client.entity.PokemonClientDelegate;
import com.cobblemon.mod.common.client.render.MatrixWrapper;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.effect.MobEffectInstance;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.level.ClipContext;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.HitResult;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.NotNull;
import org.joml.Quaternionf;
import org.joml.Vector3f;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(LivingEntity.class)
public abstract class LivingEntityMixin {
public abstract class LivingEntityMixin extends Entity implements OrientationControllable {
public LivingEntityMixin(EntityType<?> entityType, Level level) {
super(entityType, level);
}
@Unique
private final OrientationController cobblemon$orientationController = new OrientationController((LivingEntity) (Object) this);
public OrientationController getOrientationController() {
return cobblemon$orientationController;
}
@Inject(method = "onEffectRemoved", at = @At(value = "TAIL"))
private void cobblemon$onEffectRemoved(MobEffectInstance effect, CallbackInfo ci) {
@@ -28,4 +55,67 @@ public abstract class LivingEntityMixin {
}
}
@Override
public @NotNull HitResult pick(double hitDistance, float partialTicks, boolean hitFluids) {
Entity vehicle = this.getVehicle();
if (vehicle instanceof PokemonEntity pokemonEntity) {
int seatIndex = pokemonEntity.getPassengers().indexOf(this);
Seat seat = pokemonEntity.getSeats().get(seatIndex);
PokemonClientDelegate delegate = (PokemonClientDelegate) pokemonEntity.getDelegate();
MatrixWrapper locator = delegate.getLocatorStates().get(seat.getLocator());
Vec3 locatorOffset = new Vec3(locator.getMatrix().getTranslation(new Vector3f()));
Vec3 eyePosition = cobblemon$getEyePosition(partialTicks, pokemonEntity, locatorOffset);
Vec3 viewVector = this.getViewVector(partialTicks);
Vec3 viewDistanceVector = eyePosition.add(viewVector.x * hitDistance, viewVector.y * hitDistance, viewVector.z * hitDistance);
return this.level()
.clip(
new ClipContext(
eyePosition, viewDistanceVector, ClipContext.Block.OUTLINE, hitFluids ? net.minecraft.world.level.ClipContext.Fluid.ANY : net.minecraft.world.level.ClipContext.Fluid.NONE, this
)
);
}
return super.pick(hitDistance, partialTicks, hitFluids);
}
@Unique
private Vec3 cobblemon$getEyePosition(float partialTicks, PokemonEntity pokemonEntity, Vec3 locatorOffset) {
OrientationController controller = this.getOrientationController();
float currEyeHeight = this.getEyeHeight();
Vec3 offset = locatorOffset.add(pokemonEntity.position());
if (controller.isActive() && controller.getOrientation() != null) {
Quaternionf orientation = controller.getRenderOrientation(partialTicks);
Vec3 rotatedEyeHeight = new Vec3(orientation.transform(new Vector3f(0f, currEyeHeight - (this.getBbHeight() / 2), 0f)));
offset.add(rotatedEyeHeight);
}
return offset;
}
@Inject(method = "tick", at = @At("HEAD"))
private void cobblemon$updateRenderOrientation(CallbackInfo ci) {
this.cobblemon$orientationController.tick();
}
@Override
public void absMoveTo(double x, double y, double z, float yaw, float pitch) {
if (cobblemon$orientationController.getActive()) {
this.absMoveTo(x, y, z);
this.setYRot(yaw % 360.0f);
this.setXRot(pitch % 360.0f);
this.yRotO = this.getYRot();
this.xRotO = this.getXRot();
} else {
this.absMoveTo(x, y, z);
this.absRotateTo(yaw, pitch);
}
}
}
@@ -18,6 +18,7 @@ import com.cobblemon.mod.common.api.pokemon.PokemonSpecies;
import com.cobblemon.mod.common.api.storage.party.PartyStore;
import com.cobblemon.mod.common.api.storage.party.PlayerPartyStore;
import com.cobblemon.mod.common.api.tags.CobblemonItemTags;
import com.cobblemon.mod.common.duck.PlayerDuck;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import com.cobblemon.mod.common.pokedex.scanner.PokedexEntityData;
import com.cobblemon.mod.common.pokedex.scanner.ScannableEntity;
@@ -47,6 +48,7 @@ import net.minecraft.world.food.FoodProperties;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import org.jetbrains.annotations.Nullable;
import org.joml.Vector3f;
import org.objectweb.asm.Opcodes;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
@@ -61,7 +63,12 @@ import java.util.*;
import java.util.stream.Collectors;
@Mixin(Player.class)
public abstract class PlayerMixin extends LivingEntity implements ScannableEntity, OrientationControllable {
public abstract class PlayerMixin extends LivingEntity implements ScannableEntity, OrientationControllable, PlayerDuck {
@Unique
private Vector3f cobblemon$driverInput;
@Unique
private Vector3f cobblemon$lastSentDriverInput;
@Shadow public abstract CompoundTag getShoulderEntityLeft();
@@ -81,8 +88,8 @@ public abstract class PlayerMixin extends LivingEntity implements ScannableEntit
@Shadow @Final private static Map<Pose, EntityDimensions> POSES;
@Shadow @Final public static EntityDimensions STANDING_DIMENSIONS;
@Unique
private final OrientationController cobblemon$orientationController = new OrientationController(this);
@Shadow protected int jumpTriggerTime;
@Unique private final OrientationController cobblemon$orientationController = new OrientationController(this);
protected PlayerMixin(EntityType<? extends LivingEntity> p_20966_, Level p_20967_) {
super(p_20966_, p_20967_);
@@ -279,4 +286,25 @@ public abstract class PlayerMixin extends LivingEntity implements ScannableEntit
public OrientationController getOrientationController() {
return cobblemon$orientationController;
}
@Override
public void setDriverInput(Vector3f driverInput) {
cobblemon$driverInput = driverInput;
}
@Override
public Vector3f getDriverInput() {
return cobblemon$driverInput;
}
@Override
public void setLastSentDriverInput(Vector3f lastSentDriverInput) {
cobblemon$lastSentDriverInput = lastSentDriverInput;
}
@Override
public Vector3f getLastSentDriverInput() {
return cobblemon$lastSentDriverInput;
}
}
@@ -12,7 +12,7 @@ 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.ServerEntityDuck;
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;
@@ -41,7 +41,7 @@ import java.util.Set;
import java.util.function.Consumer;
@Mixin(ServerEntity.class)
public abstract class ServerEntityMixin implements ServerEntityDuck {
public abstract class ServerEntityMixin {
@Shadow
@Final
@@ -53,10 +53,6 @@ public abstract class ServerEntityMixin implements ServerEntityDuck {
private Matrix3f cobblemon$lastSentOrientation;
@Unique
private boolean cobblemon$lastSentActive;
@Unique
private Vector3f cobblemon$driverInput;
@Unique
private Vector3f cobblemon$lastSentDriverInput;
@Inject(method = "sendChanges", at = @At("TAIL"))
private void cobblemon$sendChanges(CallbackInfo ci) {
@@ -67,13 +63,15 @@ public abstract class ServerEntityMixin implements ServerEntityDuck {
private void cobblemon$sendRidingStateChanges() {
if (!(this.entity instanceof PokemonEntity pokemonEntity)) return;
if (pokemonEntity.getRidingState() == null) return;
if (pokemonEntity.getRiding() == null) return;
var ridingBehaviour = pokemonEntity.getRiding();
var ridingState = pokemonEntity.getRidingState();
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();
var previousRidingState = pokemonEntity.getPreviousRidingState();
if (previousRidingState != null && !ridingState.shouldSync(previousRidingState)) return;
cobblemon$broadcast(new ClientboundUpdateRidingStatePacket(pokemonEntity.getId(), ridingBehaviour.getKey(), pokemonEntity.getRidingState(), null));
cobblemon$broadcast(new ClientboundUpdateRidingStatePacket(pokemonEntity.getId(), ridingBehaviour, ridingState, null));
}
private void cobblemon$sendOrientationChanges() {
@@ -93,12 +91,14 @@ public abstract class ServerEntityMixin implements ServerEntityDuck {
private void cobblemon$sendDriverInput() {
if (!(this.entity instanceof ServerPlayer serverPlayer)) return;
if(!(serverPlayer.getVehicle() instanceof PokemonEntity)) return;
if(cobblemon$driverInput == null) return;
Vector3f driverInput = ((PlayerDuck)serverPlayer).getDriverInput();
if(driverInput == null) return;
// If no change in input is detected then don't send a new packet
if (cobblemon$driverInput.equals(cobblemon$lastSentDriverInput)) return;
cobblemon$lastSentDriverInput = cobblemon$driverInput;
cobblemon$broadcast(new ClientboundUpdateDriverInputPacket(cobblemon$driverInput, entity.getId()));
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) {
@@ -116,9 +116,4 @@ public abstract class ServerEntityMixin implements ServerEntityDuck {
}
}
@Override
public void setDriverInput(Vector3f driverInput) {
cobblemon$driverInput = driverInput;
}
}
@@ -10,7 +10,10 @@ 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;
@@ -43,4 +46,10 @@ 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,6 +8,7 @@
package com.cobblemon.mod.common.mixin.client;
import com.cobblemon.mod.common.OrientationControllable;
import com.cobblemon.mod.common.client.render.player.MountedPlayerRenderer;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import net.minecraft.client.model.HumanoidModel;
@@ -59,9 +60,9 @@ public class HumanoidModelMixin {
}
Entity vehicle = entity.getVehicle();
if (vehicle instanceof PokemonEntity pokemonEntity && entity instanceof AbstractClientPlayer) {
if (vehicle instanceof PokemonEntity pokemonEntity && entity instanceof OrientationControllable) {
var shouldRotatePlayerHead = pokemonEntity.ifRidingAvailableSupply(false, (behaviour, settings, state) -> {
return behaviour.shouldRotatePlayerHead(settings, state, pokemonEntity);
return behaviour.shouldRotateRiderHead(settings, state, pokemonEntity);
});
if (!shouldRotatePlayerHead) {
@@ -73,7 +74,9 @@ public class HumanoidModelMixin {
}
}
MountedPlayerRenderer.INSTANCE.animate(pokemonEntity, (AbstractClientPlayer) entity, relevantPartsByName, netHeadYaw, headPitch, ageInTicks, limbSwing, limbSwingAmount);
if (!(entity instanceof AbstractClientPlayer player)) return;
MountedPlayerRenderer.INSTANCE.animate(pokemonEntity, player, relevantPartsByName, netHeadYaw, headPitch, ageInTicks, limbSwing, limbSwingAmount);
}
}
}
@@ -0,0 +1,37 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.mixin.client;
import com.cobblemon.mod.common.client.render.entity.MountedLivingEntityRenderer;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.model.EntityModel;
import net.minecraft.client.renderer.entity.LivingEntityRenderer;
import net.minecraft.world.entity.LivingEntity;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(LivingEntityRenderer.class)
public class LivingEntityRendererMixin<T extends LivingEntity, M extends EntityModel<T>> {
@Inject(
method = "setupRotations",
at = @At("HEAD")
)
private void cobblemon$modifyRoll(LivingEntity entity, PoseStack poseStack, float bob, float yBodyRot, float partialTick, float scale, CallbackInfo ci) {
if (entity.isPassenger()) {
var vehicle = entity.getVehicle();
if (vehicle instanceof PokemonEntity pokemonEntity) {
MountedLivingEntityRenderer.INSTANCE.render(entity, pokemonEntity, poseStack, bob, yBodyRot, partialTick, scale);
}
}
}
}
@@ -9,6 +9,7 @@
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;
@@ -17,6 +18,7 @@ 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;
@@ -25,10 +27,12 @@ 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;
@@ -47,13 +51,23 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.Map;
@Mixin(LocalPlayer.class)
public abstract class LocalPlayerMixin extends AbstractClientPlayer implements OrientationControllable{
public abstract class LocalPlayerMixin extends AbstractClientPlayer implements OrientationControllable, DoubleJump {
@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);
}
@@ -88,7 +102,17 @@ public abstract class LocalPlayerMixin extends AbstractClientPlayer implements O
private void cobblemon$updateDriverInputRideTick(CallbackInfo ci) {
if (Minecraft.getInstance().player != (Object)this) return;
if (!(this.getVehicle() instanceof PokemonEntity pokemonEntity)) return;
CobblemonNetwork.INSTANCE.sendToServer(new ServerboundUpdateDriverInputPacket(this.xxa, this.zza, this.jumping, this.isShiftKeyDown()));
// 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
@@ -123,6 +147,33 @@ 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();
@@ -9,20 +9,16 @@
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;
@@ -35,9 +31,9 @@ public abstract class PlayerRendererMixin extends LivingEntityRenderer<AbstractC
super(context, model, shadowRadius);
}
@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);
@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);
}
@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"))
@@ -55,17 +51,4 @@ public abstract class PlayerRendererMixin extends LivingEntityRenderer<AbstractC
playerModel.crouching = false;
}
}
@Inject(
method = "setupRotations(Lnet/minecraft/client/player/AbstractClientPlayer;Lcom/mojang/blaze3d/vertex/PoseStack;FFFF)V",
at = @At("HEAD")
)
private void cobblemon$modifyRoll(AbstractClientPlayer player, PoseStack poseStack, float a, float b, float partialTicks, float i, CallbackInfo ci) {
if (player.isPassenger()) {
var vehicle = player.getVehicle();
if (vehicle instanceof PokemonEntity entity) {
MountedPlayerRenderer.INSTANCE.render(player, entity, poseStack, a, b, partialTicks, i);
}
}
}
}
@@ -15,7 +15,6 @@ import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(Entity.class)
@@ -30,19 +29,4 @@ public abstract class EntityMixin {
}
}
@Redirect(
method = "collide(Lnet/minecraft/world/phys/Vec3;)Lnet/minecraft/world/phys/Vec3;",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/world/entity/Entity;onGround()Z"
)
)
private boolean cobblemon$forceOnGroundForStepUp(Entity entity) {
if (entity instanceof PokemonEntity pokemon && entity.hasControllingPassenger()) {
return true;
}
return entity.onGround();
}
}
@@ -89,6 +89,7 @@ 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,7 +8,6 @@
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
@@ -88,35 +87,23 @@ object CobblemonBlockEntities : PlatformRegistry<Registry<BlockEntityType<*>>, R
BlockEntityType.Builder.of(::DisplayCaseBlockEntity, CobblemonBlocks.DISPLAY_CASE).build(null)
)
@JvmField
val INCENSE_SWEET: BlockEntityType<SweetIncenseBlockEntity> = this.create("incense_sweet",
BlockEntityType.Builder.of(::SweetIncenseBlockEntity, CobblemonBlocks.INCENSE_SWEET).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 CAMPFIRE: BlockEntityType<CampfireBlockEntity> = this.create("campfire_pot",
BlockEntityType.Builder.of(::CampfireBlockEntity, CobblemonBlocks.CAMPFIRE).build(null)
BlockEntityType.Builder.of(::CampfireBlockEntity,
CobblemonBlocks.CAMPFIRE,
CobblemonBlocks.SOUL_CAMPFIRE
).build(null)
)
/*@JvmField
val LURE_CAKE: BlockEntityType<LureCakeBlockEntity> =
BlockEntityType.Builder.of({ pos, state -> LureCakeBlockEntity(pos, state) },
CobblemonBlocks.LURE_CAKE
).build(null)*/
@JvmField
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)
val POKE_SNACK: BlockEntityType<PokeSnackBlockEntity> = this.create(
"poke_snack",
BlockEntityType.Builder.of(::PokeSnackBlockEntity, CobblemonBlocks.POKE_SNACK, CobblemonBlocks.POKE_CAKE).build(null)
)
}
@@ -40,6 +40,7 @@ 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
@@ -230,8 +231,10 @@ 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)))
@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)))
// 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 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
@@ -240,11 +243,9 @@ 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 LURE_CAKE = this.create("lure_cake", LureCakeBlock(BlockBehaviour.Properties.of().forceSolidOn().strength(0.5F).sound(SoundType.WOOL).pushReaction(PushReaction.DESTROY)))
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))
@JvmField
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 }))
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))
@JvmField
val TUMBLESTONE_CLUSTER = tumblestoneBlock("tumblestone_cluster", GrowableStoneBlock.STAGE_3, 7, 3, null)
@@ -449,7 +450,10 @@ 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 { if ((it.getValue(CampfireBlock.SOUL) as Boolean)) 9 else 14 }))
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))
@JvmField
val RELIC_COIN_POUCH = create("relic_coin_pouch", CoinPouchBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.RELIC_COIN_POUCH_SOUNDS).pushReaction(PushReaction.DESTROY).noOcclusion(), true))
@@ -492,8 +496,9 @@ 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()))
@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)))
// 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 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 }))
@@ -517,7 +522,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().sound(CobblemonSounds.VIVICHOKE_SOUNDS)))
val HEARTY_GRAINS = this.create("hearty_grains", HeartyGrainsBlock(BlockBehaviour.Properties.of().pushReaction(PushReaction.DESTROY).ignitedByLava().mapColor(MapColor.PLANT).noCollission().randomTicks().instabreak()))
@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)))
@@ -540,11 +545,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(SoundType.GRASS)))
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)))
@JvmField
val TATAMI_BLOCK = this.create("tatami_block", HorizontalRotationalBlock(BlockBehaviour.Properties.of().ignitedByLava().mapColor(MapColor.PLANT).strength(0.1F).sound(SoundType.MOSS_CARPET)))
val TATAMI_BLOCK = this.create("tatami_block", HorizontalRotationalBlock(BlockBehaviour.Properties.of().ignitedByLava().mapColor(MapColor.PLANT).strength(0.1F).sound(CobblemonSounds.TATAMI_BLOCK_SOUNDS)))
@JvmField
val TATAMI_MAT = this.create("tatami_mat", HorizontalRotationCarpetBlock(BlockBehaviour.Properties.of().ignitedByLava().mapColor(MapColor.PLANT).strength(0.1F).sound(SoundType.MOSS_CARPET)))
val TATAMI_MAT = this.create("tatami_mat", HorizontalRotationCarpetBlock(BlockBehaviour.Properties.of().ignitedByLava().mapColor(MapColor.PLANT).strength(0.1F).sound(CobblemonSounds.TATAMI_MAT_SOUNDS)))
/**
* Returns a map of all the blocks that can be stripped with an axe in the format of input - output.
@@ -59,6 +59,7 @@ object CobblemonCommands {
ApplyPlayerTextureCommand.register(dispatcher)
RenameBoxCommand.register(dispatcher)
ChangeBoxWallpaperCommand.register(dispatcher)
RunMolangCommand.register(dispatcher)
RunMolangScriptCommand.register(dispatcher)
SpectateBattleCommand.register(dispatcher)
CobblemonConfigCommand.register(dispatcher)
@@ -70,6 +71,7 @@ object CobblemonCommands {
MarkGiveAllCommand.register(dispatcher)
PcSearchCommand.register(dispatcher)
PcTakeCommand.register(dispatcher)
ValidateTeamCommand.register(dispatcher)
// Possibly lock down registration if and only if under dev environment or running in an environment
// with a certain system environment variable set
@@ -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)
// @JvmField
// val BUGWORT = compostableBlockItem("bugwort", CobblemonBlocks.BUGWORT) // TODO after 1.7
@JvmField
val POKE_BAIT = noSettingsItem("poke_bait")
@JvmField
val LURE_CAKE = blockItem("lure_cake", CobblemonBlocks.LURE_CAKE)
val POKE_CAKE = this.create("poke_cake", BlockItem(CobblemonBlocks.POKE_CAKE, Item.Properties().stacksTo(1)))
@JvmField
val POKE_CAKE = blockItem("poke_cake", CobblemonBlocks.POKE_CAKE)
val POKE_SNACK = this.create("poke_snack", BlockItem(CobblemonBlocks.POKE_SNACK, Item.Properties().stacksTo(16)))
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", 16, 7, 0.343f, false)
val PEWTER_CRUNCHIES = regionalFoodItem("pewter_crunchies", 64, 7, 0.343f, false)
@JvmField
val RAGE_CANDY_BAR = regionalFoodItem("rage_candy_bar", 16, 8, 0.2f, false)
val RAGE_CANDY_BAR = regionalFoodItem("rage_candy_bar", 64, 8, 0.2f, false)
@JvmField
val LAVA_COOKIE = regionalFoodItem("lava_cookie", 16, 7, 0.343f, false)
val LAVA_COOKIE = regionalFoodItem("lava_cookie", 64, 2, 0.3f, false)
@JvmField
val OLD_GATEAU = regionalFoodItem("old_gateau", 16, 9, 0.1335f, false)
val OLD_GATEAU = regionalFoodItem("old_gateau", 64, 9, 0.1335f, false)
@JvmField
val CASTELIACONE = regionalFoodItem("casteliacone", 16, 5, .28f, false)
val CASTELIACONE = regionalFoodItem("casteliacone", 64, 5, .28f, false)
@JvmField
val LUMIOSE_GALETTE = regionalFoodItem("lumiose_galette", 16, 8, 0.2f, false)
val LUMIOSE_GALETTE = regionalFoodItem("lumiose_galette", 64, 4, 0.4f, false)
@JvmField
val BIG_MALASADA = regionalFoodItem("big_malasada", 16, 7, 0.343f, false)
val BIG_MALASADA = regionalFoodItem("big_malasada", 64, 7, 0.343f, false)
@JvmField
val SMOKED_TAIL_CURRY = regionalFoodItem("smoked_tail_curry", 16, 10, 0.6f, false, ItemStack(Items.BOWL, 1))
val SMOKED_TAIL_CURRY = regionalFoodItem("smoked_tail_curry", 64, 10, 0.6f, false, ItemStack(Items.BOWL, 1))
@JvmField
val JUBILIFE_MUFFIN = regionalFoodItem("jubilife_muffin", 16, 8, 0.2f, false)
val JUBILIFE_MUFFIN = regionalFoodItem("jubilife_muffin", 64, 8, 0.2f, false)
@JvmField
val OPEN_FACED_SANDWICH = regionalFoodItem("open_faced_sandwich", 16, 13, 0.5f, false)
val OPEN_FACED_SANDWICH = regionalFoodItem("open_faced_sandwich", 64, 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(16)
val POTATO_MOCHI = create("potato_mochi", CobblemonItem(Properties().stacksTo(64)
.food(FoodProperties.Builder()
.nutrition(8)
.saturationModifier(0.2375F)
.nutrition(4)
.saturationModifier(0.4F)
.build())))
@JvmField
val CANDIED_APPLE = create("candied_apple", CobblemonItem(Properties().stacksTo(16)
val CANDIED_APPLE = create("candied_apple", CobblemonItem(Properties().stacksTo(64)
.food(FoodProperties.Builder()
.nutrition(6)
.saturationModifier(0.2335F)
.usingConvertsTo(Items.STICK)
.build())))
@JvmField
val CANDIED_BERRY = create("candied_berry", CobblemonItem(Properties().stacksTo(16)
val CANDIED_BERRY = create("candied_berry", CobblemonItem(Properties().stacksTo(64)
.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)
// @JvmField
// val INCENSE_SWEET = blockItem("incense_sweet", CobblemonBlocks.INCENSE_SWEET) // TODO after 1.7
// 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 = noSettingsItem("moomoo_milk")
val MOOMOO_MILK = create("moomoo_milk", CobblemonItem(Item.Properties().craftRemainder(Items.GLASS_BOTTLE)))
@JvmField
val POTION = create("potion", PotionItem(PotionType.POTION))
@@ -23,6 +23,7 @@ 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
@@ -78,6 +79,7 @@ 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
@@ -409,6 +411,7 @@ 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))
@@ -26,11 +26,12 @@ object CobblemonPoiTypes: PlatformRegistry<Registry<PoiType>, ResourceKey<Regist
@JvmField
val NURSE_KEY = createKey("nurse", CobblemonBlocks.HEALING_MACHINE, 1, 1)
@JvmField
val LURE_CAKE_KEY = createKey("lure_cake", CobblemonBlocks.LURE_CAKE, 0, 1)
val POKE_SNACK_KEY = createKey("poke_snack", CobblemonBlocks.POKE_SNACK, 0, 1)
@JvmField
val SACCHARINE_LOG_SLATHERED_KEY = createKey("saccharine_log_slathered", CobblemonBlocks.SACCHARINE_LOG_SLATHERED, 0, 1)
@JvmField
val INCENSE_SWEET_KEY = createKey("incense_sweet", CobblemonBlocks.INCENSE_SWEET, 0, 1)
// TODO after 1.7
// @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,6 +204,19 @@ 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
@@ -230,6 +243,24 @@ 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
@@ -324,8 +355,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,
@@ -343,6 +374,7 @@ 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,
@@ -352,6 +384,23 @@ 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,
@@ -398,6 +447,33 @@ 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,
@@ -437,7 +513,6 @@ 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,
@@ -448,7 +523,7 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, ResourceKey<Regi
)
@JvmField
val CAMPFIRE_POT_PLACE_CAMPFIRE = this.create("block.campfire_pot.place_campfire")
val CAMPFIRE_POT_SET = this.create("block.campfire_pot.set")
@JvmField
val CAMPFIRE_POT_RETRIEVE = this.create("block.campfire_pot.retrieve")
@JvmField
@@ -456,12 +531,14 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, ResourceKey<Regi
@JvmField
val CAMPFIRE_POT_CLOSE = this.create("block.campfire_pot.close")
@JvmField
val CAMPFIRE_POT_USE = this.create("block.campfire_pot.use")
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")
@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,9 +15,12 @@ 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
@@ -31,15 +34,31 @@ 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()
val abilitiesJson = ShowdownService.service.getAbilityIds()
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")
for (i in 0 until abilitiesJson.size()) {
val id = abilitiesJson[i].asString
val jsonAbility = abilitiesJson[i].asJsonObject
val id = jsonAbility.get("id").asString
val ability = AbilityTemplate(id)
this.register(ability)
}
@@ -95,6 +95,7 @@ class Berry(
val fruitModelIdentifier: ResourceLocation,
val fruitTexture: ResourceLocation,
val stageOnePositioning: GrowthPoint,
val pokeSnackPositionings: Array<GrowthPoint>,
val weight: Float,
val boneMealChance: Float
) {
@@ -293,6 +294,14 @@ 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)
}
@@ -360,9 +369,12 @@ class Berry(
val flowerTexture = buffer.readIdentifier()
val fruitModelIdentifier = buffer.readIdentifier()
val fruitTexture = buffer.readIdentifier()
val stageOneYPos = GrowthPoint(Vec3(buffer.readDouble(), buffer.readDouble(), buffer.readDouble()), Vec3(buffer.readDouble(), buffer.readDouble(), buffer.readDouble()))
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 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, stageOneYPos, 0F, boneMealChance)
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)
}
}
@@ -12,6 +12,7 @@ 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
@@ -38,6 +39,7 @@ 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
@@ -156,6 +158,17 @@ 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)
@@ -168,6 +181,20 @@ object MoLangFunctions {
val replace = params.getString(2)
return@Function StringValue(text.replace(search, replace))
},
"is_included" to java.util.function.Function { params ->
val text = params.getString(0)
val search = params.getString(1)
return@Function DoubleValue(text.contains(search))
},
"to_lower" to java.util.function.Function { params ->
return@Function StringValue(params.getString(0).lowercase())
},
"to_upper" to java.util.function.Function { params ->
return@Function StringValue(params.getString(0).uppercase())
},
"string_length" to java.util.function.Function { params ->
return@Function DoubleValue(params.getString(0).length)
},
"is_blank" to java.util.function.Function { params ->
val arg = params.get<MoValue>(0)
return@Function DoubleValue((arg is StringValue && (arg.value.isBlank() || arg.value.toDoubleOrNull() == 0.0)) || (arg is DoubleValue && arg.value == 0.0))
@@ -693,6 +720,9 @@ 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 ->
@@ -837,14 +867,12 @@ object MoLangFunctions {
}
map.put("is_standing_on_blocks") { params ->
val depth = params.getDouble(0).toInt()
val blocks: MutableSet<Block> = mutableSetOf()
val blockStrings: MutableSet<String> = mutableSetOf()
for (blockIndex in 1..<params.params.size) {
val blockString = params.getString(blockIndex)
val block = BuiltInRegistries.BLOCK.get(blockString.asIdentifierDefaultingNamespace("minecraft"))
blocks.add(block)
blockStrings.add(params.getString(blockIndex))
}
return@put if (entity.isStandingOn(blocks, depth)) DoubleValue.ONE else DoubleValue.ZERO
return@put if (entity.isStandingOn(blockStrings, depth)) DoubleValue.ONE else DoubleValue.ZERO
}
if (entity is PosableEntity) {
map.put("play_animation") { params ->
@@ -870,6 +898,10 @@ object MoLangFunctions {
return@put DoubleValue.ONE
}
}
map.put("face") { params -> ObjectValue(ReferenceDialogueFaceProvider(entity.id, params.getBooleanOrNull(0) != false))
}
}
if (entity is Schedulable) {
map.put("run_molang_after") { params ->
@@ -1039,7 +1071,6 @@ object MoLangFunctions {
map.put("name") { StringValue(npc.name.string) }
map.put("level") { DoubleValue(npc.level) }
map.put("has_aspect") { params -> DoubleValue(npc.aspects.contains(params.getString(0))) }
map.put("face") { params -> ObjectValue(ReferenceDialogueFaceProvider(npc.id, params.getBooleanOrNull(0) != false)) }
map.put("in_battle") { DoubleValue(npc.isInBattle()) }
map.put("battles") { ArrayStruct(npc.battleIds.mapNotNull { BattleRegistry.getBattle(it)?.struct }.mapIndexed { index, value -> "$index" to value }.toMap()) }
map.put("stop_battles") { _ -> npc.battleIds.forEach { BattleRegistry.getBattle(it)?.stop() } }
@@ -1664,6 +1695,34 @@ object MoLangFunctions {
map.put("pasture_conflict_enabled") {
DoubleValue(pokemonEntity.getBehaviourFlag(PokemonBehaviourFlag.PASTURE_CONFLICT))
}
map.put("run_action_effect") { params ->
val runtime = MoLangRuntime().setup()
runtime.environment.cloneFrom(params.environment)
runtime.withQueryValue("entity", pokemonEntity.struct)
val actionEffect = ActionEffects.actionEffects[params.getString(0).asIdentifierDefaultingNamespace()]
if (actionEffect != null) {
val context = ActionEffectContext(
actionEffect = actionEffect,
providers = mutableListOf(TargetsProvider(pokemonEntity)),
runtime = runtime,
level = pokemonEntity.level()
)
// This was taken from NPC run_action_effect but this variable isn't used, so I just commented it out.
// pokemonEntity.actionEffect = context
pokemonEntity.brain.setMemory(CobblemonMemories.ACTIVE_ACTION_EFFECT, context)
pokemonEntity.brain.setActiveActivityIfPossible(CobblemonActivities.ACTION_EFFECT)
actionEffect.run(context).thenRun {
val pokemonActionEffect = pokemonEntity.brain.getMemory(CobblemonMemories.ACTIVE_ACTION_EFFECT).orElse(null)
if (pokemonActionEffect == context && pokemonEntity.brain.isActive(CobblemonActivities.ACTION_EFFECT)) {
pokemonEntity.brain.eraseMemory(CobblemonMemories.ACTIVE_ACTION_EFFECT)
//pokemonEntity.actionEffect = null
}
}
return@put DoubleValue.ONE
}
return@put DoubleValue.ZERO
}
map
}
)
@@ -21,9 +21,12 @@ 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
@@ -36,11 +39,26 @@ 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()
val movesJson = ShowdownService.service.getMoves()
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")
for (i in 0 until movesJson.size()) {
val jsMove = movesJson[i].asJsonObject
val id = jsMove.get("id").asString
@@ -10,14 +10,12 @@ package com.cobblemon.mod.common.api.orientation
import com.cobblemon.mod.common.util.math.geometry.toDegrees
import com.cobblemon.mod.common.util.math.geometry.toRadians
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.Mth
import net.minecraft.world.entity.LivingEntity
import org.joml.Matrix3f
import org.joml.Quaternionf
import org.joml.Vector3f
import kotlin.math.asin
import kotlin.math.sign
open class OrientationController(val entity: LivingEntity) {
@@ -132,6 +132,8 @@ object CobblemonPermissions {
@JvmStatic
val ABANDON_MULTITEAM = this.create("${COMMAND_PREFIX}abandonmultiteam", PermissionLevel.NONE)
@JvmStatic
val RUN_MOLANG = this.create("${COMMAND_PREFIX}runmolang", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
@JvmStatic
val RUN_MOLANG_SCRIPT = this.create("${COMMAND_PREFIX}runmolangscript", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
@JvmStatic
val COBBLEMON_CONFIG_RELOAD = this.create("${COMMAND_PREFIX}cobblemonconfig.reload", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
@@ -141,6 +143,8 @@ object CobblemonPermissions {
val CHANGE_BOX_COUNT = this.create("${COMMAND_PREFIX}boxcount", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
@JvmStatic
val SPECTATE_BATTLE = this.create("${COMMAND_PREFIX}spectatebattle", PermissionLevel.ALL_COMMANDS)
@JvmStatic
val VALIDATE_TEAM = this.create("${COMMAND_PREFIX}validateteam", PermissionLevel.ALL_COMMANDS)
@JvmStatic
val SEE_HIDDEN_NPCS = this.create("seehiddennpcs", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
@@ -42,6 +42,7 @@ import com.cobblemon.mod.common.api.riding.stats.RidingStatDefinition
import com.cobblemon.mod.common.api.spawning.TimeRange
import com.cobblemon.mod.common.api.types.ElementalType
import com.cobblemon.mod.common.api.types.adapters.ElementalTypeAdapter
import com.cobblemon.mod.common.battles.runner.ShowdownService
import com.cobblemon.mod.common.net.messages.client.data.SpeciesRegistrySyncPacket
import com.cobblemon.mod.common.pokemon.FormData
import com.cobblemon.mod.common.pokemon.Species
@@ -76,6 +77,7 @@ 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> {
@@ -123,6 +125,7 @@ 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)
@@ -157,7 +160,8 @@ object PokemonSpecies : JsonDataRegistry<Species> {
}
this.species.forEach(Species::resolveEvolutionMoves)
Cobblemon.showdownThread.queue {
it.registerSpecies()
it.resetRegistryData("species")
it.sendRegistryData(allShowdownSpecies(), "species")
it.indicateSpeciesInitialized()
// Reload this with the mod
CobblemonHeldItemManager.load()
@@ -312,4 +316,18 @@ 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,8 +8,11 @@
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
@@ -18,7 +21,12 @@ 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)
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),
)
sealed class ParticleData {
@@ -18,11 +18,4 @@ import net.minecraft.world.entity.PlayerRideableJumping
*
* @since 1.7.0
*/
interface Rideable : PlayerRideableJumping {
/**
* Denotes the manager responsible for handling any instance of riding taking place on an entity
*/
val riding: RidingBehaviour<RidingBehaviourSettings, RidingBehaviourState>?
}
interface Rideable : PlayerRideableJumping
@@ -18,58 +18,41 @@ 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 behaviour: RidingBehaviourSettings? = null
val behaviours: Map<RidingStyle, 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 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
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
}
)
}
return RidingProperties(stats = stats, seats = seats, conditions = conditions, behaviour = behaviour)
return RidingProperties(seats = seats, conditions = conditions, behaviours = behaviours)
}
}
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(behaviour) { _, behaviour ->
behaviour.encode(buffer)
buffer.writeNullable(behaviours) { _, v ->
buffer.writeMap(
v,
{ _, style -> buffer.writeEnumConstant(style) },
{ _, 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 shouldRotatePlayerHead(settings: Settings, state: State, vehicle: PokemonEntity): Boolean
fun shouldRotateRiderHead(settings: Settings, state: State, vehicle: PokemonEntity): Boolean
fun getRideSounds(settings: Settings, state: State, vehicle: PokemonEntity): RideSoundSettingsList
@@ -101,4 +101,19 @@ 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,6 +10,8 @@ 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
@@ -23,4 +25,13 @@ 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)
}
@@ -41,7 +41,7 @@ object RidingBehaviours {
fun register(key: ResourceLocation, behaviour: RidingBehaviour<out RidingBehaviourSettings, out RidingBehaviourState>) {
if (behaviours.contains(key)) error("Behaviour already registered to key $key")
behaviours[key] = RidingController(behaviour) as RidingBehaviour<RidingBehaviourSettings, RidingBehaviourState>
behaviours[key] = behaviour as RidingBehaviour<RidingBehaviourSettings, RidingBehaviourState>
}
fun get(key: ResourceLocation): RidingBehaviour<RidingBehaviourSettings, RidingBehaviourState> {
@@ -8,206 +8,119 @@
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.util.SmoothDouble
import net.minecraft.tags.FluidTags
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
import kotlin.collections.get
/**
* Small wrapper around a RidingBehaviour to provide sane defaults in the event that the behaviour is not active.
*
* @author landonjw
*/
class RidingController<Settings : RidingBehaviourSettings, State : RidingBehaviourState>(val behaviour: RidingBehaviour<Settings, State>) : RidingBehaviour<Settings, State> {
class RidingController(
val entity: PokemonEntity,
val behaviours: Map<RidingStyle, RidingBehaviourSettings>) {
override val key = behaviour.key
var lastTransitionAge: Int = 0
private set
override fun getRidingStyle(settings: Settings, state: State): RidingStyle {
return behaviour.getRidingStyle(settings, state)
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 isActive(settings: Settings, state: State, vehicle: PokemonEntity) =
behaviour.isActive(settings, state, vehicle)
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 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)
if (context == null && !this.entity.level().isClientSide) {
entity.ejectPassengers()
}
}
override fun updatePassengerRotation(
settings: Settings,
state: State,
vehicle: PokemonEntity,
driver: LivingEntity
) {
if (!isActive(settings, state, vehicle)) return
return behaviour.updatePassengerRotation(settings, state, vehicle, driver)
fun getBehaviour(): RidingBehaviour<RidingBehaviourSettings, RidingBehaviourState>? {
if (context == null) return null
return RidingBehaviours.get(context!!.behaviour)
}
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 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 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 canTransitionToLand(): Boolean {
if (entity.isInLiquid || entity.isUnderWater) return false
return entity.onGround()
}
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 canTransitionToLiquid(): Boolean {
return 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)
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 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,7 +25,6 @@ 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
@@ -244,7 +243,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(normalizeVal(state.rideVelocity.get().length(), 0.0, topSpeed)), rotMin)
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())))
@@ -300,7 +299,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(normalizeVal(state.rideVelocity.get().length(), 0.0, topSpeed)), rotMin)
val rotLimit = max(90 * sqrt(RidingBehaviour.scaleToRange(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
@@ -316,7 +315,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(normalizeVal(state.rideVelocity.get().length(), 0.0, topSpeed)))
val yawForce = xInput * ( 1.0 - sqrt(RidingBehaviour.scaleToRange(state.rideVelocity.get().length(), 0.0, topSpeed)))
//Yaw locally a bit when up or down so that its more intuitive to make it out of a dive or a straight vertical
//climb
@@ -344,16 +343,6 @@ 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,
@@ -401,7 +390,7 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
val glideTopSpeed = vehicle.runtime.resolveDouble(settings.glidespeedExpr)
//Must I ensure that topspeed is greater than minimum?
val normalizedGlideSpeed = normalizeVal(state.rideVelocity.get().length(), topSpeed, glideTopSpeed)
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
@@ -451,7 +440,7 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
return false
}
override fun shouldRotatePlayerHead(
override fun shouldRotateRiderHead(
settings: BirdSettings,
state: BirdState,
vehicle: PokemonEntity
@@ -472,6 +461,7 @@ 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
@@ -493,6 +483,7 @@ class BirdSettings : RidingBehaviourSettings {
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeRidingStats(stats)
rideSounds.encode(buffer)
buffer.writeExpression(infiniteAltitude)
buffer.writeExpression(infiniteStamina)
@@ -505,6 +496,7 @@ class BirdSettings : RidingBehaviourSettings {
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
stats.putAll(buffer.readRidingStats())
rideSounds = RideSoundSettingsList.decode(buffer)
infiniteAltitude = buffer.readExpression()
infiniteStamina = buffer.readExpression()
@@ -17,6 +17,7 @@ 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.*
@@ -228,7 +229,7 @@ class BirdNoRollBehaviour : RidingBehaviour<BirdNoRollSettings, BirdNoRollState>
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
//This rotation limit will be used to know if the ride has exceeded it and its needs correcting.
val rotMin = 15.0
val rotLimit = max(handling * sqrt(normalizeVal(state.rideVelocity.get().length(), 0.0, topSpeed)), rotMin)
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())))
@@ -282,7 +283,7 @@ class BirdNoRollBehaviour : RidingBehaviour<BirdNoRollSettings, BirdNoRollState>
val yInput = mouseYSmoother.getNewDeltaValue(mouseYc * 0.1 * invertPitch, deltaTime * smoothingSpeed);
//Give the ability to yaw with x mouse input when at low speeds.
val yawForce = xInput * ( 1.0 - min(sqrt(normalizeVal(state.rideVelocity.get().length(), 0.0, topSpeed)), 0.5))
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.
@@ -302,16 +303,6 @@ class BirdNoRollBehaviour : RidingBehaviour<BirdNoRollSettings, BirdNoRollState>
return Vec3.ZERO
}
/*
* Normalizes the given value between a min and a max.
* The result is clamped between 0.0 and 1.0, where 0.0 represents x is at or below min
* and 1.0 represents x is at or above it.
*/
private fun normalizeVal(x: Double, min: Double, max: Double): Double {
require(max > min) { "max must be greater than min" }
return ((x - min) / (max - min)).coerceIn(0.0, 1.0)
}
override fun canJump(
settings: BirdNoRollSettings,
state: BirdNoRollState,
@@ -359,7 +350,7 @@ class BirdNoRollBehaviour : RidingBehaviour<BirdNoRollSettings, BirdNoRollState>
val glideTopSpeed = vehicle.runtime.resolveDouble(settings.glidespeedExpr)
//Must I ensure that topspeed is greater than minimum?
val normalizedGlideSpeed = normalizeVal(state.rideVelocity.get().length(), topSpeed, glideTopSpeed)
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
@@ -409,7 +400,7 @@ class BirdNoRollBehaviour : RidingBehaviour<BirdNoRollSettings, BirdNoRollState>
return false
}
override fun shouldRotatePlayerHead(
override fun shouldRotateRiderHead(
settings: BirdNoRollSettings,
state: BirdNoRollState,
vehicle: PokemonEntity
@@ -430,6 +421,7 @@ class BirdNoRollBehaviour : RidingBehaviour<BirdNoRollSettings, BirdNoRollState>
class BirdNoRollSettings : RidingBehaviourSettings {
override val key = BirdNoRollBehaviour.KEY
override val stats = mutableMapOf<RidingStat, IntRange>()
var infiniteAltitude: Expression = "false".asExpression()
private set
@@ -451,6 +443,7 @@ class BirdNoRollSettings : RidingBehaviourSettings {
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeRidingStats(stats)
rideSounds.encode(buffer)
buffer.writeExpression(infiniteAltitude)
buffer.writeExpression(infiniteStamina)
@@ -463,6 +456,7 @@ class BirdNoRollSettings : RidingBehaviourSettings {
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
stats.putAll(buffer.readRidingStats())
rideSounds = RideSoundSettingsList.decode(buffer)
infiniteAltitude = buffer.readExpression()
infiniteStamina = buffer.readExpression()
@@ -75,8 +75,7 @@ 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 statSpeed = vehicle.rideProp.calculate(RidingStat.SPEED, RidingStyle.AIR, 0 )
val speedStat = settings.calculate(RidingStat.SPEED, 0)
return Vec3(xVector, yVector, zVector)
}
@@ -184,7 +183,7 @@ class GliderBehaviour : RidingBehaviour<GliderSettings, RidingBehaviourState> {
return false
}
override fun shouldRotatePlayerHead(settings: GliderSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Boolean {
override fun shouldRotateRiderHead(settings: GliderSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Boolean {
return false
}
@@ -197,6 +196,7 @@ 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,6 +211,7 @@ class GliderSettings : RidingBehaviourSettings {
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeRidingStats(stats)
rideSounds.encode(buffer)
buffer.writeExpression(glideSpeed)
buffer.writeExpression(speed)
@@ -218,6 +219,7 @@ class GliderSettings : RidingBehaviourSettings {
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
stats.putAll(buffer.readRidingStats())
rideSounds = RideSoundSettingsList.decode(buffer)
glideSpeed = buffer.readExpression()
speed = buffer.readExpression()
@@ -12,12 +12,13 @@ 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.*
@@ -269,7 +270,11 @@ class HelicopterBehaviour : RidingBehaviour<HelicopterSettings, RidingBehaviourS
return false
}
override fun shouldRotatePlayerHead(settings: HelicopterSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Boolean {
override fun shouldRotateRiderHead(
settings: HelicopterSettings,
state: RidingBehaviourState,
vehicle: PokemonEntity
): Boolean {
return false
}
@@ -283,6 +288,7 @@ 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
@@ -300,6 +306,7 @@ class HelicopterSettings : RidingBehaviourSettings {
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeRidingStats(stats)
rideSounds.encode(buffer)
buffer.writeExpression(gravity)
buffer.writeExpression(horizontalAcceleration)
@@ -308,6 +315,7 @@ 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*normalizeVal(state.rideVelocity.get().length(), 0.0, topSpeed).toFloat()
val normSpeed = 1.0f - 0.5f*RidingBehaviour.scaleToRange(state.rideVelocity.get().length(), 0.0, topSpeed).toFloat()
//driver.yRot += (entity.riding.deltaRotation.y - turnAmount)
//driver.setYHeadRot(driver.yHeadRot + (entity.riding.deltaRotation.y) - turnAmount)
@@ -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*normalizeVal(state.rideVelocityocity.length(), 0.0, topSpeed).toFloat()
val normSpeed = 1.0f // = 1.0f - 0.5f*RidingBehaviour.scaleToRange(state.rideVelocityocity.length(), 0.0, topSpeed).toFloat()
val turnRate = (handling.toFloat() / 20.0f)
@@ -308,15 +308,6 @@ 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,
@@ -452,7 +443,7 @@ class HoverBehaviour : RidingBehaviour<HoverSettings, HoverState> {
return false
}
override fun shouldRotatePlayerHead(
override fun shouldRotateRiderHead(
settings: HoverSettings,
state: HoverState,
vehicle: PokemonEntity
@@ -473,6 +464,7 @@ 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()
@@ -504,6 +496,7 @@ class HoverSettings : RidingBehaviourSettings {
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeRidingStats(stats)
rideSounds.encode(buffer)
buffer.writeExpression(speedExpr)
buffer.writeExpression(accelerationExpr)
@@ -513,6 +506,7 @@ 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 = (normalizeSpeed(state.rideVelocity.get().length(), minSpeed, topSpeed))
// var staminaRate = (RidingBehaviour.scaleToRange(state.rideVelocity.get().length(), minSpeed, topSpeed))
//
// //interpolate between 0.25 and 1.0 so that you always have at least a min of 0.25 stam loss
// staminaRate = 0.25 + (0.75 * staminaRate.pow(3))
@@ -95,16 +95,6 @@ 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,
@@ -160,7 +150,7 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
//speed up and slow down based on input
if (driver.zza > 0.0 && speed < topSpeed && state.stamina.get() > 0.0f) {
//modify acceleration to be slower when at closer speeds to top speed
val accelMod = max(-(normalizeSpeed(speed, minSpeed, topSpeed)) + 1, 0.0)
val accelMod = max(-(RidingBehaviour.scaleToRange(speed, minSpeed, topSpeed)) + 1, 0.0)
state.rideVelocity.set(
Vec3(
state.rideVelocity.get().x,
@@ -178,7 +168,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((normalizeSpeed(speed, minSpeed, topSpeed) - 1).pow(2) * 4, 0.1)
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(
@@ -324,7 +314,7 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
val minSpeed = vehicle.runtime.resolveDouble(settings.minSpeed)
//Must I ensure that topspeed is greater than minimum?
val normalizedSpeed = normalizeSpeed(state.rideVelocity.get().length(), minSpeed, topSpeed)
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.
@@ -368,7 +358,7 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
return false
}
override fun shouldRotatePlayerHead(
override fun shouldRotateRiderHead(
settings: JetSettings,
state: JetState,
vehicle: PokemonEntity
@@ -399,6 +389,7 @@ 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
@@ -429,6 +420,7 @@ class JetSettings : RidingBehaviourSettings {
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeRidingStats(stats)
rideSounds.encode(buffer)
buffer.writeExpression(gravity)
buffer.writeExpression(minSpeed)
@@ -442,6 +434,7 @@ class JetSettings : RidingBehaviourSettings {
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
stats.putAll(buffer.readRidingStats())
rideSounds = RideSoundSettingsList.decode(buffer)
gravity = buffer.readExpression()
minSpeed = buffer.readExpression()
@@ -23,6 +23,7 @@ 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
@@ -79,16 +80,6 @@ class JetNoRollBehaviour : RidingBehaviour<JetNoRollSettings, JetNoRollState> {
return state.rideVelocity.get().length().toFloat()
}
//TODO: Move these functions to a riding util class.
/*
* Normalizes the current speed between minSpeed and maxSpeed.
* The result is clamped between 0.0 and 1.0, where 0.0 represents minSpeed and 1.0 represents maxSpeed.
*/
private fun normalizeSpeed(currSpeed: Double, minSpeed: Double, maxSpeed: Double): Double {
require(maxSpeed > minSpeed) { "maxSpeed must be greater than minSpeed" }
return ((currSpeed - minSpeed) / (maxSpeed - minSpeed)).coerceIn(0.0, 1.0)
}
override fun rotation(
settings: JetNoRollSettings,
state: JetNoRollState,
@@ -145,7 +136,7 @@ class JetNoRollBehaviour : RidingBehaviour<JetNoRollSettings, JetNoRollState> {
//speed up and slow down based on input
if (driver.zza > 0.0 && speed < topSpeed && state.stamina.get() > 0.0f) {
//modify acceleration to be slower when at closer speeds to top speed
val accelMod = max(-(normalizeSpeed(speed, minSpeed, topSpeed)) + 1, 0.0)
val accelMod = max(-(RidingBehaviour.scaleToRange(speed, minSpeed, topSpeed)) + 1, 0.0)
state.rideVelocity.set(
Vec3(
state.rideVelocity.get().x,
@@ -163,7 +154,7 @@ class JetNoRollBehaviour : RidingBehaviour<JetNoRollSettings, JetNoRollState> {
)
} else if (driver.zza < 0.0 && speed > minSpeed) {
//modify deccel to be slower when at closer speeds to minimum speed
val deccelMod = max((normalizeSpeed(speed, minSpeed, topSpeed) - 1).pow(2) * 4, 0.1)
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(
@@ -307,7 +298,7 @@ class JetNoRollBehaviour : RidingBehaviour<JetNoRollSettings, JetNoRollState> {
val minSpeed = vehicle.runtime.resolveDouble(settings.minSpeed)
//Must I ensure that topspeed is greater than minimum?
val normalizedSpeed = normalizeSpeed(state.rideVelocity.get().length(), minSpeed, topSpeed)
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.
@@ -351,7 +342,7 @@ class JetNoRollBehaviour : RidingBehaviour<JetNoRollSettings, JetNoRollState> {
return false
}
override fun shouldRotatePlayerHead(
override fun shouldRotateRiderHead(
settings: JetNoRollSettings,
state: JetNoRollState,
vehicle: PokemonEntity
@@ -382,6 +373,7 @@ class JetNoRollBehaviour : RidingBehaviour<JetNoRollSettings, JetNoRollState> {
class JetNoRollSettings : RidingBehaviourSettings {
override val key = JetNoRollBehaviour.KEY
override val stats = mutableMapOf<RidingStat, IntRange>()
var gravity: Expression = "0".asExpression()
private set
@@ -412,6 +404,7 @@ class JetNoRollSettings : RidingBehaviourSettings {
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeRidingStats(stats)
rideSounds.encode(buffer)
buffer.writeExpression(gravity)
buffer.writeExpression(minSpeed)
@@ -425,6 +418,7 @@ class JetNoRollSettings : RidingBehaviourSettings {
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
stats.putAll(buffer.readRidingStats())
rideSounds = RideSoundSettingsList.decode(buffer)
gravity = buffer.readExpression()
minSpeed = buffer.readExpression()
@@ -15,6 +15,7 @@ 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.*
@@ -284,15 +285,6 @@ class RocketBehaviour : RidingBehaviour<RocketSettings, RocketState> {
return newVelocity
}
/*
* Normalizes the current speed between minSpeed and maxSpeed.
* The result is clamped between 0.0 and 1.0, where 0.0 represents minSpeed and 1.0 represents maxSpeed.
*/
private fun normalizeVal(currSpeed: Double, minSpeed: Double, maxSpeed: Double): Double {
require(maxSpeed > minSpeed) { "maxSpeed must be greater than minSpeed" }
return ((currSpeed - minSpeed) / (maxSpeed - minSpeed)).coerceIn(0.0, 1.0)
}
override fun angRollVel(
settings: RocketSettings,
state: RocketState,
@@ -373,7 +365,7 @@ class RocketBehaviour : RidingBehaviour<RocketSettings, RocketState> {
//val exceededSpeed = max(vehicle.deltaMovement.length() - topSpeed, 0.0)
//TODO: Remove this magic number and get a better comparison for boost top speed
val normalizedBoostSpeed = normalizeVal(state.rideVelocity.get().length(), topSpeed, topSpeed * 3)
val normalizedBoostSpeed = RidingBehaviour.scaleToRange(state.rideVelocity.get().length(), topSpeed, topSpeed * 3)
return 1.0f + normalizedBoostSpeed.pow(2).toFloat() * 0.2f
} else {
return 1.0f
@@ -437,7 +429,7 @@ class RocketBehaviour : RidingBehaviour<RocketSettings, RocketState> {
return false
}
override fun shouldRotatePlayerHead(
override fun shouldRotateRiderHead(
settings: RocketSettings,
state: RocketState,
vehicle: PokemonEntity
@@ -458,6 +450,7 @@ class RocketBehaviour : RidingBehaviour<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
@@ -485,6 +478,7 @@ class RocketSettings : RidingBehaviourSettings {
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeRidingStats(stats)
rideSounds.encode(buffer)
buffer.writeExpression(speedExpr)
buffer.writeExpression(accelerationExpr)
@@ -494,6 +488,7 @@ class RocketSettings : RidingBehaviourSettings {
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
stats.putAll(buffer.readRidingStats())
rideSounds = RideSoundSettingsList.decode(buffer)
speedExpr = buffer.readExpression()
accelerationExpr = buffer.readExpression()
@@ -12,10 +12,13 @@ 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
@@ -107,13 +110,13 @@ class CompositeBehaviour : RidingBehaviour<CompositeSettings, CompositeState> {
}
}
override fun shouldRotatePlayerHead(
override fun shouldRotateRiderHead(
settings: CompositeSettings,
state: CompositeState,
vehicle: PokemonEntity
): Boolean {
return chooseBehaviour(settings, state) { behaviour, behaviourSettings, behaviourState ->
behaviour.shouldRotatePlayerHead(behaviourSettings, behaviourState, vehicle)
behaviour.shouldRotateRiderHead(behaviourSettings, behaviourState, vehicle)
}
}
@@ -380,8 +383,10 @@ 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)
@@ -389,6 +394,7 @@ 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()
@@ -14,6 +14,7 @@ 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.*
@@ -173,7 +174,7 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
val normSpeed = 1.0f
// TurnRate should always be quick if not sprinting
val invRelSpeed = (inverseLerp(vehicle.deltaMovement.horizontalDistance(), 0.0, topSpeed ).pow(0.5) - 1.0f) * -1.0f
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()
//Ensure you only ever rotate as much difference as there is between the angles.
@@ -201,22 +202,17 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
vehicle: PokemonEntity,
driver: Player
): Vec3 {
// Check to see if the ride should be walking or sprinting
val walkSpeed = getWalkSpeed(vehicle)
val canSprint = vehicle.runtime.resolveBoolean(settings.canSprint)
val canJump = vehicle.runtime.resolveBoolean(settings.canJump)
val rideTopSpeed = vehicle.runtime.resolveDouble(settings.speedExpr) * 1.3
val topSpeed = if(state.sprinting.get()) rideTopSpeed else walkSpeed
val walkSpeed = getWalkSpeed(vehicle)
val topSpeed = if(canSprint && state.sprinting.get()) rideTopSpeed else walkSpeed
val accel = vehicle.runtime.resolveDouble(settings.accelerationExpr) * 2
//Flag for determining if player is actively inputting
var activeInput = false
// Gather the previous velocity and set the horizontal fields of the velocity vector to the magnitude of the
// ones from deltaMovement. This is important as when a collision happens the Deltamovement will reflect it
// but rideVelocity won't.
var newVelocity = Vec3(state.rideVelocity.get().x, 0.0, state.rideVelocity.get().z).normalize().scale(vehicle.deltaMovement.horizontalDistance())
newVelocity = newVelocity.add(0.0, state.rideVelocity.get().y, 0.0)
var newVelocity = Vec3(state.rideVelocity.get().x, state.rideVelocity.get().y, state.rideVelocity.get().z).normalize().scale(vehicle.deltaMovement.length())
//speed up and slow down based on input
if (driver.zza != 0.0f && state.stamina.get() > 0.0) {
@@ -239,9 +235,6 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
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.8
val terminalVel = 2.0
@@ -255,18 +248,12 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
}
//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 = 1.0
val velMag = newVelocity.length()
newVelocity = newVelocity.add(0.0, jumpForce, 0.0)
val test = 0.0
//Ensure this doesn't add unwanted forward velocity
//val mag = if(newVelocity.length() < rideTopSpeed) newVelocity.length() else rideTopSpeed
//newVelocity = newVelocity.normalize().scale(mag)
}
//Zero out lateral velocity possibly picked up from a controller transition
@@ -275,15 +262,6 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
return newVelocity
}
/*
* lerps the current val between minVal and maxVal.
* The result is clamped between 0.0 and 1.0, where 0.0 represents minVal and 1.0 represents maxVal.
*/
private fun inverseLerp(currVal: Double, minVal: Double, maxVal: Double): Double {
require(maxVal > minVal) { "minVal must be greater than maxVal" }
return ((currVal - minVal) / (maxVal - minVal)).coerceIn(0.0, 1.0)
}
private fun getWalkSpeed( vehicle: PokemonEntity ): Double {
val walkspeed = vehicle.runtime.resolveDouble(vehicle.behaviour.moving.walk.walkSpeed)
val movementSpeed = vehicle.attributes.getValue(Attributes.MOVEMENT_SPEED)
@@ -431,7 +409,7 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
return false
}
override fun shouldRotatePlayerHead(
override fun shouldRotateRiderHead(
settings: HorseSettings,
state: HorseState,
vehicle: PokemonEntity
@@ -452,10 +430,14 @@ 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
@@ -479,6 +461,7 @@ class HorseSettings : RidingBehaviourSettings {
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeRidingStats(stats)
rideSounds.encode(buffer)
buffer.writeExpression(speedExpr)
buffer.writeExpression(accelerationExpr)
@@ -488,6 +471,7 @@ class HorseSettings : RidingBehaviourSettings {
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
stats.putAll(buffer.readRidingStats())
rideSounds = RideSoundSettingsList.decode(buffer)
speedExpr = buffer.readExpression()
accelerationExpr = buffer.readExpression()
@@ -17,6 +17,7 @@ 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.*
@@ -146,7 +147,7 @@ class MinekartBehaviour : RidingBehaviour<MinekartSettings, MinekartState> {
val turnInput = (driver.xxa *-1.0f) * turningAcceleration
val driftInput = turnInput * 0.25f
val lowSpeedTurnBoost = (inverseLerp(vehicle.deltaMovement.horizontalDistance(), 0.0, topSpeed ).pow(0.5) - 2.0f) * -1.0f
val 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
@@ -292,15 +293,6 @@ class MinekartBehaviour : RidingBehaviour<MinekartSettings, MinekartState> {
return newVelocity
}
/*
* lerps the current val between minVal and maxVal.
* The result is clamped between 0.0 and 1.0, where 0.0 represents minVal and 1.0 represents maxVal.
*/
private fun inverseLerp(currVal: Double, minVal: Double, maxVal: Double): Double {
require(maxVal > minVal) { "minVal must be greater than maxVal" }
return ((currVal - minVal) / (maxVal - minVal)).coerceIn(0.0, 1.0)
}
override fun angRollVel(
settings: MinekartSettings,
state: MinekartState,
@@ -434,7 +426,7 @@ class MinekartBehaviour : RidingBehaviour<MinekartSettings, MinekartState> {
return false
}
override fun shouldRotatePlayerHead(
override fun shouldRotateRiderHead(
settings: MinekartSettings,
state: MinekartState,
vehicle: PokemonEntity
@@ -455,6 +447,7 @@ class MinekartBehaviour : RidingBehaviour<MinekartSettings, MinekartState> {
class MinekartSettings : RidingBehaviourSettings {
override val key = MinekartBehaviour.KEY
override val stats = mutableMapOf<RidingStat, IntRange>()
var canJump = "true".asExpression()
private set
@@ -484,12 +477,14 @@ class MinekartSettings : RidingBehaviourSettings {
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()
@@ -17,6 +17,7 @@ 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.*
@@ -220,9 +221,6 @@ class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
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
@@ -393,7 +391,7 @@ class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
return false
}
override fun shouldRotatePlayerHead(
override fun shouldRotateRiderHead(
settings: VehicleSettings,
state: VehicleState,
vehicle: PokemonEntity
@@ -414,6 +412,7 @@ 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
@@ -456,6 +455,7 @@ class VehicleSettings : RidingBehaviourSettings {
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeRidingStats(stats)
rideSounds.encode(buffer)
buffer.writeExpression(canJump)
buffer.writeExpression(speed)
@@ -466,6 +466,7 @@ class VehicleSettings : RidingBehaviourSettings {
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
stats.putAll(buffer.readRidingStats())
rideSounds = RideSoundSettingsList.decode(buffer)
canJump = buffer.readExpression()
speed = buffer.readExpression()
@@ -330,7 +330,7 @@ class BoatBehaviour : RidingBehaviour<BoatSettings, BoatState> {
return vehicle.runtime.resolveBoolean(settings.rotatePokemonHead)
}
override fun shouldRotatePlayerHead(
override fun shouldRotateRiderHead(
settings: BoatSettings,
state: BoatState,
vehicle: PokemonEntity
@@ -354,6 +354,7 @@ 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()
@@ -364,11 +365,13 @@ 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,10 +15,13 @@ 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
@@ -215,7 +218,7 @@ class BurstBehaviour : RidingBehaviour<BurstSettings, BurstState> {
return false
}
override fun shouldRotatePlayerHead(
override fun shouldRotateRiderHead(
settings: BurstSettings,
state: BurstState,
vehicle: PokemonEntity
@@ -236,6 +239,7 @@ class BurstBehaviour : RidingBehaviour<BurstSettings, BurstState> {
class BurstSettings : RidingBehaviourSettings {
override val key = BurstBehaviour.KEY
override val stats = mutableMapOf<RidingStat, IntRange>()
var dashSpeed = 1F
private set
@@ -244,11 +248,13 @@ 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()
}
@@ -17,6 +17,7 @@ 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.*
@@ -194,7 +195,7 @@ class DolphinBehaviour : RidingBehaviour<DolphinSettings, DolphinState> {
// Yaw
// TODO:move all magic numbers to handling expression
val yawSpeed = handling * deltaTime * 0.25
val yawForce = sin(controller.roll.toRadians()) * yawSpeed * ( 1.0 - min(sqrt(normalizeVal(state.rideVelocity.get().length(), 0.0, topSpeed)), 0.5))
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())
@@ -210,16 +211,6 @@ class DolphinBehaviour : RidingBehaviour<DolphinSettings, DolphinState> {
return Vec3.ZERO
}
/*
* Normalizes the given value between a min and a max.
* The result is clamped between 0.0 and 1.0, where 0.0 represents x is at or below min
* and 1.0 represents x is at or above it.
*/
private fun normalizeVal(x: Double, min: Double, max: Double): Double {
require(max > min) { "max must be greater than min" }
return ((x - min) / (max - min)).coerceIn(0.0, 1.0)
}
override fun canJump(
settings: DolphinSettings,
state: DolphinState,
@@ -306,7 +297,7 @@ class DolphinBehaviour : RidingBehaviour<DolphinSettings, DolphinState> {
return false
}
override fun shouldRotatePlayerHead(
override fun shouldRotateRiderHead(
settings: DolphinSettings,
state: DolphinState,
vehicle: PokemonEntity
@@ -328,6 +319,7 @@ 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
@@ -357,6 +349,7 @@ class DolphinSettings : RidingBehaviourSettings {
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeRidingStats(stats)
rideSounds.encode(buffer)
buffer.writeExpression(canJump)
buffer.writeExpression(jumpVector[0])
@@ -369,6 +362,7 @@ class DolphinSettings : RidingBehaviourSettings {
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
stats.putAll(buffer.readRidingStats())
rideSounds = RideSoundSettingsList.decode(buffer)
canJump = buffer.readExpression()
jumpVector = listOf(
@@ -63,9 +63,9 @@ class RideSoundManager(
}
fun getActiveRideKey(): ResourceLocation {
val key = ride.ridingBehaviourSettings?.key ?: cobblemonResource("no_key")
val key = ride.ridingController?.context?.settings?.key ?: cobblemonResource("no_key")
if (key == CompositeBehaviour.KEY) {
return (ride.ridingState as? CompositeState)?.activeBehaviour?.get() ?: cobblemonResource("no_key")
return (ride.ridingController?.context?.state as? CompositeState)?.activeBehaviour?.get() ?: cobblemonResource("no_key")
} else {
return key
}
@@ -44,10 +44,12 @@ class FishingSpawningCondition: SpawningCondition<FishingSpawnablePosition>() {
if (minLureLevel != null) { // check for the lureLevel of the rod
val pokerodStack = spawnablePosition.rodStack
val lureLevel = EnchantmentHelper.getItemEnchantmentLevel(
spawnablePosition.enchantmentRegistry.getHolder(Enchantments.LURE).get(),
pokerodStack
)
val lureLevel = spawnablePosition.enchantmentRegistry.getHolder(Enchantments.LURE).map {
EnchantmentHelper.getItemEnchantmentLevel(
it,
pokerodStack
)
}.orElse(0)
if (lureLevel < minLureLevel!!) {
return false
} else if (maxLureLevel != null && lureLevel > maxLureLevel!!) {
@@ -29,13 +29,13 @@ import net.minecraft.world.entity.Entity
* @author Hiroku, Plastered_Crab
* @since March 18th, 2025
*/
class SpawnBaitInfluence(val effects: List<SpawnBait.Effect>, val onUsed: (time: Int) -> Unit = {}) : SpawningInfluence {
open class SpawnBaitInfluence(val effects: List<SpawnBait.Effect>, val onUsed: (time: Int, entity: PokemonEntity?) -> Unit = { _, _ -> }) : SpawningInfluence {
var usedTimes = 0
private fun markUsed() {
private fun markUsed(entity: PokemonEntity? = null) {
usedTimes++
onUsed(usedTimes)
onUsed(usedTimes, entity)
}
override fun affectSpawn(action: SpawnAction<*>, entity: Entity) {
@@ -43,7 +43,7 @@ class SpawnBaitInfluence(val effects: List<SpawnBait.Effect>, val onUsed: (time:
val merged = SpawnBaitUtils.mergeEffects(effects)
merged.forEach { effect ->
if (Math.random() <= effect.chance) {
markUsed()
markUsed(entity)
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.LureCakeDetector
import com.cobblemon.mod.common.api.spawning.prospecting.LurePokeSnackDetector
import com.cobblemon.mod.common.api.spawning.prospecting.SaccharineLogSlatheredDetector
/**
@@ -31,7 +31,7 @@ interface SpawningInfluenceDetector {
companion object {
@JvmStatic
val detectors = mutableSetOf<SpawningInfluenceDetector>(
LureCakeDetector,
LurePokeSnackDetector,
SaccharineLogSlatheredDetector,
IncenseSweetDetector
)
@@ -37,18 +37,19 @@ object IncenseSweetDetector : SpawningInfluenceDetector {
val searchRange = RANGE + ceil(sqrt(((input.length pow 2) + (input.width pow 2)).toDouble())).toInt()
val centerPos = input.getCenter().toBlockPos()
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)
}
// 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)
// }
return listOfInfluences
}
@@ -1,104 +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.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>()
}
@@ -0,0 +1,258 @@
/*
* 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,7 +25,6 @@ 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
@@ -259,9 +258,15 @@ open class PartyStore(override val uuid: UUID) : PokemonStore<PartyPosition>() {
BattlePokemon.playerOwned(it)
}.also { if (healPokemon) it.effectedPokemon.heal() }
}.toMutableList()
if (leadingPokemon != null) {
Collections.rotate(result, result.size - this.indexOfFirst { it.uuid == leadingPokemon })
// 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)
}
}
return result
}
fun clearParty() {
@@ -38,6 +38,8 @@ 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() }
@@ -45,14 +47,16 @@ class Search(
"mythical" -> PokemonFilter { pokemon -> pokemon.isMythical() }
"ultrabeast", "ultra_beast" -> PokemonFilter { pokemon -> pokemon.isUltraBeast() }
else -> {
val nameFilter = filter.lowercase(Locale.ROOT)
PokemonFilter { pokemon ->
// search by partial name
if (pokemon.species.resourceIdentifier.path.contains(nameFilter)) {
return@PokemonFilter true
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 exactly match in different props
return@PokemonFilter PokemonProperties.parse(filter).matches(pokemon)
}
}
}
@@ -8,7 +8,6 @@
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
@@ -40,17 +39,13 @@ 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 ->
@@ -60,6 +55,8 @@ object BagItems : DataRegistry {
}
}
}
ShowdownService.service.sendRegistryData(bagItemsScripts, "bagItem")
this.observable.emit(this)
}
}
@@ -57,7 +57,7 @@ data class BattleFormat(
val GEN_9_SINGLES = BattleFormat(
battleType = BattleTypes.SINGLES,
ruleSet = mutableSetOf(BattleRules.OBTAINABLE, BattleRules.PAST, BattleRules.UNOBTAINABLE)
ruleSet = mutableSetOf("Standard NatDex")
)
val GEN_9_DOUBLES = BattleFormat(
@@ -121,6 +121,8 @@ object BattleRegistry {
packedTeamBuilder.append("${if (pk.dmaxLevel < 10) pk.dmaxLevel else ""},")
// Teratype
packedTeamBuilder.append("${pokemon.effectedPokemon.teraType.name},")
// Original nature
packedTeamBuilder.append("${pk.nature.name.path},")
team.add(packedTeamBuilder.toString())
}
@@ -127,7 +127,8 @@ data class MoveActionResponse(var moveName: String, var targetPnx: String? = nul
// No PP or disabled or something
return false
}
val availableTargets = (gimmickMove?.target ?: move.target).targetList(activeBattlePokemon)?.takeIf { it.isNotEmpty() } ?: return true
val availableTargets = (if (gimmickID != null && validGimmickMove) gimmickMove.target else 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,6 +16,9 @@ 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,6 +10,7 @@ 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
@@ -27,15 +28,15 @@ interface ShowdownService {
fun closeConnection()
fun startBattle(battle: PokemonBattle, messages: Array<String>)
fun send(battleId: UUID, messages: Array<String>)
fun getAbilityIds(): JsonArray
fun getMoves(): JsonArray
fun getItemIds(): JsonArray
fun registerSpecies()
fun registerBagItems()
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 indicateSpeciesInitialized() {}
fun validateTeam(packedTeam: String, format: String = "cobblemon") {}
companion object {
val service: ShowdownService by lazy { GraalShowdownService() }
val service: ShowdownService by lazy { SocketShowdownService() }
}
}
@@ -10,8 +10,6 @@ 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
@@ -33,6 +31,7 @@ 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
@@ -134,49 +133,43 @@ class GraalShowdownService : ShowdownService {
sendToShowdown(battleId, messages)
}
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 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 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)))
}
}
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"
}
receiveSpeciesDataFn.execute(jsArray)
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()
}
override fun indicateSpeciesInitialized() {
val afterCobbledSpeciesInitFn = this.context.getBindings("js").getMember("afterCobbledSpeciesInit")
afterCobbledSpeciesInitFn.execute()
val func = this.context.getBindings("js").getMember("afterSpeciesInit")
func.execute()
}
override fun registerBagItems() {
val receiveBagItemDataFn = this.context.getBindings("js").getMember("receiveBagItemData")
for ((itemId, js) in BagItems.bagItemsScripts) {
receiveBagItemDataFn.execute(itemId, js.replace("\n", " "))
}
override fun validateTeam(packedTeam: String, format: String) {
val func = this.context.getBindings("js").getMember("validateTeam")
func.execute(packedTeam, format).asString()
}
private fun sendToShowdown(battleId: UUID, messages: Array<String>) {
@@ -9,9 +9,9 @@
package com.cobblemon.mod.common.battles.runner.socket
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.abilities.AbilityTemplate
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
@@ -102,47 +102,54 @@ class SocketShowdownService(val host: String = "localhost", val port: Int = 1846
ShowdownInterpreter.interpretMessage(battleId, message)
}
override fun getAbilityIds(): JsonArray {
writer.write(">getCobbledAbilityIds")
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")
writer.flush()
val response = readMessage()
return gson.fromJson(response, JsonArray::class.java)
}
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")
override fun resetRegistryData(type: String) {
writer.write(">resetData $type")
acknowledge()
}
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 resetAllRegistries() {
writer.write(">resetAll")
acknowledge()
}
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)
}
}
override fun validateTeam(packedTeam: String, format: String) {
writer.write(">validator $packedTeam $format")
writer.flush()
val response = readMessage()
val jsonarray = gson.fromJson(response, JsonArray::class.java)
for (i in 0 until jsonarray.size()) {
val msg = jsonarray[i].asString
Cobblemon.LOGGER.error(msg)
}
}
@@ -155,14 +162,8 @@ 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(">afterCobbledSpeciesInit")
writer.write(">afterSpeciesInit")
acknowledge()
}
@@ -226,115 +226,99 @@ 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_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)
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)
// Stage 2
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)
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)
// 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)
// 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)
private val NORTH_AABB = arrayOf(
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)
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)
)
// 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_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)
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)
// Stage 2
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)
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)
// 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)
// 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)
private val SOUTH_AABB = arrayOf(
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)
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)
)
// 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_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)
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)
// Stage 2
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)
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)
// 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)
// 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)
private val EAST_AABB = arrayOf(
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)
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)
)
// 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_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)
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)
// Stage 2
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)
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)
// 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)
// 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)
private val WEST_AABB = arrayOf(
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)
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)
)
}
@@ -57,7 +57,8 @@ class BugwortBlock(settings: Properties) : CropBlock(settings), BonemealableBloc
builder.add(IS_WILD)
}
override fun getBaseSeedId(): ItemLike = CobblemonItems.BUGWORT
// TODO after 1.7
// override fun getBaseSeedId(): ItemLike = CobblemonItems.BUGWORT
override fun getShape(state: BlockState, world: BlockGetter, pos: BlockPos, context: CollisionContext): VoxelShape = AGE_TO_SHAPE[this.getAge(state)]
@@ -93,26 +94,27 @@ class BugwortBlock(settings: Properties) : CropBlock(settings), BonemealableBloc
return floor.`is`(BlockTags.DIRT) || floor.`is`(Blocks.FARMLAND)
}
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)
}
// 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 getBonemealAgeIncrease(world: Level): Int = 1
@@ -1,168 +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.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
}
}
@@ -1,178 +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.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,24 +10,23 @@ 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.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.*
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
@@ -116,7 +115,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)
return !this.isMaxAge(state) && canSurvive(state, world, pos) && !(!canGrowInto(world, pos.above()) && state.getValue(AGE) >= AGE_AFTER_HARVEST)
}
private fun canGrowInto(world: LevelReader, pos: BlockPos): Boolean {
@@ -124,28 +123,6 @@ 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,
@@ -157,15 +134,12 @@ 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)
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()
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 state
}
override fun isValidBonemealTarget(level: LevelReader, pos: BlockPos, state: BlockState): Boolean {
@@ -211,7 +185,9 @@ class HeartyGrainsBlock(settings: Properties) : CropBlock(settings), SimpleWater
}
}
override fun getBonemealAgeIncrease(world: Level): Int = 1
override fun getBonemealAgeIncrease(level: Level): Int {
return Mth.nextInt(level.random, 1, 3)
}
override fun canSurvive(state: BlockState, world: LevelReader, pos: BlockPos): Boolean {
return when (state.getValue(HALF)) {
@@ -222,9 +198,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) && canGrowInto(world, pos.above())
return floor.`is`(CobblemonBlockTags.HEARTY_GRAINS_WATER_PLANTABLE)
floor.`is`(CobblemonBlockTags.HEARTY_GRAINS_LAND_PLANTABLE) && canGrowInto(world, pos.above())
floor.`is`(CobblemonBlockTags.HEARTY_GRAINS_LAND_PLANTABLE)
}
DoubleBlockHalf.UPPER -> {
getLowerHalf(world, pos, state) != null
@@ -248,4 +224,8 @@ 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
}
}
@@ -1,25 +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.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)
}
}
@@ -1,134 +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.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
}
}
@@ -0,0 +1,327 @@
/*
* 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,11 +11,12 @@ 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
@@ -55,12 +56,16 @@ import org.jetbrains.annotations.Nullable
import net.minecraft.world.level.block.CampfireBlock as MCCampfireBlock
@Suppress("OVERRIDE_DEPRECATION")
class CampfireBlock(settings: Properties) : BaseEntityBlock(settings), SimpleWaterloggedBlock {
class CampfireBlock(settings: Properties, val isSoul: Boolean) : BaseEntityBlock(settings), SimpleWaterloggedBlock {
companion object {
val CODEC = simpleCodec(::CampfireBlock)
val CODEC: MapCodec<CampfireBlock> = RecordCodecBuilder.mapCodec {
it.group(
propertiesCodec(),
Codec.BOOL.fieldOf("isSoul").forGetter(CampfireBlock::isSoul)
).apply(it, ::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)
@@ -78,7 +83,6 @@ class CampfireBlock(settings: Properties) : BaseEntityBlock(settings), SimpleWat
registerDefaultState(stateDefinition.any()
.setValue(FACING, Direction.NORTH)
.setValue(LIT, true)
.setValue(SOUL, false)
.setValue(ITEM_DIRECTION, Direction.NORTH)
.setValue(POWERED, false))
}
@@ -182,11 +186,9 @@ class CampfireBlock(settings: Properties) : BaseEntityBlock(settings), SimpleWat
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)
@@ -201,7 +203,7 @@ class CampfireBlock(settings: Properties) : BaseEntityBlock(settings), SimpleWat
}
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
builder.add(FACING, ITEM_DIRECTION, LIT, SOUL, POWERED)
builder.add(FACING, ITEM_DIRECTION, LIT, POWERED)
}
override fun updateShape(
@@ -271,7 +273,7 @@ class CampfireBlock(settings: Properties) : BaseEntityBlock(settings), SimpleWat
}
override fun getCloneItemStack(level: LevelReader, pos: BlockPos, state: BlockState): ItemStack {
return if (state.getValue(SOUL)) ItemStack(Blocks.SOUL_CAMPFIRE) else ItemStack(Blocks.CAMPFIRE)
return if (isSoul) ItemStack(Blocks.SOUL_CAMPFIRE) else ItemStack(Blocks.CAMPFIRE)
}
override fun neighborChanged(
@@ -36,6 +36,7 @@ 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
@@ -46,6 +47,8 @@ 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
@@ -59,6 +62,7 @@ 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(
@@ -91,9 +95,6 @@ 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
@@ -148,14 +149,40 @@ 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(container.asCraftInput(), level)
recipeValue.matches(craftInput, 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
}
@@ -187,7 +214,7 @@ class CookingPotMenu : RecipeBookMenu<CraftingInput, CookingPotRecipeBase>, Cont
}
override fun shouldMoveToInventory(slotIndex: Int): Boolean {
return slotIndex != CampfireBlockEntity.Companion.PREVIEW_ITEM_SLOT && !CampfireBlockEntity.SEASONING_SLOTS.contains(slotIndex)
return !CampfireBlockEntity.SEASONING_SLOTS.contains(slotIndex)
}
override fun quickMoveStack(
@@ -209,8 +236,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.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)
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)
) {
return ItemStack.EMPTY
}
@@ -244,6 +271,7 @@ 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()
}
@@ -1,31 +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.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,12 +31,11 @@ class CookingPotResultSlot(
val menu = player.containerMenu
if (menu is CookingPotMenu) {
menu.broadcastChanges()
player.playNotifySound(CobblemonSounds.CAMPFIRE_POT_USE, SoundSource.MASTER, 1.0f, 1.0f)
player.playNotifySound(CobblemonSounds.CAMPFIRE_POT_TAKE_ITEM, SoundSource.MASTER, 1.0f, 1.0f)
}
super.onTake(player, stack)
}
override fun mayPlace(stack: ItemStack): Boolean = false
override fun isActive(): Boolean = hasItem()
}
@@ -12,6 +12,7 @@ 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
@@ -39,11 +40,12 @@ 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
@@ -51,6 +53,7 @@ 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
@@ -67,11 +70,10 @@ 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 PREVIEW_ITEM_SLOT = 13
const val ITEMS_SIZE = 14
const val ITEMS_SIZE = 13
val PLAYER_INVENTORY_SLOTS = 14..40
val PLAYER_HOTBAR_SLOTS = 41..49
val PLAYER_INVENTORY_SLOTS = 13..39
val PLAYER_HOTBAR_SLOTS = 40..48
const val CRAFTING_GRID_WIDTH = 3
const val PLAYER_INVENTORY_WIDTH = 9
@@ -89,15 +91,21 @@ class CampfireBlockEntity(pos: BlockPos, state: BlockState) : BaseContainerBlock
if (!level.isClientSide) return
val isLit = campfireBlockEntity.dataAccess.get(COOKING_PROGRESS_INDEX) > 0
val isSoundActive = BlockEntitySoundTracker.isActive(pos, campfireBlockEntity.runningSound.location)
val isRunningSoundActive = BlockEntitySoundTracker.isActive(pos, campfireBlockEntity.runningSound.location)
val isAmbientSoundActive = BlockEntitySoundTracker.isActive(pos, campfireBlockEntity.ambientSound.location)
val containsItems = campfireBlockEntity.getSeasonings().isNotEmpty() || campfireBlockEntity.getIngredients().isNotEmpty()
if (isLit && !isSoundActive) {
BlockEntitySoundTracker.play(
pos,
CancellableSoundInstance(campfireBlockEntity.runningSound, pos, true, 0.8f, 1.0f)
)
} else if (!isLit && isSoundActive) {
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 {
BlockEntitySoundTracker.stop(pos, campfireBlockEntity.runningSound.location)
BlockEntitySoundTracker.stop(pos, campfireBlockEntity.ambientSound.location)
}
campfireBlockEntity.brothColor =
@@ -149,7 +157,6 @@ class CampfireBlockEntity(pos: BlockPos, state: BlockState) : BaseContainerBlock
if (optionalRecipe == null) {
campfireBlockEntity.cookingProgress = 0
campfireBlockEntity.setItem(PREVIEW_ITEM_SLOT, ItemStack.EMPTY)
return
}
@@ -160,8 +167,6 @@ 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
@@ -171,7 +176,7 @@ class CampfireBlockEntity(pos: BlockPos, state: BlockState) : BaseContainerBlock
if (!ItemStack.isSameItemSameComponents(
resultSlotItem,
cookedItem
) || resultSlotItem.count >= resultSlotItem.maxStackSize
) || resultSlotItem.count + cookedItem.count > resultSlotItem.maxStackSize
) {
campfireBlockEntity.cookingProgress = 0
return
@@ -191,11 +196,11 @@ class CampfireBlockEntity(pos: BlockPos, state: BlockState) : BaseContainerBlock
resultSlotItem.grow(cookedItem.count)
}
campfireBlockEntity.consumeCraftingIngredients(recipe)
campfireBlockEntity.consumeCraftingIngredients(recipe, level, pos, state, campfireBlockEntity)
level.playSoundServer(
position = pos.bottomCenter,
sound = CobblemonSounds.CAMPFIRE_POT_CRAFT,
sound = CobblemonSounds.CAMPFIRE_POT_COOK,
)
setChanged(level, pos, state);
@@ -204,7 +209,8 @@ class CampfireBlockEntity(pos: BlockPos, state: BlockState) : BaseContainerBlock
}
}
private val runningSound = CobblemonSounds.CAMPFIRE_POT_COOK
private val runningSound = CobblemonSounds.CAMPFIRE_POT_ACTIVE
private val ambientSound = CobblemonSounds.CAMPFIRE_POT_AMBIENT
private var cookingProgress: Int = 0
private var cookingTotalTime: Int = COOKING_TOTAL_TIME
private var isLidOpen: Boolean = true
@@ -270,32 +276,17 @@ class CampfireBlockEntity(pos: BlockPos, state: BlockState) : BaseContainerBlock
return particleStorm
}
fun consumeCraftingIngredients(recipe: CookingPotRecipeBase) {
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 consumeItem(slot: Int) {
val itemInSlot = getItem(slot)
if (!itemInSlot.isEmpty) {
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
}
}
if (itemInSlot.item.hasCraftingRemainingItem()) {
remainderItems[itemInSlot.item.craftingRemainingItem!!] = (remainderItems[itemInSlot.item.craftingRemainingItem!!] ?: 0) + 1
}
itemInSlot.shrink(1)
if (itemInSlot.count <= 0) setItem(slot, ItemStack.EMPTY)
}
}
@@ -305,6 +296,24 @@ 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> =
@@ -429,6 +438,7 @@ class CampfireBlockEntity(pos: BlockPos, state: BlockState) : BaseContainerBlock
if (level?.isClientSide == true) {
BlockEntitySoundTracker.stop(blockPos, runningSound.location)
BlockEntitySoundTracker.stop(blockPos, ambientSound.location)
}
}
@@ -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.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)
@@ -1,30 +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 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()
}
}
@@ -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.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)
@@ -8,130 +8,127 @@
package com.cobblemon.mod.common.block.entity
import com.cobblemon.mod.common.CobblemonBlockEntities
import com.cobblemon.mod.common.CobblemonItemComponents
import com.cobblemon.mod.common.CobblemonItems
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.item.components.BaitEffectsComponent
import com.cobblemon.mod.common.item.components.FlavourComponent
import com.cobblemon.mod.common.item.components.FoodColourComponent
import com.cobblemon.mod.common.item.components.IngredientComponent
import com.cobblemon.mod.common.util.DataKeys
import net.minecraft.core.BlockPos
import net.minecraft.core.HolderLookup
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.nbt.*
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 CakeBlockEntity(
type: BlockEntityType<*>,
pos: BlockPos,
state: BlockState
) : BlockEntity(type, pos, state) {
open val maxBites: Int
get() = 6
open class PokeSnackBlockEntity(pos: BlockPos, state: BlockState) : TintBlockEntity(CobblemonBlockEntities.POKE_SNACK, pos, state) {
var flavourComponent: FlavourComponent? = null
var baitEffectsComponent: BaitEffectsComponent? = null
var foodColourComponent: FoodColourComponent? = null
var bites: Int = 0
var ingredientComponent: IngredientComponent? = null
fun initializeFromItemStack(itemStack: ItemStack) {
flavourComponent = itemStack.get(CobblemonItemComponents.FLAVOUR)
baitEffectsComponent = itemStack.get(CobblemonItemComponents.BAIT_EFFECTS)
foodColourComponent = itemStack.get(CobblemonItemComponents.FOOD_COLOUR)
}
ingredientComponent = itemStack.get(CobblemonItemComponents.INGREDIENT)
if (isLure()) baitEffectsComponent = itemStack.get(CobblemonItemComponents.BAIT_EFFECTS)
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.CAKE_FLAVOUR, encodedTag)
}
}
baitEffectsComponent?.let { component ->
CobblemonItemComponents.BAIT_EFFECTS.codec()!!
.encodeStart(NbtOps.INSTANCE, component)
.result()
.ifPresent { encodedTag ->
tag.put(DataKeys.CAKE_BAIT_EFFECTS, encodedTag)
}
}
foodColourComponent?.let { component ->
CobblemonItemComponents.FOOD_COLOUR.codec()!!
.encodeStart(NbtOps.INSTANCE, component)
.result()
.ifPresent { encodedTag ->
tag.put(DataKeys.CAKE_FOOD_COLOUR, encodedTag)
}
flavourComponent?.let {
getColourMixFromFlavours(it.getDominantFlavours())?.let {
setTint(it)
}
}
}
override fun loadAdditional(
tag: CompoundTag,
registries: HolderLookup.Provider
) {
super.loadAdditional(tag, registries)
bites = tag.getInt(DataKeys.CAKE_BITES)
if (tag.contains(DataKeys.CAKE_FLAVOUR)) {
CobblemonItemComponents.FLAVOUR.codec()
?.parse(NbtOps.INSTANCE, tag.get(DataKeys.CAKE_FLAVOUR))
?.result()
?.ifPresent { component ->
flavourComponent = component
}
fun isLure(): Boolean {
val block = blockState.block
if (block is PokeSnackBlock) {
return block.isLure
}
if (tag.contains(DataKeys.CAKE_BAIT_EFFECTS)) {
CobblemonItemComponents.BAIT_EFFECTS.codec()
?.parse(NbtOps.INSTANCE, tag.get(DataKeys.CAKE_BAIT_EFFECTS))
?.result()
?.ifPresent { component ->
baitEffectsComponent = component
}
}
if (tag.contains(DataKeys.CAKE_FOOD_COLOUR)) {
CobblemonItemComponents.FOOD_COLOUR.codec()
?.parse(NbtOps.INSTANCE, tag.get(DataKeys.CAKE_FOOD_COLOUR))
?.result()
?.ifPresent { 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
return false
}
fun toItemStack(): ItemStack {
val stack = ItemStack(this.blockState.block)
if (stack.item == CobblemonItems.LURE_CAKE && baitEffectsComponent != null) {
if (isLure() && 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
}
}
/**
* 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) {
super.saveAdditional(tag, registries)
flavourComponent?.let { component ->
CobblemonItemComponents.FLAVOUR.codec()!!
.encodeStart(NbtOps.INSTANCE, component)
.result()
.ifPresent { encodedTag ->
tag.put(DataKeys.FLAVOUR, encodedTag)
}
}
baitEffectsComponent?.let { component ->
CobblemonItemComponents.BAIT_EFFECTS.codec()!!
.encodeStart(NbtOps.INSTANCE, component)
.result()
.ifPresent { encodedTag ->
tag.put(DataKeys.BAIT_EFFECTS, encodedTag)
}
}
ingredientComponent?.let { component ->
CobblemonItemComponents.INGREDIENT.codec()!!
.encodeStart(NbtOps.INSTANCE, component)
.result()
.ifPresent { encodedTag ->
tag.put(DataKeys.INGREDIENTS, encodedTag)
}
}
}
override fun loadAdditional(tag: CompoundTag, registries: HolderLookup.Provider) {
super.loadAdditional(tag, registries)
if (tag.contains(DataKeys.FLAVOUR)) {
CobblemonItemComponents.FLAVOUR.codec()
?.parse(NbtOps.INSTANCE, tag.get(DataKeys.FLAVOUR))
?.result()
?.ifPresent { component ->
flavourComponent = component
}
}
if (tag.contains(DataKeys.BAIT_EFFECTS)) {
CobblemonItemComponents.BAIT_EFFECTS.codec()
?.parse(NbtOps.INSTANCE, tag.get(DataKeys.BAIT_EFFECTS))
?.result()
?.ifPresent { component ->
baitEffectsComponent = component
}
}
if (tag.contains(DataKeys.INGREDIENTS)) {
CobblemonItemComponents.INGREDIENT.codec()
?.parse(NbtOps.INSTANCE, tag.get(DataKeys.INGREDIENTS))
?.result()
?.ifPresent { component ->
ingredientComponent = component
}
}
}
}
@@ -13,4 +13,6 @@ 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(CobblemonBlockEntities.INCENSE_SWEET, pos, state)
class SweetIncenseBlockEntity(pos: BlockPos, state: BlockState) : BlockEntity(
null // CobblemonBlockEntities.INCENSE_SWEET // TODO after 1.7
, pos, state)
@@ -0,0 +1,51 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.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,7 +19,15 @@ import com.cobblemon.mod.common.api.molang.ObjectValue
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.client.render.models.blockbench.ExcludedLabels
import com.cobblemon.mod.common.client.render.models.blockbench.PosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.animation.*
import com.cobblemon.mod.common.client.render.models.blockbench.animation.ActiveAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.animation.BimanualSwingAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.animation.BipedWalkAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.animation.PitchTiltAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.animation.PrimaryAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.animation.PunchAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.animation.QuadrupedWalkAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.animation.SingleBoneLookAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.animation.WingFlapIdleAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.pose.ModelPartTransformation
import com.cobblemon.mod.common.client.render.models.blockbench.wavefunction.WaveFunction
import com.cobblemon.mod.common.client.render.models.blockbench.wavefunction.sineFunction
@@ -117,6 +125,22 @@ object ClientMoLangFunctions {
ObjectValue(anim)
}
},
"pitch_tilt" to Function {
model -> Function { params ->
val bone = params.getStringOrNull(0) ?: "root"
val maxChangePerTick = params.getDoubleOrNull(1) ?: 1.5F
val minPitch = params.getDoubleOrNull(2) ?: -45F
val maxPitch = params.getDoubleOrNull(3) ?: 45F
ObjectValue(
PitchTiltAnimation(
bone = model.getPart(bone),
minPitch = minPitch.toFloat(),
maxPitch = maxPitch.toFloat(),
maxChangePerTick = maxChangePerTick.toFloat()
)
)
}
},
"look" to Function {
model -> Function { params ->
val boneName = params.getString(0)
@@ -20,19 +20,20 @@ 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
@@ -59,6 +60,8 @@ 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
@@ -124,7 +127,7 @@ object CobblemonClient {
this.registerBlockEntityRenderers()
registerBlockRenderTypes()
//registerColors()
registerColors()
registerFlywheelRenderers()
this.registerEntityRenderers()
this.registerItemColors()
@@ -196,16 +199,16 @@ object CobblemonClient {
// .apply()
}
/*
fun registerColors() {
this.implementation.registerBlockColors(BlockColorProvider { _, _, _, _ ->
return@BlockColorProvider 0xE0A33A
}, CobblemonBlocks.APRICORN_LEAVES)
this.implementation.registerItemColors(ItemColorProvider { _, _ ->
return@ItemColorProvider 0xE0A33A
}, CobblemonItems.APRICORN_LEAVES)
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)
}
*/
private fun registerBlockRenderTypes() {
@@ -289,10 +292,10 @@ object CobblemonClient {
CobblemonBlocks.SACCHARINE_WALL_HANGING_SIGN,
CobblemonBlocks.SACCHARINE_SAPLING,
CobblemonBlocks.POTTED_SACCHARINE_SAPLING,
CobblemonBlocks.LURE_CAKE,
CobblemonBlocks.POKE_CAKE,
CobblemonBlocks.POKE_SNACK,
CobblemonBlocks.LECTERN,
CobblemonBlocks.CAMPFIRE,
CobblemonBlocks.SOUL_CAMPFIRE,
CobblemonBlocks.BLACK_CAMPFIRE_POT,
CobblemonBlocks.BLUE_CAMPFIRE_POT,
CobblemonBlocks.GREEN_CAMPFIRE_POT,
@@ -349,12 +352,7 @@ 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.LURE_CAKE, ::CakeBlockEntityRenderer)
this.implementation.registerBlockEntityRenderer(CobblemonBlockEntities.POKE_CAKE, ::CakeBlockEntityRenderer)
this.implementation.registerBlockEntityRenderer(
CobblemonBlockEntities.CANDLE_POKE_CAKE,
::CandlePokeCakeBlockEntityRenderer
)
this.implementation.registerBlockEntityRenderer(CobblemonBlockEntities.POKE_SNACK, ::PokeSnackBlockEntityRenderer)
}
private fun registerEntityRenderers() {
@@ -383,9 +381,8 @@ 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)
}
@@ -17,6 +17,7 @@ import com.cobblemon.mod.common.battles.ForcePassActionResponse
import com.cobblemon.mod.common.battles.PassActionResponse
import com.cobblemon.mod.common.client.CobblemonClient
import com.cobblemon.mod.common.net.messages.server.battle.BattleSelectActionsPacket
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import java.util.UUID
class ClientBattle(
@@ -32,11 +33,10 @@ class ClientBattle(
set(value) {
field = value
if (value != null) {
val wildMonProps = value.activePokemon[0].battlePokemon?.properties
wildMonProps?.let {props ->
val wildSpecies = PokemonSpecies.getByName(props.species!!)!!
val wildMonSpecies = value.activePokemon[0].battlePokemon?.properties?.species?.asIdentifierDefaultingNamespace()
knowledge = wildMonSpecies?.let {
//FIXME: Better knowledge checking based on aspects/form and such
knowledge = CobblemonClient.clientPokedexData.getKnowledgeForSpecies(wildSpecies.resourceIdentifier)
CobblemonClient.clientPokedexData.getKnowledgeForSpecies(it)
} ?: PokedexEntryProgress.NONE
}
else {
@@ -20,6 +20,8 @@ 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
@@ -100,13 +100,14 @@ class RideControlsOverlay : Gui(Minecraft.getInstance()) {
// Don't render if duration config set to 0 or less
if (maxDurationFrames <= 0F) return
val rideBehaviourSettings = riddenEntity.ridingBehaviourSettings
val rideBehaviourKey = rideBehaviourSettings?.key
val rideBehaviourSettings = riddenEntity.ridingController?.context?.settings ?: return
val ridingBehaviourState = riddenEntity.ridingController?.context?.state ?: return
val rideBehaviourKey = rideBehaviourSettings.key
// Update current ride behaviour if changed
// If composite, get active behaviour instead
if (rideBehaviourKey == CompositeBehaviour.KEY) {
val activeBehaviour = (riddenEntity.ridingState as CompositeState).activeBehaviour.get()
val activeBehaviour = (ridingBehaviourState as CompositeState).activeBehaviour.get()
if (currentBehaviourKey != activeBehaviour) {
currentBehaviourKey = activeBehaviour
resetOverlayState()
@@ -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(this.request.activePokemon.getFormat().battleType.pokemonPerSide == 1) {
if(request.activePokemon.getFormat().battleType.pokemonPerSide == 1) {
move.onClick()
} else {
battleGUI.changeActionSelection(BattleTargetSelection(battleGUI, request, move.move))
battleGUI.changeActionSelection(BattleTargetSelection(battleGUI, request, move.move, move.response.gimmickID, move.move.gimmickMove))
playDownSound(Minecraft.getInstance().soundManager)
}
return true
@@ -14,6 +14,7 @@ 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
@@ -43,7 +44,9 @@ import kotlin.math.sin
class BattleTargetSelection(
battleGUI: BattleGUI,
request: SingleActionRequest,
val move: InBattleMove
val move: InBattleMove,
val gimmickID: String?,
gimmickMove: InBattleGimmickMove?
) : BattleActionSelection(
battleGUI = battleGUI,
request = request,
@@ -81,9 +84,10 @@ 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 = move.target.targetList(request.activePokemon)
val multiTargetList = if(selectableTargetList == null) request.activePokemon.getMultiTargetList(move.target) else null
val selectableTargetList = targetType.targetList(request.activePokemon)
val multiTargetList = if(selectableTargetList == null) request.activePokemon.getMultiTargetList(targetType) 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,6 +39,7 @@ 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)
@@ -174,24 +175,36 @@ 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.index != PREVIEW_ITEM_SLOT) {
super.renderSlot(guiGraphics, slot)
return
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)
}
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()
super.renderSlot(guiGraphics, slot)
}
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.stats[RidingStat.SPEED]?.ranges?.put(ridingStyle, minSpeed..maxSpeed)
vehicle.rideProp.behaviours?.get(ridingStyle)?.stats?.put(RidingStat.SPEED, minSpeed..maxSpeed)
}
if (minAcceleration < maxAcceleration) {
vehicle.rideProp.stats[RidingStat.ACCELERATION]?.ranges?.put(ridingStyle, minAcceleration..maxAcceleration)
vehicle.rideProp.behaviours?.get(ridingStyle)?.stats?.put(RidingStat.ACCELERATION, minSpeed..maxSpeed)
}
if (minSkill < maxSkill) {
vehicle.rideProp.stats[RidingStat.SKILL]?.ranges?.put(ridingStyle, minSkill..maxSkill)
vehicle.rideProp.behaviours?.get(ridingStyle)?.stats?.put(RidingStat.SKILL, minSpeed..maxSpeed)
}
if (minJump < maxJump) {
vehicle.rideProp.stats[RidingStat.JUMP]?.ranges?.put(ridingStyle, minJump..maxJump)
vehicle.rideProp.behaviours?.get(ridingStyle)?.stats?.put(RidingStat.JUMP, minSpeed..maxSpeed)
}
if (minStamina < maxStamina) {
vehicle.rideProp.stats[RidingStat.STAMINA]?.ranges?.put(ridingStyle, minStamina..maxStamina)
vehicle.rideProp.behaviours?.get(ridingStyle)?.stats?.put(RidingStat.STAMINA, minSpeed..maxSpeed)
}
CobblemonNetwork.sendToServer(
@@ -198,7 +198,9 @@ class RidingStatsDebugGUI(val vehicle: PokemonEntity) : Screen(lang("ui.debug.ri
}
fun getRidingStatRange(ridingStat: RidingStat): IntRange {
return vehicle.rideProp.stats[ridingStat]?.ranges?.get(ridingStyle) ?: 0..0
// TODO
return 0..0
// return vehicle.rideProp.stats[ridingStat]?.ranges?.get(ridingStyle) ?: 0..0
}
fun getScaledWidth() = Minecraft.getInstance().window.guiScaledWidth
@@ -240,7 +240,6 @@ class DialogueScreen(var dialogueDTO: DialogueDTO) : Screen("gui.dialogue".asTra
}
this.dialogueStartTick = minecraft!!.player!!.tickCount
dialogueDTO.currentPageDTO.clientActions.flatMap(String::asExpressions).resolve(runtime)
}
var gibberIndex = 0
@@ -719,6 +719,7 @@ class PCGUI(
if (pasture.pastureScrollList.isHovered) pasture.pastureScrollList.mouseScrolled(mouseX, mouseY, amount, verticalAmount)
}
if (storageWidget.isHovered && mouseX < (storageWidget.x + StorageWidget.SCREEN_WIDTH)) this.storageWidget.box -= verticalAmount.toInt()
if (wallpaperWidget.isHovered) wallpaperWidget.mouseScrolled(mouseX, mouseY, amount, verticalAmount)
return super.mouseScrolled(mouseX, mouseY, amount, verticalAmount)
}
@@ -17,6 +17,7 @@ import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.components.Button
import net.minecraft.client.resources.sounds.SimpleSoundInstance
import net.minecraft.client.sounds.SoundManager
import net.minecraft.network.chat.Component
import net.minecraft.network.chat.MutableComponent
import net.minecraft.resources.ResourceLocation
@@ -25,7 +26,7 @@ class SummaryTab(
val label: MutableComponent? = null,
val icon: ResourceLocation? = null,
onPress: OnPress
): Button(pX, pY, WIDTH, HEIGHT, label, onPress, DEFAULT_NARRATION), CobblemonRenderable {
): Button(pX, pY, WIDTH, HEIGHT, label ?: Component.empty(), onPress, DEFAULT_NARRATION), CobblemonRenderable {
companion object {
private const val WIDTH = 39
private const val HEIGHT = 13
@@ -0,0 +1,43 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.net.effect
import com.cobblemon.mod.common.api.net.ClientNetworkPacketHandler
import com.cobblemon.mod.common.block.PokeSnackBlock
import com.cobblemon.mod.common.net.messages.client.effect.PokeSnackBlockParticlesPacket
import net.minecraft.client.Minecraft
import net.minecraft.core.particles.ParticleTypes
object PokeSnackBlockParticlesHandler : ClientNetworkPacketHandler<PokeSnackBlockParticlesPacket> {
override fun handle(packet: PokeSnackBlockParticlesPacket, client: Minecraft) {
val level = Minecraft.getInstance().level ?: return
val blockState = level.getBlockState(packet.blockPos)
val block = blockState.block
if (block is PokeSnackBlock) {
block.spawnEatParticles(level, packet.blockPos)
}
val entityPos = packet.entityPos
if (entityPos != null) {
val random = level.random
repeat(5) {
level.addParticle(
ParticleTypes.POOF,
entityPos.x + random.nextDouble(),
entityPos.y + random.nextDouble(),
entityPos.z + random.nextDouble(),
0.0,
0.0,
0.0
)
}
}
}
}
@@ -32,7 +32,7 @@ object SaccharineLogBlockParticlesHandler : ClientNetworkPacketHandler<Saccharin
val entityPos = packet.entityPos
if (entityPos != null) {
val random = level.random
repeat(10) {
repeat(5) {
level.addParticle(
ParticleTypes.POOF,
entityPos.x + random.nextDouble(),

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