Compare commits

...
51 Commits
Author SHA1 Message Date
Segfault Guy 41cf6c9efb Merge branch 'relic_song' of https://gitlab.com/cable-mc/cobblemon into relic_song 2026-01-15 23:02:08 -05:00
Segfault Guy 15b73cedb6 Minor optimizations and edge case cleanup. 2026-01-15 23:00:35 -05:00
Segfault Guy 56e8ed5a92 Merge branch 'relic_song' of https://gitlab.com/cable-mc/cobblemon into relic_song 2026-01-14 16:50:20 -05:00
Segfault Guy 99cfe4f0c3 OST framework 2026-01-14 16:49:50 -05:00
Segfault Guy 557ace977a Merge branch 'relic_song' of https://gitlab.com/cable-mc/cobblemon into relic_song 2025-12-16 19:32:56 -05:00
Segfault Guy 1ba55b9f90 Fix stuttering on loop start. Rework caching to remedy edge cases. 2025-12-16 19:31:26 -05:00
Segfault Guy e1960c2b0b Add DepthRequirement and SkyRequirement. 2025-12-16 13:45:33 -05:00
Segfault Guy 4bee9412ce Generalize EntityQueryRequirements. 2025-12-16 13:44:42 -05:00
Segfault Guy 264c7659f6 Merge remote-tracking branch 'origin/1.8.0' into relic_song 2025-12-16 09:53:08 -05:00
Polymeta 06ebf51078 Merge branch 'Wii-Break-It-Wii-Fix-It' into 'main'
Boat Tags and Mint Leaves

Closes #1855

See merge request cable-mc/cobblemon!2082
2025-12-13 22:14:32 +00:00
Polymeta 39c5bcd73c Merge branch 'even-more-seasonings' into 'main'
Adding Missing Effects for Seasonings

See merge request cable-mc/cobblemon!1991
2025-12-13 22:13:24 +00:00
CeejayandPolymeta 8f5f158b60 Adding Missing Effects for Seasonings 2025-12-13 22:13:24 +00:00
dhi_holo 3bc1e35125 Update CHANGELOG.md 2025-12-13 18:42:50 +10:00
dhi_holo 460bedddae Boat Tags
Resolves #1855
2025-12-13 18:12:24 +10:00
dhi_holo 8b64c315f3 Merge branch 'main' into Wii-Break-It-Wii-Fix-It 2025-12-13 17:51:15 +10:00
dhi_holo 7c824a2b72 OVERSIGHTS 2025-12-13 17:50:57 +10:00
Paul f197ac5077 Removed a bunch of duplicates from is_held_item because it looks whack. 2025-12-12 16:24:30 +10:00
Hiroku e0be669482 Merge branch 'patch-1' into 'main'
Fix Soothe Bell not tagged as `cobblemon:held/is_held_item`

See merge request cable-mc/cobblemon!2081
2025-12-12 06:17:17 +00:00
Micky 08ea21fff4 Fix Soothe Bell not tagged as cobblemon:held/is_held_item 2025-12-10 13:27:23 +00:00
Polymeta 28c0f8ab7d Merge branch 'item-interactions-sync' into 'main'
Item interactions sync

See merge request cable-mc/cobblemon!2068
2025-12-08 16:08:48 +00:00
lionandPolymeta fcbe0db568 Item interactions sync 2025-12-08 16:08:48 +00:00
Polymeta 83cefe476e Merge branch 'partner-mark' into 'main'
Add Partner Mark

See merge request cable-mc/cobblemon!2034
2025-12-08 15:57:21 +00:00
Guilherme Pereira 690ccbd260 refactor check riding statistics method for server player mixin 2025-12-08 12:06:20 -03:00
Polymeta 63846d53fb Merge branch 'main' into 'main'
Fixed shiny Pokémon not respecting the silent flag on spawn.

See merge request cable-mc/cobblemon!2064
2025-12-08 13:07:25 +00:00
Polymeta 86cb865742 fixed mochi items being consumed twice
Signed-off-by: Polymeta <40173118+Polymeta@users.noreply.github.com>
2025-12-08 13:01:14 +00:00
Polymeta 1a0c9b6a82 Merge branch 'malformed-jsons-no-more' into 'main'
Fixes a crash caused by malformed riding prespectives json file when trying to ride a pokemon

See merge request cable-mc/cobblemon!2077
2025-12-08 12:59:22 +00:00
Polymeta 985eb103fe Merge branch 'sterr/ride-sound-changes' into 'main'
New ride sound stuff

See merge request cable-mc/cobblemon!2065
2025-12-08 11:44:22 +00:00
Polymeta aec76a78da Merge branch 'restore_adorn_texturing' into 'main'
Restore adorn texturing

Closes #1833

