Compare commits

...
Author SHA1 Message Date
JPAKx4 9eeeab340c Merge branch 'irl_date_time' into irl_date_time_spawns
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/api/spawning/condition/SpawningCondition.kt
2025-08-08 21:08:31 -05:00
JPAKx4 9163046b45 Merge branch 'main' into irl_date_time
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/api/molang/MoLangFunctions.kt
2025-08-08 21:03:49 -05:00
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
Paul 756989b0c0 Fixed storages not saving (OOPS) 2025-08-04 21:26:01 +10:00
Paul 58a4267f42 Added a living entity look_at_position function so we can make funny battle effects 2025-08-04 21:25:54 +10:00
Hiroku e8826a3d0e Merge branch 'trigger-behaviour' into 'main'
EntityCallbacks

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

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

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

See merge request cable-mc/cobblemon!1641
2025-08-01 08:12:32 +00:00
Jackson BowesandPolymeta 8b48cfaa50 Ride specific sound implementation 2025-08-01 08:12:32 +00:00
Polymeta 50c3ffc774 Merge branch 'bugfix-entity-health' into 'main'
Couple Pokémon HP / Entity health in a more sensible way.

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

Closes #1616

See merge request cable-mc/cobblemon!1756
2025-07-28 08:56:41 +00:00
dhi_holoandPolymeta af4d2077ae Eat your, well, lots of things 2025-07-28 08:56:41 +00:00
Paul 3f090ce691 Merge remote-tracking branch 'origin/main' into trigger-behaviour 2025-07-28 11:34:37 +10:00
Paul 8e6681b3aa Introduced EntityCallbacks 2025-07-27 22:02:30 +10:00
Paul 50427965ab Merge remote-tracking branch 'origin/main' into trigger-behaviour 2025-07-26 21:41:09 +10:00
Paul 3966607373 Tidy up the script actions for adding/removing a Behaviour on an entity 2025-07-26 18:34:40 +10:00
Nadja Reitzenstein 29833df50e Couple Pokémon HP / Entity health in a more sensible way.
Specifically, don't touch Battle HP for wild Pokémon so that you can't hurt a Pokémon down out of battle for an easier catch, and always round entity health up so that only a HP of 0 is a health of 0
2025-07-24 13:09:33 +02:00
JPAK d5b5e1055b Merge branch 'irl_date_time_molang' into irl_date_time_spawns 2025-04-22 14:28:06 -05:00
JPAK 06732e89fd Update Changelog and error handling. 2025-04-22 14:27:40 -05:00
JPAK 304f368ae1 Merge branch 'irl_date_time_molang' into irl_date_time_spawns 2025-04-22 12:34:10 -05:00
JPAK 9e8d6a4f39 Add an option for String timezones to "date_of" and "date_local_time" functions. 2025-04-22 12:33:45 -05:00
JPAK e22eeafdea Merge branch 'irl_date_time_molang' into irl_date_time_spawns 2025-04-22 12:11:12 -05:00
JPAK 662efa55c6 Fix imports. 2025-04-22 12:10:39 -05:00
JPAK efb9e538f3 Merge branch 'main' into irl_date_time_molang
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/api/molang/MoLangFunctions.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/config/Category.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/config/CobblemonConfig.kt
2025-04-22 12:03:37 -05:00
JPAK 47e9491e4e Merge remote-tracking branch 'origin/irl_date_time_spawns' into irl_date_time_spawns 2025-02-07 14:36:32 -06:00
JPAK c4713b522b Add "irlDateRange" and "irlTimeRange" as a pokemon spawning condition. 2025-02-07 14:36:22 -06:00
JPAK 42c711cfc7 Fix config and add langs for config. 2025-02-07 14:35:59 -06:00
JPAK 1296acd151 Merge remote-tracking branch 'origin/irl_date_time_spawns' into irl_date_time_spawns 2025-02-07 12:28:39 -06:00
JPAK 1a5ef95879 Add "irlDateRange" and "irlTimeRange" as a pokemon spawning condition. 2025-02-07 12:28:21 -06:00
JPAK 681d5f8bcc Merge remote-tracking branch 'origin/irl_date_time' into irl_date_time_molang
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/config/Category.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/config/CobblemonConfig.kt
2025-02-07 12:27:49 -06:00
JordanandJPAK a52d8452b4 Revert "Add "irlDateRange" and "irlTimeRange" as a pokemon spawning condition."
This reverts commit b0be0b2dca
2025-02-07 12:23:13 -06:00
JPAK e65298b587 Add "irlDateRange" and "irlTimeRange" as a pokemon spawning condition. 2025-02-07 12:23:13 -06:00
JPAK f8c09b8e26 Add config for time zones and add an optional parameter to MoLangFunction's "date_local_time" and "date_of" to use the config time zone instead of the system time zone. 2025-02-07 12:23:11 -06:00
JPAK 5d1019afd5 Add config for time zones and add an optional parameter to MoLangFunction's "date_local_time" and "date_of" to use the config time zone instead of the system time zone. 2025-02-07 12:22:28 -06:00
JPAK f36f126a2b Merge branch 'irl_date_time_molang' into irl_date_time_spawns 2024-12-29 14:46:28 -06:00
JPAK eb9bd116b2 Merge remote-tracking branch 'origin/irl_date_time' into irl_date_time_molang
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/config/CobblemonConfig.kt
2024-12-29 14:28:06 -06:00
JPAK da40245fa6 Add "irlDateRange" and "irlTimeRange" as a pokemon spawning condition. 2024-12-29 14:16:04 -06:00
JPAK 70bf4d6654 Add config for time zones and add an optional parameter to MoLangFunction's "date_local_time" and "date_of" to use the config time zone instead of the system time zone. 2024-12-29 14:14:43 -06:00
Jordan 080a5b9c74 Revert "Add "irlDateRange" and "irlTimeRange" as a pokemon spawning condition."
This reverts commit b0be0b2dca
2024-12-27 19:45:08 +00:00
JPAK b0be0b2dca Add "irlDateRange" and "irlTimeRange" as a pokemon spawning condition. 2024-12-27 13:35:41 -06:00
JPAK d8300436c9 Add config for time zones and add an optional parameter to MoLangFunction's "date_local_time" and "date_of" to use the config time zone instead of the system time zone. 2024-12-26 22:25:15 -06:00
208 changed files with 2005 additions and 754 deletions
+2
View File
@@ -12,9 +12,11 @@
### Developer
- baseScale in NPCs has been removed from classes and presets, and in the entity it is replaced with hitboxScale and renderScale.
- Added `EntityCallbacks` to `NPCEntity` and `PokemonEntity`. You can trigger custom callback types by using `[Entity].callbacks.process(...)`
### MoLang & Datapacks
- Added a `behaviours` datapack folder which motivates the Behaviour Editor screen on NPCs and Pokémon.
- Added "callback" support on PokemonEntity and NPCEntity that allows MoLang scripts to be executed in response to certain scenarios.
- Added very many behaviours to choose from.
- Removed many NPC presets that now exist as behaviours. This is a breaking change if you are using NPCs with these presets.
- NPCs with these presets should be modified to instead list under `"behaviours"` all the equivalent behaviours.
+23 -1
View File
@@ -194,6 +194,19 @@
### Changes
- Changed pokemon caught and seen count to update based on the current pokedex being looked
- Renamed `chargeGainedPerTick` config to `secondsToChargeHealingMachine`.
- Added config setting `maxPokedexScanningDetectionRange` to control from what distance the player can scan Pokémon using the Pokédex.
- Added the `is_standing_on_blocks(depth, blocks...)` MoLang function, which returns whether the specified entity is standing on a specific block or set of blocks. Example usage: `q.is_standing_on_blocks(2, minecraft:sand)`
- Added config setting `hideUnimplementedPokemonInThePokedex` which hides unimplemented Pokémon from the Pokédex when set to true.
- Added molang particle queries for getting distance to targeted entities
- Added debug renderer for poseable entity locators
- Added time zone as a config option
- Added optional boolean to use time zone for MoLang function "date_local_time" and "date_of"
- Added "irlDateRange" and "irlTimeRange" as a condition for spawning
- Added an optional MoValue to either use system timezone when set as true, or use the supplied string as a timezone for MoLang functions `date_local_time` and `date_of`. If the timezone supplied fails to parse this function now returns `0.0`.
### Additions
- Added time zone as a config option
- Added optional boolean to use time zone for MoLang function "date_local_time" and "date_of"
- Made Blocks of Gold count as Big Nuggets when held by a Pokémon (for Fling functionality)
- Players can now eat Sweet and Tart Apples, Whipped Dreams, and the Alcremie Sweets.
- Updated Sweet and Tart Apple sprites
@@ -239,6 +252,7 @@
- Reorganised the `block` texture folder to be more organised, in line with the `item` texture folder.
- Offset in EmitterShape now ignores scale to be more like Blockbench by default. You can get this behaviour back by adding `"scale": true` in the `attachment_options` property in most EmitterShapes.
- Not specifying a dex in `/pokedex printcalculations {player} {dex}` will now print the National Dex statistics instead of showing all dexes. `/pokedex printcalculations {player} all` is how to view all dex statistics in one command.
- Removed Braised Vivichoke
### Fixes
- Fixed game crashing when removing national pokedex using datapacks
@@ -306,6 +320,8 @@
- Fixed Moon Ball moon phase logic to actually work correctly
- Fixed `/pokedex printcalculations` to now show the correct percentage completed of the Pokedex
- Fixed mod incompatibility with the `Raised` mod
- Fixed a vulnerability that could cause party and PC rollbacks under specific circumstances.
- Fixed a rare edge case where sorting your PC could be rolled back later.
### Developer
- A finished battle now has winners and losers set inside of `PokemonBattle` instead of them always being empty.
@@ -346,7 +362,8 @@
- Added PokedexManager.obtain as a replacement for .catch which is not a friendly function name in Java.
- Added `Pokemon#hyperTrainIV()` and `IVs#setHyperTrainedIV(Stat, Int)`
- `ElementalType` now implelments `ShowdownIdentifiable` to ensure the communcation with showdown stays consistent (also in regards to TeraTypes)
- `ElementalType` now implements `ShowdownIdentifiable` to ensure the communcation with showdown stays consistent (also in regards to TeraTypes)
- Pokemon no longer have a change observable
### MoLang & Datapacks
- The following usages for item predicates can now use item conditions like advancements do, you can learn about them in the [Minecraft wiki](https://minecraft.wiki/w/Advancement_definition#minecraft:filled_bucket)
@@ -390,6 +407,11 @@
- Adds Flows for `STARTER_CHOSEN`, `EV_GAINED`, `POKEMON_RELEASED`, `POKEMON_NICKNAMED`, `HELD_ITEM`, and `TRADE_COMPLETED` events
- Adds Pokemon functions for `pokeball`, `held_item`, `remove_held_item`, `add_aspects`, and `remove_aspects`
- Added `pokemon.hyper_train_iv` as an available Molang function.
- Added `prepare_{effect}` and `damage_{effect}` action effect hooks in battles for more battle particle effects.
- Added `q.has_argument(<argument_name>, [argument_value])` MoLang function to several battle-related action effect contexts.
- Added `q.has_argument_at(<index>, [argument_value])` MoLang function to several battle-related action effect contexts.
- Added `q.hit_count` MoLang function to move action effect contexts.
- Added `is_included`, `to_lower`, `to_upper`, and `string_length` as available Molang functions.
## [1.6.1 (January 26th, 2025)](#1-6-1)
@@ -0,0 +1,17 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.duck;
public interface ChannelDuck {
void cobblemon$applyLowPassFilter(float gain, float hfGain);
void cobblemon$inverseAttenuation(float rolloffFactor);
void cobblemon$clearFilters();
// void cobblemon$exponentialAttenuation(float rolloff, float refDistance, float maxDistance);
}
@@ -0,0 +1,69 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.mixin;
import com.cobblemon.mod.common.duck.ChannelDuck;
import com.mojang.blaze3d.audio.Channel;
import org.lwjgl.openal.AL10;
import org.lwjgl.openal.EXTEfx;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
@Mixin(Channel.class)
public abstract class ChannelMixin implements ChannelDuck {
@Shadow
private int source;
// Id of filter applied when sound is obstructed
private int cobblemon$lowPassFilterId = 0;
@Override
public void cobblemon$applyLowPassFilter(float gain, float hfGain) {
// Delete old filter if it exists
if (cobblemon$lowPassFilterId != 0) {
AL10.alSourcei(source, EXTEfx.AL_DIRECT_FILTER, 0); // Detach filter from source
EXTEfx.alDeleteFilters(cobblemon$lowPassFilterId); // Delete old filter
cobblemon$lowPassFilterId = 0;
}
// Generate a fresh filter
int filterId = EXTEfx.alGenFilters();
if (!EXTEfx.alIsFilter(filterId)) {
System.err.println("Failed to create OpenAL low-pass filter");
return;
}
EXTEfx.alFilteri(filterId, EXTEfx.AL_FILTER_TYPE, EXTEfx.AL_FILTER_LOWPASS);
EXTEfx.alFilterf(filterId, EXTEfx.AL_LOWPASS_GAIN, gain);
EXTEfx.alFilterf(filterId, EXTEfx.AL_LOWPASS_GAINHF, hfGain);
AL10.alSourcei(source, EXTEfx.AL_DIRECT_FILTER, filterId);
// Save the new filter ID
cobblemon$lowPassFilterId = filterId;
}
@Override
public void cobblemon$inverseAttenuation(float rolloffFactor) {
AL10.alSourcei(source, AL10.AL_DISTANCE_MODEL, AL10.AL_INVERSE_DISTANCE);
AL10.alSourcef(source, AL10.AL_ROLLOFF_FACTOR, rolloffFactor);
AL10.alSourcef(source, AL10.AL_REFERENCE_DISTANCE, 1.0f);
AL10.alSourcef(source, AL10.AL_MAX_DISTANCE, 0.0f);
}
@Override
public void cobblemon$clearFilters() {
AL10.alSourcei(source, EXTEfx.AL_DIRECT_FILTER, 0);
if (EXTEfx.alIsFilter(cobblemon$lowPassFilterId)) {
EXTEfx.alDeleteFilters(cobblemon$lowPassFilterId);
cobblemon$lowPassFilterId = 0;
}
}
}
@@ -8,23 +8,33 @@
package com.cobblemon.mod.common.mixin;
import com.cobblemon.mod.common.api.riding.sound.RideAttenuationModel;
import com.cobblemon.mod.common.api.riding.sound.RideLoopSound;
import com.cobblemon.mod.common.client.sound.BattleMusicController;
import com.cobblemon.mod.common.duck.ChannelDuck;
import com.cobblemon.mod.common.duck.SoundEngineDuck;
import com.google.common.collect.Multimap;
import com.llamalad7.mixinextras.sugar.Local;
import com.mojang.blaze3d.audio.Channel;
import net.minecraft.client.resources.sounds.Sound;
import net.minecraft.client.resources.sounds.SoundInstance;
import net.minecraft.client.resources.sounds.TickableSoundInstance;
import net.minecraft.client.sounds.ChannelAccess;
import net.minecraft.client.sounds.SoundEngine;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
import java.util.Iterator;
import java.util.Map;
@Mixin(SoundEngine.class)
@@ -45,6 +55,86 @@ public abstract class SoundEngineMixin implements SoundEngineDuck {
@Shadow
public abstract boolean isActive(SoundInstance sound);
@Inject(method = "play",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/client/sounds/ChannelAccess$ChannelHandle;execute(Ljava/util/function/Consumer;)V"
),
cancellable = true
)
private void cobblemon$overrideChannelExecute(
SoundInstance sound,
CallbackInfo ci,
@Local(name = "h") float h,
@Local(name = "i") float i,
@Local(name = "g") float g,
@Local(name = "bl2") boolean bl2,
@Local(name = "bl3") boolean bl3,
@Local(name = "bl") boolean bl,
@Local(name = "vec3") Vec3 vec3,
@Local(name = "channelHandle") ChannelAccess.ChannelHandle handle
) {
if (!(sound instanceof RideLoopSound rideSound)) return;
if (handle == null) return;
handle.execute((channel) -> {
channel.setPitch(i);
channel.setVolume(h);
if (rideSound.getRideAttenuation() == RideAttenuationModel.LINEAR) {
channel.linearAttenuation(g);
} else if (rideSound.getRideAttenuation() == RideAttenuationModel.EXPONENTIAL){
((ChannelDuck) channel).cobblemon$inverseAttenuation(g);
} else {
channel.disableAttenuation();
}
// Apply low pass filter if the rideSound dictate that it should be muffled
if (rideSound.getShouldMuffle()) {
((ChannelDuck) channel).cobblemon$applyLowPassFilter(1.0f, rideSound.getMuffleAmount());
} else {
((ChannelDuck) channel).cobblemon$clearFilters();
}
channel.setLooping(bl2 && !bl3);
channel.setSelfPosition(vec3);
channel.setRelative(bl);
});
}
@Inject(
method = "tickNonPaused",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/client/sounds/ChannelAccess$ChannelHandle;execute(Ljava/util/function/Consumer;)V",
ordinal = 0
)
)
private void cobblemon$tickableSoundTick(
CallbackInfo ci,
@Local(name = "tickableSoundInstance") TickableSoundInstance instance,
@Local(name = "channelHandle", ordinal = 0) ChannelAccess.ChannelHandle handle,
@Local(name = "f", ordinal = 0) float volume,
@Local(name = "g", ordinal = 0) float pitch,
@Local(name = "vec3", ordinal = 0) Vec3 vec3
) {
if (!(instance instanceof RideLoopSound rideSound)) return;
handle.execute(channel -> {
channel.setVolume(volume);
channel.setPitch(pitch);
channel.setSelfPosition(vec3);
// Apply low pass filter if the rideSound dictate that it should be muffled
if (rideSound.getShouldMuffle()) {
((ChannelDuck) channel).cobblemon$applyLowPassFilter(1.0f, rideSound.getMuffleAmount());
} else {
((ChannelDuck) channel).cobblemon$clearFilters();
}
});
}
/** Resumes the Source belonging to the queried SoundInstance. */
private void resume(SoundInstance sound) {
ChannelAccess.ChannelHandle sourceManager = this.instanceToChannel.get(sound);
@@ -75,6 +75,7 @@ import com.cobblemon.mod.common.config.constraint.IntConstraint
import com.cobblemon.mod.common.config.starter.StarterConfig
import com.cobblemon.mod.common.data.CobblemonDataProvider
import com.cobblemon.mod.common.events.AdvancementHandler
import com.cobblemon.mod.common.events.EntityCallbackHandler
import com.cobblemon.mod.common.events.FlowHandler
import com.cobblemon.mod.common.events.PokedexHandler
import com.cobblemon.mod.common.events.ServerTickHandler
@@ -313,6 +314,7 @@ object Cobblemon {
CobblemonEvents.POKEMON_PROPERTY_INITIALISED.emit(Unit)
FlowHandler.setup()
EntityCallbackHandler.setup()
ifDedicatedServer {
isDedicatedServer = true
@@ -389,40 +389,28 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
val SWEET_HEART = noSettingsItem("sweet_heart") // todo make a SweetHeartItem class for breeding purposes
@JvmField
val TASTY_TAIL = noSettingsItem("tasty_tail")
val TASTY_TAIL = create("tasty_tail", foodItem(3, 0.3f))
@JvmField // todo get final values for this from vera
val PEWTER_CRUNCHIES = regionalFoodItem("pewter_crunchies", 16, 10, 1.2f, MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1f, false)
@JvmField // todo get final values for this from vera
val RAGE_CANDY_BAR = regionalFoodItem("rage_candy_bar", 16, 10, 1.2f, MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1f, false)
@JvmField // todo get final values for this from vera
val LAVA_COOKIE = regionalFoodItem("lava_cookie", 16, 10, 1.2f, MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1f)
@JvmField // todo get final values for this from vera
val OLD_GATEAU = regionalFoodItem("old_gateau", 16, 10, 1.2f, MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1f)
@JvmField // todo get final values for this from vera
val CASTELIACONE = regionalFoodItem("casteliacone", 16, 10, 1.2f, MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1f)
@JvmField // todo get final values for this from vera
val SHALOUR_SABLE = regionalFoodItem("shalour_sable", 16, 10, 1.2f, MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1f)
@JvmField // todo get final values for this from vera
val LUMIOSE_GALETTE = regionalFoodItem("lumiose_galette", 16, 10, 1.2f, MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1f)
@JvmField // todo get final values for this from vera
val BIG_MALASADA = regionalFoodItem("big_malasada", 16, 10, 1.2f, MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1f)
@JvmField // todo get final values for this from vera
val SMOKED_TAIL_CURRY = regionalFoodItem("smoked_tail_curry", 16, 10, 1.2f, MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1f, false, ItemStack(Items.BOWL, 1))
@JvmField // todo get final values for this from vera
val JUBILIFE_MUFFIN = regionalFoodItem("jubilife_muffin", 16, 10, 1.2f, MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1f)
@JvmField // todo get final values for this from vera
val OPEN_FACED_SANDWICH = regionalFoodItem("open_faced_sandwich", 16, 10, 1.2f, MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1f)
@JvmField
val PEWTER_CRUNCHIES = regionalFoodItem("pewter_crunchies", 16, 7, 0.343f, false)
@JvmField
val RAGE_CANDY_BAR = regionalFoodItem("rage_candy_bar", 16, 8, 0.2f, false)
@JvmField
val LAVA_COOKIE = regionalFoodItem("lava_cookie", 16, 7, 0.343f, false)
@JvmField
val OLD_GATEAU = regionalFoodItem("old_gateau", 16, 9, 0.1335f, false)
@JvmField
val CASTELIACONE = regionalFoodItem("casteliacone", 16, 5, .28f, false)
@JvmField
val LUMIOSE_GALETTE = regionalFoodItem("lumiose_galette", 16, 8, 0.2f, false)
@JvmField
val BIG_MALASADA = regionalFoodItem("big_malasada", 16, 7, 0.343f, false)
@JvmField
val SMOKED_TAIL_CURRY = regionalFoodItem("smoked_tail_curry", 16, 10, 0.6f, false, ItemStack(Items.BOWL, 1))
@JvmField
val JUBILIFE_MUFFIN = regionalFoodItem("jubilife_muffin", 16, 8, 0.2f, false)
@JvmField
val OPEN_FACED_SANDWICH = regionalFoodItem("open_faced_sandwich", 16, 13, 0.5f, false)
// todo we might need to wait on these for later? These impact battles and may be harder to do
/*@JvmField
@@ -435,28 +423,21 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val POTATO_MOCHI = create("potato_mochi", CobblemonItem(Properties().stacksTo(16)
.food(FoodProperties.Builder()
.nutrition(10) // todo get final values for this from vera
.saturationModifier(1.2F) // todo get final values for this from vera
.build())))
@JvmField
val FRIED_RICE = create("fried_rice", CobblemonItem(Properties().stacksTo(16)
.food(FoodProperties.Builder()
.nutrition(10) // todo get final values for this from vera
.saturationModifier(1.2F) // todo get final values for this from vera
.usingConvertsTo(Items.BOWL)
.nutrition(8)
.saturationModifier(0.2375F)
.build())))
@JvmField
val CANDIED_APPLE = create("candied_apple", CobblemonItem(Properties().stacksTo(16)
.food(FoodProperties.Builder()
.nutrition(10) // todo get final values for this from vera
.saturationModifier(1.2F) // todo get final values for this from vera
.nutrition(6)
.saturationModifier(0.2335F)
.usingConvertsTo(Items.STICK)
.build())))
@JvmField
val CANDIED_BERRY = create("candied_berry", CobblemonItem(Properties().stacksTo(16)
.food(FoodProperties.Builder()
.nutrition(10) // todo get final values for this from vera
.saturationModifier(1.2F) // todo get final values for this from vera
.nutrition(5)
.saturationModifier(0.22F)
.usingConvertsTo(Items.STICK)
.build())))
@@ -684,14 +665,12 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val MEDICINAL_LEEK = heldItem("medicinal_leek", MedicinalLeekItem(CobblemonBlocks.MEDICINAL_LEEK, Item.Properties().food(FoodProperties.Builder().fast().nutrition(1).saturationModifier(0.2f).build())), "leek")
@JvmField
val ROASTED_LEEK = compostableItem("roasted_leek", CobblemonItem(Item.Properties().food(FoodProperties.Builder().fast().nutrition(3).saturationModifier(0.3f).build())), 0.85f)
@JvmField
val BRAISED_VIVICHOKE = compostableItem("braised_vivichoke", foodItem(6, 0.6F), 0.85f)
val ROASTED_LEEK = compostableItem("roasted_leek", CobblemonItem(Item.Properties().food(FoodProperties.Builder().fast().nutrition(3).saturationModifier(0.2f).build())), 0.85f)
@JvmField
val VIVICHOKE_DIP = create("vivichoke_dip", object : CobblemonItem(Properties().stacksTo(1)
.food(FoodProperties.Builder()
.nutrition(10)
.saturationModifier(1.2F)
.saturationModifier(0.6F)
.effect(MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1F)
.alwaysEdible()
.usingConvertsTo(Items.BOWL)
@@ -702,7 +681,7 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
}
})
@JvmField
val ENERGY_ROOT = compostableItem("energy_root", EnergyRootItem(CobblemonBlocks.ENERGY_ROOT, Item.Properties().food(FoodProperties.Builder().nutrition(1).fast().saturationModifier(0.2f).build())), 0.60f)
val ENERGY_ROOT = compostableItem("energy_root", EnergyRootItem(CobblemonBlocks.ENERGY_ROOT, Properties()), 0.60f)
@JvmField
val REVIVAL_HERB = compostableItem("revival_herb", RevivalHerbItem(CobblemonBlocks.REVIVAL_HERB))
@JvmField
@@ -732,7 +711,7 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val HEAL_POWDER = create("heal_powder", HealPowderItem())
@JvmField
val LEEK_AND_POTATO_STEW = create("leek_and_potato_stew", CobblemonItem(Item.Properties().food(FoodProperties.Builder().nutrition(8).saturationModifier(0.6f).usingConvertsTo(Items.BOWL).build()).stacksTo(1)))
val LEEK_AND_POTATO_STEW = create("leek_and_potato_stew", CobblemonItem(Item.Properties().food(FoodProperties.Builder().nutrition(6).saturationModifier(0.6f).usingConvertsTo(Items.BOWL).build()).stacksTo(1)))
@JvmField
val REVIVE = create("revive", ReviveItem(max = false))
@JvmField
@@ -1543,8 +1522,6 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
stacksTo: Int,
nutrition: Int,
saturationModifier: Float,
effect: MobEffectInstance? = null,
effectChance: Float? = null,
alwaysEdible: Boolean = false,
convertsToOnUse: ItemStack? = null
): RegionalFoodItem {
@@ -1552,10 +1529,6 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
.nutrition(nutrition)
.saturationModifier(saturationModifier)
if (effect != null && effectChance != null) {
foodPropertiesBuilder.effect(effect, effectChance)
}
if (alwaysEdible == true) {
foodPropertiesBuilder.alwaysEdible()
}
@@ -275,6 +275,11 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, ResourceKey<Regi
RELIC_COIN_SACK_STEP
)
@JvmField
val RIDE_LOOP_LEATHER = create("ride.loop.leather")
@JvmField
val RIDE_LOOP_PLUMAGE = create("ride.loop.plumage")
@JvmField
val MOVE_QUICKATTACK_TARGET = this.create("move.quickattack.target")
@JvmField
@@ -319,6 +324,8 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, ResourceKey<Regi
@JvmField
val IMPACT_WATER = this.create("impact.water")
@JvmField
val TUMBLESTONE_SOUNDS = SoundType(1f, 1.1f,
TUMBLESTONE_BREAK,
@@ -8,8 +8,15 @@
package com.cobblemon.mod.common.api.ai
import com.bedrockk.molang.runtime.MoLangRuntime
import com.cobblemon.mod.common.api.ai.config.BehaviourConfig
import com.cobblemon.mod.common.api.molang.ExpressionLike
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.setup
import com.cobblemon.mod.common.api.scripting.CobblemonScripts
import com.cobblemon.mod.common.entity.MoLangScriptingEntity
import com.cobblemon.mod.common.util.resolve
import com.cobblemon.mod.common.util.withQueryValue
import com.mojang.serialization.Dynamic
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.entity.LivingEntity
@@ -28,6 +35,9 @@ class BehaviourConfigurationContext {
val memories = mutableSetOf<MemoryModuleType<*>>()
val sensors = mutableSetOf<SensorType<*>>()
val onAddScripts = mutableListOf<ResourceLocation>()
val onAdd = mutableListOf<ExpressionLike>()
fun getOrCreateActivity(activity: Activity): ActivityConfigurationContext {
return activities.firstOrNull { it.activity == activity } ?: ActivityConfigurationContext(activity).also(activities::add)
}
@@ -48,6 +58,14 @@ class BehaviourConfigurationContext {
this.sensors.addAll(sensors)
}
fun addOnAddScript(script: ResourceLocation) {
onAddScripts.add(script)
}
fun addOnAddScript(script: ExpressionLike) {
onAdd.add(script)
}
fun apply(entity: LivingEntity, behaviourConfigs: List<BehaviourConfig>, dynamic: Dynamic<*>) {
if (entity is MoLangScriptingEntity) {
@@ -62,6 +80,11 @@ class BehaviourConfigurationContext {
// Apply the brain config
if (entity is MoLangScriptingEntity) {
brain = entity.assignNewBrainWithMemoriesAndSensors(dynamic, memories, sensors)
val runtime = MoLangRuntime().setup()
runtime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
onAddScripts.forEach { CobblemonScripts.run(it, runtime) }
onAdd.forEach { runtime.resolve(it) }
}
activities.forEach { it.apply(entity) }
brain.setCoreActivities(coreActivities)
@@ -9,7 +9,17 @@
package com.cobblemon.mod.common.api.ai
import com.bedrockk.molang.Expression
import com.bedrockk.molang.runtime.MoLangRuntime
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.setup
import com.cobblemon.mod.common.api.molang.ObjectValue
import com.cobblemon.mod.common.api.scripting.CobblemonScripts
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import com.cobblemon.mod.common.util.resolve
import net.minecraft.world.entity.Entity
/**
* This is used in the AI system to store optional additional information about the attack target,
@@ -30,5 +40,32 @@ import com.cobblemon.mod.common.util.asExpression
*/
class CobblemonAttackTargetData(
/** An expression that returns false when the target is no longer worth attacking. */
val shouldContinue: Expression = "true".asExpression()
)
var shouldContinue: Expression = "true".asExpression(),
var hitsUntilDisengage: Int = -1, // -1 means no limit
var disengageAction: (entity: Entity) -> Unit = {}
) {
val struct = ObjectValue<CobblemonAttackTargetData>(this).also {
it.addFunction("hits_until_disengage") { DoubleValue(hitsUntilDisengage.toDouble()) }
it.addFunction("set_hits_until_disengage") { params ->
val value = params.getDouble(0)
hitsUntilDisengage = value.toInt()
DoubleValue(hitsUntilDisengage.toDouble())
}
it.addFunction("set_disengage_action") { params ->
val script = params.getString(0).asIdentifierDefaultingNamespace()
val function = CobblemonScripts.scripts[script] ?: run {
Cobblemon.LOGGER.warn("set_disengage_action was given script '$script' which does not exist.")
return@addFunction DoubleValue.ZERO
}
disengageAction = { entity ->
val runtime = MoLangRuntime().setup()
runtime.environment.query.addFunction("entity") { entity.asMostSpecificMoLangValue() }
runtime.resolve(function)
}
DoubleValue.ONE
}
it.addFunction("unset_disengage_action") { params ->
disengageAction = {}
}
}
}
@@ -17,6 +17,7 @@ import com.cobblemon.mod.common.api.scripting.CobblemonScripts
import com.cobblemon.mod.common.util.asTranslated
import com.cobblemon.mod.common.util.resolve
import com.cobblemon.mod.common.util.withQueryValue
import com.google.gson.annotations.SerializedName
import net.minecraft.network.chat.Component
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.entity.LivingEntity
@@ -28,23 +29,35 @@ class CobblemonBehaviour(
val visible: Boolean = true,
val entityType: ResourceLocation? = null,
val configurations: List<BehaviourConfig> = mutableListOf(),
val undo: ExpressionLike? = null,
val undoScript: ResourceLocation? = null,
// I feel like the onAdd+onAddScript etc could be merged using some interface and a clever deserializer,
// detect if it's a ResourceLocation and failing that, Expression. The on[..]Script fields are kinda fringe though.
@SerializedName("onRemove", alternate = ["undo"])
val onRemove: ExpressionLike? = null,
@SerializedName("onRemoveScript", alternate = ["undoScript"])
val onRemoveScript: ResourceLocation? = null,
val onAdd: ExpressionLike? = null,
val onAddScript: ResourceLocation? = null,
) {
fun canBeApplied(entity: LivingEntity) = entityType?.let { entityType == entity.type.builtInRegistryHolder().unwrapKey().get().location() } != false
fun configure(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext) {
if (onAdd != null) {
behaviourConfigurationContext.addOnAddScript(onAdd)
}
if (onAddScript != null) {
behaviourConfigurationContext.addOnAddScript(onAddScript)
}
configurations.forEach { it.configure(entity, behaviourConfigurationContext) }
}
/** Undoes anything that needs undoing once this configuration is being removed from an entity that had it before. */
fun undo(entity: LivingEntity) {
fun onRemove(entity: LivingEntity) {
val runtime = MoLangRuntime().setup()
runtime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
if (undoScript != null) {
CobblemonScripts.run(undoScript, runtime)
if (onRemoveScript != null) {
CobblemonScripts.run(onRemoveScript, runtime)
}
if (undo != null) {
runtime.resolve(undo)
if (onRemove != null) {
runtime.resolve(onRemove)
}
}
}
@@ -0,0 +1,29 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.ai
import net.minecraft.world.entity.ai.behavior.BlockPosTracker
import net.minecraft.world.phys.Vec3
/**
* Wraps around [BlockPosTracker] to add additional flags for Cobblemon-specific behavior.
*
* @author Hiroku
* @since August 4th, 2025
*/
class CobblemonBlockPosTracker(
pos: Vec3,
val flags: Set<String> = emptySet(),
) : BlockPosTracker(pos) {
companion object {
const val BATTLE_LOOK_BYPASS_FLAG = "battle_look_bypass"
}
fun isBattleLookBypass() = flags.contains(BATTLE_LOOK_BYPASS_FLAG)
}
@@ -108,7 +108,7 @@ class WaterWanderTaskConfig : SingleTaskConfig {
completionRange = 0
)
)
lookTarget.set(BlockPosTracker(target /* trust me dude. */))
lookTarget.set(BlockPosTracker(target))
return@Trigger true
}
}
@@ -13,6 +13,7 @@ import com.bedrockk.molang.runtime.struct.QueryStruct
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.Cobblemon.LOGGER
import com.cobblemon.mod.common.CobblemonBuildDetails
import com.cobblemon.mod.common.CobblemonNetwork
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.actor.ActorType
@@ -77,7 +78,7 @@ open class PokemonBattle(
val side2: BattleSide
) {
/** Whether logging will be silenced for this battle. */
var mute = true
var mute = !CobblemonBuildDetails.SNAPSHOT
val struct = this.asMoLangValue()
val runtime = MoLangRuntime().also { it.environment.query = struct }
@@ -10,6 +10,7 @@ package com.cobblemon.mod.common.api.events.pokeball
import com.cobblemon.mod.common.api.events.Cancelable
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.moLangFunctionMap
import com.cobblemon.mod.common.entity.pokeball.EmptyPokeBallEntity
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
@@ -20,8 +21,9 @@ class ThrownPokeballHitEvent(
val pokeBall : EmptyPokeBallEntity,
val pokemon : PokemonEntity
) : Cancelable() {
val context = mutableMapOf(
"pokeball" to pokeBall.struct,
"pokemon" to pokemon.asMoLangValue()
val functions = moLangFunctionMap(
"pokeball" to { pokeBall.struct },
"pokemon" to { pokemon.asMoLangValue() },
cancelFunc
)
}
@@ -23,7 +23,9 @@ import com.cobblemon.mod.common.CobblemonBlockEntities
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.CobblemonUnlockableWallpapers
import com.cobblemon.mod.common.Environment
import com.cobblemon.mod.common.api.ai.CobblemonBlockPosTracker
import com.cobblemon.mod.common.api.ai.CobblemonWanderControl
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.battles.model.actor.ActorType
import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
@@ -32,7 +34,6 @@ import com.cobblemon.mod.common.api.dialogue.ReferenceDialogueFaceProvider
import com.cobblemon.mod.common.api.drop.DropEntry
import com.cobblemon.mod.common.api.mark.Marks
import com.cobblemon.mod.common.api.moves.BenchedMove
import com.cobblemon.mod.common.api.moves.MoveTemplate
import com.cobblemon.mod.common.api.moves.Moves
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
import com.cobblemon.mod.common.api.moves.animations.ActionEffects
@@ -57,6 +58,7 @@ import com.cobblemon.mod.common.api.scheduling.ClientTaskTracker
import com.cobblemon.mod.common.api.scheduling.Schedulable
import com.cobblemon.mod.common.api.scheduling.ServerTaskTracker
import com.cobblemon.mod.common.api.scripting.CobblemonScripts
import com.cobblemon.mod.common.api.spawning.TimeRange
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
import com.cobblemon.mod.common.api.storage.PokemonStore
import com.cobblemon.mod.common.api.storage.party.NPCPartyStore
@@ -68,13 +70,11 @@ import com.cobblemon.mod.common.api.tags.CobblemonItemTags
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.battles.BattleBuilder
import com.cobblemon.mod.common.battles.BattleFormat
import com.cobblemon.mod.common.battles.BattleFormat.Companion.GEN_9_DOUBLES
import com.cobblemon.mod.common.battles.BattleFormat.Companion.GEN_9_SINGLES
import com.cobblemon.mod.common.battles.BattleFormat.Companion.GEN_9_TRIPLES
import com.cobblemon.mod.common.battles.BattleRegistry
import com.cobblemon.mod.common.battles.actor.PlayerBattleActor
import com.cobblemon.mod.common.battles.actor.PokemonBattleActor
import com.cobblemon.mod.common.client.render.models.blockbench.wavefunction.WaveFunctions
import com.cobblemon.mod.common.entity.MoLangScriptingEntity
import com.cobblemon.mod.common.entity.PosableEntity
import com.cobblemon.mod.common.entity.npc.NPCBattleActor
import com.cobblemon.mod.common.entity.npc.NPCEntity
@@ -143,6 +143,12 @@ import net.minecraft.world.level.levelgen.Heightmap
import net.minecraft.world.level.pathfinder.PathType
import net.minecraft.world.phys.AABB
import net.minecraft.world.phys.Vec3
import java.time.Instant
import java.time.LocalDateTime
import java.time.ZoneId
import java.time.ZoneOffset
import java.time.format.DateTimeFormatter
import java.util.*
/**
* Holds a bunch of useful MoLang trickery that can be used or extended in API
@@ -168,6 +174,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))
@@ -275,16 +295,44 @@ object MoLangFunctions {
DoubleValue(System.currentTimeMillis())
},
// the rest of the world use dd/MM/yyyy grow up america (this comment was generated by copilot)
"date_local_time" to java.util.function.Function { _ ->
val time = System.currentTimeMillis()
val date = java.util.Date(time)
val formatted = java.text.SimpleDateFormat("DD/MM/YYYY").format(date)
"date_local_time" to java.util.function.Function { params ->
val useTimeZone = params.get<MoValue>(0)
var timezone: ZoneId? = null
if (useTimeZone is DoubleValue){
timezone = if (useTimeZone.value == 1.0) Cobblemon.config.timeZoneId else null
} else if (useTimeZone is StringValue){
try {
timezone = ZoneId.of(useTimeZone.value)
} catch (_: Exception) {
Cobblemon.LOGGER.error("Error parsing timezone: ${useTimeZone.value}")
return@Function DoubleValue.ZERO
}
}
val date: LocalDateTime = if (timezone != null) LocalDateTime.now(timezone) else LocalDateTime.now()
val formatted = DateTimeFormatter.ofPattern("dd/MM/uuuu").format(date)
StringValue(formatted)
},
"date_of" to java.util.function.Function { params ->
val time = params.getDouble(0).toLong()
val useTimeZone = params.get<MoValue>(1)
var timezone: ZoneId? = null
if (useTimeZone is DoubleValue){
timezone = if (useTimeZone.value == 1.0) Cobblemon.config.timeZoneId else null
} else if (useTimeZone is StringValue){
try {
timezone = ZoneId.of(useTimeZone.value)
} catch (_: Exception) {
Cobblemon.LOGGER.error("Error parsing timezone: ${useTimeZone.value}")
return@Function DoubleValue.ZERO
}
}
val date = java.util.Date(time)
val formatted = java.text.SimpleDateFormat("DD/MM/YYYY").format(date)
val instant: Instant = date.toInstant()
val formatted = DateTimeFormatter.ofPattern("dd/MM/uuuu").format(LocalDateTime.ofInstant(instant,
timezone ?: ZoneOffset.systemDefault()
))
StringValue(formatted)
},
"date_is_after" to java.util.function.Function { params ->
@@ -305,6 +353,12 @@ object MoLangFunctions {
{ worldHolder ->
val world = worldHolder.value()
val map = hashMapOf<String, java.util.function.Function<MoParams, Any>>()
map.put("is_time_of_day") { params ->
val timeOfDay = TimeRange.timeRanges[params.getString(0).lowercase()]
?: return@put DoubleValue.ZERO
val time = world.dayTime % 24000
return@put DoubleValue(timeOfDay.contains(time.toInt()))
}
map.put("game_time") { _ -> DoubleValue(world.gameTime.toDouble()) }
map.put("time_of_day") {
val time = world.dayTime % 24000
@@ -640,7 +694,7 @@ object MoLangFunctions {
map
}
)
val itemStackFunctions = mutableListOf<(ItemStack, RegistryAccess) -> HashMap<String, java.util.function.Function<MoParams, Any>>>(
val itemStackFunctions = mutableListOf<(ItemStack, RegistryAccess) -> java.util.HashMap<String, java.util.function.Function<MoParams, Any>>>(
{ stack, registryAccess ->
val itemRegistry = registryAccess.registryOrThrow(Registries.ITEM)
val holder = itemRegistry.wrapAsHolder(stack.item)
@@ -659,7 +713,7 @@ object MoLangFunctions {
}
)
val entityFunctions: MutableList<(Entity) -> HashMap<String, java.util.function.Function<MoParams, Any>>> = mutableListOf(
val entityFunctions: MutableList<(Entity) -> java.util.HashMap<String, java.util.function.Function<MoParams, Any>>> = mutableListOf(
{ entity ->
val map = hashMapOf<String, java.util.function.Function<MoParams, Any>>()
map.put("uuid") { _ -> StringValue(entity.uuid.toString()) }
@@ -688,6 +742,34 @@ object MoLangFunctions {
return@put owner?.asMostSpecificMoLangValue() ?: DoubleValue.ZERO
}
if (entity is MoLangScriptingEntity) {
map.put("add_callback") { params ->
val type = params.getString(0).asIdentifierDefaultingNamespace()
val callback = params.getString(1).asIdentifierDefaultingNamespace()
val allowDuplicates = params.getBooleanOrNull(2) ?: false
entity.callbacks.addCallback(type, callback, allowDuplicates)
}
map.put("remove_callback") { params ->
val type = params.getString(0).asIdentifierDefaultingNamespace()
val callback = params.getString(1).asIdentifierDefaultingNamespace()
entity.callbacks.removeCallback(type, callback)
}
map.put("has_callback") { params ->
val type = params.getString(0).asIdentifierDefaultingNamespace()
val callback = params.getString(1).asIdentifierDefaultingNamespace()
return@put DoubleValue(callback in (entity.callbacks[type] ?: emptySet()))
}
map.put("clear_callbacks") { params ->
val type = params.getStringOrNull(0)?.asIdentifierDefaultingNamespace()
if (type != null) {
entity.callbacks[type]?.clear()
} else {
entity.callbacks.clear()
}
return@put DoubleValue.ONE
}
}
if (entity is PathfinderMob) {
map.put("walk_to") { params ->
val x = params.getDouble(0)
@@ -869,7 +951,7 @@ object MoLangFunctions {
}
)
val livingEntityFunctions: MutableList<(LivingEntity) -> HashMap<String, java.util.function.Function<MoParams, Any>>> = mutableListOf<(LivingEntity) -> HashMap<String, java.util.function.Function<MoParams, Any>>>(
val livingEntityFunctions: MutableList<(LivingEntity) -> java.util.HashMap<String, java.util.function.Function<MoParams, Any>>> = mutableListOf<(LivingEntity) -> java.util.HashMap<String, java.util.function.Function<MoParams, Any>>>(
{ entity ->
val map = hashMapOf<String, java.util.function.Function<MoParams, Any>>()
map.put("is_living_entity") { DoubleValue.ONE }
@@ -877,6 +959,15 @@ object MoLangFunctions {
map.put("is_sleeping") { _ -> DoubleValue(entity.isSleeping) }
map.put("health") { _ -> DoubleValue(entity.health) }
map.put("max_health") { _ -> DoubleValue(entity.maxHealth) }
map.put("look_at_position") { params ->
val x = params.getDouble(0)
val y = params.getDouble(1)
val z = params.getDouble(2)
val duration = params.getIntOrNull(3) ?: 20
val flags = params.params.subList(4, params.params.size).map { it.asString() }
val brain = entity.brain
brain.setMemoryWithExpiry(MemoryModuleType.LOOK_TARGET, CobblemonBlockPosTracker(Vec3(x, y, z), flags.toSet()), duration.toLong())
}
map.put("get_wander_control_memory") {
val value = entity.brain.getMemorySafely(CobblemonMemories.WANDER_CONTROL).orElse(null)
?: CobblemonWanderControl()
@@ -914,6 +1005,22 @@ object MoLangFunctions {
return@put DoubleValue.ZERO
}
}
map.put("set_uuid_memory") { params ->
val memory = params.getString(0).asIdentifierDefaultingNamespace().let(BuiltInRegistries.MEMORY_MODULE_TYPE::get) as MemoryModuleType<UUID>
val uuid = params.getString(1).asUUID ?: return@put DoubleValue.ZERO
val expiry = params.getIntOrNull(2) ?: -1
if (expiry != -1) {
entity.brain.setMemoryWithExpiry(memory, uuid, expiry.toLong())
} else {
entity.brain.setMemory(memory, uuid)
}
return@put DoubleValue.ONE
}
map.put("erase_memory") { params ->
val memories = params.params.map { it.asString().asIdentifierDefaultingNamespace() }.map(BuiltInRegistries.MEMORY_MODULE_TYPE::get)
memories.forEach { entity.brain.eraseMemory(it) }
return@put DoubleValue.ONE
}
map.put("has_memory_value") { params ->
val memoryTypes = params.params.map {
it.asString().asIdentifierDefaultingNamespace()
@@ -1232,6 +1339,23 @@ object MoLangFunctions {
}
)
val battleMessageFunctions = mutableListOf<(BattleMessage) -> HashMap<String, java.util.function.Function<MoParams, Any>>>(
{ message ->
val map = hashMapOf<String, java.util.function.Function<MoParams, Any>>()
map.put("has_argument_at") { params ->
val index = params.getInt(0)
val value = params.getStringOrNull(1) ?: ""
return@put DoubleValue(value == message.argumentAt(index))
}
map.put("has_argument") { params ->
val argumentName = params.getString(0)
val value = params.getStringOrNull(1) ?: ""
return@put DoubleValue(value == message.optionalArgument(argumentName))
}
return@mutableListOf map
}
)
val pokemonFunctions = mutableListOf<(Pokemon) -> HashMap<String, java.util.function.Function<MoParams, Any>>>(
{ pokemon ->
val map = hashMapOf<String, java.util.function.Function<MoParams, Any>>()
@@ -1896,7 +2020,7 @@ object MoLangFunctions {
}
)
val pokemonPropertiesFunctions: MutableList<(PokemonProperties) -> HashMap<String, java.util.function.Function<MoParams, Any>>> = mutableListOf(
val pokemonPropertiesFunctions: MutableList<(PokemonProperties) -> java.util.HashMap<String, java.util.function.Function<MoParams, Any>>> = mutableListOf(
{ props ->
val map = hashMapOf<String, java.util.function.Function<MoParams, Any>>()
@@ -1954,7 +2078,7 @@ object MoLangFunctions {
}
)
val evolutionFunctions: MutableList<(Evolution) -> HashMap<String, java.util.function.Function<MoParams, Any>>> = mutableListOf(
val evolutionFunctions: MutableList<(Evolution) -> java.util.HashMap<String, java.util.function.Function<MoParams, Any>>> = mutableListOf(
{ evolution ->
val map = hashMapOf<String, java.util.function.Function<MoParams, Any>>()
@@ -2190,6 +2314,14 @@ object MoLangFunctions {
return this
}
fun QueryStruct.addBattleMessageFunctions(battleMessage: BattleMessage): QueryStruct {
val addedFunctions = battleMessageFunctions
.flatMap { it.invoke(battleMessage).entries }
.associate { it.key to it.value }
functions.putAll(addedFunctions)
return this
}
fun QueryStruct.addPokemonEntityFunctions(pokemonEntity: PokemonEntity): QueryStruct {
val addedFunctions = pokemonEntityFunctions
.flatMap { it.invoke(pokemonEntity).entries }
@@ -37,6 +37,7 @@ import com.cobblemon.mod.common.api.pokemon.stats.Stat
import com.cobblemon.mod.common.api.pokemon.stats.Stats
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourSettings
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
import com.cobblemon.mod.common.api.riding.stats.RidingStatDefinition
import com.cobblemon.mod.common.api.spawning.TimeRange
import com.cobblemon.mod.common.api.types.ElementalType
@@ -127,6 +128,7 @@ object PokemonSpecies : JsonDataRegistry<Species> {
.registerTypeAdapter(ItemPredicate::class.java, LegacyItemConditionWrapperAdapter)
.registerTypeAdapter(RidingBehaviourSettings::class.java, RidingBehaviourSettingsAdapter)
.registerTypeAdapter(RidingStatDefinition::class.java, RidingStatDefinitionAdapter)
.registerTypeAdapter(RideSoundSettingsList::class.java, RideSoundSettingsListAdapter)
.registerTypeAdapter(Expression::class.java, ExpressionAdapter)
.disableHtmlEscaping()
.enableComplexMapKeySerialization()
@@ -9,6 +9,7 @@
package com.cobblemon.mod.common.api.riding.behaviour
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import net.minecraft.resources.ResourceLocation
@@ -88,6 +89,8 @@ interface RidingBehaviour<Settings : RidingBehaviourSettings, State : RidingBeha
fun shouldRotatePlayerHead(settings: Settings, state: State, vehicle: PokemonEntity): Boolean
fun getRideSounds(settings: Settings, state: State, vehicle: PokemonEntity): RideSoundSettingsList
fun maxUpStep(settings: Settings, state: State, vehicle: PokemonEntity): Float? = null
fun canStopRiding(settings: Settings, state: State, vehicle: PokemonEntity, passenger: Player): Boolean = true
@@ -9,6 +9,7 @@
package com.cobblemon.mod.common.api.riding.behaviour
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.resources.ResourceLocation
@@ -190,6 +191,11 @@ class RidingController<Settings : RidingBehaviourSettings, State : RidingBehavio
return behaviour.maxUpStep(settings, state, vehicle)
}
override fun getRideSounds(settings: Settings, state: State, vehicle: PokemonEntity): RideSoundSettingsList {
if (!isActive(settings, state, vehicle)) return RideSoundSettingsList()
return behaviour.getRideSounds(settings, state, vehicle)
}
override fun createDefaultState(settings: Settings): State {
return behaviour.createDefaultState(settings)
}
@@ -16,6 +16,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.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.*
@@ -23,6 +25,7 @@ import com.cobblemon.mod.common.util.math.geometry.toRadians
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import net.minecraft.sounds.SoundEvent
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
@@ -469,6 +472,14 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
return true
}
override fun getRideSounds(
settings: BirdSettings,
state: BirdState,
vehicle: PokemonEntity
): RideSoundSettingsList {
return settings.rideSounds
}
override fun createDefaultState(settings: BirdSettings) = BirdState()
}
@@ -491,8 +502,11 @@ class BirdSettings : RidingBehaviourSettings {
var glidespeedExpr: Expression = "q.get_ride_stats('SPEED', 'AIR', 2.0, 1.0)".asExpression()
private set
var rideSounds: RideSoundSettingsList = RideSoundSettingsList()
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
rideSounds.encode(buffer)
buffer.writeExpression(infiniteAltitude)
buffer.writeExpression(infiniteStamina)
buffer.writeExpression(glidespeedExpr)
@@ -504,6 +518,7 @@ class BirdSettings : RidingBehaviourSettings {
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
rideSounds = RideSoundSettingsList.decode(buffer)
infiniteAltitude = buffer.readExpression()
infiniteStamina = buffer.readExpression()
glidespeedExpr = buffer.readExpression()
@@ -17,6 +17,7 @@ import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourSettings
import com.cobblemon.mod.common.api.riding.posing.PoseOption
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
@@ -187,6 +188,10 @@ class GliderBehaviour : RidingBehaviour<GliderSettings, RidingBehaviourState> {
return false
}
override fun getRideSounds(settings: GliderSettings, state: RidingBehaviourState, vehicle: PokemonEntity): RideSoundSettingsList {
return settings.rideSounds
}
override fun createDefaultState(settings: GliderSettings) = RidingBehaviourState()
}
@@ -202,14 +207,18 @@ class GliderSettings : RidingBehaviourSettings {
var canStrafe: Expression = "false".asExpression()
private set
var rideSounds: RideSoundSettingsList = RideSoundSettingsList()
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
rideSounds.encode(buffer)
buffer.writeExpression(glideSpeed)
buffer.writeExpression(speed)
buffer.writeExpression(canStrafe)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
rideSounds = RideSoundSettingsList.decode(buffer)
glideSpeed = buffer.readExpression()
speed = buffer.readExpression()
canStrafe = buffer.readExpression()
@@ -17,6 +17,7 @@ import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourSettings
import com.cobblemon.mod.common.api.riding.posing.PoseOption
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.*
@@ -272,6 +273,10 @@ class HelicopterBehaviour : RidingBehaviour<HelicopterSettings, RidingBehaviourS
return false
}
override fun getRideSounds(settings: HelicopterSettings, state: RidingBehaviourState, vehicle: PokemonEntity): RideSoundSettingsList {
return settings.rideSounds
}
override fun createDefaultState(settings: HelicopterSettings) = RidingBehaviourState()
}
@@ -291,8 +296,11 @@ class HelicopterSettings : RidingBehaviourSettings {
var speed: Expression = "1.0".asExpression()
private set
var rideSounds: RideSoundSettingsList = RideSoundSettingsList()
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
rideSounds.encode(buffer)
buffer.writeExpression(gravity)
buffer.writeExpression(horizontalAcceleration)
buffer.writeExpression(verticalVelocity)
@@ -300,6 +308,7 @@ class HelicopterSettings : RidingBehaviourSettings {
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
rideSounds = RideSoundSettingsList.decode(buffer)
gravity = buffer.readExpression()
horizontalAcceleration = buffer.readExpression()
verticalVelocity = buffer.readExpression()
@@ -13,6 +13,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.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.*
@@ -458,12 +460,24 @@ class HoverBehaviour : RidingBehaviour<HoverSettings, HoverState> {
return false
}
override fun getRideSounds(
settings: HoverSettings,
state: HoverState,
vehicle: PokemonEntity
): RideSoundSettingsList {
return settings.rideSounds
}
override fun createDefaultState(settings: HoverSettings) = HoverState()
}
class HoverSettings : RidingBehaviourSettings {
override val key = HoverBehaviour.KEY
var rideSound: ResourceLocation = "ride.loop.saucer".asIdentifierDefaultingNamespace()
var volumeExpr: Expression = "math.pow(math.min(q.ride_velocity() / 0.8, 1.0),2)".asExpression()
var pitchExpr: Expression = "math.pow(math.min(1.0 + 0.2*(q.ride_velocity() / 0.8), 1.2),2)".asExpression()
var canJump = "true".asExpression()
private set
@@ -486,8 +500,11 @@ class HoverSettings : RidingBehaviourSettings {
var handlingExpr: Expression = "q.get_ride_stats('SKILL', 'AIR', 140.0, 20.0)".asExpression()
private set
var rideSounds: RideSoundSettingsList = RideSoundSettingsList()
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
rideSounds.encode(buffer)
buffer.writeExpression(speedExpr)
buffer.writeExpression(accelerationExpr)
buffer.writeExpression(staminaExpr)
@@ -496,6 +513,7 @@ class HoverSettings : RidingBehaviourSettings {
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
rideSounds = RideSoundSettingsList.decode(buffer)
speedExpr = buffer.readExpression()
accelerationExpr = buffer.readExpression()
staminaExpr = buffer.readExpression()
@@ -21,6 +21,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 net.minecraft.resources.ResourceLocation
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
@@ -380,10 +382,22 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
return false
}
override fun shouldRotatePlayerHead(settings: JetSettings, state: JetState, vehicle: PokemonEntity): Boolean {
override fun shouldRotatePlayerHead(
settings: JetSettings,
state: JetState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun getRideSounds(
settings: JetSettings,
state: JetState,
vehicle: PokemonEntity
): RideSoundSettingsList {
return settings.rideSounds
}
override fun createDefaultState(settings: JetSettings) = JetState()
override fun damageOnCollision(
@@ -428,8 +442,11 @@ class JetSettings : RidingBehaviourSettings {
var staminaExpr: Expression = "q.get_ride_stats('STAMINA', 'AIR', 60.0, 10.0)".asExpression()
private set
var rideSounds: RideSoundSettingsList = RideSoundSettingsList()
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
rideSounds.encode(buffer)
buffer.writeExpression(gravity)
buffer.writeExpression(minSpeed)
buffer.writeExpression(handlingYawExpr)
@@ -443,6 +460,7 @@ class JetSettings : RidingBehaviourSettings {
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
rideSounds = RideSoundSettingsList.decode(buffer)
gravity = buffer.readExpression()
minSpeed = buffer.readExpression()
handlingYawExpr = buffer.readExpression()
@@ -11,6 +11,7 @@ package com.cobblemon.mod.common.api.riding.behaviour.types.composite
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.*
import com.cobblemon.mod.common.api.riding.behaviour.types.composite.strategies.CompositeRidingStrategies
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.adapters.RidingBehaviourSettingsAdapter
@@ -327,6 +328,16 @@ class CompositeBehaviour : RidingBehaviour<CompositeSettings, CompositeState> {
}
}
override fun getRideSounds(
settings: CompositeSettings,
state: CompositeState,
vehicle: PokemonEntity
): RideSoundSettingsList {
return chooseBehaviour(settings, state) { behaviour, behaviourSettings, behaviourState ->
behaviour.getRideSounds(behaviourSettings, behaviourState, vehicle)
}
}
override fun isActive(
settings: CompositeSettings,
state: CompositeState,
@@ -13,6 +13,7 @@ import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.*
import com.cobblemon.mod.common.api.riding.posing.PoseOption
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.*
@@ -439,6 +440,14 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
return true
}
override fun getRideSounds(
settings: HorseSettings,
state: HorseState,
vehicle: PokemonEntity
): RideSoundSettingsList {
return settings.rideSounds
}
override fun createDefaultState(settings: HorseSettings) = HorseState()
}
@@ -467,8 +476,11 @@ class HorseSettings : RidingBehaviourSettings {
var handlingExpr: Expression = "q.get_ride_stats('SKILL', 'LAND', 140.0, 20.0)".asExpression()
private set
var rideSounds: RideSoundSettingsList = RideSoundSettingsList()
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
rideSounds.encode(buffer)
buffer.writeExpression(speedExpr)
buffer.writeExpression(accelerationExpr)
buffer.writeExpression(staminaExpr)
@@ -477,6 +489,7 @@ class HorseSettings : RidingBehaviourSettings {
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
rideSounds = RideSoundSettingsList.decode(buffer)
speedExpr = buffer.readExpression()
accelerationExpr = buffer.readExpression()
staminaExpr = buffer.readExpression()
@@ -15,6 +15,7 @@ import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.*
import com.cobblemon.mod.common.api.riding.posing.PoseOption
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
@@ -269,6 +270,14 @@ class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
return false
}
override fun getRideSounds(
settings: VehicleSettings,
state: VehicleState,
vehicle: PokemonEntity
): RideSoundSettingsList {
return settings.rideSounds
}
override fun createDefaultState(settings: VehicleSettings) = VehicleState()
}
@@ -299,8 +308,11 @@ class VehicleSettings : RidingBehaviourSettings {
var lookYawLimit = "101".asExpression()
private set
var rideSounds: RideSoundSettingsList = RideSoundSettingsList()
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
rideSounds.encode(buffer)
buffer.writeExpression(canJump)
buffer.writeExpression(jumpVector[0])
buffer.writeExpression(jumpVector[1])
@@ -314,6 +326,7 @@ class VehicleSettings : RidingBehaviourSettings {
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
rideSounds = RideSoundSettingsList.decode(buffer)
canJump = buffer.readExpression()
jumpVector = listOf(
buffer.readExpression(),
@@ -14,6 +14,7 @@ import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.*
import com.cobblemon.mod.common.api.riding.posing.PoseOption
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
@@ -337,6 +338,14 @@ class BoatBehaviour : RidingBehaviour<BoatSettings, BoatState> {
return true
}
override fun getRideSounds(
settings: BoatSettings,
state: BoatState,
vehicle: PokemonEntity
): RideSoundSettingsList {
return settings.rideSounds
}
override fun maxUpStep(settings: BoatSettings, state: BoatState, vehicle: PokemonEntity) = 0f
override fun createDefaultState(settings: BoatSettings) = BoatState()
@@ -345,6 +354,7 @@ class BoatBehaviour : RidingBehaviour<BoatSettings, BoatState> {
class BoatSettings : RidingBehaviourSettings {
override val key = BoatBehaviour.KEY
var rideSounds: RideSoundSettingsList = RideSoundSettingsList()
var terminalVelocity = "-2.0".asExpression()
private set
@@ -354,10 +364,12 @@ class BoatSettings : RidingBehaviourSettings {
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
rideSounds.encode(buffer)
buffer.writeExpression(terminalVelocity)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
rideSounds = RideSoundSettingsList.decode(buffer)
terminalVelocity = buffer.readExpression()
}
}
@@ -14,6 +14,7 @@ import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.*
import com.cobblemon.mod.common.api.riding.posing.PoseOption
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.blockPositionsAsListRounded
@@ -222,6 +223,14 @@ class BurstBehaviour : RidingBehaviour<BurstSettings, BurstState> {
return false
}
override fun getRideSounds(
settings: BurstSettings,
state: BurstState,
vehicle: PokemonEntity
): RideSoundSettingsList {
return settings.rideSounds
}
override fun createDefaultState(settings: BurstSettings) = BurstState()
}
@@ -231,12 +240,16 @@ class BurstSettings : RidingBehaviourSettings {
var dashSpeed = 1F
private set
var rideSounds: RideSoundSettingsList = RideSoundSettingsList()
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
rideSounds.encode(buffer)
buffer.writeFloat(dashSpeed)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
rideSounds = RideSoundSettingsList.decode(buffer)
dashSpeed = buffer.readFloat()
}
}
@@ -15,6 +15,7 @@ import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.*
import com.cobblemon.mod.common.api.riding.posing.PoseOption
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.*
@@ -261,6 +262,14 @@ class DolphinBehaviour : RidingBehaviour<DolphinSettings, DolphinState> {
return false
}
override fun getRideSounds(
settings: DolphinSettings,
state: DolphinState,
vehicle: PokemonEntity
): RideSoundSettingsList {
return settings.rideSounds
}
override fun createDefaultState(settings: DolphinSettings) = DolphinState()
}
@@ -286,8 +295,11 @@ class DolphinSettings : RidingBehaviourSettings {
var strafeFactor = "0.2".asExpression()
private set
var rideSounds: RideSoundSettingsList = RideSoundSettingsList()
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
rideSounds.encode(buffer)
buffer.writeExpression(canJump)
buffer.writeExpression(jumpVector[0])
buffer.writeExpression(jumpVector[1])
@@ -299,6 +311,7 @@ class DolphinSettings : RidingBehaviourSettings {
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
rideSounds = RideSoundSettingsList.decode(buffer)
canJump = buffer.readExpression()
jumpVector = listOf(
buffer.readExpression(),
@@ -0,0 +1,24 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.riding.sound;
/**
* Enum of attenuations models for ride sounds
*
* @author Jackowes
* @since May 11th, 2025
*/
enum class RideAttenuationModel {
NONE,
LINEAR,
EXPONENTIAL
}
@@ -0,0 +1,190 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.riding.sound
import com.cobblemon.mod.common.api.riding.behaviour.types.composite.CompositeBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.types.composite.CompositeState
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.resolveDouble
import net.minecraft.client.Minecraft
import net.minecraft.client.resources.sounds.AbstractTickableSoundInstance
import net.minecraft.client.resources.sounds.SoundInstance
import net.minecraft.resources.ResourceLocation
import net.minecraft.sounds.SoundEvent
import net.minecraft.sounds.SoundSource
import net.minecraft.util.Mth
import net.minecraft.util.Mth.lerp
import net.minecraft.world.level.ClipContext
import net.minecraft.world.phys.HitResult
import net.minecraft.world.phys.Vec3
import kotlin.math.max
import kotlin.math.min
import kotlin.math.pow
import kotlin.math.sin
/**
* Class to handle looping ride sounds for clients.
*
* @author Jackowes
* @since April 21st, 2025
*/
class RideLoopSound(val ride: PokemonEntity, val soundSettings: RideSoundSettings) :
AbstractTickableSoundInstance(SoundEvent.createVariableRangeEvent(soundSettings.soundLocation), SoundSource.NEUTRAL, SoundInstance.createUnseededRandom()) {
var shouldStop: Boolean = false
var fade: Float = 0.0f
var shouldMuffle: Boolean = soundSettings.muffleEnabled
var rideAttenuation: RideAttenuationModel = soundSettings.attenuationModel
var muffleAmount: Float = 1.0f // Currently slightly misleading. 1.0 is on muffle and 0.0 is full hFgain muting
// TODO: revisit below flag once multi passenger support is enabled
var isPassenger: Boolean = Minecraft.getInstance().player?.id == ride.controllingPassenger?.id
init {
this.looping = true
this.delay = 0
this.volume = 0.1f
this.attenuation = SoundInstance.Attenuation.NONE // This class performs its own attenuation
if (this.isPassenger) this.relative = true
}
override fun tick() {
// Check if the sound should fade in/out or stop
this.runFade()
// If the ride is out of attenuation distance then don't play
if (this.outOfRange()) {
this.volume = 0.0f
return
}
// Perform data driven pitch and volume calculation
this.volume = ((soundSettings.volumeExpr.let { ride.runtime.resolveDouble(it) })).toFloat()
this.volume *= this.fade
this.pitch = ((soundSettings.pitchExpr.let { ride.runtime.resolveDouble(it) })).toFloat()
if (!this.isPassenger && this.soundSettings.playForNonPassengers) {
this.setPos()
// This should now be done fully through openal. Add to RideAttneuationModel and the
// ChannelMixin to expand on the attenuation models.
// Attenuate due to distance
// this.volume *= attenuation()
// Calculate influence due to the doppler effect
this.pitch *= calcDopplerInfluence().toFloat()
// Calculate muffling due to occlusion
val soundOc = soundOcclusion()
if (soundOc != 0.0f) {
val newMuffle = lerp(soundOc,1.0f, 0.2f).coerceIn(0.2f,1.0f)
// lerp muffle smoothly
this.muffleAmount = lerp(0.1f, this.muffleAmount, newMuffle)
this.shouldMuffle = true
this.volume *= muffleAmount
} else {
this.shouldMuffle = true
this.muffleAmount = lerp(0.1f, this.muffleAmount, 1.0f)
this.volume *= muffleAmount
}
}
}
fun runFade() {
if (this.shouldStop && this.fade == 0.0f) {
this.stop()
} else if(this.shouldStop) {
this.fade = max(0.0f, this.fade - 0.05f)
} else if(!this.shouldStop && this.fade != 1.0f){
this.fade = min(1.0f, this.fade + 0.05f)
}
}
fun outOfRange(): Boolean {
val attenDist = this.sound.attenuationDistance
val listenerPosition = Minecraft.getInstance().player?.eyePosition ?: return false
return listenerPosition.distanceTo(ride.position()) > attenDist
}
fun calcDopplerInfluence(): Double {
val rideVel = ride.getRideVelocity()
// TODO: Expand this to multiple players
val listener = Minecraft.getInstance().player ?: return 1.0
val listenerVel = listener.deltaMovement//listener.position().subtract(Vec3(listener.xOld, listener.yOld, listener.zOld))
// Tweaked constant based off speed of sound in room temp air. Exaggerated for a better effect in minecraft
val waveSpeed = 343.0f / 50.0f
// listener->ride los velocity
val lineOfSightToRide = ride.position().subtract(listener.position()).normalize()
//val lineOfSightToPlayer = listener.position().subtract(ride.position()).normalize()
// Speed of the observer towards the sound source
val velObserver = listenerVel.dot(lineOfSightToRide)
// Speed of the sound source towards the observer
val velSrc = rideVel.dot(lineOfSightToRide.scale(-1.0))
// Use the doppler effect equation to determine the pitch change
val pitchFactor = (waveSpeed + velObserver) / (waveSpeed - velSrc)
return pitchFactor
}
fun soundOcclusion(): Float {
val listener = Minecraft.getInstance().player ?: return 0.0f
val level = listener.level()
val maxAttenDist = this.sound.attenuationDistance
var totalMuffle = 0.0
val center = ride.boundingBox.center
val hit = level.clip(
ClipContext(
listener.eyePosition,
center,
ClipContext.Block.COLLIDER,
ClipContext.Fluid.NONE,
listener
)
)
if(hit.type != HitResult.Type.MISS) {
//Calculate the distance the hit was away from the listener
val hitDist = hit.location.distanceTo(listener.eyePosition)
//Calculate the influence based on distance of this particular ray hit
val hitMuffle = lerp((1 - (hitDist / maxAttenDist)), 0.3, 1.0)
totalMuffle += hitMuffle
}
return totalMuffle.toFloat()
}
// fun attenuation(): Float {
// val listener = Minecraft.getInstance().player ?: return 1.0f
// val soundDistance = (ride.position().subtract(listener.eyePosition)).length()
// val maxAttenDistance = this.sound.attenuationDistance
//
// return (1 - (soundDistance / maxAttenDistance).pow(2)).toFloat()
// }
fun setPos() {
this.x = ride.x
this.y = ride.y
this.z = ride.z
}
fun stopSound() {
this.shouldStop = true
//Minecraft.getInstance().soundManager.stop(this)
}
}
@@ -0,0 +1,77 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.riding.sound
import com.cobblemon.mod.common.api.riding.behaviour.types.composite.CompositeBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.types.composite.CompositeState
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.client.Minecraft
import net.minecraft.resources.ResourceLocation
/**
* Class to control playing all ride sounds
*
* @author Jackowes
* @since April 26th, 2025
*/
class RideSoundManager(
private val ride: PokemonEntity
) {
private val activeSounds = mutableListOf<RideLoopSound>()
private var rideActive: Boolean = false
private var currKey: ResourceLocation = cobblemonResource("no_key")
fun tick() {
val currActive = ride.hasControllingPassenger()
if( !currActive && !this.rideActive) return
// Remove stopped sounds
activeSounds.removeIf { it.isStopped}
if (!currActive && this.rideActive) {
this.rideActive = false
this.currKey = cobblemonResource("no_key")
this.stop()
} else if (currActive && !this.rideActive) {
this.rideActive = true
this.currKey = getActiveRideKey()
this.getBehaviourSounds()
} else if (currActive && this.rideActive && this.currKey != getActiveRideKey()) {
this.currKey = getActiveRideKey()
this.stop()
this.getBehaviourSounds()
}
}
fun getBehaviourSounds() {
val rideSounds = ride.ifRidingAvailableSupply(null) { behaviour, settings, state ->
behaviour.getRideSounds(settings, state, ride)
}
rideSounds?.sounds?.forEach {
val loopingRideSound = RideLoopSound(ride,it)
activeSounds.add(loopingRideSound)
Minecraft.getInstance().soundManager.play(loopingRideSound)
}
}
fun getActiveRideKey(): ResourceLocation {
val key = ride.ridingBehaviourSettings?.key ?: cobblemonResource("no_key")
if (key == CompositeBehaviour.KEY) {
return (ride.ridingState as? CompositeState)?.activeBehaviour?.get() ?: cobblemonResource("no_key")
} else {
return key
}
}
fun stop() {
activeSounds.forEach { it.stopSound() }
}
}
@@ -0,0 +1,79 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.riding.sound;
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.api.net.Encodable
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.readExpression
import com.cobblemon.mod.common.util.writeExpression
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation;
/**
* Class to store data for looping sounds played during riding.
*
* @author Jackowes
* @since April 26th, 2025
*/
data class RideSoundSettings(
val soundLocation: ResourceLocation,
val volumeExpr: Expression = "1.0".asExpression(),
val pitchExpr: Expression = "1.0".asExpression(),
val playForNonPassengers: Boolean = false,
val muffleEnabled: Boolean = false,
val attenuationModel: RideAttenuationModel = RideAttenuationModel.NONE
): Encodable {
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(soundLocation)
buffer.writeExpression(volumeExpr)
buffer.writeExpression(pitchExpr)
buffer.writeBoolean(playForNonPassengers)
buffer.writeBoolean(muffleEnabled)
}
companion object {
fun decode(buffer: RegistryFriendlyByteBuf): RideSoundSettings {
// Try to read if available, fallback to default otherwise
val soundLocation = buffer.readResourceLocation()
val volumeExpr = buffer.readExpression()
val pitchExpr = buffer.readExpression()
val playForNonPassengers = buffer.readBoolean()
val muffleEnabled = buffer.readBoolean()
val attenuationModel = RideAttenuationModel.EXPONENTIAL
return RideSoundSettings(
soundLocation,
volumeExpr,
pitchExpr,
playForNonPassengers,
muffleEnabled,
attenuationModel
)
}
}
}
class RideSoundSettingsList(var sounds: List<RideSoundSettings> = mutableListOf()) {
fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeCollection(sounds) { _, sound -> sound.encode(buffer) }
}
companion object {
fun decode(buffer: RegistryFriendlyByteBuf): RideSoundSettingsList {
val list = RideSoundSettingsList()
list.sounds = buffer.readList { RideSoundSettings.decode(buffer) }.toMutableList()
return list
}
}
}
@@ -25,7 +25,7 @@ import org.joml.Vector3f
*/
class RidingAnimationData
class RidingAnimationData(val ride: PokemonEntity)
{
var prevOrientation: Matrix3f = Matrix3f()
var prevRot: Vec3 = Vec3.ZERO
@@ -34,29 +34,30 @@ class RidingAnimationData
var rotDeltaSpring: Vec3Spring = Vec3Spring()
var localVelocitySpring: Vec3Spring = Vec3Spring()
fun update(entity: PokemonEntity) {
fun update() {
val activeRide = ride.hasControllingPassenger()
if (!activeRide) return
var currRot = Vec3.ZERO
var angDelta = Vec3.ZERO
val stiffness = 90.0
val damping = 18.0
// val stiffness = 16.0
// val damping = 8.0
// Update velocity first
val currentVelocity = Vec3(
entity.x - entity.xOld,
entity.y - entity.yOld,
entity.z - entity.zOld
ride.x - ride.xOld,
ride.y - ride.yOld,
ride.z - ride.zOld
)
val r = entity.ifRidingAvailableSupply(false) { behaviour, settings, state ->
behaviour.shouldRoll(settings, state, entity);
val r = ride.ifRidingAvailableSupply(false) { behaviour, settings, state ->
behaviour.shouldRoll(settings, state, ride);
}
if( r ) {
//TODO: should this return?
val controller = (entity.firstPassenger as? OrientationControllable)?.orientationController ?: return
val controller = (ride.firstPassenger as? OrientationControllable)?.orientationController ?: return
val orientation = controller.orientation ?: Matrix3f()
val currPitch = controller.pitch
@@ -105,8 +106,8 @@ class RidingAnimationData
// Update previous values
prevOrientation.set(currOrientation)
} else {
val currPitch = entity.xRot
val currYaw = entity.yRot
val currPitch = ride.xRot
val currYaw = ride.yRot
val currRoll = 0.0f
currRot = Vec3(currPitch.toDouble(), currYaw.toDouble(), currRoll.toDouble())
@@ -120,7 +121,7 @@ class RidingAnimationData
/******************************************************
* Calculate ride local velocity
*****************************************************/
val yaw360 = -1.0 * (entity.yRot + 360.0f) % 360.0f
val yaw360 = -1.0 * (ride.yRot + 360.0f) % 360.0f
val orientationNonRollable = Matrix3f().rotateY(yaw360.toRadians())
//Convert to Vector3f
@@ -0,0 +1,24 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.spawning
import java.time.LocalDate
open class IrlDateRange (
val startDate: LocalDate,
val stopDate: LocalDate? = null
) {
operator fun contains(date: LocalDate): Boolean {
return if(stopDate != null) {
(startDate.isEqual(date) || startDate.isBefore(date)) && (stopDate.isAfter(date) || stopDate.isEqual(date))
} else {
startDate.isEqual(date)
}
}
}
@@ -0,0 +1,20 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.spawning
import java.time.LocalTime
open class IrlTimeRange(
val startTime: LocalTime,
val stopTime: LocalTime
) {
operator fun contains(time: LocalTime): Boolean {
return (time.isAfter(startTime) || time == startTime) && time.isBefore(stopTime)
}
}
@@ -74,6 +74,8 @@ object SpawnLoader {
.registerTypeAdapter(NPCClass::class.java, NPCClassReferenceAdapter)
.registerTypeAdapter(Expression::class.java, ExpressionAdapter)
.registerTypeAdapter(ExpressionLike::class.java, ExpressionLikeAdapter)
.registerTypeAdapter(IrlTimeRange::class.java, IrlTimeRangeAdapter)
.registerTypeAdapter(IrlDateRange::class.java, IrlDateRangeAdapter)
.create()
var deserializingConditionClass: Class<out SpawningCondition<*>>? = null
@@ -8,7 +8,10 @@
package com.cobblemon.mod.common.api.spawning.condition
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.conditional.RegistryLikeCondition
import com.cobblemon.mod.common.api.spawning.IrlDateRange
import com.cobblemon.mod.common.api.spawning.IrlTimeRange
import com.cobblemon.mod.common.api.spawning.MoonPhaseRange
import com.cobblemon.mod.common.api.spawning.TimeRange
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
@@ -21,6 +24,8 @@ import net.minecraft.tags.TagKey
import net.minecraft.world.level.biome.Biome
import net.minecraft.world.level.levelgen.WorldgenRandom
import net.minecraft.world.level.levelgen.structure.Structure
import java.time.LocalDate
import java.time.LocalTime
/**
* The root of spawning conditions that can be applied to a spawnable position. What type
@@ -58,6 +63,8 @@ abstract class SpawningCondition<T : SpawnablePosition> {
var timeRange: TimeRange? = null
var structures: MutableList<Either<ResourceLocation, TagKey<Structure>>>? = null
var isSlimeChunk: Boolean? = null
var irlTimeRange: IrlTimeRange? = null
var irlDateRange: IrlDateRange? = null
var markers: MutableList<String>? = null
@Transient
@@ -129,6 +136,10 @@ abstract class SpawningCondition<T : SpawnablePosition> {
val random = Random(seed)
return random.nextInt(10) == 0*/
} else if (irlTimeRange != null && !irlTimeRange!!.contains(LocalTime.now(Cobblemon.config.timeZoneId))){
return false
} else if (irlDateRange != null && !irlDateRange!!.contains(LocalDate.now(Cobblemon.config.timeZoneId))){
return false
}
return true
@@ -160,6 +171,10 @@ abstract class SpawningCondition<T : SpawnablePosition> {
return false
if (structures != null && structures!!.any { it == null })
return false
if(irlTimeRange != null && irlTimeRange!!.stopTime.isBefore(irlTimeRange!!.startTime))
return false
if(irlDateRange != null && irlDateRange!!.stopDate != null && irlDateRange!!.stopDate!!.isBefore(irlDateRange!!.startDate))
return false
return true
}
}
@@ -8,7 +8,6 @@
package com.cobblemon.mod.common.api.storage
import com.cobblemon.mod.common.api.reactive.Observable.Companion.stopAfter
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.DataKeys
@@ -45,9 +44,6 @@ open class BottomlessStore(override val uuid: UUID) : PokemonStore<BottomlessPos
override fun initialize() {
pokemon.forEachIndexed { index, pokemon ->
pokemon.storeCoordinates.set(StoreCoordinates(this, BottomlessPosition(index)))
pokemon.changeObservable.pipe(
stopAfter { pokemon.storeCoordinates.get()?.store != this }
).subscribe { storeChangeObservable.emit(Unit) }
}
}
@@ -116,4 +112,8 @@ open class BottomlessStore(override val uuid: UUID) : PokemonStore<BottomlessPos
storeChangeObservable.emit(Unit)
}
}
override fun onPokemonChanged(pokemon: Pokemon) {
this.storeChangeObservable.emit(Unit)
}
}
@@ -46,6 +46,8 @@ abstract class PokemonStore<T : StorePosition> : Iterable<Pokemon> {
abstract fun getObservingPlayers(): Iterable<ServerPlayer>
/** Sends the contents of this store to a player as if they've never seen it before. This initializes the store then sends each contained Pokémon. */
abstract fun sendTo(player: ServerPlayer)
/** Notifies the store that the provided Pokémon has changed in some way that would require persisting. */
abstract fun onPokemonChanged(pokemon: Pokemon)
/**
* Runs initialization logic for this store, knowing that it has just been constructed in a [PokemonStoreFactory].
@@ -11,7 +11,6 @@ package com.cobblemon.mod.common.api.storage.party
import com.cobblemon.mod.common.CobblemonNetwork.sendPacket
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.api.reactive.Observable
import com.cobblemon.mod.common.api.reactive.Observable.Companion.stopAfter
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.api.storage.InvalidSpeciesException
import com.cobblemon.mod.common.api.storage.PokemonStore
@@ -62,22 +61,10 @@ open class PartyStore(override val uuid: UUID) : PokemonStore<PartyPosition>() {
throw IllegalArgumentException("Slot position is out of bounds")
} else {
slots[position.slot] = pokemon
if (pokemon != null) {
if (pokemon.storeCoordinates.get()?.store != this) {
// It's new to this store. Attach the listener
trackPokemon(pokemon)
}
}
anyChangeObservable.emit(Unit)
}
}
fun trackPokemon(pokemon: Pokemon) {
pokemon.changeObservable
.pipe(stopAfter { pokemon.storeCoordinates.get()?.store != this })
.subscribe { anyChangeObservable.emit(Unit) }
}
override fun getFirstAvailablePosition(): PartyPosition? {
for (i in slots.indices) {
if (slots[i] == null) {
@@ -148,10 +135,13 @@ open class PartyStore(override val uuid: UUID) : PokemonStore<PartyPosition>() {
for (slot in slots.indices) {
val pokemon = get(slot) ?: continue
pokemon.storeCoordinates.set(StoreCoordinates(this, PartyPosition(slot)))
trackPokemon(pokemon)
}
}
override fun onPokemonChanged(pokemon: Pokemon) {
anyChangeObservable.emit(Unit)
}
fun toGappyList() = slots.toList()
/** Maps the slots of the party using the giving mapper function, but preserving the nulls in the party at the right spots. */
@@ -62,14 +62,7 @@ open class PCBox(val pc: PCStore) : Iterable<Pokemon> {
open operator fun set(index: Int, pokemon: Pokemon?) {
if (index in 0 until POKEMON_PER_BOX) {
this.pokemon[index] = pokemon
if (pokemon != null) {
val previousCoordinates = pokemon.storeCoordinates.get()
val position = previousCoordinates?.position
pokemon.storeCoordinates.set(StoreCoordinates(pc, PCPosition(boxNumber, index)))
if (previousCoordinates?.store !is PCStore || previousCoordinates.store.uuid != pc.uuid || (position as PCPosition).box != boxNumber) {
trackPokemon(pokemon)
}
}
pokemon?.storeCoordinates?.set(StoreCoordinates(pc, PCPosition(boxNumber, index)))
if (emit) {
boxChangeEmitter.emit(Unit)
}
@@ -97,7 +90,6 @@ open class PCBox(val pc: PCStore) : Iterable<Pokemon> {
if (pokemon != null) {
val position = PCPosition(box, slot)
pokemon.storeCoordinates.set(StoreCoordinates(pc, position))
trackPokemon(pokemon)
}
}
boxChangeEmitter.subscribe { pc.pcChangeObservable.emit(Unit) }
@@ -108,17 +100,7 @@ open class PCBox(val pc: PCStore) : Iterable<Pokemon> {
pokemon.forEachIndexed { slot, pokemon ->
pokemon?.storeCoordinates?.set(StoreCoordinates(pc, PCPosition(boxNumber, slot)))
}
}
fun trackPokemon(pokemon: Pokemon) {
pokemon.changeObservable
.pipe(
stopAfter {
val coordinates = it.storeCoordinates.get() ?: return@stopAfter true
return@stopAfter coordinates.store !is PCStore || coordinates.store.uuid != pc.uuid || (coordinates.position as PCPosition).box != boxNumber
}
)
.subscribe { boxChangeEmitter.emit(Unit) }
boxChangeEmitter.emit(Unit)
}
fun sendTo(player: ServerPlayer) {
@@ -284,6 +284,10 @@ open class PCStore(
}
}
override fun onPokemonChanged(pokemon: Pokemon) {
pcChangeObservable.emit(Unit)
}
fun clearPC() {
boxes.forEach { box ->
box.getNonEmptySlots().forEach{
@@ -21,126 +21,126 @@ object ElementalTypes {
val NORMAL = register(
name = "Normal",
displayName = Component.translatable("cobblemon.type.normal"),
hue = 0xDDDDCF,
hue = 0xE8E8DA,
textureXMultiplier = 0
)
val FIRE = register(
name = "Fire",
displayName = Component.translatable("cobblemon.type.fire"),
hue = 0xE55C32,
hue = 0xFF6E21,
textureXMultiplier = 1
)
val WATER = register(
name = "Water",
displayName = Component.translatable("cobblemon.type.water"),
hue = 0x4A9BE8,
hue = 0x3FA5FF,
textureXMultiplier = 2
)
val GRASS = register(
name = "Grass",
displayName = Component.translatable("cobblemon.type.grass"),
hue = 0x4DBC3C,
hue = 0x62D14F,
textureXMultiplier = 3
)
val ELECTRIC = register(
name = "Electric",
displayName = Component.translatable("cobblemon.type.electric"),
hue = 0xEFD128,
hue = 0xFFD314,
textureXMultiplier = 4
)
val ICE = register(
name = "Ice",
displayName = Component.translatable("cobblemon.type.ice"),
hue = 0x6BC3EF,
hue = 0x54F2F2,
textureXMultiplier = 5
)
val FIGHTING = register(
name = "Fighting",
displayName = Component.translatable("cobblemon.type.fighting"),
hue = 0xC44C5C,
hue = 0xEF565D,
textureXMultiplier = 6
)
val POISON = register(
name = "Poison",
displayName = Component.translatable("cobblemon.type.poison"),
hue = 0xA24BD8,
hue = 0xD651FF,
textureXMultiplier = 7
)
val GROUND = register(
name = "Ground",
displayName = Component.translatable("cobblemon.type.ground"),
hue = 0xD89950,
hue = 0xF4A453,
textureXMultiplier = 8
)
val FLYING = register(
name = "Flying",
displayName = Component.translatable("cobblemon.type.flying"),
hue = 0xBCC1FF,
hue = 0xB8B2FF,
textureXMultiplier = 9
)
val PSYCHIC = register(
name = "Psychic",
displayName = Component.translatable("cobblemon.type.psychic"),
hue = 0xD86AD6,
hue = 0xFF5E9E,
textureXMultiplier = 10
)
val BUG = register(
name = "Bug",
displayName = Component.translatable("cobblemon.type.bug"),
hue = 0xA2C831,
hue = 0xD3D319,
textureXMultiplier = 11
)
val ROCK = register(
name = "Rock",
displayName = Component.translatable("cobblemon.type.rock"),
hue = 0xAA9666,
hue = 0xB7A16E,
textureXMultiplier = 12
)
val GHOST = register(
name = "Ghost",
displayName = Component.translatable("cobblemon.type.ghost"),
hue = 0x9572E5,
hue = 0x9C80F7,
textureXMultiplier = 13
)
val DRAGON = register(
name = "Dragon",
displayName = Component.translatable("cobblemon.type.dragon"),
hue = 0x535DE8,
hue = 0x7580FF,
textureXMultiplier = 14
)
val DARK = register(
name = "Dark",
displayName = Component.translatable("cobblemon.type.dark"),
hue = 0x5C6CB2,
hue = 0x587DA0,
textureXMultiplier = 15
)
val STEEL = register(
name = "Steel",
displayName = Component.translatable("cobblemon.type.steel"),
hue = 0xC3CCE0,
hue = 0xABD1F4,
textureXMultiplier = 16
)
val FAIRY = register(
name = "Fairy",
displayName = Component.translatable("cobblemon.type.fairy"),
hue = 0xEA727E,
hue = 0xFF7FE5,
textureXMultiplier = 17
)
@@ -11,6 +11,7 @@ package com.cobblemon.mod.common.battles.interpreter.instructions
import com.bedrockk.molang.runtime.MoLangRuntime
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.molang.MoLangFunctions.addBattleMessageFunctions
import com.cobblemon.mod.common.api.moves.Moves
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
import com.cobblemon.mod.common.api.moves.animations.ActionEffects
@@ -41,6 +42,11 @@ class ActivateInstruction(val instructionSet: InstructionSet, val message: Battl
override var holds = mutableSetOf<String>()
override val id = cobblemonResource("activate")
override fun addMolangQueries(runtime: MoLangRuntime) {
super.addMolangQueries(runtime)
runtime.environment.query.addBattleMessageFunctions(message)
}
override fun preActionEffect(battle: PokemonBattle) {
val pokemon = message.battlePokemon(0, battle) ?: return
val effect = message.effectAt(1) ?: return
@@ -13,6 +13,7 @@ import com.cobblemon.mod.common.api.battles.interpreter.BattleContext
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.interpreter.InvalidInstructionException
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.molang.MoLangFunctions.addBattleMessageFunctions
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
import com.cobblemon.mod.common.api.moves.animations.ActionEffects
import com.cobblemon.mod.common.api.moves.animations.UsersProvider
@@ -43,6 +44,11 @@ class BoostInstruction(battle: PokemonBattle, val message: BattleMessage, val is
val stages = message.argumentAt(2)?.toInt() ?: throw InvalidInstructionException(message)
val stat = Stats.getStat(statKey).displayName
override fun addMolangQueries(runtime: MoLangRuntime) {
super.addMolangQueries(runtime)
runtime.environment.query.addBattleMessageFunctions(message)
}
override fun preActionEffect(battle: PokemonBattle) {
}
@@ -11,6 +11,7 @@ package com.cobblemon.mod.common.battles.interpreter.instructions
import com.bedrockk.molang.runtime.MoLangRuntime
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.molang.MoLangFunctions.addBattleMessageFunctions
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
import com.cobblemon.mod.common.api.moves.animations.UsersProvider
import com.cobblemon.mod.common.api.pokemon.status.Statuses
@@ -35,6 +36,12 @@ class CantInstruction(val message: BattleMessage): ActionEffectInstruction {
override var future: CompletableFuture<*> = CompletableFuture.completedFuture(Unit)
override var holds = mutableSetOf<String>()
override val id = cobblemonResource("cant")
override fun addMolangQueries(runtime: MoLangRuntime) {
super.addMolangQueries(runtime)
runtime.environment.query.addBattleMessageFunctions(message)
}
override fun preActionEffect(battle: PokemonBattle) {
}
@@ -25,7 +25,6 @@ import com.cobblemon.mod.common.battles.dispatch.UntilDispatch
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.net.messages.client.animation.PlayPosableAnimationPacket
import com.cobblemon.mod.common.net.messages.client.battle.BattleHealthChangePacket
import com.cobblemon.mod.common.net.messages.client.effect.RunPosableMoLangPacket
import com.cobblemon.mod.common.pokemon.evolution.progress.DamageTakenEvolutionProgress
import com.cobblemon.mod.common.pokemon.evolution.progress.RecoilEvolutionProgress
import com.cobblemon.mod.common.pokemon.status.statuses.persistent.PoisonStatus
@@ -103,10 +102,18 @@ class DamageInstruction(
status = pokemon.effectedPokemon.status?.status ?: status
}
// Damage effects without a action effect receive the generic damage effect
val actionEffect = status?.getActionEffect() ?: effect?.let {
ActionEffects.actionEffects["generic_damage".asIdentifierDefaultingNamespace()]
} ?: return@dispatch GO
// Damage inflicted by an effect (not just a move) will potentially get its own action effect.
// We don't play an effect at all if there's no effect on the damage because we anticipate that
// the move's action effect will handle that. Status action effects are specially defined (we could
// migrate this back to damage_{status} later tbh) but then it will try damage_{status} and failing
// that then we should try to show something about the fact that some kind of effect damage is being applied.
val actionEffect = status?.getActionEffect()
?: effect?.let {
ActionEffects.actionEffects["damage_${it.id}".asIdentifierDefaultingNamespace()]
?: ActionEffects.actionEffects["generic_damage".asIdentifierDefaultingNamespace()]
}
?: return@dispatch GO // will occur if the damage is from a move
val providers = mutableListOf<Any>(battle)
battlePokemon.effectedPokemon.entity?.let { UsersProvider(it) }?.let(providers::add)
@@ -210,7 +217,7 @@ class DamageInstruction(
} else if (causedFaint) {
GO
} else {
UntilDispatch { "effects" !in holds}
UntilDispatch { "effects" !in holds }
}
}
}
@@ -21,11 +21,11 @@ import com.cobblemon.mod.common.util.battleLang
* @since December 30th, 2022
*/
class HitCountInstruction(val message: BattleMessage): InterpreterInstruction {
val hitCount = message.argumentAt(1)?.toIntOrNull()
override fun invoke(battle: PokemonBattle) {
battle.dispatchGo {
val battlePokemon = message.battlePokemon(0, battle) ?: return@dispatchGo
val hitCount = message.argumentAt(1)?.toIntOrNull() ?: return@dispatchGo
val hitCount = hitCount ?: return@dispatchGo
val lang = if (hitCount == 1) battleLang("hit_count_singular") else battleLang("hit_count", hitCount)
battle.minorBattleActions[battlePokemon.uuid] = message
battle.broadcastChatMessage(lang)
@@ -15,7 +15,9 @@ import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.interpreter.Effect
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.molang.MoLangFunctions.addBattleMessageFunctions
import com.cobblemon.mod.common.api.molang.MoLangFunctions.addStandardFunctions
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
import com.cobblemon.mod.common.api.moves.Moves
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
import com.cobblemon.mod.common.api.moves.animations.TargetsProvider
@@ -28,9 +30,11 @@ import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.battles.dispatch.UntilDispatch
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.pokemon.evolution.progress.UseMoveEvolutionProgress
import com.cobblemon.mod.common.util.asArrayValue
import com.cobblemon.mod.common.util.battleLang
import com.cobblemon.mod.common.util.cobblemonResource
import java.util.concurrent.CompletableFuture
import net.minecraft.world.entity.LivingEntity
/**
* Format: |move|POKEMON|MOVE|TARGET
@@ -93,15 +97,27 @@ class MoveInstruction(
battle.broadcastChatMessage(lang)
battle.majorBattleActions[userPokemon.uuid] = message
val providers = mutableListOf<Any>(battle)
userPokemon.effectedPokemon.entity?.let { UsersProvider(it) }?.let(providers::add)
if (spreadTargetPokemon.isNotEmpty()) {
providers.add(TargetsProvider(spreadTargetPokemon.filter { it?.effectedPokemon?.entity != null}.mapNotNull { spreadTarget -> spreadTarget?.effectedPokemon?.entity }))
} else {
targetPokemon?.effectedPokemon?.entity?.let { TargetsProvider(it) }?.let(providers::add)
}
val runtime = MoLangRuntime().also {
battle.addQueryFunctions(it.environment.query).addStandardFunctions()
it.environment.query.addBattleMessageFunctions(message)
}
val providers = mutableListOf<Any>(battle)
userPokemon.effectedPokemon.entity?.let { entity ->
runtime.environment.query.addFunction("user") { entity.asMostSpecificMoLangValue()}
UsersProvider(entity)
}?.let(providers::add)
if (spreadTargetPokemon.isNotEmpty()) {
val targetEntities = spreadTargetPokemon.mapNotNull { it?.effectedPokemon?.entity }
runtime.environment.query.addFunction("targets") { targetEntities.asArrayValue { it.asMostSpecificMoLangValue() } }
providers.add(TargetsProvider(targetEntities))
} else {
targetPokemon?.effectedPokemon?.entity?.let { entity ->
runtime.environment.query.addFunction("target") { entity.asMostSpecificMoLangValue() }
val provider = TargetsProvider(entity)
providers.add(provider)
}
}
actionEffect ?: return@dispatch GO
@@ -114,6 +130,7 @@ class MoveInstruction(
val subsequentInstructions = instructionSet.findInstructionsCausedBy(this)
val missedTargets = subsequentInstructions.filterIsInstance<MissInstruction>().mapNotNull { it.target }
val hitCountInstruction = subsequentInstructions.filterIsInstance<HitCountInstruction>().firstOrNull()
runtime.environment.query.addFunction("missed") { params ->
if (params.params.size == 0) {
@@ -134,6 +151,10 @@ class MoveInstruction(
}
}
if (hitCountInstruction != null && hitCountInstruction.hitCount != null) {
runtime.environment.query.addFunction("hit_count") { DoubleValue(hitCountInstruction.hitCount.toDouble()) }
}
runtime.environment.query.addFunction("move") { move.struct }
runtime.environment.query.addFunction("instruction_id") { StringValue(cobblemonResource("move").toString()) }
@@ -8,10 +8,26 @@
package com.cobblemon.mod.common.battles.interpreter.instructions
import com.bedrockk.molang.runtime.MoLangRuntime
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.molang.MoLangFunctions.addBattleMessageFunctions
import com.cobblemon.mod.common.api.molang.MoLangFunctions.addFunctions
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
import com.cobblemon.mod.common.api.moves.animations.ActionEffects
import com.cobblemon.mod.common.api.moves.animations.UsersProvider
import com.cobblemon.mod.common.api.pokemon.status.Statuses
import com.cobblemon.mod.common.battles.ShowdownInterpreter
import com.cobblemon.mod.common.battles.dispatch.ActionEffectInstruction
import com.cobblemon.mod.common.battles.dispatch.GO
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.battles.dispatch.UntilDispatch
import com.cobblemon.mod.common.pokemon.status.statuses.persistent.PoisonStatus
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import com.cobblemon.mod.common.util.battleLang
import com.cobblemon.mod.common.util.cobblemonResource
import java.util.concurrent.CompletableFuture
import net.minecraft.resources.ResourceLocation
/**
* Format: |-prepare|ATTACKER|MOVE and |-prepare|ATTACKER|MOVE|DEFENDER
@@ -20,13 +36,56 @@ import com.cobblemon.mod.common.util.battleLang
* @author Renaissance
* @since March 24th, 2023
*/
class PrepareInstruction(val message: BattleMessage): InterpreterInstruction {
class PrepareInstruction(val message: BattleMessage): ActionEffectInstruction {
override var future: CompletableFuture<*> = CompletableFuture.completedFuture(Unit)
override var holds = mutableSetOf<String>()
override val id = cobblemonResource("prepare")
override fun invoke(battle: PokemonBattle) {
battle.dispatchWaiting(1.5F) {
val pokemon = message.battlePokemon(0, battle) ?: return@dispatchWaiting
override fun addMolangQueries(runtime: MoLangRuntime) {
super.addMolangQueries(runtime)
runtime.environment.query.addBattleMessageFunctions(message)
}
override fun preActionEffect(battle: PokemonBattle) {
val pokemon = message.battlePokemon(0, battle) ?: return
val effect = message.effectAt(1) ?: return
ShowdownInterpreter.broadcastOptionalAbility(battle, effect, pokemon)
battle.dispatch{
ShowdownInterpreter.lastCauser[battle.battleId] = message
battle.minorBattleActions[pokemon.uuid] = message
GO
}
}
override fun runActionEffect(battle: PokemonBattle, runtime: MoLangRuntime) {
val effect = message.effectAt(1)
val battlePokemon = message.battlePokemon(0, battle) ?: return
battle.dispatch {
val actionEffect = effect?.let { ActionEffects.actionEffects["prepare_${it.id}".asIdentifierDefaultingNamespace()] }
?: return@dispatch GO // not likely
val providers = mutableListOf<Any>(battle)
battlePokemon.effectedPokemon.entity?.let { UsersProvider(it) }?.let(providers::add)
val context = ActionEffectContext(
actionEffect = actionEffect,
runtime = runtime,
providers = providers,
level = battle.players.firstOrNull()?.level()
)
this.future = actionEffect.run(context)
holds = context.holds // Reference so future things can check on this action effect's holds
future.thenApply { holds.clear() }
return@dispatch GO
}
}
override fun postActionEffect(battle: PokemonBattle) {
battle.dispatch {
val pokemon = message.battlePokemon(0, battle) ?: return@dispatch GO
val pokemonName = pokemon.getName()
val effectID = message.effectAt(1)?.id ?: return@dispatchWaiting
val effectID = message.effectAt(1)?.id ?: return@dispatch GO
//Prevents spam when the move Role Play is used
val lang = when (effectID) {
"shadowforce" -> battleLang("prepare.phantomforce", pokemonName) //Phantom Force and Shadow Force share the same text
@@ -35,6 +94,7 @@ class PrepareInstruction(val message: BattleMessage): InterpreterInstruction {
}
battle.broadcastChatMessage(lang)
battle.minorBattleActions[pokemon.uuid] = message
UntilDispatch { "effects" !in holds }
}
}
}
@@ -21,6 +21,7 @@ import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.BaseEntityBlock
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.HorizontalDirectionalBlock
import net.minecraft.world.level.block.Rotation
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.StateDefinition
@@ -79,7 +80,12 @@ class MonitorBlock(settings: Properties) : MultiblockBlock(settings) {
pos: BlockPos,
collisionContext: CollisionContext
): VoxelShape {
return HITBOX
return when (state.getValue(HorizontalDirectionalBlock.FACING)) {
Direction.WEST -> HITBOX_WEST
Direction.EAST -> HITBOX_EAST
Direction.SOUTH -> HITBOX_SOUTH
else -> HITBOX_NORTH
}
}
@Deprecated("Deprecated in Java")
@@ -97,13 +103,34 @@ class MonitorBlock(settings: Properties) : MultiblockBlock(settings) {
//0 is off
val SCREEN = EnumProperty.create("screen", MonitorScreen::class.java)
val HITBOX = Shapes.or(
val HITBOX_SOUTH = Shapes.or(
Shapes.box(0.0625, 0.0, 0.0625, 0.9375, 0.375, 0.9375),
Shapes.box(0.0625, 0.875, 0.0625, 0.9375, 1.0, 0.9375),
Shapes.box(0.8125, 0.375, 0.0625, 0.9375, 0.875, 0.9375),
Shapes.box(0.1875, 0.375, 0.125, 0.8125, 0.875, 0.9375),
Shapes.box(0.0625, 0.375, 0.0625, 0.1875, 0.875, 0.9375)
)
val HITBOX_NORTH = Shapes.or(
Shapes.box(0.0625, 0.0, 0.0625, 0.9375, 0.375, 0.9375),
Shapes.box(0.0625, 0.875, 0.0625, 0.9375, 1.0, 0.9375),
Shapes.box(0.0625, 0.375, 0.0625, 0.1875, 0.875, 0.9375),
Shapes.box(0.1875, 0.375, 0.0625, 0.8125, 0.875, 0.875),
Shapes.box(0.8125, 0.375, 0.0625, 0.9375, 0.875, 0.9375)
)
val HITBOX_EAST = Shapes.or(
Shapes.box(0.0625, 0.0, 0.0625, 0.9375, 0.375, 0.9375),
Shapes.box(0.0625, 0.875, 0.0625, 0.9375, 1.0, 0.9375),
Shapes.box(0.0625, 0.375, 0.0625, 0.9375, 0.875, 0.1875),
Shapes.box(0.125, 0.375, 0.1875, 0.9375, 0.875, 0.8125),
Shapes.box(0.0625, 0.375, 0.8125, 0.9375, 0.875, 0.9375)
)
val HITBOX_WEST = Shapes.or(
Shapes.box(0.0625, 0.0, 0.0625, 0.9375, 0.375, 0.9375),
Shapes.box(0.0625, 0.875, 0.0625, 0.9375, 1.0, 0.9375),
Shapes.box(0.0625, 0.375, 0.8125, 0.9375, 0.875, 0.9375),
Shapes.box(0.0625, 0.375, 0.1875, 0.875, 0.875, 0.8125),
Shapes.box(0.0625, 0.375, 0.0625, 0.9375, 0.875, 0.1875)
)
}
enum class MonitorScreen : StringRepresentable {
OFF,
@@ -14,7 +14,6 @@ import com.cobblemon.mod.common.CobblemonNetwork.sendToServer
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.moves.Move
import com.cobblemon.mod.common.api.reactive.Observable.Companion.stopAfter
import com.cobblemon.mod.common.api.scheduling.Schedulable
import com.cobblemon.mod.common.api.scheduling.SchedulingTracker
import com.cobblemon.mod.common.api.storage.party.PartyPosition
@@ -25,7 +24,11 @@ import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import com.cobblemon.mod.common.client.gui.ExitButton
import com.cobblemon.mod.common.client.gui.TypeIcon
import com.cobblemon.mod.common.client.gui.summary.widgets.*
import com.cobblemon.mod.common.client.gui.summary.widgets.EvolutionSelectScreen
import com.cobblemon.mod.common.client.gui.summary.widgets.MarkingsWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.ModelWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.NicknameEntryWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.PartyWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.screens.SummaryTab
import com.cobblemon.mod.common.client.gui.summary.widgets.screens.info.InfoWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.screens.marks.MarksWidget
@@ -328,9 +331,6 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
saveMarkings()
this.selectedPokemon.moveSet.changeFunction = {}
this.party.getOrNull(newSelection)?.let { newPokemon ->
newPokemon.changeObservable.pipe( stopAfter { Minecraft.getInstance().screen != this || this.selectedPokemon != newPokemon } ).subscribe {
updatePokemonInfo()
}
this.selectedPokemon = newPokemon
}
updatePokemonInfo()
@@ -758,6 +758,8 @@ open class PosableModel(@Transient override val rootPart: Bone) : ModelFrame {
yRot = Mth.lerp(state.getPartialTicks(), entity.yBodyRotO, entity.yBodyRot)
yRot = 180f - Mth.wrapDegrees(yRot)
}
//For some reason locators are positioned upside-down so this fixes that
matrixStack.mulPose(Axis.ZP.rotationDegrees(180f))
} else if (entity is EmptyPokeBallEntity) {
scale = 0.7F
yRot = Mth.lerp(state.getPartialTicks(), entity.yRot, entity.yRotO)
@@ -773,9 +775,6 @@ open class PosableModel(@Transient override val rootPart: Bone) : ModelFrame {
matrixStack.scale(scale, scale, scale)
matrixStack.mulPose(Axis.YP.rotationDegrees(yRot))
//For some reason locators are positioned upside-down so this fixes that
matrixStack.mulPose(Axis.ZP.rotationDegrees(180f))
locatorAccess.update(matrixStack, entity, scale, state.locatorStates, isRoot = true)
}
@@ -18,6 +18,7 @@ enum class Category(val lang: String) {
Pokedex("pokedex"),
Storage("storage"),
Starter("starter"),
System("system"),
Interface("interface"),
Riding("riding"),
Debug("debug"),
@@ -16,8 +16,11 @@ import com.cobblemon.mod.common.config.constraint.IntConstraint
import com.cobblemon.mod.common.pokeball.catching.calculators.CobblemonCaptureCalculator
import com.cobblemon.mod.common.util.adapters.CaptureCalculatorAdapter
import com.cobblemon.mod.common.util.adapters.IntRangeAdapter
import com.cobblemon.mod.common.util.adapters.ZoneIdAdapter
import com.google.gson.GsonBuilder
import com.google.gson.annotations.SerializedName
import java.time.ZoneId
import java.time.ZoneOffset
import kotlin.reflect.full.memberProperties
import kotlin.reflect.jvm.isAccessible
@@ -29,6 +32,7 @@ class CobblemonConfig {
.registerTypeAdapter(IntRange::class.java, IntRangeAdapter)
.registerTypeAdapter(ItemDropMethod::class.java, ItemDropMethod.adapter)
.registerTypeAdapter(CaptureCalculator::class.java, CaptureCalculatorAdapter)
.registerTypeHierarchyAdapter(ZoneId::class.java, ZoneIdAdapter)
.create()
}
@@ -263,6 +267,9 @@ class CobblemonConfig {
@CobblemonConfigField(Category.Pokedex, lang = "hide_unimplemented_pokemon_in_the_pokedex")
var hideUnimplementedPokemonInThePokedex = false
@CobblemonConfigField(Category.System, lang = "time_zone_id")
var timeZoneId: ZoneId = ZoneOffset.systemDefault()
@CobblemonConfigField(Category.Interface, lang = "summary_pokemon_follow_cursor")
var summaryPokemonFollowCursor = true
@@ -85,7 +85,7 @@ object StarterDataLoader : JsonDataRegistry<StarterCategory> {
}
}
observable.emit()
observable.emit(this)
}
override fun sync(player: ServerPlayer) {}
@@ -0,0 +1,79 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.entity
import com.bedrockk.molang.runtime.MoLangRuntime
import com.bedrockk.molang.runtime.MoParams
import com.bedrockk.molang.runtime.value.MoValue
import com.cobblemon.mod.common.api.ai.CobblemonBehaviour
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.setup
import com.cobblemon.mod.common.api.scripting.CobblemonScripts
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.resolve
import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.ListTag
import net.minecraft.nbt.StringTag
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.entity.Entity
/**
* A class that holds callbacks for an entity, allowing scripts to be associated with specific events.
* Mainly keeps code bloat away from the entity classes. There's likely to be some overlap between these
* and the datapacked MoLang callbacks but the difference is that these are per-entity which allows
* [CobblemonBehaviour]s to add callbacks very gracefully, rather than running the same code for all
* entities then looking for some other property on the entity.
*
* @author Hiroku
* @since July 26th, 2025
*/
class EntityCallbacks(val entity: Entity) : HashMap<ResourceLocation, MutableList<ResourceLocation>>() {
companion object {
val HIT_BY_POKEBALL = cobblemonResource("hit_by_pokeball")
val SAW_ENTITY = cobblemonResource("saw_entity")
val HURT = cobblemonResource("hurt")
val DIED = cobblemonResource("died")
}
val runtime = MoLangRuntime().setup().also {
it.environment.query.addFunction("entity") { entity.asMostSpecificMoLangValue() }
}
/**
* Adds a script to the callbacks for a specific type. If `allowDuplicates` is false, it will not add
* the script if it already exists in the list for that type.
*
* Returns true if the script was added, false if it was not added due to a duplicate being found and respected.
*/
fun addCallback(type: ResourceLocation, callback: ResourceLocation, allowDuplicates: Boolean): Boolean {
val callbacks = this.getOrPut(type) { mutableListOf() }
if (!allowDuplicates && callback in callbacks) {
return false
}
callbacks.add(callback)
return true
}
fun removeCallback(type: ResourceLocation, callback: ResourceLocation): Boolean {
val callbacks = this[type] ?: return false
return callbacks.remove(callback)
}
fun process(type: ResourceLocation, functions: Map<String, (MoParams) -> MoValue> = hashMapOf()) {
if (type !in this) {
return
}
functions.forEach { (name, function) -> runtime.environment.query.addFunction(name, function) }
val callbacks = this[type]?.mapNotNull { CobblemonScripts.scripts[it] } ?: return
for (callback in callbacks) {
runtime.resolve(callback)
}
}
}
@@ -17,11 +17,11 @@ import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
import com.cobblemon.mod.common.entity.npc.NPCEntity
import com.cobblemon.mod.common.util.DataKeys
import com.mojang.serialization.Dynamic
import com.mojang.serialization.DynamicOps
import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.ListTag
import net.minecraft.nbt.StringTag
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.entity.Entity
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.Brain
import net.minecraft.world.entity.ai.memory.MemoryModuleType
@@ -40,6 +40,7 @@ interface MoLangScriptingEntity {
val registeredVariables: MutableList<MoLangConfigVariable>
var config: VariableStruct
var data: VariableStruct
var callbacks: EntityCallbacks
fun getExtraVariables(): List<MoLangConfigVariable> = emptyList()
fun remakeBrain()
@@ -100,7 +101,7 @@ interface MoLangScriptingEntity {
fun updateBehaviours(behaviours: Collection<ResourceLocation>) {
val removingBehaviours = this@MoLangScriptingEntity.behaviours.filterNot(behaviours::contains).mapNotNull(CobblemonBehaviours.behaviours::get)
removingBehaviours.forEach { behaviour ->
behaviour.undo(this as LivingEntity)
behaviour.onRemove(this as LivingEntity)
}
this@MoLangScriptingEntity.behaviours.clear()
this@MoLangScriptingEntity.behaviours.addAll(behaviours)
@@ -38,6 +38,7 @@ import com.cobblemon.mod.common.api.scheduling.SchedulingTracker
import com.cobblemon.mod.common.api.storage.party.NPCPartyStore
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.entity.BehaviourEditingTracker
import com.cobblemon.mod.common.entity.EntityCallbacks
import com.cobblemon.mod.common.entity.MoLangScriptingEntity
import com.cobblemon.mod.common.entity.OmniPathingEntity
import com.cobblemon.mod.common.entity.PosableEntity
@@ -242,6 +243,7 @@ class NPCEntity(world: Level) : AgeableMob(CobblemonEntities.NPC, world), Npc, P
override val registeredVariables: MutableList<MoLangConfigVariable> = mutableListOf()
override var data = VariableStruct()
override var config = VariableStruct()
override var callbacks = EntityCallbacks(this)
val aspects: Set<String>
get() = entityData.get(ASPECTS)
@@ -9,6 +9,7 @@
package com.cobblemon.mod.common.entity.pokeball
import com.bedrockk.molang.runtime.struct.QueryStruct
import com.bedrockk.molang.runtime.value.DoubleValue
import com.bedrockk.molang.runtime.value.StringValue
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonEntities.EMPTY_POKEBALL
@@ -17,6 +18,7 @@ import com.cobblemon.mod.common.api.events.CobblemonEvents
import com.cobblemon.mod.common.api.events.pokeball.PokeBallCaptureCalculatedEvent
import com.cobblemon.mod.common.api.events.pokeball.ThrownPokeballHitEvent
import com.cobblemon.mod.common.api.events.pokemon.PokemonCapturedEvent
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
import com.cobblemon.mod.common.api.net.serializers.StringSetDataSerializer
import com.cobblemon.mod.common.api.net.serializers.Vec3DataSerializer
import com.cobblemon.mod.common.api.pokeball.PokeBalls
@@ -120,6 +122,7 @@ class EmptyPokeBallEntity : ThrowableItemProjectile, PosableEntity, WaterDragMod
.addFunction("capture_state") { StringValue(captureState.name) }
.addFunction("ball_type") { StringValue(pokeBall.name.toString()) }
.addFunction("aspects") { aspects.asArrayValue { StringValue(it) } }
.addFunction("thrower") { owner?.asMostSpecificMoLangValue() ?: DoubleValue.ZERO }
override fun defineSynchedData(builder: SynchedEntityData.Builder) {
pokeBall = PokeBalls.POKE_BALL
@@ -445,14 +448,7 @@ class EmptyPokeBallEntity : ThrowableItemProjectile, PosableEntity, WaterDragMod
level().playSoundServer(position(), CobblemonSounds.POKE_BALL_HIT, volume = 1F)
// Hit Pokémon plays recoil animation
val pkt = PlayPosableAnimationPacket(pokemonEntity.id, setOf("recoil"), emptyList())
pkt.sendToPlayersAround(
x = pokemonEntity.x,
y = pokemonEntity.y,
z = pokemonEntity.z,
worldKey = pokemonEntity.level().dimension(),
distance = 50.0
)
pokemonEntity.playAnimation("recoil")
// Bounce backwards away from the hit Pokémon
deltaMovement = displace.multiply(-1.0, 0.0, -1.0).normalize().yRot(mul * PI/3).multiply(0.1, 0.0, 0.1).add(0.0, 1.0 / 3, 0.0)
@@ -52,6 +52,8 @@ import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourSettings
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourState
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviours
import com.cobblemon.mod.common.api.riding.events.SelectDriverEvent
import com.cobblemon.mod.common.api.riding.sound.RideLoopSound
import com.cobblemon.mod.common.api.riding.sound.RideSoundManager
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.api.riding.util.RidingAnimationData
import com.cobblemon.mod.common.api.scheduling.Schedulable
@@ -282,7 +284,8 @@ open class PokemonEntity(
var enablePoseTypeRecalculation = true
val ridingAnimationData: RidingAnimationData = RidingAnimationData()
val ridingAnimationData: RidingAnimationData = RidingAnimationData(this)
var rideSoundManager: RideSoundManager = RideSoundManager(this)
var previousRidingState: RidingBehaviourState? = null
var ridingState: RidingBehaviourState? = null
@@ -297,6 +300,7 @@ open class PokemonEntity(
.withQueryValue("entity", struct)
.also {
it.environment.query.addFunction("passenger_count") { DoubleValue(passengers.size.toDouble()) }
it.environment.query.addFunction("ride_velocity") { DoubleValue(getRideVelocity().length()) }
it.environment.query.addFunction("get_ride_stats") { params ->
val rideStat = RidingStat.valueOf(params.getString(0).uppercase())
val rideStyle = RidingStyle.valueOf(params.getString(1).uppercase())
@@ -377,6 +381,7 @@ open class PokemonEntity(
override val registeredVariables = mutableListOf<MoLangConfigVariable>()
override var config = VariableStruct()
override var data = VariableStruct()
override var callbacks = EntityCallbacks(this)
var platform: PlatformType
get() = entityData.get(PLATFORM_TYPE)
@@ -556,7 +561,8 @@ open class PokemonEntity(
isPokemonWalking = walkDist - walkDistO > 0.005F
if (passengers.isNotEmpty()) {
ridingAnimationData.update(this)
rideSoundManager.tick()
ridingAnimationData.update()
}
flyDistO = flyDist
@@ -639,6 +645,11 @@ open class PokemonEntity(
super.customServerAiStep()
}
fun getRideVelocity(): Vec3 {
return this.ridingAnimationData.velocitySpring.value
//return this.position().subtract(prevPosition)
}
fun setMoveControl(moveControl: MoveControl) {
this.moveControl = moveControl
}
@@ -1184,8 +1195,7 @@ open class PokemonEntity(
this.getUUID(),
canSitOnShoulder() && pokemon in player.party(),
!(pokemon.heldItemNoCopy().isEmpty && itemStack.isEmpty),
(!pokemon.cosmeticItem.isEmpty && itemStack.isEmpty) || cosmeticItemDefinition != null,
canRide
(!pokemon.cosmeticItem.isEmpty && itemStack.isEmpty) || cosmeticItemDefinition != null, canRide
).sendToPlayer(player)
}
else {
@@ -1215,7 +1225,8 @@ open class PokemonEntity(
if (this.health == 0F) {
pokemon.currentHealth = 0
} else {
} else if (this.ownerUUID != null) {
// Only touch battle HP for non-wild Pokémon so that quick ball et. al. aren't owerpowered.
pokemon.currentHealth = (pokemon.maxHealth * (this.health / this.maxHealth)).toInt()
}
true
@@ -2142,23 +2153,6 @@ open class PokemonEntity(
this.jumpInputStrength = 0
}
}
// val lookAngle: Vec3 = driver.getLookAngle()
// if (!driver.isNearGround() || lookAngle.y >= 0.3) {
// setBehaviourFlag(PokemonBehaviourFlag.FLYING, true)
// entityData.set(POSE_TYPE, PoseType.HOVER)
//
// val deltaMovement = this.deltaMovement
// this.setDeltaMovement(
// lookAngle.x * 0.1 + (lookAngle.x * 1.5 - deltaMovement.x) * 1,
// lookAngle.y * 0.1 + (lookAngle.y * 1.5 - deltaMovement.y) * 1,
// lookAngle.z * 0.1 + (lookAngle.z * 1.5 - deltaMovement.z) * 1
// )
// }
// else {
// setBehaviourFlag(PokemonBehaviourFlag.FLYING, false)
// this.setDeltaMovement(0.0, 0.0, 0.0)
// }
}
fun Entity.isNearGround(): Boolean {
@@ -2166,21 +2160,6 @@ open class PokemonEntity(
return this.level().getBlockState(blockBelow).isSolid
}
// override fun jump() {
//// val vec3d = this.velocity
////
////
////
//// this.setVelocity(vec3d.x, this.jumpVelocity.toDouble(), vec3d.z)
//// if (this.isSprinting) {
//// val f = this.yaw * 0.017453292f
//// this.velocity =
//// velocity.add((-MathHelper.sin(f) * 0.2f).toDouble(), 0.0, (MathHelper.cos(f) * 0.2f).toDouble())
//// }
////
//// this.velocityDirty = true
// }
override fun onPassengerTurned(entityToUpdate: Entity) {
if (entityToUpdate !is LivingEntity) return
ifRidingAvailable { behaviour, settings, state ->
@@ -50,6 +50,8 @@ import net.minecraft.world.level.pathfinder.PathType
import org.joml.Matrix3f
import org.joml.Vector3f
import java.util.*
import kotlin.math.ceil
import kotlin.math.max
import kotlin.math.min
import kotlin.math.pow
import kotlin.math.round
@@ -155,11 +157,16 @@ class PokemonServerDelegate : PokemonSideDelegate {
fun updateHealth(pokemon: Pokemon) {
if (acknowledgedHPValue != pokemon.currentHealth) {
acknowledgedHPValue = pokemon.currentHealth
val currentHPRatio: Float = pokemon.currentHealth / pokemon.maxHealth.toFloat()
if (currentHPRatio.isFinite()) {
// Entity Health is stored as float but only ever in-/decreased by whole numbers.
entity.health = round(entity.maxHealth * currentHPRatio)
if (pokemon.currentHealth <= 0) {
entity.health = 0.0f
} else {
val currentHPRatio: Float = pokemon.currentHealth / pokemon.maxHealth.toFloat()
if (currentHPRatio.isFinite()) {
// Entity Health is stored as float but only ever in-/decreased by whole numbers.
entity.health = ceil(entity.maxHealth * currentHPRatio)
}
}
}
}
@@ -9,6 +9,7 @@
package com.cobblemon.mod.common.entity.pokemon.ai.tasks
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.ai.CobblemonBlockPosTracker
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import net.minecraft.world.entity.ai.behavior.EntityTracker
import net.minecraft.world.entity.ai.behavior.OneShot
@@ -33,11 +34,16 @@ object LookAtTargetedBattlePokemonTask {
Trigger { world, entity, _ ->
val targeted = it.tryGet(targetedBattlePokemon).orElse(null)
val targetedEntity = targeted?.let { world.getEntity(it) as? PokemonEntity }
val look = it.tryGet(lookTarget).orElse(null) as? EntityTracker
var look = it.tryGet(lookTarget).orElse(null)
if (look is CobblemonBlockPosTracker && look.isBattleLookBypass()) {
return@Trigger false
} else if (look !is EntityTracker) {
look = null
}
if (targeted != null && targetedEntity == null) {
entity.brain.eraseMemory(CobblemonMemories.TARGETED_BATTLE_POKEMON)
return@Trigger false
} else if (targetedEntity != null && look?.entity != targetedEntity) {
} else if (targetedEntity != null && (look as? EntityTracker)?.entity != targetedEntity) {
entity.brain.setMemory(MemoryModuleType.LOOK_TARGET, EntityTracker(targetedEntity, true))
return@Trigger true
} else if (targetedEntity == null) {
@@ -0,0 +1,44 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.events
import com.bedrockk.molang.runtime.MoParams
import com.bedrockk.molang.runtime.value.MoValue
import com.cobblemon.mod.common.api.Priority
import com.cobblemon.mod.common.api.events.CobblemonEvents.THROWN_POKEBALL_HIT
import com.cobblemon.mod.common.api.events.pokeball.ThrownPokeballHitEvent
import com.cobblemon.mod.common.api.reactive.Observable
import com.cobblemon.mod.common.entity.EntityCallbacks
import com.cobblemon.mod.common.entity.EntityCallbacks.Companion.HIT_BY_POKEBALL
import com.cobblemon.mod.common.entity.MoLangScriptingEntity
import net.minecraft.resources.ResourceLocation
/**
* Handles the registration of entity callbacks for events related to entities. You can add your own
* as well, all this actually does is subscribe to the event and pass it through the [EntityCallbacks] of
* the entity the event is about. In general, entity callbacks occur on the normal priority so that
* plugins can jump in ahead but on parity with the general MoLang callbacks.
*
* @author Hiroku
* @since July 26th, 2025
*/
object EntityCallbackHandler {
fun setup() {
bindCallback(THROWN_POKEBALL_HIT, HIT_BY_POKEBALL, ThrownPokeballHitEvent::pokemon, ThrownPokeballHitEvent::functions)
}
fun <T> bindCallback(
observable: Observable<T>,
type: ResourceLocation,
entity: (T) -> MoLangScriptingEntity,
functions: (T) -> Map<String, (MoParams) -> MoValue>
) {
observable.subscribe(priority = Priority.NORMAL) { entity(it).callbacks.process(type, functions(it)) }
}
}
@@ -37,7 +37,7 @@ object FlowHandler {
CobblemonEvents.BATTLE_STARTED_POST.subscribe { CobblemonFlows.run(cobblemonResource("battle_started_post"), it.context) }
CobblemonEvents.APRICORN_HARVESTED.subscribe { CobblemonFlows.run(cobblemonResource("apricorn_harvested"), it.context) }
CobblemonEvents.BERRY_HARVEST.subscribe { CobblemonFlows.run(cobblemonResource("berry_harvested"), it.context) }
CobblemonEvents.THROWN_POKEBALL_HIT.subscribe { CobblemonFlows.run(cobblemonResource("thrown_pokeball_hit"), it.context) }
CobblemonEvents.THROWN_POKEBALL_HIT.subscribe { CobblemonFlows.run(cobblemonResource("thrown_pokeball_hit"), emptyMap(), functions = it.functions) }
CobblemonEvents.POKEMON_HEALED.subscribe { CobblemonFlows.run(cobblemonResource("pokemon_healed"), it.context, it.functions) }
CobblemonEvents.POKEMON_ASPECTS_CHANGED.subscribe { CobblemonFlows.run(cobblemonResource("pokemon_aspects_changed"), it.context) }
CobblemonEvents.LEVEL_UP_EVENT.subscribe { CobblemonFlows.run(cobblemonResource("level_up"), it.context, it.functions) }
@@ -140,7 +140,7 @@ class AprijuiceItem(val type: Apricorn): CobblemonItem(Properties().stacksTo(16)
val hasFlavour = stack.get(CobblemonItemComponents.FLAVOUR)?.flavours?.any { it.value > 0 } == true
if (!hasFlavour && user is Player && !world.isClientSide) {
user.foodData.eat(1, 0.1f)
user.foodData.eat(4, 1.2f)
stack.consume(1, user)
}
@@ -383,7 +383,6 @@ object CobblemonItemGroups {
private fun consumableEntries(displayContext: ItemDisplayParameters, entries: Output) {
entries.accept(CobblemonItems.ROASTED_LEEK)
entries.accept(CobblemonItems.LEEK_AND_POTATO_STEW)
entries.accept(CobblemonItems.BRAISED_VIVICHOKE)
entries.accept(CobblemonItems.VIVICHOKE_DIP)
entries.accept(CobblemonItems.TART_APPLE)
entries.accept(CobblemonItems.SWEET_APPLE)
@@ -399,8 +398,9 @@ object CobblemonItemGroups {
entries.accept(CobblemonItems.CANDIED_APPLE)
entries.accept(CobblemonItems.CANDIED_BERRY)
entries.accept(CobblemonItems.FRIED_RICE)
entries.accept(CobblemonItems.POTATO_MOCHI)
entries.accept(CobblemonItems.PONIGIRI)
entries.accept(CobblemonItems.SINISTER_TEA)
entries.accept(CobblemonItems.PEWTER_CRUNCHIES)
entries.accept(CobblemonItems.RAGE_CANDY_BAR)
@@ -409,7 +409,6 @@ object CobblemonItemGroups {
entries.accept(CobblemonItems.OLD_GATEAU)
entries.accept(CobblemonItems.CASTELIACONE)
entries.accept(CobblemonItems.LUMIOSE_GALETTE)
entries.accept(CobblemonItems.SHALOUR_SABLE)
entries.accept(CobblemonItems.BIG_MALASADA)
entries.accept(CobblemonItems.SMOKED_TAIL_CURRY)
entries.accept(CobblemonItems.OPEN_FACED_SANDWICH)
@@ -419,6 +418,7 @@ object CobblemonItemGroups {
entries.accept(CobblemonItems.FINE_REMEDY)
entries.accept(CobblemonItems.SUPERB_REMEDY)
entries.accept(CobblemonItems.HEAL_POWDER)
entries.accept(CobblemonItems.MOOMOO_MILK)
entries.accept(CobblemonItems.MEDICINAL_BREW)
entries.accept(CobblemonItems.POTION)
@@ -518,10 +518,23 @@ object CobblemonItemGroups {
entries.accept(CobblemonItems.JOLLY_MINT)
entries.accept(CobblemonItems.NAIVE_MINT)
entries.accept(CobblemonItems.SERIOUS_MINT)
entries.accept(CobblemonItems.MOOMOO_MILK)
entries.accept(CobblemonItems.ABILITY_CAPSULE)
entries.accept(CobblemonItems.ABILITY_PATCH)
entries.accept(CobblemonItems.POKE_BAIT)
entries.accept(CobblemonItems.LURE_CAKE)
entries.accept(CobblemonItems.POKE_CAKE)
entries.accept(CobblemonItems.POKE_PUFF)
entries.accept(CobblemonItems.APRIJUICE_RED)
entries.accept(CobblemonItems.APRIJUICE_YELLOW)
entries.accept(CobblemonItems.APRIJUICE_GREEN)
entries.accept(CobblemonItems.APRIJUICE_BLUE)
entries.accept(CobblemonItems.APRIJUICE_PINK)
entries.accept(CobblemonItems.APRIJUICE_BLACK)
entries.accept(CobblemonItems.APRIJUICE_WHITE)
}
private fun evolutionItemEntries(displayContext: ItemDisplayParameters, entries: Output) {
@@ -742,7 +755,6 @@ object CobblemonItemGroups {
injector.putAfter(CobblemonItems.MEDICINAL_LEEK, Items.POISONOUS_POTATO)
injector.putAfter(CobblemonItems.ROASTED_LEEK, CobblemonItems.MEDICINAL_LEEK)
injector.putAfter(CobblemonItems.BRAISED_VIVICHOKE, CobblemonItems.ROASTED_LEEK)
injector.putAfter(CobblemonItems.WHIPPED_DREAM, Items.PUMPKIN_PIE)
injector.putAfter(CobblemonItems.STRAWBERRY_SWEET, CobblemonItems.WHIPPED_DREAM)
@@ -755,24 +767,24 @@ object CobblemonItemGroups {
injector.putAfter(CobblemonItems.CANDIED_APPLE, CobblemonItems.RIBBON_SWEET)
injector.putAfter(CobblemonItems.CANDIED_BERRY, CobblemonItems.CANDIED_APPLE)
injector.putAfter(CobblemonItems.FRIED_RICE, CobblemonItems.CANDIED_BERRY)
injector.putAfter(CobblemonItems.POTATO_MOCHI, CobblemonItems.FRIED_RICE)
injector.putAfter(CobblemonItems.POTATO_MOCHI, CobblemonItems.CANDIED_BERRY)
injector.putAfter(CobblemonItems.PONIGIRI, CobblemonItems.POTATO_MOCHI)
injector.putAfter(CobblemonItems.PEWTER_CRUNCHIES, CobblemonItems.POTATO_MOCHI)
injector.putAfter(CobblemonItems.PEWTER_CRUNCHIES, CobblemonItems.PONIGIRI)
injector.putAfter(CobblemonItems.RAGE_CANDY_BAR, CobblemonItems.PEWTER_CRUNCHIES)
injector.putAfter(CobblemonItems.LAVA_COOKIE, CobblemonItems.RAGE_CANDY_BAR)
injector.putAfter(CobblemonItems.JUBILIFE_MUFFIN, CobblemonItems.LAVA_COOKIE)
injector.putAfter(CobblemonItems.OLD_GATEAU, CobblemonItems.JUBILIFE_MUFFIN)
injector.putAfter(CobblemonItems.CASTELIACONE, CobblemonItems.OLD_GATEAU)
injector.putAfter(CobblemonItems.LUMIOSE_GALETTE, CobblemonItems.CASTELIACONE)
injector.putAfter(CobblemonItems.SHALOUR_SABLE, CobblemonItems.LUMIOSE_GALETTE)
injector.putAfter(CobblemonItems.BIG_MALASADA, CobblemonItems.SHALOUR_SABLE)
injector.putAfter(CobblemonItems.BIG_MALASADA, CobblemonItems.LUMIOSE_GALETTE)
injector.putAfter(CobblemonItems.SMOKED_TAIL_CURRY, CobblemonItems.BIG_MALASADA)
injector.putAfter(CobblemonItems.OPEN_FACED_SANDWICH, CobblemonItems.SMOKED_TAIL_CURRY)
injector.putAfter(CobblemonItems.LEEK_AND_POTATO_STEW, Items.RABBIT_STEW)
injector.putAfter(CobblemonItems.VIVICHOKE_DIP, CobblemonItems.LEEK_AND_POTATO_STEW)
injector.putAfter(CobblemonItems.SINISTER_TEA, Items.MILK_BUCKET)
}
private fun toolsAndUtilitiesInjections(injector: Injector) {
@@ -25,8 +25,8 @@ class PonigiriItem : Item(
Properties().stacksTo(16)
.food(FoodProperties.Builder()
.alwaysEdible()
.nutrition(2)
.saturationModifier(0.55f)
.nutrition(4)
.saturationModifier(0.275f)
.build())
) {
override fun getName(stack: ItemStack): Component {
@@ -56,9 +56,7 @@ import com.cobblemon.mod.common.api.pokemon.moves.LearnsetQuery
import com.cobblemon.mod.common.api.pokemon.stats.Stat
import com.cobblemon.mod.common.api.pokemon.stats.Stats
import com.cobblemon.mod.common.api.properties.CustomPokemonProperty
import com.cobblemon.mod.common.api.reactive.Observable
import com.cobblemon.mod.common.api.reactive.SettableObservable
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.api.riding.RidingProperties
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.api.scheduling.afterOnServer
@@ -625,7 +623,7 @@ open class Pokemon : ShowdownIdentifiable {
/**
* Arbitrary data compound. Be aware that updating this is not enough for a Pokémon to be recognized as dirty
* and in need of saving. Emit to [changeObservable] if you are making a change otherwise you'll see reversions.
* and in need of saving. Call [onChange] if you are making a change otherwise you'll potentially see reversions.
*/
var persistentData: CompoundTag =
CompoundTag()
@@ -2023,20 +2021,18 @@ open class Pokemon : ShowdownIdentifiable {
onChange(packet)
}
/** An [Observable] that emits the Pokémon whenever any change is made to it. The change itself is not included. */
val changeObservable = SimpleObservable<Pokemon>()
/**
* Function to run when a save-able change has been made to the Pokémon. This takes a packet to send to watching
* players just for convenience, but the main thing is that this will push an update to [changeObservable] which
* is primarily (for our purposes, at least) so that we can detect when storage needs to be queued for saving.
* players just for convenience, but the main thing is that this will notify the store that this Pokémon is in
* (if it's in a party/PC/whatever) that the Pokémon has changed and that it should consider saving it when it can.
*/
fun onChange(packet: PokemonUpdatePacket<*>? = null) {
if (packet != null && storeCoordinates.get() != null) {
val storeCoordinates = storeCoordinates.get() ?: return // If they aren't in a store then we don't care.
if (packet != null) {
notify(packet)
}
changeObservable.emit(this)
storeCoordinates.store.onPokemonChanged(pokemon = this)
}
/**
@@ -0,0 +1,59 @@
/*
* 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.util.adapters
import com.cobblemon.mod.common.api.spawning.IrlDateRange
import com.google.gson.JsonDeserializationContext
import com.google.gson.JsonDeserializer
import com.google.gson.JsonElement
import com.google.gson.JsonPrimitive
import com.google.gson.JsonSerializationContext
import com.google.gson.JsonSerializer
import java.lang.reflect.Type
import java.time.LocalDate
import java.time.format.DateTimeFormatter
object IrlDateRangeAdapter: JsonSerializer<IrlDateRange>, JsonDeserializer<IrlDateRange> {
val dateFormat: DateTimeFormatter = DateTimeFormatter.ofPattern("M/d")
//parsing dates requires a year instead of just assuming this year so we do jank
val dateFormatWithYear: DateTimeFormatter = DateTimeFormatter.ofPattern("uuuu/M/d")
override fun serialize(
src: IrlDateRange,
typeOfSrc: Type,
context: JsonSerializationContext
): JsonElement {
return if(src.stopDate != null){
JsonPrimitive(src.startDate.format(dateFormat) + "-" + src.stopDate.format(dateFormat))
} else {
JsonPrimitive(src.startDate.format(dateFormat))
}
}
override fun deserialize(
json: JsonElement,
typeOfT: Type,
context: JsonDeserializationContext
): IrlDateRange {
val year = LocalDate.now().year.toString()
return if(json.asString.contains("-")){
val dateStrings = json.asString.split("-")
IrlDateRange(
startDate = LocalDate.parse(year + "/" + dateStrings[0], dateFormatWithYear),
stopDate = LocalDate.parse(year + "/" + dateStrings[1], dateFormatWithYear)
)
} else {
IrlDateRange(
startDate = LocalDate.parse(year + "/" + json.asString, dateFormatWithYear)
)
}
}
}
@@ -0,0 +1,45 @@
/*
* 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.util.adapters
import com.cobblemon.mod.common.api.spawning.IrlTimeRange
import com.google.gson.JsonDeserializationContext
import com.google.gson.JsonDeserializer
import com.google.gson.JsonElement
import com.google.gson.JsonPrimitive
import com.google.gson.JsonSerializationContext
import com.google.gson.JsonSerializer
import java.lang.reflect.Type
import java.time.LocalTime
import java.time.format.DateTimeFormatter
object IrlTimeRangeAdapter: JsonSerializer<IrlTimeRange>, JsonDeserializer<IrlTimeRange> {
val timeFormat: DateTimeFormatter = DateTimeFormatter.ofPattern("H:mm")
override fun serialize(
src: IrlTimeRange,
typeOfSrc: Type,
context: JsonSerializationContext
): JsonElement {
return JsonPrimitive(src.startTime.format(timeFormat) + "-" + src.stopTime.format(timeFormat))
}
override fun deserialize(
json: JsonElement,
typeOfT: Type,
context: JsonDeserializationContext
): IrlTimeRange {
val timeStrings = json.asString.split("-")
return IrlTimeRange(
startTime = LocalTime.parse(timeStrings[0], timeFormat),
stopTime = LocalTime.parse(timeStrings[1], timeFormat)
)
}
}
@@ -0,0 +1,45 @@
/*
* 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.util.adapters
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettings
import com.google.gson.JsonDeserializationContext
import com.google.gson.JsonDeserializer
import com.google.gson.JsonElement
import java.lang.reflect.Type
/**
* Class to control playing all ride sounds
*
* @author Jackowes
* @since April 26th, 2025
*/
object RideSoundSettingsListAdapter: JsonDeserializer<RideSoundSettingsList?> {
override fun deserialize(
element: JsonElement,
type: Type,
context: JsonDeserializationContext
): RideSoundSettingsList {
val sounds = when {
element.isJsonArray -> {
element.asJsonArray.map {
context.deserialize(it, RideSoundSettings::class.java)
}
}
element.isJsonObject -> {
listOf(context.deserialize<RideSoundSettings>(element, RideSoundSettings::class.java))
}
else -> {
emptyList()
}
}
return RideSoundSettingsList(sounds)
}
}
@@ -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.util.adapters
import com.cobblemon.mod.common.Cobblemon
import com.google.gson.JsonDeserializationContext
import com.google.gson.JsonDeserializer
import com.google.gson.JsonElement
import com.google.gson.JsonPrimitive
import com.google.gson.JsonSerializationContext
import com.google.gson.JsonSerializer
import java.lang.reflect.Type
import java.time.ZoneId
import java.time.ZoneOffset
/**
* A type adapter for [ZoneOffset]s.
* This will never fail to deserialize and will instead default to the System's default time zone and log an error message.
*
* @author JPAK
* @since December 26, 2024
*/
object ZoneIdAdapter: JsonSerializer<ZoneId>, JsonDeserializer<ZoneId> {
override fun serialize(
src: ZoneId,
typeOfSrc: Type,
context: JsonSerializationContext
): JsonElement {
return JsonPrimitive(src.id)
}
override fun deserialize(
json: JsonElement,
typeOfT: Type,
context: JsonDeserializationContext
): ZoneId {
return try {
ZoneId.of(json.asString)
} catch (_: Exception) {
Cobblemon.LOGGER.error("Failed to load ZoneId from the ID {}, defaulting to the system time zone", json.asString)
ZoneOffset.systemDefault()
}
}
}
@@ -307,10 +307,6 @@
"item.cobblemon.serious_mint": "Serious Mint",
"item.cobblemon.serious_mint.tooltip": "Changes a Pokémon's stats to be that of a Serious nature",
"item.cobblemon.potato_mochi": "Potato Mochi",
"item.cobblemon.fried_rice": "Fried Rice",
"item.cobblemon.candied_apple": "Candied Apple",
"item.cobblemon.ability_shield": "Ability Shield",
"item.cobblemon.ability_shield.tooltip": "Prevents the holder's ability from being changed or suppressed",
"item.cobblemon.absorb_bulb": "Absorb Bulb",
@@ -597,10 +593,9 @@
"item.cobblemon.energy_root": "Energy Root",
"item.cobblemon.energy_root.tooltip": "Restores 80 HP of a Pokémon whilst reducing friendship",
"item.cobblemon.revival_herb": "Revival Herb",
"item.cobblemon.revival_herb.tooltip": "Fully revives a fainted Pokémon whilst reducing Friendship",
"item.cobblemon.revival_herb.tooltip": "Revives a fainted Pokémon to 25% of their max HP whilst reducing Friendship",
"item.cobblemon.roasted_leek": "Roasted Leek",
"item.cobblemon.leek_and_potato_stew": "Leek and Potato Stew",
"item.cobblemon.braised_vivichoke": "Braised Vivichoke",
"item.cobblemon.vivichoke_dip": "Vivichoke Dip",
"item.cobblemon.berry_juice": "Berry Juice",
"item.cobblemon.berry_juice.tooltip": "Restores 20 HP when a Pokémon holding the item falls below 50% HP",
@@ -775,7 +770,6 @@
"item.cobblemon.tasty_tail": "Tasty Tail",
"item.cobblemon.candied_berry": "Candied Berries",
"item.cobblemon.candied_apple": "Candied Apple",
"item.cobblemon.fried_rice": "Fried Rice",
"item.cobblemon.pewter_crunchies": "Pewter Crunchies",
"item.cobblemon.rage_candy_bar": "Rage Candy Bar",
"item.cobblemon.lava_cookie": "Lava Cookie",
@@ -804,15 +798,15 @@
"cobblemon.item_class.flavor_seasoning": "Flavor Seasoning",
"cobblemon.item_class.bait_effect_seasoning": "Bait Seasoning",
"cobblemon.item_class.mob_effect_seasoning": "Effect Seasoning",
"cobblemon.item_class.food_seasoning": "Food Seasoning",
"cobblemon.item_class.food_seasoning": "Nutrition Seasoning",
"cobblemon.seasoning_bait_effect_header": "Bait",
"cobblemon.seasoning_mob_effect_header": "Effect",
"cobblemon.seasoning_food_header": "Food",
"cobblemon.seasoning_food_header": "Nutrition",
"cobblemon.seasoning_flavor_header": "Flavor",
"cobblemon.seasoning_bait_effect_info_header": "Bait Info:",
"cobblemon.seasoning_mob_effect_info_header": "Effect Info:",
"cobblemon.seasoning_food_info_header": "Food Info:",
"cobblemon.seasoning_food_info_header": "Nutrition Info:",
"cobblemon.seasoning_flavor_info_header": "Flavor Info:",
"cobblemon.seasoning_flavor.spicy": "Spicy",
"cobblemon.seasoning_flavor.dry": "Dry",
@@ -2388,6 +2382,7 @@
"cobblemon.config.ui.category.interface": "Interface",
"cobblemon.config.ui.category.riding": "Riding",
"cobblemon.config.ui.category.debug": "Debug",
"cobblemon.config.ui.category.system": "System",
"cobblemon.config.ui.max_pokemon_level": "Max Pokémon Level",
"cobblemon.config.ui.max_pokemon_level.tooltip": "The maximum level Pokémon can get to.",
@@ -2570,6 +2565,8 @@
"cobblemon.config.ui.despawner_max_age_ticks.tooltip": "The age (in game ticks) a Pokémon must reach before it can be despawned at the near distance boundary.",
"cobblemon.config.ui.enable_debug_keys": "Enable Debug Keys",
"cobblemon.config.ui.enable_debug_keys.tooltip": "Enables additional commands and chat logging to assist with implementing Pokémon (see the wiki for a list of commands).",
"cobblemon.config.ui.time_zone_id": "Time Zone Id",
"cobblemon.config.ui.time_zone_id.tooltip": "Time Zone to use for some MoLangs and IRL time zone dates. Server Side only.",
"cobblemon.config.ui.allow_starter_on_join": "Allow Starter on Join",
"cobblemon.config.ui.prompt_starter_once_only": "Prompt Starter Once Only",
@@ -3088,6 +3085,7 @@
"cobblemon.battle.end.powershift": "%1$s swapped its offensive stats with its defensive stats!",
"cobblemon.battle.end.powertrick": "%1$s switched its Attack and Defense!",
"cobblemon.battle.end.protosynthesis": "The effects of %1$s's Protosynthesis wore off!",
"cobblemon.battle.end.sandtomb": "%1$s was freed from Sand Tomb!",
"cobblemon.battle.end.skydrop": "%1$s was freed from the Sky Drop!",
"cobblemon.battle.end.stockpile": "%1$s's stockpiled effect wore off!",
"cobblemon.battle.end.substitute": "%1$s's substitute faded!",
@@ -3732,6 +3730,15 @@
"cobblemon.behaviour.npc_scale_configuration.name": "NPC Entity Scales",
"cobblemon.behaviour.npc_scale_configuration.desc": "Allows configuring the render- and hitbox-scale of the NPC.",
"cobblemon.behaviour.pokemon_gets_mad_at_thrower.name": "Gets Mad At Poké Ball Thrower",
"cobblemon.behaviour.pokemon_gets_mad_at_thrower.desc": "Gets angry at the player that threw a Poké Ball at it. Aggressive Pokémon will attack the player, while non-aggressive Pokémon will flee.",
"cobblemon.behaviour.attack_hostile_mobs.name": "Attacks Hostile Mobs",
"cobblemon.behaviour.attack_hostile_mobs.desc": "Attacks hostile mobs that are nearby, even if unprovoked.",
"cobblemon.behaviour.pokemon_bee.name": "Pollinates Flowers",
"cobblemon.behaviour.pokemon_bee.desc": "Pollinates flowers and adds honey to hives and Saccharine tree leaves.",
"npc.sacchi.name": "Professor Sacchi",
"cobblemon.move.pound": "Pound",
@@ -1,7 +1,6 @@
{
"parent": "cobblemon:block/revival_herb",
"textures": {
"0": "cobblemon:block/crops/mental_herb_stage_0",
"particle": "cobblemon:block/crops/mental_herb_stage_0"
"0": "cobblemon:block/crops/mental_herb_stage_0"
}
}
@@ -1,7 +1,6 @@
{
"parent": "cobblemon:block/revival_herb",
"textures": {
"0": "cobblemon:block/crops/mental_herb_stage_1",
"particle": "cobblemon:block/crops/mental_herb_stage_1"
"0": "cobblemon:block/crops/mental_herb_stage_1"
}
}
@@ -1,7 +1,6 @@
{
"parent": "cobblemon:block/revival_herb",
"textures": {
"0": "cobblemon:block/crops/mirror_herb_stage_0",
"particle": "cobblemon:block/crops/mirror_herb_stage_0"
"0": "cobblemon:block/crops/mirror_herb_stage_0"
}
}
@@ -1,7 +1,6 @@
{
"parent": "cobblemon:block/revival_herb",
"textures": {
"0": "cobblemon:block/crops/mirror_herb_stage_1",
"particle": "cobblemon:block/crops/mirror_herb_stage_1"
"0": "cobblemon:block/crops/mirror_herb_stage_1"
}
}
@@ -1,7 +1,6 @@
{
"parent": "cobblemon:block/revival_herb",
"textures": {
"0": "cobblemon:block/crops/power_herb_stage_0",
"particle": "cobblemon:block/crops/power_herb_stage_0"
"0": "cobblemon:block/crops/power_herb_stage_0"
}
}
@@ -1,7 +1,6 @@
{
"parent": "cobblemon:block/revival_herb",
"textures": {
"0": "cobblemon:block/crops/power_herb_stage_1",
"particle": "cobblemon:block/crops/power_herb_stage_1"
"0": "cobblemon:block/crops/power_herb_stage_1"
}
}
@@ -1,9 +1,9 @@
{
"credit": "Made with Blockbench / EikoBiko",
"texture_size": [64, 64],
"format_version": "1.21.6",
"credit": "Made with Blockbench",
"texture_size": [32, 16],
"textures": {
"0": "cobblemon:block/crops/revival_herb_stage_0",
"particle": "cobblemon:block/crops/revival_herb_stage_0"
"particle": "#0"
},
"elements": [
{
@@ -11,12 +11,8 @@
"to": [15, 9, 8],
"rotation": {"angle": -45, "axis": "y", "origin": [8, 0, 8]},
"faces": {
"north": {"uv": [0, 4, 7, 9], "texture": "#0"},
"east": {"uv": [3, 4.5, 3, 4.5], "texture": "#0"},
"south": {"uv": [0, 4, 7, 9], "texture": "#0"},
"west": {"uv": [3, 4.5, 3, 4.5], "texture": "#0"},
"up": {"uv": [8, 0, 0, 0], "texture": "#0"},
"down": {"uv": [8, 0, 0, 0], "texture": "#0"}
"north": {"uv": [0, 0, 7, 10], "texture": "#0"},
"south": {"uv": [0, 0, 7, 10], "texture": "#0"}
}
},
{
@@ -24,25 +20,17 @@
"to": [8, 9, 15],
"rotation": {"angle": -45, "axis": "y", "origin": [8, 0, 8]},
"faces": {
"north": {"uv": [3, 4.5, 3, 4.5], "texture": "#0"},
"east": {"uv": [0, 4, 7, 9], "texture": "#0"},
"south": {"uv": [3, 4.5, 3, 4.5], "texture": "#0"},
"west": {"uv": [0, 4, 7, 9], "texture": "#0"},
"up": {"uv": [0, 8, 0, 0], "texture": "#0"},
"down": {"uv": [0, 0, 0, 8], "texture": "#0"}
"east": {"uv": [0, 0, 7, 10], "texture": "#0"},
"west": {"uv": [0, 0, 7, 10], "texture": "#0"}
}
},
{
"from": [1, -1, 8],
"to": [15, 7, 8],
"from": [4, 1, 8],
"to": [12, 5, 8],
"rotation": {"angle": 0, "axis": "y", "origin": [8, 0, 8]},
"faces": {
"north": {"uv": [0, 0, 7, 4], "texture": "#0"},
"east": {"uv": [0, 0, 0, 3.5], "texture": "#0"},
"south": {"uv": [0, 0, 7, 4], "texture": "#0"},
"west": {"uv": [0, 0, 0, 3.5], "texture": "#0"},
"up": {"uv": [2, 0, 0, 0], "texture": "#0"},
"down": {"uv": [2, 0, 0, 0], "texture": "#0"}
"north": {"uv": [0, 11, 4, 15], "texture": "#0"},
"south": {"uv": [0, 11, 4, 15], "texture": "#0"}
}
},
{
@@ -50,25 +38,17 @@
"to": [10, 11, 8],
"rotation": {"angle": 0, "axis": "y", "origin": [8, 0, 8]},
"faces": {
"north": {"uv": [14, 1.5, 16, 4.5], "texture": "#0"},
"east": {"uv": [0, 0, 0, 3.5], "texture": "#0"},
"south": {"uv": [14, 1.5, 16, 4.5], "texture": "#0"},
"west": {"uv": [0, 0, 0, 3.5], "texture": "#0"},
"up": {"uv": [2, 0, 0, 0], "texture": "#0"},
"down": {"uv": [2, 0, 0, 0], "texture": "#0"}
"north": {"uv": [11, 5, 13, 11], "texture": "#0"},
"south": {"uv": [11, 5, 13, 11], "texture": "#0"}
}
},
{
"from": [8, -1, 1],
"to": [8, 14, 15],
"from": [8, 1, 4],
"to": [8, 14, 12],
"rotation": {"angle": 0, "axis": "y", "origin": [8, 0, 8]},
"faces": {
"north": {"uv": [0, 0, 0, 8.5], "texture": "#0"},
"east": {"uv": [7, 0, 14, 7.5], "texture": "#0"},
"south": {"uv": [0, 0, 0, 8.5], "texture": "#0"},
"west": {"uv": [14, 0, 7, 7.5], "texture": "#0"},
"up": {"uv": [0, 6, 0, 0], "texture": "#0"},
"down": {"uv": [0, 0, 0, 6], "texture": "#0"}
"east": {"uv": [7, 2, 11, 15], "texture": "#0"},
"west": {"uv": [11, 2, 7, 15], "texture": "#0"}
}
},
{
@@ -76,12 +56,8 @@
"to": [11, 16, 11],
"rotation": {"angle": 0, "axis": "y", "origin": [8, 0, 8]},
"faces": {
"north": {"uv": [0, 13, 3, 16], "texture": "#0"},
"east": {"uv": [0, 13, 3, 16], "texture": "#0"},
"south": {"uv": [0, 13, 3, 16], "texture": "#0"},
"west": {"uv": [3, 13, 0, 16], "texture": "#0"},
"up": {"uv": [0, 16, 3, 13], "texture": "#0"},
"down": {"uv": [0, 13, 3, 16], "texture": "#0"}
"up": {"uv": [13, 6, 16, 0], "texture": "#0"},
"down": {"uv": [13, 0, 16, 6], "texture": "#0"}
}
},
{
@@ -89,12 +65,12 @@
"to": [10.5, 15.5, 10.5],
"rotation": {"angle": 0, "axis": "y", "origin": [8, -0.5, 8]},
"faces": {
"north": {"uv": [0, 10, 2.5, 12.5], "texture": "#0"},
"east": {"uv": [0, 10, 2.5, 12.5], "texture": "#0"},
"south": {"uv": [0, 10, 2.5, 12.5], "texture": "#0"},
"west": {"uv": [2.5, 10, 0, 12.5], "texture": "#0"},
"up": {"uv": [0, 12.5, 2.5, 10], "texture": "#0"},
"down": {"uv": [0, 10, 2.5, 12.5], "texture": "#0"}
"north": {"uv": [13.5, 6, 16, 11], "texture": "#0"},
"east": {"uv": [13.5, 6, 16, 11], "texture": "#0"},
"south": {"uv": [13.5, 6, 16, 11], "texture": "#0"},
"west": {"uv": [16, 6, 13.5, 11], "texture": "#0"},
"up": {"uv": [13.5, 11, 16, 6], "texture": "#0"},
"down": {"uv": [13.5, 6, 16, 11], "texture": "#0"}
}
},
{
@@ -102,12 +78,8 @@
"to": [11, 10, 11],
"rotation": {"angle": 0, "axis": "y", "origin": [8, 0, 8]},
"faces": {
"north": {"uv": [0, 13, 3, 16], "texture": "#0"},
"east": {"uv": [0, 13, 3, 16], "texture": "#0"},
"south": {"uv": [0, 13, 3, 16], "texture": "#0"},
"west": {"uv": [3, 13, 0, 16], "texture": "#0"},
"up": {"uv": [0, 16, 3, 13], "texture": "#0"},
"down": {"uv": [0, 16, 3, 13], "texture": "#0"}
"up": {"uv": [13, 6, 16, 0], "texture": "#0"},
"down": {"uv": [13, 6, 16, 0], "texture": "#0"}
}
},
{
@@ -115,12 +87,8 @@
"to": [5, 16, 11],
"rotation": {"angle": 0, "axis": "y", "origin": [8, 0, 8]},
"faces": {
"north": {"uv": [0, 13, 3, 16], "texture": "#0"},
"east": {"uv": [0, 13, 3, 16], "texture": "#0"},
"south": {"uv": [0, 13, 3, 16], "texture": "#0"},
"west": {"uv": [0, 13, 3, 16], "texture": "#0"},
"up": {"uv": [0, 16, 3, 13], "texture": "#0"},
"down": {"uv": [0, 13, 3, 16], "texture": "#0"}
"east": {"uv": [13, 0, 16, 6], "texture": "#0"},
"west": {"uv": [13, 0, 16, 6], "texture": "#0"}
}
},
{
@@ -128,12 +96,8 @@
"to": [11, 16, 11],
"rotation": {"angle": 0, "axis": "y", "origin": [8, 0, 8]},
"faces": {
"north": {"uv": [0, 13, 3, 16], "texture": "#0"},
"east": {"uv": [0, 13, 3, 16], "texture": "#0"},
"south": {"uv": [0, 13, 3, 16], "texture": "#0"},
"west": {"uv": [3, 13, 0, 16], "texture": "#0"},
"up": {"uv": [0, 16, 3, 13], "texture": "#0"},
"down": {"uv": [0, 13, 3, 16], "texture": "#0"}
"north": {"uv": [13, 0, 16, 6], "texture": "#0"},
"south": {"uv": [13, 0, 16, 6], "texture": "#0"}
}
},
{
@@ -141,12 +105,8 @@
"to": [11, 16, 5],
"rotation": {"angle": 0, "axis": "y", "origin": [8, 0, 8]},
"faces": {
"north": {"uv": [0, 13, 3, 16], "texture": "#0"},
"east": {"uv": [0, 13, 3, 16], "texture": "#0"},
"south": {"uv": [0, 13, 3, 16], "texture": "#0"},
"west": {"uv": [3, 13, 0, 16], "texture": "#0"},
"up": {"uv": [0, 16, 3, 13], "texture": "#0"},
"down": {"uv": [0, 13, 3, 16], "texture": "#0"}
"north": {"uv": [13, 0, 16, 6], "texture": "#0"},
"south": {"uv": [13, 0, 16, 6], "texture": "#0"}
}
},
{
@@ -154,12 +114,8 @@
"to": [11, 16, 11],
"rotation": {"angle": 0, "axis": "y", "origin": [8, 0, 8]},
"faces": {
"north": {"uv": [0, 13, 3, 16], "texture": "#0"},
"east": {"uv": [0, 13, 3, 16], "texture": "#0"},
"south": {"uv": [0, 13, 3, 16], "texture": "#0"},
"west": {"uv": [3, 13, 0, 16], "texture": "#0"},
"up": {"uv": [0, 16, 3, 13], "texture": "#0"},
"down": {"uv": [0, 13, 3, 16], "texture": "#0"}
"east": {"uv": [13, 0, 16, 6], "texture": "#0"},
"west": {"uv": [16, 0, 13, 6], "texture": "#0"}
}
}
],
@@ -1,7 +1,6 @@
{
"parent": "cobblemon:block/revival_herb",
"textures": {
"0": "cobblemon:block/crops/revival_herb_stage_0",
"particle": "cobblemon:block/crops/revival_herb_stage_0"
"0": "cobblemon:block/crops/revival_herb_stage_0"
}
}
@@ -1,7 +1,6 @@
{
"parent": "cobblemon:block/revival_herb",
"textures": {
"0": "cobblemon:block/crops/revival_herb_stage_1",
"particle": "cobblemon:block/crops/revival_herb_stage_1"
"0": "cobblemon:block/crops/revival_herb_stage_1"
}
}
@@ -1,7 +1,6 @@
{
"parent": "cobblemon:block/revival_herb",
"textures": {
"0": "cobblemon:block/crops/revival_herb_stage_2",
"particle": "cobblemon:block/crops/revival_herb_stage_2"
"0": "cobblemon:block/crops/revival_herb_stage_2"
}
}
@@ -1,7 +1,6 @@
{
"parent": "cobblemon:block/revival_herb",
"textures": {
"0": "cobblemon:block/crops/revival_herb_stage_3",
"particle": "cobblemon:block/crops/revival_herb_stage_3"
"0": "cobblemon:block/crops/revival_herb_stage_3"
}
}
@@ -1,7 +1,6 @@
{
"parent": "cobblemon:block/revival_herb",
"textures": {
"0": "cobblemon:block/crops/revival_herb_stage_4",
"particle": "cobblemon:block/crops/revival_herb_stage_4"
"0": "cobblemon:block/crops/revival_herb_stage_4"
}
}
@@ -1,7 +1,6 @@
{
"parent": "cobblemon:block/revival_herb",
"textures": {
"0": "cobblemon:block/crops/revival_herb_stage_5",
"particle": "cobblemon:block/crops/revival_herb_stage_5"
"0": "cobblemon:block/crops/revival_herb_stage_5"
}
}
@@ -1,7 +1,6 @@
{
"parent": "cobblemon:block/revival_herb",
"textures": {
"0": "cobblemon:block/crops/revival_herb_stage_6",
"particle": "cobblemon:block/crops/revival_herb_stage_6"
"0": "cobblemon:block/crops/revival_herb_stage_6"
}
}
@@ -1,7 +1,6 @@
{
"parent": "cobblemon:block/revival_herb",
"textures": {
"0": "cobblemon:block/crops/revival_herb_stage_7",
"particle": "cobblemon:block/crops/revival_herb_stage_7"
"0": "cobblemon:block/crops/revival_herb_stage_7"
}
}
@@ -1,7 +1,6 @@
{
"parent": "cobblemon:block/revival_herb",
"textures": {
"0": "cobblemon:block/crops/revival_herb_stage_8",
"particle": "cobblemon:block/crops/revival_herb_stage_8"
"0": "cobblemon:block/crops/revival_herb_stage_8"
}
}
@@ -1,7 +1,6 @@
{
"parent": "cobblemon:block/revival_herb",
"textures": {
"0": "cobblemon:block/crops/white_herb_stage_0",
"particle": "cobblemon:block/crops/white_herb_stage_0"
"0": "cobblemon:block/crops/white_herb_stage_0"
}
}
@@ -1,7 +1,6 @@
{
"parent": "cobblemon:block/revival_herb",
"textures": {
"0": "cobblemon:block/crops/white_herb_stage_1",
"particle": "cobblemon:block/crops/white_herb_stage_1"
"0": "cobblemon:block/crops/white_herb_stage_1"
}
}
@@ -1,6 +0,0 @@
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "cobblemon:item/food/braised_vivichoke"
}
}
@@ -1,6 +0,0 @@
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "cobblemon:item/food/fried_rice"
}
}
@@ -2,5 +2,13 @@
"parent": "minecraft:item/generated",
"textures": {
"layer0": "cobblemon:item/food/lumiose_galette"
}
},
"overrides": [
{
"predicate": {
"custom_model_data": 2
},
"model": "cobblemon:item/shalour_sable"
}
]
}
@@ -1152,6 +1152,33 @@
{ "name": "cobblemon:animation/wings/chitin_wing_flap_medium_8", "volume": 0.3 }
]
},
"ride.loop.leather": {
"sounds": [
{
"name": "cobblemon:ride/loop/leather_flight_loop",
"stream": true,
"attenuation_distance": 48.0
}
]
},
"ride.loop.plumage": {
"sounds": [
{
"name": "cobblemon:ride/loop/plumage_flight_loop",
"stream": true,
"attenuation_distance": 48.0,
"volume": 0.25
}
]
},
"ride.loop.saucer": {
"sounds": [
{
"name": "cobblemon:ride/loop/saucer_flight_loop",
"attenuation_distance": 48.0
}
]
},
"pokemon.bulbasaur.ambient": {
"sounds": [ "cobblemon:pokemon/bulbasaur/bulbasaur_ambient" ]
},

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