Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BlockRenderContext requires RenderType #284

Closed
AViewFromTheTop opened this issue May 4, 2024 · 45 comments
Closed

BlockRenderContext requires RenderType #284

AViewFromTheTop opened this issue May 4, 2024 · 45 comments

Comments

@AViewFromTheTop
Copy link

Bug Description

Hello!
I am the lead developer of Wilder Wild.
Upon looking at me.jellysquid.mods.sodium.client.render.chunk.compile.pipeline.BlockRenderContext, I found that the class differs from that of Sodium's.
This parity is crucial as I am importing both Embeddium and Sodium to support custom Liquid and Snowlogging rendering, and Snowlogging specifically requires a mixin that calls the update method in this class. However, since they share the same exact file paths, Embeddium overrides Sodium's source, ensuring one of the two mods will be guaranteed to crash with Wilder Wild.

Reproduction Steps

Compare Sodium's and Embeddium's sources.

Log File

latest.log

@embeddedt
Copy link
Owner

We can remove this parameter for 1.20.x on Fabric as it's not used and we do intend to keep binary compatibility with Sodium 0.5.8 as closely as possible. However, it will probably get added back in a future version of MC to keep the Forge & Fabric branches more in sync.

That said, as mentioned on the developer page we prefer that people don't mixin to add custom functionality, as it means we can never change any of these functions without breaking some mod. Can you explain in more detail what you are trying to accomplish?

@AViewFromTheTop
Copy link
Author

Rendering Snow Layers inside of other blocks.
I can’t add custom models because a lot of plants have offsets in-game.

@AViewFromTheTop
Copy link
Author

Screenshot_20240504_113721
This should give a better view of what's going on.

@AViewFromTheTop
Copy link
Author

If there's a way to maintain Sodium's structure while also supporting Embeddium's structure, that would be preferable.
I just have no way of importing two sources with the same paths, let alone compiling for both of them.
Do you think it would be possible to create a second method in the class that uses Sodium's parameters, and passes a dummy RenderLayer if possible? I'm not too sure how that would play out.

@AViewFromTheTop
Copy link
Author

Quick update, Treetrain1 found an API you guys have!
He’s going to test it out to see if that fixes the issue or not.

@embeddedt
Copy link
Owner

If there's a way to maintain Sodium's structure while also supporting Embeddium's structure, that would be preferable.
I just have no way of importing two sources with the same paths, let alone compiling for both of them.

We're going to fix the method signature to match Sodium's regardless of my comment about mixins, as it's a valid parity issue, and we intend to maintain binary compatibility with mods targeting Sodium 0.5.8 where possible. (This of course won't apply to Sodium 0.6+.)

Rendering Snow Layers inside of other blocks.
I can’t add custom models because a lot of plants have offsets in-game.

I assume this is what Treetrain1 found but our ChunkMeshEvent should be usable for this purpose.

@AViewFromTheTop
Copy link
Author

Alright, thank you!
I wasn’t able to get the API working anyway.

@embeddedt
Copy link
Owner

Is this helpful? I realized the API is not too intuitive without having already studied the vanilla meshing code in detail 😅, so I'll probably add a similar example somewhere in the codebase for other modders.