See merge request cable-mc/cobblemon!2078
2025-12-08 11:36:41 +00:00
Jacob Hartmann 17f3edf2be Changelog 2025-12-07 20:35:11 -07:00
Jacob Hartmann 9c5bfda20f Updating texture paths 2025-12-07 20:34:09 -07:00
Gertimoshka 6d545fc1ca Fixes a crash caused by malformed riding prespectives json file when trying to ride a pokemon 2025-12-07 11:47:04 +02:00
Khvrram 7e6ca7be5e Merge branch cobblemon:main into main 2025-12-04 01:59:17 +00:00
Segfault Guy 0c320905e1 Merge remote-tracking branch 'origin/1.8.0' into relic_song 2025-12-01 19:59:33 -05:00
Sterrezo a575c6bc3d Update CHANGELOG.md 2025-12-01 15:47:34 +01:00
Sterrezo 9c102a7850 Merge branch 'main' into sterr/ride-sound-changes 2025-12-01 15:45:40 +01:00
Sterrezo 22a1eaab19 Volume balancing 2025-12-01 15:35:22 +01:00
Sterrezo 0118ace5fc Implemented stereo flight loops 2025-12-01 15:09:37 +01:00
Sterrezo d4a93abbf7 Added support for submerged ride sounds 2025-12-01 14:27:55 +01:00
Sterrezo d0ee3f3e9d Added an option for ride sounds to not play for the passengers 2025-12-01 13:56:56 +01:00
Khvrram 46ae53f565 Merge branch cobblemon:main into main 2025-12-01 04:25:52 +00:00
Khurram Valiyev 4d123b6e4b Fixed shiny Pokémon not respecting the silent flag on spawn. 2025-11-29 22:50:52 -05:00
Guilherme Pereira 29b560d244 Merge branch 'main' of gitlab.com:cable-mc/cobblemon into partner-mark 2025-11-27 18:21:19 -03:00
Guilherme Pereira 7cedfb9047 update CHANGELOG.md 2025-11-22 22:02:14 -03:00
Guilherme Pereira a7ab2e37c2 Merge branch 'main' of gitlab.com:cable-mc/cobblemon into partner-mark 2025-11-22 21:57:49 -03:00
Guilherme Pereira 4723f91500 add riding stats 2025-11-22 21:57:25 -03:00
Guilherme Pereira e23aa6b066 add partner mark molang callback 2025-11-22 19:39:21 -03:00
Segfault Guy b92148d0f9 Use cached buffers when reading from stream while rewinding. 2025-10-23 23:07:34 -04:00
Segfault Guy 6f1ac0c10b Simplify CompoundAudioStream to only compound streams of the same format. Enable compouding of loop streams. 2025-10-23 15:00:50 -04:00
Segfault Guy 692d42e626 LoopPointAudioStream cleanup 2025-10-20 14:37:53 -04:00
Segfault Guy f73e93a5a8 Battle music loop point support. 2025-10-19 21:06:48 -04:00
Segfault Guy 21ae3dc9e8 duck consolidation 2025-09-24 22:19:49 -04:00
132 changed files with 1927 additions and 348 deletions
+25
View File
@@ -2,22 +2,47 @@
## [1.7.2 (MONTH Xth, YEAR)](#1-7-2)
### Additions
- Added riding statistics: `Distance by Pokémon on Land`, `Distance by Pokémon in Air` and `Distance by Pokémon in Liquid`.
- Added the Partner Mark, which party Pokémon have a very small chance to earn as the player walks, provided they have enough friendship.
- Added trades for Saccharine Saplings, Hearty Grains, Chipped Pot, and Masterpiece Teacup to the Wandering Trader.
- Added brewing recipe for Throat Spray.
- Added new effects
- Mental Restoration: Reduces "Time Since Last Rest" statistic, which controls Insomnia, by half a minute per second per effect level
- Negative Effect Cleanse: Removes all negative effects
- Effect Cleanse: Removes all effects
- Added more seasonings for sinister tea
- Mental Herb - Mental Restoration I for 10 seconds
- White Herb - Negative Effect Cleanse
- Milk - Effect Cleanse
- Moomoo Milk - Effect Cleanse
### Changes
- Ride sounds have been separated into stereo for passengers and mono for other players. Riding will now sound more spacious.
### Fixes
- Fixed orientation not getting reset after crashing while flying and getting back on the Pokémon.
- Fixed Furfrou not being trimmable on NeoForge.
- Fixed shiny Pokémon not respecting the silent flag on spawn.
- Fixed honey from Saccharine Leaves being harvestable at less than the max age.
- Items given from interacting with Saccharine Leaves are placed in the active hotbar slot if possible.
- Fix all Pokémon being saved to chunks and never despawning.
- Fixed PokeSnacks crashing if there was no available spawn.
- Fixed Pokémon spawn influences not being specific to regional forms
- Fixed crashing when viewing another player in spectator mode
- Fixed missing apricorn textures for Adorn blocks
- Fixed crashing when riding due to malformed json file, instead resetting it
- Fixed mochi items consuming two items at a time
- Fix item interaction sometimes not working properly when playing on servers.
- Fixed Soothe Bell not being properly tagged as a held item.
- Fixed Mint Leaves not being usable for filling the Resurrection Machine.
- Added Saccharine Boats and Saccharine Boat with Chests to relevant boat tags.
### Developer
- Changed the `owner` parameter in the `OwnerQueryRequirement` interface from `ServerPlayer` to `Player`. This method is now also called on the client to verify whether a Pokémon interaction succeeded, so make sure to update your implementations to handle both server and client contexts.
### Molang & Datapacks
- Ride sounds can now be set as exclusive to passengers
- Ride sounds no longer play when submerged unless specified with a new setting
### Particles
@@ -0,0 +1,15 @@
package com.cobblemon.mod.common.duck;
import net.minecraft.client.sounds.AudioStream;
import net.minecraft.resources.ResourceLocation;
import java.util.concurrent.CompletableFuture;
public interface SoundBufferLibraryDuck {
CompletableFuture<AudioStream> getStreamWithLoopPoints(ResourceLocation audioLocation, int loopStart, int loopEnd);
CompletableFuture<AudioStream> getStreamWithIntro(ResourceLocation introLocation, ResourceLocation endLocation, boolean isLoop);
CompletableFuture<AudioStream> getStreamWithIntro(ResourceLocation introLocation, ResourceLocation endLocation, int loopStart, int loopEnd);
}
@@ -0,0 +1,22 @@
package com.cobblemon.mod.common.duck;
import net.minecraft.resources.ResourceLocation;
public interface SoundDuck {
int getLoopStart();
int getLoopEnd();
ResourceLocation getIntro();
void setLoopStart(int startInSamples);
void setLoopEnd(int endInSamples);
void setIntro(ResourceLocation intro);
boolean hasLoopPoints();
boolean hasIntro();
}
@@ -6,7 +6,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.bridges;
package com.cobblemon.mod.common.duck;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureProcessor;
@@ -0,0 +1,24 @@
package com.cobblemon.mod.common.mixin;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import net.minecraft.client.Minecraft;
import net.minecraft.sounds.Music;
import net.minecraft.world.level.biome.Biome;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import java.util.Optional;
@Mixin(Minecraft.class)
public class MinecraftMixin {
/** Used by MusicManager to dispatch the music to play. */
@WrapOperation(
method = "getSituationalMusic",
at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/biome/Biome;getBackgroundMusic()Ljava/util/Optional;")
)
public Optional<Music> getBackgroundMusic(Biome instance, Operation<Optional<Music>> original) {
return original.call(instance);
}
}
@@ -11,11 +11,15 @@ package com.cobblemon.mod.common.mixin;
import com.cobblemon.mod.common.Cobblemon;
import com.cobblemon.mod.common.OrientationControllable;
import com.cobblemon.mod.common.PlayerSpawnerAccessor;
import com.cobblemon.mod.common.api.riding.RidingStyle;
import com.cobblemon.mod.common.api.spawning.spawner.PlayerSpawner;
import com.cobblemon.mod.common.api.spawning.spawner.PlayerSpawnerFactory;
import com.cobblemon.mod.common.api.stats.CobblemonStats;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import com.cobblemon.mod.common.world.gamerules.CobblemonGameRules;
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import com.llamalad7.mixinextras.sugar.Local;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
@@ -89,4 +93,32 @@ public abstract class ServerPlayerMixin implements PlayerSpawnerAccessor {
private boolean cobblemon$cancelSavingPokemonMounts(boolean original, @Local(ordinal=0) Entity entity) {
return original && !(entity instanceof PokemonEntity);
}
@WrapOperation(method = "checkRidingStatistics", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/level/ServerPlayer;getVehicle()Lnet/minecraft/world/entity/Entity;"))
public Entity cobblemon$checkRidingStatistics(ServerPlayer player, Operation<Entity> original, @Local int distance) {
var entity = original.call(player);
if (!(entity instanceof PokemonEntity pokemonEntity)) {
return entity;
}
var ridingController = pokemonEntity.getRidingController();
if (ridingController == null) return entity;
var activeContext = ridingController.getContext();
if (activeContext == null) return entity;
var ridingStyle = activeContext.getStyle();
CobblemonStats.CobblemonStat stat = switch (ridingStyle) {
case RidingStyle.LAND -> CobblemonStats.RIDING_LAND;
case RidingStyle.AIR -> CobblemonStats.RIDING_AIR;
case RidingStyle.LIQUID -> CobblemonStats.RIDING_LIQUID;
default -> null;
};
if (stat != null) {
player.awardStat(CobblemonStats.getStat(stat), distance);
}
return entity;
}
}
@@ -0,0 +1,95 @@
package com.cobblemon.mod.common.mixin;
import com.cobblemon.mod.common.Cobblemon;
import com.cobblemon.mod.common.client.sound.streams.CompoundAudioStream;
import com.cobblemon.mod.common.client.sound.streams.LoopPointAudioStream;
import com.cobblemon.mod.common.duck.SoundBufferLibraryDuck;
import net.minecraft.Util;
import net.minecraft.client.sounds.AudioStream;
import net.minecraft.client.sounds.JOrbisAudioStream;
import net.minecraft.client.sounds.LoopingAudioStream;
import net.minecraft.client.sounds.SoundBufferLibrary;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.ResourceProvider;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
@Mixin(SoundBufferLibrary.class)
public class SoundBufferLibraryMixin implements SoundBufferLibraryDuck {
@Shadow
private ResourceProvider resourceManager;
/**
* Creates a LoopPointAudioStream with defined loop points.
*
* @param audioLocation The audio file to open for stream.
* @param loopStart The starting point of the stream's loop.
* @param loopEnd The ending point of the stream's loop.
*/
@Override
public CompletableFuture<AudioStream> getStreamWithLoopPoints(ResourceLocation audioLocation, int loopStart, int loopEnd) {
return CompletableFuture.supplyAsync(() -> {
try {
InputStream introStream = this.resourceManager.open(audioLocation);
return new LoopPointAudioStream(JOrbisAudioStream::new, introStream, loopStart, loopEnd);
} catch (IOException e) {
throw new CompletionException(e);
}
}, Util.nonCriticalIoPool());
}
/**
* Creates a CompoundAudioStream from a JOrbisAudioStream intro and LoopPointAudioStream end.
*
* @param introLocation The audio file to open for the intro stream.
* @param endLocation The audio file to open for the end stream.
* @param loopStart The starting point of the ending LoopPointAudioStream's loop.
* @param loopEnd The ending point of the ending LoopPointAudioStream's loop.
*/
@Override
public CompletableFuture<AudioStream> getStreamWithIntro(ResourceLocation introLocation, ResourceLocation endLocation, int loopStart, int loopEnd) {
return createCompoundStream(
introLocation,
endLocation,
(inputStream) -> new LoopPointAudioStream(JOrbisAudioStream::new, inputStream, loopStart, loopEnd)
);
}
/**
* Creates a CompoundAudioStream from a JOrbisAudioStream intro and a JOrbisAudioStream or LoopingAudioStream end.
*
* @param introLocation The audio file to open for the intro stream.
* @param endLocation The audio file to open for the end stream.
* @param isLoop Whether the end stream loops (LoopingAudioStream) or not (JOrbisAudioStream).
*/
@Override
public CompletableFuture<AudioStream> getStreamWithIntro(ResourceLocation introLocation, ResourceLocation endLocation, boolean isLoop) {
return createCompoundStream(
introLocation,
endLocation,
!isLoop ? JOrbisAudioStream::new : (inputStream) -> new LoopingAudioStream(JOrbisAudioStream::new, inputStream)
);
}
private CompletableFuture<AudioStream> createCompoundStream(ResourceLocation introLocation, ResourceLocation endLocation, LoopingAudioStream.AudioStreamProvider endProvider) {
return CompletableFuture.supplyAsync(() -> {
try {
InputStream introInputStream = this.resourceManager.open(introLocation);
InputStream endInputStream = this.resourceManager.open(endLocation);
AudioStream introStream = new JOrbisAudioStream(introInputStream);
AudioStream endStream = endProvider.create(endInputStream);
return new CompoundAudioStream(introStream, endStream);
} catch (Exception e) {
Cobblemon.LOGGER.error(e.getMessage());
throw new CompletionException(e);
}
}, Util.nonCriticalIoPool());
}
}
@@ -8,18 +8,24 @@
package com.cobblemon.mod.common.mixin;
import com.cobblemon.mod.common.Cobblemon;
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.SoundBufferLibraryDuck;
import com.cobblemon.mod.common.duck.SoundDuck;
import com.cobblemon.mod.common.duck.SoundEngineDuck;
import com.google.common.collect.Multimap;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
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.AudioStream;
import net.minecraft.client.sounds.ChannelAccess;
import net.minecraft.client.sounds.SoundBufferLibrary;
import net.minecraft.client.sounds.SoundEngine;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.sounds.SoundSource;
@@ -30,12 +36,10 @@ 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;
import java.util.concurrent.CompletableFuture;
@Mixin(SoundEngine.class)
public abstract class SoundEngineMixin implements SoundEngineDuck {
@@ -120,4 +124,23 @@ public abstract class SoundEngineMixin implements SoundEngineDuck {
cb.cancel();
}
}
/** Dispatch custom stream for sounds that have an intro. WrapOperation because the Fabric API has its own Redirect that we don't want to use. */
@WrapOperation(method = "play", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/sounds/SoundBufferLibrary;getStream(Lnet/minecraft/resources/ResourceLocation;Z)Ljava/util/concurrent/CompletableFuture;"))
public CompletableFuture<AudioStream> getStream(SoundBufferLibrary bufferLibrary, ResourceLocation id, boolean looping, Operation<CompletableFuture<AudioStream>> original, SoundInstance soundInstance) {
SoundDuck soundDuck = (SoundDuck)soundInstance.getSound();
SoundBufferLibraryDuck libraryDuck = (SoundBufferLibraryDuck)bufferLibrary;
if (soundDuck.hasIntro() && soundDuck.hasLoopPoints() && looping) {
return libraryDuck.getStreamWithIntro(soundDuck.getIntro(), id, soundDuck.getLoopStart(), soundDuck.getLoopEnd());
}
else if (soundDuck.hasIntro()) {
return libraryDuck.getStreamWithIntro(soundDuck.getIntro(), id, looping);
}
else if (soundDuck.hasLoopPoints() && looping) {
return libraryDuck.getStreamWithLoopPoints(id, soundDuck.getLoopStart(), soundDuck.getLoopEnd());
}
else {
return original.call(bufferLibrary, id, looping);
}
}
}
@@ -0,0 +1,27 @@
package com.cobblemon.mod.common.mixin;
import com.cobblemon.mod.common.duck.SoundDuck;
import com.google.gson.JsonObject;
import com.llamalad7.mixinextras.injector.ModifyReturnValue;
import net.minecraft.client.resources.sounds.Sound;
import net.minecraft.client.resources.sounds.SoundEventRegistrationSerializer;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.GsonHelper;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
@Mixin(SoundEventRegistrationSerializer.class)
public class SoundEventRegistrationSerializerMixin {
/** Injects custom fields into Sound resource for loop behavior. */
@ModifyReturnValue(method = "getSound", at = @At("RETURN"))
private Sound getSound(Sound original, JsonObject json) {
int loopStart = GsonHelper.getAsInt(json, "loopStart", 0);
int loopEnd = GsonHelper.getAsInt(json, "loopEnd", 0);
ResourceLocation intro = ResourceLocation.parse(GsonHelper.getAsString(json, "intro", ""));
((SoundDuck)original).setLoopStart(loopStart);
((SoundDuck)original).setLoopEnd(loopEnd);
((SoundDuck)original).setIntro(intro);
return original;
}
}
@@ -0,0 +1,59 @@
package com.cobblemon.mod.common.mixin;
import com.cobblemon.mod.common.duck.SoundDuck;
import net.minecraft.client.resources.sounds.Sound;
import net.minecraft.resources.ResourceLocation;
import org.spongepowered.asm.mixin.Mixin;
import static net.minecraft.client.resources.sounds.Sound.SOUND_LISTER;
@Mixin(Sound.class)
public abstract class SoundMixin implements SoundDuck {
int loopStart = 0;
int loopEnd = 0;
ResourceLocation intro = null;
@Override
public ResourceLocation getIntro() {
return SOUND_LISTER.idToFile(this.intro);
}
@Override
public int getLoopStart() {
return loopStart;
}
@Override
public int getLoopEnd() {
return loopEnd;
}
@Override
public void setLoopStart(int startInSamples) {
this.loopStart = startInSamples;
}
@Override
public void setLoopEnd(int endInSamples) {
this.loopEnd = endInSamples;
}
@Override
public void setIntro(ResourceLocation intro) {
this.intro = intro;
}
@Override
public boolean hasLoopPoints() {
return (this.loopEnd >= 0 && this.loopStart >= 0) && (this.loopEnd + this.loopStart != 0) && (loopStart < loopEnd || loopStart == 0 || loopEnd == 0);
}
// this is used to determine if the LoopPointAudioStream is necessary or if the legacy LoopingAudioStream can be used
@Override
public boolean hasIntro() {
return this.intro != null && !this.intro.getPath().isBlank();
}
}
@@ -8,7 +8,7 @@
package com.cobblemon.mod.common.mixin;
import com.cobblemon.mod.common.bridges.StructureProcessorListBridge;
import com.cobblemon.mod.common.duck.StructureProcessorListBridge;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.List;
@@ -42,8 +42,8 @@ import com.cobblemon.mod.common.api.scheduling.ServerRealTimeTaskTracker
import com.cobblemon.mod.common.api.scheduling.ServerTaskTracker
import com.cobblemon.mod.common.api.spawning.BestSpawner
import com.cobblemon.mod.common.api.spawning.CobblemonSpawningZoneGenerator
import com.cobblemon.mod.common.api.spawning.position.AreaSpawnablePositionResolver
import com.cobblemon.mod.common.api.spawning.SpawningZoneGenerator
import com.cobblemon.mod.common.api.spawning.position.AreaSpawnablePositionResolver
import com.cobblemon.mod.common.api.starter.StarterHandler
import com.cobblemon.mod.common.api.stats.CobblemonStats
import com.cobblemon.mod.common.api.storage.PokemonStoreManager
@@ -76,12 +76,7 @@ import com.cobblemon.mod.common.config.LastChangedVersion
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.CallbackHandler
import com.cobblemon.mod.common.events.PokedexHandler
import com.cobblemon.mod.common.events.ServerTickHandler
import com.cobblemon.mod.common.events.StatHandler
import com.cobblemon.mod.common.events.*
import com.cobblemon.mod.common.net.messages.client.settings.ServerSettingsPacket
import com.cobblemon.mod.common.permission.LaxPermissionValidator
import com.cobblemon.mod.common.platform.events.PlatformEvents
@@ -206,6 +201,7 @@ object Cobblemon {
implementation.registerEntityDataSerializers()
implementation.registerCriteria()
implementation.registerEntitySubPredicates()
implementation.registerMobEffects()
DispenserBehaviorRegistry.registerDispenserBehaviors()
DropEntry.register("command", CommandDropEntry::class.java)
@@ -117,6 +117,11 @@ interface CobblemonImplementation {
fun registerEntitySubPredicates()
/**
* TODO
*/
fun registerMobEffects()
/**
* Add a feature to the current platform implementation.
@@ -0,0 +1,33 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common
import com.cobblemon.mod.common.api.cooking.effects.CleanseAllEffect
import com.cobblemon.mod.common.api.cooking.effects.CleanseNegativeEffect
import com.cobblemon.mod.common.api.cooking.effects.MentalRestorationEffect
import com.cobblemon.mod.common.platform.PlatformRegistry
import net.minecraft.core.Registry
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.core.registries.Registries
import net.minecraft.resources.ResourceKey
import net.minecraft.world.effect.MobEffect
object CobblemonMobEffects : PlatformRegistry<Registry<MobEffect>, ResourceKey<Registry<MobEffect>>, MobEffect>() {
override val registry: Registry<MobEffect> = BuiltInRegistries.MOB_EFFECT
override val resourceKey: ResourceKey<Registry<MobEffect>> = Registries.MOB_EFFECT
@JvmField
val CLEANSE_NEGATIVE = create("cleanse_negative", CleanseNegativeEffect())
@JvmField
val MENTAL_RESTORATION = create("mental_restoration", MentalRestorationEffect())
@JvmField
val CLEANSE_ALL = create("cleanse_all", CleanseAllEffect())
}
@@ -21,15 +21,12 @@ import com.cobblemon.mod.common.client.net.callback.partymove.OpenPartyMoveCallb
import com.cobblemon.mod.common.client.net.cooking.ToggleCookingPotLidHandler
import com.cobblemon.mod.common.client.net.data.CobblemonMechanicsSyncHandler
import com.cobblemon.mod.common.client.net.data.DataRegistrySyncPacketHandler
import com.cobblemon.mod.common.client.net.data.PokemonInteractionsSyncHandler
import com.cobblemon.mod.common.client.net.data.RideSettingsSyncHandler
import com.cobblemon.mod.common.client.net.debug.OpenRidingStatsDebugGUIHandler
import com.cobblemon.mod.common.client.net.dialogue.DialogueClosedHandler
import com.cobblemon.mod.common.client.net.dialogue.DialogueOpenedHandler
import com.cobblemon.mod.common.client.net.effect.PokeSnackBlockParticlesHandler
import com.cobblemon.mod.common.client.net.effect.RunPosableMoLangHandler
import com.cobblemon.mod.common.client.net.effect.SaccharineLogBlockParticlesHandler
import com.cobblemon.mod.common.client.net.effect.SpawnSnowstormEntityParticleHandler
import com.cobblemon.mod.common.client.net.effect.SpawnSnowstormParticleHandler
import com.cobblemon.mod.common.client.net.effect.*
import com.cobblemon.mod.common.client.net.gui.ExpGainedDataPacketHandler
import com.cobblemon.mod.common.client.net.gui.InteractPokemonUIPacketHandler
import com.cobblemon.mod.common.client.net.gui.PokedexUIPacketHandler
@@ -37,11 +34,7 @@ import com.cobblemon.mod.common.client.net.gui.SummaryUIPacketHandler
import com.cobblemon.mod.common.client.net.npc.CloseNPCEditorHandler
import com.cobblemon.mod.common.client.net.npc.OpenNPCEditorHandler
import com.cobblemon.mod.common.client.net.orientation.ClientboundUpdateOrientationHandler
import com.cobblemon.mod.common.client.net.pasture.ClosePastureHandler
import com.cobblemon.mod.common.client.net.pasture.OpenPastureHandler
import com.cobblemon.mod.common.client.net.pasture.PokemonPasturedHandler
import com.cobblemon.mod.common.client.net.pasture.PokemonUnpasturedHandler
import com.cobblemon.mod.common.client.net.pasture.UpdatePastureConflictFlagHandler
import com.cobblemon.mod.common.client.net.pasture.*
import com.cobblemon.mod.common.client.net.pokedex.ServerConfirmedRegisterHandler
import com.cobblemon.mod.common.client.net.pokemon.update.ClientboundUpdateRidingStateHandler
import com.cobblemon.mod.common.client.net.pokemon.update.PokemonUpdatePacketHandler
@@ -59,14 +52,7 @@ import com.cobblemon.mod.common.client.net.storage.party.SetPartyPokemonHandler
import com.cobblemon.mod.common.client.net.storage.party.SetPartyReferenceHandler
import com.cobblemon.mod.common.client.net.storage.pc.*
import com.cobblemon.mod.common.client.net.toast.ToastPacketHandler
import com.cobblemon.mod.common.client.net.trade.TradeAcceptanceChangedHandler
import com.cobblemon.mod.common.client.net.trade.TradeCancelledHandler
import com.cobblemon.mod.common.client.net.trade.TradeCompletedHandler
import com.cobblemon.mod.common.client.net.trade.TradeOfferExpiredHandler
import com.cobblemon.mod.common.client.net.trade.TradeOfferNotificationHandler
import com.cobblemon.mod.common.client.net.trade.TradeProcessStartedHandler
import com.cobblemon.mod.common.client.net.trade.TradeStartedHandler
import com.cobblemon.mod.common.client.net.trade.TradeUpdatedHandler
import com.cobblemon.mod.common.client.net.trade.*
import com.cobblemon.mod.common.net.PacketRegisterInfo
import com.cobblemon.mod.common.net.messages.client.CalculateSeatPositionsPacket
import com.cobblemon.mod.common.net.messages.client.OpenBehaviourEditorPacket
@@ -83,20 +69,12 @@ import com.cobblemon.mod.common.net.messages.client.data.*
import com.cobblemon.mod.common.net.messages.client.debug.RequestOpenRidingStatsDebugGUIPacket
import com.cobblemon.mod.common.net.messages.client.dialogue.DialogueClosedPacket
import com.cobblemon.mod.common.net.messages.client.dialogue.DialogueOpenedPacket
import com.cobblemon.mod.common.net.messages.client.effect.PokeSnackBlockParticlesPacket
import com.cobblemon.mod.common.net.messages.client.effect.RunPosableMoLangPacket
import com.cobblemon.mod.common.net.messages.client.effect.SaccharineLogBlockParticlesPacket
import com.cobblemon.mod.common.net.messages.client.effect.SpawnSnowstormEntityParticlePacket
import com.cobblemon.mod.common.net.messages.client.effect.SpawnSnowstormParticlePacket
import com.cobblemon.mod.common.net.messages.client.effect.*
import com.cobblemon.mod.common.net.messages.client.npc.CloseNPCEditorPacket
import com.cobblemon.mod.common.net.messages.client.npc.OpenNPCEditorPacket
import com.cobblemon.mod.common.net.messages.client.orientation.ClientboundUpdateDriverInputPacket
import com.cobblemon.mod.common.net.messages.client.orientation.ClientboundUpdateOrientationPacket
import com.cobblemon.mod.common.net.messages.client.pasture.ClosePasturePacket
import com.cobblemon.mod.common.net.messages.client.pasture.OpenPasturePacket
import com.cobblemon.mod.common.net.messages.client.pasture.PokemonPasturedPacket
import com.cobblemon.mod.common.net.messages.client.pasture.PokemonUnpasturedPacket
import com.cobblemon.mod.common.net.messages.client.pasture.UpdatePastureConflictFlagPacket
import com.cobblemon.mod.common.net.messages.client.pasture.*
import com.cobblemon.mod.common.net.messages.client.pokedex.ServerConfirmedRegisterPacket
import com.cobblemon.mod.common.net.messages.client.pokemon.update.*
import com.cobblemon.mod.common.net.messages.client.pokemon.update.evolution.AddEvolutionPacket
@@ -116,43 +94,19 @@ import com.cobblemon.mod.common.net.messages.client.storage.party.InitializePart
import com.cobblemon.mod.common.net.messages.client.storage.party.MoveClientPartyPokemonPacket
import com.cobblemon.mod.common.net.messages.client.storage.party.SetPartyPokemonPacket
import com.cobblemon.mod.common.net.messages.client.storage.party.SetPartyReferencePacket
import com.cobblemon.mod.common.net.messages.client.storage.pc.ClosePCPacket
import com.cobblemon.mod.common.net.messages.client.storage.pc.InitializePCPacket
import com.cobblemon.mod.common.net.messages.client.storage.pc.MoveClientPCPokemonPacket
import com.cobblemon.mod.common.net.messages.client.storage.pc.OpenPCPacket
import com.cobblemon.mod.common.net.messages.client.storage.pc.RenamePCBoxPacket
import com.cobblemon.mod.common.net.messages.client.storage.pc.SetPCBoxPacket
import com.cobblemon.mod.common.net.messages.client.storage.pc.SetPCPokemonPacket
import com.cobblemon.mod.common.net.messages.client.storage.pc.*
import com.cobblemon.mod.common.net.messages.client.storage.pc.wallpaper.ChangePCBoxWallpaperPacket
import com.cobblemon.mod.common.net.messages.client.storage.pc.wallpaper.RequestPCBoxWallpapersPacket
import com.cobblemon.mod.common.net.messages.client.storage.pc.wallpaper.SetPCBoxWallpapersPacket
import com.cobblemon.mod.common.net.messages.client.storage.pc.wallpaper.UnlockPCBoxWallpaperPacket
import com.cobblemon.mod.common.net.messages.client.toast.ToastPacket
import com.cobblemon.mod.common.net.messages.client.trade.TradeAcceptanceChangedPacket
import com.cobblemon.mod.common.net.messages.client.trade.TradeCancelledPacket
import com.cobblemon.mod.common.net.messages.client.trade.TradeCompletedPacket
import com.cobblemon.mod.common.net.messages.client.trade.TradeOfferExpiredPacket
import com.cobblemon.mod.common.net.messages.client.trade.TradeOfferNotificationPacket
import com.cobblemon.mod.common.net.messages.client.trade.TradeProcessStartedPacket
import com.cobblemon.mod.common.net.messages.client.trade.TradeStartedPacket
import com.cobblemon.mod.common.net.messages.client.trade.TradeUpdatedPacket
import com.cobblemon.mod.common.net.messages.client.trade.*
import com.cobblemon.mod.common.net.messages.client.ui.ExpGainedDataPacket
import com.cobblemon.mod.common.net.messages.client.ui.InteractPokemonUIPacket
import com.cobblemon.mod.common.net.messages.client.ui.PokedexUIPacket
import com.cobblemon.mod.common.net.messages.client.ui.SummaryUIPacket
import com.cobblemon.mod.common.net.messages.server.BattleChallengePacket
import com.cobblemon.mod.common.net.messages.server.BattleChallengeResponsePacket
import com.cobblemon.mod.common.net.messages.server.BenchMovePacket
import com.cobblemon.mod.common.net.messages.server.RequestMoveSwapPacket
import com.cobblemon.mod.common.net.messages.server.RequestPlayerInteractionsPacket
import com.cobblemon.mod.common.net.messages.server.SelectStarterPacket
import com.cobblemon.mod.common.net.messages.server.SendOutPokemonPacket
import com.cobblemon.mod.common.net.messages.server.battle.BattleSelectActionsPacket
import com.cobblemon.mod.common.net.messages.server.battle.BattleTeamLeavePacket
import com.cobblemon.mod.common.net.messages.server.battle.BattleTeamRequestPacket
import com.cobblemon.mod.common.net.messages.server.battle.BattleTeamResponsePacket
import com.cobblemon.mod.common.net.messages.server.battle.RemoveSpectatorPacket
import com.cobblemon.mod.common.net.messages.server.battle.SpectateBattlePacket
import com.cobblemon.mod.common.net.messages.server.*
import com.cobblemon.mod.common.net.messages.server.battle.*
import com.cobblemon.mod.common.net.messages.server.behaviour.DamageOnCollisionPacket
import com.cobblemon.mod.common.net.messages.server.behaviour.SetEntityBehaviourPacket
import com.cobblemon.mod.common.net.messages.server.block.AdjustBlockEntityViewerCountPacket
@@ -177,11 +131,7 @@ import com.cobblemon.mod.common.net.messages.server.pasture.UnpasturePokemonPack
import com.cobblemon.mod.common.net.messages.server.pokedex.scanner.FinishScanningPacket
import com.cobblemon.mod.common.net.messages.server.pokedex.scanner.StartScanningPacket
import com.cobblemon.mod.common.net.messages.server.pokemon.interact.InteractPokemonPacket
import com.cobblemon.mod.common.net.messages.server.pokemon.update.ServerboundUpdateRidingStatePacket
import com.cobblemon.mod.common.net.messages.server.pokemon.update.SetActiveMarkPacket
import com.cobblemon.mod.common.net.messages.server.pokemon.update.SetItemHiddenPacket
import com.cobblemon.mod.common.net.messages.server.pokemon.update.SetMarkingsPacket
import com.cobblemon.mod.common.net.messages.server.pokemon.update.SetNicknamePacket
import com.cobblemon.mod.common.net.messages.server.pokemon.update.*
import com.cobblemon.mod.common.net.messages.server.pokemon.update.evolution.AcceptEvolutionPacket
import com.cobblemon.mod.common.net.messages.server.riding.DismountPokemonPacket
import com.cobblemon.mod.common.net.messages.server.riding.ServerboundUpdateDriverInputPacket
@@ -192,21 +142,11 @@ import com.cobblemon.mod.common.net.messages.server.storage.party.MovePartyPokem
import com.cobblemon.mod.common.net.messages.server.storage.party.ReleasePartyPokemonPacket
import com.cobblemon.mod.common.net.messages.server.storage.party.SwapPartyPokemonPacket
import com.cobblemon.mod.common.net.messages.server.storage.pc.*
import com.cobblemon.mod.common.net.messages.server.trade.AcceptTradeRequestPacket
import com.cobblemon.mod.common.net.messages.server.trade.CancelTradePacket
import com.cobblemon.mod.common.net.messages.server.trade.ChangeTradeAcceptancePacket
import com.cobblemon.mod.common.net.messages.server.trade.OfferTradePacket
import com.cobblemon.mod.common.net.messages.server.trade.PerformTradePacket
import com.cobblemon.mod.common.net.messages.server.trade.UpdateTradeOfferPacket
import com.cobblemon.mod.common.net.messages.server.trade.*
import com.cobblemon.mod.common.net.serverhandling.ChallengeHandler
import com.cobblemon.mod.common.net.serverhandling.ChallengeResponseHandler
import com.cobblemon.mod.common.net.serverhandling.RequestInteractionsHandler
import com.cobblemon.mod.common.net.serverhandling.battle.BattleSelectActionsHandler
import com.cobblemon.mod.common.net.serverhandling.battle.RemoveSpectatorHandler
import com.cobblemon.mod.common.net.serverhandling.battle.SpectateBattleHandler
import com.cobblemon.mod.common.net.serverhandling.battle.TeamLeaveHandler
import com.cobblemon.mod.common.net.serverhandling.battle.TeamRequestHandler
import com.cobblemon.mod.common.net.serverhandling.battle.TeamRequestResponseHandler
import com.cobblemon.mod.common.net.serverhandling.battle.*
import com.cobblemon.mod.common.net.serverhandling.behaviour.DamageOnCollisionPacketHandler
import com.cobblemon.mod.common.net.serverhandling.behaviour.SetEntityBehaviourHandler
import com.cobblemon.mod.common.net.serverhandling.block.AdjustBlockEntityViewerCountHandler
@@ -232,11 +172,7 @@ import com.cobblemon.mod.common.net.serverhandling.pasture.UnpasturePokemonHandl
import com.cobblemon.mod.common.net.serverhandling.pokedex.scanner.FinishScanningHandler
import com.cobblemon.mod.common.net.serverhandling.pokedex.scanner.StartScanningHandler
import com.cobblemon.mod.common.net.serverhandling.pokemon.interact.InteractPokemonHandler
import com.cobblemon.mod.common.net.serverhandling.pokemon.update.ServerboundUpdateRidingStateHandler
import com.cobblemon.mod.common.net.serverhandling.pokemon.update.SetActiveMarkHandler
import com.cobblemon.mod.common.net.serverhandling.pokemon.update.SetItemHiddenHandler
import com.cobblemon.mod.common.net.serverhandling.pokemon.update.SetMarkingsHandler
import com.cobblemon.mod.common.net.serverhandling.pokemon.update.SetNicknameHandler
import com.cobblemon.mod.common.net.serverhandling.pokemon.update.*
import com.cobblemon.mod.common.net.serverhandling.riding.DismountPokemonPacketHandler
import com.cobblemon.mod.common.net.serverhandling.riding.DriverInputPacketHandler
import com.cobblemon.mod.common.net.serverhandling.riding.ServerboundUpdateRiderRotationHandler
@@ -250,12 +186,7 @@ import com.cobblemon.mod.common.net.serverhandling.storage.party.MovePartyPokemo
import com.cobblemon.mod.common.net.serverhandling.storage.party.ReleasePCPokemonHandler
import com.cobblemon.mod.common.net.serverhandling.storage.party.SwapPartyPokemonHandler
import com.cobblemon.mod.common.net.serverhandling.storage.pc.*
import com.cobblemon.mod.common.net.serverhandling.trade.AcceptTradeRequestHandler
import com.cobblemon.mod.common.net.serverhandling.trade.CancelTradeHandler
import com.cobblemon.mod.common.net.serverhandling.trade.ChangeTradeAcceptanceHandler
import com.cobblemon.mod.common.net.serverhandling.trade.OfferTradeHandler
import com.cobblemon.mod.common.net.serverhandling.trade.PerformTradeHandler
import com.cobblemon.mod.common.net.serverhandling.trade.UpdateTradeOfferHandler
import com.cobblemon.mod.common.net.serverhandling.trade.*
import com.cobblemon.mod.common.util.server
import net.minecraft.server.level.ServerPlayer
@@ -401,6 +332,7 @@ object CobblemonNetwork {
// Data registries
list.add(PacketRegisterInfo(AbilityRegistrySyncPacket.ID, AbilityRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(CobblemonMechanicsSyncPacket.ID, CobblemonMechanicsSyncPacket::decode, CobblemonMechanicsSyncHandler))
list.add(PacketRegisterInfo(PokemonInteractionsSyncPacket.ID, PokemonInteractionsSyncPacket::decode, PokemonInteractionsSyncHandler))
list.add(PacketRegisterInfo(MovesRegistrySyncPacket.ID, MovesRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(BerryRegistrySyncPacket.ID, BerryRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(SpeciesRegistrySyncPacket.ID, SpeciesRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
@@ -0,0 +1,22 @@
/*
* 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.cooking.effects
import net.minecraft.world.effect.MobEffect
import net.minecraft.world.effect.MobEffectCategory
import net.minecraft.world.entity.Entity
import net.minecraft.world.entity.LivingEntity
class CleanseAllEffect : MobEffect(MobEffectCategory.BENEFICIAL, 0xFFFFFF) {
override fun isInstantenous() = true
override fun applyInstantenousEffect(source: Entity?, indirect: Entity?, target: LivingEntity, amplifier: Int, proximity: Double) {
target.removeAllEffects()
}
override fun shouldApplyEffectTickThisTick(duration: Int, amplifier: Int) = false
}
@@ -0,0 +1,27 @@
/*
* 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.cooking.effects
import net.minecraft.world.effect.MobEffect
import net.minecraft.world.effect.MobEffectCategory
import net.minecraft.world.entity.Entity
import net.minecraft.world.entity.LivingEntity
class CleanseNegativeEffect : MobEffect(MobEffectCategory.BENEFICIAL, 0xFFFFFF) {
override fun isInstantenous() = true
override fun applyInstantenousEffect(source: Entity?, indirect: Entity?, target: LivingEntity, amplifier: Int, proximity: Double) {
val snapshot = target.activeEffects.toList()
for (ei in snapshot) {
if (ei.effect.value().category == MobEffectCategory.HARMFUL) {
target.removeEffect(ei.effect)
}
}
}
override fun shouldApplyEffectTickThisTick(duration: Int, amplifier: Int) = false
}
@@ -0,0 +1,33 @@
/*
* 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.cooking.effects
import net.minecraft.server.level.ServerPlayer
import net.minecraft.stats.Stats
import net.minecraft.world.effect.MobEffect
import net.minecraft.world.effect.MobEffectCategory
import net.minecraft.world.entity.LivingEntity
class MentalRestorationEffect : MobEffect(MobEffectCategory.BENEFICIAL, 0xAA66FF) {
override fun applyEffectTick(entity: LivingEntity, amplifier: Int): Boolean {
if (entity is ServerPlayer) {
val stats = entity.stats
val statType = Stats.CUSTOM.get(Stats.TIME_SINCE_REST)
val current = stats.getValue(statType)
val reduction = 31 * (amplifier + 1)// reduce by half a minute every second per level (30 ticks removed per tick + 1 to account for the tick passing)
val newValue = Math.max(current - reduction, 0)
stats.setValue(entity, statType, newValue)
}
return true
}
override fun shouldApplyEffectTickThisTick(duration: Int, amplifier: Int): Boolean {
return true
}
}
@@ -12,10 +12,10 @@ import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.data.JsonDataRegistry
import com.cobblemon.mod.common.api.molang.ExpressionLike
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.pokemon.requirement.OwnerQueryRequirement
import com.cobblemon.mod.common.api.pokemon.requirement.Requirement
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.net.messages.client.data.PokemonInteractionsSyncPacket
import com.cobblemon.mod.common.pokemon.adapters.CobblemonRequirementAdapter
import com.cobblemon.mod.common.pokemon.evolution.adapters.LegacyItemConditionWrapperAdapter
import com.cobblemon.mod.common.pokemon.requirements.PokemonPropertiesRequirement
@@ -51,7 +51,12 @@ object PokemonInteractions : JsonDataRegistry<PokemonInteractionSet> {
val speciesInteractions = mutableListOf<PokemonInteractionSet>()
val generalInteractions = mutableListOf<PokemonInteractionSet>()
override fun sync(player: ServerPlayer) {}
override fun sync(player: ServerPlayer) {
PokemonInteractionsSyncPacket(
speciesInteractions,
generalInteractions,
).sendToPlayer(player)
}
override fun reload(data: Map<ResourceLocation, PokemonInteractionSet>) {
speciesInteractions.clear()
@@ -15,11 +15,10 @@ import com.cobblemon.mod.common.api.molang.MoLangFunctions.setup
import com.cobblemon.mod.common.api.pokemon.requirement.Requirement
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.net.messages.client.sound.UnvalidatedPlaySoundS2CPacket
import com.cobblemon.mod.common.util.asExpressionLike
import com.cobblemon.mod.common.util.giveOrDropItemStack
import com.cobblemon.mod.common.util.withPlayerValue
import com.cobblemon.mod.common.util.withQueryValue
import com.cobblemon.mod.common.pokemon.adapters.CobblemonRequirementAdapter
import com.cobblemon.mod.common.util.*
import net.minecraft.core.registries.Registries
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
import net.minecraft.sounds.SoundSource
@@ -28,30 +27,83 @@ import net.minecraft.world.entity.EquipmentSlot
import net.minecraft.world.entity.item.ItemEntity
import net.minecraft.world.item.ItemStack
/**
* A
*/
data class PokemonInteractionSet(
val requirements: List<Requirement> = listOf(),
val interactions: List<PokemonInteraction> = listOf()
)
val interactions: List<PokemonInteraction> = listOf(),
) {
fun encode(buffer: FriendlyByteBuf) {
buffer.writeCollection(requirements) { it, requirement ->
CobblemonRequirementAdapter.encode(requirement, it)
}
buffer.writeCollection(interactions) { it, interaction ->
interaction.encode(it)
}
}
companion object {
fun decode(buffer: FriendlyByteBuf): PokemonInteractionSet {
val requirements = buffer.readList { it ->
CobblemonRequirementAdapter.decode(it)
}
val interactions = buffer.readList { it ->
PokemonInteraction.decode(it)
}
return PokemonInteractionSet(
requirements,
interactions,
)
}
}
}
data class PokemonInteraction(
val grouping: ResourceLocation,
val requirements: List<Requirement> = listOf(),
val effects: List<InteractionEffect> = listOf(),
val cooldown: ExpressionLike = "0".asExpressionLike()
)
) {
fun encode(buffer: FriendlyByteBuf) {
buffer.writeResourceLocation(grouping)
buffer.writeCollection(requirements) { it, requirement ->
CobblemonRequirementAdapter.encode(requirement, it)
}
buffer.writeExpressionLike(cooldown)
}
companion object {
fun decode(buffer: FriendlyByteBuf): PokemonInteraction {
val grouping = buffer.readResourceLocation()
val requirements = buffer.readList { it ->
CobblemonRequirementAdapter.decode(it)
}
// No need to sync these for now
val effects = emptyList<InteractionEffect>()
val cooldown = buffer.readExpressionLike()
return PokemonInteraction(
grouping,
requirements,
effects,
cooldown,
)
}
}
}
interface InteractionEffect {
fun applyEffect(pokemon: PokemonEntity, player: ServerPlayer)
}
class DropItemEffect(val item: ResourceLocation, val amount: IntRange?): InteractionEffect {
class DropItemEffect(val item: ResourceLocation, val amount: IntRange?) : InteractionEffect {
override fun applyEffect(
pokemon: PokemonEntity,
player: ServerPlayer
) {
val item = player.registryAccess().registryOrThrow(Registries.ITEM).get(item) ?: throw IllegalArgumentException("Cannot load item with id: $item")
val item = player.registryAccess().registryOrThrow(Registries.ITEM).get(item) ?: throw IllegalArgumentException(
"Cannot load item with id: $item"
)
val stack = ItemStack(item)
stack.count = amount?.randomOrNull() ?: 1
if (stack.isEmpty)
@@ -68,12 +120,14 @@ class DropItemEffect(val item: ResourceLocation, val amount: IntRange?): Interac
}
}
class GiveItemEffect(val item: ResourceLocation, val amount: IntRange?): InteractionEffect {
class GiveItemEffect(val item: ResourceLocation, val amount: IntRange?) : InteractionEffect {
override fun applyEffect(
pokemon: PokemonEntity,
player: ServerPlayer
) {
val item = player.registryAccess().registryOrThrow(Registries.ITEM).get(item) ?: throw IllegalArgumentException("Cannot load item with id: $item")
val item = player.registryAccess().registryOrThrow(Registries.ITEM).get(item) ?: throw IllegalArgumentException(
"Cannot load item with id: $item"
)
val stack = ItemStack(item)
stack.count = amount?.randomOrNull() ?: 1
if (stack.isEmpty)
@@ -86,14 +140,29 @@ class GiveItemEffect(val item: ResourceLocation, val amount: IntRange?): Interac
}
}
class PlaySoundEffect(val sound: ResourceLocation, val soundSource: SoundSource?, val playAround: Boolean = true, val distance: Double = 64.0, val volume: Float = 1.0F, val pitch: Float = 1.0F): InteractionEffect {
class PlaySoundEffect(
val sound: ResourceLocation,
val soundSource: SoundSource?,
val playAround: Boolean = true,
val distance: Double = 64.0,
val volume: Float = 1.0F,
val pitch: Float = 1.0F
) : InteractionEffect {
override fun applyEffect(
pokemon: PokemonEntity,
player: ServerPlayer
) {
val packet = UnvalidatedPlaySoundS2CPacket(sound, soundSource ?: SoundSource.NEUTRAL, pokemon.x, pokemon.y, pokemon.z, volume, pitch)
val packet = UnvalidatedPlaySoundS2CPacket(
sound,
soundSource ?: SoundSource.NEUTRAL,
pokemon.x,
pokemon.y,
pokemon.z,
volume,
pitch
)
if (playAround) {
packet.sendToPlayersAround( pokemon.x, pokemon.y, pokemon.z, distance, pokemon.level().dimension())
packet.sendToPlayersAround(pokemon.x, pokemon.y, pokemon.z, distance, pokemon.level().dimension())
} else {
packet.sendToPlayer(player)
}
@@ -104,7 +173,7 @@ class PlaySoundEffect(val sound: ResourceLocation, val soundSource: SoundSource?
}
}
class ShrinkItemEffect(val amount: Int = 1): InteractionEffect {
class ShrinkItemEffect(val amount: Int = 1) : InteractionEffect {
override fun applyEffect(
pokemon: PokemonEntity,
player: ServerPlayer
@@ -120,7 +189,7 @@ class ShrinkItemEffect(val amount: Int = 1): InteractionEffect {
}
}
class ScriptEffect(val script: ExpressionLike): InteractionEffect {
class ScriptEffect(val script: ExpressionLike) : InteractionEffect {
override fun applyEffect(
pokemon: PokemonEntity,
player: ServerPlayer
@@ -779,6 +779,20 @@ object MoLangFunctions {
battleStartResult.ifSuccessful { returnValue = it.struct }
return@put returnValue
}
map.put("get_custom_stat") { params ->
val statName = params.getString(0)
val resourceLocation = ResourceLocation.tryParse(statName) ?: return@put DoubleValue.ZERO
val statResourceLocation = BuiltInRegistries.CUSTOM_STAT.get(resourceLocation) ?: return@put DoubleValue.ZERO
val exists = net.minecraft.stats.Stats.CUSTOM.contains(statResourceLocation)
if (!exists) return@put DoubleValue.ZERO
val stat = net.minecraft.stats.Stats.CUSTOM.get(statResourceLocation)
val value = player.stats.getValue(stat)
DoubleValue(value)
}
}
map
}
@@ -1739,22 +1753,35 @@ object MoLangFunctions {
pokemon.removeCosmeticItem()
}
map.put("add_marks") { params ->
var appliedMark = false
for (param in params.params) {
val identifier = param.asString().asIdentifierDefaultingNamespace()
val mark = Marks.getByIdentifier(identifier)
if (mark != null) pokemon.exchangeMark(mark, true)
if (mark != null) {
pokemon.exchangeMark(mark, true)
appliedMark = true
}
}
DoubleValue(appliedMark)
}
map.put("add_marks_with_chance") { params ->
var appliedMark = false
for (param in params.params) {
val identifier = param.asString().asIdentifierDefaultingNamespace()
val mark = Marks.getByIdentifier(identifier)
mark?.let {
val probability = it.chance.coerceIn(0F, 1F) * 100
val randomValue = Random.nextDouble(0.0, 100.0)
if (randomValue < probability) pokemon.exchangeMark(it, true)
if (randomValue < probability) {
pokemon.exchangeMark(it, true)
appliedMark = true
}
}
}
DoubleValue(appliedMark)
}
map.put("add_potential_marks") { params ->
for (param in params.params) {
@@ -24,15 +24,14 @@ import net.minecraft.world.entity.LivingEntity
interface EntityQueryRequirement : Requirement {
override fun check(pokemon: Pokemon): Boolean {
val queriedEntity = pokemon.entity ?: pokemon.getOwnerPlayer() ?: return false
return this.check(pokemon, queriedEntity)
return this.check(queriedEntity)
}
/**
* Checks if the given [Pokemon] & [LivingEntity] satisfies the requirement.
* Checks if the given [LivingEntity] satisfies the requirement.
*
* @param pokemon The [Pokemon] being queried.
* @param queriedEntity The [LivingEntity] that can be compared in context of a [Level].
* @return If the requirement was satisfied.
*/
fun check(pokemon: Pokemon, queriedEntity: LivingEntity): Boolean
fun check(queriedEntity: LivingEntity): Boolean
}
@@ -12,6 +12,7 @@ import com.cobblemon.mod.common.entity.npc.NPCEntity
import com.cobblemon.mod.common.pokemon.Pokemon
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
/**
* A [Requirement] that expects a [LivingEntity] to be attached to the [Pokemon].
@@ -26,7 +27,7 @@ interface OwnerQueryRequirement : Requirement {
override fun check(pokemon: Pokemon): Boolean {
val owner = pokemon.getOwnerEntity() ?: return false
return when (owner) {
is ServerPlayer -> {
is Player -> {
this.checkPlayer(pokemon, owner)
}
is NPCEntity -> {
@@ -37,13 +38,13 @@ interface OwnerQueryRequirement : Requirement {
}
/**
* Checks if the given [Pokemon] & [ServerPlayer] satisfies the requirement.
* Checks if the given [Pokemon] & [Player] satisfies the requirement.
*
* @param pokemon The [Pokemon] being queried.
* @param owner The [ServerPlayer] that owns this [Pokemon].
* @param owner The [Player] that owns this [Pokemon].
* @return If the requirement was satisfied.
*/
fun checkPlayer(pokemon: Pokemon, owner: ServerPlayer): Boolean
fun checkPlayer(pokemon: Pokemon, owner: Player): Boolean
/**
* Checks if the given [Pokemon] & [NPCEntity] satisfies the requirement.
@@ -12,6 +12,7 @@ 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.getIsSubmerged
import com.cobblemon.mod.common.util.resolveDouble
import net.minecraft.client.Minecraft
import net.minecraft.client.resources.sounds.AbstractTickableSoundInstance
@@ -65,6 +66,21 @@ class RideLoopSound(val ride: PokemonEntity, val soundSettings: RideSoundSetting
return
}
// Mute the sound if it's not meant to play for the passenger
if (this.isPassenger && !this.soundSettings.playForPassengers) {
this.volume = 0.0f
return
}
// Mute sounds when submerged unless they support underwater sounds, underwater sounds don't play above water
if (this.ride.getIsSubmerged() && !this.soundSettings.submerged) {
this.volume = 0.0f
return
} else if (!this.ride.getIsSubmerged() && this.soundSettings.submerged) {
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
@@ -27,7 +27,9 @@ data class RideSoundSettings(
val soundLocation: ResourceLocation,
@SerializedName("volumeExpr") private val _volumeExpr: Expression? = "1.0".asExpression(),
@SerializedName("pitchExpr") private val _pitchExpr: Expression? = "1.0".asExpression(),
val playForPassengers: Boolean = true,
val playForNonPassengers: Boolean = false,
val submerged: Boolean = false,
val muffleEnabled: Boolean = false,
@SerializedName("attenuationModel") private val _attenuationModel: RideAttenuationModel? = RideAttenuationModel.NONE
): Encodable {
@@ -46,7 +48,9 @@ data class RideSoundSettings(
buffer.writeResourceLocation(soundLocation)
buffer.writeExpression(volumeExpr)
buffer.writeExpression(pitchExpr)
buffer.writeBoolean(playForPassengers)
buffer.writeBoolean(playForNonPassengers)
buffer.writeBoolean(submerged)
buffer.writeBoolean(muffleEnabled)
buffer.writeEnum(attenuationModel)
}
@@ -57,7 +61,9 @@ data class RideSoundSettings(
val soundLocation = buffer.readResourceLocation()
val volumeExpr = buffer.readExpression()
val pitchExpr = buffer.readExpression()
val playForPassengers = buffer.readBoolean()
val playForNonPassengers = buffer.readBoolean()
val submerged = buffer.readBoolean()
val muffleEnabled = buffer.readBoolean()
val attenuationModel = buffer.readEnum(RideAttenuationModel::class.java)
@@ -65,7 +71,9 @@ data class RideSoundSettings(
soundLocation,
volumeExpr,
pitchExpr,
playForPassengers,
playForNonPassengers,
submerged,
muffleEnabled,
attenuationModel
)
@@ -8,30 +8,58 @@
package com.cobblemon.mod.common.api.stats
import com.cobblemon.mod.common.Cobblemon
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.resources.ResourceLocation
import net.minecraft.stats.StatFormatter
object CobblemonStats {
val stats = mutableMapOf<String, ResourceLocation>()
val stats = mutableMapOf<String, CobblemonStat>()
var CAPTURED: ResourceLocation = ResourceLocation.fromNamespaceAndPath("cobblemon", "captured") //makeCustomStat("captured", StatFormatter.DEFAULT)
val SHINIES_CAPTURED: ResourceLocation = ResourceLocation.fromNamespaceAndPath("cobblemon", "shinies_captured") //makeCustomStat("shinies_captured", StatFormatter.DEFAULT)
val RELEASED: ResourceLocation = ResourceLocation.fromNamespaceAndPath("cobblemon", "released") //makeCustomStat("released", StatFormatter.DEFAULT)
val EVOLVED: ResourceLocation = ResourceLocation.fromNamespaceAndPath("cobblemon", "evolved") //makeCustomStat("evolved", StatFormatter.DEFAULT)
val LEVEL_UP: ResourceLocation = ResourceLocation.fromNamespaceAndPath("cobblemon", "level_up") //makeCustomStat("level_up", StatFormatter.DEFAULT)
val BATTLES_WON: ResourceLocation = ResourceLocation.fromNamespaceAndPath("cobblemon", "battles_won") //makeCustomStat("battles_won", StatFormatter.DEFAULT)
val BATTLES_LOST: ResourceLocation = ResourceLocation.fromNamespaceAndPath("cobblemon", "battles_lost") //makeCustomStat("battles_lost", StatFormatter.DEFAULT)
val BATTLES_FLED: ResourceLocation = ResourceLocation.fromNamespaceAndPath("cobblemon", "battles_fled") //makeCustomStat("battles_fled", StatFormatter.DEFAULT)
val BATTLES_TOTAL: ResourceLocation = ResourceLocation.fromNamespaceAndPath("cobblemon", "battles_total") //makeCustomStat("battles_total", StatFormatter.DEFAULT)
val DEX_ENTRIES: ResourceLocation = ResourceLocation.fromNamespaceAndPath("cobblemon", "dex_entries") //makeCustomStat("dex_entries", StatFormatter.DEFAULT)
val EGGS_COLLECTED: ResourceLocation = ResourceLocation.fromNamespaceAndPath("cobblemon", "eggs_collected") //makeCustomStat("eggs_collected", StatFormatter.DEFAULT)
val EGGS_HATCHED: ResourceLocation = ResourceLocation.fromNamespaceAndPath("cobblemon", "eggs_hatched") //makeCustomStat("eggs_hatched", StatFormatter.DEFAULT)
val TRADED: ResourceLocation = ResourceLocation.fromNamespaceAndPath("cobblemon", "traded") //makeCustomStat("traded", StatFormatter.DEFAULT)
val FOSSILS_REVIVED: ResourceLocation = ResourceLocation.fromNamespaceAndPath("cobblemon", "fossils_revived") //makeCustomStat("fossils_revived", StatFormatter.DEFAULT)
val TIMES_RIDDEN: ResourceLocation = ResourceLocation.fromNamespaceAndPath("cobblemon", "times_ridden")
val ROD_CASTS: ResourceLocation = ResourceLocation.fromNamespaceAndPath("cobblemon", "rod_casts")
val REEL_INS: ResourceLocation = ResourceLocation.fromNamespaceAndPath("cobblemon", "reel_ins")
val POKEMON_INTERACTED_WITH: ResourceLocation = ResourceLocation.fromNamespaceAndPath("cobblemon", "pokemon_interacted_with") //makeCustomStat("pokemon_interacted_with", StatFormatter.DEFAULT)
//TODO block, stats (interact, gimmi), riding (styles?)
@JvmField
var CAPTURED: CobblemonStat = CobblemonStat("captured")
@JvmField
val SHINIES_CAPTURED: CobblemonStat = CobblemonStat("shinies_captured")
@JvmField
val RELEASED: CobblemonStat = CobblemonStat("released")
@JvmField
val EVOLVED: CobblemonStat = CobblemonStat("evolved")
@JvmField
val LEVEL_UP: CobblemonStat = CobblemonStat("level_up")
@JvmField
val BATTLES_WON: CobblemonStat = CobblemonStat("battles_won")
@JvmField
val BATTLES_LOST: CobblemonStat = CobblemonStat("battles_lost")
@JvmField
val BATTLES_FLED: CobblemonStat = CobblemonStat("battles_fled")
@JvmField
val BATTLES_TOTAL: CobblemonStat = CobblemonStat("battles_total")
@JvmField
val DEX_ENTRIES: CobblemonStat = CobblemonStat("dex_entries")
@JvmField
val EGGS_COLLECTED: CobblemonStat = CobblemonStat("eggs_collected")
@JvmField
val EGGS_HATCHED: CobblemonStat = CobblemonStat("eggs_hatched")
@JvmField
val TRADED: CobblemonStat = CobblemonStat("traded")
@JvmField
val FOSSILS_REVIVED: CobblemonStat = CobblemonStat("fossils_revived")
@JvmField
val TIMES_RIDDEN: CobblemonStat = CobblemonStat("times_ridden")
@JvmField
val ROD_CASTS: CobblemonStat = CobblemonStat("rod_casts")
@JvmField
val REEL_INS: CobblemonStat = CobblemonStat("reel_ins")
@JvmField
val POKEMON_INTERACTED_WITH: CobblemonStat = CobblemonStat("pokemon_interacted_with")
@JvmField
val RIDING_LAND: CobblemonStat = CobblemonStat("riding_land", StatFormatter.DISTANCE)
@JvmField
val RIDING_AIR: CobblemonStat = CobblemonStat("riding_air", StatFormatter.DISTANCE)
@JvmField
val RIDING_LIQUID: CobblemonStat = CobblemonStat("riding_liquid", StatFormatter.DISTANCE)
//TODO block, stats (interact, gimmi)
fun registerStats() {
stats["captured"] = CAPTURED
@@ -52,5 +80,22 @@ object CobblemonStats {
stats["rod_casts"] = ROD_CASTS
stats["reel_ins"] = REEL_INS
stats["pokemon_interacted_with"] = POKEMON_INTERACTED_WITH
stats["riding_land"] = RIDING_LAND
stats["riding_air"] = RIDING_AIR
stats["riding_liquid"] = RIDING_LIQUID
}
@JvmStatic
fun getStat(cobblemonStat: CobblemonStat) : ResourceLocation {
val resourceLocation = cobblemonStat.resourceLocation
val stat = BuiltInRegistries.CUSTOM_STAT.get(resourceLocation)
if (stat == null) {
Cobblemon.LOGGER.error("Could not find stat with id {}", resourceLocation)
}
return stat ?: throw NullPointerException("Could not find stat with id $resourceLocation")
}
data class CobblemonStat(val path: String, val formatter: StatFormatter = StatFormatter.DEFAULT) {
val resourceLocation: ResourceLocation = ResourceLocation.fromNamespaceAndPath("cobblemon", path);
}
}
@@ -207,6 +207,7 @@ object BattleBuilder {
val side1Theme = players[0].getBattleTheme()
val side2Theme = players[2].getBattleTheme()
// the music that's played for a side is driven by the opposite leader's theme
side1Actors.forEach { it.battleTheme = side2Theme }
side2Actors.forEach { it.battleTheme = side1Theme }
@@ -192,6 +192,7 @@ object CobblemonClient {
}
}
}
BattleMusicController.onTick()
ClientPlayerIcon.onTick()
PartyOverlayDataControl.tick(event.client.isPaused)
}
@@ -12,6 +12,7 @@ import com.cobblemon.mod.common.api.net.ClientNetworkPacketHandler
import com.cobblemon.mod.common.client.sound.BattleMusicController
import com.cobblemon.mod.common.client.sound.instances.BattleMusicInstance
import com.cobblemon.mod.common.net.messages.client.battle.BattleMusicPacket
import com.cobblemon.mod.common.ost.BattleThemes
import net.minecraft.client.Minecraft
import net.minecraft.sounds.SoundEvent
@@ -26,8 +27,12 @@ object BattleMusicHandler : ClientNetworkPacketHandler<BattleMusicPacket> {
override fun handle(packet: BattleMusicPacket, client: Minecraft) {
val soundManager = client.soundManager
val currMusic = BattleMusicController.music
// TODO to support legacy behavior, use ResourceLocation for BattleTrackConfig THEN SoundEvent
val newMusic = packet.music?.let {
val event = SoundEvent.createVariableRangeEvent(it)
val location = BattleThemes.getByIdentifier(it)?.dispatchTrack()? ?: it
val event = SoundEvent.createVariableRangeEvent(location)
BattleMusicInstance(event, packet.volume, packet.pitch)
}
@@ -0,0 +1,27 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.net.data
import com.cobblemon.mod.common.api.interaction.PokemonInteractions
import com.cobblemon.mod.common.api.net.ClientNetworkPacketHandler
import com.cobblemon.mod.common.net.messages.client.data.PokemonInteractionsSyncPacket
import net.minecraft.client.Minecraft
object PokemonInteractionsSyncHandler : ClientNetworkPacketHandler<PokemonInteractionsSyncPacket> {
override fun handle(
packet: PokemonInteractionsSyncPacket,
client: Minecraft,
) {
PokemonInteractions.speciesInteractions.clear()
PokemonInteractions.speciesInteractions.addAll(packet.speciesInteractions)
PokemonInteractions.generalInteractions.clear()
PokemonInteractions.generalInteractions.addAll(packet.generalInteractions)
}
}
@@ -8,6 +8,8 @@
package com.cobblemon.mod.common.client.persisted
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.Cobblemon.LOGGER
import com.cobblemon.mod.common.config.CobblemonConfig
import java.io.BufferedReader
import java.io.BufferedWriter
@@ -31,7 +33,14 @@ object ClientPersistedData {
fun readSnapshotsData() : SnapshotAcknowledgementData? {
if (snapshotsPath.exists()) {
BufferedReader(InputStreamReader(FileInputStream(snapshotsPath.toFile()))).use { reader ->
return gson.fromJson(reader, SnapshotAcknowledgementData::class.java)
try {
return gson.fromJson(reader, SnapshotAcknowledgementData::class.java)
} catch (exception: Exception) {
LOGGER.error("Failed to load snapshots.json... Resetting File")
val data = SnapshotAcknowledgementData(Cobblemon.VERSION, false)
writeSnapshotsData(data)
return data
}
}
}
@@ -46,16 +55,23 @@ object ClientPersistedData {
fun readRidingPerspectivesData() : RidingPerspectiveData? {
if (ridingPerspectivesPath.exists()) {
BufferedReader(InputStreamReader(FileInputStream(ridingPerspectivesPath.toFile()))).use { reader ->
return gson.fromJson(reader, RidingPerspectiveData::class.java)
try {
return gson.fromJson(reader, RidingPerspectiveData::class.java)
} catch (exception: Exception) {
LOGGER.error("Failed to load riding_perspectives.json... Resetting File")
val data = RidingPerspectiveData()
saveRidingPerspectivesData(data)
return data
}
}
}
return null
}
fun saveRidingPerspectivesData() {
fun saveRidingPerspectivesData(data: RidingPerspectiveData) {
ridingPerspectivesPath.toFile().parentFile.mkdirs()
BufferedWriter(FileWriter(ridingPerspectivesPath.toFile())).use { writer -> gson.toJson(ridingPerspectives, writer) }
BufferedWriter(FileWriter(ridingPerspectivesPath.toFile())).use { writer -> gson.toJson(data, writer) }
}
data class SnapshotAcknowledgementData(val version: String, val dontShowAgain: Boolean)
@@ -64,7 +80,7 @@ object ClientPersistedData {
) {
fun updatePerspective(key: ResourceLocation, cameraType: CameraType) {
perspectives[key] = cameraType
saveRidingPerspectivesData()
saveRidingPerspectivesData(this)
}
}
}
@@ -9,6 +9,8 @@
package com.cobblemon.mod.common.client.sound
import com.cobblemon.mod.common.client.sound.instances.BattleMusicInstance
import com.cobblemon.mod.common.ost.BattleMusicCue
import com.cobblemon.mod.common.ost.BattleThemeConfig
import com.cobblemon.mod.common.util.pauseSounds
import com.cobblemon.mod.common.util.resumeSounds
import net.minecraft.client.Minecraft
@@ -23,6 +25,15 @@ import net.minecraft.sounds.SoundEvents
*/
object BattleMusicController {
/** The active [BattleThemeConfig] for dispatching [BattleMusicInstance]s and subscribing to [BattleMusicCue]s. */
private var config: BattleThemeConfig? = null
/** The queued [BattleMusicCue]. */
private var queuedCue: BattleMusicCue? = null
/** The active [BattleMusicCue]. */
private var activeCue: BattleMusicCue? = null
/** The last [BattleMusicInstance] played. */
var music = BattleMusicInstance(SoundEvents.EMPTY, 0.0F, 0.0F)
private set
@@ -56,4 +67,13 @@ object BattleMusicController {
/** Fade out and end the current [BattleMusicInstance]. */
fun endMusic() = music.setFade()
/** Processes the [queuedCue]. */
private fun processCue(cue: BattleMusicCue) {
}
/** */
fun onTick() {
}
}
@@ -0,0 +1,55 @@
package com.cobblemon.mod.common.client.sound.streams
import net.minecraft.client.sounds.AudioStream
import java.nio.ByteBuffer
import javax.sound.sampled.AudioFormat
import kotlin.jvm.Throws
/**
* An [AudioStream] composed of multiple streams stitched together.
*
* Bytes of audio are read according to the active stream's implementation of [AudioStream.read].
*
* @param streams The [AudioStream]s to chain sequentially. Each stream must share the same [AudioFormat].
*
* @author Segfault Guy
* @since October 19th, 2025
*/
class CompoundAudioStream @Throws(IllegalArgumentException::class) constructor(vararg streams: AudioStream): AudioStream {
private val streams = streams.iterator()
/** The [AudioStream] currently playing. */
private var runningStream: AudioStream = this.streams.next()
private val format = this.runningStream.format
override fun getFormat() = format
init {
/* Enqueuing buffers of different formats to the same source is not permissible in OpenAL 1.1 standard. */
streams.filterNot { runningStream.format.matches(it.format) }.takeIf { it.isNotEmpty() }?.let { incompatibleStreams ->
val message = StringBuilder("Cannot compose CompoundAudioStream from streams of different formats:\n${runningStream.format}")
incompatibleStreams.forEach {
message.append("\n${it.format}")
}
throw IllegalArgumentException(message.toString())
}
}
/** Reads [size] bytes of PCM, from the current [runningStream], for 1 second worth of audio. */
override fun read(size: Int): ByteBuffer? {
var buffer = this.runningStream.read(size)
if (!buffer.hasRemaining() && this.streams.hasNext()) {
this.runningStream.close()
this.runningStream = this.streams.next()
buffer = this.runningStream.read(size)
}
return buffer // it's fine if the buffer is not completely full - the stream will sound seamless
}
override fun close() {
this.runningStream.close()
while (streams.hasNext()) streams.next().close()
}
}
@@ -0,0 +1,286 @@
package com.cobblemon.mod.common.client.sound.streams
import com.cobblemon.mod.common.Cobblemon
import com.mojang.blaze3d.audio.Channel
import net.minecraft.Util
import net.minecraft.client.sounds.AudioStream
import net.minecraft.client.sounds.LoopingAudioStream
import org.lwjgl.BufferUtils
import java.io.BufferedInputStream
import java.io.IOException
import java.io.InputStream
import java.nio.ByteBuffer
import java.util.concurrent.CompletableFuture
import javax.sound.sampled.AudioFormat
import kotlin.properties.Delegates
/**
* An [AudioStream] divided into an intro substream and a loop substream, determined by loop points provided with the Sound resource.
*
* Similar to [LoopingAudioStream], the loop will play indefinitely until stopped.
*
* @param provider Provider for creating the internal [AudioStream] that decodes the audio.
* @param primaryInputStream The initial [InputStream] of the encoded audio to play.
* @param loopStart The starting point of the main audio loop in SAMPLES. If 0, loop will run from start until the [loopEnd].
* @param loopEnd The ending point of the main audio loop in SAMPLES. If 0, loop will run to completion.
*
* @author Segfault Guy
* @since October 19th, 2025
*/
class LoopPointAudioStream @Throws(IllegalArgumentException::class) constructor(
private val provider: LoopingAudioStream.AudioStreamProvider,
primaryInputStream: InputStream,
loopStart: Int = 0,
loopEnd: Int = 0
): AudioStream {
private val DEBUG_FLAG = true
/** Amount of buffers of decoded PCM, from the start of the loop, to cache. */
private val CACHE_LIMIT = Channel.QUEUED_BUFFER_COUNT // CACHE_LIMIT matches the number of buffers pre-read before feeding to OpenAL
/** Buffers of PCM that start the loop. */
private val bufferCache = mutableListOf<ByteBuffer>() // cache size: 4 buffers, each of size ~176400 (70kb of cache) which is fine
/** The total number of bytes saved in the [bufferCache]. */
private var cacheSize = 0
/** The number of buffers read from the [bufferCache] */
private var bufferCounter = 0
/** The bytes bytween the end of the cache and the start of the rewinded [decoderStream]. */
private var preliminaryBuffer: ByteBuffer? = null
/** Whether the [preliminaryBuffer] has been read on this pass. */
private var readPrem: Boolean = false
/** The OGG Vorbis. This is encoded audio. */
private lateinit var encodedStream: BufferedInputStream
/** The Vorbis decoder. This decodes PCM from the [encodedStream] on calls to [AudioStream.read]. */
private lateinit var decoderStream: AudioStream
/** The starting point of the loop in BYTES. */
private var loopStart by Delegates.notNull<Int>()
/** The ending point of the loop in BYTES. */
private var loopEnd by Delegates.notNull<Int>()
/** Whether this is the first pass of the [encodedStream] and we haven't looped yet. */
private var firstPass = true
/** Whether [decoderStream] has been rewinded to [loopStart]. */
private var loopPrepared = false
/** The total number of bytes that have been read from the decoded stream. */
private var runningRead = 0
init {
if (loopStart > 0 && loopEnd > 0 && loopEnd < loopStart) throw IllegalArgumentException("Invalid loop points: non-zero end precedes non-zero start")
if (loopStart < 0 || loopEnd < 0) throw IllegalArgumentException("Invalid loop points: only non-negative values allowed")
this.encodedStream = BufferedInputStream(primaryInputStream)
this.encodedStream.mark(Int.Companion.MAX_VALUE)
this.buildDecoderStream()
this.loopStart = this.samplesToBytes(loopStart)
this.loopEnd = this.samplesToBytes(loopEnd)
}
override fun getFormat(): AudioFormat? {
return this.decoderStream.format
}
/** Reads 1 second worth of PCM (bytes) for an OpenAL buffer (this will be AT LEAST 176400 bytes for stereo audio formats) */
override fun read(size: Int): ByteBuffer? {
// if decoderStream has not finished rewinding, there's a problem
if (!this.firstPass && !this.loopPrepared) {
Cobblemon.LOGGER.warn("Decoder not rewinded!")
}
this.debug("read " + size + " | runningRead: " + this.runningRead + " | loopStart: " + this.loopStart + " | loopEnd: " + this.loopEnd)
val buffer = this.getCachedOrRawBuffer(size)
val sizeAfterRead = this.runningRead + buffer.remaining()
// if this read reaches the end loop point
if (loopEnd > 0 && sizeAfterRead >= this.loopEnd) {
val remainderBeforeEnd = this.loopEnd - this.runningRead
// limit buffer if it goes past the loop point (cached buffers don't apply as they are always within the loop points)
if (buffer.remaining() > remainderBeforeEnd)
{
this.debug("limit and reset read")
buffer.limit(remainderBeforeEnd)
}
else {
this.debug("reset read")
}
this.resetLoop()
}
// if loopEnd is 0 and the entire stream is read, or exaggerated and the stream is exhausted before reaching the point
else if (!buffer.hasRemaining()) {
this.debug("exhaustive read")
this.resetLoop()
}
// standard read
else {
this.debug("standard read")
this.runningRead += buffer.remaining();
}
return buffer
}
/**
* Retrieves a [ByteBuffer] of varying size from the [bufferCache] if this is not the [firstPass] and a cached buffer
* is available, or reads approximately [size] bytes from the [decoderStream].
*/
private fun getCachedOrRawBuffer(size: Int): ByteBuffer {
val cacheExists = CACHE_LIMIT > 0 && !this.bufferCache.isEmpty() && this.bufferCounter < this.bufferCache.size
// get buffer from cache
if (cacheExists && !this.firstPass) {
val toReturn = this.bufferCache.get(this.bufferCounter++)
this.debug("get cached buffer: " + toReturn.remaining())
return toReturn
}
// TODO coalesce this with bufferCache
else if (!this.readPrem && this.preliminaryBuffer != null && !this.firstPass) {
this.readPrem = true
val toReturn = this.preliminaryBuffer!!
this.debug("read prem buffer: " + toReturn.remaining())
return toReturn
}
// decode a buffer of approximate size
else {
val toReturn = this.decoderStream.read(size)
this.debug("read raw: " + toReturn.remaining())
this.cacheBuffer(toReturn)
return toReturn
}
}
/**
* Caches [buffer] in the [bufferCache] during the [firstPass] if the [CACHE_LIMIT] has not been reached and the buffer
* contains bytes within the [loopStart] and the [loopEnd] boundaries.
*/
private fun cacheBuffer(buffer: ByteBuffer) {
val canCacheBuffer = CACHE_LIMIT > 0 && loopStart > 0 && this.bufferCache.size < CACHE_LIMIT && this.firstPass
val canStartCaching = this.runningRead + buffer.remaining() > this.loopStart
if (!canCacheBuffer || !canStartCaching) return
// offset used - so that only the tail of a buffer that contains the loopStart point is cached
val remainderBeforeStart = Math.max(this.loopStart - this.runningRead, 0)
var remainderBeforeEnd = 0
// only cache the head of a buffer that goes past the loopEnd point
if (loopEnd > 0 && buffer.remaining() + this.runningRead > this.loopEnd) {
remainderBeforeEnd = this.loopEnd - this.runningRead - remainderBeforeStart
// NOTE: read will resetLoop and stop caching if CACHE_LIMIT has not been reached but loopEnd has
}
// otherwise, cache the entire remainder of the buffer
else {
remainderBeforeEnd = buffer.remaining() - remainderBeforeStart
}
if (remainderBeforeEnd <= 0) return
// copy to avoid mutating provided buffer
val buffed = BufferUtils.createByteBuffer(remainderBeforeEnd)
buffed.put(0, buffer, remainderBeforeStart, remainderBeforeEnd)
buffed.rewind()
this.debug("cache buffer | remainderBeforeStart: " + remainderBeforeStart + " | remainderBeforeEnd: " + remainderBeforeEnd + " | size: " + buffed.remaining())
this.bufferCache.add(buffed)
this.cacheSize += buffed.remaining()
this.debug("cacheSize: " + cacheSize + " bufferCacheSize: " + this.bufferCache.size)
}
/** Converts PCM samples to bytes. */
private fun samplesToBytes(sampleAmount: Int): Int {
val format = this.format ?: return 0
return sampleAmount * format.getSampleSizeInBits() / 8 * format.getChannels()
}
/**
* Rewind [decoderStream] to start from [loopStart].
*
* NOTE: Assumes that [bufferCache] is finalized.
*/
private fun resetLoop() {
// reset state info
this.loopPrepared = false
this.runningRead = this.loopStart
this.bufferCounter = 0
this.firstPass = false
this.debug("reset")
// if the entire loop is small enough to fit the cache, skip rewinding the decoderStream
if (this.bufferCache.size > 0 && this.bufferCache.size < CACHE_LIMIT) {
this.debug("comprehensive cache")
this.loopPrepared = true
return
}
/*
Seeking is implemented agnostic of the OGG Vorbis decoder. Seek to the loop start by decoding PCM in bulk from
the beginning of the OGG Vorbis up until the desired sample offset is reached. Then for playing the loop, continue
streaming decoded PCM into OpenAL buffers.
*/
this.decoderStream.close()
this.encodedStream.reset()
this.buildDecoderStream()
// if loopStart is 0, there's no cache, so loop starts by reading from the new decoderStream
if (this.bufferCache.size == 0) {
this.debug("nonexistent cache")
this.loopPrepared = true
return
}
/*
After the first pass, encodedStream will have the entire Vorbis OGG buffered and there won't be any subsequent reads
from disk. However, there's still latency in decoding a lot of PCM. This needs to be done asynchronously, otherwise
the client will momentarily freeze each time the loop resets. In the unlikely case that the decoderStream is not
rewinded by the time the Channel needs to read a buffer, it will be read from bufferCache.
*/
CompletableFuture.supplyAsync<Any?>({
try {
val decoderStart = this.loopStart + this.cacheSize
val buffer = decoderStream.read(decoderStart)
// because the deecoderStream will only read an approximation, need to account for additional bytes
if (this.preliminaryBuffer == null && buffer.remaining() > decoderStart) {
this.debug("rewind buffer size: " + buffer.remaining() + " overshot by: " + (buffer.remaining() - decoderStart))
this.preliminaryBuffer = BufferUtils.createByteBuffer(buffer.remaining() - decoderStart)
this.preliminaryBuffer!!.put(0, buffer, decoderStart, buffer.remaining() - decoderStart)
this.preliminaryBuffer!!.rewind()
// decoderStream.read is deterministic, so this should only be set once
}
this.readPrem = false
this.loopPrepared = true
this.debug("decoderStream rewinded")
return@supplyAsync null
} catch (e: IOException) {
throw RuntimeException(e)
}
}, Util.nonCriticalIoPool())
}
private fun buildDecoderStream() {
this.decoderStream = provider.create(LoopingAudioStream.NoCloseBuffer(this.encodedStream))
}
override fun close() {
this.decoderStream.close()
this.encodedStream.close() // closes the InputStream used
}
private fun debug(log: String) {
if (this.DEBUG_FLAG) Cobblemon.LOGGER.error(log)
}
}
@@ -1208,14 +1208,13 @@ open class PokemonEntity(
}
}
if (hand == InteractionHand.MAIN_HAND && player is ServerPlayer) {
if (player.isShiftKeyDown) {
if (hand == InteractionHand.MAIN_HAND) {
if (player.isShiftKeyDown && player is ServerPlayer) {
showInteractionWheel(player, itemStack)
return InteractionResult.sidedSuccess(level().isClientSide)
}
else if (pokemon.getOwnerPlayer() == player) {
// TODO #105
if (this.attemptItemInteraction(player, player.getItemInHand(hand))) return InteractionResult.SUCCESS
else if (this.ownerUUID == player.uuid && this.attemptItemInteraction(player, player.getItemInHand(hand))) {
return InteractionResult.sidedSuccess(level().isClientSide)
}
}
@@ -1391,18 +1390,24 @@ open class PokemonEntity(
return bagItemLike.handleInteraction(player, battlePokemon, stack)
}
if (player !is ServerPlayer || this.isBusy) {
if (this.isBusy) {
return false
}
val interaction = PokemonInteractions.findInteraction(this)
if (interaction != null && !pokemon.isOnInteractionCooldown(interaction.grouping)) {
interaction.effects.forEach { it.applyEffect(this, player) }
pokemon.interactionCooldowns.put(interaction.grouping, runtime.resolveInt(interaction.cooldown))
if (player is ServerPlayer) {
interaction.effects.forEach { it.applyEffect(this, player) }
pokemon.interactionCooldowns.put(interaction.grouping, runtime.resolveInt(interaction.cooldown))
}
return true
}
if (player !is ServerPlayer) {
return false
}
// Evolution item logic
if (pokemon.getOwnerPlayer() == player) {
val context = ItemInteractionEvolution.ItemInteractionContext(stack, player.level())
@@ -30,21 +30,13 @@ import com.cobblemon.mod.common.api.events.battles.BattleStartedEvent
import com.cobblemon.mod.common.api.events.battles.BattleVictoryEvent
import com.cobblemon.mod.common.api.events.fishing.PokerodCastEvent
import com.cobblemon.mod.common.api.events.fishing.PokerodReelEvent
import com.cobblemon.mod.common.api.events.pokemon.CollectEggEvent
import com.cobblemon.mod.common.api.events.pokemon.FossilRevivedEvent
import com.cobblemon.mod.common.api.events.pokemon.HatchEggEvent
import com.cobblemon.mod.common.api.events.pokemon.LevelUpEvent
import com.cobblemon.mod.common.api.events.pokemon.PokemonCapturedEvent
import com.cobblemon.mod.common.api.events.pokemon.PokemonGainedEvent
import com.cobblemon.mod.common.api.events.pokemon.RidePokemonEvent
import com.cobblemon.mod.common.api.events.pokemon.TradeEvent
import com.cobblemon.mod.common.api.events.pokemon.*
import com.cobblemon.mod.common.api.events.pokemon.evolution.EvolutionCompleteEvent
import com.cobblemon.mod.common.api.events.storage.ReleasePokemonEvent
import com.cobblemon.mod.common.api.stats.CobblemonStats.getStat
import com.cobblemon.mod.common.util.getPlayer
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
import java.util.UUID
import java.util.*
object StatHandler : EventHandler {
override fun registerListeners() {
@@ -100,13 +92,13 @@ object StatHandler : EventHandler {
fun onBattleStart(event : BattleStartedEvent) {
event.battle.players.forEach { player ->
player.awardStat(this.getStat(getStat(Cobblemon.statistics.BATTLES_TOTAL)))
player.awardStat(getStat(Cobblemon.statistics.BATTLES_TOTAL))
}
}
fun onDexEntryGain(event : PokemonGainedEvent) {
val player = event.pokemon.getOwnerPlayer()
player?.awardStat(getStat(getStat(Cobblemon.statistics.DEX_ENTRIES)))
player?.awardStat(getStat(Cobblemon.statistics.DEX_ENTRIES))
}
fun onCollectEgg(event : CollectEggEvent) {
@@ -139,12 +131,4 @@ object StatHandler : EventHandler {
fun onReelIn(event : PokerodReelEvent) {
event.player.awardStat(getStat(Cobblemon.statistics.REEL_INS))
}
fun getStat(resourceLocation: ResourceLocation) : ResourceLocation {
val stat = BuiltInRegistries.CUSTOM_STAT.get(resourceLocation)
if (stat == null) {
Cobblemon.LOGGER.error("Could not find stat with id {}", resourceLocation)
}
return stat ?: throw NullPointerException("Could not find stat with id $resourceLocation")
}
}
@@ -25,9 +25,7 @@ class MochiItem(stat: Stats): EVIncreaseItem(stat, 4) {
}
pokemon.feedPokemon(1)
stack.consume(1, player)
return super.applyToPokemon(player, stack, pokemon)
}
}
@@ -35,9 +35,16 @@ class SinisterTeaItem : Item(
val effectsComponent = stack.get(CobblemonItemComponents.MOB_EFFECTS)
if (effectsComponent != null) {
for (effect in effectsComponent.mobEffects) {
val effectCopy = MobEffectInstance(effect)
user.addEffect(effectCopy)
for (raw in effectsComponent.mobEffects) {
val inst = MobEffectInstance(raw)
val holder = inst.effect
val effect = holder.value()
if (effect.isInstantenous) {
effect.applyInstantenousEffect(user as? Player, null, user, inst.amplifier, 1.0)
} else {
user.addEffect(inst)
}
}
}
}
@@ -20,8 +20,10 @@ import net.minecraft.resources.ResourceLocation
*
* Handled by [com.cobblemon.mod.common.client.net.battle.BattleMusicHandler].
*
* @property music The location of the sound to play during a battle. If null, gracefully ends the currently playing sound.
* @property restartExisting If true and the location of [music] matches the currently playing sound, will restart from the beginning.
* @property music The location of a [SoundEvent], or the location of the []to dispatch a sound from, to play during a battle.
* If null, gracefully ends the currently playing sound.
*
* @property restartExisting If true and [music] matches the location of the currently playing sound, will restart from the beginning.
*
* @author Segfault Guy
* @since April 20th, 2023
@@ -0,0 +1,48 @@
/*
* 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.net.messages.client.data
import com.cobblemon.mod.common.api.interaction.PokemonInteractionSet
import com.cobblemon.mod.common.api.net.NetworkPacket
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.network.RegistryFriendlyByteBuf
class PokemonInteractionsSyncPacket(
val speciesInteractions: List<PokemonInteractionSet>,
val generalInteractions: List<PokemonInteractionSet>,
) : NetworkPacket<PokemonInteractionsSyncPacket> {
override val id = ID
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeCollection(speciesInteractions) { it, speciesInteraction ->
speciesInteraction.encode(it)
}
buffer.writeCollection(generalInteractions) { it, generalInteraction ->
generalInteraction.encode(it)
}
}
companion object {
val ID = cobblemonResource("pokemon_interactions_sync")
fun decode(buffer: RegistryFriendlyByteBuf): PokemonInteractionsSyncPacket {
val speciesInteractions = buffer.readList { it ->
PokemonInteractionSet.decode(it)
}
val generalInteractions = buffer.readList { it ->
PokemonInteractionSet.decode(it)
}
return PokemonInteractionsSyncPacket(
speciesInteractions,
generalInteractions,
)
}
}
}
@@ -0,0 +1,4 @@
package com.cobblemon.mod.common.net.messages.client.data
class SoundTrackConfigRegistrySyncPacket {
}
@@ -21,7 +21,9 @@ class PokemonStateUpdatePacket(pokemon: () -> Pokemon?, value: PokemonState): Si
value.writeToBuffer(buffer)
}
override fun set(pokemon: Pokemon, value: PokemonState) { pokemon.state = value }
override fun set(pokemon: Pokemon, value: PokemonState) {
pokemon.state = value
}
companion object {
val ID = cobblemonResource("state_update")
@@ -13,30 +13,24 @@ import com.cobblemon.mod.common.api.mark.Marks
import com.cobblemon.mod.common.api.pokeball.PokeBalls
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.client.CobblemonClient
import com.cobblemon.mod.common.entity.PlatformType
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.pokemon.Gender
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.readEnumConstant
import com.cobblemon.mod.common.util.readIdentifier
import com.cobblemon.mod.common.util.readString
import com.cobblemon.mod.common.util.readText
import com.cobblemon.mod.common.util.writeEnumConstant
import com.cobblemon.mod.common.util.writeIdentifier
import com.cobblemon.mod.common.util.writeString
import com.cobblemon.mod.common.util.writeText
import java.util.UUID
import com.cobblemon.mod.common.util.*
import net.minecraft.client.multiplayer.ClientLevel
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.network.chat.MutableComponent
import net.minecraft.network.protocol.game.ClientboundAddEntityPacket
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.entity.Entity
import java.util.*
class SpawnPokemonPacket(
var ownerId: UUID?,
var pokemonUUID: UUID,
var storageUUID: UUID?,
var scaleModifier: Float,
var speciesId: ResourceLocation,
var gender: Gender,
@@ -61,6 +55,7 @@ class SpawnPokemonPacket(
var tickSpawned: Int,
var rideBoosts: Map<RidingStat, Float>,
var rideStamina: Float,
var silent: Boolean,
vanillaSpawnPacket: ClientboundAddEntityPacket,
) : SpawnExtraDataEntityPacket<SpawnPokemonPacket, PokemonEntity>(vanillaSpawnPacket) {
@@ -69,6 +64,7 @@ class SpawnPokemonPacket(
constructor(entity: PokemonEntity, vanillaSpawnPacket: ClientboundAddEntityPacket) : this(
entity.ownerUUID,
entity.pokemon.uuid,
entity.pokemon.storeCoordinates.get()?.store?.uuid,
entity.pokemon.scaleModifier,
entity.exposedSpecies.resourceIdentifier,
entity.pokemon.gender,
@@ -93,12 +89,14 @@ class SpawnPokemonPacket(
entity.tickCount,
entity.entityData.get(PokemonEntity.RIDE_BOOSTS),
entity.entityData.get(PokemonEntity.RIDE_STAMINA),
entity.isSilent,
vanillaSpawnPacket
)
override fun encodeEntityData(buffer: RegistryFriendlyByteBuf) {
buffer.writeNullable(ownerId) { _, v -> buffer.writeUUID(v) }
buffer.writeUUID(this.pokemonUUID)
buffer.writeNullable(this.storageUUID) { pb, value -> pb.writeUUID(value) }
buffer.writeFloat(this.scaleModifier)
buffer.writeIdentifier(this.speciesId)
buffer.writeEnumConstant(this.gender)
@@ -127,10 +125,21 @@ class SpawnPokemonPacket(
{ _, value -> buffer.writeFloat(value) }
)
buffer.writeFloat(rideStamina)
buffer.writeBoolean(silent)
}
override fun applyData(entity: PokemonEntity, level: ClientLevel) {
entity.ownerUUID = ownerId
// Reuse the stored Pokémon instance to avoid creating a duplicate,
// preventing client/entity desync issues.
this@SpawnPokemonPacket.storageUUID?.let {
val storedPokemon = CobblemonClient.storage.locatePokemon(it, this@SpawnPokemonPacket.pokemonUUID)
if (storedPokemon != null) {
entity.pokemon = storedPokemon
}
}
entity.pokemon.apply {
uuid = this@SpawnPokemonPacket.pokemonUUID
scaleModifier = this@SpawnPokemonPacket.scaleModifier
@@ -162,6 +171,7 @@ class SpawnPokemonPacket(
entity.entityData.set(PokemonEntity.RIDE_BOOSTS, rideBoosts)
entity.entityData.set(PokemonEntity.RIDE_STAMINA, rideStamina)
entity.entityData.set(PokemonEntity.SCALE_MODIFIER, scaleModifier)
entity.isSilent = silent
entity.ejectPassengers()
passengers.forEach {
@@ -179,6 +189,7 @@ class SpawnPokemonPacket(
fun decode(buffer: RegistryFriendlyByteBuf): SpawnPokemonPacket {
val ownerId = buffer.readNullable { buffer.readUUID() }
val pokemonUUID = buffer.readUUID()
val storageUUID = buffer.readNullable { buffer.readUUID() }
val scaleModifier = buffer.readFloat()
val speciesId = buffer.readIdentifier()
val gender = buffer.readEnumConstant(Gender::class.java)
@@ -206,11 +217,13 @@ class SpawnPokemonPacket(
{ buffer.readFloat() }
)
val rideStamina = buffer.readFloat()
val silent = buffer.readBoolean()
val vanillaPacket = decodeVanillaPacket(buffer)
return SpawnPokemonPacket(
ownerId,
pokemonUUID,
storageUUID,
scaleModifier,
speciesId,
gender,
@@ -235,6 +248,7 @@ class SpawnPokemonPacket(
tickSpawned,
rideBoosts,
rideStamina,
silent,
vanillaPacket
)
}
@@ -0,0 +1,27 @@
package com.cobblemon.mod.common.ost
import net.minecraft.resources.ResourceLocation
import net.minecraft.sounds.SoundEvent
/**
* Event to signal a transition in battle music. When received by the client, will either replace the currently playing
* battle music with a new instance, or pause and temporarily switches from the current instance to play a [soundEvent].
*
* @author Segfault Guy
* @since December 16, 2925
*/
data class BattleMusicCue(
/** The identifier of this cue. */
val id: ResourceLocation,
/** The track or sound effect to play. */
val soundEvent: SoundEvent,
/** Whether the [soundEvent] should replace and become the main track. */
val replace: Boolean,
/** Determines which cue's [soundEvent] plays if multiple cues are scheduled at once. Priority is lost when cue becomes
* the main track. */
val priority: Int,
/** Time in seconds before the [soundEvent] starts playing. */
val delay: Int,
/** If the currently playing track should fade out, and the [soundEvent] should fade in. */
val fade: Boolean = false
)
@@ -0,0 +1,17 @@
package com.cobblemon.mod.common.ost
import net.minecraft.resources.ResourceLocation
/**
* A configuration that dispatches a [BattleTrack] to use when playing battle music.
*
* @author Segfault Guy
* @since January 6th, 2026
*/
class BattleThemeConfig(
id: ResourceLocation,
/** */
override val tracks: Collection<BattleTrack>,
/** 2nd-priority [BattleMusicCue]s that are subscribed by every track in the configuration. */
val cues: Collection<BattleMusicCue>,
) : SoundTrackConfig(id, tracks)
@@ -0,0 +1,43 @@
package com.cobblemon.mod.common.ost
import com.cobblemon.mod.common.api.data.JsonDataRegistry
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.util.adapters.IdentifierAdapter
import com.cobblemon.mod.common.util.cobblemonResource
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.reflect.TypeToken
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
import net.minecraft.server.packs.PackType
object BattleThemes : JsonDataRegistry<BattleThemeConfig> {
override val resourcePath = "battle_themes"
override val id: ResourceLocation = cobblemonResource(this.resourcePath)
override val type = PackType.SERVER_DATA
override val observable = SimpleObservable<BattleThemeConfig>()
override val gson: Gson = GsonBuilder()
.setPrettyPrinting()
.registerTypeAdapter(ResourceLocation::class.java, IdentifierAdapter)
.create()
override val typeToken = TypeToken.get(BattleThemeConfig::class.java)
/** */
private val battleSoundTrackConfigs = mutableMapOf<ResourceLocation, BattleThemeConfig>()
override fun reload(data: Map<ResourceLocation, BattleThemeConfig>) {
TODO("Not yet implemented")
}
override fun sync(player: ServerPlayer) {
TODO("Not yet implemented")
}
@JvmStatic
fun getByIdentifier(identifier: ResourceLocation): BattleThemeConfig? = this.battleSoundTrackConfigs[identifier]
}
@@ -0,0 +1,10 @@
package com.cobblemon.mod.common.ost
/**
*
*
*/
class BattleTrack(
val battle_conditions
) : SoundTrack {
}
@@ -0,0 +1,19 @@
package com.cobblemon.mod.common.ost
import com.cobblemon.mod.common.api.pokemon.requirement.EntityQueryRequirement
import net.minecraft.resources.ResourceLocation
/**
* r
*
* @author Segfault Guy
* @since December 16, 2025
*/
open class SoundTrack {
val ambient: ResourceLocation
val wildLocation: ResourceLocation
val trainerLocation: ResourceLocation
val priority: Int = 1
val requirements: MutableSet<EntityQueryRequirement>
}
@@ -0,0 +1,23 @@
package com.cobblemon.mod.common.ost
import net.minecraft.resources.ResourceLocation
/**
* A configuration that dispatches a [SoundTrack] to use when playing music.
*
* @author Segfault Guy
* @since January 6th, 2026
*/
open class SoundTrackConfig(
/** The identifier of this configuration. */
val id: ResourceLocation,
/** The */
open val tracks: Collection<SoundTrack>,
) {
fun dispatchTrack(): BattleTrack {
// TODO based on requirements
}
}
// should this be templated instead?
@@ -1437,6 +1437,11 @@ open class Pokemon : ShowdownIdentifiable {
}
fun getOwnerEntity(): LivingEntity? {
if (isClient) {
val ownerUUID = entity?.ownerUUID ?: return null
return entity?.level()?.getPlayerByUUID(ownerUUID)
}
return storeCoordinates.get()?.let {
if (isPlayerOwned()) {
server()?.playerList?.getPlayer(it.store.uuid)
@@ -8,13 +8,22 @@
package com.cobblemon.mod.common.pokemon.adapters
import com.cobblemon.mod.common.api.interaction.InteractionEffect
import com.cobblemon.mod.common.api.molang.ExpressionLike
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.pokemon.adapters.RequirementAdapter
import com.cobblemon.mod.common.api.pokemon.requirement.Requirement
import com.cobblemon.mod.common.pokemon.evolution.adapters.LegacyItemConditionWrapperAdapter
import com.cobblemon.mod.common.pokemon.requirements.*
import com.cobblemon.mod.common.util.adapters.*
import com.cobblemon.mod.common.util.readString
import com.cobblemon.mod.common.util.writeString
import com.google.common.collect.HashBiMap
import com.google.gson.JsonDeserializationContext
import com.google.gson.JsonElement
import com.google.gson.JsonSerializationContext
import com.google.gson.*
import net.minecraft.advancements.critereon.ItemPredicate
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.LowerCaseEnumTypeAdapterFactory
import java.lang.reflect.Type
import kotlin.reflect.KClass
@@ -58,6 +67,22 @@ object CobblemonRequirementAdapter : RequirementAdapter {
this.registerType(PropertyRangeRequirement.ADAPTER_VARIANT, PropertyRangeRequirement::class)
this.registerType(AdvancementRequirement.ADAPTER_VARIANT, AdvancementRequirement::class)
this.registerType(OwnerHoldsItemRequirement.ADAPTER_VARIANT, OwnerHoldsItemRequirement::class)
this.registerType(DepthRequirement.ADAPTER_VARIANT, DepthRequirement::class)
this.registerType(SkyRequirement.ADAPTER_VARIANT, SkyRequirement::class)
}
val gson: Gson by lazy {
GsonBuilder()
.registerTypeAdapter(ResourceLocation::class.java, IdentifierAdapter)
.registerTypeAdapter(PokemonProperties::class.java, pokemonPropertiesShortAdapter)
.registerTypeAdapter(ItemPredicate::class.java, LegacyItemConditionWrapperAdapter)
.registerTypeAdapter(Requirement::class.java, CobblemonRequirementAdapter)
.registerTypeAdapter(InteractionEffect::class.java, InteractionEffectAdapter)
.registerTypeAdapter(ExpressionLike::class.java, ExpressionLikeAdapter)
.registerTypeAdapter(IntRange::class.java, IntRangeAdapter)
.registerTypeAdapterFactory(LowerCaseEnumTypeAdapterFactory())
.setPrettyPrinting()
.create()
}
override fun <T : Requirement> registerType(id: String, type: KClass<T>) {
@@ -66,15 +91,40 @@ object CobblemonRequirementAdapter : RequirementAdapter {
override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): Requirement {
val variant = json.asJsonObject.get(VARIANT).asString.lowercase()
val type = this.types[variant] ?: throw IllegalArgumentException("Cannot resolve evolution requirement type for variant $variant")
val type = getRequirementType(variant)
return context.deserialize(json, type.java)
}
override fun serialize(src: Requirement, typeOfSrc: Type, context: JsonSerializationContext): JsonElement {
val json = context.serialize(src, src::class.java).asJsonObject
val variant = this.types.inverse()[src::class] ?: throw IllegalArgumentException("Cannot resolve evolution requirement for type ${src::class.qualifiedName}")
override fun serialize(requirement: Requirement, typeOfSrc: Type, context: JsonSerializationContext): JsonElement {
val json = context.serialize(requirement, requirement::class.java).asJsonObject
val variant = getVariant(requirement)
json.addProperty(VARIANT, variant)
return json
}
fun encode(requirement: Requirement, buffer: FriendlyByteBuf) {
buffer.writeString(gson.toJson(requirement, Requirement::class.java))
}
fun decode(buffer: FriendlyByteBuf): Requirement {
return gson.fromJson(buffer.readString(), Requirement::class.java)
}
private fun getVariant(requirement: Requirement): String {
val variant = this.types.inverse()[requirement::class]
if (variant == null) {
throw IllegalArgumentException("Cannot resolve evolution requirement for type ${requirement::class.qualifiedName}")
}
return variant
}
private fun getRequirementType(variant: String): KClass<out Requirement> {
val requirementType = this.types[variant]
if (requirementType == null) {
throw IllegalArgumentException("Cannot resolve evolution requirement type for variant $variant")
}
return requirementType
}
}
@@ -11,9 +11,9 @@ package com.cobblemon.mod.common.pokemon.requirements
import com.cobblemon.mod.common.api.pokemon.requirement.OwnerQueryRequirement
import com.cobblemon.mod.common.entity.npc.NPCEntity
import com.cobblemon.mod.common.pokemon.Pokemon
import net.minecraft.server.level.ServerPlayer
import net.minecraft.resources.ResourceLocation
import kotlin.collections.iterator
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.entity.player.Player
/**
* An [com.cobblemon.mod.common.api.pokemon.requirement.Requirement] that checks if the player has a certain [net.minecraft.advancements.Advancement]
@@ -26,13 +26,16 @@ class AdvancementRequirement(val requiredAdvancement: ResourceLocation) : OwnerQ
override fun checkPlayer(
pokemon: Pokemon,
owner: ServerPlayer
owner: Player
): Boolean {
for (entry in owner.advancements.progress) {
val serverPlayer = owner as? ServerPlayer ?: return false
for (entry in serverPlayer.advancements.progress) {
if (entry.key.id == requiredAdvancement && entry.value.isDone) {
return true
}
}
return false
}
@@ -9,13 +9,12 @@
package com.cobblemon.mod.common.pokemon.requirements
import com.cobblemon.mod.common.api.pokemon.requirement.EntityQueryRequirement
import com.cobblemon.mod.common.pokemon.Pokemon
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.phys.AABB
import net.minecraft.world.phys.Vec3
/**
* A [EntityQueryRequirement] for when a [Pokemon] is expected to be in a certain area.
* A [EntityQueryRequirement] for when a [LivingEntity] is expected to be in a certain area.
*
* @property box The [AABB] expected to be in.
* @author Licious
@@ -27,5 +26,5 @@ class AreaRequirement : EntityQueryRequirement {
}
val box: AABB = AABB.unitCubeFromLowerCorner(Vec3.ZERO)
override fun check(pokemon: Pokemon, queriedEntity: LivingEntity) = box.contains(queriedEntity.position())
override fun check(queriedEntity: LivingEntity) = box.contains(queriedEntity.position())
}
@@ -18,7 +18,7 @@ import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.level.biome.Biome
/**
* A [Requirement] for when a [Pokemon] is expected to be in a certain [Biome].
* A [Requirement] for when a [LivingEntity] is expected to be in a certain [Biome].
*
* @property biomeCondition The [ResourceLocation] of the [Biome] the queried entity is expected to be in.
* @author Licious
@@ -27,7 +27,7 @@ import net.minecraft.world.level.biome.Biome
class BiomeRequirement : EntityQueryRequirement {
val biomeCondition: RegistryLikeCondition<Biome>? = null
val biomeAnticondition: RegistryLikeCondition<Biome>? = null
override fun check(pokemon: Pokemon, queriedEntity: LivingEntity): Boolean {
override fun check(queriedEntity: LivingEntity): Boolean {
val biome = queriedEntity.level().getBiome(queriedEntity.blockPosition()).value()
val registry = queriedEntity.level().registryAccess().registryOrThrow(Registries.BIOME)
return (biomeCondition == null || biomeCondition.fits(biome, registry)) && (biomeAnticondition == null || !biomeAnticondition.fits(biome, registry))
@@ -0,0 +1,28 @@
package com.cobblemon.mod.common.pokemon.requirements
import com.cobblemon.mod.common.api.pokemon.requirement.EntityQueryRequirement
import net.minecraft.world.entity.LivingEntity
/**
* An [EntityQueryRequirement] that evaluates whether a [LivingEntity] is at a certain block level.
*
* @property minY The minimum block level to satisfy this requirement. If null, defaults to the dimension's min height.
* @property maxY The maximum block level to satisfy this requirement. If null, defaults to the dimension's max height.
* @author Segfault Guy
* @since December 16, 2025
*/
class DepthRequirement(val minY: Int?, val maxY: Int?) : EntityQueryRequirement {
override fun check(queriedEntity: LivingEntity): Boolean {
val minLevelY = queriedEntity.level().minBuildHeight
val maxLevelY = queriedEntity.level().maxBuildHeight
val test = minY?.coerceIn(minLevelY, maxLevelY) ?: minLevelY
val test2 = maxY?.coerceIn(minLevelY, maxLevelY) ?: maxLevelY
return queriedEntity.blockPosition().y in test..test2
}
companion object {
const val ADAPTER_VARIANT = "depth"
}
}
@@ -28,7 +28,7 @@ class MoonPhaseRequirement(moonPhase: MoonPhase) : EntityQueryRequirement {
val moonPhase: MoonPhase = moonPhase
override fun check(pokemon: Pokemon, queriedEntity: LivingEntity): Boolean {
override fun check(queriedEntity: LivingEntity): Boolean {
return try {
val moonPhase = MoonPhase.ofWorld(queriedEntity.level())
this.moonPhase == moonPhase
@@ -12,11 +12,9 @@ import com.cobblemon.mod.common.api.pokemon.requirement.OwnerQueryRequirement
import com.cobblemon.mod.common.entity.npc.NPCEntity
import com.cobblemon.mod.common.pokemon.Pokemon
import net.minecraft.advancements.critereon.ItemPredicate
import net.minecraft.server.level.ServerPlayer
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.InteractionHand
import net.minecraft.world.entity.LivingEntity
import kotlin.collections.iterator
import net.minecraft.world.entity.player.Player
/**
* An [OwnerQueryRequirement] that checks if the player/npc is holding a certain [ItemPredicate]
@@ -30,7 +28,7 @@ class OwnerHoldsItemRequirement(val itemCondition: ItemPredicate)
override fun checkPlayer(
pokemon: Pokemon,
owner: ServerPlayer
owner: Player
): Boolean {
return checkItem(owner)
}
@@ -0,0 +1,20 @@
package com.cobblemon.mod.common.pokemon.requirements
import com.cobblemon.mod.common.api.pokemon.requirement.EntityQueryRequirement
import net.minecraft.world.entity.LivingEntity
/**
* An [EntityQueryRequirement] that evaluates whether a [LivingEntity] is exposed to a dimension's sky.
*
* @param invert If true, this requirement evaluates whether the entity CANNOT see a dimension's sky.
* @author Segfault Guy
* @since December 16, 2025
*/
class SkyRequirement(val invert: Boolean = false) : EntityQueryRequirement {
override fun check(queriedEntity: LivingEntity) = queriedEntity.level().canSeeSky(queriedEntity.blockPosition()).xor(invert)
companion object {
const val ADAPTER_VARIANT = "sky"
}
}
@@ -16,7 +16,7 @@ import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.level.levelgen.structure.Structure
/**
* A [EntityQueryRequirement] for when a [Pokemon] is expected to be in a certain structure.
* A [EntityQueryRequirement] for when a [LivingEntity] is expected to be in a certain structure.
*
* @property structureCondition The [Identifier] of the structure the queried entity is expected to be in.
* @author whatsy
@@ -25,7 +25,7 @@ import net.minecraft.world.level.levelgen.structure.Structure
class StructureRequirement : EntityQueryRequirement {
val structureCondition: RegistryLikeCondition<Structure>? = null
val structureAnticondition: RegistryLikeCondition<Structure>? = null
override fun check(pokemon: Pokemon, queriedEntity: LivingEntity): Boolean {
override fun check(queriedEntity: LivingEntity): Boolean {
val structures = queriedEntity.level().getChunk(queriedEntity.blockPosition()).allReferences
val registry = queriedEntity.level().registryAccess().registryOrThrow(Registries.STRUCTURE)
return (structureCondition == null || structures.any { structureCondition.fits(it.key, registry) }) && (structureAnticondition == null || !structures.any { structureAnticondition.fits(it.key, registry) })
@@ -22,7 +22,7 @@ import net.minecraft.world.entity.LivingEntity
*/
class TimeRangeRequirement : EntityQueryRequirement {
val range = TimeRange(0..23999)
override fun check(pokemon: Pokemon, queriedEntity: LivingEntity) = this.range.contains((queriedEntity.level().dayTime() % DAY_DURATION).toInt())
override fun check(queriedEntity: LivingEntity) = this.range.contains((queriedEntity.level().dayTime() % DAY_DURATION).toInt())
companion object {
const val ADAPTER_VARIANT = "time_range"
private const val DAY_DURATION = 24000
@@ -20,7 +20,7 @@ class WeatherRequirement : EntityQueryRequirement {
val isRaining: Boolean? = null
val isThundering: Boolean? = null
override fun check(pokemon: Pokemon, queriedEntity: LivingEntity): Boolean {
override fun check(queriedEntity: LivingEntity): Boolean {
val world = queriedEntity.level()
return when {
isRaining == true && !world.isRaining -> false
@@ -15,7 +15,7 @@ import net.minecraft.resources.ResourceLocation
import net.minecraft.world.level.Level
/**
* A [EntityQueryRequirement] for when a [Pokemon] is expected to be in a [Level].
* A [EntityQueryRequirement] for when a [LivingEntity] is expected to be in a [Level].
*
* @property identifier The [ResourceLocation] of the [Level] the queried entity is expected to be in.
* @author Licious
@@ -26,5 +26,5 @@ class WorldRequirement : EntityQueryRequirement {
const val ADAPTER_VARIANT = "world"
}
val identifier: ResourceLocation = ResourceLocation.parse("minecraft:the_overworld")
override fun check(pokemon: Pokemon, queriedEntity: LivingEntity) = queriedEntity.level().dimension().location() == this.identifier
override fun check(queriedEntity: LivingEntity) = queriedEntity.level().dimension().location() == this.identifier
}
@@ -8,22 +8,22 @@
package com.cobblemon.mod.common.util.adapters
import com.google.gson.JsonDeserializationContext
import com.google.gson.JsonDeserializer
import com.google.gson.JsonElement
import com.google.gson.JsonSerializationContext
import com.google.gson.JsonSerializer
import com.google.gson.*
import com.mojang.serialization.Codec
import com.mojang.serialization.JsonOps
import java.lang.reflect.Type
class CodecBackedAdapter<T>(val codec: Codec<T>) : JsonDeserializer<T>, JsonSerializer<T> {
override fun deserialize(
jElement: JsonElement,
typeOfT: Type,
context: JsonDeserializationContext
): T {
return JsonOps.INSTANCE.withDecoder(codec).apply(jElement).result().get().first as T
val ops = RegistryOpsProvider.getOpsWithDefaultFallback()
val result = ops.withDecoder(codec).apply(jElement)
return result.result().orElseThrow {
IllegalStateException("Failed to deserialize $jElement: ${result.error().orElse(null)}")
}.first
}
override fun serialize(
@@ -31,6 +31,10 @@ class CodecBackedAdapter<T>(val codec: Codec<T>) : JsonDeserializer<T>, JsonSeri
typeOfSrc: Type?,
context: JsonSerializationContext?
): JsonElement {
return JsonOps.INSTANCE.withEncoder(codec).apply(src).result().get()
val ops = RegistryOpsProvider.getOpsWithDefaultFallback()
val result = ops.withEncoder(codec).apply(src)
return result.result().orElseThrow {
IllegalStateException("Failed to serialize $src: ${result.error().orElse(null)}")
}
}
}
@@ -0,0 +1,41 @@
/*
* 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.cobblemon.mod.common.Environment
import com.cobblemon.mod.common.util.server
import com.google.gson.JsonElement
import com.mojang.serialization.DynamicOps
import com.mojang.serialization.JsonOps
import net.minecraft.client.Minecraft
import net.minecraft.resources.RegistryOps
/**
* Utility for obtaining RegistryOps with the correct client/server registry access.
* Used when manually encoding/decoding registry-backed values (e.g., items, predicates).
*/
object RegistryOpsProvider {
fun getOpsWithDefaultFallback(): DynamicOps<JsonElement> {
if (Cobblemon.implementation.environment() == Environment.CLIENT) {
val minecraft = Minecraft.getInstance()
if (minecraft != null && minecraft.level != null)
return RegistryOps.create(JsonOps.INSTANCE, minecraft.level!!.registryAccess())
}
if (Cobblemon.implementation.environment() == Environment.SERVER) {
val server = server()
if (server != null)
return RegistryOps.create(JsonOps.INSTANCE, server.registryAccess())
}
return JsonOps.INSTANCE
}
}
@@ -820,6 +820,14 @@
"cobblemon.cooking.cooking_quality.medium": "Medium",
"cobblemon.cooking.cooking_quality.high": "High",
"effect.cobblemon.cleanse_all": "Effect Cleanse",
"effect.cobblemon.cleanse_negative": "Negative Effect Cleanse",
"effect.cobblemon.mental_restoration": "Mental Restoration",
"effect.cobblemon.cleanse_all": "Effect Cleanse",
"effect.cobblemon.cleanse_negative": "Negative Effect Cleanse",
"effect.cobblemon.mental_restoration": "Mental Restoration",
"cobblemon.fossil.armor": "Armor",
"cobblemon.fossil.bird": "Bird",
"cobblemon.fossil.claw": "Claw",
@@ -3515,6 +3523,7 @@
"tag.item.cobblemon.plants": "Plants",
"tag.item.cobblemon.seeds": "Seeds",
"tag.item.cobblemon.boats": "Boats",
"tag.item.cobblemon.chest_boats": "Chest Boats",
"tag.item.cobblemon.hanging_signs": "Hanging Signs",
"tag.item.cobblemon.signs": "Signs",
"tag.item.cobblemon.dawn_stone_ores": "Dawn Stone Ores",
@@ -9445,5 +9454,8 @@
"stat.cobblemon.traded": "Pokémon Traded",
"stat.cobblemon.times_ridden": "Pokémon Ridden",
"stat.cobblemon.rod_casts": "PokéRod Casts",
"stat.cobblemon.reel_ins": "PokéRod Catches"
"stat.cobblemon.reel_ins": "PokéRod Catches",
"stat.cobblemon.riding_land": "Distance by Pokémon on Land",
"stat.cobblemon.riding_air": "Distance by Pokémon in Air",
"stat.cobblemon.riding_liquid": "Distance by Pokémon in Liquid"
}
@@ -1517,76 +1517,139 @@
{ "name": "cobblemon:animation/wings/steel_wing_flap_medium_8", "volume": 0.25 }
]
},
"ride.loop.wind": {
"ride.loop.wind.mono": {
"sounds": [
{
"name": "cobblemon:ride/loop/wind_loop",
"name": "cobblemon:ride/loop/wind_loop_mono",
"stream": true,
"attenuation_distance": 48.0,
"volume": 0.15
}
]
},
"ride.loop.leather": {
"ride.loop.wind.stereo": {
"sounds": [
{
"name": "cobblemon:ride/loop/leather_flight_loop",
"name": "cobblemon:ride/loop/wind_loop_stereo",
"stream": true,
"volume": 0.04
}
]
},
"ride.loop.leather.mono": {
"sounds": [
{
"name": "cobblemon:ride/loop/leather_flight_loop_mono",
"stream": true,
"attenuation_distance": 48.0,
"volume": 0.25
}
]
},
"ride.loop.plumage": {
"ride.loop.leather.stereo": {
"sounds": [
{
"name": "cobblemon:ride/loop/plumage_flight_loop",
"name": "cobblemon:ride/loop/leather_flight_loop_stereo",
"stream": true,
"volume": 0.05
}
]
},
"ride.loop.plumage.mono": {
"sounds": [
{
"name": "cobblemon:ride/loop/plumage_flight_loop_mono",
"stream": true,
"attenuation_distance": 48.0,
"volume": 0.25
}
]
},
"ride.loop.saucer": {
"ride.loop.plumage.stereo": {
"sounds": [
{
"name": "cobblemon:ride/loop/saucer_flight_loop",
"name": "cobblemon:ride/loop/plumage_flight_loop_stereo",
"stream": true,
"volume": 0.05
}
]
},
"ride.loop.saucer.mono": {
"sounds": [
{
"name": "cobblemon:ride/loop/saucer_flight_loop_mono",
"stream": true,
"attenuation_distance": 48.0,
"volume": 0.25
}
]
},
"ride.loop.rocket": {
"ride.loop.saucer.stereo": {
"sounds": [
{
"name": "cobblemon:ride/loop/rocket_flight_loop",
"name": "cobblemon:ride/loop/saucer_flight_loop_stereo",
"stream": true,
"volume": 0.1
}
]
},
"ride.loop.rocket.mono": {
"sounds": [
{
"name": "cobblemon:ride/loop/rocket_flight_loop_mono",
"stream": true,
"attenuation_distance": 48.0,
"volume": 0.4
}
]
},
"ride.loop.water_surface": {
"ride.loop.rocket.stereo": {
"sounds": [
{
"name": "cobblemon:ride/loop/water_surface_loop",
"name": "cobblemon:ride/loop/rocket_flight_loop_stereo",
"stream": true,
"volume": 0.3
}
]
},
"ride.loop.water_surface.mono": {
"sounds": [
{
"name": "cobblemon:ride/loop/water_surface_loop_mono",
"stream": true,
"attenuation_distance": 32.0,
"volume": 0.4
}
]
},
"ride.loop.underwater": {
"ride.loop.water_surface.stereo": {
"sounds": [
{
"name": "cobblemon:ride/loop/underwater_loop",
"name": "cobblemon:ride/loop/water_surface_loop_stereo",
"stream": true,
"volume": 0.13
}
]
},
"ride.loop.underwater.mono": {
"sounds": [
{
"name": "cobblemon:ride/loop/underwater_loop_mono",
"stream": true,
"attenuation_distance": 16.0,
"volume": 0.4
}
]
},
"ride.loop.underwater.stereo": {
"sounds": [
{
"name": "cobblemon:ride/loop/underwater_loop_stereo",
"stream": true,
"volume": 0.2
}
]
},
"pokemon.bulbasaur.ambient": {
"sounds": [ "cobblemon:pokemon/bulbasaur/bulbasaur_ambient" ]
},
Binary file not shown.

After

Width:  |  Height:  |  Size: 599 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 606 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 566 B

@@ -151,4 +151,9 @@ transitive-accessible method net/minecraft/world/food/FoodData add (IF)V
transitive-accessible method net/minecraft/client/model/HumanoidModel setupAttackAnimation (Lnet/minecraft/world/entity/LivingEntity;F)V
transitive-accessible method net/minecraft/client/model/HumanoidModel poseRightArm (Lnet/minecraft/world/entity/LivingEntity;)V
transitive-accessible method net/minecraft/client/model/HumanoidModel poseLeftArm (Lnet/minecraft/world/entity/LivingEntity;)V
transitive-accessible method net/minecraft/client/model/HumanoidModel poseLeftArm (Lnet/minecraft/world/entity/LivingEntity;)V
transitive-accessible class net/minecraft/client/sounds/LoopingAudioStream$NoCloseBuffer
transitive-accessible method net/minecraft/client/sounds/LoopingAudioStream$NoCloseBuffer <init> (Ljava/io/InputStream;)V
transitive-accessible field com/mojang/blaze3d/audio/Channel QUEUED_BUFFER_COUNT I
@@ -17,7 +17,7 @@
"conditions": {
"items": [
{
"items": "#cobblemon:mint_leaf"
"items": "#cobblemon:mint_leaves"
}
]
}
@@ -0,0 +1,41 @@
t.ticks_between_checks = 20 * 10;
math.mod(q.player.world.game_time, t.ticks_between_checks) != 0 ? {
return 0;
};
t.steps_required = 10000;
t.friendship_required = 200;
t.total_blocks =
q.player.get_custom_stat('minecraft:walk_one_cm') +
q.player.get_custom_stat('minecraft:crouch_one_cm') +
q.player.get_custom_stat('minecraft:sprint_one_cm') +
q.player.get_custom_stat('minecraft:walk_on_water_one_cm') +
q.player.get_custom_stat('minecraft:fall_one_cm') +
q.player.get_custom_stat('minecraft:climb_one_cm') +
q.player.get_custom_stat('minecraft:fly_one_cm') +
q.player.get_custom_stat('minecraft:walk_under_water_one_cm') +
q.player.get_custom_stat('minecraft:minecart_one_cm') +
q.player.get_custom_stat('minecraft:boat_one_cm') +
q.player.get_custom_stat('minecraft:pig_one_cm') +
q.player.get_custom_stat('minecraft:horse_one_cm') +
q.player.get_custom_stat('minecraft:aviate_one_cm') +
q.player.get_custom_stat('minecraft:swim_one_cm') +
q.player.get_custom_stat('minecraft:strider_one_cm') +
q.player.get_custom_stat('cobblemon:riding_land') +
q.player.get_custom_stat('cobblemon:riding_air') +
q.player.get_custom_stat('cobblemon:riding_liquid');
t.total_blocks = t.total_blocks / 100.0;
t.player_data = q.player.data();
math.floor(t.total_blocks / t.steps_required) > math.floor(t.player_data.last_partner_mark_check / t.steps_required) ? {
t.party = q.player.party;
for_each(t.pokemon, t.party.pokemon, {
t.pokemon.friendship >= t.friendship_required ? {
t.pokemon.add_marks_with_chance('cobblemon:mark_partner');
};
});
};
t.player_data.last_partner_mark_check = t.total_blocks;
@@ -4,5 +4,6 @@
"titleColor": "40B282",
"description": "cobblemon.mark.mark_partner.desc",
"texture": "cobblemon:textures/gui/mark/mark_partner.png",
"indexNumber": 104
"indexNumber": 104,
"chance": 0.01
}
@@ -26,7 +26,7 @@
},
{
"content": 2,
"tag": "#cobblemon:mint_leaf"
"tag": "#cobblemon:mint_leaves"
},
{
"content": 2,
@@ -0,0 +1,14 @@
{
"ingredient": "cobblemon:mental_herb",
"colour": "light_blue",
"mobEffects": [
{
"effect": "cobblemon:mental_restoration",
"duration": 200,
"amplifier": 0,
"ambient": false,
"visible": true,
"showIcon": true
}
]
}
@@ -0,0 +1,14 @@
{
"ingredient": "minecraft:milk_bucket",
"colour": "white",
"mobEffects": [
{
"effect": "cobblemon:cleanse_all",
"duration": 1,
"amplifier": 0,
"ambient": false,
"visible": true,
"showIcon": true
}
]
}
@@ -0,0 +1,14 @@
{
"ingredient": "cobblemon:moomoo_milk",
"colour": "white",
"mobEffects": [
{
"effect": "cobblemon:cleanse_all",
"duration": 1,
"amplifier": 0,
"ambient": false,
"visible": true,
"showIcon": true
}
]
}
@@ -0,0 +1,14 @@
{
"ingredient": "cobblemon:white_herb",
"colour": "white",
"mobEffects": [
{
"effect": "cobblemon:cleanse_negative",
"duration": 1,
"amplifier": 0,
"ambient": false,
"visible": true,
"showIcon": true
}
]
}
@@ -57,15 +57,33 @@
{
"muffleEnabled": true,
"pitchExpr": "math.max(1.0 ,0.2 + math.pow(math.min(q.ride_velocity() / 1.5, 1.0),2))",
"playForNonPassengers": true,
"soundLocation": "cobblemon:ride.loop.wind",
"playForPassengers": true,
"playForNonPassengers": false,
"soundLocation": "cobblemon:ride.loop.wind.stereo",
"volumeExpr": "math.pow(math.min(q.ride_velocity() / 1.5, 1.0),2)"
},
{
"muffleEnabled": true,
"pitchExpr": "math.max(1.0 ,0.2 + math.pow(math.min(q.ride_velocity() / 1.5, 1.0),2))",
"playForPassengers": false,
"playForNonPassengers": true,
"soundLocation": "cobblemon:ride.loop.leather",
"soundLocation": "cobblemon:ride.loop.wind.mono",
"volumeExpr": "math.pow(math.min(q.ride_velocity() / 1.5, 1.0),2)"
},
{
"muffleEnabled": true,
"pitchExpr": "math.max(1.0 ,0.2 + math.pow(math.min(q.ride_velocity() / 1.5, 1.0),2))",
"playForPassengers": true,
"playForNonPassengers": false,
"soundLocation": "cobblemon:ride.loop.leather.stereo",
"volumeExpr": "math.pow(math.min(q.ride_velocity() / 1.5, 1.0),2)"
},
{
"muffleEnabled": true,
"pitchExpr": "math.max(1.0 ,0.2 + math.pow(math.min(q.ride_velocity() / 1.5, 1.0),2))",
"playForPassengers": false,
"playForNonPassengers": true,
"soundLocation": "cobblemon:ride.loop.leather.mono",
"volumeExpr": "math.pow(math.min(q.ride_velocity() / 1.5, 1.0),2)"
}
],

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