diff --git a/src/main/java/dev/latvian/mods/kubejs/bindings/event/ClientEvents.java b/src/main/java/dev/latvian/mods/kubejs/bindings/event/ClientEvents.java index 8c666ac78..a60d0d78b 100644 --- a/src/main/java/dev/latvian/mods/kubejs/bindings/event/ClientEvents.java +++ b/src/main/java/dev/latvian/mods/kubejs/bindings/event/ClientEvents.java @@ -7,6 +7,7 @@ import dev.latvian.mods.kubejs.client.EntityRendererRegistryKubeEvent; import dev.latvian.mods.kubejs.client.LangKubeEvent; import dev.latvian.mods.kubejs.client.MenuScreenRegistryKubeEvent; +import dev.latvian.mods.kubejs.client.ParticleProviderRegistryKubeEvent; import dev.latvian.mods.kubejs.event.EventGroup; import dev.latvian.mods.kubejs.event.EventHandler; import dev.latvian.mods.kubejs.event.EventTargetType; @@ -29,4 +30,5 @@ public interface ClientEvents { EventHandler DEBUG_RIGHT = GROUP.client("rightDebugInfo", () -> DebugInfoKubeEvent.class); TargetedEventHandler ATLAS_SPRITE_REGISTRY = GROUP.client("atlasSpriteRegistry", () -> AtlasSpriteRegistryKubeEvent.class).requiredTarget(EventTargetType.ID); TargetedEventHandler LANG = GROUP.client("lang", () -> LangKubeEvent.class).requiredTarget(EventTargetType.STRING); + EventHandler PARTICLE_PROVIDER_REGISTRY = GROUP.client("particleProviderRegistry", () -> ParticleProviderRegistryKubeEvent.class); } diff --git a/src/main/java/dev/latvian/mods/kubejs/client/ClientAssetPacks.java b/src/main/java/dev/latvian/mods/kubejs/client/ClientAssetPacks.java index 2f58b7cd3..be774be63 100644 --- a/src/main/java/dev/latvian/mods/kubejs/client/ClientAssetPacks.java +++ b/src/main/java/dev/latvian/mods/kubejs/client/ClientAssetPacks.java @@ -61,6 +61,8 @@ public List inject(List original) { KubeJSPlugins.forEachPlugin(internalAssetPack, KubeJSPlugin::generateAssets); + internalAssetPack.buildSounds(); + var langMap = new HashMap(); var langEvents = new HashMap(); var enUsLangEvent = langEvents.computeIfAbsent("en_us", s -> new LangKubeEvent(s, langMap)); diff --git a/src/main/java/dev/latvian/mods/kubejs/client/KubeAnimatedParticle.java b/src/main/java/dev/latvian/mods/kubejs/client/KubeAnimatedParticle.java new file mode 100644 index 000000000..e865c73e0 --- /dev/null +++ b/src/main/java/dev/latvian/mods/kubejs/client/KubeAnimatedParticle.java @@ -0,0 +1,92 @@ +package dev.latvian.mods.kubejs.client; + +import dev.latvian.mods.kubejs.color.Color; +import dev.latvian.mods.kubejs.typings.Info; +import it.unimi.dsi.fastutil.floats.Float2IntFunction; +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.client.particle.SimpleAnimatedParticle; +import net.minecraft.client.particle.SpriteSet; +import net.minecraft.util.RandomSource; +import net.minecraft.world.phys.Vec3; +import org.jetbrains.annotations.Nullable; + +import java.util.function.Consumer; + +public class KubeAnimatedParticle extends SimpleAnimatedParticle { + + private Float2IntFunction lightColorFunction; + @Nullable + private Consumer onTick; + + public KubeAnimatedParticle(ClientLevel level, double x, double y, double z, SpriteSet sprites) { + super(level, x, y, z, sprites, 0.0125F); + setLifetime(20); + setSpriteFromAge(sprites); + lightColorFunction = super::getLightColor; + } + + public void setGravity(float g) { + gravity = g; + } + + @Info(value = "Sets teh friction of the particle, the particle's motion is multiplied by this value every tick") + public void setFriction(float f) { + friction = f; + } + + public void setColor(Color color, boolean alpha) { + setColor(color.getRgbJS()); + if (alpha) { + setAlpha((color.getArgbJS() >>> 24) / 255F); + } + } + + public void setColor(Color color) { + setColor(color, false); + } + + public void setPhysicality(boolean hasPhysics) { + this.hasPhysics = hasPhysics; + } + + public void setFasterWhenYMotionBlocked(boolean b) { + speedUpWhenYMotionIsBlocked = b; + } + + public void setLightColor(Float2IntFunction function) { + lightColorFunction = function; + } + + public void onTick(@Nullable Consumer tick) { + onTick = tick; + } + + public void setSpeed(Vec3 speed) { + setParticleSpeed(speed.x(), speed.y(), speed.z()); + } + + // Getters for protected values + + public ClientLevel getLevel() { return level; } + public double getX() { return x; } + public double getY() { return y; } + public double getZ() { return z; } + public double getXSpeed() { return xd; } + public double getYSpeed() { return yd; } + public double getZSpeed() { return zd; } + public SpriteSet getSpriteSet() { return sprites; } + public RandomSource getRandom() { return random; } + + @Override + public int getLightColor(float partialTick) { + return lightColorFunction.get(partialTick); + } + + @Override + public void tick() { + super.tick(); + if (onTick != null) { + onTick.accept(this); + } + } +} diff --git a/src/main/java/dev/latvian/mods/kubejs/client/KubeJSModClientEventHandler.java b/src/main/java/dev/latvian/mods/kubejs/client/KubeJSModClientEventHandler.java index 8c4ab2780..db1943929 100644 --- a/src/main/java/dev/latvian/mods/kubejs/client/KubeJSModClientEventHandler.java +++ b/src/main/java/dev/latvian/mods/kubejs/client/KubeJSModClientEventHandler.java @@ -35,6 +35,7 @@ import net.neoforged.neoforge.client.event.RegisterColorHandlersEvent; import net.neoforged.neoforge.client.event.RegisterKeyMappingsEvent; import net.neoforged.neoforge.client.event.RegisterMenuScreensEvent; +import net.neoforged.neoforge.client.event.RegisterParticleProvidersEvent; import net.neoforged.neoforge.client.event.RegisterShadersEvent; import net.neoforged.neoforge.client.extensions.common.IClientFluidTypeExtensions; import net.neoforged.neoforge.client.extensions.common.RegisterClientExtensionsEvent; @@ -170,4 +171,11 @@ public ResourceLocation getRenderOverlayTexture(Minecraft mc) { } } } + + @SubscribeEvent + public static void registerParticleProviders(RegisterParticleProvidersEvent event) { + if (ClientEvents.PARTICLE_PROVIDER_REGISTRY.hasListeners()) { + ClientEvents.PARTICLE_PROVIDER_REGISTRY.post(new ParticleProviderRegistryKubeEvent(event)); + } + } } diff --git a/src/main/java/dev/latvian/mods/kubejs/client/ParticleGenerator.java b/src/main/java/dev/latvian/mods/kubejs/client/ParticleGenerator.java new file mode 100644 index 000000000..5e05af8d3 --- /dev/null +++ b/src/main/java/dev/latvian/mods/kubejs/client/ParticleGenerator.java @@ -0,0 +1,30 @@ +package dev.latvian.mods.kubejs.client; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; + +import java.util.ArrayList; +import java.util.List; + +public class ParticleGenerator { + + public transient List textures = new ArrayList<>(); + + public ParticleGenerator texture(String texture) { + textures.add(texture); + return this; + } + + public ParticleGenerator textures(List textures) { + this.textures = textures; + return this; + } + + public JsonObject toJson() { + var array = new JsonArray(textures.size()); + textures.forEach(array::add); + var json = new JsonObject(); + json.add("textures", array); + return json; + } +} diff --git a/src/main/java/dev/latvian/mods/kubejs/client/ParticleProviderRegistryKubeEvent.java b/src/main/java/dev/latvian/mods/kubejs/client/ParticleProviderRegistryKubeEvent.java new file mode 100644 index 000000000..95e324b8d --- /dev/null +++ b/src/main/java/dev/latvian/mods/kubejs/client/ParticleProviderRegistryKubeEvent.java @@ -0,0 +1,51 @@ +package dev.latvian.mods.kubejs.client; + +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.client.particle.Particle; +import net.minecraft.client.particle.ParticleEngine; +import net.minecraft.client.particle.ParticleProvider; +import net.minecraft.client.particle.SpriteSet; +import net.minecraft.core.particles.ParticleOptions; +import net.minecraft.core.particles.ParticleType; +import net.neoforged.neoforge.client.event.RegisterParticleProvidersEvent; + +import java.util.function.Consumer; + +public class ParticleProviderRegistryKubeEvent implements ClientKubeEvent { + + private final RegisterParticleProvidersEvent parent; + + public ParticleProviderRegistryKubeEvent(RegisterParticleProvidersEvent event) { + parent = event; + } + + public void register(ParticleType type, SpriteSetParticleProvider spriteProvider) { + parent.registerSpriteSet(type, spriteProvider); + } + + public void register(ParticleType type, Consumer particle) { + parent.registerSpriteSet(type, set -> (type1, level, x, y, z, xSpeed, ySpeed, zSpeed) -> { + var kube = new KubeAnimatedParticle(level, x, y, z, set); + kube.setParticleSpeed(xSpeed, ySpeed, zSpeed); + particle.accept(kube); + return kube; + }); + } + + public void register(ParticleType type) { + register(type, p -> {}); + } + + public void registerSpecial(ParticleType type, ParticleProvider provider) { + parent.registerSpecial(type, provider); + } + + @FunctionalInterface + public interface SpriteSetParticleProvider extends ParticleEngine.SpriteParticleRegistration { + Particle create(T type, ClientLevel clientLevel, double x, double y, double z, SpriteSet sprites, double xSpeed, double ySpeed, double zSpeed); + + default ParticleProvider create(SpriteSet sprites) { + return (type, level, x, y, z, xSpeed, ySpeed, zSpeed) -> create(type, level, x, y, z, sprites, xSpeed, ySpeed, zSpeed); + } + } +} diff --git a/src/main/java/dev/latvian/mods/kubejs/client/SoundsGenerator.java b/src/main/java/dev/latvian/mods/kubejs/client/SoundsGenerator.java new file mode 100644 index 000000000..0efaf8c7a --- /dev/null +++ b/src/main/java/dev/latvian/mods/kubejs/client/SoundsGenerator.java @@ -0,0 +1,171 @@ +package dev.latvian.mods.kubejs.client; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import net.minecraft.Util; +import net.minecraft.util.Mth; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; +import java.util.stream.Stream; + +public class SoundsGenerator { + + private final Map sounds = new HashMap<>(); + + public void addSound(String path, Consumer consumer, boolean overlayExisting) { + if (overlayExisting && sounds.containsKey(path)) { + consumer.accept(sounds.get(path)); + } else { + sounds.put(path, Util.make(new SoundGen(), consumer)); + } + } + + public void addSound(String path, Consumer consumer) { + addSound(path, consumer, false); + } + + public JsonObject toJson() { + var json = new JsonObject(); + sounds.forEach((path, gen) -> json.add(path, gen.toJson())); + return json; + } + + public static class SoundGen { + + private boolean replace = false; + @Nullable + private String subtitle; + private final List instances = new ArrayList<>(); + + public SoundGen replace(boolean b) { + replace = b; + return this; + } + + public SoundGen replace() { return replace(true); } + + public SoundGen subtitle(@Nullable String subtitle) { + this.subtitle = subtitle; + return this; + } + + public SoundGen sound(String file) { + instances.add(new SoundInstance(file)); + return this; + } + + public SoundGen sounds(String... sounds) { + instances.addAll(Stream.of(sounds).map(SoundInstance::new).toList()); + return this; + } + + public SoundGen sound(String file, Consumer consumer) { + instances.add(Util.make(new SoundInstance(file), consumer)); + return this; + } + + public JsonObject toJson() { + var json = new JsonObject(); + if (replace) { + json.addProperty("replace", true); + } + if (subtitle != null) { + json.addProperty("subtitle", subtitle); + } + if (!instances.isEmpty()) { + var array = new JsonArray(instances.size()); + instances.forEach(inst -> array.add(inst.toJson())); + json.add("sounds", array); + } + return json; + } + + } + + public static class SoundInstance { + + private final String fileLocation; + private boolean complex = false; + private float volume = 1.0F; + private float pitch = 1.0F; + private int weight = 1; + private boolean stream = false; + private int attenuationDistance = 16; + private boolean preload = false; + private boolean isEventReference = false; + + public SoundInstance(String fileLocation) { + this.fileLocation = fileLocation; + } + + private SoundInstance complex() { + complex = true; + return this; + } + + public SoundInstance volume(float f) { + volume = Mth.clamp(f, 0.0F, 1.0F); + return complex(); + } + + public SoundInstance pitch(float f) { + pitch = Mth.clamp(f, 0.0F, 1.0F); + return complex(); + } + + public SoundInstance weight(int i) { + weight = i; + return complex(); + } + + public SoundInstance stream(boolean b) { + stream = b; + return complex(); + } + + public SoundInstance stream() { return stream(true); } + + public SoundInstance attenuationDistance(int i) { + attenuationDistance = i; + return complex(); + } + + public SoundInstance preload(boolean b) { + preload = b; + return complex(); + } + + public SoundInstance preload() { return preload(true); } + + public SoundInstance asReferenceToEvent() { + isEventReference = true; + return complex(); + } + + public JsonElement toJson() { + if (!complex) { + return new JsonPrimitive(fileLocation.toString()); + } + + final JsonObject json = new JsonObject(); + json.addProperty("name", fileLocation.toString()); + json.addProperty("volume", volume); + json.addProperty("pitch", pitch); + json.addProperty("weight", weight); + json.addProperty("stream", stream); + json.addProperty("attenuation_distance", attenuationDistance); + json.addProperty("preload", preload); + if (isEventReference) { + json.addProperty("type", "event"); + } + return json; + } + } +} diff --git a/src/main/java/dev/latvian/mods/kubejs/core/ClientLevelKJS.java b/src/main/java/dev/latvian/mods/kubejs/core/ClientLevelKJS.java index c542f237d..2ea1b0df7 100644 --- a/src/main/java/dev/latvian/mods/kubejs/core/ClientLevelKJS.java +++ b/src/main/java/dev/latvian/mods/kubejs/core/ClientLevelKJS.java @@ -1,9 +1,11 @@ package dev.latvian.mods.kubejs.core; +import dev.latvian.mods.kubejs.client.KubeAnimatedParticle; import dev.latvian.mods.kubejs.player.EntityArrayList; import dev.latvian.mods.kubejs.script.ScriptType; import dev.latvian.mods.rhino.util.RemapPrefixForJS; import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.client.particle.SpriteSet; import net.minecraft.core.particles.ParticleOptions; @RemapPrefixForJS("kjs$") @@ -53,4 +55,8 @@ public interface ClientLevelKJS extends LevelKJS { } } } + + default KubeAnimatedParticle kubeParticle(double x, double y, double z, SpriteSet spriteSet) { + return new KubeAnimatedParticle(kjs$self(), x, y, z, spriteSet); + } } diff --git a/src/main/java/dev/latvian/mods/kubejs/generator/KubeAssetGenerator.java b/src/main/java/dev/latvian/mods/kubejs/generator/KubeAssetGenerator.java index 820e98292..876bf431e 100644 --- a/src/main/java/dev/latvian/mods/kubejs/generator/KubeAssetGenerator.java +++ b/src/main/java/dev/latvian/mods/kubejs/generator/KubeAssetGenerator.java @@ -3,8 +3,11 @@ import dev.latvian.mods.kubejs.client.LoadedTexture; import dev.latvian.mods.kubejs.client.ModelGenerator; import dev.latvian.mods.kubejs.client.MultipartBlockStateGenerator; +import dev.latvian.mods.kubejs.client.ParticleGenerator; +import dev.latvian.mods.kubejs.client.SoundsGenerator; import dev.latvian.mods.kubejs.client.VariantBlockStateGenerator; import dev.latvian.mods.kubejs.color.Color; +import dev.latvian.mods.kubejs.event.EventResult; import dev.latvian.mods.kubejs.script.ConsoleJS; import dev.latvian.mods.kubejs.script.data.GeneratedData; import net.minecraft.Util; @@ -130,4 +133,18 @@ default boolean mask(ResourceLocation target, ResourceLocation mask, ResourceLoc texture(target, in); return true; } + + default void particle(ResourceLocation id, Consumer consumer) { + json(ResourceLocation.fromNamespaceAndPath(id.getNamespace(), "particles/" + id.getPath()), Util.make(new ParticleGenerator(), consumer).toJson()); + } + + default void sounds(String namespace, Consumer consumer) {} + + default void buildSounds() {} + + @Override + default void afterPosted(EventResult result) { + KubeResourceGenerator.super.afterPosted(result); + buildSounds(); + } } diff --git a/src/main/java/dev/latvian/mods/kubejs/misc/ParticleTypeBuilder.java b/src/main/java/dev/latvian/mods/kubejs/misc/ParticleTypeBuilder.java index 0e16e9e20..b43495466 100644 --- a/src/main/java/dev/latvian/mods/kubejs/misc/ParticleTypeBuilder.java +++ b/src/main/java/dev/latvian/mods/kubejs/misc/ParticleTypeBuilder.java @@ -1,6 +1,8 @@ package dev.latvian.mods.kubejs.misc; import com.mojang.serialization.MapCodec; +import dev.latvian.mods.kubejs.client.ParticleGenerator; +import dev.latvian.mods.kubejs.generator.KubeAssetGenerator; import dev.latvian.mods.kubejs.registry.BuilderBase; import dev.latvian.mods.rhino.util.ReturnsSelf; import net.minecraft.core.particles.ParticleOptions; @@ -9,15 +11,20 @@ import net.minecraft.network.codec.StreamCodec; import net.minecraft.resources.ResourceLocation; +import java.util.List; +import java.util.function.Consumer; + @ReturnsSelf public class ParticleTypeBuilder extends BuilderBase> { public transient boolean overrideLimiter; public transient MapCodec codec; public transient StreamCodec streamCodec; + public transient Consumer assetGen; public ParticleTypeBuilder(ResourceLocation i) { super(i); overrideLimiter = false; + assetGen = gen -> gen.texture(id.toString()); } @Override @@ -43,4 +50,19 @@ public ParticleTypeBuilder streamCodec(StreamCodec textures) { + assetGen = g -> g.textures(textures); + return this; + } + + public ParticleTypeBuilder texture(String texture) { + assetGen = g -> g.texture(texture); + return this; + } + + @Override + public void generateAssets(KubeAssetGenerator generator) { + generator.particle(id, assetGen); + } } diff --git a/src/main/java/dev/latvian/mods/kubejs/misc/SoundEventBuilder.java b/src/main/java/dev/latvian/mods/kubejs/misc/SoundEventBuilder.java index bb7b59bd0..ab17313ea 100644 --- a/src/main/java/dev/latvian/mods/kubejs/misc/SoundEventBuilder.java +++ b/src/main/java/dev/latvian/mods/kubejs/misc/SoundEventBuilder.java @@ -1,16 +1,36 @@ package dev.latvian.mods.kubejs.misc; +import dev.latvian.mods.kubejs.client.SoundsGenerator; +import dev.latvian.mods.kubejs.generator.KubeAssetGenerator; import dev.latvian.mods.kubejs.registry.BuilderBase; +import dev.latvian.mods.rhino.util.ReturnsSelf; import net.minecraft.resources.ResourceLocation; import net.minecraft.sounds.SoundEvent; +import java.util.function.Consumer; + +@ReturnsSelf public class SoundEventBuilder extends BuilderBase { + + public transient Consumer assetGen; + public SoundEventBuilder(ResourceLocation i) { super(i); + assetGen = gen -> gen.sound(id.toString()).subtitle(id.toLanguageKey("sound")); + } + + public SoundEventBuilder sounds(Consumer gen) { + assetGen = gen; + return this; } @Override public SoundEvent createObject() { return SoundEvent.createVariableRangeEvent(id); } + + @Override + public void generateAssets(KubeAssetGenerator generator) { + generator.sounds(id.getNamespace(), g -> g.addSound(id.getPath(), assetGen)); + } } diff --git a/src/main/java/dev/latvian/mods/kubejs/script/data/VirtualAssetPack.java b/src/main/java/dev/latvian/mods/kubejs/script/data/VirtualAssetPack.java index e45bddd49..6068ccdf3 100644 --- a/src/main/java/dev/latvian/mods/kubejs/script/data/VirtualAssetPack.java +++ b/src/main/java/dev/latvian/mods/kubejs/script/data/VirtualAssetPack.java @@ -1,20 +1,26 @@ package dev.latvian.mods.kubejs.script.data; import dev.latvian.mods.kubejs.client.LoadedTexture; +import dev.latvian.mods.kubejs.client.SoundsGenerator; +import dev.latvian.mods.kubejs.event.EventResult; import dev.latvian.mods.kubejs.generator.KubeAssetGenerator; import dev.latvian.mods.kubejs.script.ScriptType; +import net.minecraft.Util; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.packs.PackType; import java.util.HashMap; import java.util.Map; +import java.util.function.Consumer; public class VirtualAssetPack extends VirtualResourcePack implements KubeAssetGenerator { private final Map loadedTextures; + private final Map sounds; public VirtualAssetPack(GeneratedDataStage stage) { super(ScriptType.CLIENT, PackType.CLIENT_RESOURCES, stage); loadedTextures = new HashMap<>(); + sounds = new HashMap<>(); } @Override @@ -36,4 +42,15 @@ public void close() { super.close(); loadedTextures.clear(); } + + @Override + public void sounds(String namespace, Consumer consumer) { + sounds.put(namespace, Util.make(new SoundsGenerator(), consumer)); + } + + @Override + public void buildSounds() { + sounds.forEach((mod, gen) -> json(ResourceLocation.fromNamespaceAndPath(mod, "sounds"), gen.toJson())); + sounds.clear(); + } }