public static void onChunkMesh(ChunkMeshEvent event) {
        LevelChunk chunk = event.getWorld().getChunk(event.getSectionOrigin().x(), event.getSectionOrigin().z());
        LevelChunkSection section = chunk.getSection(chunk.getSectionIndexFromSectionY(event.getSectionOrigin().y()));
        // Never schedule a mesh appender for empty sections (unless you need to render geometry in them).
        // Embeddium normally greatly speeds up initial world loads by not scheduling a rebuild task for an empty
        // section (since there is nothing to build), but the task is still created if you register a mesh appender
        // because mods may wish to render custom geometry in otherwise empty sections.
        // Since we only want to render a snowlogged effect on actual blocks, we don't need to bother with empty
        // sections.
        if(section != null && !section.hasOnlyAir()) {
            BlockState snowState = Blocks.SNOW.defaultBlockState().setValue(SnowLayerBlock.LAYERS, 7);
            event.addMeshAppender(ctx -> {
                // This part of the code runs on the meshing thread, not the main thread
                BlockPos.MutableBlockPos cursor = new BlockPos.MutableBlockPos();
                var vertexConsumer = ctx.vertexConsumerProvider().apply(RenderType.solid());
                PoseStack stack = new PoseStack();
                RandomSource random = RandomSource.create();
                // Even though we have a snapshot of the world, we still use the same coordinates
                int baseX = ctx.sectionOrigin().minBlockX();
                int baseY = ctx.sectionOrigin().minBlockY();
                int baseZ = ctx.sectionOrigin().minBlockZ();
                for(int y = 0; y < 16; y++) {
                    for(int z = 0; z < 16; z++) {
                        for(int x = 0; x < 16; x++) {
                            cursor.set(baseX + x, baseY + y, baseZ + z);
                            // Access blocks through the context's getter, not the section itself, as the latter
                            // is not safe to use on a meshing thread
                            BlockState selfState = ctx.blockRenderView().getBlockState(cursor);
                            if(!selfState.isAir()) {
                                // Check if the block underneath is netherite block
                                cursor.move(0, -1, 0);
                                if(ctx.blockRenderView().getBlockState(cursor).is(Blocks.NETHERITE_BLOCK)) {
                                    // Snowlog the original block
                                    cursor.move(0, 1, 0);
                                    stack.pushPose();
                                    stack.translate(x, y, z);

                                    // FIXME horrible hack to hide z-fighting of the sides of the snow
                                    float scaleFactor = 0.99f;
                                    float translateFactor = (1 - scaleFactor) / 2;
                                    stack.translate(translateFactor, translateFactor, translateFactor);
                                    stack.scale(scaleFactor, scaleFactor, scaleFactor);
                                    
                                    // Render the snow blockstate at the given position
                                    Minecraft.getInstance().getBlockRenderer().renderBatched(snowState, cursor, ctx.blockRenderView(), stack, vertexConsumer, true, random);
                                    
                                    // Reset the PoseStack for the next use
                                    stack.popPose();
                                }
                            }
                        }
                    }
                }
            });
        }
}

@AViewFromTheTop
Copy link
Author

Is this helpful? I realized the API is not too intuitive without having already studied the vanilla meshing code in detail 😅, so I'll probably add a similar example somewhere in the codebase for other modders.

public static void onChunkMesh(ChunkMeshEvent event) {
        LevelChunk chunk = event.getWorld().getChunk(event.getSectionOrigin().x(), event.getSectionOrigin().z());
        LevelChunkSection section = chunk.getSection(chunk.getSectionIndexFromSectionY(event.getSectionOrigin().y()));
        // Never schedule a mesh appender for empty sections (unless you need to render geometry in them).
        // Embeddium normally greatly speeds up initial world loads by not scheduling a rebuild task for an empty
        // section (since there is nothing to build), but the task is still created if you register a mesh appender
        // because mods may wish to render custom geometry in otherwise empty sections.
        // Since we only want to render a snowlogged effect on actual blocks, we don't need to bother with empty
        // sections.
        if(section != null && !section.hasOnlyAir()) {
            BlockState snowState = Blocks.SNOW.defaultBlockState().setValue(SnowLayerBlock.LAYERS, 7);
            event.addMeshAppender(ctx -> {
                // This part of the code runs on the meshing thread, not the main thread
                BlockPos.MutableBlockPos cursor = new BlockPos.MutableBlockPos();
                var vertexConsumer = ctx.vertexConsumerProvider().apply(RenderType.solid());
                PoseStack stack = new PoseStack();
                RandomSource random = RandomSource.create();
                // Even though we have a snapshot of the world, we still use the same coordinates
                int baseX = ctx.sectionOrigin().minBlockX();
                int baseY = ctx.sectionOrigin().minBlockY();
                int baseZ = ctx.sectionOrigin().minBlockZ();
                for(int y = 0; y < 16; y++) {
                    for(int z = 0; z < 16; z++) {
                        for(int x = 0; x < 16; x++) {
                            cursor.set(baseX + x, baseY + y, baseZ + z);
                            // Access blocks through the context's getter, not the section itself, as the latter
                            // is not safe to use on a meshing thread
                            BlockState selfState = ctx.blockRenderView().getBlockState(cursor);
                            if(!selfState.isAir()) {
                                // Check if the block underneath is netherite block
                                cursor.move(0, -1, 0);
                                if(ctx.blockRenderView().getBlockState(cursor).is(Blocks.NETHERITE_BLOCK)) {
                                    // Snowlog the original block
                                    cursor.move(0, 1, 0);
                                    stack.pushPose();
                                    stack.translate(x, y, z);

                                    // FIXME horrible hack to hide z-fighting of the sides of the snow
                                    float scaleFactor = 0.99f;
                                    float translateFactor = (1 - scaleFactor) / 2;
                                    stack.translate(translateFactor, translateFactor, translateFactor);
                                    stack.scale(scaleFactor, scaleFactor, scaleFactor);
                                    
                                    // Render the snow blockstate at the given position
                                    Minecraft.getInstance().getBlockRenderer().renderBatched(snowState, cursor, ctx.blockRenderView(), stack, vertexConsumer, true, random);
                                    
                                    // Reset the PoseStack for the next use
                                    stack.popPose();
                                }
                            }
                        }
                    }
                }
            });
        }
}

