diff --git a/.gitignore b/.gitignore index ddb049c..ce2a361 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,5 @@ build eclipse run run-data + +app.js diff --git a/build.gradle b/build.gradle index f35e7fe..6ece24e 100644 --- a/build.gradle +++ b/build.gradle @@ -3,7 +3,7 @@ plugins { id 'eclipse' id 'idea' id 'maven-publish' - id 'net.neoforged.moddev' version '2.0.88' + id 'net.neoforged.moddev' version '2.0.140' } version = mod_version @@ -28,10 +28,7 @@ neoForge { mappingsVersion = project.parchment_mappings_version minecraftVersion = project.parchment_minecraft_version } - - accessTransformers { - file('src/main/resources/META-INF/accesstransformer.cfg') - } + validateAccessTransformers = true runs { client { @@ -46,12 +43,6 @@ neoForge { systemProperty 'neoforge.enabledGameTestNamespaces', project.mod_id } - data { - data() - - programArguments.addAll '--mod', project.mod_id, '--all', '--output', file('src/generated/resources/').getAbsolutePath(), '--existing', file('src/main/resources/').getAbsolutePath() - } - configureEach { systemProperty 'forge.logging.markers', 'REGISTRIES' systemProperty 'particlestorm.debug', 'true' @@ -61,7 +52,9 @@ neoForge { // 忽略无效指令,避免有的人没安装 JetBrain Runtime 无法启动游戏 "-XX:+IgnoreUnrecognizedVMOptions", // 启用 JetBrain Runtime 热重载功能 - "-XX:+AllowEnhancedClassRedefinition" + "-XX:+AllowEnhancedClassRedefinition", + // 并发修改检测 + //"-javaagent:mods/CMESuckMyDuck-1.0.5.jar=net/minecraft/util/ClassInstanceMultiMap;byClass;Map;nonstatic" ]) if (isRunningInIdea) { systemProperty "terminal.jline", "true" @@ -94,7 +87,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 770f008..fb65e94 100644 --- a/gradle.properties +++ b/gradle.properties @@ -12,7 +12,7 @@ minecraft_version=1.21.1 # as they do not follow standard versioning conventions. minecraft_version_range=[1.21.1,1.22) # The Neo version must agree with the Minecraft version to get a valid artifact -neo_version=21.1.170 +neo_version=21.1.219 # The Neo version range can use any version of Neo as bounds neo_version_range=[21,) # The loader version range can only use the major version of FML as bounds @@ -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.1.4 +mod_version=1.3.0 # 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.8.2 +geckolib_version=4.8.4 #systemProp.http.proxyHost=localhost -#systemProp.http.proxyPort=7890 +#systemProp.http.proxyPort=7897 #systemProp.https.proxyHost=localhost -#systemProp.https.proxyPort=7890 +#systemProp.https.proxyPort=7897 diff --git a/src/main/java/org/mesdag/particlestorm/PSGameClient.java b/src/main/java/org/mesdag/particlestorm/PSGameClient.java index 4c14122..f3c045e 100644 --- a/src/main/java/org/mesdag/particlestorm/PSGameClient.java +++ b/src/main/java/org/mesdag/particlestorm/PSGameClient.java @@ -10,36 +10,39 @@ import net.minecraft.client.renderer.LevelRenderer; import net.minecraft.client.renderer.MultiBufferSource; import net.minecraft.client.renderer.RenderType; +import net.minecraft.client.renderer.ShaderInstance; import net.minecraft.client.renderer.debug.DebugRenderer; import net.minecraft.client.renderer.texture.TextureAtlas; import net.minecraft.client.renderer.texture.TextureManager; import net.minecraft.util.Mth; import net.neoforged.api.distmarker.Dist; import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.ModLoader; import net.neoforged.fml.common.EventBusSubscriber; import net.neoforged.fml.event.config.ModConfigEvent; import net.neoforged.fml.event.lifecycle.FMLClientSetupEvent; -import net.neoforged.neoforge.client.event.ClientTickEvent; -import net.neoforged.neoforge.client.event.EntityRenderersEvent; -import net.neoforged.neoforge.client.event.RegisterClientReloadListenersEvent; -import net.neoforged.neoforge.client.event.RenderLevelStageEvent; -import net.neoforged.neoforge.common.NeoForge; -import org.mesdag.particlestorm.api.IComponent; -import org.mesdag.particlestorm.api.IEventNode; +import net.neoforged.neoforge.client.event.*; +import org.jetbrains.annotations.ApiStatus; +import org.mesdag.particlestorm.api.*; import org.mesdag.particlestorm.api.geckolib.GeckoLibHelper; import org.mesdag.particlestorm.data.component.*; import org.mesdag.particlestorm.data.event.*; -import org.mesdag.particlestorm.particle.MolangParticleLoader; +import org.mesdag.particlestorm.particle.MolangParticleEngine; +import org.mesdag.particlestorm.particle.MolangParticleInstance; import org.mesdag.particlestorm.particle.ParticleEmitter; -@EventBusSubscriber(modid = ParticleStorm.MODID, bus = EventBusSubscriber.Bus.MOD, value = Dist.CLIENT) +import java.io.IOException; +import java.util.Queue; + +@EventBusSubscriber(modid = ParticleStorm.MODID, value = Dist.CLIENT) public final class PSGameClient { - public static final MolangParticleLoader LOADER = new MolangParticleLoader(); + @Deprecated(forRemoval = true, since = "1.3.0") + @ApiStatus.ScheduledForRemoval(inVersion = "1.4.0") + public static final MolangParticleEngine LOADER = MolangParticleEngine.INSTANCE; public static final ParticleRenderType PARTICLE_ADD = new ParticleRenderType() { @Override public BufferBuilder begin(Tesselator tesselator, TextureManager textureManager) { RenderSystem.enableDepthTest(); - Minecraft.getInstance().gameRenderer.lightTexture().turnOnLightLayer(); RenderSystem.depthMask(false); RenderSystem.setShaderTexture(0, TextureAtlas.LOCATION_PARTICLES); RenderSystem.enableBlend(); @@ -51,6 +54,32 @@ public String toString() { return "PARTICLE_ADD"; } }; + public static final ParticleRenderType PARTICLE_BLEND = new ParticleRenderType() { + @Override + public BufferBuilder begin(Tesselator tesselator, TextureManager textureManager) { + RenderSystem.enableDepthTest(); + RenderSystem.depthMask(false); + RenderSystem.setShaderTexture(0, TextureAtlas.LOCATION_PARTICLES); + RenderSystem.enableBlend(); + RenderSystem.blendFunc(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA); + return tesselator.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.PARTICLE); + } + + public String toString() { + return "PARTICLE_BLEND"; + } + }; + + private static ShaderInstance particleNoDiscard; + + public static ShaderInstance getParticleNoDiscardShader() { + return particleNoDiscard; + } + + @SubscribeEvent + public static void registerShaders(RegisterShadersEvent event) throws IOException { + event.registerShader(new ShaderInstance(event.getResourceProvider(), ParticleStorm.asResource("particle_no_discard"), DefaultVertexFormat.PARTICLE), instance -> particleNoDiscard = instance); + } @SubscribeEvent public static void registerRenderers(EntityRenderersEvent.RegisterRenderers event) { @@ -60,55 +89,62 @@ public static void registerRenderers(EntityRenderersEvent.RegisterRenderers even } @SubscribeEvent - public static void clientSetup(FMLClientSetupEvent event) { - event.enqueueWork(() -> { + public static void modConfig$Loading(ModConfigEvent.Loading event) { + if (ParticleStorm.MODID.equals(event.getConfig().getModId())) { PSClientConfigs.onLoad(); - NeoForge.EVENT_BUS.addListener(PSGameClient::tick); - NeoForge.EVENT_BUS.addListener(PSGameClient::renderLevelStage); - }); + } } @SubscribeEvent public static void modConfig$Reloading(ModConfigEvent.Reloading event) { - if (event.getConfig().getModId().equals(ParticleStorm.MODID)) { + if (ParticleStorm.MODID.equals(event.getConfig().getModId())) { PSClientConfigs.onLoad(); } } - private static void tick(ClientTickEvent.Pre event) { + @SubscribeEvent + public static void clientNetwork$LoggingOut(ClientPlayerNetworkEvent.LoggingOut event) { + MolangParticleEngine.INSTANCE.removeAll(); + } + + @SubscribeEvent + public static void tick(ClientTickEvent.Pre event) { Minecraft minecraft = Minecraft.getInstance(); - LocalPlayer localPlayer = minecraft.player; - if (localPlayer == null) { - LOADER.removeAll(); - } else if (!minecraft.isPaused() && localPlayer.level().tickRateManager().runsNormally()) { - LOADER.tick(localPlayer); + LocalPlayer player = minecraft.player; + if (player != null && !minecraft.isPaused() && player.clientLevel.tickRateManager().runsNormally()) { + MolangParticleEngine.INSTANCE.tick(minecraft, player); } } - private static void renderLevelStage(RenderLevelStageEvent event) { + @SubscribeEvent + public static void renderLevelStage(RenderLevelStageEvent event) { + if (event.getStage() != RenderLevelStageEvent.Stage.AFTER_PARTICLES) return; if (!PSClientConfigs.showEmitterOutline) return; Minecraft minecraft = Minecraft.getInstance(); - if (event.getStage() == RenderLevelStageEvent.Stage.AFTER_PARTICLES && minecraft.getEntityRenderDispatcher().shouldRenderHitBoxes()) { - float partialTicks = event.getPartialTick().getGameTimeDeltaPartialTick(true); - PoseStack poseStack = event.getPoseStack(); - MultiBufferSource.BufferSource bufferSource = minecraft.renderBuffers().bufferSource(); - 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.particleId.toString(), x, y + 0.5, z, 0xFFFFFF); - DebugRenderer.renderFloatingText(poseStack, bufferSource, "id: " + emitter.id, x, y + 0.3, z, 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; - double d2 = camera.getPosition().z; - poseStack.pushPose(); - poseStack.translate(x - d0, y - d1, z - d2); - LevelRenderer.renderLineBox(poseStack, bufferSource.getBuffer(RenderType.lines()), -0.5, -0.5, -0.5, 0.5, 0.5, 0.5, 0, 1, 0, 1); - poseStack.popPose(); - } + if (!minecraft.getEntityRenderDispatcher().shouldRenderHitBoxes()) return; + PoseStack poseStack = event.getPoseStack(); + MultiBufferSource.BufferSource bufferSource = minecraft.renderBuffers().bufferSource(); + Camera camera = event.getCamera(); + double camX = camera.getPosition().x; + double camY = camera.getPosition().y; + double camZ = camera.getPosition().z; + float partialTick = event.getPartialTick().getGameTimeDeltaPartialTick(true); + var iterator = MolangParticleEngine.INSTANCE.getEmitters(); + while (iterator.hasNext()) { + ParticleEmitter emitter = iterator.next().getValue(); + if (emitter.hideOutline) continue; + double x = Mth.lerp(partialTick, emitter.posO.x, emitter.getX()); + double y = Mth.lerp(partialTick, emitter.posO.y, emitter.getY()); + double z = Mth.lerp(partialTick, emitter.posO.z, emitter.getZ()); + 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); + Queue queue = MolangParticleEngine.INSTANCE.getParticlesForEmitter(emitter); + int count = queue == null ? 0 : queue.size(); + DebugRenderer.renderFloatingText(poseStack, bufferSource, "particles: " + count, x, y + 0.1, z, count >= emitter.particleGroup.getLimit() ? 0xFF0000 : 0xFFFFFF); + poseStack.pushPose(); + poseStack.translate(x - camX, y - camY, z - camZ); + LevelRenderer.renderLineBox(poseStack, bufferSource.getBuffer(RenderType.lines()), -0.5, -0.5, -0.5, 0.5, 0.5, 0.5, 0, 1, 0, 1); + poseStack.popPose(); } } @@ -116,7 +152,14 @@ private static void renderLevelStage(RenderLevelStageEvent event) { public static void reload(RegisterClientReloadListenersEvent event) { registerComponents(); registerEventNodes(); - event.registerReloadListener(LOADER); + event.registerReloadListener(MolangParticleEngine.INSTANCE); + } + + @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) + ); } private static void registerComponents() { @@ -140,7 +183,7 @@ private static void registerComponents() { IComponent.register("particle_initial_speed", ParticleInitialSpeed.CODEC); IComponent.register("particle_initial_spin", ParticleInitialSpin.CODEC); - IComponent.register("particle_initialization", ParticleInitialization.CODEC); + IComponent.register(ParticleInitialization.ID, ParticleInitialization.CODEC); IComponent.register(ParticleMotionDynamic.ID, ParticleMotionDynamic.CODEC); IComponent.register("particle_motion_parametric", ParticleMotionParametric.CODEC); @@ -155,6 +198,8 @@ private static void registerComponents() { IComponent.register("particle_kill_plane", ParticleLifetimeKillPlane.CODEC); IComponent.register("particle_expire_if_in_blocks", ParticleExpireIfInBlocks.CODEC); IComponent.register("particle_expire_if_not_in_blocks", ParticleExpireIfNotInBlocks.CODEC); + + ModLoader.postEvent(new RegisterCustomComponentEvent()); } private static void registerEventNodes() { @@ -165,5 +210,16 @@ private static void registerEventNodes() { IEventNode.register("sound_effect", SoundEffect.CODEC.codec()); IEventNode.register("expression", NodeMolangExp.CODEC); IEventNode.register("log", EventLog.CODEC); + + ModLoader.postEvent(new RegisterCustomEventNodeEvent()); + } + + @SubscribeEvent + public static void fmlClientSetup(FMLClientSetupEvent event) { + event.enqueueWork(() -> { + if (ParticleStorm.GECKOLIB_LOADED) { + GeckoLibHelper.postEvent(); + } + }); } } diff --git a/src/main/java/org/mesdag/particlestorm/PSModClient.java b/src/main/java/org/mesdag/particlestorm/PSModClient.java deleted file mode 100644 index 4fc4fc1..0000000 --- a/src/main/java/org/mesdag/particlestorm/PSModClient.java +++ /dev/null @@ -1,17 +0,0 @@ -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 6ee9648..026735a 100644 --- a/src/main/java/org/mesdag/particlestorm/ParticleStorm.java +++ b/src/main/java/org/mesdag/particlestorm/ParticleStorm.java @@ -19,7 +19,6 @@ import net.neoforged.neoforge.event.entity.player.PlayerEvent; import net.neoforged.neoforge.network.PacketDistributor; import net.neoforged.neoforge.network.event.RegisterPayloadHandlersEvent; -import net.neoforged.neoforge.network.registration.PayloadRegistrar; import net.neoforged.neoforge.registries.DeferredHolder; import net.neoforged.neoforge.registries.DeferredRegister; import org.mesdag.particlestorm.api.geckolib.GeckoLibHelper; @@ -42,7 +41,10 @@ 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") && LoadingModList.get().getModFileById("geckolib") != null; + public static final boolean GECKOLIB_LOADED = LoadingModList.get().getModFileById("geckolib") != null; + public static final boolean SODIUM_LOADED = LoadingModList.get().getModFileById("sodium") != null; + public static final boolean IRIS_LOADED = LoadingModList.get().getModFileById("iris") != null; + public static final boolean DEBUG = Boolean.getBoolean("particlestorm.debug") && GECKOLIB_LOADED; private static final DeferredRegister> REGISTER = DeferredRegister.create(BuiltInRegistries.PARTICLE_TYPE, MODID); public static final DeferredHolder, ParticleType> MOLANG = registerParticleType(REGISTER, "molang"); @@ -61,11 +63,12 @@ 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); + event.registrar("1") + .playToClient(EmitterCreationPacketS2C.TYPE, EmitterCreationPacketS2C.STREAM_CODEC, EmitterCreationPacketS2C::handle) + .playToClient(EmitterAttachPacketS2C.TYPE, EmitterAttachPacketS2C.STREAM_CODEC, EmitterAttachPacketS2C::handle) + .playBidirectional(EmitterRemovalPacket.TYPE, EmitterRemovalPacket.STREAM_CODEC, EmitterRemovalPacket::handle) + .playBidirectional(EmitterSynchronizePacket.TYPE, EmitterSynchronizePacket.STREAM_CODEC, EmitterSynchronizePacket::handle) + ; } private static void registerCommands(RegisterCommandsEvent event) { diff --git a/src/main/java/org/mesdag/particlestorm/api/IEmitterComponent.java b/src/main/java/org/mesdag/particlestorm/api/IEmitterComponent.java index 13adc68..42197cf 100644 --- a/src/main/java/org/mesdag/particlestorm/api/IEmitterComponent.java +++ b/src/main/java/org/mesdag/particlestorm/api/IEmitterComponent.java @@ -3,9 +3,9 @@ import org.mesdag.particlestorm.particle.ParticleEmitter; public interface IEmitterComponent extends IComponent { - default void update(ParticleEmitter entity) {} + default void update(ParticleEmitter emitter) {} - default void apply(ParticleEmitter entity) {} + default void apply(ParticleEmitter emitter) {} default boolean requireUpdate() { return false; diff --git a/src/main/java/org/mesdag/particlestorm/api/IMolangParticleInstance.java b/src/main/java/org/mesdag/particlestorm/api/IMolangParticleInstance.java index 01cc805..7e7cf0c 100644 --- a/src/main/java/org/mesdag/particlestorm/api/IMolangParticleInstance.java +++ b/src/main/java/org/mesdag/particlestorm/api/IMolangParticleInstance.java @@ -1,10 +1,13 @@ package org.mesdag.particlestorm.api; +import net.minecraft.client.Camera; import net.minecraft.client.particle.Particle; +import net.minecraft.client.renderer.culling.Frustum; 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.AABB; import net.minecraft.world.phys.Vec3; import org.jetbrains.annotations.Nullable; import org.joml.Vector3f; @@ -48,6 +51,10 @@ default Particle self() { void setExpireOnContact(boolean b); + void setCollisionRadius(float radius); + + float getCollisionRadius(); + void setComponents(List components); float getScaleU(); @@ -102,9 +109,22 @@ default Particle self() { void setCollision(boolean bool); + void discard(); + + boolean isDiscarded(); + // region default default void moveDirectly(double x, double y, double z) { - self().setBoundingBox(self().getBoundingBox().move(x, y, z)); + AABB aabb = self().getBoundingBox(); + float radius = getCollisionRadius(); + self().setBoundingBox(new AABB( + aabb.minX - radius + x, + aabb.minY - radius + y, + aabb.minZ - radius + z, + aabb.maxX + radius + x, + aabb.maxY + radius + y, + aabb.maxZ + radius + z + )); self().setLocationFromBoundingbox(); } @@ -137,5 +157,9 @@ default Vec3 getPosition() { default float getInvTickRate() { return getEmitter().invTickRate; } + + default boolean isVisible(Camera camera, Frustum frustum, float partialTick) { + return frustum.isVisible(self().getRenderBoundingBox(partialTick)); + } // endregion } diff --git a/src/main/java/org/mesdag/particlestorm/api/IntAllocator.java b/src/main/java/org/mesdag/particlestorm/api/IntAllocator.java index a4b32f5..1b374f7 100644 --- a/src/main/java/org/mesdag/particlestorm/api/IntAllocator.java +++ b/src/main/java/org/mesdag/particlestorm/api/IntAllocator.java @@ -1,29 +1,24 @@ package org.mesdag.particlestorm.api; +import it.unimi.dsi.fastutil.ints.IntHeapPriorityQueue; +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import it.unimi.dsi.fastutil.ints.IntPriorityQueue; +import it.unimi.dsi.fastutil.ints.IntSet; import org.mesdag.particlestorm.ParticleStorm; -import java.util.HashSet; -import java.util.PriorityQueue; -import java.util.Set; - public class IntAllocator { - private final PriorityQueue availableIds; - private final Set usedIds; + private final IntPriorityQueue availableIds; + private final IntSet usedIds; private int nextId; public IntAllocator() { - this.availableIds = new PriorityQueue<>(); - this.usedIds = new HashSet<>(); + this.availableIds = new IntHeapPriorityQueue(); + this.usedIds = new IntOpenHashSet(); this.nextId = 0; } public int allocate() { - int id; - if (availableIds.isEmpty()) { - id = nextId++; - } else { - id = availableIds.poll(); - } + int id = availableIds.isEmpty() ? nextId++ : availableIds.dequeueInt(); usedIds.add(id); return id; } @@ -31,7 +26,7 @@ public int allocate() { public void release(int id) { if (usedIds.contains(id)) { usedIds.remove(id); - availableIds.offer(id); + availableIds.enqueue(id); } else { ParticleStorm.LOGGER.warn("ID {} is not currently allocated.", id); } diff --git a/src/main/java/org/mesdag/particlestorm/api/MolangInstance.java b/src/main/java/org/mesdag/particlestorm/api/MolangInstance.java index 2f0808a..5406291 100644 --- a/src/main/java/org/mesdag/particlestorm/api/MolangInstance.java +++ b/src/main/java/org/mesdag/particlestorm/api/MolangInstance.java @@ -18,13 +18,13 @@ public interface MolangInstance { float tickLifetime(); - double getRandom1(); + float getRandom1(); - double getRandom2(); + float getRandom2(); - double getRandom3(); + float getRandom3(); - double getRandom4(); + float getRandom4(); ResourceLocation getIdentity(); diff --git a/src/main/java/org/mesdag/particlestorm/api/ParticlePresetLoadedEvent.java b/src/main/java/org/mesdag/particlestorm/api/ParticlePresetLoadedEvent.java index a889155..baf5f27 100644 --- a/src/main/java/org/mesdag/particlestorm/api/ParticlePresetLoadedEvent.java +++ b/src/main/java/org/mesdag/particlestorm/api/ParticlePresetLoadedEvent.java @@ -1,15 +1,22 @@ package org.mesdag.particlestorm.api; import net.neoforged.bus.api.Event; +import org.mesdag.particlestorm.data.DefinedParticleEffect; import org.mesdag.particlestorm.particle.ParticlePreset; public class ParticlePresetLoadedEvent extends Event { + private final DefinedParticleEffect effect; private final ParticlePreset preset; - public ParticlePresetLoadedEvent(ParticlePreset preset) { + public ParticlePresetLoadedEvent(DefinedParticleEffect effect, ParticlePreset preset) { + this.effect = effect; this.preset = preset; } + public DefinedParticleEffect getEffect() { + return effect; + } + public ParticlePreset getPreset() { return preset; } diff --git a/src/main/java/org/mesdag/particlestorm/api/RegisterCustomComponentEvent.java b/src/main/java/org/mesdag/particlestorm/api/RegisterCustomComponentEvent.java new file mode 100644 index 0000000..f056c61 --- /dev/null +++ b/src/main/java/org/mesdag/particlestorm/api/RegisterCustomComponentEvent.java @@ -0,0 +1,18 @@ +package org.mesdag.particlestorm.api; + +import com.mojang.serialization.Codec; +import net.minecraft.resources.ResourceLocation; +import net.neoforged.bus.api.Event; +import net.neoforged.fml.event.IModBusEvent; + +public class RegisterCustomComponentEvent extends Event implements IModBusEvent { + public RegisterCustomComponentEvent() {} + + public void register(ResourceLocation id, Codec codec) { + IComponent.register(id, codec); + } + + public void register(String vanillaPath, Codec codec) { + IComponent.register(vanillaPath, codec); + } +} diff --git a/src/main/java/org/mesdag/particlestorm/api/RegisterCustomEventNodeEvent.java b/src/main/java/org/mesdag/particlestorm/api/RegisterCustomEventNodeEvent.java new file mode 100644 index 0000000..3b98950 --- /dev/null +++ b/src/main/java/org/mesdag/particlestorm/api/RegisterCustomEventNodeEvent.java @@ -0,0 +1,13 @@ +package org.mesdag.particlestorm.api; + +import com.mojang.serialization.Codec; +import net.neoforged.bus.api.Event; +import net.neoforged.fml.event.IModBusEvent; + +public class RegisterCustomEventNodeEvent extends Event implements IModBusEvent { + public RegisterCustomEventNodeEvent() {} + + public void register(String name, Codec codec) { + IEventNode.register(name, codec); + } +} diff --git a/src/main/java/org/mesdag/particlestorm/api/RegisterCustomMaterialEvent.java b/src/main/java/org/mesdag/particlestorm/api/RegisterCustomMaterialEvent.java new file mode 100644 index 0000000..53dc56a --- /dev/null +++ b/src/main/java/org/mesdag/particlestorm/api/RegisterCustomMaterialEvent.java @@ -0,0 +1,19 @@ +package org.mesdag.particlestorm.api; + +import net.neoforged.bus.api.Event; +import net.neoforged.fml.event.IModBusEvent; +import org.mesdag.particlestorm.data.description.DescriptionMaterial; + +import java.util.function.Function; + +public class RegisterCustomMaterialEvent extends Event implements IModBusEvent { + private final Function consumer; + + public RegisterCustomMaterialEvent(Function function) { + this.consumer = function; + } + + public DescriptionMaterial register(String name) { + return consumer.apply(name); + } +} diff --git a/src/main/java/org/mesdag/particlestorm/api/RegisterCustomParticleTypeEvent.java b/src/main/java/org/mesdag/particlestorm/api/RegisterCustomParticleTypeEvent.java index 3b6f652..73832bf 100644 --- a/src/main/java/org/mesdag/particlestorm/api/RegisterCustomParticleTypeEvent.java +++ b/src/main/java/org/mesdag/particlestorm/api/RegisterCustomParticleTypeEvent.java @@ -11,8 +11,8 @@ 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.MolangParticleEngine; import org.mesdag.particlestorm.particle.ParticleEmitter; import org.mesdag.particlestorm.particle.ParticlePreset; @@ -55,7 +55,7 @@ public static V createParticle(Pa 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); + return (V) provider.create(emitter, MolangParticleEngine.INSTANCE.id2Particle().get(emitter.particleId), (ClientLevel) emitter.level, emitter.getX(), emitter.getY(), emitter.getZ(), sprites); } @FunctionalInterface diff --git a/src/main/java/org/mesdag/particlestorm/api/RegisterMolangQueriesEvent.java b/src/main/java/org/mesdag/particlestorm/api/RegisterMolangQueriesEvent.java index 9856c80..91faf44 100644 --- a/src/main/java/org/mesdag/particlestorm/api/RegisterMolangQueriesEvent.java +++ b/src/main/java/org/mesdag/particlestorm/api/RegisterMolangQueriesEvent.java @@ -4,16 +4,15 @@ import net.neoforged.fml.event.IModBusEvent; import java.util.function.BiConsumer; -import java.util.function.ToDoubleFunction; public class RegisterMolangQueriesEvent extends Event implements IModBusEvent { - private final BiConsumer> variable; + private final BiConsumer> variable; - public RegisterMolangQueriesEvent(BiConsumer> variable) { + public RegisterMolangQueriesEvent(BiConsumer> variable) { this.variable = variable; } - public void registerVariable(String name, ToDoubleFunction value) { + public void registerVariable(String name, ToFloatFunction value) { variable.accept(name, value); } } diff --git a/src/main/java/org/mesdag/particlestorm/api/ToFloatFunction.java b/src/main/java/org/mesdag/particlestorm/api/ToFloatFunction.java new file mode 100644 index 0000000..523cc13 --- /dev/null +++ b/src/main/java/org/mesdag/particlestorm/api/ToFloatFunction.java @@ -0,0 +1,6 @@ +package org.mesdag.particlestorm.api; + +@FunctionalInterface +public interface ToFloatFunction { + float applyAsFloat(T t); +} 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 f42d1a8..0000fd2 100644 --- a/src/main/java/org/mesdag/particlestorm/api/geckolib/GeckoLibHelper.java +++ b/src/main/java/org/mesdag/particlestorm/api/geckolib/GeckoLibHelper.java @@ -1,5 +1,7 @@ package org.mesdag.particlestorm.api.geckolib; +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.datafixers.DSL; import it.unimi.dsi.fastutil.ints.IntIterator; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.resources.ResourceLocation; @@ -11,15 +13,17 @@ import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.phys.Vec3; import net.neoforged.bus.api.IEventBus; +import net.neoforged.fml.ModLoader; 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.jetbrains.annotations.Nullable; +import org.joml.Quaternionf; import org.mesdag.particlestorm.ParticleStorm; import org.mesdag.particlestorm.data.molang.MolangExp; import org.mesdag.particlestorm.data.molang.VariableTable; import org.mesdag.particlestorm.mixed.*; +import org.mesdag.particlestorm.particle.MolangParticleEngine; import org.mesdag.particlestorm.particle.ParticleEmitter; import software.bernie.geckolib.animatable.GeoAnimatable; import software.bernie.geckolib.animatable.instance.AnimatableInstanceCache; @@ -29,66 +33,68 @@ import software.bernie.geckolib.loading.json.raw.LocatorValue; import java.util.List; +import java.util.Map; public final class GeckoLibHelper { - public static DeferredHolder, BlockEntityType> TEST_ENTITY; + 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)); + TEST_ENTITY = ENTITY.register("test_entity", () -> BlockEntityType.Builder.of(TestBlock.Entity::new, TEST.get()).build(DSL.remainderType())); BLOCK.register(bus); ENTITY.register(bus); } public static void registerRenderers(EntityRenderersEvent.RegisterRenderers event) { - event.registerBlockEntityRenderer(GeckoLibHelper.TEST_ENTITY.get(), ExampleBlockEntityRenderer::new); + event.registerBlockEntityRenderer(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) { - return value.values(); + public static void postEvent() { + ModLoader.postEvent(new RegisterLocatorPreTransformerEvent()); + } + + public static double[] getLocatorOffset(LocatorValue locatorValue) { + if (locatorValue.locatorClass() == null) { + return locatorValue.values(); } - return value.locatorClass().offset(); + return locatorValue.locatorClass().offset(); } - public static double[] getLocatorRotation(Object locatorValue) { - LocatorValue value = (LocatorValue) locatorValue; - if (value.locatorClass() == null) { + public static double[] getLocatorRotation(LocatorValue locatorValue) { + if (locatorValue.locatorClass() == null) { return new double[3]; } - return value.locatorClass().rotation(); + return locatorValue.locatorClass().rotation(); } - /** - * @return true means failed to add emitter - */ - public static boolean processParticleEffect(Object a, Object c, Object d) { - List bones = IAnimationController.of((AnimationController) c).particlestorm$getBonesWhichHasLocators(); + /// @return true means failed to add emitter + public static boolean processParticleEffect(@Nullable GeoAnimatable animatable, AnimationController controller, ParticleKeyframeData keyframeData) { + List bones = IAnimationController.of(controller).particlestorm$getBonesWhichHasLocators(); if (bones.isEmpty()) return true; - ParticleKeyframeData keyframeData = (ParticleKeyframeData) d; - IParticleKeyframeData iData = (IParticleKeyframeData) keyframeData; - Entity entity = null; - BlockEntity blockEntity = null; + IParticleKeyframeData iData = IParticleKeyframeData.of(keyframeData); + Entity entity; + BlockEntity blockEntity; VariableTable variableTable; Level level; - GeoAnimatable animatable = (GeoAnimatable) a; switch (animatable) { case Entity entity1 -> { entity = entity1; + blockEntity = null; variableTable = IEntity.of(entity).particlestorm$getVariableTable(); level = entity.level(); } - case GeoWithCurrentEntity withCurrentEntity when withCurrentEntity.getCurrentEntity() != null -> { + case ParticleStormGeoReplacedEntity withCurrentEntity when withCurrentEntity.getCurrentEntity() != null -> { entity = withCurrentEntity.getCurrentEntity(); + blockEntity = null; variableTable = IEntity.of(entity).particlestorm$getVariableTable(); level = entity.level(); } case BlockEntity entity1 when entity1.getLevel() != null -> { + entity = null; blockEntity = entity1; variableTable = ((IBlockEntity) blockEntity).particlestorm$getVariableTable(); level = blockEntity.getLevel(); @@ -100,44 +106,72 @@ public static boolean processParticleEffect(Object a, Object c, Object d) { ResourceLocation particle = iData.particlestorm$getParticle(); MolangExp expression = iData.particlestorm$getExpression(variableTable); IAnimatableInstanceCache cache = IAnimatableInstanceCache.of(animatable.getAnimatableInstanceCache()); - for (GeoBone geoBone : bones) { - IGeoBone bone = IGeoBone.of(geoBone); - LocatorValue locator = bone.particlestorm$getLocators().get(keyframeData.getLocator()); + for (GeoBone bone : bones) { + LocatorValue locator = IGeoBone.of(bone).particlestorm$getLocators().get(keyframeData.getLocator()); if (locator == null) continue; - ParticleEmitter current = PSGameClient.LOADER.getEmitter(cache.particlestorm$getCachedId().getInt(locator)); + ParticleEmitter current = MolangParticleEngine.INSTANCE.getEmitter(cache.particlestorm$getCachedId().getInt(locator)); if (current == null || current.isRemoved() || !particle.equals(current.particleId)) { Vec3 pos = entity == null ? blockEntity.getBlockPos().getBottomCenter() : entity.position(); ParticleEmitter emitter = new ParticleEmitter(level, pos, particle, expression); - PSGameClient.LOADER.addEmitter(emitter, false); + MolangParticleEngine.INSTANCE.addEmitter(emitter, false); cache.particlestorm$getCachedId().put(locator, emitter.id); emitter.attachEntity(entity); emitter.attachedBlock = blockEntity; double[] offset = getLocatorOffset(locator); double[] rotation = getLocatorRotation(locator); - emitter.offsetPos = new Vec3(offset[0] * 0.0625, offset[1] * 0.0625, -offset[2] * 0.0625); - emitter.offsetRot = new Vector3f((float) Math.toRadians(rotation[0]), (float) Math.toRadians(rotation[1]), (float) Math.toRadians(rotation[2])); - emitter.parentPosition = cache.particlestorm$getPosition(); - emitter.parentRotation = cache.particlestorm$getRotation(); - emitter.parentMode = ParticleEmitter.ParentMode.LOCATOR; + LocatorState state = cache.particlestorm$getLocatorState(locator); + state.px = (float) (offset[0] * 0.0625); + state.py = (float) (offset[1] * 0.0625); + state.pz = (float) (offset[2] * 0.0625); + state.rx = (float) Math.toRadians(rotation[0]); + state.ry = (float) Math.toRadians(rotation[1]); + state.rz = (float) Math.toRadians(rotation[2]); } } return false; } - public static void setCurrentEntity(Object animatable, Entity entity) { - if (animatable instanceof GeoWithCurrentEntity withCurrentEntity) { + public static void setCurrentEntity(GeoAnimatable animatable, @Nullable Entity entity) { + if (animatable instanceof ParticleStormGeoReplacedEntity withCurrentEntity) { withCurrentEntity.setCurrentEntity(entity); } } - public static void removeEmittersWhenAnimationChange(Object animationState, Object animatableInstanceCache) { + public static void removeEmittersWhenAnimationChange(AnimationController.State animationState, AnimatableInstanceCache animatableInstanceCache) { if (animationState == AnimationController.State.TRANSITIONING) { - IntIterator iterator = IAnimatableInstanceCache.of((AnimatableInstanceCache) animatableInstanceCache).particlestorm$getCachedId().values().iterator(); + IntIterator iterator = IAnimatableInstanceCache.of(animatableInstanceCache).particlestorm$getCachedId().values().iterator(); while (iterator.hasNext()) { - PSGameClient.LOADER.removeEmitter(iterator.nextInt(), false); + MolangParticleEngine.INSTANCE.removeEmitter(iterator.nextInt(), false); iterator.remove(); } } } + + private static final PoseStack poseStack = new PoseStack(); + private static final Quaternionf quaternion = new Quaternionf(); + + public static void transformLocator(GeoBone bone, GeoAnimatable animatable, float partialTick) { + Map locators = IGeoBone.of(bone).particlestorm$getLocators(); + if (locators == null || locators.isEmpty()) return; + poseStack.pushPose(); + RegisterLocatorPreTransformerEvent.getTransformer(animatable).transform(bone, animatable, poseStack, partialTick); + IAnimatableInstanceCache cache = IAnimatableInstanceCache.of(animatable.getAnimatableInstanceCache()); + for (LocatorValue locator : locators.values()) { + ParticleEmitter emitter = MolangParticleEngine.INSTANCE.getEmitter(cache.particlestorm$getCachedId().getInt(locator)); + if (emitter == null || emitter.isRemoved()) continue; + LocatorState state = cache.particlestorm$getLocatorState(locator); + poseStack.pushPose(); + poseStack.mulPose(quaternion.rotationXYZ(state.rx, state.ry, state.rz)); + poseStack.translate(-state.px, state.py, state.pz); + emitter.parentSpace = poseStack.last().pose(); + poseStack.popPose(); + } + poseStack.popPose(); + } + + public static class LocatorState { + float px, py, pz; + float rx, ry, rz; + } } diff --git a/src/main/java/org/mesdag/particlestorm/api/geckolib/GeoWithCurrentEntity.java b/src/main/java/org/mesdag/particlestorm/api/geckolib/GeoWithCurrentEntity.java deleted file mode 100644 index 435b65c..0000000 --- a/src/main/java/org/mesdag/particlestorm/api/geckolib/GeoWithCurrentEntity.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.mesdag.particlestorm.api.geckolib; - -import net.minecraft.world.entity.Entity; -import software.bernie.geckolib.animatable.GeoReplacedEntity; - -public interface GeoWithCurrentEntity extends GeoReplacedEntity { - Entity getCurrentEntity(); - - void setCurrentEntity(Entity entity); -} diff --git a/src/main/java/org/mesdag/particlestorm/api/geckolib/ParticleStormGeoReplacedEntity.java b/src/main/java/org/mesdag/particlestorm/api/geckolib/ParticleStormGeoReplacedEntity.java new file mode 100644 index 0000000..5e4cccb --- /dev/null +++ b/src/main/java/org/mesdag/particlestorm/api/geckolib/ParticleStormGeoReplacedEntity.java @@ -0,0 +1,11 @@ +package org.mesdag.particlestorm.api.geckolib; + +import net.minecraft.world.entity.Entity; +import org.jetbrains.annotations.Nullable; +import software.bernie.geckolib.animatable.GeoReplacedEntity; + +public interface ParticleStormGeoReplacedEntity extends GeoReplacedEntity { + @Nullable Entity getCurrentEntity(); + + void setCurrentEntity(@Nullable Entity entity); +} diff --git a/src/main/java/org/mesdag/particlestorm/api/geckolib/RegisterLocatorPreTransformerEvent.java b/src/main/java/org/mesdag/particlestorm/api/geckolib/RegisterLocatorPreTransformerEvent.java new file mode 100644 index 0000000..e7d6aa4 --- /dev/null +++ b/src/main/java/org/mesdag/particlestorm/api/geckolib/RegisterLocatorPreTransformerEvent.java @@ -0,0 +1,147 @@ +package org.mesdag.particlestorm.api.geckolib; + +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.math.Axis; +import it.unimi.dsi.fastutil.objects.Reference2ObjectMap; +import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap; +import net.minecraft.client.renderer.entity.LivingEntityRenderer; +import net.minecraft.core.Direction; +import net.minecraft.util.Mth; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.Pose; +import net.minecraft.world.item.Item; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.BlockEntityType; +import net.neoforged.bus.api.Event; +import net.neoforged.fml.event.IModBusEvent; +import software.bernie.geckolib.animatable.GeoAnimatable; +import software.bernie.geckolib.animatable.GeoBlockEntity; +import software.bernie.geckolib.animatable.GeoEntity; +import software.bernie.geckolib.animatable.GeoItem; +import software.bernie.geckolib.animatable.instance.AnimatableInstanceCache; +import software.bernie.geckolib.cache.object.GeoBone; +import software.bernie.geckolib.util.RenderUtil; + +import java.util.ArrayDeque; +import java.util.Deque; + +public class RegisterLocatorPreTransformerEvent extends Event implements IModBusEvent { + private static final Reference2ObjectMap> singletonTransformers = new Reference2ObjectOpenHashMap<>(); + private static final Reference2ObjectMap, Transformer> blockEntityTransformers = new Reference2ObjectOpenHashMap<>(); + private static final Reference2ObjectMap, Transformer> entityTransformers = new Reference2ObjectOpenHashMap<>(); + + RegisterLocatorPreTransformerEvent() {} + + public void register(T item, Transformer transformer) { + singletonTransformers.put(item.getAnimatableInstanceCache(), transformer); + } + + public void register(BlockEntityType blockEntity, Transformer transformer) { + blockEntityTransformers.put(blockEntity, transformer); + } + + public void register(EntityType entity, Transformer transformer) { + entityTransformers.put(entity, transformer); + } + + public void register(T replacedEntity, Transformer transformer) { + singletonTransformers.put(replacedEntity.getAnimatableInstanceCache(), transformer); + } + + @SuppressWarnings("unchecked") + public static Transformer getTransformer(T animatable) { + Transformer transformer = switch (animatable) { + case Entity entity -> entityTransformers.getOrDefault(entity.getType(), Transformer::entityTransformer); + case BlockEntity blockEntity -> blockEntityTransformers.getOrDefault(blockEntity.getType(), Transformer::defaultTransformer); + case Item ignored -> singletonTransformers.getOrDefault(animatable.getAnimatableInstanceCache(), Transformer::defaultTransformer); + case ParticleStormGeoReplacedEntity ignored -> singletonTransformers.getOrDefault(animatable.getAnimatableInstanceCache(), Transformer::replacedEntityTransformer); + default -> null; + }; + return transformer == null ? Transformer::defaultTransformer : (Transformer) transformer; + } + + @FunctionalInterface + public interface Transformer { + void transform(GeoBone bone, T animatable, PoseStack poseStack, float partialTick); + + static void replacedEntityTransformer(GeoBone bone, GeoAnimatable animatable, PoseStack poseStack, float partialTick) { + Entity entity = ((ParticleStormGeoReplacedEntity) animatable).getCurrentEntity(); + if (entity != null) { + transformEntity(entity, poseStack, partialTick); + } + defaultTransformer(bone, animatable, poseStack, partialTick); + } + + static void entityTransformer(GeoBone bone, GeoAnimatable animatable, PoseStack poseStack, float partialTick) { + transformEntity((Entity) animatable, poseStack, partialTick); + defaultTransformer(bone, animatable, poseStack, partialTick); + } + + /// [software.bernie.geckolib.renderer.GeoEntityRenderer#actuallyRender] + static void transformEntity(Entity entity, PoseStack poseStack, float partialTick) { + LivingEntity living = entity instanceof LivingEntity livingEntity ? livingEntity : null; + boolean shouldSit = entity.isPassenger() && (entity.getVehicle() != null); + float lerpBodyRot = living == null ? 0 : Mth.lerp(partialTick, living.yBodyRotO, living.yBodyRot); + float lerpHeadRot = living == null ? 0 : Mth.lerp(partialTick, living.yHeadRotO, living.yHeadRot); + + if (shouldSit && entity.getVehicle() instanceof LivingEntity livingEntity) { + lerpBodyRot = Mth.rotLerp(partialTick, livingEntity.yBodyRotO, livingEntity.yBodyRot); + float netHeadYaw = lerpHeadRot - lerpBodyRot; + float clampedHeadYaw = Mth.clamp(Mth.wrapDegrees(netHeadYaw), -85, 85); + lerpBodyRot = lerpHeadRot - clampedHeadYaw; + + if (clampedHeadYaw * clampedHeadYaw > 2500f) { + lerpBodyRot += clampedHeadYaw * 0.2f; + } + } + if (entity.getPose() == Pose.SLEEPING && living != null) { + Direction bedDirection = living.getBedOrientation(); + if (bedDirection != null) { + float eyePosOffset = living.getEyeHeight(Pose.STANDING) - 0.1F; + poseStack.translate(-bedDirection.getStepX() * eyePosOffset, 0, -bedDirection.getStepZ() * eyePosOffset); + } + } + float nativeScale = living != null ? living.getScale() : 1; + poseStack.scale(nativeScale, nativeScale, nativeScale); + if (entity.isFullyFrozen()) { + lerpBodyRot += (float) (Math.cos(entity.tickCount * 3.25d) * Math.PI * 0.4d); + } + if (!entity.hasPose(Pose.SLEEPING)) { + poseStack.mulPose(Axis.YP.rotationDegrees(180 - lerpBodyRot)); + } + if (living != null) { + if (living.deathTime > 0) { + float deathRotation = (living.deathTime + partialTick - 1f) / 20f * 1.6f; + poseStack.mulPose(Axis.ZP.rotationDegrees(Math.min(Mth.sqrt(deathRotation), 1) * 90)); + } else if (living.isAutoSpinAttack()) { + poseStack.mulPose(Axis.XP.rotationDegrees(-90f - living.getXRot())); + poseStack.mulPose(Axis.YP.rotationDegrees((living.tickCount + partialTick) * -75f)); + } else if (entity.hasPose(Pose.SLEEPING)) { + Direction bedOrientation = living.getBedOrientation(); + + poseStack.mulPose(Axis.YP.rotationDegrees(bedOrientation != null ? RenderUtil.getDirectionAngle(bedOrientation) : lerpBodyRot)); + poseStack.mulPose(Axis.ZP.rotationDegrees(90)); + poseStack.mulPose(Axis.YP.rotationDegrees(270f)); + } else if (LivingEntityRenderer.isEntityUpsideDown(living)) { + poseStack.translate(0, (entity.getBbHeight() + 0.1f) / nativeScale, 0); + poseStack.mulPose(Axis.ZP.rotationDegrees(180f)); + } + } + poseStack.translate(0, 0.01f, 0); + } + + static void defaultTransformer(GeoBone bone, GeoAnimatable animatable, PoseStack poseStack, float partialTick) { + Deque chain = new ArrayDeque<>(); + GeoBone current = bone; + while (current != null) { + chain.add(current); + current = current.getParent(); + } + while (!chain.isEmpty()) { + RenderUtil.prepMatrixForBone(poseStack, chain.pollLast()); + } + } + } +} diff --git a/src/main/java/org/mesdag/particlestorm/api/geckolib/ReplacedCreeperEntity.java b/src/main/java/org/mesdag/particlestorm/api/geckolib/ReplacedCreeperEntity.java index 21e4d9d..7924f5e 100644 --- a/src/main/java/org/mesdag/particlestorm/api/geckolib/ReplacedCreeperEntity.java +++ b/src/main/java/org/mesdag/particlestorm/api/geckolib/ReplacedCreeperEntity.java @@ -2,16 +2,21 @@ import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.EntityType; +import org.jetbrains.annotations.Nullable; import software.bernie.geckolib.animatable.instance.AnimatableInstanceCache; import software.bernie.geckolib.animation.AnimatableManager; import software.bernie.geckolib.animation.AnimationController; import software.bernie.geckolib.constant.DefaultAnimations; import software.bernie.geckolib.util.GeckoLibUtil; -public class ReplacedCreeperEntity implements GeoWithCurrentEntity { +public class ReplacedCreeperEntity implements ParticleStormGeoReplacedEntity { + public static final ReplacedCreeperEntity INSTANCE = new ReplacedCreeperEntity(); + private final AnimatableInstanceCache cache = GeckoLibUtil.createInstanceCache(this); private Entity currentEntity; + private ReplacedCreeperEntity() {} + public void registerControllers(AnimatableManager.ControllerRegistrar controllers) { controllers.add(new AnimationController[]{DefaultAnimations.genericWalkIdleController(this)}); } @@ -30,7 +35,7 @@ public Entity getCurrentEntity() { } @Override - public void setCurrentEntity(Entity entity) { + public void setCurrentEntity(@Nullable Entity entity) { this.currentEntity = entity; } } diff --git a/src/main/java/org/mesdag/particlestorm/api/geckolib/ReplacedCreeperModel.java b/src/main/java/org/mesdag/particlestorm/api/geckolib/ReplacedCreeperModel.java deleted file mode 100644 index 07011f6..0000000 --- a/src/main/java/org/mesdag/particlestorm/api/geckolib/ReplacedCreeperModel.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.mesdag.particlestorm.api.geckolib; - -import org.mesdag.particlestorm.ParticleStorm; -import software.bernie.geckolib.model.DefaultedEntityGeoModel; - -public class ReplacedCreeperModel extends DefaultedEntityGeoModel { - public ReplacedCreeperModel() { - super(ParticleStorm.asResource("creeper")); - } -} diff --git a/src/main/java/org/mesdag/particlestorm/api/geckolib/ReplacedCreeperRenderer.java b/src/main/java/org/mesdag/particlestorm/api/geckolib/ReplacedCreeperRenderer.java index 4361dac..3792141 100644 --- a/src/main/java/org/mesdag/particlestorm/api/geckolib/ReplacedCreeperRenderer.java +++ b/src/main/java/org/mesdag/particlestorm/api/geckolib/ReplacedCreeperRenderer.java @@ -7,12 +7,14 @@ import net.minecraft.util.Mth; import net.minecraft.world.entity.monster.Creeper; import org.jetbrains.annotations.Nullable; +import org.mesdag.particlestorm.ParticleStorm; import software.bernie.geckolib.cache.object.BakedGeoModel; +import software.bernie.geckolib.model.DefaultedEntityGeoModel; import software.bernie.geckolib.renderer.GeoReplacedEntityRenderer; public class ReplacedCreeperRenderer extends GeoReplacedEntityRenderer { - public ReplacedCreeperRenderer(EntityRendererProvider.Context renderManager) { - super(renderManager, new ReplacedCreeperModel(), new ReplacedCreeperEntity()); + public ReplacedCreeperRenderer(EntityRendererProvider.Context context) { + super(context, new DefaultedEntityGeoModel<>(ParticleStorm.asResource("creeper")), ReplacedCreeperEntity.INSTANCE); } @Override diff --git a/src/main/java/org/mesdag/particlestorm/data/MathHelper.java b/src/main/java/org/mesdag/particlestorm/data/MathHelper.java index 1f5f000..e6f2e4a 100644 --- a/src/main/java/org/mesdag/particlestorm/data/MathHelper.java +++ b/src/main/java/org/mesdag/particlestorm/data/MathHelper.java @@ -106,7 +106,11 @@ public static void forCompound(Map table, List toInit, VariableTable vars) { for (VariableAssignment assignment : toInit) { // 重定向,防止因找不到变量而爆栈 - vars.setValue(assignment.variable().name(), assignment.value()); + Variable variable = new Variable(assignment.variable().name(), assignment.value()); + if (!assignment.variable().isMutable()) { + variable.markImmutable(); + } + vars.setValue(variable.name(), variable); } } } 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 06df93e..784fc90 100644 --- a/src/main/java/org/mesdag/particlestorm/data/component/EmitterInitialization.java +++ b/src/main/java/org/mesdag/particlestorm/data/component/EmitterInitialization.java @@ -18,6 +18,10 @@ public record EmitterInitialization(MolangExp creationExpression, MolangExp perU MolangExp.CODEC.fieldOf("per_update_expression").orElse(MolangExp.EMPTY).forGetter(EmitterInitialization::perUpdateExpression) ).apply(instance, EmitterInitialization::new)); + public EmitterInitialization { + creationExpression.markImmutable(); + } + @Override public Codec codec() { return CODEC; @@ -29,13 +33,13 @@ public List getAllMolangExp() { } @Override - public void update(ParticleEmitter entity) { - perUpdateExpression.calculate(entity); + public void update(ParticleEmitter emitter) { + perUpdateExpression.calculate(emitter); } @Override - public void apply(ParticleEmitter entity) { - creationExpression.calculate(entity); + public void apply(ParticleEmitter emitter) { + creationExpression.calculate(emitter); } @Override 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 4195334..1de62b7 100644 --- a/src/main/java/org/mesdag/particlestorm/data/component/EmitterLifetime.java +++ b/src/main/java/org/mesdag/particlestorm/data/component/EmitterLifetime.java @@ -3,11 +3,14 @@ import com.mojang.serialization.Codec; import com.mojang.serialization.codecs.RecordCodecBuilder; import org.mesdag.particlestorm.api.IEmitterComponent; +import org.mesdag.particlestorm.api.IMolangParticleInstance; import org.mesdag.particlestorm.data.molang.FloatMolangExp; import org.mesdag.particlestorm.data.molang.MolangExp; +import org.mesdag.particlestorm.particle.MolangParticleEngine; import org.mesdag.particlestorm.particle.ParticleEmitter; import java.util.List; +import java.util.Queue; 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. @@ -127,6 +130,14 @@ public void update(ParticleEmitter emitter) { e.apply(emitter); } emitter.updateRandoms(emitter.level.random); + Queue queue = MolangParticleEngine.INSTANCE.getParticlesForEmitter(emitter); + if (queue != null) { + for (IMolangParticleInstance instance : queue) { + FloatMolangExp exp = instance.getPreset().perUpdateExpression; + if (exp == null) continue; + exp.calculate(instance); + } + } } } 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 a6a8967..b85b43d 100644 --- a/src/main/java/org/mesdag/particlestorm/data/component/EmitterLifetimeEvents.java +++ b/src/main/java/org/mesdag/particlestorm/data/component/EmitterLifetimeEvents.java @@ -2,7 +2,8 @@ import com.mojang.serialization.Codec; import com.mojang.serialization.codecs.RecordCodecBuilder; -import net.minecraft.util.Tuple; +import it.unimi.dsi.fastutil.floats.FloatObjectImmutablePair; +import it.unimi.dsi.fastutil.floats.FloatObjectPair; import org.mesdag.particlestorm.ParticleStorm; import org.mesdag.particlestorm.api.IEmitterComponent; import org.mesdag.particlestorm.api.IEventNode; @@ -13,7 +14,6 @@ import java.util.Comparator; import java.util.List; import java.util.Map; -import java.util.function.Function; /// Allows for lifetime events on the emitter to trigger certain events. /// @@ -34,8 +34,8 @@ public final class EmitterLifetimeEvents implements IEmitterComponent { public final Map> travelDistanceEvents; public final List loopingTravelDistanceEvents; - public final List, List>> sortedTimeline; - public final List, List>> sortedTravelDistance; + public final List>> sortedTimeline; + public final 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) @@ -54,14 +54,14 @@ public EmitterLifetimeEvents(List creationEvent, List expiration this.sortedTimeline = new ArrayList<>(); timeline.entrySet().stream() - .map(entry -> new Tuple<>(Float.parseFloat(entry.getKey()), entry.getValue())) - .sorted(Comparator.comparing(Tuple::getA)) - .forEachOrdered(tuple -> sortedTimeline.add(new Tuple<>(time -> time >= tuple.getA() * 20, tuple.getB()))); + .map(entry -> new FloatObjectImmutablePair<>(Float.parseFloat(entry.getKey()) * 20, entry.getValue())) + .sorted(Comparator.comparing(FloatObjectPair::leftFloat)) + .forEachOrdered(sortedTimeline::add); this.sortedTravelDistance = new ArrayList<>(); travelDistanceEvents.entrySet().stream() - .map(entry -> new Tuple<>(Float.parseFloat(entry.getKey()), entry.getValue())) - .sorted(Comparator.comparing(Tuple::getA)) - .forEachOrdered(tuple -> sortedTravelDistance.add(new Tuple<>(dist -> dist >= tuple.getA(), tuple.getB()))); + .map(entry -> new FloatObjectImmutablePair<>(Float.parseFloat(entry.getKey()), entry.getValue())) + .sorted(Comparator.comparing(FloatObjectPair::leftFloat)) + .forEachOrdered(sortedTravelDistance::add); } @Override @@ -77,19 +77,19 @@ public List getAllMolangExp() { @Override public void update(ParticleEmitter emitter) { for (int i = emitter.lastTimeline; i < sortedTimeline.size(); i++) { - Tuple, List> tuple = sortedTimeline.get(i); - if (tuple.getA().apply(emitter.lifetime)) { + FloatObjectPair> pair = sortedTimeline.get(i); + if (emitter.age >= pair.leftFloat()) { emitter.lastTimeline = i + 1; - executes(emitter, tuple.getB()); + executes(emitter, pair.right()); break; } } if (emitter.moveDist == emitter.moveDistO) return; for (int i = emitter.lastTravelDist; i < sortedTravelDistance.size(); i++) { - Tuple, List> tuple = sortedTravelDistance.get(i); - if (tuple.getA().apply(emitter.moveDist)) { + FloatObjectPair> pair = sortedTravelDistance.get(i); + if (emitter.moveDist >= pair.leftFloat()) { emitter.lastTravelDist = i + 1; - executes(emitter, tuple.getB()); + executes(emitter, pair.right()); break; } } @@ -105,11 +105,14 @@ public void update(ParticleEmitter emitter) { @Override public void apply(ParticleEmitter emitter) { - emitter.children.removeIf(child -> { - child.parent = null; - child.remove(); - return true; - }); + List children = emitter.getChildren(false); + if (children != null) { + children.removeIf(child -> { + child.parent = null; + child.remove(); + return true; + }); + } executes(emitter, creationEvent); emitter.cachedLooping = new float[loopingTravelDistanceEvents.size()]; } 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 d5ff702..2dac9f5 100644 --- a/src/main/java/org/mesdag/particlestorm/data/component/EmitterLocalSpace.java +++ b/src/main/java/org/mesdag/particlestorm/data/component/EmitterLocalSpace.java @@ -22,9 +22,9 @@ /// `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), - Codec.BOOL.fieldOf("rotation").orElse(false).forGetter(EmitterLocalSpace::rotation), - Codec.BOOL.fieldOf("velocity").orElse(false).forGetter(EmitterLocalSpace::velocity) + Codec.BOOL.lenientOptionalFieldOf("position", false).forGetter(EmitterLocalSpace::position), + Codec.BOOL.lenientOptionalFieldOf("rotation", false).forGetter(EmitterLocalSpace::rotation), + Codec.BOOL.lenientOptionalFieldOf("velocity", false).forGetter(EmitterLocalSpace::velocity) ).apply(instance, EmitterLocalSpace::new)); public EmitterLocalSpace(boolean position, boolean rotation, boolean velocity) { 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 784dce7..988e883 100644 --- a/src/main/java/org/mesdag/particlestorm/data/component/EmitterShape.java +++ b/src/main/java/org/mesdag/particlestorm/data/component/EmitterShape.java @@ -3,12 +3,12 @@ import com.mojang.datafixers.util.Either; 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.Entity; import net.minecraft.world.entity.EntityDimensions; import net.minecraft.world.phys.Vec3; import org.joml.Quaternionf; @@ -18,12 +18,13 @@ 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.EmitterPreset; +import org.mesdag.particlestorm.particle.MolangParticleEngine; import org.mesdag.particlestorm.particle.ParticleEmitter; import org.mesdag.particlestorm.particle.ParticlePreset; import java.util.Arrays; import java.util.List; +import java.util.Queue; public abstract sealed class EmitterShape implements IEmitterComponent permits EmitterShape.Disc, EmitterShape.Box, EmitterShape.EntityAABB, EmitterShape.Point, EmitterShape.Sphere { protected final boolean surfaceOnly; @@ -38,16 +39,16 @@ public boolean isSurfaceOnly() { } @Override - public void update(ParticleEmitter entity) { - if (entity.spawned) return; - if (entity.spawnDuration <= 1 || entity.age % entity.spawnDuration == 0) { - for (int num = 0; num < entity.spawnRate; num++) { - if (hasSpaceInParticleLimit(entity)) { - emittingParticle(entity); + public void update(ParticleEmitter emitter) { + if (emitter.spawned) return; + if (emitter.spawnDuration <= 1 || emitter.age % emitter.spawnDuration == 0) { + for (int num = 0; num < emitter.spawnRate; num++) { + if (hasSpaceInParticleLimit(emitter)) { + emittingParticle(emitter); } } - if (entity.getPreset().emitterRateType == EmitterRate.Type.INSTANT) { - entity.spawned = true; + if (emitter.getPreset().emitterRateType == EmitterRate.Type.INSTANT) { + emitter.spawned = true; } } } @@ -77,29 +78,22 @@ private void emittingParticle(Par component.apply(instance); } speed.mul(instance.getInitialSpeed()); - if (emitter.parentMode == ParticleEmitter.ParentMode.LOCATOR) { - position.x *= -1; - position.y *= -1; - speed.x *= -1; - speed.y *= -1; - } - EmitterPreset emitterPreset = emitter.getPreset(); - if (emitter.parentMode != ParticleEmitter.ParentMode.WORLD && emitterPreset.localPosition && !emitterPreset.localRotation) { - speed.x *= -1; - speed.z *= -1; - } - if (emitterPreset.localRotation) { - MathHelper.applyEuler(emitter.rot.x, emitter.rot.y, 0.0F, position); - } - if (emitterPreset.localPosition) { + speed.mul(emitter.invTickRate); + + if (emitter.isLocalSpace()) { + if (!emitter.getPreset().localPosition) { + Vec3 emitterPos = emitter.getPosition(); + position.add((float) emitterPos.x, (float) emitterPos.y, (float) emitterPos.z); + } + Entity attachedEntity = emitter.getAttachedEntity(); + if (attachedEntity != null && emitter.getPreset().localVelocity) { + Vec3 emitterVec = attachedEntity.getDeltaMovement(); + speed.add((float) emitterVec.x, (float) emitterVec.y, (float) emitterVec.z); + } + } else { Vec3 emitterPos = emitter.getPosition(); position.add((float) emitterPos.x, (float) emitterPos.y, (float) emitterPos.z); } - speed.mul(emitter.invTickRate); - if (emitter.getAttachedEntity() != null && emitterPreset.localVelocity) { - Vec3 emitterVec = emitter.getAttachedEntity().getDeltaMovement(); - speed.add((float) emitterVec.x, (float) emitterVec.y, (float) emitterVec.z); - } instance.setParticleSpeed(speed.x, speed.y, speed.z); instance.setPos(position.x, position.y, position.z); @@ -109,20 +103,24 @@ private void emittingParticle(Par for (IParticleComponent component : particlePreset.effect.orderedParticleComponents) { component.apply(instance); } + if (instance.isDiscarded()) { + return; + } instance.setComponents(particlePreset.effect.orderedParticleComponentsWhichRequireUpdate); if (!particlePreset.motionDynamic) instance.setParticleSpeed(0.0, 0.0, 0.0); - Minecraft.getInstance().particleEngine.add(instance); + MolangParticleEngine.INSTANCE.addParticle(instance); } private static boolean hasSpaceInParticleLimit(ParticleEmitter emitter) { ParticleGroup particleGroup = emitter.particleGroup; - return Minecraft.getInstance().particleEngine.trackedParticleCounts.getInt(particleGroup) < particleGroup.getLimit(); + Queue queue = MolangParticleEngine.INSTANCE.getParticlesForEmitter(emitter); + return queue == null || queue.size() < particleGroup.getLimit(); } /// 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), + FloatMolangExp3.CODEC.fieldOf("offset").orElse(FloatMolangExp3.ZERO).forGetter(disc -> disc.offset), FloatMolangExp.CODEC.fieldOf("radius").orElse(FloatMolangExp.ONE).forGetter(disc -> disc.radius), PlaneNormal.CODEC.fieldOf("plane_normal").orElse(PlaneNormal.Y).forGetter(disc -> disc.planeNormal), Direction.CODEC.fieldOf("direction").orElse(Direction.OUTWARDS).forGetter(disc -> disc.direction), @@ -247,7 +245,11 @@ public Codec codec() { @Override public List getAllMolangExp() { - return List.of(direction.direct.exp1(), direction.direct.exp2(), direction.direct.exp3()); + return List.of( + offset.exp1(), offset.exp2(), offset.exp3(), + halfDimensions.exp1(), halfDimensions.exp2(), halfDimensions.exp3(), + direction.direct.exp1(), direction.direct.exp2(), direction.direct.exp3() + ); } @Override 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 f929548..3b817fc 100644 --- a/src/main/java/org/mesdag/particlestorm/data/component/ParticleAppearanceLighting.java +++ b/src/main/java/org/mesdag/particlestorm/data/component/ParticleAppearanceLighting.java @@ -14,6 +14,8 @@ 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(); + private ParticleAppearanceLighting() {} + @Override public Codec codec() { return CODEC; 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 001a438..182db16 100644 --- a/src/main/java/org/mesdag/particlestorm/data/component/ParticleExpireIfInBlocks.java +++ b/src/main/java/org/mesdag/particlestorm/data/component/ParticleExpireIfInBlocks.java @@ -1,15 +1,13 @@ package org.mesdag.particlestorm.data.component; -import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.serialization.Codec; -import net.minecraft.commands.arguments.blocks.BlockStateParser; -import net.minecraft.core.HolderLookup; -import net.minecraft.core.registries.Registries; +import net.minecraft.core.BlockPos; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.resources.ResourceLocation; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.Blocks; -import net.minecraft.world.level.block.state.BlockState; -import org.mesdag.particlestorm.ParticleStorm; +import org.joml.Vector3f; +import org.mesdag.particlestorm.api.IMolangParticleInstance; import org.mesdag.particlestorm.api.IParticleComponent; import org.mesdag.particlestorm.data.molang.MolangExp; @@ -26,11 +24,11 @@ public final class ParticleExpireIfInBlocks implements IParticleComponent { blocks -> List.copyOf(blocks.ids) ); public final Set ids; - public final Set states; + public final Set blocks; public ParticleExpireIfInBlocks(Set ids) { this.ids = ids; - this.states = new HashSet<>(); + this.blocks = new HashSet<>(); } @Override @@ -45,23 +43,35 @@ public List getAllMolangExp() { @Override public void initialize(Level level) { - if (states.isEmpty()) { - try { - HolderLookup lookup = level.holderLookup(Registries.BLOCK); - for (String id : ids) { - BlockStateParser.BlockResult result = BlockStateParser.parseForBlock(lookup, id, false); - states.add(result.blockState()); - } - } catch (CommandSyntaxException e) { - states.add(Blocks.AIR.defaultBlockState()); - ParticleStorm.LOGGER.error(e.getMessage()); + if (blocks.isEmpty()) { + for (String id : ids) { + BuiltInRegistries.BLOCK.getOptional(ResourceLocation.parse(id)).ifPresent(blocks::add); } } } + private static final Vector3f vector3f = new Vector3f(); + + @Override + public void apply(IMolangParticleInstance instance) { + instance.getEmitter().local2World(vector3f.set((float) instance.getX(), (float) instance.getY(), (float) instance.getZ()), 1); + if (!blocks.contains(instance.getLevel().getBlockState(BlockPos.containing(vector3f.x, vector3f.y, vector3f.z)).getBlock())) { + instance.discard(); + } + } + + @Override + public void update(IMolangParticleInstance instance) { + apply(instance); + } + + @Override + public boolean requireUpdate() { + return true; + } + @Override public String toString() { - return "ParticleExpireIfInBlocks[" + - "blocks=" + ids + ']'; + return "ParticleExpireIfInBlocks[blocks=" + ids + ']'; } } 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 03cbcfc..7b21440 100644 --- a/src/main/java/org/mesdag/particlestorm/data/component/ParticleExpireIfNotInBlocks.java +++ b/src/main/java/org/mesdag/particlestorm/data/component/ParticleExpireIfNotInBlocks.java @@ -1,15 +1,13 @@ package org.mesdag.particlestorm.data.component; -import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.serialization.Codec; -import net.minecraft.commands.arguments.blocks.BlockStateParser; -import net.minecraft.core.HolderLookup; -import net.minecraft.core.registries.Registries; +import net.minecraft.core.BlockPos; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.resources.ResourceLocation; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.Blocks; -import net.minecraft.world.level.block.state.BlockState; -import org.mesdag.particlestorm.ParticleStorm; +import org.joml.Vector3f; +import org.mesdag.particlestorm.api.IMolangParticleInstance; import org.mesdag.particlestorm.api.IParticleComponent; import org.mesdag.particlestorm.data.molang.MolangExp; @@ -23,11 +21,11 @@ public final class ParticleExpireIfNotInBlocks implements IParticleComponent { blocks -> List.copyOf(blocks.ids) ); private final Set ids; - public final Set states; + public final Set blocks; public ParticleExpireIfNotInBlocks(Set ids) { this.ids = ids; - this.states = new HashSet<>(); + this.blocks = new HashSet<>(); } @Override @@ -42,23 +40,35 @@ public List getAllMolangExp() { @Override public void initialize(Level level) { - if (states.isEmpty()) { - try { - HolderLookup lookup = level.holderLookup(Registries.BLOCK); - for (String id : ids) { - BlockStateParser.BlockResult result = BlockStateParser.parseForBlock(lookup, id, false); - states.add(result.blockState()); - } - } catch (CommandSyntaxException e) { - states.add(Blocks.AIR.defaultBlockState()); - ParticleStorm.LOGGER.error(e.getMessage()); + if (blocks.isEmpty()) { + for (String id : ids) { + BuiltInRegistries.BLOCK.getOptional(ResourceLocation.parse(id)).ifPresent(blocks::add); } } } + private static final Vector3f vector3f = new Vector3f(); + + @Override + public void apply(IMolangParticleInstance instance) { + instance.getEmitter().local2World(vector3f.set((float) instance.getX(), (float) instance.getY(), (float) instance.getZ()), 1); + if (!blocks.contains(instance.getLevel().getBlockState(BlockPos.containing(vector3f.x, vector3f.y, vector3f.z)).getBlock())) { + instance.discard(); + } + } + + @Override + public void update(IMolangParticleInstance instance) { + apply(instance); + } + + @Override + public boolean requireUpdate() { + return true; + } + @Override public String toString() { - return "ParticleExpireIfNotInBlocks[" + - "blocks=" + ids + ']'; + return "ParticleExpireIfNotInBlocks[blocks=" + ids + ']'; } } 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 cf4e171..c087e3c 100644 --- a/src/main/java/org/mesdag/particlestorm/data/component/ParticleInitialization.java +++ b/src/main/java/org/mesdag/particlestorm/data/component/ParticleInitialization.java @@ -2,6 +2,7 @@ import com.mojang.serialization.Codec; import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.minecraft.resources.ResourceLocation; import org.mesdag.particlestorm.api.IMolangParticleInstance; import org.mesdag.particlestorm.api.IParticleComponent; import org.mesdag.particlestorm.data.molang.FloatMolangExp; @@ -10,16 +11,12 @@ import java.util.List; /// Starts the particle with a specified render expression. -public record ParticleInitialization(FloatMolangExp perRenderExpression) implements IParticleComponent { +public record ParticleInitialization(FloatMolangExp perRenderExpression, FloatMolangExp perUpdateExpression) implements IParticleComponent { + public static final ResourceLocation ID = ResourceLocation.withDefaultNamespace("particle_initialization"); public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( - FloatMolangExp.CODEC.fieldOf("per_render_expression").forGetter(ParticleInitialization::perRenderExpression), + FloatMolangExp.CODEC.lenientOptionalFieldOf("per_render_expression", FloatMolangExp.ZERO).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); - })); + ).apply(instance, ParticleInitialization::new)); @Override public Codec codec() { @@ -28,7 +25,7 @@ public Codec codec() { @Override public List getAllMolangExp() { - return List.of(perRenderExpression); + return List.of(perRenderExpression, perUpdateExpression); } @Override 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 00773c8..3bbef1d 100644 --- a/src/main/java/org/mesdag/particlestorm/data/component/ParticleLifeTimeEvents.java +++ b/src/main/java/org/mesdag/particlestorm/data/component/ParticleLifeTimeEvents.java @@ -2,8 +2,9 @@ import com.mojang.serialization.Codec; import com.mojang.serialization.codecs.RecordCodecBuilder; +import it.unimi.dsi.fastutil.floats.FloatObjectImmutablePair; +import it.unimi.dsi.fastutil.floats.FloatObjectPair; import net.minecraft.resources.ResourceLocation; -import net.minecraft.util.Tuple; import org.mesdag.particlestorm.ParticleStorm; import org.mesdag.particlestorm.api.IEventNode; import org.mesdag.particlestorm.api.IMolangParticleInstance; @@ -14,7 +15,6 @@ import java.util.Comparator; import java.util.List; import java.util.Map; -import java.util.function.Function; /// All events use the event names in the event section /// @@ -30,7 +30,7 @@ public final class ParticleLifeTimeEvents implements IParticleComponent { public final List expirationEvent; public final Map> timeline; - public final List, List>> sortedTimeline; + public final 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) @@ -42,9 +42,9 @@ public ParticleLifeTimeEvents(List creationEvent, List expiratio this.sortedTimeline = new ArrayList<>(); timeline.entrySet().stream() - .map(entry -> new Tuple<>(Float.parseFloat(entry.getKey()), entry.getValue())) - .sorted(Comparator.comparing(Tuple::getA)) - .forEachOrdered(tuple -> sortedTimeline.add(new Tuple<>(time -> time >= tuple.getA() * 20, tuple.getB()))); + .map(entry -> new FloatObjectImmutablePair<>(Float.parseFloat(entry.getKey()) * 20, entry.getValue())) + .sorted(Comparator.comparing(FloatObjectPair::leftFloat)) + .forEachOrdered(sortedTimeline::add); } @Override @@ -60,10 +60,10 @@ public List getAllMolangExp() { @Override 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.self().getLifetime())) { + FloatObjectPair> pair = sortedTimeline.get(i); + if (instance.getAge() >= pair.leftFloat()) { instance.setLastTimeline(i + 1); - executes(instance, tuple.getB()); + executes(instance, pair.right()); break; } } 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 c238685..8bca800 100644 --- a/src/main/java/org/mesdag/particlestorm/data/component/ParticleMotionCollision.java +++ b/src/main/java/org/mesdag/particlestorm/data/component/ParticleMotionCollision.java @@ -35,7 +35,14 @@ /// 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 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( BoolMolangExp.CODEC.lenientOptionalFieldOf("enabled", BoolMolangExp.TRUE).forGetter(ParticleMotionCollision::enabled), @@ -69,9 +76,8 @@ public void apply(IMolangParticleInstance instance) { update(instance); instance.setCollisionDrag(collisionDrag * instance.getInvTickRate()); instance.setCoefficientOfRestitution(coefficientOfRestitution); - float radius = Math.max(collisionRadius, Mth.EPSILON); - instance.self().setBoundingBox(instance.self().getBoundingBox().inflate(radius, 0.0, radius)); - instance.self().setLocationFromBoundingbox(); + instance.setCollisionRadius(Math.max(collisionRadius, Mth.EPSILON)); + instance.moveDirectly(0, 0, 0); instance.setExpireOnContact(expireOnContact); } @@ -92,10 +98,8 @@ public String toString() { '}'; } - /** - * @param event Triggers the specified event if the conditions are met - * @param minSpeed Optional minimum speed for event triggering - */ + /// @param event Triggers the specified event if the conditions are met + /// @param minSpeed Optional minimum speed for event triggering public record Event(String event, float minSpeed) { public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( Codec.STRING.fieldOf("event").forGetter(Event::event), 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 ba974cb..eee3159 100644 --- a/src/main/java/org/mesdag/particlestorm/data/component/ParticleMotionDynamic.java +++ b/src/main/java/org/mesdag/particlestorm/data/component/ParticleMotionDynamic.java @@ -35,8 +35,12 @@ /// 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 record ParticleMotionDynamic( + FloatMolangExp3 linerAcceleration, + FloatMolangExp linearDragCoefficient, + FloatMolangExp rotationAcceleration, + FloatMolangExp rotationDragCoefficient +) implements IParticleComponent { public static final ResourceLocation ID = ResourceLocation.withDefaultNamespace("particle_motion_dynamic"); public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( FloatMolangExp3.CODEC.fieldOf("linear_acceleration").orElse(FloatMolangExp3.ZERO).forGetter(ParticleMotionDynamic::linerAcceleration), 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 e725700..3eb37f3 100644 --- a/src/main/java/org/mesdag/particlestorm/data/component/ParticleMotionParametric.java +++ b/src/main/java/org/mesdag/particlestorm/data/component/ParticleMotionParametric.java @@ -2,6 +2,7 @@ import com.mojang.serialization.Codec; import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.minecraft.world.phys.Vec3; import org.mesdag.particlestorm.api.IMolangParticleInstance; import org.mesdag.particlestorm.api.IParticleComponent; import org.mesdag.particlestorm.data.molang.FloatMolangExp; @@ -25,9 +26,9 @@ /// 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), - FloatMolangExp3.CODEC.fieldOf("direction").orElse(FloatMolangExp3.ZERO).forGetter(ParticleMotionParametric::direction), - FloatMolangExp.CODEC.fieldOf("rotation").orElse(FloatMolangExp.ZERO).forGetter(ParticleMotionParametric::rotation) + FloatMolangExp3.CODEC.lenientOptionalFieldOf("relative_position", FloatMolangExp3.ZERO).forGetter(ParticleMotionParametric::relativePosition), + FloatMolangExp3.CODEC.lenientOptionalFieldOf("direction", FloatMolangExp3.ZERO).forGetter(ParticleMotionParametric::direction), + FloatMolangExp.CODEC.lenientOptionalFieldOf("rotation", FloatMolangExp.ZERO).forGetter(ParticleMotionParametric::rotation) ).apply(instance, ParticleMotionParametric::new)); @Override @@ -46,7 +47,8 @@ public List getAllMolangExp() { @Override public void update(IMolangParticleInstance instance) { float[] pos = relativePosition.calculate(instance); - instance.moveDirectly(pos[0], pos[1], pos[2]); + Vec3 position = instance.getEmitter().getPosition(); + instance.self().setPos(position.x + pos[0], position.y + pos[1], position.z + pos[2]); if (direction != FloatMolangExp3.ZERO) { float[] dir = direction.calculate(instance); instance.self().setParticleSpeed(dir[0], dir[1], dir[2]); 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 8e2c880..400788c 100644 --- a/src/main/java/org/mesdag/particlestorm/data/description/DescriptionMaterial.java +++ b/src/main/java/org/mesdag/particlestorm/data/description/DescriptionMaterial.java @@ -2,26 +2,52 @@ import com.mojang.serialization.Codec; import net.minecraft.util.StringRepresentable; +import net.neoforged.fml.ModLoader; +import org.mesdag.particlestorm.api.RegisterCustomMaterialEvent; -import java.util.Locale; +import java.util.ArrayList; +import java.util.List; -public enum DescriptionMaterial implements StringRepresentable { - TERRAIN_SHEET, - PARTICLE_SHEET_OPAQUE, - PARTICLE_SHEET_TRANSLUCENT, - PARTICLE_SHEET_LIT, - CUSTOM, - NO_RENDER, +public final class DescriptionMaterial implements StringRepresentable { + private static List list = new ArrayList<>(); + private static DescriptionMaterial[] values; - particles_alpha, - particles_blend, - particles_add, - particles_opaque; + public static final DescriptionMaterial + TERRAIN_SHEET = register("terrain_sheet"), + PARTICLE_SHEET_OPAQUE = register("particle_sheet_opaque"), + PARTICLE_SHEET_TRANSLUCENT = register("particle_sheet_translucent"), + PARTICLE_SHEET_LIT = register("particle_sheet_lit"), + CUSTOM = register("custom"), + NO_RENDER = register("no_renderer"), + particles_alpha = register("particles_alpha"), + particles_blend = register("particles_blend"), + particles_add = register("particles_add"), + particles_opaque = register("particles_opaque"); - public static final Codec CODEC = StringRepresentable.fromEnum(DescriptionMaterial::values); + + private static DescriptionMaterial register(String name) { + DescriptionMaterial material = new DescriptionMaterial(name); + list.add(material); + return material; + } + + public static final Codec CODEC = StringRepresentable.fromValues(() -> { + if (values == null) { + ModLoader.postEvent(new RegisterCustomMaterialEvent(DescriptionMaterial::register)); + values = list.toArray(DescriptionMaterial[]::new); + list = null; + } + return values; + }); + + private final String name; + + private DescriptionMaterial(String name) { + this.name = name; + } @Override public String getSerializedName() { - return name().toLowerCase(Locale.ROOT); + return name; } } diff --git a/src/main/java/org/mesdag/particlestorm/data/event/EventRandomize.java b/src/main/java/org/mesdag/particlestorm/data/event/EventRandomize.java index 43d391d..d202c1b 100644 --- a/src/main/java/org/mesdag/particlestorm/data/event/EventRandomize.java +++ b/src/main/java/org/mesdag/particlestorm/data/event/EventRandomize.java @@ -18,6 +18,7 @@ public final class EventRandomize implements IEventNode { public final List>> sortedNodes; + @SuppressWarnings({"rawtypes", "unchecked"}) public EventRandomize(List> nodes) { this.nodes = nodes; 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 18999e1..d3fdd69 100644 --- a/src/main/java/org/mesdag/particlestorm/data/event/NodeMolangExp.java +++ b/src/main/java/org/mesdag/particlestorm/data/event/NodeMolangExp.java @@ -3,8 +3,11 @@ import com.mojang.datafixers.util.Either; import com.mojang.serialization.Codec; import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.minecraft.world.phys.Vec3; +import org.joml.Vector3f; import org.mesdag.particlestorm.ParticleStorm; import org.mesdag.particlestorm.api.IEventNode; +import org.mesdag.particlestorm.api.IMolangParticleInstance; import org.mesdag.particlestorm.api.MolangInstance; import org.mesdag.particlestorm.data.molang.MolangExp; import org.mesdag.particlestorm.data.molang.compiler.MolangParser; @@ -31,6 +34,8 @@ public boolean shouldLog() { return log; } + private static final Vector3f vector3f = new Vector3f(); + @Override public void execute(MolangInstance instance) { if (variable == null && !expStr.isEmpty() && !expStr.isBlank()) { @@ -40,7 +45,13 @@ public void execute(MolangInstance instance) { if (variable != null) { double v = variable.get(instance); if (log) { - ParticleStorm.LOGGER.info("{}[{}]: {}={}", instance.getIdentity(), instance.getPosition(), expStr, v); + if (instance instanceof IMolangParticleInstance p) { + p.getEmitter().local2World(vector3f.set((float) p.getX(), (float) p.getY(), (float) p.getZ()), 1); + } else { + Vec3 pos = instance.getPosition(); + vector3f.set(pos.x, pos.y, pos.z); + } + ParticleStorm.LOGGER.info("{}[{},{},{}]: {}={}", instance.getIdentity(), vector3f.x, vector3f.y, vector3f.z, 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 3c12962..8437f48 100644 --- a/src/main/java/org/mesdag/particlestorm/data/event/ParticleEffect.java +++ b/src/main/java/org/mesdag/particlestorm/data/event/ParticleEffect.java @@ -9,11 +9,14 @@ import net.minecraft.resources.ResourceLocation; import net.minecraft.util.ByIdMap; import net.minecraft.util.StringRepresentable; -import org.mesdag.particlestorm.PSGameClient; +import net.minecraft.world.phys.Vec3; +import org.joml.Vector3f; import org.mesdag.particlestorm.api.IEventNode; +import org.mesdag.particlestorm.api.IMolangParticleInstance; import org.mesdag.particlestorm.api.MolangInstance; import org.mesdag.particlestorm.data.molang.MolangExp; import org.mesdag.particlestorm.data.molang.compiler.MolangQueries; +import org.mesdag.particlestorm.particle.MolangParticleEngine; import org.mesdag.particlestorm.particle.ParticleEmitter; import java.util.List; @@ -35,9 +38,17 @@ public ParticleEffect(ResourceLocation effect, Type type, MolangExp preEffectExp this(effect, type, preEffectExpression, List.of()); } + private static final Vector3f vector3f = new Vector3f(); + @Override public void execute(MolangInstance instance) { - PSGameClient.LOADER.addEmitter(new ParticleEmitter(instance.getEmitter(), this), false); + ParticleEmitter emitter = new ParticleEmitter(instance.getEmitter(), this); + if (instance instanceof IMolangParticleInstance p) { + p.getEmitter().local2World(vector3f.set((float) p.getX(), (float) p.getY(), (float) p.getZ()), 1); + emitter.setPos(new Vec3(vector3f.x, vector3f.y, vector3f.z)); + emitter.posO = emitter.getPosition(); + } + MolangParticleEngine.INSTANCE.addEmitter(emitter, false); } @Override diff --git a/src/main/java/org/mesdag/particlestorm/data/event/SoundEffect.java b/src/main/java/org/mesdag/particlestorm/data/event/SoundEffect.java index 8b280cc..6b77c59 100644 --- a/src/main/java/org/mesdag/particlestorm/data/event/SoundEffect.java +++ b/src/main/java/org/mesdag/particlestorm/data/event/SoundEffect.java @@ -11,7 +11,9 @@ import net.minecraft.sounds.SoundEvents; import net.minecraft.sounds.SoundSource; import net.minecraft.world.phys.Vec3; +import org.joml.Vector3f; import org.mesdag.particlestorm.api.IEventNode; +import org.mesdag.particlestorm.api.IMolangParticleInstance; import org.mesdag.particlestorm.api.MolangInstance; public record SoundEffect(Holder soundEffect) implements IEventNode { @@ -22,9 +24,16 @@ public record SoundEffect(Holder soundEffect) implements IEventNode SOUND_EFFECT_CODEC.fieldOf("sound_effect").orElseGet(() -> Holder.direct(SoundEvents.EMPTY)).forGetter(SoundEffect::soundEffect) ).apply(instance, SoundEffect::new)); + private static final Vector3f vector3f = new Vector3f(); + @Override public void execute(MolangInstance instance) { - Vec3 position = instance.getPosition(); - instance.getLevel().playLocalSound(position.x, position.y, position.z, soundEffect.value(), SoundSource.AMBIENT, 1.0F, 1.0F, true); + if (instance instanceof IMolangParticleInstance p) { + p.getEmitter().local2World(vector3f.set((float) p.getX(), (float) p.getY(), (float) p.getZ()), 1); + } else { + Vec3 pos = instance.getPosition(); + vector3f.set(pos.x, pos.y, pos.z); + } + instance.getLevel().playLocalSound(vector3f.x, vector3f.y, vector3f.z, soundEffect.value(), SoundSource.AMBIENT, 1.0F, 1.0F, true); } } diff --git a/src/main/java/org/mesdag/particlestorm/data/molang/BoolMolangExp.java b/src/main/java/org/mesdag/particlestorm/data/molang/BoolMolangExp.java index f552e5e..2184f3f 100644 --- a/src/main/java/org/mesdag/particlestorm/data/molang/BoolMolangExp.java +++ b/src/main/java/org/mesdag/particlestorm/data/molang/BoolMolangExp.java @@ -2,7 +2,6 @@ import com.mojang.datafixers.util.Either; import com.mojang.serialization.Codec; -import org.jetbrains.annotations.Nullable; import org.mesdag.particlestorm.api.MolangInstance; public class BoolMolangExp extends MolangExp { @@ -14,7 +13,7 @@ public class BoolMolangExp extends MolangExp { ); private final boolean constant; - public BoolMolangExp(boolean constant, @Nullable String expression) { + public BoolMolangExp(boolean constant, String expression) { super(expression); this.constant = constant; } diff --git a/src/main/java/org/mesdag/particlestorm/data/molang/FloatMolangExp.java b/src/main/java/org/mesdag/particlestorm/data/molang/FloatMolangExp.java index ef325dd..e0ddf2b 100644 --- a/src/main/java/org/mesdag/particlestorm/data/molang/FloatMolangExp.java +++ b/src/main/java/org/mesdag/particlestorm/data/molang/FloatMolangExp.java @@ -30,7 +30,7 @@ public boolean initialized() { @Override public float calculate(MolangInstance instance) { if (!initialized()) return 0.0F; - return variable == null ? constant : (float) variable.get(instance); + return variable == null ? constant : variable.get(instance); } @Override 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 6c3a3ec..a469e59 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,11 @@ public float[] calculate(MolangInstance instance) { return new float[]{exp1.calculate(instance), exp2.calculate(instance)}; } + public void markImmutable() { + exp1.markImmutable(); + exp2.markImmutable(); + } + @Deprecated public boolean initialized() { return exp1.initialized() && exp2.initialized(); diff --git a/src/main/java/org/mesdag/particlestorm/data/molang/FloatMolangExp3.java b/src/main/java/org/mesdag/particlestorm/data/molang/FloatMolangExp3.java index bbb055e..e6af884 100644 --- a/src/main/java/org/mesdag/particlestorm/data/molang/FloatMolangExp3.java +++ b/src/main/java/org/mesdag/particlestorm/data/molang/FloatMolangExp3.java @@ -19,6 +19,12 @@ public float[] calculate(MolangInstance instance) { return new float[]{exp1.calculate(instance), exp2.calculate(instance), exp3.calculate(instance)}; } + public void markImmutable() { + exp1.markImmutable(); + exp2.markImmutable(); + exp3.markImmutable(); + } + @Override public String toString() { return "FloatMolangExp3{" + diff --git a/src/main/java/org/mesdag/particlestorm/data/molang/FloatMolangExpList.java b/src/main/java/org/mesdag/particlestorm/data/molang/FloatMolangExpList.java index 3465921..a47d1f8 100644 --- a/src/main/java/org/mesdag/particlestorm/data/molang/FloatMolangExpList.java +++ b/src/main/java/org/mesdag/particlestorm/data/molang/FloatMolangExpList.java @@ -8,39 +8,27 @@ import java.util.List; import java.util.Objects; -public class FloatMolangExpList { +public record FloatMolangExpList(int size, List expressions) { public static final FloatMolangExpList EMPTY = new FloatMolangExpList(0, List.of()); public static final Codec CODEC = Codec.either(FloatMolangExp.CODEC, Codec.list(FloatMolangExp.CODEC)).xmap( either -> either.map(e -> new FloatMolangExpList(1, Collections.singletonList(e)), l -> new FloatMolangExpList(l.size(), l)), list -> list.size() == 1 ? Either.left(list.expressions.getFirst()) : Either.right(list.expressions) ); - private final int size; - private final List expressions; - public FloatMolangExpList(int size, List expressions) { + public FloatMolangExpList { if (size != expressions.size()) { throw new IllegalArgumentException("Size of " + size + " not match the size of expressions"); } - this.size = size; - this.expressions = expressions; } public FloatMolangExpList(int size, FloatMolangExp... expressions) { this(size, Arrays.stream(expressions).toList()); } - public int size() { - return size; - } - public boolean isEmpty() { return size == 0; } - public List getExpressions() { - return expressions; - } - public boolean isSingleExp() { return size == 1; } @@ -49,4 +37,10 @@ public FloatMolangExp getExp(int index) { Objects.checkIndex(index, size); return expressions.get(index); } + + public void markImmutable() { + for (FloatMolangExp exp : expressions) { + exp.markImmutable(); + } + } } 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 01585c4..e05f804 100644 --- a/src/main/java/org/mesdag/particlestorm/data/molang/MolangExp.java +++ b/src/main/java/org/mesdag/particlestorm/data/molang/MolangExp.java @@ -14,11 +14,12 @@ import java.util.Map; public class MolangExp { - public static final MolangExp EMPTY = Util.make(new MolangExp(""), exp -> exp.variable = new Constant(0.0)); + public static final MolangExp EMPTY = Util.make(new MolangExp(""), exp -> exp.variable = new Constant(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; + protected boolean immutable; public MolangExp(String expStr) { this.expStr = expStr; @@ -52,12 +53,19 @@ public String getExpStr() { public void compile(MolangParser parser) { if (variable == null && !expStr.isEmpty() && !expStr.isBlank()) { this.variable = parser.compileMolang(expStr); + if (immutable) { + variable.markImmutable(); + } } } public float calculate(MolangInstance instance) { if (!initialized()) return 0.0F; - return (float) variable.get(instance); + return variable.get(instance); + } + + public void markImmutable() { + this.immutable = true; } public MathValue getVariable() { diff --git a/src/main/java/org/mesdag/particlestorm/data/molang/VariableTable.java b/src/main/java/org/mesdag/particlestorm/data/molang/VariableTable.java index 12cae18..3dd179d 100644 --- a/src/main/java/org/mesdag/particlestorm/data/molang/VariableTable.java +++ b/src/main/java/org/mesdag/particlestorm/data/molang/VariableTable.java @@ -2,12 +2,12 @@ import org.jetbrains.annotations.Nullable; import org.mesdag.particlestorm.api.MolangInstance; +import org.mesdag.particlestorm.api.ToFloatFunction; import org.mesdag.particlestorm.data.molang.compiler.value.Variable; import java.util.Hashtable; import java.util.Map; import java.util.function.Function; -import java.util.function.ToDoubleFunction; public class VariableTable { public final Map table; @@ -22,16 +22,16 @@ public VariableTable(@Nullable VariableTable parent) { this(new Hashtable<>(), parent); } - public double getValue(String name, MolangInstance instance) { + public float getValue(String name, MolangInstance instance) { Variable variable = table.get(name); if (variable == null) { - if (parent == null) return 0.0; + if (parent == null) return 0; return parent.getValue(name, instance); } return variable.get(instance); } - public void setValue(String name, ToDoubleFunction function) { + public void setValue(String name, ToFloatFunction function) { Variable variable = table.get(name); if (variable == null) { table.put(name, new Variable(name, function)); diff --git a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/MathValue.java b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/MathValue.java index 18bb721..962cc97 100644 --- a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/MathValue.java +++ b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/MathValue.java @@ -1,38 +1,31 @@ package org.mesdag.particlestorm.data.molang.compiler; import org.mesdag.particlestorm.api.MolangInstance; +import org.mesdag.particlestorm.api.ToFloatFunction; -import java.util.function.ToDoubleFunction; +/// Base interface for all computational values in the math system +/// +/// All mathematical objects are an extension of this interface, allowing for an indefinitely-nestable +/// mathematical system that can be accessed via this one access point +public interface MathValue extends ToFloatFunction { + /// Get computed or stored value + float get(MolangInstance instance); -/** - * Base interface for all computational values in the math system - *

- * All mathematical objects are an extension of this interface, allowing for an indefinitely-nestable - * mathematical system that can be accessed via this one access point - */ -public interface MathValue extends ToDoubleFunction { - /** - * Get computed or stored value - */ - double get(MolangInstance instance); + default void set(ToFloatFunction function) {} - default void set(ToDoubleFunction function) {} - - /** - * Return whether this type of MathValue should be considered mutable; its value could change. - *
- * This is used to cache calculated values, optimising computational work - */ + /// Return whether this type of MathValue should be considered mutable; its value could change. + /// + /// This is used to cache calculated values, optimising computational work default boolean isMutable() { return true; } - /** - * Use {@link #get} - */ - @Deprecated() + void markImmutable(); + + /// Use [#get] + @Deprecated @Override - default double applyAsDouble(MolangInstance instance) { + default float applyAsFloat(MolangInstance instance) { return get(instance); } } 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 191f095..d9cbf2a 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 @@ -10,6 +10,7 @@ import org.mesdag.particlestorm.data.molang.compiler.function.limit.ClampFunction; import org.mesdag.particlestorm.data.molang.compiler.function.limit.MaxFunction; import org.mesdag.particlestorm.data.molang.compiler.function.limit.MinFunction; +import org.mesdag.particlestorm.data.molang.compiler.function.misc.IsBlockFunction; import org.mesdag.particlestorm.data.molang.compiler.function.misc.PiFunction; import org.mesdag.particlestorm.data.molang.compiler.function.misc.ToDegFunction; import org.mesdag.particlestorm.data.molang.compiler.function.misc.ToRadFunction; @@ -31,7 +32,7 @@ import static org.mesdag.particlestorm.data.molang.compiler.MolangQueries.applyPrefixAliases; public class MolangParser { - private static final Pattern EXPRESSION_FORMAT = Pattern.compile("^[\\w\\s_+-/*%^&|<>=!?:.,()]+$"); + private static final Pattern EXPRESSION_FORMAT = Pattern.compile("^[\\w\\s_+-/*%^&|<>=!?:.,()']+$"); private static final Pattern WHITESPACE = Pattern.compile("\\s"); private static final Pattern NUMERIC = Pattern.compile("^-?\\d+(\\.\\d+)?$"); private static final String MOLANG_RETURN = "return "; @@ -66,6 +67,7 @@ public class MolangParser { map.put("math.to_deg", ToDegFunction::new); map.put("math.to_rad", ToRadFunction::new); map.put("math.trunc", TruncateFunction::new); + map.put("query.is_block", IsBlockFunction::new); }); private final VariableTable table; @@ -247,7 +249,7 @@ public List>> compileSymbols(char[] chars) { public MathValue parseSymbols(List>> symbols) throws IllegalArgumentException { if (symbols.size() == 2) { - Optional prefix = symbols.getFirst().left().filter(left -> left.startsWith("-") || left.startsWith("!") || isFunctionRegistered(left)); + Optional prefix = symbols.getFirst().left().filter(left -> left.startsWith("-") || left.startsWith("!") || isFunctionRegistered(MolangQueries.applyQueryAliases(left))); Optional> group = symbols.get(1).right(); if (prefix.isPresent() && group.isPresent()) @@ -285,7 +287,10 @@ protected MathValue compileSingleValue(Either> symbol) t return new BooleanNegate(compileSingleValue(Either.left(string.substring(1)))); if (isNumeric(string)) - return new Constant(Double.parseDouble(string)); + return new Constant(Float.parseFloat(string)); + + if (string.startsWith("'") && string.endsWith("'")) + return new StringValue(string.substring(1, string.length() - 1)); if (isLikelyVariable(string)) { if (string.startsWith("-")) @@ -390,6 +395,8 @@ protected MathValue compileFunction(String name, List args) throws Il return new Negative(compileFunction(name.substring(1), args)); } + name = MolangQueries.applyQueryAliases(name); + if (!isFunctionRegistered(name)) return null; 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 091142a..24aba90 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 @@ -1,22 +1,21 @@ package org.mesdag.particlestorm.data.molang.compiler; +import com.google.common.collect.ImmutableMap; import net.minecraft.client.CameraType; import net.minecraft.client.Minecraft; import net.neoforged.fml.ModLoader; -import org.mesdag.particlestorm.PSGameClient; import org.mesdag.particlestorm.api.MolangInstance; import org.mesdag.particlestorm.api.RegisterMolangQueriesEvent; +import org.mesdag.particlestorm.api.ToFloatFunction; import org.mesdag.particlestorm.data.molang.compiler.value.Variable; +import org.mesdag.particlestorm.particle.MolangParticleEngine; -import java.util.Collection; -import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import java.util.function.ToDoubleFunction; public final class MolangQueries { - private static final Map UNFROZEN_QUERIES = new ConcurrentHashMap<>(); - private static final Map FROZEN_QUERIES = new HashMap<>(); + private static Map UNFROZEN_QUERIES = new ConcurrentHashMap<>(); + private static Map FROZEN_QUERIES = ImmutableMap.of(); static { setDefaultQueryValues(); @@ -33,10 +32,10 @@ public static void registerVariable(String name, Variable variable) { } static Variable getQueryFor(String name) { - return FROZEN_QUERIES.getOrDefault(applyPrefixAliases(name, "query.", "q."), new Variable(name, 0)); + return FROZEN_QUERIES.getOrDefault(applyQueryAliases(name), new Variable(name, 0)); } - private static void registerQueryVariable(String name, ToDoubleFunction value) { + private static void registerQueryVariable(String name, ToFloatFunction value) { checkFrozen(); checkUnregistered(name); UNFROZEN_QUERIES.put(name, new Variable(name, value)); @@ -52,14 +51,12 @@ private static void checkFrozen() { if (!FROZEN_QUERIES.isEmpty()) throw new UnsupportedOperationException("Had already frozen!"); } - /** - * Parse a given string formatted with a prefix, swapping out any potential aliases for the defined proper name - * - * @param text The base text to parse - * @param properName The "correct" prefix to apply - * @param aliases The available prefixes to check and replace - * @return The unaliased string, or the original string if no aliases match - */ + /// Parse a given string formatted with a prefix, swapping out any potential aliases for the defined proper name + /// + /// @param text The base text to parse + /// @param properName The "correct" prefix to apply + /// @param aliases The available prefixes to check and replace + /// @return The unaliased string, or the original string if no aliases match public static String applyPrefixAliases(String text, String properName, String... aliases) { for (String alias : aliases) { if (text.startsWith(alias)) @@ -69,26 +66,33 @@ public static String applyPrefixAliases(String text, String properName, String.. return text; } + public static String applyQueryAliases(String text) { + if (text.startsWith("q.")) { + return "query" + text.substring(1); + } + return text; + } + private static void setDefaultQueryValues() { - registerQueryVariable("query.cardinal_player_facing", p -> Minecraft.getInstance().player == null ? 0.0 : Minecraft.getInstance().player.getDirection().ordinal()); - registerQueryVariable("query.day", p -> p.getLevel().getGameTime() / 24000d); - registerQueryVariable("query.has_cape", p -> Minecraft.getInstance().player == null ? 0.0 : Minecraft.getInstance().player.getSkin().capeTexture() == null ? 0 : 1); + registerQueryVariable("query.cardinal_player_facing", p -> Minecraft.getInstance().player == null ? 0 : Minecraft.getInstance().player.getDirection().ordinal()); + registerQueryVariable("query.day", p -> p.getLevel().getGameTime() / 24000F); + registerQueryVariable("query.has_cape", p -> Minecraft.getInstance().player == null ? 0 : Minecraft.getInstance().player.getSkin().capeTexture() == null ? 0 : 1); registerQueryVariable("query.is_first_person", p -> Minecraft.getInstance().options.getCameraType() == CameraType.FIRST_PERSON ? 1 : 0); registerQueryVariable("query.moon_brightness", p -> p.getLevel().getMoonBrightness()); registerQueryVariable("query.moon_phase", p -> p.getLevel().getMoonPhase()); - registerQueryVariable("query.player_level", p -> Minecraft.getInstance().player == null ? 0.0 : Minecraft.getInstance().player.experienceLevel); + registerQueryVariable("query.player_level", p -> Minecraft.getInstance().player == null ? 0 : Minecraft.getInstance().player.experienceLevel); 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 -> 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()); - registerQueryVariable("query.attached_xo", p -> p.getAttachedEntity() == null ? 0.0 : p.getAttachedEntity().xo); - registerQueryVariable("query.attached_yo", p -> p.getAttachedEntity() == null ? 0.0 : p.getAttachedEntity().yo); - registerQueryVariable("query.attached_zo", p -> p.getAttachedEntity() == null ? 0.0 : p.getAttachedEntity().zo); + registerQueryVariable("query.total_emitter_count", p -> MolangParticleEngine.INSTANCE.totalEmitterCount()); + registerQueryVariable("query.total_particle_count", p -> MolangParticleEngine.INSTANCE.totalParticleCount()); + registerQueryVariable("query.attached_x", p -> p.getAttachedEntity() == null ? 0 : (float) p.getAttachedEntity().getX()); + registerQueryVariable("query.attached_y", p -> p.getAttachedEntity() == null ? 0 : (float) p.getAttachedEntity().getY()); + registerQueryVariable("query.attached_z", p -> p.getAttachedEntity() == null ? 0 : (float) p.getAttachedEntity().getZ()); + registerQueryVariable("query.attached_xo", p -> p.getAttachedEntity() == null ? 0 : (float) p.getAttachedEntity().xo); + registerQueryVariable("query.attached_yo", p -> p.getAttachedEntity() == null ? 0 : (float) p.getAttachedEntity().yo); + registerQueryVariable("query.attached_zo", p -> p.getAttachedEntity() == null ? 0 : (float) p.getAttachedEntity().zo); ModLoader.postEvent(new RegisterMolangQueriesEvent(MolangQueries::registerQueryVariable)); - FROZEN_QUERIES.putAll(UNFROZEN_QUERIES); - UNFROZEN_QUERIES.clear(); + FROZEN_QUERIES = ImmutableMap.copyOf(UNFROZEN_QUERIES); + UNFROZEN_QUERIES = null; } } 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 5e4feae..cbbf10a 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,6 +4,7 @@ import it.unimi.dsi.fastutil.chars.CharSet; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import net.minecraft.Util; +import net.minecraft.util.Mth; import java.util.Arrays; import java.util.Map; @@ -11,41 +12,37 @@ import java.util.Optional; import java.util.function.BiFunction; -/** - * Mathematical operator representing a single operation - *

- * Each record should represent a distinct mathematical function for computational purposes, with each function being deterministic and immutable. - */ +/// Mathematical operator representing a single operation +/// +/// Each record should represent a distinct mathematical function for computational purposes, with each function being deterministic and immutable. public record Operator(String symbol, int precedence, Operation operation) implements Comparable { private static final Map OPERATORS = new Object2ObjectOpenHashMap<>(14); private static final CharSet OPERATOR_SYMBOLS = Util.make(new CharOpenHashSet(15), set -> set.addAll(Arrays.asList('?', ':', ','))); private static int LONGEST_OPERATOR; - public static final Operator ADD = register("+", 1, Double::sum); + public static final Operator ADD = register("+", 1, Float::sum); public static final Operator SUB = register("-", 1, (a, b) -> a - b); public static final Operator MUL = register("*", 2, (a, b) -> a * b); public static final Operator DIV = register("/", 2, (a, b) -> b == 0 ? a : a / b); public static final Operator MOD = register("%", 2, (a, b) -> b == 0 ? a : a % b); - public static final Operator POW = register("^", 3, Math::pow); + public static final Operator POW = register("^", 3, (a, b) -> (float) Math.pow(a, b)); public static final Operator AND = register("&&", 5, (a, b) -> a != 0 && b != 0 ? 1 : 0); public static final Operator OR = register("||", 5, (a, b) -> a != 0 || b != 0 ? 1 : 0); public static final Operator LT = register("<", 5, (a, b) -> a < b ? 1 : 0); public static final Operator LTE = register("<=", 5, (a, b) -> a <= b ? 1 : 0); public static final Operator GT = register(">", 5, (a, b) -> a > b ? 1 : 0); public static final Operator GTE = register(">=", 5, (a, b) -> a >= b ? 1 : 0); - public static final Operator EQUAL = register("==", 5, (a, b) -> Math.abs(a - b) < 0.00001 ? 1 : 0); - public static final Operator NOT_EQUAL = register("!=", 5, (a, b) -> Math.abs(a - b) >= 0.00001 ? 1 : 0); + public static final Operator EQUAL = register("==", 5, (a, b) -> Math.abs(a - b) < Mth.EPSILON ? 1 : 0); + public static final Operator NOT_EQUAL = register("!=", 5, (a, b) -> Math.abs(a - b) >= Mth.EPSILON ? 1 : 0); //public static final Operator NULL_COALESCING = register("??", 6, (a, b) -> a == 0 ? b : a); public static final Operator ASSIGN_VARIABLE = register("=", Integer.MAX_VALUE, (a, b) -> 0); - /** - * Instantiate and register a new mathematical operator. - * Note that it should be a functionally distinct operator from other existing operators. - * - * @param symbol The expressed mathematical symbol for this operator - * @param precedence The precedence value for this operator, in relation to other operators - * @param operation The computational function for this operator - */ + /// Instantiate and register a new mathematical operator. + /// Note that it should be a functionally distinct operator from other existing operators. + /// + /// @param symbol The expressed mathematical symbol for this operator + /// @param precedence The precedence value for this operator, in relation to other operators + /// @param operation The computational function for this operator public static Operator register(String symbol, int precedence, Operation operation) { final Operator operator = new Operator(symbol, precedence, operation); @@ -61,43 +58,34 @@ public static Operator register(String symbol, int precedence, Operation operati return operator; } - /** - * @param symbol The mathematical/expression symbol representing an Operator - * @return Whether an Operator has been registered for the given symbol - */ + /// @param symbol The mathematical/expression symbol representing an Operator + /// @return Whether an Operator has been registered for the given symbol public static boolean isOperator(String symbol) { return OPERATORS.containsKey(symbol); } - /** - * @param symbol The mathematical/expression symbol representing an Operator - * @return An {@link Optional} potentially containing the Operator for the given symbol - */ + /// @param symbol The mathematical/expression symbol representing an Operator + /// @return An [Optional] potentially containing the Operator for the given symbol public static Optional getOperatorFor(String symbol) { return Optional.ofNullable(OPERATORS.get(symbol)); } - /** - * Returns the character length of the longest currently registered operator - */ + /// Returns the character length of the longest currently registered operator public static int maxOperatorLength() { return LONGEST_OPERATOR; } - /** - * Returns whether the given character is a part of any registered operators or otherwise acts like an operative separator for expressions - */ + /// Returns whether the given character is a part of any registered operators or otherwise acts like an operative separator for expressions public static boolean isOperativeSymbol(char symbol) { return OPERATOR_SYMBOLS.contains(symbol); } - /** - * Compute the resultant value of the two input values for this operation - * @param argA The first input argument - * @param argB The second input argument - * @return The computed value of the two inputs - */ - public double compute(double argA, double argB) { + /// Compute the resultant value of the two input values for this operation + /// + /// @param argA The first input argument + /// @param argB The second input argument + /// @return The computed value of the two inputs + public float compute(float argA, float argB) { return this.operation.compute(argA, argB); } @@ -106,9 +94,7 @@ public int compareTo(Operator operator) { return Integer.compare(this.precedence, operator.precedence); } - /** - * Determine whether this operator takes mathematical precedence over the other operator - */ + /// Determine whether this operator takes mathematical precedence over the other operator public boolean takesPrecedenceOver(Operator operator) { return compareTo(operator) > 0; } @@ -118,18 +104,14 @@ public int hashCode() { return Objects.hash(this.symbol); } - /** - * Functional interface representing the computational work of an {@link Operator} - */ + /// Functional interface representing the computational work of an [Operator] @FunctionalInterface public interface Operation { - /** - * Unboxed equivalent of {@link BiFunction} for computing the mathematical result of two input arguments - * - * @param argA The first input argument - * @param argB The second input argument - * @return The computed value of the two inputs - */ - double compute(double argA, double argB); + /// Unboxed equivalent of [BiFunction] for computing the mathematical result of two input arguments + /// + /// @param argA The first input argument + /// @param argB The second input argument + /// @return The computed value of the two inputs + float compute(float argA, float argB); } } diff --git a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/MathFunction.java b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/MathFunction.java index c467383..e1f8d39 100644 --- a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/MathFunction.java +++ b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/MathFunction.java @@ -5,16 +5,14 @@ import java.util.StringJoiner; -/** - * Computational function wrapping a {@link MathValue} - *

- * Subclasses of this represent mathematical functions to be performed on a pre-defined number of input variables. - *
- * Functions should be deterministic - identical input values should result in identical output values, and values should adhere to the {@link MathValue#isMutable() Mutability} contract of {@link MathValue} for determining result-caching. - */ +/// Computational function wrapping a [MathValue] +/// +/// Subclasses of this represent mathematical functions to be performed on a pre-defined number of input variables. +/// +/// Functions should be deterministic - identical input values should result in identical output values, and values should adhere to the [Mutability][#isMutable()] contract of [MathValue] for determining result-caching. public abstract class MathFunction implements MathValue { - private final boolean isMutable; - private double cachedValue = Double.MIN_VALUE; + private boolean isMutable; + private float cachedValue = Float.MIN_VALUE; protected MathFunction(MathValue... values) { validate(values); @@ -22,33 +20,27 @@ protected MathFunction(MathValue... values) { this.isMutable = isMutable(values); } - /** - * Return the expression name/symbol for this function. - * This is the value that would be seen in a mathematical expression string - */ + /// Return the expression name/symbol for this function. + /// This is the value that would be seen in a mathematical expression string public abstract String getName(); @Override - public final double get(MolangInstance instance) { + public final float get(MolangInstance instance) { if (this.isMutable) return compute(instance); - if (this.cachedValue == Double.MIN_VALUE) + if (this.cachedValue == Float.MIN_VALUE) this.cachedValue = compute(instance); return this.cachedValue; } - /** - * Compute the result of this function from its stored arguments - */ - public abstract double compute(MolangInstance instance); + /// Compute the result of this function from its stored arguments + public abstract float compute(MolangInstance instance); - /** - * @return Whether this function should be considered mutable; the value could change - *
- * This would normally be determined by whether any of the stored args are mutable - */ + /// @return Whether this function should be considered mutable; the value could change + /// + /// This would normally be determined by whether any of the stored args are mutable public boolean isMutable(MathValue... values) { for (MathValue value : values) { if (value.isMutable()) @@ -58,19 +50,18 @@ public boolean isMutable(MathValue... values) { return false; } - /** - * @return The minimum number of args required for this function to be computable - */ + @Override + public void markImmutable() { + this.isMutable = false; + } + + /// @return The minimum number of args required for this function to be computable public abstract int getMinArgs(); - /** - * @return The arguments (in order) stored in this function - */ + /// @return The arguments (in order) stored in this function public abstract MathValue[] getArgs(); - /** - * Validate this function's arguments against its minimum requirements, throwing an exception for invalid argument states - */ + /// Validate this function's arguments against its minimum requirements, throwing an exception for invalid argument states public void validate(MathValue... inputs) throws IllegalArgumentException { final int minArgs = getMinArgs(); @@ -95,15 +86,11 @@ public String toString() { return getName() + joiner; } - /** - * Factory interface for {@link MathFunction}. - * Functionally equivalent to

{@code Function}
but with a more concise user-facing handle - */ + /// Factory interface for [MathFunction]. + /// Functionally equivalent to
`Function`
but with a more concise user-facing handle @FunctionalInterface public interface Factory { - /** - * Instantiate a new {@link MathFunction} for the given input values - */ + /// Instantiate a new [MathFunction] for the given input values T create(MathValue... values); } } diff --git a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/generic/ACosFunction.java b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/generic/ACosFunction.java index 147ab5a..bc207de 100644 --- a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/generic/ACosFunction.java +++ b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/generic/ACosFunction.java @@ -5,14 +5,11 @@ import org.mesdag.particlestorm.data.molang.compiler.MathValue; import org.mesdag.particlestorm.data.molang.compiler.function.MathFunction; -/** - * {@link MathFunction} value supplier - * - *

- * Contract: - *
- * Returns the arc-cosine of the input value angle, with the input angle converted to radians - */ +/// [MathFunction] value supplier +/// +/// **Contract:** +/// +/// Returns the arc-cosine of the input value angle, with the input angle converted to radians public final class ACosFunction extends MathFunction { private final MathValue value; @@ -28,8 +25,8 @@ public String getName() { } @Override - public double compute(MolangInstance instance) { - return Math.acos(this.value.get(instance) * (double) Mth.DEG_TO_RAD); + public float compute(MolangInstance instance) { + return (float) Math.acos(value.get(instance) * Mth.DEG_TO_RAD); } @Override @@ -39,6 +36,6 @@ public int getMinArgs() { @Override public MathValue[] getArgs() { - return new MathValue[]{this.value}; + return new MathValue[]{value}; } } diff --git a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/generic/ASinFunction.java b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/generic/ASinFunction.java index 99ccab8..a2f4111 100644 --- a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/generic/ASinFunction.java +++ b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/generic/ASinFunction.java @@ -5,14 +5,11 @@ import org.mesdag.particlestorm.data.molang.compiler.MathValue; import org.mesdag.particlestorm.data.molang.compiler.function.MathFunction; -/** - * {@link MathFunction} value supplier - * - *

- * Contract: - *
- * Returns the arc-sine of the input value angle, with the input angle converted to radians - */ +/// [MathFunction] value supplier +/// +/// **Contract:** +/// +/// Returns the arc-sine of the input value angle, with the input angle converted to radians public final class ASinFunction extends MathFunction { private final MathValue value; @@ -28,8 +25,8 @@ public String getName() { } @Override - public double compute(MolangInstance instance) { - return Math.asin(this.value.get(instance) * Mth.DEG_TO_RAD); + public float compute(MolangInstance instance) { + return (float) Math.asin(value.get(instance) * Mth.DEG_TO_RAD); } @Override @@ -39,6 +36,6 @@ public int getMinArgs() { @Override public MathValue[] getArgs() { - return new MathValue[] {this.value}; + return new MathValue[] {value}; } } diff --git a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/generic/ATan2Function.java b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/generic/ATan2Function.java index 82cc996..ab18a28 100644 --- a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/generic/ATan2Function.java +++ b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/generic/ATan2Function.java @@ -5,14 +5,11 @@ import org.mesdag.particlestorm.data.molang.compiler.MathValue; import org.mesdag.particlestorm.data.molang.compiler.function.MathFunction; -/** - * {@link MathFunction} value supplier - * - *

- * Contract: - *
- * Returns the arc-tangent theta of the input rectangular coordinate values (y,x), with the output converted to degrees - */ +/// [MathFunction] value supplier +/// +/// **Contract:** +/// +/// Returns the arc-tangent theta of the input rectangular coordinate values (y,x), with the output converted to degrees public final class ATan2Function extends MathFunction { private final MathValue y; private final MathValue x; @@ -30,8 +27,8 @@ public String getName() { } @Override - public double compute(MolangInstance instance) { - return Math.atan2(this.y.get(instance), this.x.get(instance)) * Mth.RAD_TO_DEG; + public float compute(MolangInstance instance) { + return (float) Mth.atan2(y.get(instance), x.get(instance)) * Mth.RAD_TO_DEG; } @Override @@ -41,6 +38,6 @@ public int getMinArgs() { @Override public MathValue[] getArgs() { - return new MathValue[] {this.y, this.x}; + return new MathValue[] {y, x}; } } diff --git a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/generic/ATanFunction.java b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/generic/ATanFunction.java index 691f19d..a9f9089 100644 --- a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/generic/ATanFunction.java +++ b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/generic/ATanFunction.java @@ -5,14 +5,11 @@ import org.mesdag.particlestorm.data.molang.compiler.MathValue; import org.mesdag.particlestorm.data.molang.compiler.function.MathFunction; -/** - * {@link MathFunction} value supplier - * - *

- * Contract: - *
- * Returns the arc-tangent of the input value angle, with the input angle converted to radians - */ +/// [MathFunction] value supplier +/// +/// **Contract:** +/// +/// Returns the arc-tangent of the input value angle, with the input angle converted to radians public final class ATanFunction extends MathFunction { private final MathValue value; @@ -28,8 +25,8 @@ public String getName() { } @Override - public double compute(MolangInstance instance) { - return Math.atan(this.value.get(instance) * Mth.DEG_TO_RAD); + public float compute(MolangInstance instance) { + return (float) Math.atan(value.get(instance) * Mth.DEG_TO_RAD); } @Override diff --git a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/generic/AbsFunction.java b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/generic/AbsFunction.java index 8563329..b98fd9b 100644 --- a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/generic/AbsFunction.java +++ b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/generic/AbsFunction.java @@ -4,14 +4,11 @@ import org.mesdag.particlestorm.data.molang.compiler.MathValue; import org.mesdag.particlestorm.data.molang.compiler.function.MathFunction; -/** - * {@link MathFunction} value supplier - * - *

- * Contract: - *
- * Returns the absolute (non-negative) equivalent of the input value - */ +/// [MathFunction] value supplier +/// +/// **Contract:** +/// +/// Returns the absolute (non-negative) equivalent of the input value public final class AbsFunction extends MathFunction { private final MathValue value; @@ -27,7 +24,7 @@ public String getName() { } @Override - public double compute(MolangInstance instance) { + public float compute(MolangInstance instance) { return Math.abs(this.value.get(instance)); } diff --git a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/generic/CosFunction.java b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/generic/CosFunction.java index 57e40a0..82f33f1 100644 --- a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/generic/CosFunction.java +++ b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/generic/CosFunction.java @@ -5,14 +5,11 @@ import org.mesdag.particlestorm.data.molang.compiler.MathValue; import org.mesdag.particlestorm.data.molang.compiler.function.MathFunction; -/** - * {@link MathFunction} value supplier - * - *

- * Contract: - *
- * Returns the cosine of the input value angle, with the input angle converted to radians - */ +/// [MathFunction] value supplier +/// +/// **Contract:** +/// +/// Returns the cosine of the input value angle, with the input angle converted to radians public final class CosFunction extends MathFunction { private final MathValue value; @@ -28,8 +25,8 @@ public String getName() { } @Override - public double compute(MolangInstance instance) { - return Mth.cos((float)this.value.get(instance) * Mth.DEG_TO_RAD); + public float compute(MolangInstance instance) { + return Mth.cos(value.get(instance) * Mth.DEG_TO_RAD); } @Override @@ -39,6 +36,6 @@ public int getMinArgs() { @Override public MathValue[] getArgs() { - return new MathValue[] {this.value}; + return new MathValue[] {value}; } } diff --git a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/generic/ExpFunction.java b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/generic/ExpFunction.java index f080777..e069d04 100644 --- a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/generic/ExpFunction.java +++ b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/generic/ExpFunction.java @@ -4,14 +4,11 @@ import org.mesdag.particlestorm.data.molang.compiler.MathValue; import org.mesdag.particlestorm.data.molang.compiler.function.MathFunction; -/** - * {@link MathFunction} value supplier - * - *

- * Contract: - *
- * Returns euler's number raised to the power of the input value - */ +/// [MathFunction] value supplier +/// +/// **Contract:** +/// +/// Returns euler's number raised to the power of the input value public final class ExpFunction extends MathFunction { private final MathValue value; @@ -27,8 +24,8 @@ public String getName() { } @Override - public double compute(MolangInstance instance) { - return Math.exp(this.value.get(instance)); + public float compute(MolangInstance instance) { + return (float) Math.exp(value.get(instance)); } @Override @@ -38,6 +35,6 @@ public int getMinArgs() { @Override public MathValue[] getArgs() { - return new MathValue[] {this.value}; + return new MathValue[] {value}; } } diff --git a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/generic/LogFunction.java b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/generic/LogFunction.java index 5032a84..00ffc7c 100644 --- a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/generic/LogFunction.java +++ b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/generic/LogFunction.java @@ -4,14 +4,11 @@ import org.mesdag.particlestorm.data.molang.compiler.MathValue; import org.mesdag.particlestorm.data.molang.compiler.function.MathFunction; -/** - * {@link MathFunction} value supplier - * - *

- * Contract: - *
- * Returns the log value (euler base) of the input value - */ +/// [MathFunction] value supplier +/// +/// **Contract:** +/// +/// Returns the log value (euler base) of the input value public final class LogFunction extends MathFunction { private final MathValue value; @@ -27,8 +24,8 @@ public String getName() { } @Override - public double compute(MolangInstance instance) { - return Math.log(this.value.get(instance)); + public float compute(MolangInstance instance) { + return (float) Math.log(value.get(instance)); } @Override @@ -38,6 +35,6 @@ public int getMinArgs() { @Override public MathValue[] getArgs() { - return new MathValue[] {this.value}; + return new MathValue[] {value}; } } diff --git a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/generic/ModFunction.java b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/generic/ModFunction.java index 86d2e71..a38829b 100644 --- a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/generic/ModFunction.java +++ b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/generic/ModFunction.java @@ -4,14 +4,11 @@ import org.mesdag.particlestorm.data.molang.compiler.MathValue; import org.mesdag.particlestorm.data.molang.compiler.function.MathFunction; -/** - * {@link MathFunction} value supplier - * - *

- * Contract: - *
- * Returns the remainder value of the input value when modulo'd by the modulus value - */ +/// [MathFunction] value supplier +/// +/// **Contract:** +/// +/// Returns the remainder value of the input value when modulo'd by the modulus value public final class ModFunction extends MathFunction { private final MathValue value; private final MathValue modulus; @@ -29,8 +26,8 @@ public String getName() { } @Override - public double compute(MolangInstance instance) { - return this.value.get(instance) % this.modulus.get(instance); + public float compute(MolangInstance instance) { + return value.get(instance) % modulus.get(instance); } @Override @@ -40,6 +37,6 @@ public int getMinArgs() { @Override public MathValue[] getArgs() { - return new MathValue[] {this.value, this.modulus}; + return new MathValue[] {value, modulus}; } } diff --git a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/generic/PowFunction.java b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/generic/PowFunction.java index 3e4ae6d..19e56b3 100644 --- a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/generic/PowFunction.java +++ b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/generic/PowFunction.java @@ -4,14 +4,11 @@ import org.mesdag.particlestorm.data.molang.compiler.MathValue; import org.mesdag.particlestorm.data.molang.compiler.function.MathFunction; -/** - * {@link MathFunction} value supplier - * - *

- * Contract: - *
- * Returns the input value raised to the power of the second input value - */ +/// [MathFunction] value supplier +/// +/// **Contract:** +/// +/// Returns the input value raised to the power of the second input value public final class PowFunction extends MathFunction { private final MathValue value; private final MathValue power; @@ -29,8 +26,8 @@ public String getName() { } @Override - public double compute(MolangInstance instance) { - return Math.pow(this.value.get(instance), this.power.get(instance)); + public float compute(MolangInstance instance) { + return (float) Math.pow(value.get(instance), power.get(instance)); } @Override @@ -40,6 +37,6 @@ public int getMinArgs() { @Override public MathValue[] getArgs() { - return new MathValue[] {this.value, this.power}; + return new MathValue[] {value, power}; } } diff --git a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/generic/SinFunction.java b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/generic/SinFunction.java index 4acab8b..0fd1127 100644 --- a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/generic/SinFunction.java +++ b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/generic/SinFunction.java @@ -5,14 +5,11 @@ import org.mesdag.particlestorm.data.molang.compiler.MathValue; import org.mesdag.particlestorm.data.molang.compiler.function.MathFunction; -/** - * {@link MathFunction} value supplier - * - *

- * Contract: - *
- * Returns the sine of the input value angle, with the input angle converted to radians - */ +/// [MathFunction] value supplier +/// +/// **Contract:** +/// +/// Returns the sine of the input value angle, with the input angle converted to radians public final class SinFunction extends MathFunction { private final MathValue value; @@ -28,8 +25,8 @@ public String getName() { } @Override - public double compute(MolangInstance instance) { - return Math.sin(this.value.get(instance) * Mth.DEG_TO_RAD); + public float compute(MolangInstance instance) { + return Mth.sin(value.get(instance) * Mth.DEG_TO_RAD); } @Override diff --git a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/generic/SqrtFunction.java b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/generic/SqrtFunction.java index 31fb543..626ba4e 100644 --- a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/generic/SqrtFunction.java +++ b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/generic/SqrtFunction.java @@ -4,14 +4,11 @@ import org.mesdag.particlestorm.data.molang.compiler.MathValue; import org.mesdag.particlestorm.data.molang.compiler.function.MathFunction; -/** - * {@link MathFunction} value supplier - * - *

- * Contract: - *
- * Returns the square root of the input value - */ +/// [MathFunction] value supplier +/// +/// **Contract:** +/// +/// Returns the square root of the input value public final class SqrtFunction extends MathFunction { private final MathValue value; @@ -27,8 +24,8 @@ public String getName() { } @Override - public double compute(MolangInstance instance) { - return Math.sqrt(this.value.get(instance)); + public float compute(MolangInstance instance) { + return (float) Math.sqrt(value.get(instance)); } @Override @@ -38,6 +35,6 @@ public int getMinArgs() { @Override public MathValue[] getArgs() { - return new MathValue[] {this.value}; + return new MathValue[] {value}; } } diff --git a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/limit/ClampFunction.java b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/limit/ClampFunction.java index 302eccd..257794f 100644 --- a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/limit/ClampFunction.java +++ b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/limit/ClampFunction.java @@ -5,14 +5,11 @@ import org.mesdag.particlestorm.data.molang.compiler.MathValue; import org.mesdag.particlestorm.data.molang.compiler.function.MathFunction; -/** - * {@link MathFunction} value supplier - * - *

- * Contract: - *
- * Returns the first input value if is larger than the second input value and less than the third input value; or else returns the nearest of the second two input values - */ +/// [MathFunction] value supplier +/// +/// **Contract:** +/// +/// Returns the first input value if is larger than the second input value and less than the third input value; or else returns the nearest of the second two input values public final class ClampFunction extends MathFunction { private final MathValue value; private final MathValue min; @@ -32,8 +29,8 @@ public String getName() { } @Override - public double compute(MolangInstance instance) { - return Mth.clamp(this.value.get(instance), this.min.get(instance), this.max.get(instance)); + public float compute(MolangInstance instance) { + return Mth.clamp(value.get(instance), min.get(instance), max.get(instance)); } @Override @@ -43,6 +40,6 @@ public int getMinArgs() { @Override public MathValue[] getArgs() { - return new MathValue[] {this.value, this.min, this.max}; + return new MathValue[] {value, min, max}; } } diff --git a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/limit/MaxFunction.java b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/limit/MaxFunction.java index 7707b1c..f3ad283 100644 --- a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/limit/MaxFunction.java +++ b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/limit/MaxFunction.java @@ -4,14 +4,11 @@ import org.mesdag.particlestorm.data.molang.compiler.MathValue; import org.mesdag.particlestorm.data.molang.compiler.function.MathFunction; -/** - * {@link MathFunction} value supplier - * - *

- * Contract: - *
- * Returns the greater of the two input values - */ +/// [MathFunction] value supplier +/// +/// **Contract:** +/// +/// Returns the greater of the two input values public final class MaxFunction extends MathFunction { private final MathValue valueA; private final MathValue valueB; @@ -29,8 +26,8 @@ public String getName() { } @Override - public double compute(MolangInstance instance) { - return Math.max(this.valueA.get(instance), this.valueB.get(instance)); + public float compute(MolangInstance instance) { + return Math.max(valueA.get(instance), valueB.get(instance)); } @Override @@ -40,6 +37,6 @@ public int getMinArgs() { @Override public MathValue[] getArgs() { - return new MathValue[] {this.valueA, this.valueB}; + return new MathValue[] {valueA, valueB}; } } diff --git a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/limit/MinFunction.java b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/limit/MinFunction.java index ac675bc..110d8d8 100644 --- a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/limit/MinFunction.java +++ b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/limit/MinFunction.java @@ -4,14 +4,11 @@ import org.mesdag.particlestorm.data.molang.compiler.MathValue; import org.mesdag.particlestorm.data.molang.compiler.function.MathFunction; -/** - * {@link MathFunction} value supplier - * - *

- * Contract: - *
- * Returns the lesser of the two input values - */ +/// [MathFunction] value supplier +/// +/// **Contract:** +/// +/// Returns the lesser of the two input values public final class MinFunction extends MathFunction { private final MathValue valueA; private final MathValue valueB; @@ -29,8 +26,8 @@ public String getName() { } @Override - public double compute(MolangInstance instance) { - return Math.min(this.valueA.get(instance), this.valueB.get(instance)); + public float compute(MolangInstance instance) { + return Math.min(valueA.get(instance), valueB.get(instance)); } @Override @@ -40,6 +37,6 @@ public int getMinArgs() { @Override public MathValue[] getArgs() { - return new MathValue[] {this.valueA, this.valueB}; + return new MathValue[] {valueA, valueB}; } } diff --git a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/misc/IsBlockFunction.java b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/misc/IsBlockFunction.java new file mode 100644 index 0000000..dd594a6 --- /dev/null +++ b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/misc/IsBlockFunction.java @@ -0,0 +1,53 @@ +package org.mesdag.particlestorm.data.molang.compiler.function.misc; + +import com.mojang.datafixers.util.Either; +import net.minecraft.core.BlockPos; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.BlockTags; +import net.minecraft.tags.TagKey; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; +import org.mesdag.particlestorm.api.MolangInstance; +import org.mesdag.particlestorm.data.molang.compiler.MathValue; +import org.mesdag.particlestorm.data.molang.compiler.function.MathFunction; +import org.mesdag.particlestorm.data.molang.compiler.value.StringValue; + +public final class IsBlockFunction extends MathFunction { + private final StringValue stringValue; + private final Either> either; + + public IsBlockFunction(MathValue... values) { + super(values); + if (values[0] instanceof StringValue stringValue) { + this.stringValue = stringValue; + String value = stringValue.value(); + this.either = value.startsWith("#") + ? Either.right(BlockTags.create(ResourceLocation.parse(value.substring(1)))) + : Either.left(BuiltInRegistries.BLOCK.get(ResourceLocation.parse(value))); + } else { + throw new IllegalArgumentException(values[0] + " is not a string value"); + } + } + + @Override + public String getName() { + return "query.is_block"; + } + + @Override + public float compute(MolangInstance instance) { + BlockState state = instance.getLevel().getBlockState(BlockPos.containing(instance.getPosition())); + return either.map(state::is, state::is) ? 1 : 0; + } + + @Override + public int getMinArgs() { + return 1; + } + + @Override + public MathValue[] getArgs() { + return new MathValue[]{stringValue}; + } +} diff --git a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/misc/PiFunction.java b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/misc/PiFunction.java index 5a61352..158ee42 100644 --- a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/misc/PiFunction.java +++ b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/misc/PiFunction.java @@ -1,18 +1,16 @@ package org.mesdag.particlestorm.data.molang.compiler.function.misc; +import net.minecraft.util.Mth; import org.mesdag.particlestorm.api.MolangInstance; import org.mesdag.particlestorm.data.molang.compiler.MathValue; import org.mesdag.particlestorm.data.molang.compiler.function.MathFunction; import org.mesdag.particlestorm.data.molang.compiler.value.Constant; -/** - * {@link MathFunction} value supplier - * - *

- * Contract: - *
- * Returns PI - */ +/// [MathFunction] value supplier +/// +/// **Contract:** +/// +/// Returns PI public final class PiFunction extends MathFunction { public PiFunction(MathValue... values) { super(values); @@ -24,8 +22,8 @@ public String getName() { } @Override - public double compute(MolangInstance instance) { - return Math.PI; + public float compute(MolangInstance instance) { + return Mth.PI; } @Override @@ -40,6 +38,6 @@ public int getMinArgs() { @Override public MathValue[] getArgs() { - return new MathValue[] {new Constant(Math.PI)}; + return new MathValue[] {new Constant(Mth.PI)}; } } diff --git a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/misc/ToDegFunction.java b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/misc/ToDegFunction.java index c904c4d..a767301 100644 --- a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/misc/ToDegFunction.java +++ b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/misc/ToDegFunction.java @@ -1,17 +1,15 @@ package org.mesdag.particlestorm.data.molang.compiler.function.misc; +import net.minecraft.util.Mth; import org.mesdag.particlestorm.api.MolangInstance; import org.mesdag.particlestorm.data.molang.compiler.MathValue; import org.mesdag.particlestorm.data.molang.compiler.function.MathFunction; -/** - * {@link MathFunction} value supplier - * - *

- * Contract: - *
- * Converts the input value to degrees - */ +/// [MathFunction] value supplier +/// +/// **Contract:** +/// +/// Converts the input value to degrees public final class ToDegFunction extends MathFunction { private final MathValue value; @@ -27,8 +25,8 @@ public String getName() { } @Override - public double compute(MolangInstance instance) { - return Math.toDegrees(this.value.get(instance)); + public float compute(MolangInstance instance) { + return value.get(instance) * Mth.RAD_TO_DEG; } @Override @@ -38,6 +36,6 @@ public int getMinArgs() { @Override public MathValue[] getArgs() { - return new MathValue[] {this.value}; + return new MathValue[] {value}; } } diff --git a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/misc/ToRadFunction.java b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/misc/ToRadFunction.java index 74cbb1c..4f25426 100644 --- a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/misc/ToRadFunction.java +++ b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/misc/ToRadFunction.java @@ -1,17 +1,15 @@ package org.mesdag.particlestorm.data.molang.compiler.function.misc; +import net.minecraft.util.Mth; import org.mesdag.particlestorm.api.MolangInstance; import org.mesdag.particlestorm.data.molang.compiler.MathValue; import org.mesdag.particlestorm.data.molang.compiler.function.MathFunction; -/** - * {@link MathFunction} value supplier - * - *

- * Contract: - *
- * Converts the input value to radians - */ +/// [MathFunction] value supplier +/// +/// **Contract:** +/// +/// Converts the input value to radians public final class ToRadFunction extends MathFunction { private final MathValue value; @@ -27,8 +25,8 @@ public String getName() { } @Override - public double compute(MolangInstance instance) { - return Math.toRadians(this.value.get(instance)); + public float compute(MolangInstance instance) { + return value.get(instance) * Mth.DEG_TO_RAD; } @Override @@ -38,6 +36,6 @@ public int getMinArgs() { @Override public MathValue[] getArgs() { - return new MathValue[] {this.value}; + return new MathValue[] {value}; } } diff --git a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/random/DieRollFunction.java b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/random/DieRollFunction.java index c208b7c..90eed32 100644 --- a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/random/DieRollFunction.java +++ b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/random/DieRollFunction.java @@ -8,18 +8,15 @@ import java.util.Random; import java.util.concurrent.ThreadLocalRandom; -/** - * {@link MathFunction} value supplier - * - *

- * Contract: - *
- * Returns a random value based on the input values: - *

    - *
  • Three inputs generates the sum of n (first input) random values between the second (inclusive) and third input (exclusive)
  • - *
  • Four inputs generates the sum of n (first input) random values between the second (inclusive) and third input (exclusive), seeded by the fourth input
  • - *
- */ +/// [MathFunction] value supplier +/// +/// **Contract:** +/// +/// Returns a random value based on the input values: +/// +/// - Three inputs generates the sum of _n_ (first input) random values between the second (inclusive) and third input (exclusive) +/// - Four inputs generates the sum of _n_ (first input) random values between the second (inclusive) and third input (exclusive), seeded by the fourth input +/// public final class DieRollFunction extends MathFunction { private final MathValue rolls; private final MathValue min; @@ -45,23 +42,22 @@ public String getName() { } @Override - public double compute(MolangInstance instance) { - final int rolls = (int)(Math.floor(this.rolls.get(instance))); - final double min = this.min.get(instance); - final double max = this.max.get(instance); - double sum = 0; + public float compute(MolangInstance instance) { + int rolls = (int) (Math.floor(this.rolls.get(instance))); + float min = this.min.get(instance); + float max = this.max.get(instance); + float sum = 0; Random random; if (this.random != null) { random = this.random; - random.setSeed((long)this.seed.get(instance)); - } - else { + random.setSeed((long) this.seed.get(instance)); + } else { random = ThreadLocalRandom.current(); } for (int i = 0; i < rolls; i++) { - sum += min + random.nextDouble() * (max - min); + sum += min + random.nextFloat() * (max - min); } return sum; @@ -83,8 +79,8 @@ public int getMinArgs() { @Override public MathValue[] getArgs() { if (this.seed != null) - return new MathValue[] {this.rolls, this.min, this.max, this.seed}; + return new MathValue[]{this.rolls, this.min, this.max, this.seed}; - return new MathValue[] {this.rolls, this.min, this.max}; + return new MathValue[]{this.rolls, this.min, this.max}; } } diff --git a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/random/DieRollIntegerFunction.java b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/random/DieRollIntegerFunction.java index 4170134..0aeb9ac 100644 --- a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/random/DieRollIntegerFunction.java +++ b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/random/DieRollIntegerFunction.java @@ -9,18 +9,15 @@ import java.util.Random; import java.util.concurrent.ThreadLocalRandom; -/** - * {@link MathFunction} value supplier - * - *

- * Contract: - *
- * Returns a random integer value based on the input values: - *

    - *
  • Three inputs generates the sum of n (first input) random values between the second (inclusive) and third input (inclusive)
  • - *
  • Four inputs generates the sum of n (first input) random values between the second (inclusive) and third input (inclusive), seeded by the fourth input
  • - *
- */ +/// [MathFunction] value supplier +/// +/// **Contract:** +/// +/// Returns a random integer value based on the input values: +/// +/// - Three inputs generates the sum of _n_ (first input) random values between the second (inclusive) and third input (inclusive) +/// - Four inputs generates the sum of _n_ (first input) random values between the second (inclusive) and third input (inclusive), seeded by the fourth input +/// public final class DieRollIntegerFunction extends MathFunction { private final MathValue rolls; private final MathValue min; @@ -46,10 +43,10 @@ public String getName() { } @Override - public double compute(MolangInstance instance) { - final int rolls = (int)(Math.floor(this.rolls.get(instance))); - final int min = Mth.floor(this.min.get(instance)); - final int max = Mth.ceil(this.max.get(instance)); + public float compute(MolangInstance instance) { + int rolls = (int)(Math.floor(this.rolls.get(instance))); + int min = Mth.floor(this.min.get(instance)); + int max = Mth.ceil(this.max.get(instance)); int sum = 0; Random random; diff --git a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/random/RandomFunction.java b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/random/RandomFunction.java index 4d4cb9b..5761e55 100644 --- a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/random/RandomFunction.java +++ b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/random/RandomFunction.java @@ -7,19 +7,16 @@ import java.util.Random; -/** - * {@link MathFunction} value supplier - * - *

- * Contract: - *
- * Returns a random value based on the input values: - *

    - *
  • A single input generates a value between 0 and that input (exclusive)
  • - *
  • Two inputs generates a random value between the first (inclusive) and second input (exclusive)
  • - *
  • Three inputs generates a random value between the first (inclusive) and second input (exclusive), seeded by the third input
  • - *
- */ +/// [MathFunction] value supplier +/// +/// **Contract:** +/// +/// Returns a random value based on the input values: +/// +/// - A single input generates a value between 0 and that input (exclusive) +/// - Two inputs generates a random value between the first (inclusive) and second input (exclusive) +/// - Three inputs generates a random value between the first (inclusive) and second input (exclusive), seeded by the third input +/// public final class RandomFunction extends MathFunction { private final MathValue valueA; @Nullable @@ -44,23 +41,23 @@ public String getName() { } @Override - public double compute(MolangInstance instance) { - double result; - double valueA = this.valueA.get(instance); + public float compute(MolangInstance instance) { + float result; + float valueA = this.valueA.get(instance); if (this.random != null) { this.random.setSeed((long)this.seed.get(instance)); - result = this.random.nextDouble(); + result = this.random.nextFloat(); } else { - result = Math.random(); + result = (float) Math.random(); } if (this.valueB != null) { - double valueB = this.valueB.get(instance); - double min = Math.min(valueA, valueB); - double max = Math.max(valueA, valueB); + float valueB = this.valueB.get(instance); + float min = Math.min(valueA, valueB); + float max = Math.max(valueA, valueB); result = min + result * (max - min); } diff --git a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/random/RandomIntegerFunction.java b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/random/RandomIntegerFunction.java index 2fd8a32..50c22be 100644 --- a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/random/RandomIntegerFunction.java +++ b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/random/RandomIntegerFunction.java @@ -8,19 +8,16 @@ import java.util.Random; import java.util.concurrent.ThreadLocalRandom; -/** - * {@link MathFunction} value supplier - * - *

- * Contract: - *
- * Returns a random integer value based on the input values: - *

    - *
  • A single input generates a value between 0 and that input (exclusive)
  • - *
  • Two inputs generates a random value between the first (inclusive) and second input (inclusive)
  • - *
  • Three inputs generates a random value between the first (inclusive) and second input (inclusive), seeded by the third input
  • - *
- */ +/// [MathFunction] value supplier +/// +/// **Contract:** +/// +/// Returns a random integer value based on the input values: +/// +/// - A single input generates a value between 0 and that input (exclusive) +/// - Two inputs generates a random value between the first (inclusive) and second input (inclusive) +/// - Three inputs generates a random value between the first (inclusive) and second input (inclusive), seeded by the third input +/// public final class RandomIntegerFunction extends MathFunction { private final MathValue valueA; @Nullable @@ -45,9 +42,9 @@ public String getName() { } @Override - public double compute(MolangInstance instance) { + public float compute(MolangInstance instance) { int result; - int valueA = (int)Math.round(this.valueA.get(instance)); + int valueA = Math.round(this.valueA.get(instance)); Random random; if (this.random != null) { @@ -59,7 +56,7 @@ public double compute(MolangInstance instance) { } if (this.valueB != null) { - int valueB = (int)Math.round(this.valueB.get(instance)); + int valueB = Math.round(this.valueB.get(instance)); int min = Math.min(valueA, valueB); int max = Math.max(valueA, valueB); diff --git a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/round/CeilFunction.java b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/round/CeilFunction.java index 9b7a4e6..8cc454e 100644 --- a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/round/CeilFunction.java +++ b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/round/CeilFunction.java @@ -1,17 +1,15 @@ package org.mesdag.particlestorm.data.molang.compiler.function.round; +import net.minecraft.util.Mth; import org.mesdag.particlestorm.api.MolangInstance; import org.mesdag.particlestorm.data.molang.compiler.MathValue; import org.mesdag.particlestorm.data.molang.compiler.function.MathFunction; -/** - * {@link MathFunction} value supplier - * - *

- * Contract: - *
- * Returns the smallest value that is greater than or equal to the input value and is equal to an integer - */ +/// [MathFunction] value supplier +/// +/// **Contract:** +/// +/// Returns the smallest value that is greater than or equal to the input value and is equal to an integer public final class CeilFunction extends MathFunction { private final MathValue value; @@ -27,8 +25,8 @@ public String getName() { } @Override - public double compute(MolangInstance instance) { - return Math.ceil(this.value.get(instance)); + public float compute(MolangInstance instance) { + return Mth.ceil(this.value.get(instance)); } @Override diff --git a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/round/FloorFunction.java b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/round/FloorFunction.java index d468a62..a1d3fbe 100644 --- a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/round/FloorFunction.java +++ b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/round/FloorFunction.java @@ -1,17 +1,15 @@ package org.mesdag.particlestorm.data.molang.compiler.function.round; +import net.minecraft.util.Mth; import org.mesdag.particlestorm.api.MolangInstance; import org.mesdag.particlestorm.data.molang.compiler.MathValue; import org.mesdag.particlestorm.data.molang.compiler.function.MathFunction; -/** - * {@link MathFunction} value supplier - * - *

- * Contract: - *
- * Returns the largest value that is less than or equal to the input value and is equal to an integer - */ +/// [MathFunction] value supplier +/// +/// **Contract:** +/// +/// Returns the largest value that is less than or equal to the input value and is equal to an integer public final class FloorFunction extends MathFunction { private final MathValue value; @@ -27,8 +25,8 @@ public String getName() { } @Override - public double compute(MolangInstance instance) { - return Math.floor(this.value.get(instance)); + public float compute(MolangInstance instance) { + return Mth.floor(this.value.get(instance)); } @Override diff --git a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/round/HermiteBlendFunction.java b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/round/HermiteBlendFunction.java index 0266684..56f2ba4 100644 --- a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/round/HermiteBlendFunction.java +++ b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/round/HermiteBlendFunction.java @@ -4,14 +4,11 @@ import org.mesdag.particlestorm.data.molang.compiler.MathValue; import org.mesdag.particlestorm.data.molang.compiler.function.MathFunction; -/** - * {@link MathFunction} value supplier - * - *

- * Contract: - *
- * Returns the Hermite> basis 3t^2 - 2t^3 curve interpolation value based on the input value - */ +/// [MathFunction] value supplier +/// +/// **Contract:** +/// +/// Returns the Hermite> basis 3t^2 - 2t^3 curve interpolation value based on the input value public final class HermiteBlendFunction extends MathFunction { private final MathValue valueA; @@ -27,8 +24,8 @@ public String getName() { } @Override - public double compute(MolangInstance instance) { - final double value = this.valueA.get(instance); + public float compute(MolangInstance instance) { + float value = this.valueA.get(instance); return (3 * value * value) - (2 * value * value * value); } diff --git a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/round/LerpFunction.java b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/round/LerpFunction.java index 828150e..706e434 100644 --- a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/round/LerpFunction.java +++ b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/round/LerpFunction.java @@ -5,14 +5,11 @@ import org.mesdag.particlestorm.data.molang.compiler.MathValue; import org.mesdag.particlestorm.data.molang.compiler.function.MathFunction; -/** - * {@link MathFunction} value supplier - * - *

- * Contract: - *
- * Returns the first value plus the difference between the first and second input values multiplied by the third input value - */ +/// [MathFunction] value supplier +/// +/// **Contract:** +/// +/// Returns the first value plus the difference between the first and second input values multiplied by the third input value public final class LerpFunction extends MathFunction { private final MathValue min; private final MathValue max; @@ -32,7 +29,7 @@ public String getName() { } @Override - public double compute(MolangInstance instance) { + public float compute(MolangInstance instance) { return Mth.lerp(this.delta.get(instance), this.min.get(instance), this.max.get(instance)); } diff --git a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/round/LerpRotFunction.java b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/round/LerpRotFunction.java index 09cec1f..34c77c4 100644 --- a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/round/LerpRotFunction.java +++ b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/round/LerpRotFunction.java @@ -5,14 +5,11 @@ import org.mesdag.particlestorm.data.molang.compiler.MathValue; import org.mesdag.particlestorm.data.molang.compiler.function.MathFunction; -/** - * {@link MathFunction} value supplier - * - *

- * Contract: - *
- * Returns the first value plus the difference between the first and second input values multiplied by the third input value, wrapping the end result as a degrees value - */ +/// [MathFunction] value supplier +/// +/// **Contract:** +/// +/// Returns the first value plus the difference between the first and second input values multiplied by the third input value, wrapping the end result as a degrees value public final class LerpRotFunction extends MathFunction { private final MathValue min; private final MathValue max; @@ -32,7 +29,7 @@ public String getName() { } @Override - public double compute(MolangInstance instance) { + public float compute(MolangInstance instance) { return lerpYaw(this.delta.get(instance), this.min.get(instance), this.max.get(instance)); } @@ -46,10 +43,10 @@ public MathValue[] getArgs() { return new MathValue[] {this.min, this.max, this.delta}; } - public static double lerpYaw(double delta, double start, double end) { + public static float lerpYaw(float delta, float start, float end) { start = Mth.wrapDegrees(start); end = Mth.wrapDegrees(end); - double diff = start - end; + float diff = start - end; end = diff > 180 || diff < -180 ? start + Math.copySign(360 - Math.abs(diff), diff) : end; return Mth.lerp(delta, start, end); diff --git a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/round/RoundFunction.java b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/round/RoundFunction.java index a5fcef5..3ec5759 100644 --- a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/round/RoundFunction.java +++ b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/round/RoundFunction.java @@ -4,14 +4,11 @@ import org.mesdag.particlestorm.data.molang.compiler.MathValue; import org.mesdag.particlestorm.data.molang.compiler.function.MathFunction; -/** - * {@link MathFunction} value supplier - * - *

- * Contract: - *
- * Returns the closest integer value to the input value - */ +/// [MathFunction] value supplier +/// +/// **Contract:** +/// +/// Returns the closest integer value to the input value public final class RoundFunction extends MathFunction { private final MathValue value; @@ -27,7 +24,7 @@ public String getName() { } @Override - public double compute(MolangInstance instance) { + public float compute(MolangInstance instance) { return Math.round(this.value.get(instance)); } diff --git a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/round/TruncateFunction.java b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/round/TruncateFunction.java index 2116fc4..be51211 100644 --- a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/round/TruncateFunction.java +++ b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/function/round/TruncateFunction.java @@ -4,14 +4,11 @@ import org.mesdag.particlestorm.data.molang.compiler.MathValue; import org.mesdag.particlestorm.data.molang.compiler.function.MathFunction; -/** - * {@link MathFunction} value supplier - * - *

- * Contract: - *
- * Returns the closest value that is equal to the input value or closer to zero, and is equal to an integer - */ +/// [MathFunction] value supplier +/// +/// **Contract:** +/// +/// Returns the closest value that is equal to the input value or closer to zero, and is equal to an integer public final class TruncateFunction extends MathFunction { private final MathValue value; @@ -27,8 +24,8 @@ public String getName() { } @Override - public double compute(MolangInstance instance) { - return (long)this.value.get(instance); + public float compute(MolangInstance instance) { + return (int) this.value.get(instance); } @Override @@ -38,6 +35,6 @@ public int getMinArgs() { @Override public MathValue[] getArgs() { - return new MathValue[] {this.value}; + return new MathValue[]{this.value}; } } diff --git a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/value/BooleanNegate.java b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/value/BooleanNegate.java index 2d061c0..ad3ac6e 100644 --- a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/value/BooleanNegate.java +++ b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/value/BooleanNegate.java @@ -3,20 +3,22 @@ import org.mesdag.particlestorm.api.MolangInstance; import org.mesdag.particlestorm.data.molang.compiler.MathValue; -/** - * {@link MathValue} value supplier - * - *

- * Contract: - *
- * Returns 1 if the contained value is equal to 0, otherwise returns 0 - */ +/// [MathValue] value supplier +/// +/// **Contract:** +/// +/// Returns **1** if the contained value is equal to **0**, otherwise returns **0** public record BooleanNegate(MathValue value) implements MathValue { @Override - public double get(MolangInstance instance) { + public float get(MolangInstance instance) { return this.value.get(instance) == 0 ? 1 : 0; } + @Override + public void markImmutable() { + value.markImmutable(); + } + @Override public boolean isMutable() { return this.value.isMutable(); diff --git a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/value/Calculation.java b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/value/Calculation.java index c2f31df..28a9244 100644 --- a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/value/Calculation.java +++ b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/value/Calculation.java @@ -4,43 +4,26 @@ import org.mesdag.particlestorm.data.molang.compiler.MathValue; import org.mesdag.particlestorm.data.molang.compiler.Operator; -/** - * {@link MathValue} value supplier - * - *

- * Contract: - *
- * A computed value of argA and argB defined by the contract of the {@link Operator} - */ -public final class Calculation implements MathValue { - public final Operator operator; - public final MathValue argA; - public final MathValue argB; - public final boolean isMutable; - - private double cachedValue = Double.MIN_VALUE; - - public Calculation(Operator operator, MathValue argA, MathValue argB) { - this.operator = operator; - this.argA = argA; - this.argB = argB; - this.isMutable = this.argA.isMutable() || this.argB.isMutable(); +/// [MathValue] value supplier +/// +/// **Contract:** +/// +/// A computed value of argA and argB defined by the contract of the [Operator] +public record Calculation(Operator operator, MathValue argA, MathValue argB) implements MathValue { + @Override + public float get(MolangInstance instance) { + return this.operator.compute(this.argA.get(instance), this.argB.get(instance)); } @Override - public double get(MolangInstance instance) { - if (this.isMutable) - return this.operator.compute(this.argA.get(instance), this.argB.get(instance)); - - if (this.cachedValue == Double.MIN_VALUE) - this.cachedValue = this.operator.compute(this.argA.get(instance), this.argB.get(instance)); - - return this.cachedValue; + public boolean isMutable() { + return argA.isMutable() || argB.isMutable(); } @Override - public boolean isMutable() { - return this.isMutable; + public void markImmutable() { + argA.markImmutable(); + argB.markImmutable(); } @Override diff --git a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/value/CompoundValue.java b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/value/CompoundValue.java index 186bdd5..899fc3b 100644 --- a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/value/CompoundValue.java +++ b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/value/CompoundValue.java @@ -5,18 +5,15 @@ import java.util.StringJoiner; -/** - * {@link MathValue} value supplier - * - *

- * Contract: - *
- * Contains a collection of sub-expressions that evaluate before returning the last expression, or 0 if no return is defined. - * Sub-expressions have no bearing on the final return with exception for where they may be setting variable values - */ +/// [MathValue] value supplier +/// +/// **Contract:** +/// +/// Contains a collection of sub-expressions that evaluate before returning the last expression, or 0 if no return is defined. +/// Sub-expressions have no bearing on the final return with exception for where they may be setting variable values public record CompoundValue(MathValue[] subValues) implements MathValue { @Override - public double get(MolangInstance instance) { + public float get(MolangInstance instance) { for (int i = 0; i < this.subValues.length - 1; i++) { this.subValues[i].get(instance); } @@ -24,6 +21,13 @@ public double get(MolangInstance instance) { return this.subValues[this.subValues.length - 1].get(instance); } + @Override + public void markImmutable() { + for (MathValue value : subValues) { + value.markImmutable(); + } + } + @Override public String toString() { final StringJoiner joiner = new StringJoiner("; "); diff --git a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/value/Constant.java b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/value/Constant.java index f635d21..54ced6b 100644 --- a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/value/Constant.java +++ b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/value/Constant.java @@ -3,17 +3,14 @@ import org.mesdag.particlestorm.api.MolangInstance; import org.mesdag.particlestorm.data.molang.compiler.MathValue; -/** - * {@link MathValue} value supplier - * - *

- * Contract: - *
- * An immutable double value - */ -public record Constant(double value) implements MathValue { +/// [MathValue] value supplier +/// +/// **Contract:** +/// +/// An immutable double value +public record Constant(float value) implements MathValue { @Override - public double get(MolangInstance instance) { + public float get(MolangInstance instance) { return this.value; } @@ -22,6 +19,9 @@ public boolean isMutable() { return false; } + @Override + public void markImmutable() {} + @Override public String toString() { return String.valueOf(this.value); diff --git a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/value/Group.java b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/value/Group.java index b3ea997..0d784d0 100644 --- a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/value/Group.java +++ b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/value/Group.java @@ -3,20 +3,22 @@ import org.mesdag.particlestorm.api.MolangInstance; import org.mesdag.particlestorm.data.molang.compiler.MathValue; -/** - * {@link MathValue} value supplier - * - *

- * Contract: - *
- * An unaltered return of the stored MathValue - */ +/// [MathValue] value supplier +/// +/// **Contract:** +/// +/// An unaltered return of the stored MathValue public record Group(MathValue contents) implements MathValue { @Override - public double get(MolangInstance instance) { + public float get(MolangInstance instance) { return this.contents.get(instance); } + @Override + public void markImmutable() { + contents.markImmutable(); + } + @Override public boolean isMutable() { return this.contents.isMutable(); diff --git a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/value/Negative.java b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/value/Negative.java index 41bf50a..4c15fbe 100644 --- a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/value/Negative.java +++ b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/value/Negative.java @@ -3,20 +3,22 @@ import org.mesdag.particlestorm.api.MolangInstance; import org.mesdag.particlestorm.data.molang.compiler.MathValue; -/** - * {@link MathValue} value supplier - * - *

- * Contract: - *
- * Negated equivalent of the stored value; returning a positive number if the stored value is negative, or a negative value if the stored value is positive - */ +/// [MathValue] value supplier +/// +/// **Contract:** +/// +/// Negated equivalent of the stored value; returning a positive number if the stored value is negative, or a negative value if the stored value is positive public record Negative(MathValue value) implements MathValue { @Override - public double get(MolangInstance instance) { + public float get(MolangInstance instance) { return -this.value.get(instance); } + @Override + public void markImmutable() { + value.markImmutable(); + } + @Override public boolean isMutable() { return this.value.isMutable(); diff --git a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/value/StringValue.java b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/value/StringValue.java new file mode 100644 index 0000000..074d78c --- /dev/null +++ b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/value/StringValue.java @@ -0,0 +1,24 @@ +package org.mesdag.particlestorm.data.molang.compiler.value; + +import org.mesdag.particlestorm.api.MolangInstance; +import org.mesdag.particlestorm.data.molang.compiler.MathValue; + +public record StringValue(String value) implements MathValue { + @Override + public float get(MolangInstance instance) { + return value.isEmpty() ? 0 : 1; + } + + @Override + public boolean isMutable() { + return false; + } + + @Override + public void markImmutable() {} + + @Override + public String toString() { + return value; + } +} diff --git a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/value/Ternary.java b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/value/Ternary.java index b6ead36..a696eb7 100644 --- a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/value/Ternary.java +++ b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/value/Ternary.java @@ -3,19 +3,23 @@ import org.mesdag.particlestorm.api.MolangInstance; import org.mesdag.particlestorm.data.molang.compiler.MathValue; -/** - * {@link MathValue} value supplier - * - *

- * Contract: - *
- * Returns one of two stored values dependent on the result of the stored condition value. - * This returns such that a non-zero result from the condition will return the true stored value, otherwise returning the false stored value - */ +/// [MathValue] value supplier +/// +/// **Contract:** +/// +/// Returns one of two stored values dependent on the result of the stored condition value. +/// This returns such that a non-zero result from the condition will return the **true** stored value, otherwise returning the **false** stored value public record Ternary(MathValue condition, MathValue trueValue, MathValue falseValue) implements MathValue { @Override - public double get(MolangInstance instance) { - return this.condition.get(instance) != 0 ? this.trueValue.get(instance) : this.falseValue.get(instance); + public float get(MolangInstance instance) { + return this.condition.get(instance) == 0 ? this.falseValue.get(instance) : this.trueValue.get(instance); + } + + @Override + public void markImmutable() { + condition.markImmutable(); + trueValue.markImmutable(); + falseValue.markImmutable(); } @Override diff --git a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/value/Variable.java b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/value/Variable.java index f90d91c..112a408 100644 --- a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/value/Variable.java +++ b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/value/Variable.java @@ -2,50 +2,60 @@ import org.mesdag.particlestorm.ParticleStorm; import org.mesdag.particlestorm.api.MolangInstance; +import org.mesdag.particlestorm.api.ToFloatFunction; import org.mesdag.particlestorm.data.molang.compiler.MathValue; -import java.util.function.ToDoubleFunction; - -/** - * {@link MathValue} value supplier - * - *

- * Contract: - *
- * Returns the currently stored value, which may be modified at any given time via {@link #set}. Values may be lazily evaluated to eliminate wasteful usage - */ +/// [MathValue] value supplier +/// +/// **Contract:** +/// +/// Returns the currently stored value, which may be modified at any given time via [#set]. Values may be lazily evaluated to eliminate wasteful usage public final class Variable implements MathValue { private final String name; - private ToDoubleFunction value; - private Double constant; + private ToFloatFunction value; + private Float constant; + private boolean immutable; - public Variable(String name, ToDoubleFunction value) { + public Variable(String name, ToFloatFunction value) { this.name = name; this.value = value; } - public Variable(String name, double value) { + public Variable(String name, float value) { this.name = name; this.constant = value; } @Override - public double get(MolangInstance instance) { + public float get(MolangInstance instance) { try { - if (constant != null) return constant; - return value.applyAsDouble(instance); + float v = constant == null ? value.applyAsFloat(instance) : constant; + if (immutable) { + instance.getVars().setValue(name, new Variable(name, v)); + } + return v; } catch (Exception ex) { ParticleStorm.LOGGER.error("Attempted to use Molang variable for incompatible animatable type ({}). An animation json needs to be fixed", this.name); return 0; } } - public void set(Double value) { + @Override + public boolean isMutable() { + return !immutable; + } + + @Override + public void markImmutable() { + this.immutable = true; + } + + public void set(Float value) { this.constant = value; } @Override - public void set(ToDoubleFunction value) { + public void set(ToFloatFunction value) { this.value = value; } @@ -53,7 +63,20 @@ public String name() { return name; } - public ToDoubleFunction value() { + public ToFloatFunction value() { return value; } + + public Variable copy() { + Variable variable; + if (constant == null) { + variable = new Variable(name, value); + } else { + variable = new Variable(name, constant); + } + if (immutable) { + variable.markImmutable(); + } + return variable; + } } diff --git a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/value/VariableAssignment.java b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/value/VariableAssignment.java index 74e83fa..77b2c4e 100644 --- a/src/main/java/org/mesdag/particlestorm/data/molang/compiler/value/VariableAssignment.java +++ b/src/main/java/org/mesdag/particlestorm/data/molang/compiler/value/VariableAssignment.java @@ -3,21 +3,24 @@ import org.mesdag.particlestorm.api.MolangInstance; import org.mesdag.particlestorm.data.molang.compiler.MathValue; -/** - * {@link MathValue} value supplier - * - *

- * Contract: - *
- * Assigns a variable to the given value, then returns 0 - */ +/// [MathValue] value supplier +/// +/// **Contract:** +/// +/// Assigns a variable to the given value, then returns 0 public record VariableAssignment(Variable variable, MathValue value) implements MathValue { @Override - public double get(MolangInstance instance) { + public float get(MolangInstance instance) { variable.set(value.get(instance)); return 0; } + @Override + public void markImmutable() { + variable.markImmutable(); + value.markImmutable(); + } + @Override public String toString() { return variable.name() + "=" + value.toString(); diff --git a/src/main/java/org/mesdag/particlestorm/mixed/IAnimatableInstanceCache.java b/src/main/java/org/mesdag/particlestorm/mixed/IAnimatableInstanceCache.java index 6cc5084..fa10cc9 100644 --- a/src/main/java/org/mesdag/particlestorm/mixed/IAnimatableInstanceCache.java +++ b/src/main/java/org/mesdag/particlestorm/mixed/IAnimatableInstanceCache.java @@ -1,18 +1,14 @@ package org.mesdag.particlestorm.mixed; import it.unimi.dsi.fastutil.objects.Object2IntMap; -import org.joml.Vector3f; +import org.mesdag.particlestorm.api.geckolib.GeckoLibHelper; import software.bernie.geckolib.animatable.instance.AnimatableInstanceCache; import software.bernie.geckolib.loading.json.raw.LocatorValue; public interface IAnimatableInstanceCache { Object2IntMap particlestorm$getCachedId(); - Vector3f particlestorm$getPosition(); - - Vector3f particlestorm$getRotation(); - -// Vector3f particlestorm$getScale(); + GeckoLibHelper.LocatorState particlestorm$getLocatorState(LocatorValue locator); static IAnimatableInstanceCache of(AnimatableInstanceCache cache) { return (IAnimatableInstanceCache) cache; diff --git a/src/main/java/org/mesdag/particlestorm/mixed/IGeoBone.java b/src/main/java/org/mesdag/particlestorm/mixed/IGeoBone.java index f35fbeb..903810c 100644 --- a/src/main/java/org/mesdag/particlestorm/mixed/IGeoBone.java +++ b/src/main/java/org/mesdag/particlestorm/mixed/IGeoBone.java @@ -1,12 +1,13 @@ package org.mesdag.particlestorm.mixed; +import org.jetbrains.annotations.Nullable; import software.bernie.geckolib.cache.object.GeoBone; import software.bernie.geckolib.loading.json.raw.LocatorValue; import java.util.Map; public interface IGeoBone { - Map particlestorm$getLocators(); + @Nullable Map particlestorm$getLocators(); void particlestorm$setLocators(Map locators); diff --git a/src/main/java/org/mesdag/particlestorm/mixed/IParticleKeyframeData.java b/src/main/java/org/mesdag/particlestorm/mixed/IParticleKeyframeData.java index 92fea2f..c0d2c0e 100644 --- a/src/main/java/org/mesdag/particlestorm/mixed/IParticleKeyframeData.java +++ b/src/main/java/org/mesdag/particlestorm/mixed/IParticleKeyframeData.java @@ -3,9 +3,14 @@ import net.minecraft.resources.ResourceLocation; import org.mesdag.particlestorm.data.molang.MolangExp; import org.mesdag.particlestorm.data.molang.VariableTable; +import software.bernie.geckolib.animation.keyframe.event.data.ParticleKeyframeData; public interface IParticleKeyframeData { ResourceLocation particlestorm$getParticle(); MolangExp particlestorm$getExpression(VariableTable variableTable); + + static IParticleKeyframeData of(ParticleKeyframeData data) { + return (IParticleKeyframeData) data; + } } diff --git a/src/main/java/org/mesdag/particlestorm/mixin/EntityMixin.java b/src/main/java/org/mesdag/particlestorm/mixin/EntityMixin.java index eaa3bc2..a3ca38f 100644 --- a/src/main/java/org/mesdag/particlestorm/mixin/EntityMixin.java +++ b/src/main/java/org/mesdag/particlestorm/mixin/EntityMixin.java @@ -22,9 +22,9 @@ public abstract class EntityMixin implements IEntity { Hashtable table = new Hashtable<>(); table.put("variable.entity_scale", new Variable("variable.entity_scale", p -> { if (p.getAttachedEntity() instanceof LivingEntity living) { - return living.getAttributeValue(Attributes.SCALE); + return (float) living.getAttributeValue(Attributes.SCALE); } - return 1.0; + return 1; })); this.particlestorm$variableTable = new VariableTable(table, null); } diff --git a/src/main/java/org/mesdag/particlestorm/mixin/LivingEntityMixin.java b/src/main/java/org/mesdag/particlestorm/mixin/LivingEntityMixin.java index 919916b..99c988a 100644 --- a/src/main/java/org/mesdag/particlestorm/mixin/LivingEntityMixin.java +++ b/src/main/java/org/mesdag/particlestorm/mixin/LivingEntityMixin.java @@ -5,7 +5,7 @@ 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.MolangParticleEngine; import org.mesdag.particlestorm.particle.MolangParticleOption; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; @@ -14,8 +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(ResourceLocation id)) { - PSGameClient.LOADER.addTrackedEmitter((LivingEntity) (Object) this, id); + if (instance.isClientSide && particleData instanceof MolangParticleOption(ResourceLocation id)) { + MolangParticleEngine.INSTANCE.addTrackedEmitter((LivingEntity) (Object) this, id); return false; } return true; diff --git a/src/main/java/org/mesdag/particlestorm/mixin/ParticleEngineMixin.java b/src/main/java/org/mesdag/particlestorm/mixin/ParticleEngineMixin.java index 55af078..b5c0019 100644 --- a/src/main/java/org/mesdag/particlestorm/mixin/ParticleEngineMixin.java +++ b/src/main/java/org/mesdag/particlestorm/mixin/ParticleEngineMixin.java @@ -1,15 +1,21 @@ package org.mesdag.particlestorm.mixin; +import com.llamalad7.mixinextras.sugar.Local; +import net.minecraft.client.Camera; import net.minecraft.client.particle.ParticleEngine; +import net.minecraft.client.particle.ParticleRenderType; +import net.minecraft.client.renderer.culling.Frustum; import net.minecraft.client.renderer.texture.SpriteLoader; import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.client.renderer.texture.TextureManager; import net.minecraft.resources.ResourceLocation; -import org.mesdag.particlestorm.PSGameClient; +import org.jetbrains.annotations.Nullable; 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.mesdag.particlestorm.particle.MolangParticleEngine; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; @@ -20,6 +26,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import java.util.Map; +import java.util.function.Predicate; @Mixin(ParticleEngine.class) public abstract class ParticleEngineMixin implements IParticleEngine { @@ -27,6 +34,9 @@ public abstract class ParticleEngineMixin implements IParticleEngine { @Final private Map spriteSets; + @Shadow + @Final + private TextureManager textureManager; @Unique private volatile SpriteLoader.Preparations particlestorm$preparations; @@ -35,7 +45,7 @@ public abstract class ParticleEngineMixin implements IParticleEngine { 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()) { + for (Map.Entry entry : MolangParticleEngine.INSTANCE.id2Effect().entrySet()) { TextureAtlasSprite missing = particlestorm$preparations.missing(); spriteSet.bindMissing(missing); ResourceLocation texture = entry.getValue().description.parameters().bindTexture(i); @@ -55,4 +65,16 @@ private void registerCustom(CallbackInfo ci) { private SpriteLoader.Preparations cachePreparations(SpriteLoader.Preparations preparations) { return this.particlestorm$preparations = preparations; } + + @Inject(method = "render(Lnet/minecraft/client/renderer/LightTexture;Lnet/minecraft/client/Camera;FLnet/minecraft/client/renderer/culling/Frustum;Ljava/util/function/Predicate;)V", at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/systems/RenderSystem;depthMask(Z)V")) + private void renderMolang( + CallbackInfo ci, + @Local(argsOnly = true) Camera camera, + @Local(argsOnly = true) float partialTick, + @Local(argsOnly = true) @Nullable Frustum frustum, + @Local(argsOnly = true) @Nullable Predicate renderTypePredicate + ) { + if (frustum == null || renderTypePredicate == null) return; + MolangParticleEngine.INSTANCE.renderParticles(textureManager, camera, partialTick, frustum, renderTypePredicate); + } } 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 e154110..09f3b8f 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 @@ -2,24 +2,23 @@ import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; -import org.joml.Vector3f; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import org.mesdag.particlestorm.api.geckolib.GeckoLibHelper; import org.mesdag.particlestorm.mixed.IAnimatableInstanceCache; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Pseudo; import org.spongepowered.asm.mixin.Unique; import software.bernie.geckolib.loading.json.raw.LocatorValue; +import java.util.Map; + @Pseudo @Mixin(targets = "software.bernie.geckolib.animatable.instance.AnimatableInstanceCache", remap = false) -public class AnimatableInstanceCacheMixin implements IAnimatableInstanceCache { +public abstract class AnimatableInstanceCacheMixin implements IAnimatableInstanceCache { @Unique private Object2IntMap particlestorm$cachedId; @Unique - private Vector3f particlestorm$position; - @Unique - private Vector3f particlestorm$rotation; -// @Unique -// private Vector3f particlestorm$scale; + private Map particlestorm$transform; @Override public Object2IntMap particlestorm$getCachedId() { @@ -31,26 +30,10 @@ public class AnimatableInstanceCacheMixin implements IAnimatableInstanceCache { } @Override - public Vector3f particlestorm$getPosition() { - if (particlestorm$position == null) { - this.particlestorm$position = new Vector3f(); + public GeckoLibHelper.LocatorState particlestorm$getLocatorState(LocatorValue locator) { + if (particlestorm$transform == null) { + this.particlestorm$transform = new Object2ObjectOpenHashMap<>(); } - return particlestorm$position; + return particlestorm$transform.computeIfAbsent(locator, b -> new GeckoLibHelper.LocatorState()); } - - @Override - public Vector3f particlestorm$getRotation() { - if (particlestorm$rotation == null) { - this.particlestorm$rotation = new Vector3f(); - } - return particlestorm$rotation; - } - -// @Override -// public Vector3f particlestorm$getScale() { -// if (particlestorm$scale == null) { -// this.particlestorm$scale = new Vector3f(); -// } -// return particlestorm$scale; -// } } 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 297ba30..deca1da 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 @@ -50,8 +50,8 @@ public abstract class AnimationControllerMixin implemen } @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(animatable, this, keyframeData); + private boolean processParticleEffect(Logger instance, Level level, String s, @Local(name = "keyframeData") ParticleKeyframeData keyframeData) { + return GeckoLibHelper.processParticleEffect(animatable, (AnimationController) (Object) this, keyframeData); } @Inject(method = "resetEventKeyFrames", at = @At("HEAD")) 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 3c80d79..8820f6c 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 @@ -9,11 +9,8 @@ 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.AnimatableManager; import software.bernie.geckolib.animation.AnimationController; -import software.bernie.geckolib.animation.AnimationState; import software.bernie.geckolib.cache.object.GeoBone; -import software.bernie.geckolib.model.GeoModel; import java.util.Collection; @@ -24,7 +21,7 @@ public abstract class AnimationProcessorMixin { public abstract Collection getRegisteredBones(); @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) { + private void fillLocators(CallbackInfo ci, @Local(name = "controller") AnimationController controller) { IAnimationController.of(controller).particlestorm$setBonesWhichHasLocators(getRegisteredBones()); } } diff --git a/src/main/java/org/mesdag/particlestorm/mixin/integration/geckolib/BakedModelFactory$BuiltinMixin.java b/src/main/java/org/mesdag/particlestorm/mixin/integration/geckolib/BakedModelFactory$BuiltinMixin.java index a8af9a3..708d444 100644 --- a/src/main/java/org/mesdag/particlestorm/mixin/integration/geckolib/BakedModelFactory$BuiltinMixin.java +++ b/src/main/java/org/mesdag/particlestorm/mixin/integration/geckolib/BakedModelFactory$BuiltinMixin.java @@ -1,5 +1,6 @@ package org.mesdag.particlestorm.mixin.integration.geckolib; +import com.llamalad7.mixinextras.sugar.Local; import org.mesdag.particlestorm.mixed.IGeoBone; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Pseudo; @@ -7,14 +8,13 @@ import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import software.bernie.geckolib.cache.object.GeoBone; -import software.bernie.geckolib.loading.json.raw.ModelProperties; import software.bernie.geckolib.loading.object.BoneStructure; @Pseudo @Mixin(targets = "software.bernie.geckolib.loading.object.BakedModelFactory$Builtin", remap = false) public abstract class BakedModelFactory$BuiltinMixin { @Inject(method = "constructBone", at = @At("RETURN")) - private void addLocators(BoneStructure boneStructure, ModelProperties properties, GeoBone parent, CallbackInfoReturnable cir) { + private void addLocators(CallbackInfoReturnable cir, @Local(argsOnly = true) BoneStructure boneStructure) { IGeoBone.of(cir.getReturnValue()).particlestorm$setLocators(boneStructure.self().locators()); } } 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 05c197e..c0af57d 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 @@ -1,46 +1,22 @@ package org.mesdag.particlestorm.mixin.integration.geckolib; -import org.mesdag.particlestorm.mixed.IAnimatableInstanceCache; +import org.jetbrains.annotations.Nullable; 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; -import software.bernie.geckolib.animatable.GeoAnimatable; import software.bernie.geckolib.loading.json.raw.LocatorValue; -import software.bernie.geckolib.loading.math.MolangQueries; import java.util.Map; @Pseudo @Mixin(targets = "software.bernie.geckolib.cache.object.GeoBone", remap = false) public abstract class GeoBoneMixin implements IGeoBone { - @Shadow - private float positionX; - @Shadow - private float positionY; - @Shadow - private float positionZ; - @Shadow - private float rotX; - @Shadow - private float rotY; - @Shadow - private float rotZ; -// @Shadow -// private float scaleX; -// @Shadow -// private float scaleY; -// @Shadow -// private float scaleZ; @Unique private Map particlestorm$locators; @Override - public Map particlestorm$getLocators() { + public @Nullable Map particlestorm$getLocators() { return particlestorm$locators; } @@ -48,16 +24,4 @@ public abstract class GeoBoneMixin implements IGeoBone { public void particlestorm$setLocators(Map locators) { this.particlestorm$locators = locators; } - - @Inject(method = "resetStateChanges", at = @At("TAIL")) - private void setData(CallbackInfo ci) { - 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()); - cache.particlestorm$getPosition().set(positionX, positionY, positionZ); - cache.particlestorm$getRotation().set(rotX, rotY, rotZ); - //cache.particlestorm$getScale().set(scaleX, scaleY, scaleZ); todo - } - } } diff --git a/src/main/java/org/mesdag/particlestorm/mixin/integration/geckolib/GeoModelMixin.java b/src/main/java/org/mesdag/particlestorm/mixin/integration/geckolib/GeoModelMixin.java new file mode 100644 index 0000000..428a17e --- /dev/null +++ b/src/main/java/org/mesdag/particlestorm/mixin/integration/geckolib/GeoModelMixin.java @@ -0,0 +1,28 @@ +package org.mesdag.particlestorm.mixin.integration.geckolib; + +import com.llamalad7.mixinextras.sugar.Local; +import org.mesdag.particlestorm.api.geckolib.GeckoLibHelper; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Pseudo; +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.AnimationProcessor; +import software.bernie.geckolib.cache.object.GeoBone; + +@Pseudo +@Mixin(targets = "software.bernie.geckolib.model.GeoModel", remap = false) +public abstract class GeoModelMixin { + @Inject(method = "handleAnimations", at = @At("TAIL")) + private void transform( + CallbackInfo ci, + @Local(argsOnly = true) T animatable, + @Local(argsOnly = true) float partialTick, + @Local(name = "processor") AnimationProcessor processor + ) { + for (GeoBone bone : processor.getRegisteredBones()) { + GeckoLibHelper.transformLocator(bone, animatable, partialTick); + } + } +} diff --git a/src/main/java/org/mesdag/particlestorm/mixin/integration/geckolib/GeoReplacedEntityRendererMixin.java b/src/main/java/org/mesdag/particlestorm/mixin/integration/geckolib/GeoReplacedEntityRendererMixin.java index 57ddd0f..d9816eb 100644 --- a/src/main/java/org/mesdag/particlestorm/mixin/integration/geckolib/GeoReplacedEntityRendererMixin.java +++ b/src/main/java/org/mesdag/particlestorm/mixin/integration/geckolib/GeoReplacedEntityRendererMixin.java @@ -1,7 +1,6 @@ package org.mesdag.particlestorm.mixin.integration.geckolib; -import com.mojang.blaze3d.vertex.PoseStack; -import net.minecraft.client.renderer.MultiBufferSource; +import com.llamalad7.mixinextras.sugar.Local; import net.minecraft.world.entity.Entity; import org.mesdag.particlestorm.api.geckolib.GeckoLibHelper; import org.spongepowered.asm.mixin.Final; @@ -21,7 +20,7 @@ public abstract class GeoReplacedEntityRendererMixin 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 1411552..b6def5b 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 @@ -1,7 +1,6 @@ package org.mesdag.particlestorm.mixin.integration.geckolib; -import net.minecraft.client.Minecraft; -import org.mesdag.particlestorm.PSGameClient; +import org.mesdag.particlestorm.particle.MolangParticleEngine; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Pseudo; import org.spongepowered.asm.mixin.injection.At; @@ -15,13 +14,7 @@ public abstract class MolangQueriesMixin { @Inject(method = "", at = @At("TAIL")) 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 : Minecraft.getInstance().particleEngine.trackedParticleCounts.values()) { - sum += value; - } - return sum; - }); + setActorVariable("query.total_emitter_count", actor -> MolangParticleEngine.INSTANCE.totalEmitterCount()); + setActorVariable("query.total_particle_count", actor -> MolangParticleEngine.INSTANCE.totalParticleCount()); } } diff --git a/src/main/java/org/mesdag/particlestorm/network/EmitterAttachPacketS2C.java b/src/main/java/org/mesdag/particlestorm/network/EmitterAttachPacketS2C.java index 81fe611..1b69d6a 100644 --- a/src/main/java/org/mesdag/particlestorm/network/EmitterAttachPacketS2C.java +++ b/src/main/java/org/mesdag/particlestorm/network/EmitterAttachPacketS2C.java @@ -10,8 +10,8 @@ import net.minecraft.world.entity.player.Player; import net.neoforged.neoforge.network.PacketDistributor; import net.neoforged.neoforge.network.handling.IPayloadContext; -import org.mesdag.particlestorm.PSGameClient; import org.mesdag.particlestorm.ParticleStorm; +import org.mesdag.particlestorm.particle.MolangParticleEngine; import org.mesdag.particlestorm.particle.ParticleEmitter; public record EmitterAttachPacketS2C(int particleId, int entityId) implements CustomPacketPayload { @@ -31,7 +31,7 @@ public void handle(IPayloadContext context) { context.enqueueWork(() -> { Player player = context.player(); if (player.isLocalPlayer()) { - ParticleEmitter emitter = PSGameClient.LOADER.getEmitter(particleId); + ParticleEmitter emitter = MolangParticleEngine.INSTANCE.getEmitter(particleId); Entity entity; if (emitter != null && (entity = player.level().getEntity(entityId)) != null) { emitter.attachEntity(entity); diff --git a/src/main/java/org/mesdag/particlestorm/network/EmitterCreationPacketS2C.java b/src/main/java/org/mesdag/particlestorm/network/EmitterCreationPacketS2C.java index d91be43..d89f935 100644 --- a/src/main/java/org/mesdag/particlestorm/network/EmitterCreationPacketS2C.java +++ b/src/main/java/org/mesdag/particlestorm/network/EmitterCreationPacketS2C.java @@ -15,9 +15,9 @@ import net.neoforged.neoforge.server.ServerLifecycleHooks; import org.jetbrains.annotations.Nullable; 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.particle.MolangParticleEngine; import org.mesdag.particlestorm.particle.ParticleEmitter; public record EmitterCreationPacketS2C(ResourceLocation id, Vector3f pos, MolangExp expression, int entityId) implements CustomPacketPayload { @@ -44,7 +44,7 @@ public void handle(IPayloadContext context) { if (entityId > 0) { emitter.attachEntity(player.level().getEntity(entityId)); } - PSGameClient.LOADER.addEmitter(emitter, false); + MolangParticleEngine.INSTANCE.addEmitter(emitter, false); } }).exceptionally(e -> { context.disconnect(Component.translatable("neoforge.network.invalid_flow", e.getMessage())); diff --git a/src/main/java/org/mesdag/particlestorm/network/EmitterRemovalPacket.java b/src/main/java/org/mesdag/particlestorm/network/EmitterRemovalPacket.java index 73219bd..1d1eca9 100644 --- a/src/main/java/org/mesdag/particlestorm/network/EmitterRemovalPacket.java +++ b/src/main/java/org/mesdag/particlestorm/network/EmitterRemovalPacket.java @@ -10,8 +10,8 @@ import net.minecraft.world.entity.player.Player; import net.neoforged.neoforge.network.PacketDistributor; import net.neoforged.neoforge.network.handling.IPayloadContext; -import org.mesdag.particlestorm.PSGameClient; import org.mesdag.particlestorm.ParticleStorm; +import org.mesdag.particlestorm.particle.MolangParticleEngine; import org.mesdag.particlestorm.particle.ParticleEmitter; import static org.mesdag.particlestorm.network.EmitterSynchronizePacket.KEY; @@ -33,7 +33,7 @@ public void handle(IPayloadContext context) { context.enqueueWork(() -> { Player player = context.player(); if (player.isLocalPlayer()) { - ParticleEmitter emitter = PSGameClient.LOADER.removeEmitter(id, false); + ParticleEmitter emitter = MolangParticleEngine.INSTANCE.removeEmitter(id, false); if (emitter == null) { player.sendSystemMessage(Component.translatable("particle.notFound", id)); } else { diff --git a/src/main/java/org/mesdag/particlestorm/network/EmitterSynchronizePacket.java b/src/main/java/org/mesdag/particlestorm/network/EmitterSynchronizePacket.java index e6ed4b0..e905594 100644 --- a/src/main/java/org/mesdag/particlestorm/network/EmitterSynchronizePacket.java +++ b/src/main/java/org/mesdag/particlestorm/network/EmitterSynchronizePacket.java @@ -10,8 +10,8 @@ import net.minecraft.world.entity.player.Player; import net.neoforged.neoforge.network.PacketDistributor; import net.neoforged.neoforge.network.handling.IPayloadContext; -import org.mesdag.particlestorm.PSGameClient; import org.mesdag.particlestorm.ParticleStorm; +import org.mesdag.particlestorm.particle.MolangParticleEngine; import org.mesdag.particlestorm.particle.ParticleEmitter; public record EmitterSynchronizePacket(int id, CompoundTag tag) implements CustomPacketPayload { @@ -33,7 +33,7 @@ public void handle(IPayloadContext context) { context.enqueueWork(() -> { Player player = context.player(); if (player.isLocalPlayer()) { - PSGameClient.LOADER.loadEmitter(player.level(), id, tag); + MolangParticleEngine.INSTANCE.loadEmitter(player.level(), id, tag); } else { CompoundTag data = player.getPersistentData(); if (data.contains(KEY)) { diff --git a/src/main/java/org/mesdag/particlestorm/particle/MolangParticleEngine.java b/src/main/java/org/mesdag/particlestorm/particle/MolangParticleEngine.java new file mode 100644 index 0000000..a924742 --- /dev/null +++ b/src/main/java/org/mesdag/particlestorm/particle/MolangParticleEngine.java @@ -0,0 +1,341 @@ +package org.mesdag.particlestorm.particle; + +import com.google.common.collect.EvictingQueue; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Lists; +import com.google.gson.JsonParseException; +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.vertex.BufferBuilder; +import com.mojang.blaze3d.vertex.BufferUploader; +import com.mojang.blaze3d.vertex.MeshData; +import com.mojang.blaze3d.vertex.Tesselator; +import com.mojang.serialization.JsonOps; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectIterator; +import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap; +import net.minecraft.CrashReport; +import net.minecraft.CrashReportCategory; +import net.minecraft.ReportedException; +import net.minecraft.Util; +import net.minecraft.client.Camera; +import net.minecraft.client.Minecraft; +import net.minecraft.client.particle.ParticleRenderType; +import net.minecraft.client.player.LocalPlayer; +import net.minecraft.client.renderer.GameRenderer; +import net.minecraft.client.renderer.culling.Frustum; +import net.minecraft.client.renderer.texture.TextureManager; +import net.minecraft.core.particles.ParticleGroup; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.resources.FileToIdConverter; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.packs.resources.PreparableReloadListener; +import net.minecraft.server.packs.resources.Resource; +import net.minecraft.server.packs.resources.ResourceManager; +import net.minecraft.util.GsonHelper; +import net.minecraft.util.Mth; +import net.minecraft.util.profiling.ProfilerFiller; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.level.Level; +import net.neoforged.fml.ModLoader; +import org.jetbrains.annotations.Nullable; +import org.mesdag.particlestorm.PSGameClient; +import org.mesdag.particlestorm.ParticleStorm; +import org.mesdag.particlestorm.api.*; +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.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import java.util.function.Predicate; + +public final class MolangParticleEngine implements PreparableReloadListener { + public static final MolangParticleEngine INSTANCE = new MolangParticleEngine(); + private static final FileToIdConverter PARTICLE_LISTER = FileToIdConverter.json("particle_definitions"); + private Map id2Effect = ImmutableMap.of(); + private Map id2Particle = ImmutableMap.of(); + private Map id2Emitter = ImmutableMap.of(); + + private final Int2ObjectOpenHashMap emitters = new Int2ObjectOpenHashMap<>(); + private final Object2ObjectOpenHashMap> tracker = new Object2ObjectOpenHashMap<>(); + private final Int2ObjectOpenHashMap> particlesForEmitter = new Int2ObjectOpenHashMap<>(); + private final Queue particlesToAdd = new ArrayDeque<>(); + private final Reference2ObjectOpenHashMap> groupedParticles = new Reference2ObjectOpenHashMap<>(); + private final IntAllocator allocator = new IntAllocator(); + private boolean initialized = false; + + private MolangParticleEngine() {} + + public Map id2Effect() { + return id2Effect; + } + + public Map id2Particle() { + return id2Particle; + } + + public Map id2Emitter() { + return id2Emitter; + } + + @SuppressWarnings("CallToPrintStackTrace") + public void tick(Minecraft minecraft, LocalPlayer player) { + if (!initialized) { + for (ParticlePreset detail : id2Particle.values()) { + for (IComponent component : detail.effect.orderedComponents) { + if (component instanceof IParticleComponent particleComponent) { + particleComponent.initialize(player.clientLevel); + } + } + } + removeAll(); + this.initialized = true; + } + if (!emitters.isEmpty()) { + int renderDistSqr = Mth.square(minecraft.options.renderDistance().get() * 16); + var iterator = emitters.int2ObjectEntrySet().fastIterator(); + while (iterator.hasNext()) { + ParticleEmitter emitter = iterator.next().getValue(); + try { + if (emitter.isRemoved() || emitter.level.dimension() != player.clientLevel.dimension()) { + emitter.onRemove(); + removeEmitterNoUpdate(emitter); + iterator.remove(); + } else if (Mth.lengthSquared( + emitter.getPosition().x - player.getX(), + emitter.getPosition().z - player.getZ() + ) < renderDistSqr) { + emitter.tick(); + } + } catch (Throwable e) { + ParticleStorm.LOGGER.warn("Error ticking emitter: {}", e.getMessage()); + e.printStackTrace(); + if (emitter != null) { + removeEmitterNoUpdate(emitter); + } + iterator.remove(); + } + } + } + if (!tracker.isEmpty()) { + var iterator = tracker.object2ObjectEntrySet().fastIterator(); + while (iterator.hasNext()) { + Map.Entry> entry = iterator.next(); + if (entry.getKey().isRemoved()) { + iterator.remove(); + } else if (entry.getValue().removeIf(ParticleEmitter::isRemoved) && entry.getValue().isEmpty()) { + iterator.remove(); + } + } + } + if (!particlesToAdd.isEmpty()) { + for (IMolangParticleInstance instance : particlesToAdd) { + particlesForEmitter.computeIfAbsent(instance.getEmitter().id, i -> new ArrayDeque<>()).add(instance); + groupedParticles.computeIfAbsent(instance.self().getRenderType(), o -> new ArrayDeque<>()).add(instance); + } + particlesToAdd.clear(); + } + if (!groupedParticles.isEmpty()) { + var iterator = groupedParticles.reference2ObjectEntrySet().fastIterator(); + while (iterator.hasNext()) { + iterator.next().getValue().removeIf(instance -> { + try { + instance.self().tick(); + return instance.isDiscarded(); + } catch (Throwable e) { + ParticleStorm.LOGGER.warn("Error ticking particle: {}", e.getMessage()); + e.printStackTrace(); + instance.discard(); + return true; + } + }); + } + } + if (!particlesForEmitter.isEmpty()) { + var iterator = particlesForEmitter.int2ObjectEntrySet().fastIterator(); + while (iterator.hasNext()) { + iterator.next().getValue().removeIf(IMolangParticleInstance::isDiscarded); + } + } + } + + public void renderParticles(TextureManager textureManager, Camera camera, float partialTick, Frustum frustum, Predicate renderTypePredicate) { + if (groupedParticles.isEmpty()) return; + Tesselator tesselator = Tesselator.getInstance(); + var iterator = groupedParticles.reference2ObjectEntrySet().fastIterator(); + while (iterator.hasNext()) { + var entry = iterator.next(); + ParticleRenderType type = entry.getKey(); + if (type == ParticleRenderType.NO_RENDER || !renderTypePredicate.test(type)) continue; + Queue queue = entry.getValue(); + if (queue.isEmpty()) continue; + + RenderSystem.setShader(!ParticleStorm.IRIS_LOADED && type == PSGameClient.PARTICLE_BLEND + ? PSGameClient::getParticleNoDiscardShader + : GameRenderer::getParticleShader); + BufferBuilder builder = type.begin(tesselator, textureManager); + if (builder == null) continue; + + for (IMolangParticleInstance instance : queue) { + if (instance.isVisible(camera, frustum, partialTick)) { + try { + instance.self().render(builder, camera, partialTick); + } catch (Throwable e) { + CrashReport report = CrashReport.forThrowable(e, "Rendering Molang Particle"); + CrashReportCategory category = report.addCategory("Molang Particle being rendered"); + category.setDetail("Particle Id", () -> instance.getEmitter().particleId.toString()); + throw new ReportedException(report); + } + } + } + MeshData mesh = builder.build(); + if (mesh != null) { + BufferUploader.drawWithShader(mesh); + } + } + } + + public ObjectIterator> getEmitters() { + return emitters.int2ObjectEntrySet().fastIterator(); + } + + public int totalEmitterCount() { + return emitters.size(); + } + + public int totalParticleCount() { + return particlesForEmitter.values().stream().mapToInt(Queue::size).sum(); + } + + public void loadEmitter(Level level, int id, CompoundTag tag) { + ParticleEmitter emitter = RegisterCustomEmitterTypeEvent.create(level, tag); + emitter.id = id; + emitters.put(id, emitter); + if (allocator.forceAllocate(id)) { + ParticleStorm.LOGGER.warn("There was an emitter exist before, now replaced"); + } + } + + public void addEmitter(ParticleEmitter emitter, boolean sync) { + emitter.id = allocator.allocate(); + emitters.put(emitter.id, emitter); + if (sync) EmitterSynchronizePacket.syncToServer(emitter); + } + + public void addEmitter(ParticleEmitter emitter) { + addEmitter(emitter, false); + } + + 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); + emitter.attachEntity(entity); + queue.add(emitter); + return true; + } + + public void addParticle(IMolangParticleInstance instance) { + Optional optional = instance.self().getParticleGroup(); + if (optional.isPresent()) { + Queue queue = particlesForEmitter.get(instance.getEmitter().id); + if (queue == null || queue.size() < optional.get().getLimit()) { + particlesToAdd.add(instance); + } + } else { + particlesToAdd.add(instance); + } + } + + public @Nullable Queue getParticlesForEmitter(ParticleEmitter emitter) { + return particlesForEmitter.get(emitter.id); + } + + private void removeEmitterNoUpdate(ParticleEmitter emitter) { + emitter.remove(); + allocator.release(emitter.id); + particlesForEmitter.remove(emitter.id); + } + + public void removeEmitter(ParticleEmitter emitter, boolean sync) { + removeEmitter(emitter.id, sync); + } + + public @Nullable ParticleEmitter removeEmitter(int id, boolean sync) { + ParticleEmitter removed = emitters.remove(id); + if (removed != null) { + removed.onRemove(); + } + allocator.release(id); + particlesForEmitter.remove(id); + if (sync) EmitterRemovalPacket.sendToServer(id); + return removed; + } + + public void removeAll() { + if (!emitters.isEmpty()) { + ObjectIterator> iterator = emitters.int2ObjectEntrySet().fastIterator(); + while (iterator.hasNext()) { + iterator.next().getValue().remove(); + iterator.remove(); + } + } + tracker.clear(); + particlesForEmitter.clear(); + particlesToAdd.clear(); + groupedParticles.clear(); + allocator.clear(); + } + + public boolean contains(int id) { + return allocator.isAllocated(id); + } + + public @Nullable ParticleEmitter getEmitter(int id) { + return emitters.get(id); + } + + @Override + 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 -> { + 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(() -> { + try (Reader reader = entry.getValue().openAsReader()) { + return DefinedParticleEffect.CODEC.parse(JsonOps.INSTANCE, GsonHelper.parse(reader).get("particle_effect")).getOrThrow(JsonParseException::new); + } catch (IOException exception) { + throw new IllegalStateException("Failed to load definition for particle " + id, exception); + } + }, backgroundExecutor)); + } + return Util.sequence(list); + }).thenCompose(preparationBarrier::wait).thenAcceptAsync(effects -> { + ImmutableMap.Builder id2Effect = ImmutableMap.builder(); + ImmutableMap.Builder id2Particle = ImmutableMap.builder(); + ImmutableMap.Builder id2Emitter = ImmutableMap.builder(); + for (DefinedParticleEffect effect : effects) { + ResourceLocation id = effect.description.identifier(); + id2Effect.put(id, effect); + id2Particle.put(id, new ParticlePreset(effect)); + id2Emitter.put(id, new EmitterPreset( + effect.description.type(), + effect.orderedEmitterComponents, + effect.events + )); + } + this.id2Effect = id2Effect.build(); + this.id2Particle = id2Particle.build(); + this.id2Emitter = id2Emitter.build(); + this.initialized = false; + ModLoader.postEvent(new MolangParticleLoadEvent.Post(gameExecutor)); + }, gameExecutor); + } +} diff --git a/src/main/java/org/mesdag/particlestorm/particle/MolangParticleInstance.java b/src/main/java/org/mesdag/particlestorm/particle/MolangParticleInstance.java index 599e64c..5ada2e2 100644 --- a/src/main/java/org/mesdag/particlestorm/particle/MolangParticleInstance.java +++ b/src/main/java/org/mesdag/particlestorm/particle/MolangParticleInstance.java @@ -5,16 +5,18 @@ import net.minecraft.client.multiplayer.ClientLevel; import net.minecraft.client.particle.ParticleRenderType; import net.minecraft.client.particle.TextureSheetParticle; +import net.minecraft.client.renderer.culling.Frustum; import net.minecraft.client.renderer.texture.TextureAtlasSprite; import net.minecraft.core.particles.ParticleGroup; 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.AABB; import net.minecraft.world.phys.Vec3; -import net.neoforged.fml.ModList; import org.joml.Quaternionf; import org.joml.Vector3f; +import org.mesdag.particlestorm.ParticleStorm; import org.mesdag.particlestorm.api.IEventNode; import org.mesdag.particlestorm.api.IMolangParticleInstance; import org.mesdag.particlestorm.api.IParticleComponent; @@ -26,9 +28,6 @@ import java.util.Optional; public class MolangParticleInstance extends TextureSheetParticle implements IMolangParticleInstance { - public static final int FULL_LIGHT = 0xF000F0; - private static final boolean isSodiumLoaded = ModList.get().isLoaded("sodium"); - protected final ParticlePreset preset; protected ParticleVariableTable vars; protected final float originX; @@ -37,20 +36,20 @@ public class MolangParticleInstance extends TextureSheetParticle implements IMol 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; - 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; + protected float xRot; + protected float yRot; + protected float xRotO; + protected float yRotO; + protected float rolld; + protected boolean hasCollision; + protected float collisionDrag; + protected float coefficientOfRestitution; + protected boolean expireOnContact; + + protected final float particleRandom1; + protected final float particleRandom2; + protected final float particleRandom3; + protected final float particleRandom4; protected List components; protected ParticleEmitter emitter; @@ -60,16 +59,17 @@ public class MolangParticleInstance extends TextureSheetParticle implements IMol protected float[] uvSize; protected float[] uvStep; protected int maxFrame = 1; - protected int currentFrame = 0; + protected int currentFrame; protected float[] UV; protected boolean insideKillPlane; protected ParticleGroup particleGroup; - protected int lastTimeline = 0; + protected int lastTimeline; public MolangParticleInstance(ParticlePreset preset, ClientLevel level, double x, double y, double z, ExtendMutableSpriteSet sprites) { super(level, x, y, z); this.friction = 1.0F; + this.quadSize = 0; // as collision radius this.preset = preset; setSprite(sprites.get(preset.effect.description.parameters().getTextureIndex())); this.originX = ((ITextureAtlasSprite) sprite).particlestorm$getOriginX(); @@ -78,10 +78,10 @@ public MolangParticleInstance(ParticlePreset preset, ClientLevel level, double x 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(); + this.particleRandom1 = random.nextFloat(); + this.particleRandom2 = random.nextFloat(); + this.particleRandom3 = random.nextFloat(); + this.particleRandom4 = random.nextFloat(); } @Override @@ -160,6 +160,16 @@ public void setExpireOnContact(boolean b) { this.expireOnContact = b; } + @Override + public void setCollisionRadius(float radius) { + this.quadSize = radius; + } + + @Override + public float getCollisionRadius() { + return quadSize; + } + @Override public void setComponents(List components) { this.components = components; @@ -313,6 +323,16 @@ public void setCollision(boolean bool) { this.hasCollision = bool; } + @Override + public void discard() { + remove(); + } + + @Override + public boolean isDiscarded() { + return removed; + } + @Override public VariableTable getVars() { return vars; @@ -324,22 +344,22 @@ public Level getLevel() { } @Override - public double getRandom1() { + public float getRandom1() { return particleRandom1; } @Override - public double getRandom2() { + public float getRandom2() { return particleRandom2; } @Override - public double getRandom3() { + public float getRandom3() { return particleRandom3; } @Override - public double getRandom4() { + public float getRandom4() { return particleRandom4; } @@ -380,8 +400,40 @@ public void tick() { } } - private static final Quaternionf quaternionf = new Quaternionf(); + protected static final Quaternionf quaternionf = new Quaternionf(); + protected static final Vector3f vector3f = new Vector3f(); + + // 在render前调用 + @Override + public boolean isVisible(Camera camera, Frustum frustum, float partialTick) { + Vec3 camPos = camera.getPosition(); + if (emitter.isLocalSpace()) { + emitter.local2World(vector3f.set( + (float) Mth.lerp(partialTick, xo, x), + (float) Mth.lerp(partialTick, yo, y), + (float) Mth.lerp(partialTick, zo, z) + ), partialTick); + float size = Math.max(billboardSize[0], billboardSize[1]); + boolean inFrustum = frustum.cubeInFrustum( + vector3f.x - size, + vector3f.y - size, + vector3f.z - size, + vector3f.x + size, + vector3f.y + size, + vector3f.z + size + ); + vector3f.sub((float) camPos.x, (float) camPos.y, (float) camPos.z); + return inFrustum; + } + vector3f.set( + (float) (Mth.lerp(partialTick, xo, x) - camPos.x), + (float) (Mth.lerp(partialTick, yo, y) - camPos.y), + (float) (Mth.lerp(partialTick, zo, z) - camPos.z) + ); + return IMolangParticleInstance.super.isVisible(camera, frustum, partialTick); + } + // 在isVisible后调用 @Override public void render(VertexConsumer buffer, Camera camera, float partialTicks) { quaternionf.identity(); @@ -389,12 +441,12 @@ public void render(VertexConsumer buffer, Camera camera, float 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, camera, quaternionf, partialTicks); + renderRotatedQuad(buffer, quaternionf, vector3f.x, vector3f.y, vector3f.z, partialTicks); } @Override protected void renderRotatedQuad(VertexConsumer buffer, Quaternionf quaternion, float x, float y, float z, float partialTicks) { - if (isSodiumLoaded) { + if (ParticleStorm.SODIUM_LOADED) { float f1 = getU0(); float f2 = getU1(); float f3 = getV0(); @@ -411,18 +463,34 @@ protected void renderRotatedQuad(VertexConsumer buffer, Quaternionf quaternion, @Override 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); + vector3f.set(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); } + @Override + public AABB getRenderBoundingBox(float partialTicks) { + float size = Math.max(billboardSize[0], billboardSize[1]); + return new AABB(x - size, y - size, z - size, x + size, y + size, z + size); + } + @Override public void move(double x, double y, double z) { if (stoppedByCollision) return; + double d0 = x; double d1 = y; double d2 = z; - if (hasPhysics && hasCollision && (x != 0.0 || y != 0.0 || z != 0.0) && x * x + y * y + z * z < MAXIMUM_COLLISION_VELOCITY_SQUARED) { - Vec3 vec3 = Entity.collideBoundingBox(null, new Vec3(x, y, z), getBoundingBox(), level, List.of()); + if (hasPhysics && hasCollision && (x != 0.0 || y != 0.0 || z != 0.0) && Mth.lengthSquared(x, y, z) < MAXIMUM_COLLISION_VELOCITY_SQUARED) { + AABB aabb = getBoundingBox(); + if (emitter.isLocalSpace()) { + emitter.local2World(vector3f.set(aabb.minX, aabb.minY, aabb.minZ), 1); + float mx = vector3f.x; + float my = vector3f.y; + float mz = vector3f.z; + emitter.local2World(vector3f.set(aabb.maxX, aabb.maxY, aabb.maxZ), 1); + aabb = new AABB(mx, my, mz, vector3f.x, vector3f.y, vector3f.z); + } + Vec3 vec3 = Entity.collideBoundingBox(null, new Vec3(x, y, z), aabb, level, List.of()); if (x != vec3.x) { this.xd = -Mth.sign(xd) * (Math.abs(xd) - collisionDrag) * coefficientOfRestitution; } @@ -447,13 +515,12 @@ public void move(double x, double y, double z) { if (hasPhysics && hasCollision) { this.onGround = d1 != y && d1 < 0.0; - boolean collided = d0 != x || d2 != z; - if (onGround || collided) { + if (onGround || (d0 != x || d2 != z)) { if (!preset.collisionEvents.isEmpty()) { for (ParticleMotionCollision.Event event : preset.collisionEvents) { float tickSpeed = event.minSpeed() * getInvTickRate(); - if (tickSpeed * tickSpeed < xd * xd + yd * yd + zd * zd) { + if (tickSpeed * tickSpeed < Mth.lengthSquared(xd, yd, zd)) { for (IEventNode node : preset.effect.events.get(event.event()).values()) { node.execute(this); } @@ -487,7 +554,7 @@ public FaceCameraMode getFacingCameraMode() { @Override protected int getLightColor(float partialTick) { - return preset.environmentLighting ? super.getLightColor(partialTick) : FULL_LIGHT; + return preset.environmentLighting ? super.getLightColor(partialTick) : 0xF000F0; } @Override diff --git a/src/main/java/org/mesdag/particlestorm/particle/MolangParticleLoader.java b/src/main/java/org/mesdag/particlestorm/particle/MolangParticleLoader.java deleted file mode 100644 index 7165583..0000000 --- a/src/main/java/org/mesdag/particlestorm/particle/MolangParticleLoader.java +++ /dev/null @@ -1,220 +0,0 @@ -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; -import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.objects.Object2ObjectMap; -import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.objects.ObjectIterator; -import net.minecraft.Util; -import net.minecraft.client.Minecraft; -import net.minecraft.client.player.LocalPlayer; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.resources.FileToIdConverter; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.server.packs.resources.PreparableReloadListener; -import net.minecraft.server.packs.resources.Resource; -import net.minecraft.server.packs.resources.ResourceManager; -import net.minecraft.util.GsonHelper; -import net.minecraft.util.Mth; -import net.minecraft.util.profiling.ProfilerFiller; -import net.minecraft.world.entity.Entity; -import net.minecraft.world.level.Level; -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.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<>(); - private final Int2ObjectOpenHashMap emitters = new Int2ObjectOpenHashMap<>(); - private final Object2ObjectMap> tracker = new Object2ObjectOpenHashMap<>(); - private final IntAllocator allocator = new IntAllocator(); - - private boolean initialized = false; - - public Map id2Effect() { - return id2Effect; - } - - public Map id2Particle() { - return id2Particle; - } - - public Map id2Emitter() { - return id2Emitter; - } - - public void tick(LocalPlayer localPlayer) { - 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()) { - 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(); - } - } catch (Exception e) { - ParticleStorm.LOGGER.warn("Error ticking: {}", e.getMessage()); - e.printStackTrace(); - if (emitter != null) { - emitter.remove(); - } - iterator.remove(); - } - } - } - 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(); - } - } - } - } - - public Iterable getEmitters() { - return emitters.values(); - } - - public int totalEmitterCount() { - return emitters.size(); - } - - public void loadEmitter(Level level, int id, CompoundTag tag) { - ParticleEmitter emitter = RegisterCustomEmitterTypeEvent.create(level, tag); - emitter.id = id; - emitters.put(id, emitter); - if (allocator.forceAllocate(id)) { - ParticleStorm.LOGGER.warn("There was an emitter exist before, now replaced"); - } - } - - public void addEmitter(ParticleEmitter emitter, boolean sync) { - emitter.id = allocator.allocate(); - emitters.put(emitter.id, emitter); - if (sync) EmitterSynchronizePacket.syncToServer(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); - emitter.attachEntity(entity); - queue.add(emitter); - return true; - } - - public void removeEmitter(ParticleEmitter emitter, boolean sync) { - removeEmitter(emitter.id, sync); - } - - public ParticleEmitter removeEmitter(int id, boolean sync) { - ParticleEmitter removed = emitters.remove(id); - if (removed != null) { - removed.onRemove(); - } - allocator.release(id); - if (sync) EmitterRemovalPacket.sendToServer(id); - return removed; - } - - public void removeAll() { - if (!emitters.isEmpty()) { - ObjectIterator> iterator = emitters.int2ObjectEntrySet().iterator(); - while (iterator.hasNext()) { - iterator.next().getValue().remove(); - iterator.remove(); - } - } - tracker.clear(); - allocator.clear(); - } - - public boolean contains(int id) { - return allocator.isAllocated(id); - } - - public @Nullable ParticleEmitter getEmitter(int id) { - return emitters.get(id); - } - - @Override - 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 -> { - 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(() -> { - try (Reader reader = entry.getValue().openAsReader()) { - return DefinedParticleEffect.CODEC.parse(JsonOps.INSTANCE, GsonHelper.parse(reader).get("particle_effect")).getOrThrow(JsonParseException::new); - } catch (IOException exception) { - throw new IllegalStateException("Failed to load definition for particle " + id, exception); - } - }, backgroundExecutor)); - } - return Util.sequence(list); - }).thenCompose(preparationBarrier::wait).thenAcceptAsync(effects -> { - Map id2Effect = new Hashtable<>(); - Map id2Particle = new Hashtable<>(); - Map id2Emitter = new Hashtable<>(); - for (DefinedParticleEffect effect : effects) { - ResourceLocation id = effect.description.identifier(); - id2Effect.put(id, effect); - id2Particle.put(id, new ParticlePreset(effect)); - id2Emitter.put(id, new EmitterPreset( - effect.description.type(), - effect.orderedEmitterComponents, - effect.events - )); - } - this.id2Effect = id2Effect; - 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/ParticleEmitter.java b/src/main/java/org/mesdag/particlestorm/particle/ParticleEmitter.java index 097aff6..5f2bc8f 100644 --- a/src/main/java/org/mesdag/particlestorm/particle/ParticleEmitter.java +++ b/src/main/java/org/mesdag/particlestorm/particle/ParticleEmitter.java @@ -6,13 +6,13 @@ import net.minecraft.util.Mth; import net.minecraft.util.RandomSource; import net.minecraft.world.entity.Entity; -import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.phys.Vec3; +import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.Nullable; +import org.joml.Matrix4f; import org.joml.Vector3f; -import org.mesdag.particlestorm.PSGameClient; import org.mesdag.particlestorm.api.IEmitterComponent; import org.mesdag.particlestorm.api.MolangInstance; import org.mesdag.particlestorm.data.MathHelper; @@ -34,49 +34,47 @@ public class ParticleEmitter implements MolangInstance { public ResourceLocation particleId; public MolangExp expression; - public transient ParentMode parentMode = ParentMode.WORLD; - public transient Vec3 offsetPos = Vec3.ZERO; - public transient Vector3f offsetRot = new Vector3f(); - public transient Vector3f parentPosition; - public transient Vector3f parentRotation; + public transient Matrix4f parentSpace; + protected transient EmitterPreset preset; protected transient VariableTable vars; protected transient List components; public transient ParticleEmitter parent; public transient @Nullable Runnable afterParentInit; - public transient final List children = new ArrayList<>(); + protected transient @Nullable List children; public transient Vector3f inheritedParticleSpeed; public transient boolean isManual; - protected double emitterRandom1; - protected double emitterRandom2; - protected double emitterRandom3; - protected double emitterRandom4; + protected float emitterRandom1; + protected float emitterRandom2; + protected float emitterRandom3; + protected float emitterRandom4; public int id; public transient float invTickRate; - public transient int age = 0; - public transient int lifetime = 0; + public transient int age; + public transient int lifetime; public transient boolean active = true; - public transient int loopingTime = 0; - public transient int activeTime = 0; - public transient int fullLoopTime = 0; + public transient int loopingTime; + public transient int activeTime; + public transient int fullLoopTime; public transient MutableParticleGroup particleGroup; public transient int spawnDuration = 1; - public transient int spawnRate = 0; - public transient boolean spawned = false; + public transient int spawnRate; + public transient boolean spawned; protected transient Entity attached; public transient BlockEntity attachedBlock; - public transient int lastTimeline = 0; - public transient float moveDist = 0.0F; - public transient float moveDistO = 0.0F; - public transient int lastTravelDist = 0; + public transient int lastTimeline; + public transient float moveDist; + public transient float moveDistO; + public transient int lastTravelDist; public transient float[] cachedLooping; public transient final Level level; - public Vec3 pos; + protected Vec3 pos; public Vec3 posO = Vec3.ZERO; - public Vector3f rot = new Vector3f(); + protected final Vector3f rotated = new Vector3f(); + public boolean hideOutline; private transient boolean removed = false; public ParticleEmitter(Level level, Vec3 pos, ResourceLocation particleId, MolangExp expression) { @@ -115,11 +113,7 @@ public ParticleEmitter(ParticleEmitter parent, ParticleEffect effect) { 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; + this.parentSpace = parent.parentSpace; } case PARTICLE -> this.isManual = true; case PARTICLE_WITH_VELOCITY -> { @@ -160,7 +154,7 @@ protected void init() { } protected void createVars() { - this.preset = PSGameClient.LOADER.id2Emitter().get(particleId); + this.preset = MolangParticleEngine.INSTANCE.id2Emitter().get(particleId); if (preset == null) { throw new IllegalArgumentException("Unknown particle id: '" + particleId + "'!"); } @@ -188,10 +182,10 @@ protected void createComponents() { } public synchronized void updateRandoms(RandomSource random) { - this.emitterRandom1 = random.nextDouble(); - this.emitterRandom2 = random.nextDouble(); - this.emitterRandom3 = random.nextDouble(); - this.emitterRandom4 = random.nextDouble(); + this.emitterRandom1 = random.nextFloat(); + this.emitterRandom2 = random.nextFloat(); + this.emitterRandom3 = random.nextFloat(); + this.emitterRandom4 = random.nextFloat(); } public void tick() { @@ -214,12 +208,10 @@ public void tick() { remove(); return; } - if (parentRotation != null) { - rot.set(parentRotation).add(offsetRot.x, offsetRot.y + getAttachedYRot() * Mth.DEG_TO_RAD, offsetRot.z); - } - Vector3f rotated = offsetPos.toVector3f().rotateZ(rot.z).rotateY(rot.y).rotateX(rot.x); - if (parentPosition != null) { - rotated.add(parentPosition); + if (isLocalSpace()) { + rotated.set(parentSpace.m30(), parentSpace.m31(), parentSpace.m32()); + } else { + rotated.set(0); } this.pos = new Vec3(attached.getX() + rotated.x, attached.getY() + rotated.y, attached.getZ() + rotated.z); } else if (attachedBlock != null) { @@ -227,15 +219,13 @@ public void tick() { remove(); return; } - if (parentRotation != null) { - rot.set(parentRotation).add(offsetRot); - } - Vector3f rotated = offsetPos.toVector3f().rotateZ(rot.z).rotateY(rot.y).rotateX(rot.x); - if (parentPosition != null) { - rotated.add(parentPosition); + if (isLocalSpace()) { + rotated.set(parentSpace.m30(), parentSpace.m31(), parentSpace.m32()); + } else { + rotated.set(0); } - BlockPos pos1 = attachedBlock.getBlockPos(); - this.pos = new Vec3(pos1.getX() + 0.5 + rotated.x, pos1.getY() + rotated.y, pos1.getZ() + 0.5 + rotated.z); + BlockPos bp = attachedBlock.getBlockPos(); + this.pos = new Vec3(bp.getX() + 0.5 + rotated.x, bp.getY() + rotated.y, bp.getZ() + 0.5 + rotated.z); } if (afterParentInit != null && parent != null) { @@ -250,27 +240,54 @@ public void tick() { } } - private float getAttachedYRot() { - return attached instanceof LivingEntity living ? -living.yBodyRot : attached.getYRot(); + public void local2World(Vector3f vec, float partialTick) { + if (isLocalSpace()) { + if (preset.localRotation) { + if (parentSpace != null) { + vec.mulDirection(parentSpace); + } + } + if (preset.localPosition) { + vec.add( + (float) Mth.lerp(partialTick, posO.x, pos.x), + (float) Mth.lerp(partialTick, posO.y, pos.y), + (float) Mth.lerp(partialTick, posO.z, pos.z) + ); + } + } + } + + public boolean isLocalSpace() { + return parentSpace != null; } public void remove() { this.removed = true; } + @Contract(value = "true -> !null", pure = true) + public @Nullable List getChildren(boolean create) { + if (create && children == null) { + this.children = new ArrayList<>(); + } + return children; + } + public void onRemove() { - children.removeIf(child -> { - child.parent = null; - child.remove(); - return true; - }); + if (children != null) { + children.removeIf(child -> { + child.parent = null; + child.remove(); + return true; + }); + } if (preset != null && preset.lifetimeEvents != null) { preset.lifetimeEvents.onExpiration(this); } } public void addParent(ParticleEmitter parent) { - parent.children.add(this); + parent.getChildren(true).add(this); this.parent = parent; } @@ -286,30 +303,28 @@ public EmitterPreset getPreset() { return preset; } - public void deserialize(CompoundTag compound) { - this.particleId = ResourceLocation.parse(compound.getString("particleId")); - this.expression = new MolangExp(compound.getString("expression")); - this.emitterRandom1 = compound.getDouble("emitterRandom1"); - this.emitterRandom2 = compound.getDouble("emitterRandom2"); - this.emitterRandom3 = compound.getDouble("emitterRandom3"); - this.emitterRandom4 = compound.getDouble("emitterRandom4"); - 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")); - } - - public void serialize(CompoundTag compound) { - compound.putString("particleId", particleId.toString()); - compound.putString("expression", expression.getExpStr()); - compound.putDouble("emitterRandom1", emitterRandom1); - compound.putDouble("emitterRandom2", emitterRandom2); - compound.putDouble("emitterRandom3", emitterRandom3); - compound.putDouble("emitterRandom4", emitterRandom4); - compound.putDouble("posX", pos.x); - compound.putDouble("posY", pos.y); - compound.putDouble("posZ", pos.z); - compound.putFloat("rotX", rot.x); - compound.putFloat("rotY", rot.y); - compound.putFloat("rotZ", rot.z); + public void deserialize(CompoundTag tag) { + this.particleId = ResourceLocation.parse(tag.getString("particleId")); + this.expression = new MolangExp(tag.getString("expression")); + this.emitterRandom1 = tag.getFloat("emitterRandom1"); + this.emitterRandom2 = tag.getFloat("emitterRandom2"); + this.emitterRandom3 = tag.getFloat("emitterRandom3"); + this.emitterRandom4 = tag.getFloat("emitterRandom4"); + this.posO = this.pos = new Vec3(tag.getDouble("posX"), tag.getDouble("posY"), tag.getDouble("posZ")); + this.hideOutline = tag.getBoolean("hideOutline"); + } + + public void serialize(CompoundTag tag) { + tag.putString("particleId", particleId.toString()); + tag.putString("expression", expression.getExpStr()); + tag.putDouble("emitterRandom1", emitterRandom1); + tag.putDouble("emitterRandom2", emitterRandom2); + tag.putDouble("emitterRandom3", emitterRandom3); + tag.putDouble("emitterRandom4", emitterRandom4); + tag.putDouble("posX", pos.x); + tag.putDouble("posY", pos.y); + tag.putDouble("posZ", pos.z); + tag.putBoolean("hideOutline", hideOutline); } public double getX() { @@ -345,22 +360,22 @@ public float tickLifetime() { } @Override - public double getRandom1() { + public float getRandom1() { return emitterRandom1; } @Override - public double getRandom2() { + public float getRandom2() { return emitterRandom2; } @Override - public double getRandom3() { + public float getRandom3() { return emitterRandom3; } @Override - public double getRandom4() { + public float getRandom4() { return emitterRandom4; } @@ -388,9 +403,4 @@ public float getInvTickRate() { public ParticleEmitter getEmitter() { return this; } - - public enum ParentMode { - LOCATOR, - WORLD - } } diff --git a/src/main/java/org/mesdag/particlestorm/particle/ParticlePreset.java b/src/main/java/org/mesdag/particlestorm/particle/ParticlePreset.java index 2efaa03..4487943 100644 --- a/src/main/java/org/mesdag/particlestorm/particle/ParticlePreset.java +++ b/src/main/java/org/mesdag/particlestorm/particle/ParticlePreset.java @@ -13,6 +13,7 @@ import org.mesdag.particlestorm.data.MathHelper; import org.mesdag.particlestorm.data.component.*; import org.mesdag.particlestorm.data.curve.ParticleCurve; +import org.mesdag.particlestorm.data.description.DescriptionMaterial; import org.mesdag.particlestorm.data.molang.FloatMolangExp; import org.mesdag.particlestorm.data.molang.MolangExp; import org.mesdag.particlestorm.data.molang.VariableTable; @@ -36,6 +37,7 @@ public class ParticlePreset { public float invTextureWidth; public float invTextureHeight; public boolean motionDynamic; + public @Nullable FloatMolangExp perUpdateExpression; public VariableTable vars; public List assignments; @@ -45,15 +47,24 @@ public class ParticlePreset { public ParticlePreset(DefinedParticleEffect effect) { this.effect = effect; - this.renderType = switch (effect.description.parameters().material()) { - case TERRAIN_SHEET -> ParticleRenderType.TERRAIN_SHEET; - case particles_opaque, PARTICLE_SHEET_OPAQUE -> ParticleRenderType.PARTICLE_SHEET_OPAQUE; - case particles_add -> PSGameClient.PARTICLE_ADD; - case particles_blend, PARTICLE_SHEET_TRANSLUCENT -> ParticleRenderType.PARTICLE_SHEET_TRANSLUCENT; - case particles_alpha, PARTICLE_SHEET_LIT -> ParticleRenderType.PARTICLE_SHEET_LIT; - case CUSTOM -> ParticleRenderType.CUSTOM; - default -> ParticleRenderType.NO_RENDER; - }; + DescriptionMaterial material = effect.description.parameters().material(); + if (material == DescriptionMaterial.TERRAIN_SHEET) { + this.renderType = ParticleRenderType.TERRAIN_SHEET; + } else if (material == DescriptionMaterial.particles_opaque || material == DescriptionMaterial.PARTICLE_SHEET_OPAQUE) { + this.renderType = ParticleRenderType.PARTICLE_SHEET_OPAQUE; + } else if (material == DescriptionMaterial.particles_add) { + this.renderType = PSGameClient.PARTICLE_ADD; + } else if (material == DescriptionMaterial.particles_blend) { + this.renderType = PSGameClient.PARTICLE_BLEND; + } else if (material == DescriptionMaterial.PARTICLE_SHEET_TRANSLUCENT) { + this.renderType = ParticleRenderType.PARTICLE_SHEET_TRANSLUCENT; + } else if (material == DescriptionMaterial.particles_alpha || material == DescriptionMaterial.PARTICLE_SHEET_LIT) { + this.renderType = ParticleRenderType.PARTICLE_SHEET_LIT; + } else if (material == DescriptionMaterial.CUSTOM) { + this.renderType = ParticleRenderType.CUSTOM; + } else { + this.renderType = ParticleRenderType.NO_RENDER; + } if (effect.components.get(ParticleAppearanceBillboard.ID) instanceof ParticleAppearanceBillboard component) { this.facingCameraMode = FaceCameraMode.fromComponent(component.faceCameraMode()); this.minSpeedThresholdSqr = Mth.square(component.direction().minSpeedThreshold()); @@ -70,6 +81,8 @@ public ParticlePreset(DefinedParticleEffect effect) { ParticleMotionCollision motionCollision = (ParticleMotionCollision) effect.components.get(ParticleMotionCollision.ID); if (motionCollision != null) this.collisionEvents = motionCollision.events(); this.motionDynamic = effect.components.get(ParticleMotionDynamic.ID) != null; + ParticleInitialization initialization = (ParticleInitialization) effect.components.get(ParticleInitialization.ID); + if (initialization != null) this.perUpdateExpression = initialization.perUpdateExpression(); VariableTable table = new VariableTable(addDefaultVariables(), null); MolangParser parser = new MolangParser(table); @@ -99,7 +112,7 @@ public ParticlePreset(DefinedParticleEffect effect) { } this.vars = table; this.assignments = toInit; - NeoForge.EVENT_BUS.post(new ParticlePresetLoadedEvent(this)); + NeoForge.EVENT_BUS.post(new ParticlePresetLoadedEvent(effect, this)); } public void setTicket(Class clazz, T value) { diff --git a/src/main/java/org/mesdag/particlestorm/particle/ParticleVariableTable.java b/src/main/java/org/mesdag/particlestorm/particle/ParticleVariableTable.java index 19674ee..20c7b2a 100644 --- a/src/main/java/org/mesdag/particlestorm/particle/ParticleVariableTable.java +++ b/src/main/java/org/mesdag/particlestorm/particle/ParticleVariableTable.java @@ -13,7 +13,7 @@ public ParticleVariableTable(VariableTable preset, VariableTable emitter) { } @Override - public double getValue(String name, MolangInstance instance) { + public float getValue(String name, MolangInstance instance) { Variable variable = table.get(name); if (variable == null) { variable = parent.table.get(name); // 预设表没有父级 diff --git a/src/main/resources/META-INF/accesstransformer.cfg b/src/main/resources/META-INF/accesstransformer.cfg index 083a960..b255a7d 100644 --- a/src/main/resources/META-INF/accesstransformer.cfg +++ b/src/main/resources/META-INF/accesstransformer.cfg @@ -5,5 +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 +public net.minecraft.client.renderer.culling.Frustum cubeInFrustum(DDDDDD)Z diff --git a/src/main/resources/META-INF/neoforge.mods.toml b/src/main/resources/META-INF/neoforge.mods.toml index 8f1b199..646dc83 100644 --- a/src/main/resources/META-INF/neoforge.mods.toml +++ b/src/main/resources/META-INF/neoforge.mods.toml @@ -47,8 +47,8 @@ config="${mod_id}.mixins.json" # The [[accessTransformers]] block allows you to declare where your AT file is. # If this block is omitted, a fallback attempt will be made to load an AT from META-INF/accesstransformer.cfg -#[[accessTransformers]] -#file="META-INF/accesstransformer.cfg" +[[accessTransformers]] +file="META-INF/accesstransformer.cfg" # The coremods config file path is not configurable and is always loaded from META-INF/coremods.json diff --git a/src/main/resources/assets/particlestorm/animations/block/test_block.animation.json b/src/main/resources/assets/particlestorm/animations/block/test_block.animation.json index 79ba2ec..b9dba1e 100644 --- a/src/main/resources/assets/particlestorm/animations/block/test_block.animation.json +++ b/src/main/resources/assets/particlestorm/animations/block/test_block.animation.json @@ -3,12 +3,13 @@ "animations": { "animation.test_block.new": { "loop": true, - "animation_length": 2, + "animation_length": 4, "bones": { "bone": { - "rotation": { - "vector": [0, "math.sin(query.anim_time * 90) * 180", 0] - } + "rotation": [0, "-query.anim_time * 90", 0] + }, + "bone2": { + "rotation": [0, "-query.anim_time * 90", 0] } }, "particle_effects": { diff --git a/src/main/resources/assets/particlestorm/geo/block/test_block.geo.json b/src/main/resources/assets/particlestorm/geo/block/test_block.geo.json index d5b21b5..33f5a80 100644 --- a/src/main/resources/assets/particlestorm/geo/block/test_block.geo.json +++ b/src/main/resources/assets/particlestorm/geo/block/test_block.geo.json @@ -47,8 +47,29 @@ } ], "locators": { - "locator": [-3, 6, -3], - "locator2": [3, 12, 3] + "locator": [-3, 6, -3] + } + }, + { + "name": "bone2", + "parent": "bone", + "pivot": [0, 0, -8], + "cubes": [ + { + "origin": [-1, 0, -15], + "size": [2, 2, 2], + "uv": { + "north": {"uv": [0, 0], "uv_size": [2, 2]}, + "east": {"uv": [0, 0], "uv_size": [2, 2]}, + "south": {"uv": [0, 0], "uv_size": [2, 2]}, + "west": {"uv": [0, 0], "uv_size": [2, 2]}, + "up": {"uv": [2, 2], "uv_size": [-2, -2]}, + "down": {"uv": [2, 2], "uv_size": [-2, -2]} + } + } + ], + "locators": { + "locator2": [0, 1, -14] } } ] diff --git a/src/main/resources/assets/particlestorm/particle_definitions/blend.json b/src/main/resources/assets/particlestorm/particle_definitions/blend.json new file mode 100644 index 0000000..2710a9b --- /dev/null +++ b/src/main/resources/assets/particlestorm/particle_definitions/blend.json @@ -0,0 +1,51 @@ +{ + "format_version": "1.10.0", + "particle_effect": { + "description": { + "identifier": "particlestorm:blend", + "basic_render_parameters": { + "material": "particles_blend", + "texture": "particlestorm:blend" + } + }, + "components": { + "minecraft:emitter_rate_steady": { + "spawn_rate": 10, + "max_particles": 100 + }, + "minecraft:emitter_lifetime_looping": { + "active_time": 8 + }, + "minecraft:emitter_shape_point": { + "offset": [0, 0.11, 0], + "direction": ["math.random(-0.5,0.5)", "math.random(0.1,0.5)", "math.random(-0.5,0.5)"] + }, + "minecraft:particle_lifetime_expression": { + "max_lifetime": 4 + }, + "minecraft:particle_initial_speed": "math.random(2,3.5)", + "minecraft:particle_motion_dynamic": { + "linear_acceleration": [0, -0.33, 0], + "linear_drag_coefficient": 0.6 + }, + "minecraft:particle_appearance_billboard": { + "size": ["(v.particle_age / v.particle_lifetime)*0.75+1.5", "(v.particle_age / v.particle_lifetime)*0.75+1.5"], + "facing_camera_mode": "rotate_xyz" + }, + "minecraft:particle_motion_collision": { + "collision_radius": 0.1 + }, + "minecraft:particle_appearance_tinting": { + "color": { + "interpolant": "v.particle_age / v.particle_lifetime", + "gradient": { + "0.0": "#00FFFFFF", + "0.12": "#FFFFFFFF", + "0.59": "#FFFFFFFF", + "1.0": "#00FFFFFF" + } + } + } + } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/particlestorm/particle_definitions/loading.particle.json b/src/main/resources/assets/particlestorm/particle_definitions/loading.particle.json index 539d849..e76999b 100644 --- a/src/main/resources/assets/particlestorm/particle_definitions/loading.particle.json +++ b/src/main/resources/assets/particlestorm/particle_definitions/loading.particle.json @@ -21,15 +21,16 @@ "max_particles": 60 }, "minecraft:emitter_lifetime_looping": { - "active_time": 1 + "active_time": 10 }, "minecraft:emitter_shape_point": { - "offset": ["variable.radius*-math.sin(variable.emitter_age*360)", "variable.radius*math.cos(variable.emitter_age*360)", 0] + "offset": ["variable.radius*-math.sin(variable.emitter_age*360)", "variable.radius*math.cos(variable.emitter_age*360)", 0], + "direction": [0, 0, 1] }, "minecraft:particle_lifetime_expression": { "max_lifetime": 1 }, - "minecraft:particle_initial_speed": 0, + "minecraft:particle_initial_speed": 5, "minecraft:particle_motion_dynamic": {}, "minecraft:particle_appearance_billboard": { "size": ["variable.size*(1-variable.particle_age)", "variable.size*(1-variable.particle_age)"], diff --git a/src/main/resources/assets/particlestorm/shaders/core/particle_no_discard.fsh b/src/main/resources/assets/particlestorm/shaders/core/particle_no_discard.fsh new file mode 100644 index 0000000..464e37a --- /dev/null +++ b/src/main/resources/assets/particlestorm/shaders/core/particle_no_discard.fsh @@ -0,0 +1,24 @@ +#version 150 + +#moj_import + +uniform sampler2D Sampler0; + +uniform vec4 ColorModulator; +uniform float FogStart; +uniform float FogEnd; +uniform vec4 FogColor; + +in float vertexDistance; +in vec2 texCoord0; +in vec4 vertexColor; + +out vec4 fragColor; + +void main() { + vec4 color = texture(Sampler0, texCoord0) * vertexColor * ColorModulator; +// if (color.a < 0.1) { +// discard; +// } + fragColor = linear_fog(color, vertexDistance, FogStart, FogEnd, FogColor); +} diff --git a/src/main/resources/assets/particlestorm/shaders/core/particle_no_discard.json b/src/main/resources/assets/particlestorm/shaders/core/particle_no_discard.json new file mode 100644 index 0000000..cdb43e2 --- /dev/null +++ b/src/main/resources/assets/particlestorm/shaders/core/particle_no_discard.json @@ -0,0 +1,17 @@ +{ + "vertex": "minecraft:particle", + "fragment": "particlestorm:particle_no_discard", + "samplers": [ + { "name": "Sampler0" }, + { "name": "Sampler2" } + ], + "uniforms": [ + { "name": "ModelViewMat", "type": "matrix4x4", "count": 16, "values": [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ] }, + { "name": "ProjMat", "type": "matrix4x4", "count": 16, "values": [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ] }, + { "name": "ColorModulator", "type": "float", "count": 4, "values": [ 1.0, 1.0, 1.0, 1.0 ] }, + { "name": "FogStart", "type": "float", "count": 1, "values": [ 0.0 ] }, + { "name": "FogEnd", "type": "float", "count": 1, "values": [ 1.0 ] }, + { "name": "FogColor", "type": "float", "count": 4, "values": [ 0.0, 0.0, 0.0, 0.0 ] }, + { "name": "FogShape", "type": "int", "count": 1, "values": [ 0 ] } + ] +} diff --git a/src/main/resources/assets/particlestorm/textures/particle/blend.png b/src/main/resources/assets/particlestorm/textures/particle/blend.png index c18ce56..fe4e4a3 100644 Binary files a/src/main/resources/assets/particlestorm/textures/particle/blend.png and b/src/main/resources/assets/particlestorm/textures/particle/blend.png differ diff --git a/src/main/resources/assets/particlestorm/textures/particle/missing.png b/src/main/resources/assets/particlestorm/textures/particle/missing.png deleted file mode 100644 index a374dbf..0000000 Binary files a/src/main/resources/assets/particlestorm/textures/particle/missing.png and /dev/null differ diff --git a/src/main/resources/assets/particlestorm/textures/particle/test.png b/src/main/resources/assets/particlestorm/textures/particle/test.png deleted file mode 100644 index b063568..0000000 Binary files a/src/main/resources/assets/particlestorm/textures/particle/test.png and /dev/null differ diff --git a/src/main/resources/particlestorm.mixins.json b/src/main/resources/particlestorm.mixins.json index 03e654f..288ca50 100644 --- a/src/main/resources/particlestorm.mixins.json +++ b/src/main/resources/particlestorm.mixins.json @@ -2,10 +2,9 @@ "required": true, "minVersion": "0.8", "package": "org.mesdag.particlestorm.mixin", - "compatibilityLevel": "JAVA_17", + "compatibilityLevel": "JAVA_21", "refmap": "particlestorm.refmap.json", "mixins": [ - "integration.geckolib.MolangQueriesAccessor" ], "client": [ "BlockEntityMixin", @@ -19,6 +18,7 @@ "integration.geckolib.AnimationProcessorMixin", "integration.geckolib.BakedModelFactory$BuiltinMixin", "integration.geckolib.GeoBoneMixin", + "integration.geckolib.GeoModelMixin", "integration.geckolib.GeoReplacedEntityRendererMixin", "integration.geckolib.MolangQueriesMixin", "integration.geckolib.ParticleKeyframeDataMixin"