Fix walk/fall particle icons when walking on modeled blocks (#434)

Co-authored-by: Martin Robertz <dream-master@gmx.net>
This commit is contained in:
ah-OOG-ahandMartin Robertz authored and GitHub committed 2026-08-18 08:44:10 +07:00
1 parent e729fc64d4
commit 6d90dcfedd
8 files changed
+177 -3

No files matched your search

@@ -1,5 +1,7 @@
package com.gtnewhorizon.gtnhlib.api;
import net.minecraft.block.Block;
/// This class is mixed into {@link net.minecraft.block.Block}, so the model loader can store model information on them.
/// The data must *only* be mutated by the resource reload manager, but can be read by anyone. If your model can only
/// be found by a custom locator, you can implement this yourself and override [#nhlib$isModeled()] to always be true.
@@ -8,4 +10,8 @@ public interface BlockModelInfo {
boolean nhlib$isModeled();
void nhlib$setModeled(boolean modeled);
static boolean isModeled(Block block) {
return ((BlockModelInfo) block).nhlib$isModeled();
}
}
@@ -16,12 +16,15 @@ import java.util.Random;
import net.minecraft.block.Block;
import net.minecraft.client.Minecraft;
import net.minecraft.client.particle.EntityDiggingFX;
import net.minecraft.client.renderer.RenderBlocks;
import net.minecraft.client.renderer.RenderGlobal;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.item.ItemStack;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.IIcon;
import net.minecraft.world.IBlockAccess;
import net.minecraft.world.World;
import net.minecraft.world.biome.BiomeGenBase;
import net.minecraftforge.client.ForgeHooksClient;
import net.minecraftforge.client.IItemRenderer;
@@ -31,6 +34,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.joml.Vector3f;
import org.lwjgl.opengl.GL11;
import org.spongepowered.asm.mixin.Unique;
import com.github.bsideup.jabel.Desugar;
import com.gtnewhorizon.gtnhlib.api.BlockModelInfo;
@@ -44,6 +48,8 @@ import com.gtnewhorizon.gtnhlib.client.renderer.cel.api.util.NormI8;
import com.gtnewhorizon.gtnhlib.client.renderer.cel.model.quad.ModelQuadView;
import com.gtnewhorizon.gtnhlib.client.renderer.cel.model.quad.properties.ModelQuadFacing;
import com.gtnewhorizon.gtnhlib.core.fml.transformers.BlockIconTransformer;
import com.gtnewhorizon.gtnhlib.mixins.early.models.particles.RenderGlobalAccessor;
import com.gtnewhorizon.gtnhlib.mixins.early.models.particles.WorldAccessor;
import com.gtnewhorizon.gtnhlib.util.StdLCG;
import com.gtnewhorizons.angelica.api.ThreadSafeISBRH;
@@ -507,6 +513,55 @@ public class ModelISBRH implements ISimpleBlockRenderingHandler, IItemRenderer {
return Minecraft.getMinecraft().getTextureMapBlocks().getAtlasSprite("missingno");
}
/// Spawn a particle for an entity walking on a modeled block. This method effectively replaces
/// [World#spawnParticle(String, double, double, double, double, double, double)] so we can inject a new texture,
/// using the block position that normally gets dropped before the particle is actually spawned.
///
/// @param rg The RenderGlobal instance the particle is spawned in.
/// @param blockX X position of the block the particle is spawned from.
/// @param blockY Y position...
/// @param blockZ Z position...
/// @param x X position of the particle
/// @param y Y position...
/// @param z Z position...
/// @param vX X velocity of particle
/// @param vY Y velocity...
/// @param vZ Z velocity...
@Unique
public static void spawnParticle(RenderGlobal rg, int blockX, int blockY, int blockZ, double x, double y, double z,
double vX, double vY, double vZ) {
var mc = Minecraft.getMinecraft();
if (mc == null || mc.renderViewEntity == null || mc.effectRenderer == null) return;
// Skip 3/4 of particles when they're set to decreased
var world = ((RenderGlobalAccessor) rg).getTheWorld();
if (mc.gameSettings.particleSetting == 1 && world.rand.nextInt(3) == 0) return;
// Don't render distant (>16 blocks) particles.
var dx = mc.renderViewEntity.posX - x;
var dy = mc.renderViewEntity.posY - y;
var dz = mc.renderViewEntity.posZ - z;
if (dx * dx + dy * dy * dz * dz > 16 * 16) return;
var block = world.getBlock(blockX, blockY, blockZ);
var meta = world.getBlockMetadata(blockX, blockY, blockZ);
var digFX = new EntityDiggingFX(world, x, y, z, vX, vY, vZ, block, meta);
digFX.setParticleIcon(INSTANCE.get().getParticleIcon(world, blockX, blockY, blockZ));
mc.effectRenderer.addEffect(digFX);
}
/// A helper to call [#spawnParticle] the same way vanilla calls [World#spawnParticle]. It's *probably* fine to only
/// call this on the main render global, but "probably" doesn't stop breakages.
///
/// Neither does being careful, but such is life.
public static void spawnParticleCommon(World world, int blockX, int blockY, int blockZ, double x, double y,
double z, double vX, double vY, double vZ) {
for (var iwa : ((WorldAccessor) world).getWorldAccesses()) {
if (iwa instanceof RenderGlobal rg)
ModelISBRH.spawnParticle(rg, blockX, blockY, blockZ, x, y, z, vX, vY, vZ);
}
}
/// Some blocks, like LittleTiles, rely on recursively rendering other blocks in the same location. Unfortunately,
/// this means we have to actually respect the block passed into [ModelISBRH#renderWorldBlock]. In order to read a
/// blockstate with that, we wrap the original IBA with one that just returns a slightly different block.
@@ -25,11 +25,16 @@ public enum Mixins implements IMixins {
BLOCK_PROPERTIES_ACCESSORS(Side.COMMON, "MixinTileEntitySkull"),
MODEL_TEXTURE_LOADING(new MixinBuilder("Automatically load models, textures, and metadata.").addClientMixins(
"models.FRMAccessor",
"models.MixinEffectRenderer_FixParticleIcons",
"models.MixinFileResourcePack",
"models.MixinFolderResourcePack",
"models.MixinRenderBlocks",
"models.SRRMAccessor").setPhase(Phase.EARLY).setApplyIf(() -> true)),
MODEL_PARTICLE_FIXES(new MixinBuilder("Override particle icons for models.").addClientMixins(
"models.particles.MixinEffectRenderer",
"models.particles.MixinEntity",
"models.particles.MixinRenderGlobal",
"models.particles.RenderGlobalAccessor",
"models.particles.WorldAccessor").setPhase(Phase.EARLY).setApplyIf(() -> true)),
MODEL_ITEM_RENDERER(new MixinBuilder("Restore origin pivot before modifier").addClientMixins("models.MixinModelFHC")
.setPhase(Phase.EARLY)),
MODEL_BLOCK_CRACKS(new MixinBuilder("Fix crack texture appearance for modeled blocks")
@@ -1,4 +1,4 @@
package com.gtnewhorizon.gtnhlib.mixins.early.models;
package com.gtnewhorizon.gtnhlib.mixins.early.models.particles;
import net.minecraft.block.Block;
import net.minecraft.client.particle.EffectRenderer;
@@ -15,7 +15,7 @@ import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
import com.llamalad7.mixinextras.sugar.Local;
@Mixin(EffectRenderer.class)
public class MixinEffectRenderer_FixParticleIcons {
public class MixinEffectRenderer {
@Shadow
protected World worldObj;
@@ -0,0 +1,38 @@
package com.gtnewhorizon.gtnhlib.mixins.early.models.particles;
import net.minecraft.block.Block;
import net.minecraft.entity.Entity;
import net.minecraft.world.World;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import com.gtnewhorizon.gtnhlib.api.BlockModelInfo;
import com.gtnewhorizon.gtnhlib.client.model.ModelISBRH;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import com.llamalad7.mixinextras.sugar.Local;
@SuppressWarnings("UnusedMixin")
@Mixin(Entity.class)
public class MixinEntity {
@Shadow
public World worldObj;
@WrapOperation(
method = "onEntityUpdate",
at = @At(value = "INVOKE", target = "Lnet/minecraft/world/World;spawnParticle(Ljava/lang/String;DDDDDD)V"))
private void nhlib$fixBreakIcon(World instance, String particleName, double x, double y, double z, double velocityX,
double velocityY, double velocityZ, Operation<Void> original, @Local(name = "block") Block block,
@Local(name = "j") int j, @Local(name = "i") int i, @Local(name = "k") int k) {
if (!((BlockModelInfo) block).nhlib$isModeled()) {
original.call(instance, particleName, x, y, z, velocityX, velocityY, velocityZ);
return;
}
// This is a modeled block! Refer to our helper for particles instead of the default.
ModelISBRH.spawnParticleCommon(worldObj, j, i, k, x, y, z, velocityX, velocityY, velocityZ);
}
}
@@ -0,0 +1,40 @@
package com.gtnewhorizon.gtnhlib.mixins.early.models.particles;
import static com.gtnewhorizon.gtnhlib.api.BlockModelInfo.isModeled;
import net.minecraft.block.Block;
import net.minecraft.client.multiplayer.WorldClient;
import net.minecraft.client.renderer.RenderGlobal;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import com.gtnewhorizon.gtnhlib.client.model.ModelISBRH;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import com.llamalad7.mixinextras.sugar.Local;
@Mixin(RenderGlobal.class)
public class MixinRenderGlobal {
@SuppressWarnings("NameDoesntMatchTargetClass")
@WrapOperation(
method = "playAuxSFX",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/client/multiplayer/WorldClient;spawnParticle(Ljava/lang/String;DDDDDD)V",
ordinal = 2))
private void nhlib$fixModeledFallParticles(WorldClient wc, String particleName, double x, double y, double z,
double vX, double vY, double vZ, Operation<Void> original, @Local(name = "block") Block block,
@Local(name = "p_72706_3_", argsOnly = true) int blockX,
@Local(name = "p_72706_4_", argsOnly = true) int blockY,
@Local(name = "p_72706_5_", argsOnly = true) int blockZ) {
if (!isModeled(block)) {
original.call(wc, particleName, x, y, z, vX, vY, vZ);
return;
}
// This is a modeled block! Refer to our helper for particles instead of the default.
ModelISBRH.spawnParticleCommon(wc, blockX, blockY, blockZ, x, y, z, vX, vY, vZ);
}
}
@@ -0,0 +1,14 @@
package com.gtnewhorizon.gtnhlib.mixins.early.models.particles;
import net.minecraft.client.multiplayer.WorldClient;
import net.minecraft.client.renderer.RenderGlobal;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
@Mixin(RenderGlobal.class)
public interface RenderGlobalAccessor {
@Accessor
WorldClient getTheWorld();
}
@@ -0,0 +1,16 @@
package com.gtnewhorizon.gtnhlib.mixins.early.models.particles;
import java.util.List;
import net.minecraft.world.IWorldAccess;
import net.minecraft.world.World;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
@Mixin(World.class)
public interface WorldAccessor {
@Accessor
List<IWorldAccess> getWorldAccesses();
}