I’m not entirely sure to be honest
My main issue with it was that it still required me to use that method with RenderLayer, which was impossible with both Embeddium and Sodium sources imported.
Perhaps if there was a method in an API that ran the update method, that could fix it? Because renderLayer can be gotten from the block context if I remember correctly, so a method that does that is viable.
It would also be nice for the model shaper to be passed as a parameter, but it’s not needed.

@AViewFromTheTop
Copy link
Author

In any case once I get to my PC, how should I use this?
Like would would I get the chunk mesh event?
It would probably fix it honestly.

@embeddedt
Copy link
Owner

Perhaps if there was a method in an API that ran the update method, that could fix it? Because renderLayer can be gotten from the block context if I remember correctly, so a method that does that is viable.

The idea of the API is that you do your extra rendering using vanilla functions, not Embeddium functions (as the latter are intended for internal use). We really don't want mods needing to do much special-casing for Embeddium, beyond some safer equivalent of mixing into the chunk renderer.

For instance, in the example above I call the vanilla block model renderer to add the snow layer. Aside from the use of the API hook to get the vertex consumer for the chunk section, there's nothing Embeddium-specific about that rendering logic - it uses existing vanilla infrastructure. This example doesn't use the BlockRenderContext at all.

Like would would I get the chunk mesh event?

You can register an event handler with ChunkMeshEvent.BUS.addListener. It takes a Consumer<ChunkMeshEvent> as a parameter, so you can pass a lambda, method reference, or whatever.

@embeddedt
Copy link
Owner

For the record, I am just about to release 0.3.18 with the method signature change (writing the changelog as we speak), so your existing Sodium mixin will probably work anyway. The API is intended as our long-term solution, as we're not able to retain compatibility with Sodium after they release 0.6, and we're trying to wean mods off of using mixins where we can. In short - if you don't want to look into this right now, it may not be required in the short term.

@AViewFromTheTop
Copy link
Author

Ah, that makes sense. I’ll try to add that in! Thank you!
Also, is there anything in particular going on with Sodium 0.6+ for you all to make that change? Or is it just a separate decision?

@embeddedt
Copy link
Owner

embeddedt commented May 6, 2024

is there anything in particular going on with Sodium 0.6+ for you all to make that change? Or is it just a separate decision?

I'll try to keep this concise to minimize potential drama from the answer and also to be respectful of your time, as the full answer would be rather long. 😆

We (Nolij and myself, the current two maintainers of Embeddium) have had some disagreements with CaffeineMC in the past over various concerns we had with Sodium's project management (such as breaking changes which were not strictly necessary and were very likely to break mods).

The main problem at this point, though, is that as of Sodium 0.6, the license was changed to a new, more restrictive one that is not open-source (it explicitly prevents competing forks from being published using the code). Since we obviously compete with Sodium, we can't pull anything from newer versions.

We personally do not want to use versions of Sodium under that new license, and we still think our approach to mod compatibility is ultimately more stable for modders, so we're continuing to maintain Embeddium as an independent project, under the original open-source license. For now, we're trying our best to retain compatibility with anything that depended on Sodium 0.5.8 (hence the method signature revert). In the future, we'll inevitably diverge since the projects will go in different directions.

@AViewFromTheTop
Copy link
Author

Just curious how anyone’s expected to maintain compat with Sodium if they’re not open source 😭

@embeddedt
Copy link
Owner

how anyone’s expected to maintain compat with Sodium if they’re not open source 😭

Everyone else can read the code (and even incorporate portions of it) as long as they don't write a mod deemed to be "competing" under the license. The license's terms are targeted directly at competing projects like ours, not any arbitrary software project.

For reference the terms of the license can be found here; it doesn't really give a strict definition of what constitutes competing, so I will not comment on that myself either.

@AViewFromTheTop
Copy link
Author

