diff --git a/build.gradle b/build.gradle index 9d68194..f35e7fe 100644 --- a/build.gradle +++ b/build.gradle @@ -94,7 +94,7 @@ repositories { dependencies { compileOnly "software.bernie.geckolib:geckolib-neoforge-${minecraft_version}:${geckolib_version}" - localRuntime "software.bernie.geckolib:geckolib-neoforge-${minecraft_version}:${geckolib_version}" +// localRuntime "software.bernie.geckolib:geckolib-neoforge-${minecraft_version}:${geckolib_version}" } tasks.withType(ProcessResources).configureEach { diff --git a/gradle.properties b/gradle.properties index dd3bd42..770f008 100644 --- a/gradle.properties +++ b/gradle.properties @@ -31,7 +31,7 @@ mod_name=ParticleStorm # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=LGPL-3.0 # The mod version. See https://semver.org/ -mod_version=1.0.8.7 +mod_version=1.1.4 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html @@ -41,9 +41,9 @@ mod_authors=Westernat # The description of the mod. This is a simple multiline text string that is used for display purposes in the mod list. mod_description=Uses a Bedrock Edition JSON format for particle effects. -geckolib_version=4.7.5.1 +geckolib_version=4.8.2 -# systemProp.http.proxyHost=localhost -# systemProp.http.proxyPort=7890 -# systemProp.https.proxyHost=localhost -# systemProp.https.proxyPort=7890 +#systemProp.http.proxyHost=localhost +#systemProp.http.proxyPort=7890 +#systemProp.https.proxyHost=localhost +#systemProp.https.proxyPort=7890 diff --git a/src/main/java/org/mesdag/particlestorm/PSGameClient.java b/src/main/java/org/mesdag/particlestorm/PSGameClient.java index d687f56..4c14122 100644 --- a/src/main/java/org/mesdag/particlestorm/PSGameClient.java +++ b/src/main/java/org/mesdag/particlestorm/PSGameClient.java @@ -14,7 +14,6 @@ import net.minecraft.client.renderer.texture.TextureAtlas; import net.minecraft.client.renderer.texture.TextureManager; import net.minecraft.util.Mth; -import net.minecraft.world.entity.EntityType; import net.neoforged.api.distmarker.Dist; import net.neoforged.bus.api.SubscribeEvent; import net.neoforged.fml.common.EventBusSubscriber; @@ -25,14 +24,11 @@ import net.neoforged.neoforge.client.event.RegisterClientReloadListenersEvent; import net.neoforged.neoforge.client.event.RenderLevelStageEvent; import net.neoforged.neoforge.common.NeoForge; -import org.jetbrains.annotations.NotNull; import org.mesdag.particlestorm.api.IComponent; import org.mesdag.particlestorm.api.IEventNode; -import org.mesdag.particlestorm.api.geckolib.ExampleBlockEntityRenderer; -import org.mesdag.particlestorm.api.geckolib.ReplacedCreeperRenderer; +import org.mesdag.particlestorm.api.geckolib.GeckoLibHelper; import org.mesdag.particlestorm.data.component.*; import org.mesdag.particlestorm.data.event.*; -import org.mesdag.particlestorm.mixin.ParticleEngineAccessor; import org.mesdag.particlestorm.particle.MolangParticleLoader; import org.mesdag.particlestorm.particle.ParticleEmitter; @@ -41,7 +37,7 @@ public final class PSGameClient { public static final MolangParticleLoader LOADER = new MolangParticleLoader(); public static final ParticleRenderType PARTICLE_ADD = new ParticleRenderType() { @Override - public BufferBuilder begin(Tesselator tesselator, @NotNull TextureManager textureManager) { + public BufferBuilder begin(Tesselator tesselator, TextureManager textureManager) { RenderSystem.enableDepthTest(); Minecraft.getInstance().gameRenderer.lightTexture().turnOnLightLayer(); RenderSystem.depthMask(false); @@ -57,10 +53,9 @@ public String toString() { }; @SubscribeEvent - public static void registerRenderers(final EntityRenderersEvent.RegisterRenderers event) { + public static void registerRenderers(EntityRenderersEvent.RegisterRenderers event) { if (ParticleStorm.DEBUG) { - event.registerBlockEntityRenderer(ParticleStorm.TEST_ENTITY.get(), ExampleBlockEntityRenderer::new); - event.registerEntityRenderer(EntityType.CREEPER, ReplacedCreeperRenderer::new); + GeckoLibHelper.registerRenderers(event); } } @@ -85,7 +80,7 @@ private static void tick(ClientTickEvent.Pre event) { LocalPlayer localPlayer = minecraft.player; if (localPlayer == null) { LOADER.removeAll(); - } else if (!minecraft.isPaused() && !localPlayer.level().tickRateManager().isFrozen()) { + } else if (!minecraft.isPaused() && localPlayer.level().tickRateManager().runsNormally()) { LOADER.tick(localPlayer); } } @@ -97,14 +92,14 @@ private static void renderLevelStage(RenderLevelStageEvent event) { float partialTicks = event.getPartialTick().getGameTimeDeltaPartialTick(true); PoseStack poseStack = event.getPoseStack(); MultiBufferSource.BufferSource bufferSource = minecraft.renderBuffers().bufferSource(); - for (ParticleEmitter emitter : LOADER.emitters.values()) { + for (ParticleEmitter emitter : LOADER.getEmitters()) { double x = Mth.lerp(partialTicks, emitter.posO.x, emitter.pos.x); double y = Mth.lerp(partialTicks, emitter.posO.y, emitter.pos.y); double z = Mth.lerp(partialTicks, emitter.posO.z, emitter.pos.z); - DebugRenderer.renderFloatingText(poseStack, bufferSource, emitter.getPreset().option.getId().toString(), x, y + 0.5, z, 0xFFFFFF); + DebugRenderer.renderFloatingText(poseStack, bufferSource, emitter.particleId.toString(), x, y + 0.5, z, 0xFFFFFF); DebugRenderer.renderFloatingText(poseStack, bufferSource, "id: " + emitter.id, x, y + 0.3, z, 0xFFFFFF); - int maxNum = ((ParticleEngineAccessor) minecraft.particleEngine).trackedParticleCounts().getInt(emitter.particleGroup); - DebugRenderer.renderFloatingText(poseStack, bufferSource, "particles: " + maxNum, x, y + 0.1, z, maxNum == emitter.particleGroup.getLimit() ? 0xFF0000 : 0xFFFFFF); + int maxNum = minecraft.particleEngine.trackedParticleCounts.getInt(emitter.particleGroup); + DebugRenderer.renderFloatingText(poseStack, bufferSource, "particles: " + maxNum, x, y + 0.1, z, maxNum >= emitter.particleGroup.getLimit() ? 0xFF0000 : 0xFFFFFF); Camera camera = event.getCamera(); double d0 = camera.getPosition().x; double d1 = camera.getPosition().y; @@ -169,5 +164,6 @@ private static void registerEventNodes() { IEventNode.register("particle_effect", ParticleEffect.CODEC.codec()); IEventNode.register("sound_effect", SoundEffect.CODEC.codec()); IEventNode.register("expression", NodeMolangExp.CODEC); + IEventNode.register("log", EventLog.CODEC); } } diff --git a/src/main/java/org/mesdag/particlestorm/PSModClient.java b/src/main/java/org/mesdag/particlestorm/PSModClient.java new file mode 100644 index 0000000..4fc4fc1 --- /dev/null +++ b/src/main/java/org/mesdag/particlestorm/PSModClient.java @@ -0,0 +1,17 @@ +package org.mesdag.particlestorm; + +import net.neoforged.api.distmarker.Dist; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.common.EventBusSubscriber; +import org.mesdag.particlestorm.api.RegisterCustomParticleTypeEvent; +import org.mesdag.particlestorm.particle.MolangParticleInstance; + +@EventBusSubscriber(modid = ParticleStorm.MODID, bus = EventBusSubscriber.Bus.MOD, value = Dist.CLIENT) +public final class PSModClient { + @SubscribeEvent + public static void registerCustomParticleType(RegisterCustomParticleTypeEvent event) { + event.registerWithSprites(ParticleStorm.MOLANG, (emitter, particlePreset, level, x, y, z, sprites) -> + new MolangParticleInstance(particlePreset, level, x, y, z, sprites) + ); + } +} diff --git a/src/main/java/org/mesdag/particlestorm/ParticleStorm.java b/src/main/java/org/mesdag/particlestorm/ParticleStorm.java index e4a27fc..6ee9648 100644 --- a/src/main/java/org/mesdag/particlestorm/ParticleStorm.java +++ b/src/main/java/org/mesdag/particlestorm/ParticleStorm.java @@ -3,18 +3,17 @@ import com.mojang.datafixers.util.Either; import com.mojang.serialization.Codec; import com.mojang.serialization.MapCodec; +import io.netty.buffer.ByteBuf; import net.minecraft.core.particles.ParticleType; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.nbt.CompoundTag; -import net.minecraft.network.RegistryFriendlyByteBuf; import net.minecraft.network.codec.StreamCodec; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.level.ServerPlayer; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.entity.BlockEntityType; import net.neoforged.bus.api.IEventBus; import net.neoforged.fml.ModContainer; import net.neoforged.fml.common.Mod; +import net.neoforged.fml.loading.LoadingModList; import net.neoforged.neoforge.common.NeoForge; import net.neoforged.neoforge.event.RegisterCommandsEvent; import net.neoforged.neoforge.event.entity.player.PlayerEvent; @@ -23,8 +22,7 @@ import net.neoforged.neoforge.network.registration.PayloadRegistrar; import net.neoforged.neoforge.registries.DeferredHolder; import net.neoforged.neoforge.registries.DeferredRegister; -import org.jetbrains.annotations.NotNull; -import org.mesdag.particlestorm.api.geckolib.TestBlock; +import org.mesdag.particlestorm.api.geckolib.GeckoLibHelper; import org.mesdag.particlestorm.network.EmitterAttachPacketS2C; import org.mesdag.particlestorm.network.EmitterCreationPacketS2C; import org.mesdag.particlestorm.network.EmitterRemovalPacket; @@ -44,29 +42,18 @@ public final class ParticleStorm { public static final String MODID = "particlestorm"; public static final Logger LOGGER = LoggerFactory.getLogger("ParticleStorm"); - public static final boolean DEBUG = Boolean.getBoolean("particlestorm.debug"); + public static final boolean DEBUG = Boolean.getBoolean("particlestorm.debug") && LoadingModList.get().getModFileById("geckolib") != null; - public static final DeferredRegister> PARTICLE = DeferredRegister.create(BuiltInRegistries.PARTICLE_TYPE, MODID); - public static final DeferredHolder, ParticleType> MOLANG = PARTICLE.register("molang", () -> new ParticleType(false) { - @Override - public @NotNull MapCodec codec() { - return MolangParticleOption.codec(this); - } - - @Override - public @NotNull StreamCodec streamCodec() { - return MolangParticleOption.streamCodec(this); - } - }); - public static final Codec> STRING_LIST_CODEC = Codec.either(Codec.STRING, Codec.list(Codec.STRING)).xmap( + private static final DeferredRegister> REGISTER = DeferredRegister.create(BuiltInRegistries.PARTICLE_TYPE, MODID); + public static final DeferredHolder, ParticleType> MOLANG = registerParticleType(REGISTER, "molang"); + public static final Codec> STRING_LIST_CODEC = Codec.either(Codec.STRING, Codec.STRING.listOf()).xmap( either -> either.map(Collections::singletonList, Function.identity()), l -> l.size() == 1 ? Either.left(l.getFirst()) : Either.right(l) ); - public static DeferredHolder, BlockEntityType> TEST_ENTITY; public ParticleStorm(IEventBus bus, ModContainer container) { PSClientConfigs.register(container); - PARTICLE.register(bus); + REGISTER.register(bus); registerGeoTest(bus); bus.addListener(ParticleStorm::registerPayloadHandlers); NeoForge.EVENT_BUS.addListener(ParticleStorm::registerCommands); @@ -75,26 +62,10 @@ public ParticleStorm(IEventBus bus, ModContainer container) { private static void registerPayloadHandlers(RegisterPayloadHandlersEvent event) { PayloadRegistrar registrar = event.registrar("1"); - registrar.playToClient( - EmitterCreationPacketS2C.TYPE, - EmitterCreationPacketS2C.STREAM_CODEC, - EmitterCreationPacketS2C::handle - ); - registrar.playToClient( - EmitterAttachPacketS2C.TYPE, - EmitterAttachPacketS2C.STREAM_CODEC, - EmitterAttachPacketS2C::handle - ); - registrar.playBidirectional( - EmitterRemovalPacket.TYPE, - EmitterRemovalPacket.STREAM_CODEC, - EmitterRemovalPacket::handle - ); - registrar.playBidirectional( - EmitterSynchronizePacket.TYPE, - EmitterSynchronizePacket.STREAM_CODEC, - EmitterSynchronizePacket::handle - ); + registrar.playToClient(EmitterCreationPacketS2C.TYPE, EmitterCreationPacketS2C.STREAM_CODEC, EmitterCreationPacketS2C::handle); + registrar.playToClient(EmitterAttachPacketS2C.TYPE, EmitterAttachPacketS2C.STREAM_CODEC, EmitterAttachPacketS2C::handle); + registrar.playBidirectional(EmitterRemovalPacket.TYPE, EmitterRemovalPacket.STREAM_CODEC, EmitterRemovalPacket::handle); + registrar.playBidirectional(EmitterSynchronizePacket.TYPE, EmitterSynchronizePacket.STREAM_CODEC, EmitterSynchronizePacket::handle); } private static void registerCommands(RegisterCommandsEvent event) { @@ -115,16 +86,25 @@ private static void playerLoggedIn(PlayerEvent.PlayerLoggedInEvent event) { private static void registerGeoTest(IEventBus bus) { if (DEBUG) { - DeferredRegister BLOCK = DeferredRegister.create(BuiltInRegistries.BLOCK, MODID); - DeferredRegister> ENTITY = DeferredRegister.create(BuiltInRegistries.BLOCK_ENTITY_TYPE, MODID); - DeferredHolder TEST = BLOCK.register("test_block", TestBlock::new); - TEST_ENTITY = ENTITY.register("test_entity", () -> BlockEntityType.Builder.of(TestBlock.Entity::new, TEST.get()).build(null)); - BLOCK.register(bus); - ENTITY.register(bus); + GeckoLibHelper.registerStuffs(bus); } } public static ResourceLocation asResource(String path) { return ResourceLocation.fromNamespaceAndPath(MODID, path); } + + public static DeferredHolder, ParticleType> registerParticleType(DeferredRegister> register, String name) { + return register.register(name, () -> new ParticleType<>(false) { + @Override + public MapCodec codec() { + return MolangParticleOption.CODEC; + } + + @Override + public StreamCodec streamCodec() { + return MolangParticleOption.STREAM_CODEC; + } + }); + } } diff --git a/src/main/java/org/mesdag/particlestorm/api/IComponent.java b/src/main/java/org/mesdag/particlestorm/api/IComponent.java index 9e6c028..6858ec6 100644 --- a/src/main/java/org/mesdag/particlestorm/api/IComponent.java +++ b/src/main/java/org/mesdag/particlestorm/api/IComponent.java @@ -23,6 +23,7 @@ static void register(String vanillaPath, Codec codec) { List getAllMolangExp(); + /// @return <= 0 means early initialize default int order() { return 1000; } diff --git a/src/main/java/org/mesdag/particlestorm/api/IEventNode.java b/src/main/java/org/mesdag/particlestorm/api/IEventNode.java index 642d01b..975847e 100644 --- a/src/main/java/org/mesdag/particlestorm/api/IEventNode.java +++ b/src/main/java/org/mesdag/particlestorm/api/IEventNode.java @@ -19,6 +19,7 @@ static Codec getCodec(String name) { return codec; } + @SuppressWarnings("unchecked") static void register(String name, Codec codec) { MAP.put(name, (Codec) codec); } diff --git a/src/main/java/org/mesdag/particlestorm/api/IMolangParticleInstance.java b/src/main/java/org/mesdag/particlestorm/api/IMolangParticleInstance.java new file mode 100644 index 0000000..01cc805 --- /dev/null +++ b/src/main/java/org/mesdag/particlestorm/api/IMolangParticleInstance.java @@ -0,0 +1,141 @@ +package org.mesdag.particlestorm.api; + +import net.minecraft.client.particle.Particle; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.core.particles.ParticleGroup; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.phys.Vec3; +import org.jetbrains.annotations.Nullable; +import org.joml.Vector3f; +import org.mesdag.particlestorm.particle.ParticleEmitter; +import org.mesdag.particlestorm.particle.ParticlePreset; + +import java.util.List; + +public interface IMolangParticleInstance extends MolangInstance { + default Particle self() { + return (Particle) this; + } + + int getAge(); + + void setEmitter(ParticleEmitter emitter); + + ParticlePreset getPreset(); + + @Nullable TextureAtlasSprite getSprite(); + + Vector3f getAcceleration(); + + Vector3f getFacingDirection(); + + Vector3f getInitialSpeed(); + + void setXRot(float x); + + void setYRot(float y); + + void setZRot(float z); + + void setZRotD(float delta); + + float getZRotD(); + + void setCollisionDrag(float drag); + + void setCoefficientOfRestitution(float coefficient); + + void setExpireOnContact(boolean b); + + void setComponents(List components); + + float getScaleU(); + + float getScaleV(); + + void setBillboardSize(float[] size); + + void setUvSize(float[] size); + + float[] getUvSize(); + + void setUvStep(float[] step); + + float[] getUvStep(); + + void setMaxFrame(int frame); + + int getMaxFrame(); + + void setCurrentFrame(int frame); + + int getCurrentFrame(); + + void setInsideKillPlane(boolean b); + + boolean isInsideKillPlane(); + + void setParticleGroup(ParticleGroup group); + + void setLastTimeline(int last); + + int getLastTimeline(); + + double getXd(); + + double getYd(); + + double getZd(); + + double getX(); + + double getY(); + + double getZ(); + + void setPosO(double x, double y, double z); + + void setColor(float red, float green, float blue, float alpha); + + void setUV(float u, float v, float w, float h); + + void setCollision(boolean bool); + + // region default + default void moveDirectly(double x, double y, double z) { + self().setBoundingBox(self().getBoundingBox().move(x, y, z)); + self().setLocationFromBoundingbox(); + } + + @Override + default float tickAge() { + return getAge() * getInvTickRate(); + } + + @Override + default float tickLifetime() { + return self().getLifetime() * getInvTickRate(); + } + + @Override + default ResourceLocation getIdentity() { + return getEmitter().particleId; + } + + @Override + default Vec3 getPosition() { + return self().getPos(); + } + + @Override + default @Nullable Entity getAttachedEntity() { + return getEmitter().getAttachedEntity(); + } + + @Override + default float getInvTickRate() { + return getEmitter().invTickRate; + } + // endregion +} diff --git a/src/main/java/org/mesdag/particlestorm/api/IParticleComponent.java b/src/main/java/org/mesdag/particlestorm/api/IParticleComponent.java index d1b4792..ac63ea3 100644 --- a/src/main/java/org/mesdag/particlestorm/api/IParticleComponent.java +++ b/src/main/java/org/mesdag/particlestorm/api/IParticleComponent.java @@ -1,12 +1,11 @@ package org.mesdag.particlestorm.api; import net.minecraft.world.level.Level; -import org.mesdag.particlestorm.particle.MolangParticleInstance; public interface IParticleComponent extends IComponent { - default void update(MolangParticleInstance instance) {} + default void update(IMolangParticleInstance instance) {} - default void apply(MolangParticleInstance instance) {} + default void apply(IMolangParticleInstance instance) {} default boolean requireUpdate() { return false; diff --git a/src/main/java/org/mesdag/particlestorm/api/MolangParticleLoadEvent.java b/src/main/java/org/mesdag/particlestorm/api/MolangParticleLoadEvent.java new file mode 100644 index 0000000..72458ea --- /dev/null +++ b/src/main/java/org/mesdag/particlestorm/api/MolangParticleLoadEvent.java @@ -0,0 +1,32 @@ +package org.mesdag.particlestorm.api; + +import net.neoforged.bus.api.Event; +import net.neoforged.fml.event.IModBusEvent; + +import java.util.concurrent.Executor; + +public abstract class MolangParticleLoadEvent extends Event implements IModBusEvent { + private final Executor executor; + + public MolangParticleLoadEvent(Executor executor) { + this.executor = executor; + } + + public Executor getExecutor() { + return executor; + } + + /// In Background Executor {@link net.minecraft.Util#backgroundExecutor()} + public static class Pre extends MolangParticleLoadEvent { + public Pre(Executor executor) { + super(executor); + } + } + + /// In Game Executor {@link net.minecraft.client.Minecraft} + public static class Post extends MolangParticleLoadEvent { + public Post(Executor executor) { + super(executor); + } + } +} diff --git a/src/main/java/org/mesdag/particlestorm/api/ParticlePresetLoadedEvent.java b/src/main/java/org/mesdag/particlestorm/api/ParticlePresetLoadedEvent.java new file mode 100644 index 0000000..a889155 --- /dev/null +++ b/src/main/java/org/mesdag/particlestorm/api/ParticlePresetLoadedEvent.java @@ -0,0 +1,16 @@ +package org.mesdag.particlestorm.api; + +import net.neoforged.bus.api.Event; +import org.mesdag.particlestorm.particle.ParticlePreset; + +public class ParticlePresetLoadedEvent extends Event { + private final ParticlePreset preset; + + public ParticlePresetLoadedEvent(ParticlePreset preset) { + this.preset = preset; + } + + public ParticlePreset getPreset() { + return preset; + } +} diff --git a/src/main/java/org/mesdag/particlestorm/api/RegisterCustomEmitterTypeEvent.java b/src/main/java/org/mesdag/particlestorm/api/RegisterCustomEmitterTypeEvent.java new file mode 100644 index 0000000..5ccbbaa --- /dev/null +++ b/src/main/java/org/mesdag/particlestorm/api/RegisterCustomEmitterTypeEvent.java @@ -0,0 +1,42 @@ +package org.mesdag.particlestorm.api; + +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.Tag; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.Level; +import net.neoforged.bus.api.Event; +import net.neoforged.fml.ModLoader; +import net.neoforged.fml.event.IModBusEvent; +import org.mesdag.particlestorm.particle.ParticleEmitter; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.function.BiFunction; + +public class RegisterCustomEmitterTypeEvent extends Event implements IModBusEvent { + public static final String TYPE_KEY = "type"; + private static Map> map; + + private RegisterCustomEmitterTypeEvent() {} + + public static void postEvent() { + map = new HashMap<>(); + ModLoader.postEvent(new RegisterCustomEmitterTypeEvent()); + } + + public void register(ResourceLocation id, BiFunction factory) { + map.put(id, factory); + } + + public static ParticleEmitter create(Level level, CompoundTag tag) { + if (tag.contains(TYPE_KEY, Tag.TAG_STRING)) { + ResourceLocation id = ResourceLocation.tryParse(Objects.requireNonNull(tag.get(TYPE_KEY)).getAsString()); + BiFunction factory = map.get(id); + if (factory != null) { + return factory.apply(level, tag); + } + } + return new ParticleEmitter(level, tag); + } +} diff --git a/src/main/java/org/mesdag/particlestorm/api/RegisterCustomParticleTypeEvent.java b/src/main/java/org/mesdag/particlestorm/api/RegisterCustomParticleTypeEvent.java new file mode 100644 index 0000000..3b6f652 --- /dev/null +++ b/src/main/java/org/mesdag/particlestorm/api/RegisterCustomParticleTypeEvent.java @@ -0,0 +1,83 @@ +package org.mesdag.particlestorm.api; + +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.client.particle.Particle; +import net.minecraft.client.particle.ParticleEngine; +import net.minecraft.core.Holder; +import net.minecraft.core.particles.ParticleType; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.resources.ResourceLocation; +import net.neoforged.bus.api.Event; +import net.neoforged.fml.ModLoader; +import net.neoforged.fml.event.IModBusEvent; +import org.jetbrains.annotations.Contract; +import org.mesdag.particlestorm.PSGameClient; +import org.mesdag.particlestorm.particle.ExtendMutableSpriteSet; +import org.mesdag.particlestorm.particle.ParticleEmitter; +import org.mesdag.particlestorm.particle.ParticlePreset; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +public class RegisterCustomParticleTypeEvent extends Event implements IModBusEvent { + private static Map, Provider> map; + private static ExtendMutableSpriteSet sprites; + private final Map spriteSets; + + private RegisterCustomParticleTypeEvent(Map spriteSets) { + this.spriteSets = spriteSets; + } + + public void register(ParticleType type, Provider provider) { + map.put(type, provider); + } + + public void registerWithSprites(ParticleType type, ProviderWithSprites provider) { + spriteSets.put(Objects.requireNonNull(BuiltInRegistries.PARTICLE_TYPE.getKey(type)), sprites); + register(type, provider); + } + + public void registerWithSprites(Holder> holder, ProviderWithSprites provider) { + spriteSets.put(holder.unwrapKey().orElseThrow().location(), sprites); + register(holder.value(), provider); + } + + public static void postEvent(Map spriteSets) { + map = new HashMap<>(); + sprites = new ExtendMutableSpriteSet(); + ModLoader.postEvent(new RegisterCustomParticleTypeEvent(spriteSets)); + } + + public static V createParticle(ParticleEmitter emitter) { + Provider provider = map.get(emitter.getPreset().type); + if (provider == null) { + throw new NullPointerException("Provider from '" + BuiltInRegistries.PARTICLE_TYPE.getKey(emitter.getPreset().type) + "' is not registered"); + } + + return (V) provider.create(emitter, PSGameClient.LOADER.id2Particle().get(emitter.particleId), (ClientLevel) emitter.level, emitter.getX(), emitter.getY(), emitter.getZ(), sprites); + } + + @FunctionalInterface + public interface Provider { + V create(ParticleEmitter emitter, ParticlePreset particlePreset, ClientLevel level, double x, double y, double z); + + /// @see Provider#create(ParticleEmitter, ParticlePreset, ClientLevel, double, double, double) + default V create(ParticleEmitter emitter, ParticlePreset particlePreset, ClientLevel level, double x, double y, double z, ExtendMutableSpriteSet sprites) { + return create(emitter, particlePreset, level, x, y, z); + } + } + + @FunctionalInterface + public interface ProviderWithSprites extends Provider { + /// @see ProviderWithSprites#create(ParticleEmitter, ParticlePreset, ClientLevel, double, double, double, ExtendMutableSpriteSet) + @Contract("_, _, _, _, _, _ -> fail") + @Override + default V create(ParticleEmitter emitter, ParticlePreset particlePreset, ClientLevel level, double x, double y, double z) { + throw new UnsupportedOperationException(); + } + + @Override + V create(ParticleEmitter emitter, ParticlePreset particlePreset, ClientLevel level, double x, double y, double z, ExtendMutableSpriteSet sprites); + } +} diff --git a/src/main/java/org/mesdag/particlestorm/api/geckolib/GeckoLibHelper.java b/src/main/java/org/mesdag/particlestorm/api/geckolib/GeckoLibHelper.java index fc2c52b..f42d1a8 100644 --- a/src/main/java/org/mesdag/particlestorm/api/geckolib/GeckoLibHelper.java +++ b/src/main/java/org/mesdag/particlestorm/api/geckolib/GeckoLibHelper.java @@ -1,13 +1,22 @@ package org.mesdag.particlestorm.api.geckolib; import it.unimi.dsi.fastutil.ints.IntIterator; +import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EntityType; import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.phys.Vec3; +import net.neoforged.bus.api.IEventBus; +import net.neoforged.neoforge.client.event.EntityRenderersEvent; +import net.neoforged.neoforge.registries.DeferredHolder; +import net.neoforged.neoforge.registries.DeferredRegister; import org.joml.Vector3f; import org.mesdag.particlestorm.PSGameClient; +import org.mesdag.particlestorm.ParticleStorm; import org.mesdag.particlestorm.data.molang.MolangExp; import org.mesdag.particlestorm.data.molang.VariableTable; import org.mesdag.particlestorm.mixed.*; @@ -15,7 +24,6 @@ import software.bernie.geckolib.animatable.GeoAnimatable; import software.bernie.geckolib.animatable.instance.AnimatableInstanceCache; import software.bernie.geckolib.animation.AnimationController; -import software.bernie.geckolib.animation.keyframe.event.ParticleKeyframeEvent; import software.bernie.geckolib.animation.keyframe.event.data.ParticleKeyframeData; import software.bernie.geckolib.cache.object.GeoBone; import software.bernie.geckolib.loading.json.raw.LocatorValue; @@ -23,6 +31,22 @@ import java.util.List; public final class GeckoLibHelper { + public static DeferredHolder, BlockEntityType> TEST_ENTITY; + + public static void registerStuffs(IEventBus bus) { + DeferredRegister BLOCK = DeferredRegister.create(BuiltInRegistries.BLOCK, ParticleStorm.MODID); + DeferredRegister> ENTITY = DeferredRegister.create(BuiltInRegistries.BLOCK_ENTITY_TYPE, ParticleStorm.MODID); + DeferredHolder TEST = BLOCK.register("test_block", TestBlock::new); + TEST_ENTITY = ENTITY.register("test_entity", () -> BlockEntityType.Builder.of(TestBlock.Entity::new, TEST.get()).build(null)); + BLOCK.register(bus); + ENTITY.register(bus); + } + + public static void registerRenderers(EntityRenderersEvent.RegisterRenderers event) { + event.registerBlockEntityRenderer(GeckoLibHelper.TEST_ENTITY.get(), ExampleBlockEntityRenderer::new); + event.registerEntityRenderer(EntityType.CREEPER, ReplacedCreeperRenderer::new); + } + public static double[] getLocatorOffset(Object locatorValue) { LocatorValue value = (LocatorValue) locatorValue; if (value.locatorClass() == null) { @@ -42,18 +66,17 @@ public static double[] getLocatorRotation(Object locatorValue) { /** * @return true means failed to add emitter */ - public static boolean processParticleEffect(Object particleKeyframeEvent) { - ParticleKeyframeEvent event = (ParticleKeyframeEvent) particleKeyframeEvent; - List bones = ((IAnimationController) event.getController()).particlestorm$getBonesWhichHasLocators(); + public static boolean processParticleEffect(Object a, Object c, Object d) { + List bones = IAnimationController.of((AnimationController) c).particlestorm$getBonesWhichHasLocators(); if (bones.isEmpty()) return true; - ParticleKeyframeData keyframeData = event.getKeyframeData(); + ParticleKeyframeData keyframeData = (ParticleKeyframeData) d; IParticleKeyframeData iData = (IParticleKeyframeData) keyframeData; Entity entity = null; BlockEntity blockEntity = null; VariableTable variableTable; Level level; - GeoAnimatable animatable = event.getAnimatable(); + GeoAnimatable animatable = (GeoAnimatable) a; switch (animatable) { case Entity entity1 -> { entity = entity1; @@ -108,8 +131,8 @@ public static void setCurrentEntity(Object animatable, Entity entity) { } } - public static void removeEmittersWhenAnimationChange(int size, Object animationState, Object animatableInstanceCache) { - if (size > 0 && animationState == AnimationController.State.TRANSITIONING) { + public static void removeEmittersWhenAnimationChange(Object animationState, Object animatableInstanceCache) { + if (animationState == AnimationController.State.TRANSITIONING) { IntIterator iterator = IAnimatableInstanceCache.of((AnimatableInstanceCache) animatableInstanceCache).particlestorm$getCachedId().values().iterator(); while (iterator.hasNext()) { PSGameClient.LOADER.removeEmitter(iterator.nextInt(), false); diff --git a/src/main/java/org/mesdag/particlestorm/api/geckolib/TestBlock.java b/src/main/java/org/mesdag/particlestorm/api/geckolib/TestBlock.java index 9bdc501..a28ed1d 100644 --- a/src/main/java/org/mesdag/particlestorm/api/geckolib/TestBlock.java +++ b/src/main/java/org/mesdag/particlestorm/api/geckolib/TestBlock.java @@ -10,9 +10,7 @@ import net.minecraft.world.phys.shapes.CollisionContext; import net.minecraft.world.phys.shapes.Shapes; import net.minecraft.world.phys.shapes.VoxelShape; -import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.mesdag.particlestorm.ParticleStorm; import software.bernie.geckolib.animatable.GeoBlockEntity; import software.bernie.geckolib.animatable.instance.AnimatableInstanceCache; import software.bernie.geckolib.animation.AnimatableManager; @@ -28,17 +26,17 @@ public TestBlock() { } @Override - public @Nullable BlockEntity newBlockEntity(@NotNull BlockPos pos, @NotNull BlockState state) { + public @Nullable BlockEntity newBlockEntity(BlockPos pos, BlockState state) { return new Entity(pos, state); } @Override - public @NotNull RenderShape getRenderShape(@NotNull BlockState state) { + public RenderShape getRenderShape(BlockState state) { return RenderShape.ENTITYBLOCK_ANIMATED; } @Override - protected @NotNull VoxelShape getShape(@NotNull BlockState state, @NotNull BlockGetter level, @NotNull BlockPos pos, @NotNull CollisionContext context) { + protected VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) { return BOX; } @@ -48,7 +46,7 @@ public static class Entity extends BlockEntity implements GeoBlockEntity { private final AnimatableInstanceCache cache = GeckoLibUtil.createInstanceCache(this); public Entity(BlockPos pos, BlockState state) { - super(ParticleStorm.TEST_ENTITY.get(), pos, state); + super(GeckoLibHelper.TEST_ENTITY.get(), pos, state); } @Override diff --git a/src/main/java/org/mesdag/particlestorm/api/geckolib/package-info.java b/src/main/java/org/mesdag/particlestorm/api/geckolib/package-info.java new file mode 100644 index 0000000..22df274 --- /dev/null +++ b/src/main/java/org/mesdag/particlestorm/api/geckolib/package-info.java @@ -0,0 +1,7 @@ +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault +package org.mesdag.particlestorm.api.geckolib; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/src/main/java/org/mesdag/particlestorm/api/package-info.java b/src/main/java/org/mesdag/particlestorm/api/package-info.java new file mode 100644 index 0000000..1cd5279 --- /dev/null +++ b/src/main/java/org/mesdag/particlestorm/api/package-info.java @@ -0,0 +1,7 @@ +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault +package org.mesdag.particlestorm.api; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/src/main/java/org/mesdag/particlestorm/data/DuplicateFieldDecoder.java b/src/main/java/org/mesdag/particlestorm/data/DuplicateFieldDecoder.java index 13d211c..5c32971 100644 --- a/src/main/java/org/mesdag/particlestorm/data/DuplicateFieldDecoder.java +++ b/src/main/java/org/mesdag/particlestorm/data/DuplicateFieldDecoder.java @@ -1,67 +1,91 @@ package org.mesdag.particlestorm.data; -import com.google.common.collect.Sets; import com.mojang.serialization.*; -import com.mojang.serialization.codecs.FieldEncoder; +import net.neoforged.neoforge.common.util.NeoForgeExtraCodecs; +import java.util.Arrays; import java.util.Objects; +import java.util.Optional; import java.util.Set; import java.util.stream.Stream; -public class DuplicateFieldDecoder extends MapDecoder.Implementation { - protected final Set names; - private final Decoder elementCodec; +public class DuplicateFieldDecoder { + @Deprecated + public static MapCodec fieldOf(String defaultName, Set names, Codec codec) { + return fieldOf(codec, defaultName, names.toArray(new String[0])); + } - public DuplicateFieldDecoder(final Set names, final Decoder elementCodec) { - this.names = names; - this.elementCodec = elementCodec; + @Deprecated + public static MapCodec fieldOf(String defaultName, String another, Codec codec) { + return fieldOf(codec, defaultName, another); } - public static MapCodec fieldOf(String defaultName, Set names, Codec codec) { - Set set = Sets.newHashSet(names); - set.add(defaultName); - return MapCodec.of(new FieldEncoder<>(defaultName, codec), new DuplicateFieldDecoder<>(set, codec)); + public static MapCodec fieldOf(Codec codec, String defaultName, String... alias) { + String[] names = new String[alias.length + 1]; + names[0] = defaultName; + System.arraycopy(alias, 0, names, 1, alias.length); + return NeoForgeExtraCodecs.aliasedFieldOf(codec, names); } - public static MapCodec fieldOf(String defaultName, String another, Codec codec) { - return MapCodec.of(new FieldEncoder<>(defaultName, codec), new DuplicateFieldDecoder<>(Set.of(defaultName, another), codec)); + public static MapCodec> optionalFieldOf(Codec codec, String... names) { + return new AliasOptionalFieldCodec<>(codec, names); } - @Override - public DataResult decode(final DynamicOps ops, final MapLike input) { - for (String name : names) { - final T value = input.get(name); - if (value != null) { - return elementCodec.parse(ops, value); + public static class AliasOptionalFieldCodec extends MapCodec> { + private final Codec elementCodec; + private final String[] names; + + public AliasOptionalFieldCodec(Codec elementCodec, String[] names) { + this.elementCodec = elementCodec; + this.names = Arrays.stream(names).distinct().sorted().toArray(String[]::new); + } + + @Override + public DataResult> decode(DynamicOps ops, MapLike input) { + for (String name : names) { + T t = input.get(name); + if (t == null) continue; + DataResult parsed = elementCodec.parse(ops, t); + if (parsed.isSuccess()) { + return parsed.map(Optional::of).setPartial(parsed.resultOrPartial()); + } } + return DataResult.success(Optional.empty()); } - return DataResult.error(() -> "No key " + names + " in " + input); - } - @Override - public Stream keys(final DynamicOps ops) { - return names.stream().map(ops::createString); - } + @Override + public RecordBuilder encode(Optional input, DynamicOps ops, RecordBuilder prefix) { + if (input.isEmpty()) return prefix; + return prefix.add(names[0], elementCodec.encodeStart(ops, input.get())); + } - @Override - public boolean equals(final Object o) { - if (this == o) { - return true; + @Override + public Stream keys(DynamicOps ops) { + return Arrays.stream(names).map(ops::createString); } - if (o == null || getClass() != o.getClass()) { - return false; + + @Override + public boolean equals(Object o) { + return o == this || ( + o instanceof AliasOptionalFieldCodec that && + Arrays.equals(names, that.names) && + elementCodec.equals(that.elementCodec) + ); } - final DuplicateFieldDecoder that = (DuplicateFieldDecoder) o; - return Objects.equals(names, that.names) && Objects.equals(elementCodec, that.elementCodec); - } - @Override - public int hashCode() { - return Objects.hash(names, elementCodec); - } + @Override + public int hashCode() { + int result = Objects.hashCode(names); + result = 31 * result + Objects.hashCode(elementCodec); + return result; + } - @Override - public String toString() { - return "DuplicateFieldDecoder[" + names + ": " + elementCodec + ']'; + @Override + public String toString() { + return "AliasOptionalFieldCodec{" + + "names='" + Arrays.toString(names) + '\'' + + ", elementCodec=" + elementCodec + + '}'; + } } } diff --git a/src/main/java/org/mesdag/particlestorm/data/component/EmitterInitialization.java b/src/main/java/org/mesdag/particlestorm/data/component/EmitterInitialization.java index 9ea73f5..06df93e 100644 --- a/src/main/java/org/mesdag/particlestorm/data/component/EmitterInitialization.java +++ b/src/main/java/org/mesdag/particlestorm/data/component/EmitterInitialization.java @@ -8,12 +8,10 @@ import java.util.List; -/** - * This component allows the emitter to run some Molang at creation, primarily to populate any Molang variables that get used later. - * - * @param creationExpression This is run once at emitter startup - * @param perUpdateExpression This is run once per emitter update - */ +/// This component allows the emitter to run some Molang at creation, primarily to populate any Molang variables that get used later. +/// +/// @param creationExpression This is run once at emitter startup +/// @param perUpdateExpression This is run once per emitter update public record EmitterInitialization(MolangExp creationExpression, MolangExp perUpdateExpression) implements IEmitterComponent { public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( MolangExp.CODEC.fieldOf("creation_expression").orElse(MolangExp.EMPTY).forGetter(EmitterInitialization::creationExpression), diff --git a/src/main/java/org/mesdag/particlestorm/data/component/EmitterLifetime.java b/src/main/java/org/mesdag/particlestorm/data/component/EmitterLifetime.java index e621119..4195334 100644 --- a/src/main/java/org/mesdag/particlestorm/data/component/EmitterLifetime.java +++ b/src/main/java/org/mesdag/particlestorm/data/component/EmitterLifetime.java @@ -10,11 +10,9 @@ import java.util.List; public abstract sealed class EmitterLifetime implements IEmitterComponent permits EmitterLifetime.Expression, EmitterLifetime.Looping, EmitterLifetime.Once { - /** - * Emitter will turn 'on' when the activation expression is non-zero, and will turn 'off' when it's zero. - *

- * This is useful for situations like driving an entity-attached emitter from an entity variable. - */ + /// Emitter will turn 'on' when the activation expression is non-zero, and will turn 'off' when it's zero. + /// + /// This is useful for situations like driving an entity-attached emitter from an entity variable. public static final class Expression extends EmitterLifetime { public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( FloatMolangExp.CODEC.fieldOf("activation_expression").orElse(FloatMolangExp.ONE).forGetter(Expression::getActivationExpression), @@ -28,20 +26,16 @@ public Expression(FloatMolangExp activationExpression, FloatMolangExp expiration this.expirationExpression = expirationExpression; } - /** - * When the expression is non-zero, the emitter will emit particles. - *

- * Evaluated every frame - */ + /// When the expression is non-zero, the emitter will emit particles. + /// + /// Evaluated every frame public FloatMolangExp getActivationExpression() { return activationExpression; } - /** - * Emitter will expire if the expression is non-zero. - *

- * Evaluated every frame - */ + /// Emitter will expire if the expression is non-zero. + /// + /// Evaluated every frame public FloatMolangExp getExpirationExpression() { return expirationExpression; } @@ -79,9 +73,7 @@ public String toString() { } } - /** - * Emitter will loop until it is removed. - */ + /// Emitter will loop until it is removed. public static final class Looping extends EmitterLifetime { public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( FloatMolangExp.CODEC.fieldOf("active_time").orElseGet(() -> FloatMolangExp.ofConstant(10)).forGetter(Looping::getActiveTime), @@ -95,20 +87,16 @@ public Looping(FloatMolangExp activeTime, FloatMolangExp sleepTime) { this.sleepTime = sleepTime; } - /** - * Emitter will emit particles for this time per loop - *

- * Evaluated once per particle emitter loop - */ + /// Emitter will emit particles for this time per loop + /// + /// Evaluated once per particle emitter loop public FloatMolangExp getActiveTime() { return activeTime; } - /** - * Emitter will pause emitting particles for this time per loop - *

- * Evaluated once per particle emitter loop - */ + /// Emitter will pause emitting particles for this time per loop + /// + /// Evaluated once per particle emitter loop public FloatMolangExp getSleepTime() { return sleepTime; } diff --git a/src/main/java/org/mesdag/particlestorm/data/component/EmitterLifetimeEvents.java b/src/main/java/org/mesdag/particlestorm/data/component/EmitterLifetimeEvents.java index 1069657..a6a8967 100644 --- a/src/main/java/org/mesdag/particlestorm/data/component/EmitterLifetimeEvents.java +++ b/src/main/java/org/mesdag/particlestorm/data/component/EmitterLifetimeEvents.java @@ -15,11 +15,11 @@ import java.util.Map; import java.util.function.Function; -/** - * Allows for lifetime events on the emitter to trigger certain events.

- * All events use the event names in the event section

- * All events can be an array or a string - */ +/// Allows for lifetime events on the emitter to trigger certain events. +/// +/// All events use the event names in the event section +/// +/// All events can be an array or a string public final class EmitterLifetimeEvents implements IEmitterComponent { public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( ParticleStorm.STRING_LIST_CODEC.fieldOf("creation_event").orElseGet(List::of).forGetter(events -> events.creationEvent), @@ -37,16 +37,14 @@ public final class EmitterLifetimeEvents implements IEmitterComponent { public final List, List>> sortedTimeline; public final List, List>> sortedTravelDistance; - /** - * @param creationEvent Fires when the emitter is created - * @param expirationEvent Fires when the emitter expires (does not wait for particles to expire too) - * @param timeline A series of times, e.g. 0.0 or 1.0, that trigger the event.

- * These get fired on every loop the emitter goes through - * @param travelDistanceEvents S series of distances, e.g. 0.0 or 1.0, that trigger the event.

- * These get fired when the emitter has moved by the specified input - * @param loopingTravelDistanceEvents A series of events that occur at set intervals.

- * These get fired every time the emitter has moved the specified input distance from the last time it was fired. - */ + /// @param creationEvent Fires when the emitter is created + /// @param expirationEvent Fires when the emitter expires (does not wait for particles to expire too) + /// @param timeline A series of times, e.g. 0.0 or 1.0, that trigger the event.

+ /// These get fired on every loop the emitter goes through + /// @param travelDistanceEvents S series of distances, e.g. 0.0 or 1.0, that trigger the event.

+ /// These get fired when the emitter has moved by the specified input + /// @param loopingTravelDistanceEvents A series of events that occur at set intervals.

+ /// These get fired every time the emitter has moved the specified input distance from the last time it was fired. public EmitterLifetimeEvents(List creationEvent, List expirationEvent, Map> timeline, Map> travelDistanceEvents, List loopingTravelDistanceEvents) { this.creationEvent = creationEvent; this.expirationEvent = expirationEvent; diff --git a/src/main/java/org/mesdag/particlestorm/data/component/EmitterLocalSpace.java b/src/main/java/org/mesdag/particlestorm/data/component/EmitterLocalSpace.java index cd009c1..d5ff702 100644 --- a/src/main/java/org/mesdag/particlestorm/data/component/EmitterLocalSpace.java +++ b/src/main/java/org/mesdag/particlestorm/data/component/EmitterLocalSpace.java @@ -7,15 +7,19 @@ import java.util.List; -/** - * This component specifies the frame of reference of the emitter.

- * Applies only when the emitter is attached to an entity.

- * When 'position' is true, the particles will simulate in entity space, otherwise they will simulate in world space.

- * Rotation works the same way for rotation.

- * Default is false for both, which makes the particles emit relative to the emitter, then simulate independently of the emitter.

- * Note that rotation = true and position = false is an invalid option.

- * Velocity will add the emitter's velocity to the initial particle velocity. - */ +/// This component specifies the frame of reference of the emitter. +/// +/// Applies only when the emitter is attached to an entity. +/// +/// When `position` is true, the particles will simulate in entity space, otherwise they will simulate in world space. +/// +/// `rotation` works the same way for `rotation`. +/// +/// Default is false for both, which makes the particles emit relative to the emitter, then simulate independently of the emitter. +/// +/// Note that `rotation` = true and `position` = false is an invalid option. +/// +/// `velocity` will add the emitter's velocity to the initial particle velocity. public record EmitterLocalSpace(boolean position, boolean rotation, boolean velocity) implements IEmitterComponent { public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( Codec.BOOL.fieldOf("position").orElse(false).forGetter(EmitterLocalSpace::position), @@ -33,7 +37,7 @@ public EmitterLocalSpace(boolean position, boolean rotation, boolean velocity) { @Override public Codec codec() { - return null; + return CODEC; } @Override diff --git a/src/main/java/org/mesdag/particlestorm/data/component/EmitterRate.java b/src/main/java/org/mesdag/particlestorm/data/component/EmitterRate.java index be99b20..5eb44e1 100644 --- a/src/main/java/org/mesdag/particlestorm/data/component/EmitterRate.java +++ b/src/main/java/org/mesdag/particlestorm/data/component/EmitterRate.java @@ -22,9 +22,7 @@ public enum Type { MANUAL } - /** - * All particles come out at once, then no more unless the emitter loops. - */ + /// All particles come out at once, then no more unless the emitter loops. public static final class Instant extends EmitterRate { public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( FloatMolangExp.CODEC.fieldOf("num_particles").orElseGet(() -> FloatMolangExp.ofConstant(10)).forGetter(Instant::getNumParticles) @@ -68,9 +66,7 @@ public String toString() { } } - /** - * Particles come out at a steady or Molang rate over time. - */ + /// Particles come out at a steady or Molang rate over time. public static final class Steady extends EmitterRate { public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( FloatMolangExp.CODEC.fieldOf("spawn_rate").orElse(FloatMolangExp.ONE).forGetter(Steady::getSpawnRate), @@ -127,9 +123,7 @@ public String toString() { } } - /** - * Particle emission will occur only when the emitter is told to emit via the game itself. This is mostly used by legacy particle effects. - */ + /// Particle emission will occur only when the emitter is told to emit via the game itself. This is mostly used by legacy particle effects. public static final class Manual extends EmitterRate { public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( FloatMolangExp.CODEC.fieldOf("max_particles").orElse(FloatMolangExp.ZERO).forGetter(Manual::getMaxParticles) @@ -162,6 +156,7 @@ public void apply(ParticleEmitter emitter) { } else { emitter.particleGroup.setLimit(limit); } + emitter.spawnRate = limit; } @Override diff --git a/src/main/java/org/mesdag/particlestorm/data/component/EmitterShape.java b/src/main/java/org/mesdag/particlestorm/data/component/EmitterShape.java index 42caed7..784dce7 100644 --- a/src/main/java/org/mesdag/particlestorm/data/component/EmitterShape.java +++ b/src/main/java/org/mesdag/particlestorm/data/component/EmitterShape.java @@ -4,24 +4,21 @@ import com.mojang.serialization.Codec; import com.mojang.serialization.codecs.RecordCodecBuilder; import net.minecraft.client.Minecraft; +import net.minecraft.client.particle.Particle; import net.minecraft.core.particles.ParticleGroup; import net.minecraft.util.Mth; import net.minecraft.util.RandomSource; import net.minecraft.util.StringRepresentable; import net.minecraft.world.entity.EntityDimensions; import net.minecraft.world.phys.Vec3; -import org.jetbrains.annotations.NotNull; import org.joml.Quaternionf; import org.joml.Vector3f; -import org.mesdag.particlestorm.api.IEmitterComponent; -import org.mesdag.particlestorm.api.IParticleComponent; -import org.mesdag.particlestorm.api.MolangInstance; +import org.mesdag.particlestorm.api.*; import org.mesdag.particlestorm.data.MathHelper; import org.mesdag.particlestorm.data.molang.FloatMolangExp; import org.mesdag.particlestorm.data.molang.FloatMolangExp3; import org.mesdag.particlestorm.data.molang.MolangExp; -import org.mesdag.particlestorm.mixin.ParticleEngineAccessor; -import org.mesdag.particlestorm.particle.MolangParticleInstance; +import org.mesdag.particlestorm.particle.EmitterPreset; import org.mesdag.particlestorm.particle.ParticleEmitter; import org.mesdag.particlestorm.particle.ParticlePreset; @@ -35,9 +32,7 @@ protected EmitterShape(boolean surfaceOnly) { this.surfaceOnly = surfaceOnly; } - /** - * Emit only from the edge of the shape - */ + /// Emit only from the edge of the shape public boolean isSurfaceOnly() { return surfaceOnly; } @@ -68,39 +63,40 @@ protected boolean isPoint() { return false; } - private void emittingParticle(ParticleEmitter emitter) { - MolangParticleInstance instance = (MolangParticleInstance) ((ParticleEngineAccessor) Minecraft.getInstance().particleEngine).callMakeParticle(emitter.getPreset().option, emitter.getX(), emitter.getY(), emitter.getZ(), 0.0, 0.0, 0.0); + private void emittingParticle(ParticleEmitter emitter) { + T instance = RegisterCustomParticleTypeEvent.createParticle(emitter); instance.setEmitter(emitter); - ParticlePreset preset = instance.preset; - MathHelper.redirect(preset.assignments, instance.getVars()); + ParticlePreset particlePreset = instance.getPreset(); + MathHelper.redirect(particlePreset.assignments, instance.getVars()); Vector3f position = new Vector3f(); Vector3f speed = new Vector3f(); initializeParticle(instance, position, speed); - for (IParticleComponent component : preset.effect.orderedParticleEarlyComponents) { + for (IParticleComponent component : particlePreset.effect.orderedParticleEarlyComponents) { component.apply(instance); } - speed.mul(instance.initialSpeed); + speed.mul(instance.getInitialSpeed()); if (emitter.parentMode == ParticleEmitter.ParentMode.LOCATOR) { position.x *= -1; position.y *= -1; speed.x *= -1; speed.y *= -1; } - if (emitter.parentMode != ParticleEmitter.ParentMode.WORLD && emitter.getPreset().localPosition && !emitter.getPreset().localRotation) { + EmitterPreset emitterPreset = emitter.getPreset(); + if (emitter.parentMode != ParticleEmitter.ParentMode.WORLD && emitterPreset.localPosition && !emitterPreset.localRotation) { speed.x *= -1; speed.z *= -1; } - if (emitter.getPreset().localRotation) { + if (emitterPreset.localRotation) { MathHelper.applyEuler(emitter.rot.x, emitter.rot.y, 0.0F, position); } - if (emitter.getPreset().localPosition) { + if (emitterPreset.localPosition) { Vec3 emitterPos = emitter.getPosition(); position.add((float) emitterPos.x, (float) emitterPos.y, (float) emitterPos.z); } speed.mul(emitter.invTickRate); - if (emitter.getAttachedEntity() != null && emitter.getPreset().localVelocity) { + if (emitter.getAttachedEntity() != null && emitterPreset.localVelocity) { Vec3 emitterVec = emitter.getAttachedEntity().getDeltaMovement(); speed.add((float) emitterVec.x, (float) emitterVec.y, (float) emitterVec.z); } @@ -108,25 +104,22 @@ private void emittingParticle(ParticleEmitter emitter) { instance.setParticleSpeed(speed.x, speed.y, speed.z); instance.setPos(position.x, position.y, position.z); instance.setPosO(position.x, position.y, position.z); - instance.particleGroup = emitter.particleGroup; + instance.setParticleGroup(emitter.particleGroup); - for (IParticleComponent component : preset.effect.orderedParticleComponents) { + for (IParticleComponent component : particlePreset.effect.orderedParticleComponents) { component.apply(instance); } - instance.components = preset.effect.orderedParticleComponentsWhichRequireUpdate; - instance.motionDynamic = preset.motionDynamic; - if (!instance.motionDynamic) instance.setParticleSpeed(0.0, 0.0, 0.0); + instance.setComponents(particlePreset.effect.orderedParticleComponentsWhichRequireUpdate); + if (!particlePreset.motionDynamic) instance.setParticleSpeed(0.0, 0.0, 0.0); Minecraft.getInstance().particleEngine.add(instance); } private static boolean hasSpaceInParticleLimit(ParticleEmitter emitter) { ParticleGroup particleGroup = emitter.particleGroup; - return ((ParticleEngineAccessor) Minecraft.getInstance().particleEngine).trackedParticleCounts().getInt(particleGroup) < particleGroup.getLimit(); + return Minecraft.getInstance().particleEngine.trackedParticleCounts.getInt(particleGroup) < particleGroup.getLimit(); } - /** - * This component spawns particles using a disc shape, particles can be spawned inside the shape or on its outer perimeter. - */ + /// This component spawns particles using a disc shape, particles can be spawned inside the shape or on its outer perimeter. public static final class Disc extends EmitterShape { public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( FloatMolangExp3.CODEC.fieldOf("offset").orElseGet(() -> FloatMolangExp3.ZERO).forGetter(disc -> disc.offset), @@ -135,25 +128,17 @@ public static final class Disc extends EmitterShape { Direction.CODEC.fieldOf("direction").orElse(Direction.OUTWARDS).forGetter(disc -> disc.direction), Codec.BOOL.fieldOf("surface_only").orElse(false).forGetter(EmitterShape::isSurfaceOnly) ).apply(instance, Disc::new)); - /** - * Specifies the offset from the emitter to emit the particles - *

- * Evaluated once per particle emitted - */ + /// Specifies the offset from the emitter to emit the particles + /// + /// Evaluated once per particle emitted public final FloatMolangExp3 offset; - /** - * Disc radius - *

- * Evaluated once per particle emitted - */ + /// Disc radius + /// + /// Evaluated once per particle emitted public final FloatMolangExp radius; - /** - * Specifies the normal of the disc plane, the disc will be perpendicular to this direction - */ + /// Specifies the normal of the disc plane, the disc will be perpendicular to this direction public final PlaneNormal planeNormal; - /** - * Specifies the direction of particles. - */ + /// Specifies the direction of particles. public final Direction direction; public Disc(FloatMolangExp3 offset, FloatMolangExp radius, PlaneNormal planeNormal, Direction direction, boolean surfaceOnly) { @@ -204,10 +189,8 @@ public String toString() { '}'; } - /** - * Custom direction for the normal - */ - public static class PlaneNormal { + /// Custom direction for the normal + public record PlaneNormal(String name, FloatMolangExp3 plane) { public static final PlaneNormal X = new PlaneNormal("x", FloatMolangExp3.X); public static final PlaneNormal Y = new PlaneNormal("y", FloatMolangExp3.Y); public static final PlaneNormal Z = new PlaneNormal("z", FloatMolangExp3.Z); @@ -220,13 +203,6 @@ public static class PlaneNormal { }, list -> new PlaneNormal("custom", list)), plane -> Either.right(plane.plane) ); - public final String name; - public final FloatMolangExp3 plane; - - PlaneNormal(String name, FloatMolangExp3 list) { - this.name = name; - this.plane = list; - } public boolean isCustom() { return "custom".equals(name); @@ -242,9 +218,7 @@ public String toString() { } } - /** - * All particles come out of a box of the specified size from the emitter. - */ + /// All particles come out of a box of the specified size from the emitter. public static final class Box extends EmitterShape { public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( FloatMolangExp3.CODEC.fieldOf("offset").orElse(FloatMolangExp3.ZERO).forGetter(box -> box.offset), @@ -252,15 +226,11 @@ public static final class Box extends EmitterShape { Direction.CODEC.fieldOf("direction").orElse(Direction.OUTWARDS).forGetter(box -> box.direction), Codec.BOOL.fieldOf("surface_only").orElse(false).forGetter(EmitterShape::isSurfaceOnly) ).apply(instance, Box::new)); - /** - * Specifies the offset from the emitter to emit the particles

- * Evaluated once per particle emitted - */ + /// Specifies the offset from the emitter to emit the particles + /// Evaluated once per particle emitted public final FloatMolangExp3 offset; public final FloatMolangExp3 halfDimensions; - /** - * Specifies the direction of particles. - */ + /// Specifies the direction of particles. public final Direction direction; public Box(FloatMolangExp3 offset, FloatMolangExp3 halfDimensions, Direction direction, boolean surfaceOnly) { @@ -282,16 +252,17 @@ public List getAllMolangExp() { @Override protected void initializeParticle(MolangInstance instance, Vector3f position, Vector3f speed) { - position.set(offset.calculate(instance)); + float[] offset = this.offset.calculate(instance); + position.set(offset); float[] n = halfDimensions.calculate(instance); RandomSource random = instance.getLevel().random; position.x += Mth.nextFloat(random, -n[0], n[0]); position.y += Mth.nextFloat(random, -n[1], n[1]); position.z += Mth.nextFloat(random, -n[2], n[2]); if (surfaceOnly) { - int r = random.nextInt(0, 3); - boolean i = random.nextBoolean(); - position.setComponent(r, n[r] * (i ? 1 : -1)); + int i = random.nextInt(0, 3); + boolean b = random.nextBoolean(); + position.setComponent(i, offset[i] + (b ? n[i] : -n[i])); } direction.apply(instance, this, position, speed); } @@ -307,9 +278,7 @@ public String toString() { } } - /** - * All particles come out of the axis-aligned bounding box (AABB) for the entity the emitter is attached to, or the emitter point if no entity. - */ + /// All particles come out of the axis-aligned bounding box AABB for the entity the emitter is attached to, or the emitter point if no entity. public static final class EntityAABB extends EmitterShape { public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( Direction.CODEC.fieldOf("direction").orElse(Direction.OUTWARDS).forGetter(entityAABB -> entityAABB.direction), @@ -357,23 +326,17 @@ public String toString() { } } - /** - * All particles come out of a point offset from the emitter. - */ + /// All particles come out of a point offset from the emitter. public static final class Point extends EmitterShape { public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( FloatMolangExp3.CODEC.fieldOf("offset").orElse(FloatMolangExp3.ZERO).forGetter(point -> point.offset), Direction.CODEC.fieldOf("direction").orElse(Direction.OUTWARDS).forGetter(point -> point.direction) ).apply(instance, Point::new)); - /** - * Specifies the offset from the emitter to emit the particles - *

- * Evaluated once per particle emitted - */ + /// Specifies the offset from the emitter to emit the particles + /// + /// Evaluated once per particle emitted public final FloatMolangExp3 offset; - /** - * Specifies the direction of particles. - */ + /// Specifies the direction of particles. public final Direction direction; public Point(FloatMolangExp3 offset, Direction direction) { @@ -423,21 +386,15 @@ public static final class Sphere extends EmitterShape { Direction.CODEC.fieldOf("direction").orElse(Direction.OUTWARDS).forGetter(sphere -> sphere.direction), Codec.BOOL.fieldOf("surface_only").orElse(false).forGetter(EmitterShape::isSurfaceOnly) ).apply(instance, Sphere::new)); - /** - * Specifies the offset from the emitter to emit the particles - *

- * Evaluated once per particle emitted - */ + /// Specifies the offset from the emitter to emit the particles + /// + /// Evaluated once per particle emitted public final FloatMolangExp3 offset; - /** - * Sphere radius - *

- * Evaluated once per particle emitted - */ + /// Sphere radius + /// + /// Evaluated once per particle emitted public final FloatMolangExp radius; - /** - * Specifies the direction of particles. - */ + /// Specifies the direction of particles. public final Direction direction; public Sphere(FloatMolangExp3 offset, FloatMolangExp radius, Direction direction, boolean surfaceOnly) { @@ -480,33 +437,20 @@ public String toString() { } } - /** - * Evaluated once per particle emitted - */ - public static class Direction implements StringRepresentable { - /** - * Particle direction towards center of shape - */ + /// Evaluated once per particle emitted + public record Direction(String name, FloatMolangExp3 direct) implements StringRepresentable { + /// Particle direction towards center of shape public static final Direction INWARDS = new Direction("inwards", FloatMolangExp3.ZERO); - /** - * Particle direction away from center of shape - */ + /// Particle direction away from center of shape public static final Direction OUTWARDS = new Direction("outwards", FloatMolangExp3.ZERO); public static final Codec DIRECTION_CODEC = StringRepresentable.fromValues(() -> new Direction[]{INWARDS, OUTWARDS}); public static final Codec CODEC = Codec.either(DIRECTION_CODEC, FloatMolangExp3.CODEC).xmap( either -> either.map(dir -> dir, list -> new Direction("custom", list)), dir -> dir.direct == FloatMolangExp3.ZERO ? Either.left(dir) : Either.right(dir.direct) ); - public final String name; - public final FloatMolangExp3 direct; - - Direction(String name, FloatMolangExp3 direct) { - this.name = name; - this.direct = direct; - } @Override - public @NotNull String getSerializedName() { + public String getSerializedName() { return name; } diff --git a/src/main/java/org/mesdag/particlestorm/data/component/ParticleAppearanceBillboard.java b/src/main/java/org/mesdag/particlestorm/data/component/ParticleAppearanceBillboard.java index 9baed9e..937085c 100644 --- a/src/main/java/org/mesdag/particlestorm/data/component/ParticleAppearanceBillboard.java +++ b/src/main/java/org/mesdag/particlestorm/data/component/ParticleAppearanceBillboard.java @@ -1,19 +1,20 @@ package org.mesdag.particlestorm.data.component; import com.mojang.serialization.Codec; +import com.mojang.serialization.MapCodec; import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; import net.minecraft.resources.ResourceLocation; import net.minecraft.util.ExtraCodecs; import net.minecraft.util.Mth; import net.minecraft.util.StringRepresentable; -import org.jetbrains.annotations.NotNull; +import org.mesdag.particlestorm.api.IMolangParticleInstance; import org.mesdag.particlestorm.api.IParticleComponent; import org.mesdag.particlestorm.data.DuplicateFieldDecoder; import org.mesdag.particlestorm.data.molang.FloatMolangExp; import org.mesdag.particlestorm.data.molang.FloatMolangExp2; import org.mesdag.particlestorm.data.molang.FloatMolangExp3; import org.mesdag.particlestorm.data.molang.MolangExp; -import org.mesdag.particlestorm.particle.MolangParticleInstance; import java.util.List; import java.util.Locale; @@ -22,8 +23,8 @@ public record ParticleAppearanceBillboard(FloatMolangExp2 size, FaceCameraMode f public static final ResourceLocation ID = ResourceLocation.withDefaultNamespace("particle_appearance_billboard"); public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( FloatMolangExp2.CODEC.fieldOf("size").forGetter(ParticleAppearanceBillboard::size), - DuplicateFieldDecoder.fieldOf("face_camera_mode", "facing_camera_mode", FaceCameraMode.CODEC).forGetter(ParticleAppearanceBillboard::faceCameraMode), - Direction.CODEC.fieldOf("direction").orElseGet(() -> new Direction(Direction.Mode.DERIVE_FROM_VELOCITY, 0.01F, FloatMolangExp3.ZERO)).forGetter(ParticleAppearanceBillboard::direction), + DuplicateFieldDecoder.fieldOf(FaceCameraMode.CODEC, "face_camera_mode", "facing_camera_mode").forGetter(ParticleAppearanceBillboard::faceCameraMode), + Direction.CODEC.lenientOptionalFieldOf("direction", Direction.DEFAULT).forGetter(ParticleAppearanceBillboard::direction), UV.CODEC.fieldOf("uv").orElse(UV.EMPTY).forGetter(ParticleAppearanceBillboard::uv) ).apply(instance, ParticleAppearanceBillboard::new)); @@ -35,107 +36,103 @@ public Codec codec() { @Override public List getAllMolangExp() { return List.of( - size.exp1(), size.exp2(), direction.customDirection.exp1(), direction.customDirection.exp2(), direction.customDirection.exp3(), - uv.uv.exp1(), uv.uv.exp2(), uv.uvSize.exp1(), uv.uvSize.exp2(), uv.flipbook.baseUV.exp1(), uv.flipbook.baseUV.exp2(), - uv.flipbook.sizeUV.exp1(), uv.flipbook.sizeUV.exp2(), uv.flipbook.stepUV.exp1(), uv.flipbook.stepUV.exp2(), uv.flipbook.maxFrame + size.exp1(), size.exp2(), + direction.customDirection.exp1(), direction.customDirection.exp2(), direction.customDirection.exp3(), + uv.uv.exp1(), uv.uv.exp2(), + uv.uvSize.exp1(), uv.uvSize.exp2(), + uv.flipbook.baseUV.exp1(), uv.flipbook.baseUV.exp2(), + uv.flipbook.sizeUV.exp1(), uv.flipbook.sizeUV.exp2(), + uv.flipbook.stepUV.exp1(), uv.flipbook.stepUV.exp2(), + uv.flipbook.maxFrame ); } @Override - public void update(MolangParticleInstance instance) { - if (faceCameraMode.isDirection()) { - if (direction.mode == ParticleAppearanceBillboard.Direction.Mode.CUSTOM_DIRECTION) { - float[] values = direction.customDirection.calculate(instance); - instance.xRot = values[0]; - instance.yRot = values[1]; - instance.setRoll(values[2]); - } else { - if (direction.minSpeedThreshold > 0.0F && Mth.lengthSquared(instance.getXd(), instance.getYd(), instance.getZd()) > instance.preset.minSpeedThresholdSqr) { - instance.facingDirection.set(instance.getXd(), instance.getYd(), instance.getZd()).normalize(); - } - } - } - if (size.initialized()) { - instance.billboardSize = size.calculate(instance); - } - if (uv != UV.EMPTY) { - UV.Flipbook flipbook = uv.flipbook; - if (flipbook == UV.Flipbook.EMPTY) { - updateSimpleUV(instance); - } else if (flipbook.stretchToLifetime) { - updateFlipbookUV(instance); - instance.maxFrame = (int) flipbook.maxFrame.calculate(instance); - instance.currentFrame = instance.maxFrame * instance.getAge() / instance.getLifetime(); - } else if (instance.getLevel().getGameTime() % flipbook.framesPerTick < 1.0F) { + public void update(IMolangParticleInstance instance) { + doFacingCameraMode(instance); + doSize(instance); + if (uv == UV.EMPTY) return; + UV.Flipbook flipbook = uv.flipbook; + if (flipbook == UV.Flipbook.EMPTY) { + updateSimpleUV(instance); + } else if (flipbook.stretchToLifetime) { + updateFlipbookUV(instance); + instance.setMaxFrame((int) flipbook.maxFrame.calculate(instance)); + instance.setCurrentFrame(instance.getMaxFrame() * instance.getAge() / instance.self().getLifetime()); + } else { + float gameTime = (float) ((int) instance.getLevel().getGameTime() & 0b11111111); + if (gameTime % (instance.getLevel().tickRateManager().tickrate() / flipbook.framesPerSecond) < 1.0F) { updateFlipbookUV(instance); - instance.maxFrame = (int) flipbook.maxFrame.calculate(instance); - if (instance.currentFrame < instance.maxFrame) { - instance.currentFrame++; - if (flipbook.loop && instance.currentFrame == instance.maxFrame) { - instance.currentFrame = 0; - } + instance.setMaxFrame((int) flipbook.maxFrame.calculate(instance)); + int currentFrame = instance.getCurrentFrame() + 1; + if (currentFrame < instance.getMaxFrame()) { + instance.setCurrentFrame(currentFrame); } else { - instance.currentFrame = instance.maxFrame - 1; + instance.setCurrentFrame(flipbook.loop ? 0 : instance.getMaxFrame() - 1); } } } } @Override - public void apply(MolangParticleInstance instance) { + public void apply(IMolangParticleInstance instance) { + doFacingCameraMode(instance); + doSize(instance); + if (uv == UV.EMPTY) return; + if (uv.flipbook == UV.Flipbook.EMPTY) { + updateSimpleUV(instance); + } else { + instance.setUvSize(uv.flipbook.getSizeUV(instance)); + instance.getUvSize()[0] *= instance.getScaleU(); + instance.getUvSize()[1] *= instance.getScaleV(); + instance.setUvStep(uv.flipbook.getStepUV(instance)); + instance.getUvStep()[0] *= instance.getScaleU(); + instance.getUvStep()[1] *= instance.getScaleV(); + updateFlipbookUV(instance); + } + } + + private void doFacingCameraMode(IMolangParticleInstance instance) { if (faceCameraMode.isDirection()) { - if (direction.mode == ParticleAppearanceBillboard.Direction.Mode.CUSTOM_DIRECTION) { + if (direction.mode == Direction.Mode.CUSTOM_DIRECTION) { float[] values = direction.customDirection.calculate(instance); - instance.xRot = values[0]; - instance.yRot = values[1]; - instance.setRoll(values[2]); - } else { - double xdSqr = instance.getXd() * instance.getXd(); - double zdSqr = instance.getZd() * instance.getZd(); - if (direction.minSpeedThreshold > 0.0F && xdSqr + instance.getYd() * instance.getYd() + zdSqr > instance.preset.minSpeedThresholdSqr) { - instance.facingDirection.set(instance.getXd(), instance.getYd(), instance.getZd()).normalize(); - } - } - } - if (size.initialized()) { - instance.billboardSize = size.calculate(instance); - } - if (uv != UV.EMPTY) { - if (uv.flipbook == UV.Flipbook.EMPTY) { - updateSimpleUV(instance); - } else { - instance.uvSize = uv.flipbook.getSizeUV(instance); - instance.uvSize[0] *= instance.scaleU; - instance.uvSize[1] *= instance.scaleV; - instance.uvStep = uv.flipbook.getStepUV(instance); - instance.uvStep[0] *= instance.scaleU; - instance.uvStep[1] *= instance.scaleV; - updateFlipbookUV(instance); + instance.setXRot(values[0]); + instance.setYRot(values[1]); + instance.setZRot(values[2]); + } else if (direction.minSpeedThreshold > 0.0F && Mth.lengthSquared(instance.getXd(), instance.getYd(), instance.getZd()) > instance.getPreset().minSpeedThresholdSqr) { + instance.getFacingDirection().set(instance.getXd(), instance.getYd(), instance.getZd()).normalize(); } } } + private void doSize(IMolangParticleInstance instance) { + instance.setBillboardSize(size.calculate(instance)); + } + @Override public boolean requireUpdate() { return true; } - private void updateSimpleUV(MolangParticleInstance instance) { + private void updateSimpleUV(IMolangParticleInstance instance) { + TextureAtlasSprite sprite = instance.getSprite(); + if (sprite == null) return; float[] base = uv.uv.calculate(instance); float[] size = uv.uvSize.calculate(instance); - int x = instance.getSprite().getX(); - int y = instance.getSprite().getY(); - instance.setUV(x + base[0], y + base[1], size[0] * instance.scaleU, size[1] * instance.scaleV); + int x = sprite.getX(); + int y = sprite.getY(); + instance.setUV(x + base[0], y + base[1], size[0] * instance.getScaleU(), size[1] * instance.getScaleV()); } - private void updateFlipbookUV(MolangParticleInstance instance) { + private void updateFlipbookUV(IMolangParticleInstance instance) { + TextureAtlasSprite sprite = instance.getSprite(); + if (sprite == null) return; float[] base = uv.flipbook.baseUV.calculate(instance); - int index = instance.currentFrame; - float u = instance.uvStep[0] * index; - float v = instance.uvStep[1] * index; - int x = instance.getSprite().getX(); - int y = instance.getSprite().getY(); - instance.setUV(x + base[0] + u, y + base[1] + v, instance.uvSize[0], instance.uvSize[1]); + float u = instance.getUvStep()[0] * instance.getCurrentFrame(); + float v = instance.getUvStep()[1] * instance.getCurrentFrame(); + int x = sprite.getX(); + int y = sprite.getY(); + instance.setUV(x + base[0] + u, y + base[1] + v, instance.getUvSize()[0], instance.getUvSize()[1]); } @Override @@ -149,6 +146,7 @@ public String toString() { } public enum FaceCameraMode implements StringRepresentable { + DO_NOTHING, ROTATE_XYZ, ROTATE_Y, LOOKAT_XYZ, @@ -164,7 +162,7 @@ public enum FaceCameraMode implements StringRepresentable { public static final Codec CODEC = StringRepresentable.fromEnum(FaceCameraMode::values); @Override - public @NotNull String getSerializedName() { + public String getSerializedName() { return name().toLowerCase(Locale.ROOT); } @@ -179,13 +177,15 @@ public String toString() { } public record Direction(Mode mode, float minSpeedThreshold, FloatMolangExp3 customDirection) { + public static final Direction DEFAULT = new Direction(Direction.Mode.DERIVE_FROM_VELOCITY, 0.01F, FloatMolangExp3.ZERO); + public static final MapCodec CUSTOM_MODE_CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group( + FloatMolangExp3.CODEC.fieldOf("custom_direction").orElse(FloatMolangExp3.ZERO).forGetter(Direction::customDirection) + ).apply(instance, l -> new Direction(Mode.CUSTOM_DIRECTION, 0, l))); + public static final MapCodec SPEED_MODE_CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group( + Codec.FLOAT.fieldOf("min_speed_threshold").orElse(0.01F).forGetter(Direction::minSpeedThreshold) + ).apply(instance, f -> new Direction(Mode.DERIVE_FROM_VELOCITY, f, FloatMolangExp3.ZERO))); public static final Codec CODEC = Mode.CODEC.dispatchMap( - Direction::mode, - mode -> mode == Mode.CUSTOM_DIRECTION ? RecordCodecBuilder.mapCodec(instance -> instance.group( - FloatMolangExp3.CODEC.fieldOf("custom_direction").orElse(FloatMolangExp3.ZERO).forGetter(Direction::customDirection) - ).apply(instance, l -> new Direction(mode, 0, l))) : RecordCodecBuilder.mapCodec(instance -> instance.group( - Codec.FLOAT.fieldOf("min_speed_threshold").orElse(0.01F).forGetter(Direction::minSpeedThreshold) - ).apply(instance, f -> new Direction(mode, f, FloatMolangExp3.ZERO))) + "mode", Direction::mode, mode -> mode == Mode.CUSTOM_DIRECTION ? CUSTOM_MODE_CODEC : SPEED_MODE_CODEC ).codec(); @Override @@ -207,7 +207,7 @@ public enum Mode implements StringRepresentable { }); @Override - public @NotNull String getSerializedName() { + public String getSerializedName() { return name().toLowerCase(Locale.ROOT); } @@ -218,22 +218,20 @@ public String toString() { } } - /** - * Specifies the UVs for the particle. - * - * @param texturewidth - * @param textureheight Specifies the assumed texture width/height, defaults to 1

- * When set to 1, UV's work just like normalized UV's

- * When set to the texture width/height, this works like texels - * @param uv - * @param uvSize Assuming the specified texture width and height, use these uv coordinates.

- * Evaluated every frame - */ + /// Specifies the UVs for the particle. + /// + /// @param texturewidth + /// @param textureheight Specifies the assumed texture width/height, defaults to 1

+ /// When set to 1, UV's work just like normalized UV's

+ /// When set to the texture width/height, this works like texels + /// @param uv + /// @param uvSize Assuming the specified texture width and height, use these uv coordinates.

+ /// Evaluated every frame public record UV(int texturewidth, int textureheight, FloatMolangExp2 uv, FloatMolangExp2 uvSize, Flipbook flipbook) { public static final UV EMPTY = new UV(1, 1, FloatMolangExp2.ZERO, FloatMolangExp2.ZERO, Flipbook.EMPTY); public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( - DuplicateFieldDecoder.fieldOf("texturewidth", "texture_width", ExtraCodecs.POSITIVE_INT).orElse(1).forGetter(UV::texturewidth), - DuplicateFieldDecoder.fieldOf("textureheight", "texture_height", ExtraCodecs.POSITIVE_INT).orElse(1).forGetter(UV::textureheight), + DuplicateFieldDecoder.fieldOf(ExtraCodecs.POSITIVE_INT, "texturewidth", "texture_width").orElse(1).forGetter(UV::texturewidth), + DuplicateFieldDecoder.fieldOf(ExtraCodecs.POSITIVE_INT, "textureheight", "texture_height").orElse(1).forGetter(UV::textureheight), FloatMolangExp2.CODEC.fieldOf("uv").orElse(FloatMolangExp2.ZERO).forGetter(UV::uv), FloatMolangExp2.CODEC.fieldOf("uv_size").orElse(FloatMolangExp2.ZERO).forGetter(UV::uvSize), Flipbook.CODEC.fieldOf("flipbook").orElse(Flipbook.EMPTY).forGetter(UV::flipbook) @@ -250,90 +248,37 @@ public String toString() { '}'; } - /** - * Alternate way via specifying a flipbook animation

- * A flipbook animation uses pieces of the texture to animate, by stepping over time from one frame to another - */ - public static class Flipbook { + /// Alternate way via specifying a flipbook animation + /// + /// A flipbook animation uses pieces of the texture to animate, by stepping over time from one `frame` to another + /// + /// @param baseUV Upper-left corner of starting UV patch + /// @param sizeUV Size of UV patch + /// @param stepUV How far to move the UV patch each frame + /// @param framesPerSecond Default frames per second + /// @param maxFrame Maximum frame number, with first frame being frame 1 + /// @param stretchToLifetime Optional, adjust fps to match lifetime of particle. Default=false + /// @param loop Optional, makes the animation loop when it reaches the end? Default=false + public record Flipbook(FloatMolangExp2 baseUV, FloatMolangExp2 sizeUV, FloatMolangExp2 stepUV, float framesPerSecond, FloatMolangExp maxFrame, boolean stretchToLifetime, boolean loop) { public static final Flipbook EMPTY = new Flipbook(FloatMolangExp2.ZERO, FloatMolangExp2.ZERO, FloatMolangExp2.ZERO, 0, FloatMolangExp.ZERO, false, false); public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( FloatMolangExp2.CODEC.lenientOptionalFieldOf("base_UV", FloatMolangExp2.ZERO).forGetter(Flipbook::baseUV), FloatMolangExp2.CODEC.lenientOptionalFieldOf("size_UV", FloatMolangExp2.ZERO).forGetter(Flipbook::sizeUV), FloatMolangExp2.CODEC.lenientOptionalFieldOf("step_UV", FloatMolangExp2.ZERO).forGetter(Flipbook::stepUV), - Codec.FLOAT.lenientOptionalFieldOf("frames_per_second", 1.0F).forGetter(Flipbook::framesPerSecond), + ExtraCodecs.POSITIVE_FLOAT.lenientOptionalFieldOf("frames_per_second", 1.0F).forGetter(Flipbook::framesPerSecond), FloatMolangExp.CODEC.lenientOptionalFieldOf("max_frame", FloatMolangExp.ZERO).forGetter(Flipbook::maxFrame), Codec.BOOL.lenientOptionalFieldOf("stretch_to_lifetime", false).forGetter(Flipbook::stretchToLifetime), Codec.BOOL.lenientOptionalFieldOf("loop", false).forGetter(Flipbook::loop) ).apply(instance, Flipbook::new)); - private final FloatMolangExp2 baseUV; - private final FloatMolangExp2 sizeUV; - private final FloatMolangExp2 stepUV; - private final float framesPerSecond; - private final FloatMolangExp maxFrame; - private final boolean stretchToLifetime; - private final boolean loop; - - private float framesPerTick = 1.0F; - /** - * @param baseUV Upper-left corner of starting UV patch - * @param sizeUV Size of UV patch - * @param stepUV How far to move the UV patch each frame - * @param framesPerSecond Default frames per second - * @param maxFrame Maximum frame number, with first frame being frame 1 - * @param stretchToLifetime Optional, adjust fps to match lifetime of particle. Default=false - * @param loop Optional, makes the animation loop when it reaches the end? Default=false - */ - public Flipbook(FloatMolangExp2 baseUV, FloatMolangExp2 sizeUV, FloatMolangExp2 stepUV, float framesPerSecond, FloatMolangExp maxFrame, boolean stretchToLifetime, boolean loop) { - this.baseUV = baseUV; - this.sizeUV = sizeUV; - this.stepUV = stepUV; - this.framesPerSecond = framesPerSecond; - this.maxFrame = maxFrame; - this.stretchToLifetime = stretchToLifetime; - this.loop = loop; - - if (framesPerSecond != 0.0F) { - this.framesPerTick = 20.0F / framesPerSecond; - } - } - - public float[] getSizeUV(MolangParticleInstance instance) { + public float[] getSizeUV(IMolangParticleInstance instance) { return sizeUV.calculate(instance); } - public float[] getStepUV(MolangParticleInstance instance) { + public float[] getStepUV(IMolangParticleInstance instance) { return stepUV.calculate(instance); } - public FloatMolangExp2 baseUV() { - return baseUV; - } - - public FloatMolangExp2 sizeUV() { - return sizeUV; - } - - public FloatMolangExp2 stepUV() { - return stepUV; - } - - public float framesPerSecond() { - return framesPerSecond; - } - - public FloatMolangExp maxFrame() { - return maxFrame; - } - - public boolean stretchToLifetime() { - return stretchToLifetime; - } - - public boolean loop() { - return loop; - } - @Override public String toString() { return "Flipbook{" + diff --git a/src/main/java/org/mesdag/particlestorm/data/component/ParticleAppearanceLighting.java b/src/main/java/org/mesdag/particlestorm/data/component/ParticleAppearanceLighting.java index 4fc05ad..f929548 100644 --- a/src/main/java/org/mesdag/particlestorm/data/component/ParticleAppearanceLighting.java +++ b/src/main/java/org/mesdag/particlestorm/data/component/ParticleAppearanceLighting.java @@ -9,9 +9,7 @@ import java.util.List; -/** - * When this component exists, particle will be tinted by local lighting conditions in-game. - */ +/// When this component exists, particle will be tinted by local lighting conditions in-game. public final class ParticleAppearanceLighting implements IParticleComponent { public static final ParticleAppearanceLighting INSTANCE = new ParticleAppearanceLighting(); public static final Codec CODEC = MapCodec.of(Encoder.empty(), Decoder.unit(ParticleAppearanceLighting.INSTANCE)).codec(); diff --git a/src/main/java/org/mesdag/particlestorm/data/component/ParticleAppearanceTinting.java b/src/main/java/org/mesdag/particlestorm/data/component/ParticleAppearanceTinting.java index f3803e0..2664e22 100644 --- a/src/main/java/org/mesdag/particlestorm/data/component/ParticleAppearanceTinting.java +++ b/src/main/java/org/mesdag/particlestorm/data/component/ParticleAppearanceTinting.java @@ -6,11 +6,11 @@ import com.mojang.serialization.codecs.RecordCodecBuilder; import net.minecraft.util.Mth; import net.minecraft.util.Tuple; +import org.mesdag.particlestorm.api.IMolangParticleInstance; import org.mesdag.particlestorm.api.IParticleComponent; import org.mesdag.particlestorm.api.MolangInstance; import org.mesdag.particlestorm.data.molang.FloatMolangExp; import org.mesdag.particlestorm.data.molang.MolangExp; -import org.mesdag.particlestorm.particle.MolangParticleInstance; import java.util.*; import java.util.stream.Stream; @@ -38,11 +38,11 @@ public List getAllMolangExp() { } @Override - public void update(MolangParticleInstance instance) { + public void update(IMolangParticleInstance instance) { apply(instance); } - private float[] getCalculatedColor(MolangParticleInstance instance, ArrayList> list, float ratio) { + private float[] getCalculatedColor(IMolangParticleInstance instance, ArrayList> list, float ratio) { int n = 0; for (int index = 0; index < list.size(); index++) { Tuple tuple = list.get(index); @@ -72,7 +72,7 @@ private float mix(float first, float second, float ratio) { } @Override - public void apply(MolangParticleInstance instance) { + public void apply(IMolangParticleInstance instance) { if (color.interpolant.initialized() && !color.gradient.map.isEmpty()) { float interpolant = color.interpolant.calculate(instance); float[] calculated = getCalculatedColor(instance, color.gradient.list, interpolant / color.gradient.range); diff --git a/src/main/java/org/mesdag/particlestorm/data/component/ParticleExpireIfInBlocks.java b/src/main/java/org/mesdag/particlestorm/data/component/ParticleExpireIfInBlocks.java index d895d12..001a438 100644 --- a/src/main/java/org/mesdag/particlestorm/data/component/ParticleExpireIfInBlocks.java +++ b/src/main/java/org/mesdag/particlestorm/data/component/ParticleExpireIfInBlocks.java @@ -17,10 +17,9 @@ import java.util.List; import java.util.Set; -/** - * Particles expire when in a block of the type in the list.

- * Note: this component can exist alongside particle_lifetime_expression. - */ +/// Particles expire when in a block of the type in the list. +/// +/// Note: this component can exist alongside particle_lifetime_expression. public final class ParticleExpireIfInBlocks implements IParticleComponent { public static final Codec CODEC = Codec.list(Codec.STRING).xmap( states -> new ParticleExpireIfInBlocks(new HashSet<>(states)), diff --git a/src/main/java/org/mesdag/particlestorm/data/component/ParticleExpireIfNotInBlocks.java b/src/main/java/org/mesdag/particlestorm/data/component/ParticleExpireIfNotInBlocks.java index 238fe90..03cbcfc 100644 --- a/src/main/java/org/mesdag/particlestorm/data/component/ParticleExpireIfNotInBlocks.java +++ b/src/main/java/org/mesdag/particlestorm/data/component/ParticleExpireIfNotInBlocks.java @@ -61,5 +61,4 @@ public String toString() { return "ParticleExpireIfNotInBlocks[" + "blocks=" + ids + ']'; } - } diff --git a/src/main/java/org/mesdag/particlestorm/data/component/ParticleInitialSpeed.java b/src/main/java/org/mesdag/particlestorm/data/component/ParticleInitialSpeed.java index 5045fee..e7b165e 100644 --- a/src/main/java/org/mesdag/particlestorm/data/component/ParticleInitialSpeed.java +++ b/src/main/java/org/mesdag/particlestorm/data/component/ParticleInitialSpeed.java @@ -2,19 +2,17 @@ import com.mojang.datafixers.util.Either; import com.mojang.serialization.Codec; +import org.mesdag.particlestorm.api.IMolangParticleInstance; import org.mesdag.particlestorm.api.IParticleComponent; import org.mesdag.particlestorm.data.molang.FloatMolangExp; import org.mesdag.particlestorm.data.molang.FloatMolangExp3; import org.mesdag.particlestorm.data.molang.MolangExp; -import org.mesdag.particlestorm.particle.MolangParticleInstance; import java.util.List; -/** - * Starts the particle with a specified speed, using the direction specified by the emitter shape. - * - * @param speed Evaluated once - */ +/// Starts the particle with a specified speed, using the direction specified by the emitter shape. +/// +/// @param speed Evaluated once public record ParticleInitialSpeed(Either speed) implements IParticleComponent { public static final Codec CODEC = Codec.either(FloatMolangExp.CODEC, FloatMolangExp3.CODEC).xmap( either -> either.map(f -> new ParticleInitialSpeed(Either.left(f)), l -> new ParticleInitialSpeed(Either.right(l))), @@ -32,13 +30,13 @@ public List getAllMolangExp() { } @Override - public void apply(MolangParticleInstance instance) { + public void apply(IMolangParticleInstance instance) { speed.ifLeft(exp -> { float value = exp.calculate(instance); - instance.initialSpeed.set(value); + instance.getInitialSpeed().set(value); }).ifRight(exp3 -> { float[] mul = exp3.calculate(instance); - instance.initialSpeed.set(mul); + instance.getInitialSpeed().set(mul); }); } diff --git a/src/main/java/org/mesdag/particlestorm/data/component/ParticleInitialSpin.java b/src/main/java/org/mesdag/particlestorm/data/component/ParticleInitialSpin.java index 5b7b289..43df051 100644 --- a/src/main/java/org/mesdag/particlestorm/data/component/ParticleInitialSpin.java +++ b/src/main/java/org/mesdag/particlestorm/data/component/ParticleInitialSpin.java @@ -3,19 +3,17 @@ import com.mojang.serialization.Codec; import com.mojang.serialization.codecs.RecordCodecBuilder; import net.minecraft.util.Mth; +import org.mesdag.particlestorm.api.IMolangParticleInstance; import org.mesdag.particlestorm.api.IParticleComponent; import org.mesdag.particlestorm.data.molang.FloatMolangExp; import org.mesdag.particlestorm.data.molang.MolangExp; -import org.mesdag.particlestorm.particle.MolangParticleInstance; import java.util.List; -/** - * Starts the particle with a specified orientation and rotation rate. - * - * @param rotation Specifies the initial rotation in degrees. Evaluated once - * @param rotationRate Specifies the spin rate in degrees/second. Evaluated once - */ +/// Starts the particle with a specified orientation and rotation rate. +/// +/// @param rotation Specifies the initial rotation in degrees. Evaluated once +/// @param rotationRate Specifies the spin rate in degrees/second. Evaluated once public record ParticleInitialSpin(FloatMolangExp rotation, FloatMolangExp rotationRate) implements IParticleComponent { public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( FloatMolangExp.CODEC.fieldOf("rotation").orElse(FloatMolangExp.ZERO).forGetter(ParticleInitialSpin::rotation), @@ -33,9 +31,9 @@ public List getAllMolangExp() { } @Override - public void apply(MolangParticleInstance instance) { - instance.setRoll(rotation.calculate(instance) * Mth.DEG_TO_RAD); - instance.rolld = rotationRate.calculate(instance) * instance.getInvTickRate() * Mth.DEG_TO_RAD; + public void apply(IMolangParticleInstance instance) { + instance.setZRot(rotation.calculate(instance) * Mth.DEG_TO_RAD); + instance.setZRotD(rotationRate.calculate(instance) * instance.getInvTickRate() * Mth.DEG_TO_RAD); } @Override diff --git a/src/main/java/org/mesdag/particlestorm/data/component/ParticleInitialization.java b/src/main/java/org/mesdag/particlestorm/data/component/ParticleInitialization.java index 37e8869..cf4e171 100644 --- a/src/main/java/org/mesdag/particlestorm/data/component/ParticleInitialization.java +++ b/src/main/java/org/mesdag/particlestorm/data/component/ParticleInitialization.java @@ -2,20 +2,24 @@ import com.mojang.serialization.Codec; import com.mojang.serialization.codecs.RecordCodecBuilder; +import org.mesdag.particlestorm.api.IMolangParticleInstance; import org.mesdag.particlestorm.api.IParticleComponent; import org.mesdag.particlestorm.data.molang.FloatMolangExp; import org.mesdag.particlestorm.data.molang.MolangExp; -import org.mesdag.particlestorm.particle.MolangParticleInstance; import java.util.List; -/** - * Starts the particle with a specified render expression. - */ +/// Starts the particle with a specified render expression. public record ParticleInitialization(FloatMolangExp perRenderExpression) implements IParticleComponent { public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( - FloatMolangExp.CODEC.fieldOf("per_render_expression").forGetter(ParticleInitialization::perRenderExpression) - ).apply(instance, ParticleInitialization::new)); + FloatMolangExp.CODEC.fieldOf("per_render_expression").forGetter(ParticleInitialization::perRenderExpression), + FloatMolangExp.CODEC.lenientOptionalFieldOf("per_update_expression", FloatMolangExp.ZERO).forGetter(ParticleInitialization::perRenderExpression) + ).apply(instance, (render, update) -> { + if (update != FloatMolangExp.ZERO) { + throw new IllegalArgumentException("per_update_expression is not allowed here, please use per_render_expression instead"); + } + return new ParticleInitialization(render); + })); @Override public Codec codec() { @@ -28,12 +32,12 @@ public List getAllMolangExp() { } @Override - public void update(MolangParticleInstance instance) { + public void update(IMolangParticleInstance instance) { perRenderExpression.calculate(instance); } @Override - public void apply(MolangParticleInstance instance) { + public void apply(IMolangParticleInstance instance) { perRenderExpression.calculate(instance); } diff --git a/src/main/java/org/mesdag/particlestorm/data/component/ParticleLifeTimeEvents.java b/src/main/java/org/mesdag/particlestorm/data/component/ParticleLifeTimeEvents.java index b21bff7..00773c8 100644 --- a/src/main/java/org/mesdag/particlestorm/data/component/ParticleLifeTimeEvents.java +++ b/src/main/java/org/mesdag/particlestorm/data/component/ParticleLifeTimeEvents.java @@ -6,9 +6,9 @@ import net.minecraft.util.Tuple; import org.mesdag.particlestorm.ParticleStorm; import org.mesdag.particlestorm.api.IEventNode; +import org.mesdag.particlestorm.api.IMolangParticleInstance; import org.mesdag.particlestorm.api.IParticleComponent; import org.mesdag.particlestorm.data.molang.MolangExp; -import org.mesdag.particlestorm.particle.MolangParticleInstance; import java.util.ArrayList; import java.util.Comparator; @@ -16,10 +16,9 @@ import java.util.Map; import java.util.function.Function; -/** - * All events use the event names in the event section

- * All events can be either an array or a string - */ +/// All events use the event names in the event section +/// +/// All events can be either an array or a string public final class ParticleLifeTimeEvents implements IParticleComponent { public static final ResourceLocation ID = ResourceLocation.withDefaultNamespace("particle_lifetime_events"); public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( @@ -33,11 +32,9 @@ public final class ParticleLifeTimeEvents implements IParticleComponent { public final List, List>> sortedTimeline; - /** - * @param creationEvent Fires when the particle is created - * @param expirationEvent Fires when the particle expires (does not wait for particles to expire too) - * @param timeline A series of times, e.g. 0.0 or 1.0, that trigger the event - */ + /// @param creationEvent Fires when the particle is created + /// @param expirationEvent Fires when the particle expires (does not wait for particles to expire too) + /// @param timeline A series of times, e.g. 0.0 or 1.0, that trigger the event public ParticleLifeTimeEvents(List creationEvent, List expirationEvent, Map> timeline) { this.creationEvent = creationEvent; this.expirationEvent = expirationEvent; @@ -61,11 +58,11 @@ public List getAllMolangExp() { } @Override - public void update(MolangParticleInstance instance) { - for (int i = instance.lastTimeline; i < sortedTimeline.size(); i++) { + public void update(IMolangParticleInstance instance) { + for (int i = instance.getLastTimeline(); i < sortedTimeline.size(); i++) { Tuple, List> tuple = sortedTimeline.get(i); - if (tuple.getA().apply(instance.getLifetime())) { - instance.lastTimeline = i + 1; + if (tuple.getA().apply(instance.self().getLifetime())) { + instance.setLastTimeline(i + 1); executes(instance, tuple.getB()); break; } @@ -73,7 +70,7 @@ public void update(MolangParticleInstance instance) { } @Override - public void apply(MolangParticleInstance instance) { + public void apply(IMolangParticleInstance instance) { executes(instance, creationEvent); } @@ -82,7 +79,7 @@ public boolean requireUpdate() { return true; } - public void onExpiration(MolangParticleInstance instance) { + public void onExpiration(IMolangParticleInstance instance) { executes(instance, expirationEvent); } @@ -94,9 +91,9 @@ public String toString() { "timeline=" + timeline + ']'; } - private static void executes(MolangParticleInstance instance, List triggers) { + private static void executes(IMolangParticleInstance instance, List triggers) { for (String event : triggers) { - for (IEventNode node : instance.preset.effect.events.get(event).values()) { + for (IEventNode node : instance.getPreset().effect.events.get(event).values()) { node.execute(instance); } } diff --git a/src/main/java/org/mesdag/particlestorm/data/component/ParticleLifetimeExpression.java b/src/main/java/org/mesdag/particlestorm/data/component/ParticleLifetimeExpression.java index a6c24b9..a3381d9 100644 --- a/src/main/java/org/mesdag/particlestorm/data/component/ParticleLifetimeExpression.java +++ b/src/main/java/org/mesdag/particlestorm/data/component/ParticleLifetimeExpression.java @@ -2,24 +2,22 @@ import com.mojang.serialization.Codec; import com.mojang.serialization.codecs.RecordCodecBuilder; +import org.mesdag.particlestorm.api.IMolangParticleInstance; import org.mesdag.particlestorm.api.IParticleComponent; import org.mesdag.particlestorm.data.molang.FloatMolangExp; import org.mesdag.particlestorm.data.molang.MolangExp; -import org.mesdag.particlestorm.particle.MolangParticleInstance; import java.util.List; -/** - * Standard lifetime component. These expressions control the lifetime of the particle. - * - * @param expirationExpression This expression makes the particle expire when true (non-zero)

- * The float/expr is evaluated once per particle

- * Evaluated every frame - * @param maxLifetime Alternate way to express lifetime

- * Particle will expire after this much time

- * Evaluated once

- * Available value is [0.05, infinite) - */ +/// Standard lifetime component. These expressions control the lifetime of the particle. +/// +/// @param expirationExpression This expression makes the particle expire when true (non-zero)

+/// The float/expr is evaluated once per particle

+/// Evaluated every frame

+/// @param maxLifetime Alternate way to express lifetime

+/// Particle will expire after this much time

+/// Evaluated once

+/// Available value is `[0.05, infinite)` public record ParticleLifetimeExpression(FloatMolangExp expirationExpression, FloatMolangExp maxLifetime) implements IParticleComponent { public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( FloatMolangExp.CODEC.fieldOf("expiration_expression").orElse(FloatMolangExp.ZERO).forGetter(ParticleLifetimeExpression::expirationExpression), @@ -37,16 +35,16 @@ public List getAllMolangExp() { } @Override - public void update(MolangParticleInstance instance) { + public void update(IMolangParticleInstance instance) { if (expirationExpression.initialized() && expirationExpression.getVariable().get(instance) != 0.0) { - instance.remove(); + instance.self().remove(); } } @Override - public void apply(MolangParticleInstance instance) { + public void apply(IMolangParticleInstance instance) { if (maxLifetime.initialized()) { - instance.setLifetime(Math.max((int) (maxLifetime.calculate(instance) * 20), 1)); + instance.self().setLifetime(Math.max((int) (maxLifetime.calculate(instance) * 20), 1)); } } diff --git a/src/main/java/org/mesdag/particlestorm/data/component/ParticleLifetimeKillPlane.java b/src/main/java/org/mesdag/particlestorm/data/component/ParticleLifetimeKillPlane.java index 9ebdf4e..e011b21 100644 --- a/src/main/java/org/mesdag/particlestorm/data/component/ParticleLifetimeKillPlane.java +++ b/src/main/java/org/mesdag/particlestorm/data/component/ParticleLifetimeKillPlane.java @@ -1,18 +1,19 @@ package org.mesdag.particlestorm.data.component; import com.mojang.serialization.Codec; +import org.mesdag.particlestorm.api.IMolangParticleInstance; import org.mesdag.particlestorm.api.IParticleComponent; import org.mesdag.particlestorm.data.molang.MolangExp; -import org.mesdag.particlestorm.particle.MolangParticleInstance; import java.util.List; -/** - * Particles that cross this plane expire.

- * The plane is relative to the emitter, but oriented in world space.

- * The four parameters are the usual 4 elements of a plane equation.

- * A*x + B*y + C*z + D = 0 with the parameters being [ A, B, C, D ] - */ +/// Particles that cross this plane expire. +/// +/// The plane is relative to the emitter, but oriented in world space. +/// +/// The four parameters are the usual 4 elements of a plane equation. +/// +/// `A*x + B*y + C*z + D = 0` with the parameters being `[ A, B, C, D ]` public final class ParticleLifetimeKillPlane implements IParticleComponent { public static final Codec CODEC = Codec.list(Codec.FLOAT, 4, 4).xmap( floats -> new ParticleLifetimeKillPlane(floats.getFirst(), floats.get(1), floats.get(2), floats.get(3)), @@ -35,16 +36,16 @@ public ParticleLifetimeKillPlane(float A, float B, float C, float D) { } @Override - public void update(MolangParticleInstance instance) { - if (instance.motionDynamic) return; - if (distanceSqr(instance.getX(), instance.getY(), instance.getZ()) > killDistanceSqr == instance.insideKillPlane) { - instance.remove(); + public void update(IMolangParticleInstance instance) { + if (instance.getPreset().motionDynamic) return; + if (distanceSqr(instance.getX(), instance.getY(), instance.getZ()) > killDistanceSqr == instance.isInsideKillPlane()) { + instance.self().remove(); } } @Override - public void apply(MolangParticleInstance instance) { - instance.insideKillPlane = distanceSqr(instance.getX(), instance.getY(), instance.getZ()) < killDistanceSqr; + public void apply(IMolangParticleInstance instance) { + instance.setInsideKillPlane(distanceSqr(instance.getX(), instance.getY(), instance.getZ()) < killDistanceSqr); } @Override diff --git a/src/main/java/org/mesdag/particlestorm/data/component/ParticleMotionCollision.java b/src/main/java/org/mesdag/particlestorm/data/component/ParticleMotionCollision.java index 037dd64..c238685 100644 --- a/src/main/java/org/mesdag/particlestorm/data/component/ParticleMotionCollision.java +++ b/src/main/java/org/mesdag/particlestorm/data/component/ParticleMotionCollision.java @@ -6,34 +6,35 @@ import net.minecraft.resources.ResourceLocation; import net.minecraft.util.ExtraCodecs; import net.minecraft.util.Mth; +import org.mesdag.particlestorm.api.IMolangParticleInstance; import org.mesdag.particlestorm.api.IParticleComponent; import org.mesdag.particlestorm.data.molang.BoolMolangExp; import org.mesdag.particlestorm.data.molang.MolangExp; -import org.mesdag.particlestorm.particle.MolangParticleInstance; import java.util.Collections; import java.util.List; import java.util.function.Function; -/** - * This component enables collisions between the terrain and the particle.

- * Collision detection in Minecraft consists of detecting an intersection,

- * moving to a nearby non-intersecting point for the particle (if possible),

- * and setting its direction to not be aimed towards the collision (usually perpendicular to the collision surface). - * - * @param enabled Enables collision when true/non-zero.

- * Evaluated every frame - * @param collisionDrag Alters the speed of the particle when it has collided

- * Useful for emulating friction/drag when colliding, e.g a particle that hits the ground would slow to a stop.

- * This drag slows down the particle by this amount in blocks/sec when in contact - * @param coefficientOfRestitution Used for bouncing/not-bouncing

- * Set to 0.0 to not bounce, 1.0 to bounce back up to original height and in-between to lose speed after bouncing.

- * Set to >1.0 to gain energy on each bounce - * @param collisionRadius Used to minimize interpenetration of particles with the environment

- * Note that this must be less than or equal to 1/2 block - * @param expireOnContact Triggers expiration on contact if true - * @param events Triggers an event array of individual events - */ +/// This component enables collisions between the terrain and the particle. +/// +/// Collision detection in Minecraft consists of detecting an intersection, +/// +/// moving to a nearby non-intersecting point for the particle (if possible), +/// +/// and setting its direction to not be aimed towards the collision (usually perpendicular to the collision surface). +/// +/// @param enabled Enables collision when true/non-zero.

+/// Evaluated every frame +/// @param collisionDrag Alters the speed of the particle when it has collided

+/// Useful for emulating friction/drag when colliding, e.g a particle that hits the ground would slow to a stop.

+/// This drag slows down the particle by this amount in blocks/sec when in contact +/// @param coefficientOfRestitution Used for bouncing/not-bouncing

+/// Set to 0.0 to not bounce, 1.0 to bounce back up to original height and in-between to lose speed after bouncing.

+/// Set to >1.0 to gain energy on each bounce +/// @param collisionRadius Used to minimize interpenetration of particles with the environment

+/// Note that this must be less than or equal to 1/2 block +/// @param expireOnContact Triggers expiration on contact if true +/// @param events Triggers an event array of individual events public record ParticleMotionCollision(BoolMolangExp enabled, float collisionDrag, float coefficientOfRestitution, float collisionRadius, boolean expireOnContact, List events) implements IParticleComponent { public static final ResourceLocation ID = ResourceLocation.withDefaultNamespace("particle_motion_collision"); public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( @@ -59,19 +60,19 @@ public List getAllMolangExp() { } @Override - public void update(MolangParticleInstance instance) { + public void update(IMolangParticleInstance instance) { instance.setCollision(enabled.get(instance)); } @Override - public void apply(MolangParticleInstance instance) { + public void apply(IMolangParticleInstance instance) { update(instance); - instance.collisionDrag = collisionDrag * instance.getInvTickRate(); - instance.coefficientOfRestitution = coefficientOfRestitution; + instance.setCollisionDrag(collisionDrag * instance.getInvTickRate()); + instance.setCoefficientOfRestitution(coefficientOfRestitution); float radius = Math.max(collisionRadius, Mth.EPSILON); - instance.setBoundingBox(instance.getBoundingBox().inflate(radius, 0.0, radius)); - instance.setLocationFromBoundingbox(); - instance.expireOnContact = expireOnContact; + instance.self().setBoundingBox(instance.self().getBoundingBox().inflate(radius, 0.0, radius)); + instance.self().setLocationFromBoundingbox(); + instance.setExpireOnContact(expireOnContact); } @Override diff --git a/src/main/java/org/mesdag/particlestorm/data/component/ParticleMotionDynamic.java b/src/main/java/org/mesdag/particlestorm/data/component/ParticleMotionDynamic.java index 0a1721a..ba974cb 100644 --- a/src/main/java/org/mesdag/particlestorm/data/component/ParticleMotionDynamic.java +++ b/src/main/java/org/mesdag/particlestorm/data/component/ParticleMotionDynamic.java @@ -3,37 +3,38 @@ import com.mojang.serialization.Codec; import com.mojang.serialization.codecs.RecordCodecBuilder; import net.minecraft.resources.ResourceLocation; +import org.joml.Vector3f; +import org.mesdag.particlestorm.api.IMolangParticleInstance; import org.mesdag.particlestorm.api.IParticleComponent; import org.mesdag.particlestorm.data.molang.FloatMolangExp; import org.mesdag.particlestorm.data.molang.FloatMolangExp3; import org.mesdag.particlestorm.data.molang.MolangExp; -import org.mesdag.particlestorm.particle.MolangParticleInstance; import java.util.List; -/** - * This component specifies the dynamic properties of the particle, from a simulation standpoint what forces act upon the particle?

- * These dynamics alter the velocity of the particle, which is a combination of the direction of the particle and the speed.

- * Particle direction will always be in the direction of the velocity of the particle. - * - * @param linerAcceleration The linear acceleration applied to the particle, defaults to [0, 0, 0].

- * Units are blocks/sec/sec

- * An example would be gravity which is [0, -9.8, 0]

- * Evaluated every frame - * @param linearDragCoefficient Equation is acceleration = -linear_drag_coefficient*velocity

- * Where velocity is the current direction times speed

- * Think of this as air-drag. The higher the exp, the more drag

- * Evaluated every frame - * @param rotationAcceleration Acceleration applies to the rotation speed of the particle

- * Think of a disc spinning up or a smoke puff that starts rotating but slows down over time

- * Evaluated every frame

- * Acceleration is in degrees/sec/sec - * @param rotationDragCoefficient Drag applied to slow down rotation

- * Equation is rotation_acceleration += -rotation_rate*rotation_drag_coefficient

- * Useful to slow a rotation, or to limit the rotation acceleration

- * Think of a disc that speeds up (acceleration) but reaches a terminal speed (drag)

- * Another use is if you have a particle growing in size, having the rotation slow down due to drag can add "weight" to the particle's motion - */ +/// This component specifies the dynamic properties of the particle, from a simulation standpoint what forces act upon the particle? +/// +/// These dynamics alter the velocity of the particle, which is a combination of the direction of the particle and the speed. +/// +/// Particle direction will always be in the direction of the velocity of the particle. +/// +/// @param linerAcceleration The linear acceleration applied to the particle, defaults to `[0, 0, 0]`.

+/// Units are blocks/sec/sec

+/// An example would be gravity which is `[0, -9.8, 0]`

+/// Evaluated every frame +/// @param linearDragCoefficient Equation is `acceleration = -linear_drag_coefficient*velocity`

+/// Where velocity is the current direction times speed

+/// Think of this as air-drag. The higher the exp, the more drag

+/// Evaluated every frame +/// @param rotationAcceleration Acceleration applies to the rotation speed of the particle

+/// Think of a disc spinning up or a smoke puff that starts rotating but slows down over time

+/// Evaluated every frame

+/// Acceleration is in degrees/sec/sec +/// @param rotationDragCoefficient Drag applied to slow down rotation

+/// Equation is `rotation_acceleration += -rotation_rate*rotation_drag_coefficient`

+/// Useful to slow a rotation, or to limit the rotation acceleration

+/// Think of a disc that speeds up (acceleration) but reaches a terminal speed (drag)

+/// Another use is if you have a particle growing in size, having the rotation slow down due to drag can add "weight" to the particle's motion public record ParticleMotionDynamic(FloatMolangExp3 linerAcceleration, FloatMolangExp linearDragCoefficient, FloatMolangExp rotationAcceleration, FloatMolangExp rotationDragCoefficient) implements IParticleComponent { public static final ResourceLocation ID = ResourceLocation.withDefaultNamespace("particle_motion_dynamic"); @@ -58,28 +59,29 @@ public List getAllMolangExp() { } @Override - public void update(MolangParticleInstance instance) { + public void update(IMolangParticleInstance instance) { apply(instance); } @Override - public void apply(MolangParticleInstance instance) { + public void apply(IMolangParticleInstance instance) { float invTickRate = instance.getInvTickRate(); float tickRate = instance.getLevel().tickRateManager().tickrate(); - instance.acceleration.set(linerAcceleration.calculate(instance)); + Vector3f acceleration = instance.getAcceleration(); + acceleration.set(linerAcceleration.calculate(instance)); float c = -linearDragCoefficient.calculate(instance); double xd = instance.getXd(); double yd = instance.getYd(); double zd = instance.getZd(); - instance.acceleration.add(c * (float) xd * tickRate, c * (float) yd * tickRate, c * (float) zd * tickRate); + acceleration.add(c * (float) xd * tickRate, c * (float) yd * tickRate, c * (float) zd * tickRate); float v = invTickRate * invTickRate; - instance.setParticleSpeed( - xd + instance.acceleration.x * v, - yd + instance.acceleration.y * v, - zd + instance.acceleration.z * v + instance.self().setParticleSpeed( + xd + acceleration.x * v, + yd + acceleration.y * v, + zd + acceleration.z * v ); - instance.rolld = (rotationAcceleration.calculate(instance) - rotationDragCoefficient.calculate(instance) * instance.rolld * tickRate) * v; + instance.setZRotD((rotationAcceleration.calculate(instance) - rotationDragCoefficient.calculate(instance) * instance.getZRotD() * tickRate) * v); } @Override diff --git a/src/main/java/org/mesdag/particlestorm/data/component/ParticleMotionParametric.java b/src/main/java/org/mesdag/particlestorm/data/component/ParticleMotionParametric.java index 8faed28..e725700 100644 --- a/src/main/java/org/mesdag/particlestorm/data/component/ParticleMotionParametric.java +++ b/src/main/java/org/mesdag/particlestorm/data/component/ParticleMotionParametric.java @@ -2,29 +2,27 @@ import com.mojang.serialization.Codec; import com.mojang.serialization.codecs.RecordCodecBuilder; +import org.mesdag.particlestorm.api.IMolangParticleInstance; import org.mesdag.particlestorm.api.IParticleComponent; import org.mesdag.particlestorm.data.molang.FloatMolangExp; import org.mesdag.particlestorm.data.molang.FloatMolangExp3; import org.mesdag.particlestorm.data.molang.MolangExp; -import org.mesdag.particlestorm.particle.MolangParticleInstance; import java.util.List; -/** - * This component directly controls the particle. - * - * @param relativePosition Directly set the position relative to the emitter. Defaults to [0, 0, 0]

- * E.g. a spiral might be: - *

- * "relative_position": ["Math.cos(Params.LifeTime)", 1.0, "Math.sin(Params.Lifetime)"] - *

- * Evaluated every frame - * @param direction Directly set the 3d direction of the particle

- * Doesn't affect direction if not specified

- * Evaluated every frame - * @param rotation Directly set the rotation of the particle

- * Evaluated every frame - */ +/// This component directly controls the particle. +/// +/// @param relativePosition Directly set the position relative to the emitter. Defaults to `[0, 0, 0]`

+/// E.g. a spiral might be: +/// ``` +/// "relative_position": ["Math.cos(Params.LifeTime)", 1.0, "Math.sin(Params.Lifetime)"] +/// ``` +/// Evaluated every frame +/// @param direction Directly set the 3d direction of the particle

+/// Doesn't affect direction if not specified

+/// Evaluated every frame +/// @param rotation Directly set the rotation of the particle

+/// Evaluated every frame public record ParticleMotionParametric(FloatMolangExp3 relativePosition, FloatMolangExp3 direction, FloatMolangExp rotation) implements IParticleComponent { public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( FloatMolangExp3.CODEC.fieldOf("relative_position").orElse(FloatMolangExp3.ZERO).forGetter(ParticleMotionParametric::relativePosition), @@ -46,14 +44,14 @@ public List getAllMolangExp() { } @Override - public void update(MolangParticleInstance instance) { + public void update(IMolangParticleInstance instance) { float[] pos = relativePosition.calculate(instance); instance.moveDirectly(pos[0], pos[1], pos[2]); if (direction != FloatMolangExp3.ZERO) { float[] dir = direction.calculate(instance); - instance.setParticleSpeed(dir[0], dir[1], dir[2]); + instance.self().setParticleSpeed(dir[0], dir[1], dir[2]); } - instance.setRoll(rotation.calculate(instance)); + instance.setZRot(rotation.calculate(instance)); } @Override diff --git a/src/main/java/org/mesdag/particlestorm/data/component/package-info.java b/src/main/java/org/mesdag/particlestorm/data/component/package-info.java new file mode 100644 index 0000000..73783b9 --- /dev/null +++ b/src/main/java/org/mesdag/particlestorm/data/component/package-info.java @@ -0,0 +1,7 @@ +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault +package org.mesdag.particlestorm.data.component; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/src/main/java/org/mesdag/particlestorm/data/curve/CurveType.java b/src/main/java/org/mesdag/particlestorm/data/curve/CurveType.java index f0b368a..4461319 100644 --- a/src/main/java/org/mesdag/particlestorm/data/curve/CurveType.java +++ b/src/main/java/org/mesdag/particlestorm/data/curve/CurveType.java @@ -2,7 +2,6 @@ import com.mojang.serialization.Codec; import net.minecraft.util.StringRepresentable; -import org.jetbrains.annotations.NotNull; import java.util.Locale; @@ -15,8 +14,7 @@ public enum CurveType implements StringRepresentable { public static final Codec CODEC = StringRepresentable.fromEnum(CurveType::values); @Override - public @NotNull String getSerializedName() { + public String getSerializedName() { return name().toLowerCase(Locale.ROOT); } - } diff --git a/src/main/java/org/mesdag/particlestorm/data/curve/package-info.java b/src/main/java/org/mesdag/particlestorm/data/curve/package-info.java new file mode 100644 index 0000000..1ce7cff --- /dev/null +++ b/src/main/java/org/mesdag/particlestorm/data/curve/package-info.java @@ -0,0 +1,7 @@ +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault +package org.mesdag.particlestorm.data.curve; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/src/main/java/org/mesdag/particlestorm/data/description/DescriptionMaterial.java b/src/main/java/org/mesdag/particlestorm/data/description/DescriptionMaterial.java index 5e0ab52..8e2c880 100644 --- a/src/main/java/org/mesdag/particlestorm/data/description/DescriptionMaterial.java +++ b/src/main/java/org/mesdag/particlestorm/data/description/DescriptionMaterial.java @@ -2,7 +2,6 @@ import com.mojang.serialization.Codec; import net.minecraft.util.StringRepresentable; -import org.jetbrains.annotations.NotNull; import java.util.Locale; @@ -22,7 +21,7 @@ public enum DescriptionMaterial implements StringRepresentable { public static final Codec CODEC = StringRepresentable.fromEnum(DescriptionMaterial::values); @Override - public @NotNull String getSerializedName() { + public String getSerializedName() { return name().toLowerCase(Locale.ROOT); } } diff --git a/src/main/java/org/mesdag/particlestorm/data/description/DescriptionParameters.java b/src/main/java/org/mesdag/particlestorm/data/description/DescriptionParameters.java index be0a02f..784c058 100644 --- a/src/main/java/org/mesdag/particlestorm/data/description/DescriptionParameters.java +++ b/src/main/java/org/mesdag/particlestorm/data/description/DescriptionParameters.java @@ -3,11 +3,14 @@ import com.mojang.serialization.Codec; import com.mojang.serialization.codecs.RecordCodecBuilder; import net.minecraft.resources.ResourceLocation; +import org.mesdag.particlestorm.ParticleStorm; public class DescriptionParameters { + public static final ResourceLocation MISSING_TEXTURE = ParticleStorm.asResource("missing"); + public static final DescriptionParameters EMPTY = new DescriptionParameters(DescriptionMaterial.CUSTOM, MISSING_TEXTURE); public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( - DescriptionMaterial.CODEC.fieldOf("material").orElse(DescriptionMaterial.PARTICLE_SHEET_TRANSLUCENT).forGetter(DescriptionParameters::material), - ResourceLocation.CODEC.fieldOf("texture").forGetter(DescriptionParameters::texture) + DescriptionMaterial.CODEC.lenientOptionalFieldOf("material", DescriptionMaterial.CUSTOM).orElse(DescriptionMaterial.PARTICLE_SHEET_TRANSLUCENT).forGetter(DescriptionParameters::material), + ResourceLocation.CODEC.lenientOptionalFieldOf("texture", MISSING_TEXTURE).forGetter(DescriptionParameters::texture) ).apply(instance, DescriptionParameters::new)); private final DescriptionMaterial material; private final ResourceLocation texture; diff --git a/src/main/java/org/mesdag/particlestorm/data/description/ParticleDescription.java b/src/main/java/org/mesdag/particlestorm/data/description/ParticleDescription.java index f32e948..c1f961b 100644 --- a/src/main/java/org/mesdag/particlestorm/data/description/ParticleDescription.java +++ b/src/main/java/org/mesdag/particlestorm/data/description/ParticleDescription.java @@ -2,11 +2,19 @@ import com.mojang.serialization.Codec; import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.minecraft.core.particles.ParticleType; +import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.resources.ResourceLocation; +import org.mesdag.particlestorm.ParticleStorm; -public record ParticleDescription(ResourceLocation identifier, DescriptionParameters parameters) { +public record ParticleDescription(ResourceLocation identifier, DescriptionParameters parameters, ParticleType type) { public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( ResourceLocation.CODEC.fieldOf("identifier").forGetter(ParticleDescription::identifier), - DescriptionParameters.CODEC.fieldOf("basic_render_parameters").forGetter(ParticleDescription::parameters) + DescriptionParameters.CODEC.lenientOptionalFieldOf("basic_render_parameters", DescriptionParameters.EMPTY).forGetter(ParticleDescription::parameters), + BuiltInRegistries.PARTICLE_TYPE.byNameCodec().fieldOf("type").orElseGet(ParticleStorm.MOLANG).forGetter(ParticleDescription::type) ).apply(instance, ParticleDescription::new)); + + public ParticleDescription(ResourceLocation identifier, DescriptionParameters parameters) { + this(identifier, parameters, ParticleStorm.MOLANG.get()); + } } diff --git a/src/main/java/org/mesdag/particlestorm/data/description/package-info.java b/src/main/java/org/mesdag/particlestorm/data/description/package-info.java new file mode 100644 index 0000000..326dd95 --- /dev/null +++ b/src/main/java/org/mesdag/particlestorm/data/description/package-info.java @@ -0,0 +1,7 @@ +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault +package org.mesdag.particlestorm.data.description; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/src/main/java/org/mesdag/particlestorm/data/event/NodeMolangExp.java b/src/main/java/org/mesdag/particlestorm/data/event/NodeMolangExp.java index 5fe1581..18999e1 100644 --- a/src/main/java/org/mesdag/particlestorm/data/event/NodeMolangExp.java +++ b/src/main/java/org/mesdag/particlestorm/data/event/NodeMolangExp.java @@ -1,16 +1,34 @@ package org.mesdag.particlestorm.data.event; +import com.mojang.datafixers.util.Either; import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import org.mesdag.particlestorm.ParticleStorm; import org.mesdag.particlestorm.api.IEventNode; import org.mesdag.particlestorm.api.MolangInstance; import org.mesdag.particlestorm.data.molang.MolangExp; import org.mesdag.particlestorm.data.molang.compiler.MolangParser; +import java.util.function.Function; + public final class NodeMolangExp extends MolangExp implements IEventNode { - public static final Codec CODEC = Codec.STRING.xmap(NodeMolangExp::new, NodeMolangExp::getExpStr); + public static final Codec DIRECT_CODEC = RecordCodecBuilder.create(instance -> instance.group( + Codec.STRING.fieldOf("exp").forGetter(NodeMolangExp::getExpStr), + Codec.BOOL.lenientOptionalFieldOf("log", false).forGetter(NodeMolangExp::shouldLog) + ).apply(instance, NodeMolangExp::new)); + public static final Codec CODEC = Codec.either(DIRECT_CODEC, Codec.STRING).xmap( + either -> either.map(Function.identity(), s -> new NodeMolangExp(s, false)), + e -> e.log ? Either.right(e.expStr) : Either.left(e) + ); + private final boolean log; - public NodeMolangExp(String expStr) { + public NodeMolangExp(String expStr, boolean log) { super(expStr); + this.log = log; + } + + public boolean shouldLog() { + return log; } @Override @@ -20,7 +38,10 @@ public void execute(MolangInstance instance) { this.variable = parser.compileMolang(expStr); } if (variable != null) { - variable.get(instance); + double v = variable.get(instance); + if (log) { + ParticleStorm.LOGGER.info("{}[{}]: {}={}", instance.getIdentity(), instance.getPosition(), expStr, v); + } } } } diff --git a/src/main/java/org/mesdag/particlestorm/data/event/ParticleEffect.java b/src/main/java/org/mesdag/particlestorm/data/event/ParticleEffect.java index 4b04874..3c12962 100644 --- a/src/main/java/org/mesdag/particlestorm/data/event/ParticleEffect.java +++ b/src/main/java/org/mesdag/particlestorm/data/event/ParticleEffect.java @@ -9,12 +9,11 @@ import net.minecraft.resources.ResourceLocation; import net.minecraft.util.ByIdMap; import net.minecraft.util.StringRepresentable; -import org.jetbrains.annotations.NotNull; import org.mesdag.particlestorm.PSGameClient; import org.mesdag.particlestorm.api.IEventNode; import org.mesdag.particlestorm.api.MolangInstance; import org.mesdag.particlestorm.data.molang.MolangExp; -import org.mesdag.particlestorm.data.molang.compiler.value.Variable; +import org.mesdag.particlestorm.data.molang.compiler.MolangQueries; import org.mesdag.particlestorm.particle.ParticleEmitter; import java.util.List; @@ -27,7 +26,10 @@ public record ParticleEffect(ResourceLocation effect, Type type, MolangExp preEf Type.CODEC.fieldOf("type").forGetter(ParticleEffect::type), MolangExp.CODEC.fieldOf("pre_effect_expression").orElse(MolangExp.EMPTY).forGetter(ParticleEffect::preEffectExpression), Codec.STRING.listOf().lenientOptionalFieldOf("shared_vars", List.of()).forGetter(ParticleEffect::sharedVars) - ).apply(instance, ParticleEffect::new)); + ).apply(instance, (rl, t, e, l) -> { + l = l.stream().map(s -> MolangQueries.applyPrefixAliases(s, "variable.", "v.")).toList(); + return new ParticleEffect(rl, t, e, l); + })); public ParticleEffect(ResourceLocation effect, Type type, MolangExp preEffectExpression) { this(effect, type, preEffectExpression, List.of()); @@ -35,36 +37,7 @@ public ParticleEffect(ResourceLocation effect, Type type, MolangExp preEffectExp @Override public void execute(MolangInstance instance) { - ParticleEmitter emitter = new ParticleEmitter(instance.getLevel(), instance.getPosition(), effect, preEffectExpression); - emitter.afterParentInit = parent -> { - switch (type) { - case EMITTER -> {} - case EMITTER_BOUND -> { - emitter.attachEntity(parent.getAttachedEntity()); - emitter.attachedBlock = parent.attachedBlock; - emitter.offsetPos = parent.offsetPos; - emitter.offsetRot = parent.offsetRot; - emitter.parentPosition = parent.parentPosition; - emitter.parentRotation = parent.parentRotation; - emitter.parentMode = parent.parentMode; - } - case PARTICLE -> emitter.isManual = true; - case PARTICLE_WITH_VELOCITY -> { - emitter.isManual = true; - if (parent.getAttachedEntity() != null) { - emitter.inheritedParticleSpeed = parent.getAttachedEntity().getDeltaMovement().toVector3f(); - } - } - } - }; - ParticleEmitter parent = instance.getEmitter(); - emitter.addParent(parent); - for (String name : sharedVars) { - Variable variable = parent.getVars().table.get(name); - if (variable == null) throw new IllegalArgumentException("Shared vars must defined in parent directly!"); - emitter.getVars().setValue(name, variable); - } - PSGameClient.LOADER.addEmitter(emitter, false); + PSGameClient.LOADER.addEmitter(new ParticleEmitter(instance.getEmitter(), this), false); } @Override @@ -78,25 +51,17 @@ public String toString() { } public enum Type implements StringRepresentable { - /** - * Create an emitter of the specified particle effect at the event's world location - */ + /// Create an emitter of the specified particle effect at the event's world location EMITTER, - /** - * Create an emitter of the specified particle effect at the event's location. - *

- * If the firing emitter is bound to an entity or locator, the new emitter will be bound to the same entity or locator. - */ + /// Create an emitter of the specified particle effect at the event's location. + /// + /// If the firing emitter is bound to an entity or locator, the new emitter will be bound to the same entity or locator. EMITTER_BOUND, - /** - * Manually emit a particle on an emitter of the specified type at the event location, creating the emitter if it doesn't already exist. - *

- * Make sure to use the Spawn Amount mode "Manual" on the child particle effect. - */ + /// Manually emit a particle on an emitter of the specified type at the event location, creating the emitter if it doesn't already exist. + /// + /// Make sure to use the Spawn Amount mode "Manual" on the child particle effect. PARTICLE, - /** - * The same as "Particle" except the new particle will inherit the spawning particle's velocity. - */ + /// The same as "Particle" except the new particle will inherit the spawning particle's velocity. PARTICLE_WITH_VELOCITY; public static final Codec CODEC = StringRepresentable.fromEnum(Type::values); @@ -111,7 +76,7 @@ public int getId() { } @Override - public @NotNull String getSerializedName() { + public String getSerializedName() { return name().toLowerCase(Locale.ROOT); } diff --git a/src/main/java/org/mesdag/particlestorm/data/event/package-info.java b/src/main/java/org/mesdag/particlestorm/data/event/package-info.java new file mode 100644 index 0000000..eaadc24 --- /dev/null +++ b/src/main/java/org/mesdag/particlestorm/data/event/package-info.java @@ -0,0 +1,7 @@ +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault +package org.mesdag.particlestorm.data.event; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/src/main/java/org/mesdag/particlestorm/data/molang/FloatMolangExp2.java b/src/main/java/org/mesdag/particlestorm/data/molang/FloatMolangExp2.java index ea02f35..6c3a3ec 100644 --- a/src/main/java/org/mesdag/particlestorm/data/molang/FloatMolangExp2.java +++ b/src/main/java/org/mesdag/particlestorm/data/molang/FloatMolangExp2.java @@ -16,6 +16,7 @@ public float[] calculate(MolangInstance instance) { return new float[]{exp1.calculate(instance), exp2.calculate(instance)}; } + @Deprecated public boolean initialized() { return exp1.initialized() && exp2.initialized(); } diff --git a/src/main/java/org/mesdag/particlestorm/data/molang/MolangExp.java b/src/main/java/org/mesdag/particlestorm/data/molang/MolangExp.java index 574112b..01585c4 100644 --- a/src/main/java/org/mesdag/particlestorm/data/molang/MolangExp.java +++ b/src/main/java/org/mesdag/particlestorm/data/molang/MolangExp.java @@ -2,21 +2,21 @@ import com.mojang.serialization.Codec; import io.netty.buffer.ByteBuf; +import net.minecraft.Util; import net.minecraft.network.codec.ByteBufCodecs; import net.minecraft.network.codec.StreamCodec; import org.mesdag.particlestorm.api.MolangInstance; import org.mesdag.particlestorm.data.molang.compiler.MathValue; import org.mesdag.particlestorm.data.molang.compiler.MolangParser; +import org.mesdag.particlestorm.data.molang.compiler.MolangQueries; +import org.mesdag.particlestorm.data.molang.compiler.value.Constant; import java.util.Map; public class MolangExp { - public static final MolangExp EMPTY = new MolangExp(""); - public static final Codec CODEC = Codec.STRING.xmap(MolangExp::new, e -> e.expStr); - public static final StreamCodec STREAM_CODEC = StreamCodec.composite( - ByteBufCodecs.STRING_UTF8, e -> e.expStr, - MolangExp::new - ); + public static final MolangExp EMPTY = Util.make(new MolangExp(""), exp -> exp.variable = new Constant(0.0)); + public static final Codec CODEC = Codec.STRING.xmap(MolangExp::new, MolangExp::getExpStr); + public static final StreamCodec STREAM_CODEC = ByteBufCodecs.STRING_UTF8.map(MolangExp::new, MolangExp::getExpStr); protected final String expStr; protected MathValue variable; @@ -25,18 +25,26 @@ public MolangExp(String expStr) { } public MolangExp(String key, double value) { - if (!key.startsWith("variable.")) key = "variable." + key; - this.expStr = key + "=" + value + ";"; + this.expStr = MolangQueries.applyPrefixAliases(key, "variable.", "v.") + "=" + value; } public MolangExp(Map exps) { StringBuilder builder = new StringBuilder(); + int i = 0; for (Map.Entry entry : exps.entrySet()) { - builder.append(entry.getKey()).append('=').append(entry.getValue()).append(';'); + builder.append(entry.getKey()).append('=').append(entry.getValue()); + if (++i < exps.size()) { + builder.append(';'); + } } this.expStr = builder.toString(); } + public MolangExp(MathValue variable) { + this.expStr = variable.toString(); + this.variable = variable; + } + public String getExpStr() { return expStr; } diff --git a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/MolangParser.java b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/MolangParser.java index f6f1f8f..191f095 100644 --- a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/MolangParser.java +++ b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/MolangParser.java @@ -3,7 +3,6 @@ import com.mojang.datafixers.util.Either; import it.unimi.dsi.fastutil.objects.ObjectArrayList; import net.minecraft.Util; -import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.mesdag.particlestorm.data.molang.VariableTable; import org.mesdag.particlestorm.data.molang.compiler.function.MathFunction; @@ -403,7 +402,7 @@ public static boolean isOperativeSymbol(char symbol) { } @Deprecated(forRemoval = true) - public static boolean isOperativeSymbol(@NotNull String symbol) { + public static boolean isOperativeSymbol(String symbol) { return Operator.isOperator(symbol) || symbol.equals("?") || symbol.equals(":"); } diff --git a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/MolangQueries.java b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/MolangQueries.java index d39c8cc..091142a 100644 --- a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/MolangQueries.java +++ b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/MolangQueries.java @@ -7,8 +7,8 @@ import org.mesdag.particlestorm.api.MolangInstance; import org.mesdag.particlestorm.api.RegisterMolangQueriesEvent; import org.mesdag.particlestorm.data.molang.compiler.value.Variable; -import org.mesdag.particlestorm.mixin.ParticleEngineAccessor; +import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -80,13 +80,7 @@ private static void setDefaultQueryValues() { registerQueryVariable("query.time_of_day", p -> p.getLevel().getDayTime() / 24000f); registerQueryVariable("query.time_stamp", p -> p.getLevel().getGameTime()); registerQueryVariable("query.total_emitter_count", p -> PSGameClient.LOADER.totalEmitterCount()); - registerQueryVariable("query.total_particle_count", p -> { - int sum = 0; - for (Integer value : ((ParticleEngineAccessor) Minecraft.getInstance().particleEngine).trackedParticleCounts().values()) { - sum += value; - } - return sum; - }); + registerQueryVariable("query.total_particle_count", p -> Minecraft.getInstance().particleEngine.particles.values().stream().mapToInt(Collection::size).sum()); registerQueryVariable("query.attached_x", p -> p.getAttachedEntity() == null ? 0.0 : p.getAttachedEntity().getX()); registerQueryVariable("query.attached_y", p -> p.getAttachedEntity() == null ? 0.0 : p.getAttachedEntity().getY()); registerQueryVariable("query.attached_z", p -> p.getAttachedEntity() == null ? 0.0 : p.getAttachedEntity().getZ()); diff --git a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/Operator.java b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/Operator.java index bd548ab..5e4feae 100644 --- a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/Operator.java +++ b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/Operator.java @@ -4,7 +4,6 @@ import it.unimi.dsi.fastutil.chars.CharSet; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import net.minecraft.Util; -import org.jetbrains.annotations.NotNull; import java.util.Arrays; import java.util.Map; @@ -103,7 +102,7 @@ public double compute(double argA, double argB) { } @Override - public int compareTo(@NotNull Operator operator) { + public int compareTo(Operator operator) { return Integer.compare(this.precedence, operator.precedence); } diff --git a/src/main/java/org/mesdag/particlestorm/mixed/IAnimationController.java b/src/main/java/org/mesdag/particlestorm/mixed/IAnimationController.java index 10c827b..02a1410 100644 --- a/src/main/java/org/mesdag/particlestorm/mixed/IAnimationController.java +++ b/src/main/java/org/mesdag/particlestorm/mixed/IAnimationController.java @@ -1,11 +1,17 @@ package org.mesdag.particlestorm.mixed; +import software.bernie.geckolib.animation.AnimationController; import software.bernie.geckolib.cache.object.GeoBone; +import java.util.Collection; import java.util.List; public interface IAnimationController { List particlestorm$getBonesWhichHasLocators(); - void particlestorm$setBonesWhichHasLocators(List bones); + void particlestorm$setBonesWhichHasLocators(Collection registeredBones); + + static IAnimationController of(AnimationController controller) { + return (IAnimationController) controller; + } } diff --git a/src/main/java/org/mesdag/particlestorm/mixed/IParticleEngine.java b/src/main/java/org/mesdag/particlestorm/mixed/IParticleEngine.java new file mode 100644 index 0000000..62bcf93 --- /dev/null +++ b/src/main/java/org/mesdag/particlestorm/mixed/IParticleEngine.java @@ -0,0 +1,11 @@ +package org.mesdag.particlestorm.mixed; + +import net.minecraft.client.particle.ParticleEngine; + +public interface IParticleEngine { + void particlestorm$bindSprites(); + + static IParticleEngine of(ParticleEngine engine) { + return (IParticleEngine) engine; + } +} diff --git a/src/main/java/org/mesdag/particlestorm/mixed/ITextureAtlas.java b/src/main/java/org/mesdag/particlestorm/mixed/ITextureAtlas.java deleted file mode 100644 index cc4c4c0..0000000 --- a/src/main/java/org/mesdag/particlestorm/mixed/ITextureAtlas.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.mesdag.particlestorm.mixed; - -import net.minecraft.client.renderer.texture.SpriteLoader; - -import java.util.function.Consumer; - -public interface ITextureAtlas { - void particlestorm$consume(Consumer consumer); -} diff --git a/src/main/java/org/mesdag/particlestorm/mixin/LivingEntityMixin.java b/src/main/java/org/mesdag/particlestorm/mixin/LivingEntityMixin.java index ac0c32d..919916b 100644 --- a/src/main/java/org/mesdag/particlestorm/mixin/LivingEntityMixin.java +++ b/src/main/java/org/mesdag/particlestorm/mixin/LivingEntityMixin.java @@ -2,11 +2,11 @@ import com.llamalad7.mixinextras.injector.v2.WrapWithCondition; import net.minecraft.core.particles.ParticleOptions; +import net.minecraft.resources.ResourceLocation; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.level.Level; import org.mesdag.particlestorm.PSGameClient; import org.mesdag.particlestorm.particle.MolangParticleOption; -import org.mesdag.particlestorm.particle.ParticleEmitter; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; @@ -14,11 +14,8 @@ public abstract class LivingEntityMixin { @WrapWithCondition(method = "tickEffects", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/Level;addParticle(Lnet/minecraft/core/particles/ParticleOptions;DDDDDD)V")) private boolean modify(Level instance, ParticleOptions particleData, double x, double y, double z, double xSpeed, double ySpeed, double zSpeed) { - if (particleData instanceof MolangParticleOption molang) { - LivingEntity self = (LivingEntity) (Object) this; - ParticleEmitter emitter = new ParticleEmitter(instance, self.position(), molang.getId()); - emitter.attachEntity(self); - PSGameClient.LOADER.addTrackedEmitter(self, emitter); + if (particleData instanceof MolangParticleOption(ResourceLocation id)) { + PSGameClient.LOADER.addTrackedEmitter((LivingEntity) (Object) this, id); return false; } return true; diff --git a/src/main/java/org/mesdag/particlestorm/mixin/MinecraftMixin.java b/src/main/java/org/mesdag/particlestorm/mixin/MinecraftMixin.java index e87b89e..9758646 100644 --- a/src/main/java/org/mesdag/particlestorm/mixin/MinecraftMixin.java +++ b/src/main/java/org/mesdag/particlestorm/mixin/MinecraftMixin.java @@ -1,17 +1,8 @@ package org.mesdag.particlestorm.mixin; import net.minecraft.client.Minecraft; -import net.minecraft.client.main.GameConfig; import net.minecraft.client.particle.ParticleEngine; -import net.minecraft.client.renderer.texture.TextureAtlas; -import net.minecraft.client.renderer.texture.TextureAtlasSprite; -import net.minecraft.resources.ResourceLocation; -import org.mesdag.particlestorm.PSGameClient; -import org.mesdag.particlestorm.ParticleStorm; -import org.mesdag.particlestorm.data.DefinedParticleEffect; -import org.mesdag.particlestorm.mixed.ITextureAtlas; -import org.mesdag.particlestorm.particle.ExtendMutableSpriteSet; -import org.mesdag.particlestorm.particle.MolangParticleInstance; +import org.mesdag.particlestorm.mixed.IParticleEngine; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; @@ -20,38 +11,14 @@ import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -import java.util.Map; - @Mixin(Minecraft.class) public abstract class MinecraftMixin { @Shadow @Final public ParticleEngine particleEngine; - @Inject(method = "", at = @At(value = "INVOKE", target = "Lnet/neoforged/neoforge/client/ClientHooks;onRegisterParticleProviders(Lnet/minecraft/client/particle/ParticleEngine;)V", remap = false)) - private void registerCustom(GameConfig gameConfig, CallbackInfo ci) { - ExtendMutableSpriteSet extendMutableSpriteSet = new ExtendMutableSpriteSet(); - ((ParticleEngineAccessor) particleEngine).spriteSets().put(ParticleStorm.MOLANG.getId(), extendMutableSpriteSet); - ((ParticleEngineAccessor) particleEngine).providers().put(ParticleStorm.MOLANG.getId(), new MolangParticleInstance.Provider(extendMutableSpriteSet)); - } - @Inject(method = "onResourceLoadFinished", at = @At("TAIL")) private void onLoaded(@Coerce Object gameLoadCookie, CallbackInfo ci) { - if (((ParticleEngineAccessor) particleEngine).spriteSets().get(ParticleStorm.MOLANG.getId()) instanceof ExtendMutableSpriteSet spriteSet) { - try (TextureAtlas textureAtlas = ((ParticleEngineAccessor) particleEngine).textureAtlas()) { - ((ITextureAtlas) textureAtlas).particlestorm$consume(preparations -> { - spriteSet.clear(); - int i = 0; - for (Map.Entry entry : PSGameClient.LOADER.id2Effect().entrySet()) { - TextureAtlasSprite missing = preparations.missing(); - spriteSet.bindMissing(missing); - ResourceLocation texture = entry.getValue().description.parameters().bindTexture(i); - TextureAtlasSprite sprite = preparations.regions().get(texture); - spriteSet.addSprite(sprite == null ? missing : sprite); - i++; - } - }); - } - } + IParticleEngine.of(particleEngine).particlestorm$bindSprites(); } } diff --git a/src/main/java/org/mesdag/particlestorm/mixin/ParticleEngineAccessor.java b/src/main/java/org/mesdag/particlestorm/mixin/ParticleEngineAccessor.java deleted file mode 100644 index 6fc5607..0000000 --- a/src/main/java/org/mesdag/particlestorm/mixin/ParticleEngineAccessor.java +++ /dev/null @@ -1,33 +0,0 @@ -package org.mesdag.particlestorm.mixin; - -import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; -import net.minecraft.client.particle.Particle; -import net.minecraft.client.particle.ParticleEngine; -import net.minecraft.client.particle.ParticleProvider; -import net.minecraft.client.renderer.texture.TextureAtlas; -import net.minecraft.core.particles.ParticleGroup; -import net.minecraft.core.particles.ParticleOptions; -import net.minecraft.resources.ResourceLocation; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.gen.Accessor; -import org.spongepowered.asm.mixin.gen.Invoker; - -import java.util.Map; - -@Mixin(ParticleEngine.class) -public interface ParticleEngineAccessor { - @Accessor("spriteSets") - Map spriteSets(); - - @Accessor("providers") - Map> providers(); - - @Accessor("textureAtlas") - TextureAtlas textureAtlas(); - - @Accessor("trackedParticleCounts") - Object2IntOpenHashMap trackedParticleCounts(); - - @Invoker - Particle callMakeParticle(T particleData, double x, double y, double z, double xSpeed, double ySpeed, double zSpeed); -} diff --git a/src/main/java/org/mesdag/particlestorm/mixin/ParticleEngineMixin.java b/src/main/java/org/mesdag/particlestorm/mixin/ParticleEngineMixin.java new file mode 100644 index 0000000..55af078 --- /dev/null +++ b/src/main/java/org/mesdag/particlestorm/mixin/ParticleEngineMixin.java @@ -0,0 +1,58 @@ +package org.mesdag.particlestorm.mixin; + +import net.minecraft.client.particle.ParticleEngine; +import net.minecraft.client.renderer.texture.SpriteLoader; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.resources.ResourceLocation; +import org.mesdag.particlestorm.PSGameClient; +import org.mesdag.particlestorm.ParticleStorm; +import org.mesdag.particlestorm.api.RegisterCustomParticleTypeEvent; +import org.mesdag.particlestorm.data.DefinedParticleEffect; +import org.mesdag.particlestorm.mixed.IParticleEngine; +import org.mesdag.particlestorm.particle.ExtendMutableSpriteSet; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.ModifyArg; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.util.Map; + +@Mixin(ParticleEngine.class) +public abstract class ParticleEngineMixin implements IParticleEngine { + @Shadow + @Final + private Map spriteSets; + + @Unique + private volatile SpriteLoader.Preparations particlestorm$preparations; + + @Override + public void particlestorm$bindSprites() { + if (particlestorm$preparations != null && spriteSets.get(ParticleStorm.MOLANG.getId()) instanceof ExtendMutableSpriteSet spriteSet) { + spriteSet.clear(); + int i = 0; + for (Map.Entry entry : PSGameClient.LOADER.id2Effect().entrySet()) { + TextureAtlasSprite missing = particlestorm$preparations.missing(); + spriteSet.bindMissing(missing); + ResourceLocation texture = entry.getValue().description.parameters().bindTexture(i); + spriteSet.addSprite(particlestorm$preparations.regions().getOrDefault(texture, missing)); + i++; + } + } + this.particlestorm$preparations = null; + } + + @Inject(method = "registerProviders", at = @At("TAIL")) + private void registerCustom(CallbackInfo ci) { + RegisterCustomParticleTypeEvent.postEvent(spriteSets); + } + + @ModifyArg(method = "lambda$reload$9", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/texture/TextureAtlas;upload(Lnet/minecraft/client/renderer/texture/SpriteLoader$Preparations;)V")) + private SpriteLoader.Preparations cachePreparations(SpriteLoader.Preparations preparations) { + return this.particlestorm$preparations = preparations; + } +} diff --git a/src/main/java/org/mesdag/particlestorm/mixin/TextureAtlasMixin.java b/src/main/java/org/mesdag/particlestorm/mixin/TextureAtlasMixin.java deleted file mode 100644 index f9e5a0d..0000000 --- a/src/main/java/org/mesdag/particlestorm/mixin/TextureAtlasMixin.java +++ /dev/null @@ -1,31 +0,0 @@ -package org.mesdag.particlestorm.mixin; - -import net.minecraft.client.renderer.texture.SpriteLoader; -import net.minecraft.client.renderer.texture.TextureAtlas; -import org.mesdag.particlestorm.mixed.ITextureAtlas; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Unique; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -import java.util.function.Consumer; - -@Mixin(TextureAtlas.class) -public abstract class TextureAtlasMixin implements ITextureAtlas { - @Unique - private SpriteLoader.Preparations particlestorm$preparations; - - @Override - public void particlestorm$consume(Consumer consumer) { - if (particlestorm$preparations != null) { - consumer.accept(particlestorm$preparations); - this.particlestorm$preparations = null; - } - } - - @Inject(method = "upload", at = @At("HEAD")) - private void storePreparations(SpriteLoader.Preparations preparations, CallbackInfo ci) { - this.particlestorm$preparations = preparations; - } -} diff --git a/src/main/java/org/mesdag/particlestorm/mixin/integration/geckolib/AnimatableInstanceCacheMixin.java b/src/main/java/org/mesdag/particlestorm/mixin/integration/geckolib/AnimatableInstanceCacheMixin.java index ec7782b..e154110 100644 --- a/src/main/java/org/mesdag/particlestorm/mixin/integration/geckolib/AnimatableInstanceCacheMixin.java +++ b/src/main/java/org/mesdag/particlestorm/mixin/integration/geckolib/AnimatableInstanceCacheMixin.java @@ -18,8 +18,8 @@ public class AnimatableInstanceCacheMixin implements IAnimatableInstanceCache { private Vector3f particlestorm$position; @Unique private Vector3f particlestorm$rotation; - @Unique - private Vector3f particlestorm$scale; +// @Unique +// private Vector3f particlestorm$scale; @Override public Object2IntMap particlestorm$getCachedId() { diff --git a/src/main/java/org/mesdag/particlestorm/mixin/integration/geckolib/AnimationControllerMixin.java b/src/main/java/org/mesdag/particlestorm/mixin/integration/geckolib/AnimationControllerMixin.java index f085d35..297ba30 100644 --- a/src/main/java/org/mesdag/particlestorm/mixin/integration/geckolib/AnimationControllerMixin.java +++ b/src/main/java/org/mesdag/particlestorm/mixin/integration/geckolib/AnimationControllerMixin.java @@ -6,17 +6,21 @@ import org.apache.logging.log4j.Logger; import org.mesdag.particlestorm.api.geckolib.GeckoLibHelper; import org.mesdag.particlestorm.mixed.IAnimationController; +import org.mesdag.particlestorm.mixed.IGeoBone; import org.spongepowered.asm.mixin.*; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import software.bernie.geckolib.animatable.GeoAnimatable; import software.bernie.geckolib.animation.AnimationController; -import software.bernie.geckolib.animation.keyframe.event.ParticleKeyframeEvent; import software.bernie.geckolib.animation.keyframe.event.data.ParticleKeyframeData; import software.bernie.geckolib.cache.object.GeoBone; +import software.bernie.geckolib.loading.json.raw.LocatorValue; +import java.util.Collection; import java.util.List; +import java.util.Map; +import java.util.Objects; @Pseudo @Mixin(targets = "software.bernie.geckolib.animation.AnimationController", remap = false) @@ -32,23 +36,28 @@ public abstract class AnimationControllerMixin implemen @Override public List particlestorm$getBonesWhichHasLocators() { - return particlestorm$bonesWhichHasLocators; + return Objects.requireNonNullElse(particlestorm$bonesWhichHasLocators, List.of()); } @Override - public void particlestorm$setBonesWhichHasLocators(List bones) { - this.particlestorm$bonesWhichHasLocators = bones; + public void particlestorm$setBonesWhichHasLocators(Collection registeredBones) { + if (particlestorm$bonesWhichHasLocators == null) { + this.particlestorm$bonesWhichHasLocators = registeredBones.stream().filter(bone -> { + Map locators = IGeoBone.of(bone).particlestorm$getLocators(); + return locators != null && !locators.isEmpty(); + }).toList(); + } } @WrapWithCondition(method = "processCurrentAnimation", at = @At(value = "INVOKE", target = "Lorg/apache/logging/log4j/Logger;log(Lorg/apache/logging/log4j/Level;Ljava/lang/String;)V", ordinal = 1)) private boolean processParticleEffect(Logger instance, Level level, String s, @Local(argsOnly = true, ordinal = 0) double adjustedTick, @Local ParticleKeyframeData keyframeData) { - return GeckoLibHelper.processParticleEffect(new ParticleKeyframeEvent<>(animatable, adjustedTick, (AnimationController) (Object) this, keyframeData)); + return GeckoLibHelper.processParticleEffect(animatable, this, keyframeData); } @Inject(method = "resetEventKeyFrames", at = @At("HEAD")) private void removeEmitters(CallbackInfo ci) { - if (particlestorm$bonesWhichHasLocators != null) { - GeckoLibHelper.removeEmittersWhenAnimationChange(particlestorm$bonesWhichHasLocators.size(), animationState, animatable.getAnimatableInstanceCache()); + if (!particlestorm$getBonesWhichHasLocators().isEmpty()) { + GeckoLibHelper.removeEmittersWhenAnimationChange(animationState, animatable.getAnimatableInstanceCache()); } } } diff --git a/src/main/java/org/mesdag/particlestorm/mixin/integration/geckolib/AnimationProcessorMixin.java b/src/main/java/org/mesdag/particlestorm/mixin/integration/geckolib/AnimationProcessorMixin.java index c77dd60..3c80d79 100644 --- a/src/main/java/org/mesdag/particlestorm/mixin/integration/geckolib/AnimationProcessorMixin.java +++ b/src/main/java/org/mesdag/particlestorm/mixin/integration/geckolib/AnimationProcessorMixin.java @@ -2,11 +2,9 @@ import com.llamalad7.mixinextras.sugar.Local; import org.mesdag.particlestorm.mixed.IAnimationController; -import org.mesdag.particlestorm.mixed.IGeoBone; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Pseudo; import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @@ -18,8 +16,6 @@ import software.bernie.geckolib.model.GeoModel; import java.util.Collection; -import java.util.List; -import java.util.stream.Collectors; @Pseudo @Mixin(targets = "software.bernie.geckolib.animation.AnimationProcessor", remap = false) @@ -27,16 +23,8 @@ public abstract class AnimationProcessorMixin { @Shadow public abstract Collection getRegisteredBones(); - @Unique - private List particlestorm$bonesWhichHasLocators; - @Inject(method = "tickAnimation", at = @At(value = "INVOKE", target = "Lsoftware/bernie/geckolib/animation/AnimationController;process(Lsoftware/bernie/geckolib/model/GeoModel;Lsoftware/bernie/geckolib/animation/AnimationState;Ljava/util/Map;Ljava/util/Map;DZ)V")) private void tickLocators(T animatable, GeoModel model, AnimatableManager animatableManager, double animTime, AnimationState state, boolean crashWhenCantFindBone, CallbackInfo ci, @Local AnimationController controller) { - if (particlestorm$bonesWhichHasLocators == null) { - this.particlestorm$bonesWhichHasLocators = getRegisteredBones().stream() - .filter(bone -> IGeoBone.of(bone).particlestorm$getLocators() != null) - .collect(Collectors.toList()); - } - ((IAnimationController) controller).particlestorm$setBonesWhichHasLocators(particlestorm$bonesWhichHasLocators); + IAnimationController.of(controller).particlestorm$setBonesWhichHasLocators(getRegisteredBones()); } } diff --git a/src/main/java/org/mesdag/particlestorm/mixin/integration/geckolib/GeoBoneMixin.java b/src/main/java/org/mesdag/particlestorm/mixin/integration/geckolib/GeoBoneMixin.java index 1dfd1aa..05c197e 100644 --- a/src/main/java/org/mesdag/particlestorm/mixin/integration/geckolib/GeoBoneMixin.java +++ b/src/main/java/org/mesdag/particlestorm/mixin/integration/geckolib/GeoBoneMixin.java @@ -2,7 +2,6 @@ import org.mesdag.particlestorm.mixed.IAnimatableInstanceCache; import org.mesdag.particlestorm.mixed.IGeoBone; -import org.mesdag.particlestorm.mixin.MolangQueriesAccessor; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Pseudo; import org.spongepowered.asm.mixin.Shadow; @@ -52,7 +51,7 @@ public abstract class GeoBoneMixin implements IGeoBone { @Inject(method = "resetStateChanges", at = @At("TAIL")) private void setData(CallbackInfo ci) { - if (particlestorm$locators == null) return; + if (particlestorm$locators == null || particlestorm$locators.isEmpty()) return; MolangQueries.Actor actor = MolangQueriesAccessor.callGetActor(); if (actor != null && actor.animatable() instanceof GeoAnimatable animatable) { IAnimatableInstanceCache cache = IAnimatableInstanceCache.of(animatable.getAnimatableInstanceCache()); diff --git a/src/main/java/org/mesdag/particlestorm/mixin/MolangQueriesAccessor.java b/src/main/java/org/mesdag/particlestorm/mixin/integration/geckolib/MolangQueriesAccessor.java similarity index 62% rename from src/main/java/org/mesdag/particlestorm/mixin/MolangQueriesAccessor.java rename to src/main/java/org/mesdag/particlestorm/mixin/integration/geckolib/MolangQueriesAccessor.java index 4509622..d3e1f6a 100644 --- a/src/main/java/org/mesdag/particlestorm/mixin/MolangQueriesAccessor.java +++ b/src/main/java/org/mesdag/particlestorm/mixin/integration/geckolib/MolangQueriesAccessor.java @@ -1,10 +1,12 @@ -package org.mesdag.particlestorm.mixin; +package org.mesdag.particlestorm.mixin.integration.geckolib; import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Pseudo; import org.spongepowered.asm.mixin.gen.Invoker; import software.bernie.geckolib.loading.math.MolangQueries; -@Mixin(MolangQueries.class) +@Pseudo +@Mixin(targets = "software.bernie.geckolib.loading.math.MolangQueries") public interface MolangQueriesAccessor { @Invoker static MolangQueries.Actor callGetActor() {throw new UnsupportedOperationException();} diff --git a/src/main/java/org/mesdag/particlestorm/mixin/integration/geckolib/MolangQueriesMixin.java b/src/main/java/org/mesdag/particlestorm/mixin/integration/geckolib/MolangQueriesMixin.java index 6363593..1411552 100644 --- a/src/main/java/org/mesdag/particlestorm/mixin/integration/geckolib/MolangQueriesMixin.java +++ b/src/main/java/org/mesdag/particlestorm/mixin/integration/geckolib/MolangQueriesMixin.java @@ -2,7 +2,6 @@ import net.minecraft.client.Minecraft; import org.mesdag.particlestorm.PSGameClient; -import org.mesdag.particlestorm.mixin.ParticleEngineAccessor; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Pseudo; import org.spongepowered.asm.mixin.injection.At; @@ -19,7 +18,7 @@ private static void particleQueries(CallbackInfo ci) { setActorVariable("query.total_emitter_count", actor -> PSGameClient.LOADER.totalEmitterCount()); setActorVariable("query.total_particle_count", actor -> { int sum = 0; - for (Integer value : ((ParticleEngineAccessor) Minecraft.getInstance().particleEngine).trackedParticleCounts().values()) { + for (Integer value : Minecraft.getInstance().particleEngine.trackedParticleCounts.values()) { sum += value; } return sum; diff --git a/src/main/java/org/mesdag/particlestorm/network/EmitterAttachPacketS2C.java b/src/main/java/org/mesdag/particlestorm/network/EmitterAttachPacketS2C.java index dcb69e0..81fe611 100644 --- a/src/main/java/org/mesdag/particlestorm/network/EmitterAttachPacketS2C.java +++ b/src/main/java/org/mesdag/particlestorm/network/EmitterAttachPacketS2C.java @@ -10,7 +10,6 @@ import net.minecraft.world.entity.player.Player; import net.neoforged.neoforge.network.PacketDistributor; import net.neoforged.neoforge.network.handling.IPayloadContext; -import org.jetbrains.annotations.NotNull; import org.mesdag.particlestorm.PSGameClient; import org.mesdag.particlestorm.ParticleStorm; import org.mesdag.particlestorm.particle.ParticleEmitter; @@ -24,7 +23,7 @@ public record EmitterAttachPacketS2C(int particleId, int entityId) implements Cu ); @Override - public @NotNull Type type() { + public Type type() { return TYPE; } diff --git a/src/main/java/org/mesdag/particlestorm/network/EmitterCreationPacketS2C.java b/src/main/java/org/mesdag/particlestorm/network/EmitterCreationPacketS2C.java index 5e2a64b..d91be43 100644 --- a/src/main/java/org/mesdag/particlestorm/network/EmitterCreationPacketS2C.java +++ b/src/main/java/org/mesdag/particlestorm/network/EmitterCreationPacketS2C.java @@ -13,7 +13,6 @@ import net.neoforged.neoforge.network.PacketDistributor; import net.neoforged.neoforge.network.handling.IPayloadContext; import net.neoforged.neoforge.server.ServerLifecycleHooks; -import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.joml.Vector3f; import org.mesdag.particlestorm.PSGameClient; @@ -33,7 +32,7 @@ public record EmitterCreationPacketS2C(ResourceLocation id, Vector3f pos, Molang ); @Override - public @NotNull Type type() { + public Type type() { return TYPE; } diff --git a/src/main/java/org/mesdag/particlestorm/network/EmitterRemovalPacket.java b/src/main/java/org/mesdag/particlestorm/network/EmitterRemovalPacket.java index 0b9671f..73219bd 100644 --- a/src/main/java/org/mesdag/particlestorm/network/EmitterRemovalPacket.java +++ b/src/main/java/org/mesdag/particlestorm/network/EmitterRemovalPacket.java @@ -10,7 +10,6 @@ import net.minecraft.world.entity.player.Player; import net.neoforged.neoforge.network.PacketDistributor; import net.neoforged.neoforge.network.handling.IPayloadContext; -import org.jetbrains.annotations.NotNull; import org.mesdag.particlestorm.PSGameClient; import org.mesdag.particlestorm.ParticleStorm; import org.mesdag.particlestorm.particle.ParticleEmitter; @@ -26,7 +25,7 @@ public record EmitterRemovalPacket(int id) implements CustomPacketPayload { ); @Override - public @NotNull Type type() { + public Type type() { return TYPE; } diff --git a/src/main/java/org/mesdag/particlestorm/network/EmitterSynchronizePacket.java b/src/main/java/org/mesdag/particlestorm/network/EmitterSynchronizePacket.java index 9e4dd6d..e6ed4b0 100644 --- a/src/main/java/org/mesdag/particlestorm/network/EmitterSynchronizePacket.java +++ b/src/main/java/org/mesdag/particlestorm/network/EmitterSynchronizePacket.java @@ -10,7 +10,6 @@ import net.minecraft.world.entity.player.Player; import net.neoforged.neoforge.network.PacketDistributor; import net.neoforged.neoforge.network.handling.IPayloadContext; -import org.jetbrains.annotations.NotNull; import org.mesdag.particlestorm.PSGameClient; import org.mesdag.particlestorm.ParticleStorm; import org.mesdag.particlestorm.particle.ParticleEmitter; @@ -26,7 +25,7 @@ public record EmitterSynchronizePacket(int id, CompoundTag tag) implements Custo public static final String KEY = "particlestorm:emitters"; @Override - public @NotNull Type type() { + public Type type() { return TYPE; } diff --git a/src/main/java/org/mesdag/particlestorm/network/package-info.java b/src/main/java/org/mesdag/particlestorm/network/package-info.java new file mode 100644 index 0000000..f256445 --- /dev/null +++ b/src/main/java/org/mesdag/particlestorm/network/package-info.java @@ -0,0 +1,7 @@ +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault +package org.mesdag.particlestorm.network; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/src/main/java/org/mesdag/particlestorm/package-info.java b/src/main/java/org/mesdag/particlestorm/package-info.java new file mode 100644 index 0000000..7157968 --- /dev/null +++ b/src/main/java/org/mesdag/particlestorm/package-info.java @@ -0,0 +1,7 @@ +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault +package org.mesdag.particlestorm; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/src/main/java/org/mesdag/particlestorm/particle/EmitterPreset.java b/src/main/java/org/mesdag/particlestorm/particle/EmitterPreset.java index f86d496..62cbaf4 100644 --- a/src/main/java/org/mesdag/particlestorm/particle/EmitterPreset.java +++ b/src/main/java/org/mesdag/particlestorm/particle/EmitterPreset.java @@ -1,6 +1,6 @@ package org.mesdag.particlestorm.particle; -import org.jetbrains.annotations.NotNull; +import net.minecraft.core.particles.ParticleType; import org.mesdag.particlestorm.api.IEmitterComponent; import org.mesdag.particlestorm.api.IEventNode; import org.mesdag.particlestorm.data.MathHelper; @@ -18,7 +18,7 @@ import java.util.Map; public class EmitterPreset { - public final MolangParticleOption option; + public final ParticleType type; public final List components; public final Map> events; public final VariableTable vars; @@ -29,8 +29,13 @@ public class EmitterPreset { public boolean localVelocity = false; public EmitterLifetimeEvents lifetimeEvents; + @Deprecated public EmitterPreset(MolangParticleOption option, List components, Map> events) { - this.option = option; + this(option.getType(), components, events); + } + + public EmitterPreset(ParticleType type, List components, Map> events) { + this.type = type; this.components = components; this.events = events; VariableTable table = new VariableTable(addDefaultVariables(), null); @@ -77,7 +82,7 @@ public EmitterPreset(MolangParticleOption option, List compon this.assignments = toInit; } - private static @NotNull Hashtable addDefaultVariables() { + private static Hashtable addDefaultVariables() { Hashtable table = new Hashtable<>(); table.computeIfAbsent("variable.emitter_age", s -> new Variable(s, i -> i.getEmitter().tickAge())); table.computeIfAbsent("variable.emitter_lifetime", s -> new Variable(s, i -> i.getEmitter().tickLifetime())); diff --git a/src/main/java/org/mesdag/particlestorm/particle/ExtendMutableSpriteSet.java b/src/main/java/org/mesdag/particlestorm/particle/ExtendMutableSpriteSet.java index a9bf972..02288a5 100644 --- a/src/main/java/org/mesdag/particlestorm/particle/ExtendMutableSpriteSet.java +++ b/src/main/java/org/mesdag/particlestorm/particle/ExtendMutableSpriteSet.java @@ -2,7 +2,6 @@ import net.minecraft.client.particle.ParticleEngine; import net.minecraft.client.renderer.texture.TextureAtlasSprite; -import org.jetbrains.annotations.NotNull; import java.util.ArrayList; import java.util.List; @@ -28,7 +27,7 @@ public void clear() { } @Override - public void rebind(@NotNull List sprites) { + public void rebind(List sprites) { this.sprites = new ArrayList<>(sprites); } diff --git a/src/main/java/org/mesdag/particlestorm/particle/FaceCameraMode.java b/src/main/java/org/mesdag/particlestorm/particle/FaceCameraMode.java index 87063e5..4b6c8da 100644 --- a/src/main/java/org/mesdag/particlestorm/particle/FaceCameraMode.java +++ b/src/main/java/org/mesdag/particlestorm/particle/FaceCameraMode.java @@ -3,26 +3,28 @@ import net.minecraft.client.Camera; import net.minecraft.client.particle.SingleQuadParticle; import net.minecraft.util.Mth; -import org.joml.Math; -import org.joml.Matrix4f; -import org.joml.Quaternionf; -import org.joml.Vector3f; +import net.minecraft.world.phys.Vec3; +import org.joml.*; +import org.mesdag.particlestorm.api.IMolangParticleInstance; import org.mesdag.particlestorm.data.MathHelper; +import org.mesdag.particlestorm.data.component.ParticleAppearanceBillboard; -import javax.annotation.ParametersAreNonnullByDefault; - -@ParametersAreNonnullByDefault public enum FaceCameraMode implements SingleQuadParticle.FacingCameraMode { + DO_NOTHING { + @Override + public void setRotation(Quaternionf quaternion, Camera camera, float partialTick) {} + }, LOOKAT_XYZ { - private final Vector3f wd = new Vector3f(); - private final Vector3f qd = new Vector3f(); - private final Vector3f up = new Vector3f(0, 1, 0); + private static final Vector3f wd = new Vector3f(); + private static final Vector3f qd = new Vector3f(); + private static final Vector3f up = new Vector3f(0, 1, 0); + private static final Matrix3f mat = new Matrix3f(); @Override public void setRotation(Quaternionf quaternion, Camera camera, float partialTick) {} @Override - public void setRotation(MolangParticleInstance instance, Quaternionf quaternion, Camera camera, float partialTick) { + public void setRotation(IMolangParticleInstance instance, Quaternionf quaternion, Camera camera, float partialTick) { Vector3f xd = camera.getPosition().toVector3f().sub( (float) instance.getX(), (float) instance.getY(), @@ -30,11 +32,10 @@ public void setRotation(MolangParticleInstance instance, Quaternionf quaternion, ).normalize(); up.cross(xd, wd).normalize(); xd.cross(wd, qd); - quaternion.setFromNormalized(new Matrix4f( - wd.x, qd.x, xd.x, 0, - wd.y, qd.y, xd.y, 0, - wd.z, qd.z, xd.z, 0, - 0, 0, 0, 1 + quaternion.setFromNormalized(mat.set( + wd.x, qd.x, xd.x, + wd.y, qd.y, xd.y, + wd.z, qd.z, xd.z ).invert()); } }, @@ -43,7 +44,7 @@ public void setRotation(MolangParticleInstance instance, Quaternionf quaternion, public void setRotation(Quaternionf quaternion, Camera camera, float partialTick) {} @Override - public void setRotation(MolangParticleInstance instance, Quaternionf quaternion, Camera camera, float partialTick) { + public void setRotation(IMolangParticleInstance instance, Quaternionf quaternion, Camera camera, float partialTick) { LOOKAT_XYZ.setRotation(instance, quaternion, camera, partialTick); quaternion.x = 0.0F; quaternion.z = 0.0F; @@ -80,18 +81,24 @@ public void setRotation(Quaternionf quaternion, Camera camera, float partialTick } }, LOOKAT_DIRECTION { - private final Vector3f X = new Vector3f(1.0F, 0.0F, 0.0F); + private static final Vector3f X = new Vector3f(1.0F, 0.0F, 0.0F); + private static final Vector4f t = new Vector4f(); + private static final Matrix4f m = new Matrix4f(); @Override public void setRotation(Quaternionf quaternion, Camera camera, float partialTick) {} @Override - public void setRotation(MolangParticleInstance instance, Quaternionf quaternion, Camera camera, float partialTick) { - MathHelper.setFromUnitVectors(X, instance.facingDirection, quaternion); - instance.xRot = Math.atan2( - (float) (instance.getX() - camera.getPosition().y), - (float) (camera.getPosition().z - instance.getZ()) - ); + public void setRotation(IMolangParticleInstance instance, Quaternionf quaternion, Camera camera, float partialTick) { + MathHelper.setFromUnitVectors(X, instance.getFacingDirection(), quaternion); + Vec3 pos = camera.getPosition(); + t.set( + pos.x - instance.getX(), + pos.y - instance.getY(), + pos.z - instance.getZ(), + 0 + ).mul(m.rotation(quaternion).invert()); + quaternion.rotateX((float) Mth.atan2(-t.y, t.z)); } }, EMITTER_TRANSFORM_XY { @@ -113,7 +120,15 @@ public void setRotation(Quaternionf quaternion, Camera camera, float partialTick } }; - public void setRotation(MolangParticleInstance instance, Quaternionf quaternion, Camera camera, float partialTick) { + public void setRotation(IMolangParticleInstance instance, Quaternionf quaternion, Camera camera, float partialTick) { setRotation(quaternion, camera, partialTick); } + + public static FaceCameraMode fromComponent(ParticleAppearanceBillboard.FaceCameraMode faceCameraMode) { + try { + return FaceCameraMode.valueOf(faceCameraMode.name()); + } catch (Exception e) { + return DO_NOTHING; + } + } } diff --git a/src/main/java/org/mesdag/particlestorm/particle/MolangParticleInstance.java b/src/main/java/org/mesdag/particlestorm/particle/MolangParticleInstance.java index 9c9fa7a..599e64c 100644 --- a/src/main/java/org/mesdag/particlestorm/particle/MolangParticleInstance.java +++ b/src/main/java/org/mesdag/particlestorm/particle/MolangParticleInstance.java @@ -3,26 +3,21 @@ import com.mojang.blaze3d.vertex.VertexConsumer; import net.minecraft.client.Camera; import net.minecraft.client.multiplayer.ClientLevel; -import net.minecraft.client.particle.ParticleProvider; import net.minecraft.client.particle.ParticleRenderType; import net.minecraft.client.particle.TextureSheetParticle; import net.minecraft.client.renderer.texture.TextureAtlasSprite; import net.minecraft.core.particles.ParticleGroup; -import net.minecraft.resources.ResourceLocation; import net.minecraft.util.Mth; import net.minecraft.util.RandomSource; import net.minecraft.world.entity.Entity; import net.minecraft.world.level.Level; import net.minecraft.world.phys.Vec3; import net.neoforged.fml.ModList; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import org.joml.Quaternionf; import org.joml.Vector3f; -import org.mesdag.particlestorm.PSGameClient; import org.mesdag.particlestorm.api.IEventNode; +import org.mesdag.particlestorm.api.IMolangParticleInstance; import org.mesdag.particlestorm.api.IParticleComponent; -import org.mesdag.particlestorm.api.MolangInstance; import org.mesdag.particlestorm.data.component.ParticleMotionCollision; import org.mesdag.particlestorm.data.molang.VariableTable; import org.mesdag.particlestorm.mixed.ITextureAtlasSprite; @@ -30,54 +25,51 @@ import java.util.List; import java.util.Optional; -public class MolangParticleInstance extends TextureSheetParticle implements MolangInstance { +public class MolangParticleInstance extends TextureSheetParticle implements IMolangParticleInstance { public static final int FULL_LIGHT = 0xF000F0; private static final boolean isSodiumLoaded = ModList.get().isLoaded("sodium"); - public final RandomSource random; - public final ParticlePreset preset; + protected final ParticlePreset preset; protected ParticleVariableTable vars; protected final float originX; protected final float originY; - public Vector3f acceleration = new Vector3f(); - public Vector3f facingDirection = new Vector3f(); - public Vector3f initialSpeed = new Vector3f(); - public float xRot = 0.0F; - public float yRot = 0.0F; + protected Vector3f acceleration = new Vector3f(); + protected Vector3f facingDirection = new Vector3f(); + protected Vector3f initialSpeed = new Vector3f(); + protected float xRot = 0.0F; + protected float yRot = 0.0F; protected float xRotO = 0.0F; protected float yRotO = 0.0F; - public float rolld = 0.0F; - private boolean hasCollision = false; - public float collisionDrag = 0.0F; - public float coefficientOfRestitution = 0.0F; - public boolean expireOnContact = false; + protected float rolld = 0.0F; + protected boolean hasCollision = false; + protected float collisionDrag = 0.0F; + protected float coefficientOfRestitution = 0.0F; + protected boolean expireOnContact = false; protected final double particleRandom1; protected final double particleRandom2; protected final double particleRandom3; protected final double particleRandom4; - public List components; + protected List components; protected ParticleEmitter emitter; - public boolean motionDynamic = false; - public final float scaleU; - public final float scaleV; - public float[] billboardSize = new float[2]; - public float[] uvSize; - public float[] uvStep; - public int maxFrame = 1; - public int currentFrame = 0; - public float[] UV; + protected final float scaleU; + protected final float scaleV; + protected float[] billboardSize = new float[2]; + protected float[] uvSize; + protected float[] uvStep; + protected int maxFrame = 1; + protected int currentFrame = 0; + protected float[] UV; - public boolean insideKillPlane; - public ParticleGroup particleGroup; - public int lastTimeline = 0; + protected boolean insideKillPlane; + protected ParticleGroup particleGroup; + protected int lastTimeline = 0; public MolangParticleInstance(ParticlePreset preset, ClientLevel level, double x, double y, double z, ExtendMutableSpriteSet sprites) { super(level, x, y, z); this.friction = 1.0F; - this.random = level.getRandom(); this.preset = preset; setSprite(sprites.get(preset.effect.description.parameters().getTextureIndex())); this.originX = ((ITextureAtlasSprite) sprite).particlestorm$getOriginX(); @@ -85,60 +77,229 @@ public MolangParticleInstance(ParticlePreset preset, ClientLevel level, double x this.scaleU = sprite.contents().width() * preset.invTextureWidth; this.scaleV = sprite.contents().height() * preset.invTextureHeight; + RandomSource random = level.getRandom(); this.particleRandom1 = random.nextDouble(); this.particleRandom2 = random.nextDouble(); this.particleRandom3 = random.nextDouble(); this.particleRandom4 = random.nextDouble(); } + @Override + public int getAge() { + return age; + } + + @Override public void setEmitter(ParticleEmitter emitter) { this.emitter = emitter; this.vars = new ParticleVariableTable(preset.vars, emitter.vars); } + @Override + public ParticlePreset getPreset() { + return preset; + } + + @Override + public TextureAtlasSprite getSprite() { + return sprite; + } + + @Override + public Vector3f getAcceleration() { + return acceleration; + } + + @Override + public Vector3f getFacingDirection() { + return facingDirection; + } + + @Override + public Vector3f getInitialSpeed() { + return initialSpeed; + } + + @Override + public void setXRot(float x) { + this.xRot = x; + } + + @Override + public void setYRot(float y) { + this.yRot = y; + } + + @Override + public void setZRot(float z) { + this.roll = z; + } + + @Override + public void setZRotD(float delta) { + this.rolld = delta; + } + + @Override + public float getZRotD() { + return rolld; + } + + @Override + public void setCollisionDrag(float drag) { + this.collisionDrag = drag; + } + + @Override + public void setCoefficientOfRestitution(float coefficient) { + this.coefficientOfRestitution = coefficient; + } + + @Override + public void setExpireOnContact(boolean b) { + this.expireOnContact = b; + } + + @Override + public void setComponents(List components) { + this.components = components; + } + + @Override + public float getScaleU() { + return scaleU; + } + + @Override + public float getScaleV() { + return scaleV; + } + + @Override + public void setBillboardSize(float[] size) { + this.billboardSize = size; + } + + @Override + public void setUvSize(float[] size) { + this.uvSize = size; + } + + @Override + public float[] getUvSize() { + return uvSize; + } + + @Override + public void setUvStep(float[] step) { + this.uvStep = step; + } + + @Override + public float[] getUvStep() { + return uvStep; + } + + @Override + public void setMaxFrame(int frame) { + this.maxFrame = frame; + } + + @Override + public int getMaxFrame() { + return maxFrame; + } + + @Override + public void setCurrentFrame(int frame) { + this.currentFrame = frame; + } + + @Override + public int getCurrentFrame() { + return currentFrame; + } + + @Override + public void setInsideKillPlane(boolean b) { + this.insideKillPlane = b; + } + + @Override + public boolean isInsideKillPlane() { + return insideKillPlane; + } + + @Override + public void setParticleGroup(ParticleGroup group) { + this.particleGroup = group; + } + + @Override + public void setLastTimeline(int last) { + this.lastTimeline = last; + } + + @Override + public int getLastTimeline() { + return lastTimeline; + } + + @Override public double getXd() { return xd; } + @Override public double getYd() { return yd; } + @Override public double getZd() { return zd; } + @Override public double getX() { return x; } + @Override public double getY() { return y; } + @Override public double getZ() { return z; } + @Override public void setPosO(double x, double y, double z) { this.xo = x; this.yo = y; this.zo = z; } + /// @see MolangParticleInstance#setZRot(float) + /// @deprecated public void setRoll(float roll) { this.roll = roll; } + @Deprecated public float getRoll() { return roll; } + @Override public void setColor(float red, float green, float blue, float alpha) { super.setColor(red, green, blue); super.setAlpha(alpha); } + @Override public void setUV(float u, float v, float w, float h) { if (UV == null) this.UV = new float[4]; this.UV[0] = u / originX; @@ -147,6 +308,7 @@ public void setUV(float u, float v, float w, float h) { this.UV[3] = (v + h) / originY; } + @Override public void setCollision(boolean bool) { this.hasCollision = bool; } @@ -161,15 +323,6 @@ public Level getLevel() { return level; } - public float tickAge() { - return age * emitter.invTickRate; - } - - @Override - public float tickLifetime() { - return lifetime * emitter.invTickRate; - } - @Override public double getRandom1() { return particleRandom1; @@ -190,35 +343,11 @@ public double getRandom4() { return particleRandom4; } - @Override - public ResourceLocation getIdentity() { - return preset.effect.description.identifier(); - } - - @Override - public Vec3 getPosition() { - return getPos(); - } - - @Override - public @Nullable Entity getAttachedEntity() { - return emitter.getAttachedEntity(); - } - - @Override - public float getInvTickRate() { - return emitter.invTickRate; - } - @Override public ParticleEmitter getEmitter() { return emitter; } - public TextureAtlasSprite getSprite() { - return sprite; - } - @Override protected float getU0() { return UV == null ? super.getU0() : UV[0]; @@ -239,10 +368,6 @@ protected float getV1() { return UV == null ? super.getV1() : UV[3]; } - public int getAge() { - return age; - } - @Override public void tick() { super.tick(); @@ -255,18 +380,20 @@ public void tick() { } } + private static final Quaternionf quaternionf = new Quaternionf(); + @Override - public void render(@NotNull VertexConsumer buffer, @NotNull Camera renderInfo, float partialTicks) { - Quaternionf quaternionf = new Quaternionf(); - getFacingCameraMode().setRotation(this, quaternionf, renderInfo, partialTicks); + public void render(VertexConsumer buffer, Camera camera, float partialTicks) { + quaternionf.identity(); + getFacingCameraMode().setRotation(this, quaternionf, camera, partialTicks); if (xRot != 0.0F) quaternionf.rotateX(Mth.lerp(partialTicks, xRotO, xRot)); if (yRot != 0.0F) quaternionf.rotateY(Mth.lerp(partialTicks, yRotO, yRot)); if (roll != 0.0F) quaternionf.rotateZ(Mth.lerp(partialTicks, oRoll, roll)); - renderRotatedQuad(buffer, renderInfo, quaternionf, partialTicks); + renderRotatedQuad(buffer, camera, quaternionf, partialTicks); } @Override - protected void renderRotatedQuad(@NotNull VertexConsumer buffer, @NotNull Quaternionf quaternion, float x, float y, float z, float partialTicks) { + protected void renderRotatedQuad(VertexConsumer buffer, Quaternionf quaternion, float x, float y, float z, float partialTicks) { if (isSodiumLoaded) { float f1 = getU0(); float f2 = getU1(); @@ -283,16 +410,11 @@ protected void renderRotatedQuad(@NotNull VertexConsumer buffer, @NotNull Quater } @Override - protected void renderVertex(@NotNull VertexConsumer buffer, @NotNull Quaternionf quaternion, float x, float y, float z, float xOffset, float yOffset, float quadSize, float u, float v, int packedLight) { + protected void renderVertex(VertexConsumer buffer, Quaternionf quaternion, float x, float y, float z, float xOffset, float yOffset, float quadSize, float u, float v, int packedLight) { Vector3f vector3f = new Vector3f(xOffset * billboardSize[0], yOffset * billboardSize[1], 0.0F).rotate(quaternion).add(x, y, z); buffer.addVertex(vector3f.x(), vector3f.y(), vector3f.z()).setUv(u, v).setColor(rCol, gCol, bCol, alpha).setLight(packedLight); } - public void moveDirectly(double x, double y, double z) { - setBoundingBox(getBoundingBox().move(x, y, z)); - setLocationFromBoundingbox(); - } - @Override public void move(double x, double y, double z) { if (stoppedByCollision) return; @@ -354,12 +476,12 @@ public void remove() { } @Override - public @NotNull ParticleRenderType getRenderType() { + public ParticleRenderType getRenderType() { return preset.renderType; } @Override - public @NotNull FaceCameraMode getFacingCameraMode() { + public FaceCameraMode getFacingCameraMode() { return preset.facingCameraMode; } @@ -369,20 +491,7 @@ protected int getLightColor(float partialTick) { } @Override - public @NotNull Optional getParticleGroup() { + public Optional getParticleGroup() { return Optional.ofNullable(particleGroup); } - - public static class Provider implements ParticleProvider { - private final ExtendMutableSpriteSet sprites; - - public Provider(ExtendMutableSpriteSet sprites) { - this.sprites = sprites; - } - - @Override - public TextureSheetParticle createParticle(@NotNull MolangParticleOption option, @NotNull ClientLevel level, double x, double y, double z, double xSpeed, double ySpeed, double zSpeed) { - return new MolangParticleInstance(PSGameClient.LOADER.id2Particle().get(option.getId()), level, x, y, z, sprites); - } - } } diff --git a/src/main/java/org/mesdag/particlestorm/particle/MolangParticleLoader.java b/src/main/java/org/mesdag/particlestorm/particle/MolangParticleLoader.java index faa93f6..7165583 100644 --- a/src/main/java/org/mesdag/particlestorm/particle/MolangParticleLoader.java +++ b/src/main/java/org/mesdag/particlestorm/particle/MolangParticleLoader.java @@ -1,6 +1,7 @@ package org.mesdag.particlestorm.particle; import com.google.common.collect.EvictingQueue; +import com.google.common.collect.Lists; import com.google.gson.JsonParseException; import com.mojang.serialization.JsonOps; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; @@ -22,30 +23,32 @@ import net.minecraft.util.profiling.ProfilerFiller; import net.minecraft.world.entity.Entity; import net.minecraft.world.level.Level; -import org.jetbrains.annotations.NotNull; +import net.neoforged.fml.ModLoader; import org.jetbrains.annotations.Nullable; import org.mesdag.particlestorm.ParticleStorm; import org.mesdag.particlestorm.api.IParticleComponent; import org.mesdag.particlestorm.api.IntAllocator; +import org.mesdag.particlestorm.api.MolangParticleLoadEvent; +import org.mesdag.particlestorm.api.RegisterCustomEmitterTypeEvent; import org.mesdag.particlestorm.data.DefinedParticleEffect; import org.mesdag.particlestorm.network.EmitterRemovalPacket; import org.mesdag.particlestorm.network.EmitterSynchronizePacket; import java.io.IOException; import java.io.Reader; -import java.util.ArrayList; import java.util.Hashtable; import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; +@SuppressWarnings("all") public class MolangParticleLoader implements PreparableReloadListener { private static final FileToIdConverter PARTICLE_LISTER = FileToIdConverter.json("particle_definitions"); private Map id2Effect = new Hashtable<>(); private Map id2Particle = new Hashtable<>(); private Map id2Emitter = new Hashtable<>(); - public final Int2ObjectMap emitters = new Int2ObjectOpenHashMap<>(); + private final Int2ObjectOpenHashMap emitters = new Int2ObjectOpenHashMap<>(); private final Object2ObjectMap> tracker = new Object2ObjectOpenHashMap<>(); private final IntAllocator allocator = new IntAllocator(); @@ -64,49 +67,62 @@ public Map id2Emitter() { } public void tick(LocalPlayer localPlayer) { - if (initialized) { - if (!emitters.isEmpty()) { - int renderDistSqr = Mth.square(Minecraft.getInstance().options.renderDistance().get() * 16); - ObjectIterator> iterator = emitters.int2ObjectEntrySet().iterator(); - while (iterator.hasNext()) { - ParticleEmitter emitter = iterator.next().getValue(); + if (!initialized) { + for (ParticlePreset detail : id2Particle.values()) { + for (IParticleComponent component : detail.effect.orderedParticleComponents) { + component.initialize(localPlayer.level()); + } + } + removeAll(); + this.initialized = true; + } + if (!emitters.isEmpty()) { + int renderDistSqr = Mth.square(Minecraft.getInstance().options.renderDistance().get() * 16); + ObjectIterator> iterator = emitters.int2ObjectEntrySet().fastIterator(); + while (iterator.hasNext()) { + ParticleEmitter emitter = iterator.next().getValue(); + try { if (emitter.isRemoved() || emitter.level.dimension() != localPlayer.level().dimension()) { - emitter.onRemove(); allocator.release(emitter.id); + emitter.onRemove(); + emitter.remove(); iterator.remove(); } else if (Mth.square(emitter.pos.x - localPlayer.getX()) + Mth.square(emitter.pos.z - localPlayer.getZ()) < renderDistSqr) { emitter.tick(); } - } - } - if (!tracker.isEmpty()) { - ObjectIterator>> iterator1 = tracker.entrySet().iterator(); - while (iterator1.hasNext()) { - Map.Entry> entry = iterator1.next(); - if (entry.getKey().isRemoved()) { - iterator1.remove(); - } else if (entry.getValue().removeIf(ParticleEmitter::isRemoved) && entry.getValue().isEmpty()) { - iterator1.remove(); + } catch (Exception e) { + ParticleStorm.LOGGER.warn("Error ticking: {}", e.getMessage()); + e.printStackTrace(); + if (emitter != null) { + emitter.remove(); } + iterator.remove(); } } - } else { - for (ParticlePreset detail : id2Particle.values()) { - for (IParticleComponent component : detail.effect.orderedParticleComponents) { - component.initialize(localPlayer.level()); + } + if (!tracker.isEmpty()) { + ObjectIterator>> iterator1 = tracker.entrySet().iterator(); + while (iterator1.hasNext()) { + Map.Entry> entry = iterator1.next(); + if (entry.getKey().isRemoved()) { + iterator1.remove(); + } else if (entry.getValue().removeIf(ParticleEmitter::isRemoved) && entry.getValue().isEmpty()) { + iterator1.remove(); } } - removeAll(); - this.initialized = true; } } + public Iterable getEmitters() { + return emitters.values(); + } + public int totalEmitterCount() { return emitters.size(); } public void loadEmitter(Level level, int id, CompoundTag tag) { - ParticleEmitter emitter = new ParticleEmitter(level, tag); + ParticleEmitter emitter = RegisterCustomEmitterTypeEvent.create(level, tag); emitter.id = id; emitters.put(id, emitter); if (allocator.forceAllocate(id)) { @@ -120,9 +136,14 @@ public void addEmitter(ParticleEmitter emitter, boolean sync) { if (sync) EmitterSynchronizePacket.syncToServer(emitter); } - public void addTrackedEmitter(Entity entity, ParticleEmitter emitter) { + public boolean addTrackedEmitter(Entity entity, ResourceLocation particleId) { + EvictingQueue queue = tracker.computeIfAbsent(entity, e -> EvictingQueue.create(16)); + if (!queue.isEmpty() && queue.stream().anyMatch(emitter -> particleId.equals(emitter.particleId))) return false; + ParticleEmitter emitter = new ParticleEmitter(entity.level(), entity.position(), particleId); addEmitter(emitter, false); - tracker.computeIfAbsent(entity, e -> EvictingQueue.create(16)).add(emitter); + emitter.attachEntity(entity); + queue.add(emitter); + return true; } public void removeEmitter(ParticleEmitter emitter, boolean sync) { @@ -160,9 +181,10 @@ public boolean contains(int id) { } @Override - public @NotNull CompletableFuture reload(PreparationBarrier preparationBarrier, @NotNull ResourceManager resourceManager, @NotNull ProfilerFiller preparationsProfiler, @NotNull ProfilerFiller reloadProfiler, @NotNull Executor backgroundExecutor, @NotNull Executor gameExecutor) { + public CompletableFuture reload(PreparationBarrier preparationBarrier, ResourceManager resourceManager, ProfilerFiller preparationsProfiler, ProfilerFiller reloadProfiler, Executor backgroundExecutor, Executor gameExecutor) { return CompletableFuture.supplyAsync(() -> PARTICLE_LISTER.listMatchingResources(resourceManager), backgroundExecutor).thenCompose(map -> { - List> list = new ArrayList<>(map.size()); + ModLoader.postEvent(new MolangParticleLoadEvent.Pre(backgroundExecutor)); + List> list = Lists.newArrayListWithExpectedSize(map.size()); for (Map.Entry entry : map.entrySet()) { ResourceLocation id = PARTICLE_LISTER.fileToId(entry.getKey()); list.add(CompletableFuture.supplyAsync(() -> { @@ -183,7 +205,7 @@ public boolean contains(int id) { id2Effect.put(id, effect); id2Particle.put(id, new ParticlePreset(effect)); id2Emitter.put(id, new EmitterPreset( - new MolangParticleOption(effect.description.identifier()), + effect.description.type(), effect.orderedEmitterComponents, effect.events )); @@ -192,6 +214,7 @@ public boolean contains(int id) { this.id2Particle = id2Particle; this.id2Emitter = id2Emitter; this.initialized = false; + ModLoader.postEvent(new MolangParticleLoadEvent.Post(gameExecutor)); }, gameExecutor); } } diff --git a/src/main/java/org/mesdag/particlestorm/particle/MolangParticleOption.java b/src/main/java/org/mesdag/particlestorm/particle/MolangParticleOption.java index fae0a0f..b48338c 100644 --- a/src/main/java/org/mesdag/particlestorm/particle/MolangParticleOption.java +++ b/src/main/java/org/mesdag/particlestorm/particle/MolangParticleOption.java @@ -1,47 +1,19 @@ package org.mesdag.particlestorm.particle; -import com.google.common.base.Supplier; -import com.google.common.base.Suppliers; import com.mojang.serialization.MapCodec; import io.netty.buffer.ByteBuf; import net.minecraft.core.particles.ParticleOptions; import net.minecraft.core.particles.ParticleType; import net.minecraft.network.codec.StreamCodec; import net.minecraft.resources.ResourceLocation; -import org.jetbrains.annotations.NotNull; import org.mesdag.particlestorm.ParticleStorm; - -public class MolangParticleOption implements ParticleOptions { - private final Supplier> type; - private final ResourceLocation id; - - private MolangParticleOption(Supplier> type, ResourceLocation id) { - this.type = Suppliers.memoize(type); - this.id = id; - } - - public MolangParticleOption(ResourceLocation id) { - this(ParticleStorm.MOLANG::get, id); - } - - public ResourceLocation getId() { - return id; - } +public record MolangParticleOption(ResourceLocation id) implements ParticleOptions { + public static final MapCodec CODEC = ResourceLocation.CODEC.fieldOf("id").xmap(MolangParticleOption::new, MolangParticleOption::id); + public static final StreamCodec STREAM_CODEC = ResourceLocation.STREAM_CODEC.map(MolangParticleOption::new, MolangParticleOption::id); @Override - public @NotNull ParticleType getType() { - return type.get(); - } - - public static MapCodec codec(ParticleType type) { - return ResourceLocation.CODEC.xmap( - id -> new MolangParticleOption(() -> type, id), - option -> option.id - ).fieldOf("id"); - } - - public static StreamCodec streamCodec(ParticleType type) { - return ResourceLocation.STREAM_CODEC.map(id -> new MolangParticleOption(() -> type, id), option -> option.id); + public ParticleType getType() { + return ParticleStorm.MOLANG.get(); } } diff --git a/src/main/java/org/mesdag/particlestorm/particle/ParticleEmitter.java b/src/main/java/org/mesdag/particlestorm/particle/ParticleEmitter.java index 9390925..097aff6 100644 --- a/src/main/java/org/mesdag/particlestorm/particle/ParticleEmitter.java +++ b/src/main/java/org/mesdag/particlestorm/particle/ParticleEmitter.java @@ -1,9 +1,7 @@ package org.mesdag.particlestorm.particle; -import net.minecraft.client.Minecraft; import net.minecraft.core.BlockPos; import net.minecraft.nbt.CompoundTag; -import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; import net.minecraft.util.Mth; import net.minecraft.util.RandomSource; @@ -20,16 +18,17 @@ import org.mesdag.particlestorm.data.MathHelper; import org.mesdag.particlestorm.data.component.EmitterLifetime; import org.mesdag.particlestorm.data.component.EmitterRate; +import org.mesdag.particlestorm.data.event.ParticleEffect; import org.mesdag.particlestorm.data.molang.MolangExp; import org.mesdag.particlestorm.data.molang.VariableTable; import org.mesdag.particlestorm.data.molang.compiler.MathValue; import org.mesdag.particlestorm.data.molang.compiler.MolangParser; +import org.mesdag.particlestorm.data.molang.compiler.value.Variable; import org.mesdag.particlestorm.data.molang.compiler.value.VariableAssignment; import org.mesdag.particlestorm.mixed.IEntity; import java.util.ArrayList; import java.util.List; -import java.util.function.Consumer; public class ParticleEmitter implements MolangInstance { public ResourceLocation particleId; @@ -44,7 +43,7 @@ public class ParticleEmitter implements MolangInstance { protected transient VariableTable vars; protected transient List components; public transient ParticleEmitter parent; - public transient @Nullable Consumer afterParentInit; + public transient @Nullable Runnable afterParentInit; public transient final List children = new ArrayList<>(); public transient Vector3f inheritedParticleSpeed; public transient boolean isManual; @@ -83,6 +82,7 @@ public class ParticleEmitter implements MolangInstance { public ParticleEmitter(Level level, Vec3 pos, ResourceLocation particleId, MolangExp expression) { this.level = level; setPos(pos); + this.posO = pos; this.particleId = particleId; this.expression = expression; updateRandoms(level.random); @@ -101,6 +101,46 @@ public ParticleEmitter(Level level, CompoundTag tag) { init(); } + public ParticleEmitter(ParticleEmitter parent, ParticleEffect effect) { + this.level = parent.level; + setPos(parent.pos); + this.posO = pos; + this.particleId = effect.effect(); + this.expression = effect.preEffectExpression(); + updateRandoms(level.random); + this.invTickRate = 1.0F / level.tickRateManager().tickrate(); + this.afterParentInit = () -> { + switch (effect.type()) { + case EMITTER -> {} + case EMITTER_BOUND -> { + attachEntity(parent.getAttachedEntity()); + this.attachedBlock = parent.attachedBlock; + this.offsetPos = parent.offsetPos; + this.offsetRot = parent.offsetRot; + this.parentPosition = parent.parentPosition; + this.parentRotation = parent.parentRotation; + this.parentMode = parent.parentMode; + } + case PARTICLE -> this.isManual = true; + case PARTICLE_WITH_VELOCITY -> { + this.isManual = true; + if (parent.getAttachedEntity() != null) { + this.inheritedParticleSpeed = parent.getAttachedEntity().getDeltaMovement().toVector3f(); + } + } + } + }; + addParent(parent); + createVars(); + for (String name : effect.sharedVars()) { + Variable variable = parent.getVars().table.get(name); + if (variable == null) throw new IllegalArgumentException("Shared vars must defined in parent directly!"); + vars.table.put(name, variable); + } + initVars(); + createComponents(); + } + public void attachEntity(@Nullable Entity entity) { if (entity == null) { this.vars = new VariableTable(vars.table, preset.vars); @@ -113,16 +153,21 @@ public void attachEntity(@Nullable Entity entity) { } } - private void init() { + protected void init() { + createVars(); + initVars(); + createComponents(); + } + + protected void createVars() { this.preset = PSGameClient.LOADER.id2Emitter().get(particleId); if (preset == null) { - if (Minecraft.getInstance().player != null) { - Minecraft.getInstance().player.sendSystemMessage(Component.translatable("particle.notFound", particleId.toString())); - } - remove(); - return; + throw new IllegalArgumentException("Unknown particle id: '" + particleId + "'!"); } this.vars = new VariableTable(preset.vars); + } + + protected void initVars() { if (expression != null && !expression.initialized()) { expression.compile(new MolangParser(vars)); MathValue variable = expression.getVariable(); @@ -133,6 +178,9 @@ private void init() { MathHelper.redirect(toInit, vars); } MathHelper.redirect(preset.assignments, vars); + } + + protected void createComponents() { this.components = preset.components.stream().filter(e -> { e.apply(this); return e.requireUpdate(); @@ -191,7 +239,7 @@ public void tick() { } if (afterParentInit != null && parent != null) { - afterParentInit.accept(parent); + afterParentInit.run(); this.afterParentInit = null; } @@ -227,7 +275,7 @@ public void addParent(ParticleEmitter parent) { } public boolean isRemoved() { - return removed || (attached != null && attached.isRemoved()); + return removed || (attached != null && attached.isRemoved()) || (attachedBlock != null && attachedBlock.isRemoved()); } public void setPos(Vec3 pos) { @@ -245,9 +293,8 @@ public void deserialize(CompoundTag compound) { this.emitterRandom2 = compound.getDouble("emitterRandom2"); this.emitterRandom3 = compound.getDouble("emitterRandom3"); this.emitterRandom4 = compound.getDouble("emitterRandom4"); - this.pos = new Vec3(compound.getDouble("posX"), compound.getDouble("posY"), compound.getDouble("posZ")); + this.posO = this.pos = new Vec3(compound.getDouble("posX"), compound.getDouble("posY"), compound.getDouble("posZ")); this.rot.set(compound.getFloat("rotX"), compound.getFloat("rotY"), compound.getFloat("rotZ")); - this.posO = new Vec3(compound.getDouble("movX"), compound.getDouble("movY"), compound.getDouble("movZ")); } public void serialize(CompoundTag compound) { @@ -263,9 +310,6 @@ public void serialize(CompoundTag compound) { compound.putFloat("rotX", rot.x); compound.putFloat("rotY", rot.y); compound.putFloat("rotZ", rot.z); - compound.putDouble("movX", posO.x); - compound.putDouble("movY", posO.y); - compound.putDouble("movZ", posO.z); } public double getX() { diff --git a/src/main/java/org/mesdag/particlestorm/particle/ParticlePreset.java b/src/main/java/org/mesdag/particlestorm/particle/ParticlePreset.java index 521a7e0..2efaa03 100644 --- a/src/main/java/org/mesdag/particlestorm/particle/ParticlePreset.java +++ b/src/main/java/org/mesdag/particlestorm/particle/ParticlePreset.java @@ -2,11 +2,13 @@ import com.google.common.collect.Iterables; import net.minecraft.client.particle.ParticleRenderType; -import org.jetbrains.annotations.NotNull; +import net.minecraft.util.Mth; +import net.neoforged.neoforge.common.NeoForge; +import org.jetbrains.annotations.Nullable; import org.mesdag.particlestorm.PSGameClient; -import org.mesdag.particlestorm.api.IComponent; import org.mesdag.particlestorm.api.IParticleComponent; import org.mesdag.particlestorm.api.MolangInstance; +import org.mesdag.particlestorm.api.ParticlePresetLoadedEvent; import org.mesdag.particlestorm.data.DefinedParticleEffect; import org.mesdag.particlestorm.data.MathHelper; import org.mesdag.particlestorm.data.component.*; @@ -19,27 +21,27 @@ import org.mesdag.particlestorm.data.molang.compiler.value.Variable; import org.mesdag.particlestorm.data.molang.compiler.value.VariableAssignment; -import java.util.ArrayList; -import java.util.Hashtable; -import java.util.List; -import java.util.Map; +import java.util.*; import static org.mesdag.particlestorm.data.molang.compiler.MolangQueries.applyPrefixAliases; public class ParticlePreset { - public final DefinedParticleEffect effect; - public final ParticleRenderType renderType; - public final FaceCameraMode facingCameraMode; - public final float minSpeedThresholdSqr; - public final boolean environmentLighting; + public DefinedParticleEffect effect; + public ParticleRenderType renderType; + public FaceCameraMode facingCameraMode; + public float minSpeedThresholdSqr; + public boolean environmentLighting; public ParticleLifeTimeEvents lifeTimeEvents; public List collisionEvents = List.of(); - public final float invTextureWidth; - public final float invTextureHeight; + public float invTextureWidth; + public float invTextureHeight; public boolean motionDynamic; - public final VariableTable vars; - public final List assignments; + public VariableTable vars; + public List assignments; + + /// For custom preset data + protected Map, Object> tickets; public ParticlePreset(DefinedParticleEffect effect) { this.effect = effect; @@ -52,13 +54,17 @@ public ParticlePreset(DefinedParticleEffect effect) { case CUSTOM -> ParticleRenderType.CUSTOM; default -> ParticleRenderType.NO_RENDER; }; - IComponent component1 = effect.components.get(ParticleAppearanceBillboard.ID); - if (component1 == null) throw new NullPointerException("No particle_appearance_billboard here"); - ParticleAppearanceBillboard particleAppearanceBillboard = (ParticleAppearanceBillboard) component1; - this.facingCameraMode = FaceCameraMode.valueOf(particleAppearanceBillboard.faceCameraMode().name()); - this.minSpeedThresholdSqr = particleAppearanceBillboard.direction().minSpeedThreshold() * particleAppearanceBillboard.direction().minSpeedThreshold(); - this.invTextureWidth = 1.0F / particleAppearanceBillboard.uv().texturewidth(); - this.invTextureHeight = 1.0F / particleAppearanceBillboard.uv().textureheight(); + if (effect.components.get(ParticleAppearanceBillboard.ID) instanceof ParticleAppearanceBillboard component) { + this.facingCameraMode = FaceCameraMode.fromComponent(component.faceCameraMode()); + this.minSpeedThresholdSqr = Mth.square(component.direction().minSpeedThreshold()); + this.invTextureWidth = 1.0F / component.uv().texturewidth(); + this.invTextureHeight = 1.0F / component.uv().textureheight(); + } else { + this.facingCameraMode = FaceCameraMode.DO_NOTHING; + this.minSpeedThresholdSqr = 0; + this.invTextureWidth = 1; + this.invTextureHeight = 1; + } this.environmentLighting = effect.components.containsValue(ParticleAppearanceLighting.INSTANCE); this.lifeTimeEvents = (ParticleLifeTimeEvents) effect.components.get(ParticleLifeTimeEvents.ID); ParticleMotionCollision motionCollision = (ParticleMotionCollision) effect.components.get(ParticleMotionCollision.ID); @@ -82,6 +88,7 @@ public ParticlePreset(DefinedParticleEffect effect) { List toInit = new ArrayList<>(); for (IParticleComponent component : Iterables.concat(effect.orderedParticleEarlyComponents, effect.orderedParticleComponents)) { + assert component != null; for (MolangExp exp : component.getAllMolangExp()) { exp.compile(parser); MathValue variable = exp.getVariable(); @@ -92,9 +99,20 @@ public ParticlePreset(DefinedParticleEffect effect) { } this.vars = table; this.assignments = toInit; + NeoForge.EVENT_BUS.post(new ParticlePresetLoadedEvent(this)); + } + + public void setTicket(Class clazz, T value) { + if (tickets == null) this.tickets = new HashMap<>(); + tickets.put(clazz, value); + } + + @SuppressWarnings("unchecked") + public @Nullable T getTicket(Class clazz) { + return tickets == null ? null : (T) tickets.get(clazz); } - private static @NotNull Hashtable addDefaultVariables() { + private static Hashtable addDefaultVariables() { Hashtable table = new Hashtable<>(); table.computeIfAbsent("variable.particle_age", s -> new Variable(s, MolangInstance::tickAge)); table.computeIfAbsent("variable.particle_lifetime", s -> new Variable(s, MolangInstance::tickLifetime)); diff --git a/src/main/java/org/mesdag/particlestorm/particle/package-info.java b/src/main/java/org/mesdag/particlestorm/particle/package-info.java new file mode 100644 index 0000000..ec468ef --- /dev/null +++ b/src/main/java/org/mesdag/particlestorm/particle/package-info.java @@ -0,0 +1,7 @@ +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault +package org.mesdag.particlestorm.particle; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/src/main/resources/META-INF/accesstransformer.cfg b/src/main/resources/META-INF/accesstransformer.cfg index e335bd3..083a960 100644 --- a/src/main/resources/META-INF/accesstransformer.cfg +++ b/src/main/resources/META-INF/accesstransformer.cfg @@ -5,3 +5,5 @@ public net.minecraft.client.particle.Particle setLocationFromBoundingbox()V public net.minecraft.client.particle.ParticleEngine$MutableSpriteSet protected net.minecraft.client.particle.ParticleEngine$MutableSpriteSet sprites protected net.minecraft.client.particle.ParticleEngine$MutableSpriteSet ()V +public net.minecraft.client.particle.ParticleEngine trackedParticleCounts +public net.minecraft.client.particle.ParticleEngine particles diff --git a/src/main/resources/assets/particlestorm/textures/particle/missing.png b/src/main/resources/assets/particlestorm/textures/particle/missing.png new file mode 100644 index 0000000..a374dbf Binary files /dev/null and b/src/main/resources/assets/particlestorm/textures/particle/missing.png differ diff --git a/src/main/resources/particlestorm.mixins.json b/src/main/resources/particlestorm.mixins.json index 6d7ff75..03e654f 100644 --- a/src/main/resources/particlestorm.mixins.json +++ b/src/main/resources/particlestorm.mixins.json @@ -5,15 +5,14 @@ "compatibilityLevel": "JAVA_17", "refmap": "particlestorm.refmap.json", "mixins": [ - "MolangQueriesAccessor" + "integration.geckolib.MolangQueriesAccessor" ], "client": [ "BlockEntityMixin", "EntityMixin", "LivingEntityMixin", "MinecraftMixin", - "ParticleEngineAccessor", - "TextureAtlasMixin", + "ParticleEngineMixin", "TextureAtlasSpriteMixin", "integration.geckolib.AnimatableInstanceCacheMixin", "integration.geckolib.AnimationControllerMixin",