Ah

@AViewFromTheTop
Copy link
Author

Oh, really quick, by the way!
Would you consider adding an API for Cloud positions as well?
Wilder Wild makes their position change with wind, and can be seen here: https://github.com/FrozenBlock/WilderWild/blob/master/src/main/java/net/frozenblock/wilderwild/mixin/client/embeddium/CloudRendererMixin.java
Would be nice for it to have a small API as well- the less mixins, the better in my opinion

@embeddedt
Copy link
Owner

I may be missing something but at first glance it looks like you could use ModifyArg to modify the camera X/Z passed to LevelRenderer#renderClouds from LevelRenderer#render (or modify the matrices themselves, if the camera position isn't enough), and it would work with Sodium out of the box without requiring a dedicated mixin for that code as well.

@AViewFromTheTop
Copy link
Author

FrozenBlock/WilderWild@b7444fd
This is awesome thank you!!!!
Pink Petals no longer Z-Fight with snowlogging too :)

@AViewFromTheTop
Copy link
Author

I may be missing something but at first glance it looks like you could use ModifyArg to modify the camera X/Z passed to LevelRenderer#renderClouds from LevelRenderer#render (or modify the matrices themselves, if the camera position isn't enough), and it would work with Sodium out of the box without requiring a dedicated mixin for that code as well.

Hadn't thought of that.
I'll try to look into it, thank you!

@AViewFromTheTop
Copy link
Author

Dang, the wind idea worked too! Thank you so much!!!
The only thing left honestly would be.... this monstrosity.
https://github.com/FrozenBlock/WilderWild/blob/master/src/main/java/net/frozenblock/wilderwild/mixin/client/embeddium/FluidRendererMixin.java
It's meant to render a block as a Liquid, using a static 16x16 texture.
I wouldn't be surprised if this was one that can't really be fixed/worked around with Embeddium or Vanilla methods, but I just wanted to bring it up in the event there was something that could aid it even a small bit.

@embeddedt
Copy link
Owner

embeddedt commented May 6, 2024

That mixin actually doesn't seem to function with Embeddium in its current state, not sure why.

Anyway, from trying it in vanilla, it seems you just want to override the texture/color that's used for fluid rendering; nothing else gets changed. Since the mesoglea block can only ever contains water fluid states, you should be able to just register an override for the water fluid handler using Fabric's fluid API and conditionally return different textures. (This should work with Sodium/Embeddium out of the box.)

Here is an example, which makes fluids rendered on top of netherite blocks use the diamond block texture. It should be easy to adjust this to detect the block at the given position being mesoglea.

The reason for doing it "weirdly" with the resource listener is to make it properly compose with other mods that also try to wrap water rendering. To be completely transparent, I'm not 100% sure if this is the proper way to wrap water rendering, but the Fabric Discord should be able to confirm if you have questions. The documentation does suggest that wrapping vanilla fluids is an intended use case.

ResourceManagerHelper.get(PackType.CLIENT_RESOURCES).registerReloadListener(new SimpleSynchronousResourceReloadListener() {
            @Override
            public ResourceLocation getFabricId() {
                return new ResourceLocation("embeddium", "fluid_example");
            }

            FluidRenderHandler customWaterHandler;

            @Override
            public void onResourceManagerReload(ResourceManager resourceManager) {
                if(customWaterHandler == null) {
                    FluidRenderHandler originalHandler = FluidRenderHandlerRegistry.INSTANCE.get(Fluids.WATER);
                    // Assert that the original handler exists now, otherwise the crash will happen later inside our
                    // handler
                    Objects.requireNonNull(originalHandler);
                    customWaterHandler = new FluidRenderHandler() {
                        final TextureAtlasSprite[] diamondWaterSprites = new TextureAtlasSprite[3];

                        private boolean isSingleTexture(@Nullable BlockAndTintGetter view, @Nullable BlockPos pos) {
                            return view != null && pos != null && view.getBlockState(pos.below()).is(Blocks.NETHERITE_BLOCK);
                        }

                        @Override
                        public TextureAtlasSprite[] getFluidSprites(@Nullable BlockAndTintGetter view, @Nullable BlockPos pos, FluidState state) {
                            if(isSingleTexture(view, pos)) {
                                return diamondWaterSprites;
                            } else {
                                return originalHandler.getFluidSprites(view, pos, state);
                            }
                        }

                        @Override
                        public void reloadTextures(TextureAtlas textureAtlas) {
                            originalHandler.reloadTextures(textureAtlas);
                            var diamondBlock = textureAtlas.getSprite(new ResourceLocation("block/diamond_block"));
                            diamondWaterSprites[0] = diamondBlock; // still
                            diamondWaterSprites[1] = diamondBlock; // flowing
                            diamondWaterSprites[2] = diamondBlock; // overlay
                        }

                        // Delegate all other methods to the original

                        @Override
                        public void renderFluid(BlockPos pos, BlockAndTintGetter world, VertexConsumer vertexConsumer, BlockState blockState, FluidState fluidState) {
                            originalHandler.renderFluid(pos, world, vertexConsumer, blockState, fluidState);
                        }

                        @Override
                        public int getFluidColor(@Nullable BlockAndTintGetter view, @Nullable BlockPos pos, FluidState state) {
                            return isSingleTexture(view, pos) ? 0xFFFFFFFF : originalHandler.getFluidColor(view, pos, state);
                        }
                    };
                    FluidRenderHandlerRegistry.INSTANCE.register(Fluids.WATER, Fluids.FLOWING_WATER, customWaterHandler);
                }
                
                // Load textures for our handler
                customWaterHandler.reloadTextures(Minecraft.getInstance().getModelManager().getAtlas(TextureAtlas.LOCATION_BLOCKS));
            }
        });

@AViewFromTheTop
Copy link
Author

Thank you very much!!
Is there a way to get the texture from the waterlogged block? Or does it have to be registered per-block?

@AViewFromTheTop
Copy link
Author

2024-05-07_12 10 14
Screenshot_20240507_121051
It doesn't seem to work. Being the dunce I am sometimes, it could just be me screwing something up on accident.

@embeddedt
Copy link
Owner

Let me check 1.20.6, I tested that example on 1.20.1 and Fabric has changed how fluid handler registration is done internally since then.

@embeddedt
Copy link
Owner

This revised example works on 1.20.6. renderFluid should not be overriden anymore (it causes the given sprites to be ignored), and injecting the handler from a resource reload listener is no longer required.

        FluidRenderHandler originalHandler = FluidRenderHandlerRegistry.INSTANCE.get(Fluids.WATER);
        // Assert that the original handler exists now, otherwise the crash will happen later inside our
        // handler
        Objects.requireNonNull(originalHandler);
        var customWaterHandler = new FluidRenderHandler() {
            final TextureAtlasSprite[] diamondWaterSprites = new TextureAtlasSprite[3];

            private boolean isSingleTexture(@Nullable BlockAndTintGetter view, @Nullable BlockPos pos) {
                return view != null && pos != null && view.getBlockState(pos.below()).is(Blocks.NETHERITE_BLOCK);
            }

            @Override
            public TextureAtlasSprite[] getFluidSprites(@Nullable BlockAndTintGetter view, @Nullable BlockPos pos, FluidState state) {
                if(isSingleTexture(view, pos)) {
                    return diamondWaterSprites;
                } else {
                    return originalHandler.getFluidSprites(view, pos, state);
                }
            }

            @Override
            public void reloadTextures(TextureAtlas textureAtlas) {
                originalHandler.reloadTextures(textureAtlas);
                var diamondBlock = textureAtlas.getSprite(new ResourceLocation("block/diamond_block"));
                diamondWaterSprites[0] = diamondBlock; // still
                diamondWaterSprites[1] = diamondBlock; // flowing
                diamondWaterSprites[2] = diamondBlock; // overlay
            }

            // Delegate all other methods to the original

            @Override
            public int getFluidColor(@Nullable BlockAndTintGetter view, @Nullable BlockPos pos, FluidState state) {
                return isSingleTexture(view, pos) ? 0xFFFFFFFF : originalHandler.getFluidColor(view, pos, state);
            }
        };
        FluidRenderHandlerRegistry.INSTANCE.register(Fluids.WATER, Fluids.FLOWING_WATER, customWaterHandler);

@AViewFromTheTop
Copy link
Author

AViewFromTheTop commented May 7, 2024

2024-05-07_12 38 50
Huge step in the right direction, thank you!
Only thing now is that the sides are zoomed in, is there anything that can be done about that?
It's alright if not, I can just stick to my mixins I've been using.

@embeddedt
Copy link
Owner

embeddedt commented May 7, 2024

It's alright if not, I can just stick to my mixins I've been using.

I would really prefer not to have such an invasive mixin in the fluid renderer, because the mixin will inevitably break if I ever have to refactor it.

Only thing now is that the sides are zoomed in, is there anything that can be done about that?

To solve that, I think you would either have to make another texture that's scaled the same way as the vanilla water fluid textures (so that it doesn't look zoomed in), or you could just conditionally take over fluid rendering and render it yourself in renderFluid with the desired textures (which might be preferable, if you already have the code for it for vanilla).

@AViewFromTheTop
Copy link
Author

I would really prefer not to have such an invasive mixin in the fluid renderer, because the mixin will inevitably break if I ever have to refactor it.

I've updated it for Sodium's from 1.19 through now, but it's understandable!

To solve that, I think you would either have to make another texture that's scaled the same way as the vanilla water fluid textures (so that it doesn't look zoomed in), or you could just conditionally take over fluid rendering and render it yourself in renderFluid with the desired textures (which might be preferable, if you already have the code for it for vanilla).

I think that renderFluid one is my best bet. I made a method for this exact situation in Vanilla for the library WW uses!

@AViewFromTheTop
Copy link
Author

2024-05-07_12 57 11
Awesome to know that code still works with Embeddium/Sodium.
Thank you so much!!!!!

@embeddedt
Copy link
Owner

embeddedt commented May 7, 2024

Double check that Sodium actually respects the renderFluid hook in 0.5.8. I get a feeling that's one of the many promised bugfixes which have been sitting on the development branch unable to be released for months. So you might need to still use your original mixin if you detect Sodium and not Embeddium.

@AViewFromTheTop
Copy link
Author

Will do!

@AViewFromTheTop
Copy link
Author

2024-05-07_13 01 06
Yeah... mixin it is for Sodium.

@AViewFromTheTop
Copy link
Author

Is https://github.com/FrozenBlock/WilderWild/blob/master/src/main/java/net/frozenblock/wilderwild/mixin/client/embeddium/FluidRendererMixin.java still required?

For a very specific use case, possibly so. It’s to make the bottom side of Water render as the solid-colored overlay.

@embeddedt
Copy link
Owner

What triggers that? When I look up at the underside of ocean water with just WW installed I still see the regular still texture.

@AViewFromTheTop
Copy link
Author

What triggers that? When I look up at the underside of ocean water with just WW installed I still see the regular still texture.

I meant the bottom of the block, not the underside of the surface.
Without this change, placing blocks like glass underwater looks very annoying in my opinion, especially with a full glass box. (The sides would use the solid overlay, but the ceilings show the ripple texture- with this, it’s all solid)

@embeddedt
Copy link
Owner

embeddedt commented May 8, 2024

I see. That doesn't seem to be working with just WW though.

image

Anyway, this type of change is probably another good candidate to be done conditionally in renderFluid.

@AViewFromTheTop
Copy link
Author

Would you be able to try that scenario without WW
The water face on the top part of the glass should have the ripple pattern as well without it

@AViewFromTheTop
Copy link
Author

On that note though, I’m not really sure how to easily do this using renderFluid without pretty much overwriting all water rendering
I find the mixin simpler because it just swaps out the bottom texture instead of overwriting things

@embeddedt
Copy link
Owner

Oh, now I see what you mean; the difference is extremely subtle to my eyes though (as the ripple is nowhere near as obvious as it is on the bottom of the glass).

I find the mixin simpler because it just swaps out the bottom texture instead of overwriting things

The mixin is easy for you to write, but it's very much not easy for me when players complain about it being broken down the line. 😉 That problem doesn't exist for vanilla mixins because the code is obviously not changing until another Minecraft update, but cross-mod mixins tend to be rather unstable.

This would be the kind of niche visual change where if you don't want to have the responsibility of replacing the water renderer using renderFluid, I'd be tempted to suggest "just have this not work with Embeddium", as I'm frankly not sure most users would notice the difference, and I'm having trouble thinking of a generic enough API that would be useful for more than just this specific example. renderFluid is Fabric's standard approach for deep customization of how a fluid renders.

I'll keep thinking and let you know if I do come up with something though.

@AViewFromTheTop
Copy link
Author

I feared it was too niche for that 😭

@embeddedt
Copy link
Owner

Going to close this as the original issue was fixed in 0.3.18, but feel free to reach out if you need anything else!

@AViewFromTheTop
Copy link
Author

Going to close this as the original issue was fixed in 0.3.18, but feel free to reach out if you need anything else!

Thank you very much!!!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants