fixes fluid rendering in world and in gui, as well as voxelshape on tank block and power cell block

This commit is contained in:
2026-03-12 00:58:15 -04:00
parent de33209e9a
commit 75b973f7f2
5 changed files with 152 additions and 82 deletions
@@ -11,6 +11,7 @@ import dev.architectury.registry.menu.MenuRegistry;
import net.cmr.jurassicrevived.block.entity.ModBlockEntities;
import net.cmr.jurassicrevived.block.entity.custom.PowerCellBlockEntity;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.registries.Registries;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.server.level.ServerPlayer;
@@ -18,6 +19,7 @@ import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.BaseEntityBlock;
import net.minecraft.world.level.block.Block;
@@ -28,6 +30,7 @@ import net.minecraft.world.level.block.entity.BlockEntityTicker;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.VoxelShape;
import org.jetbrains.annotations.Nullable;
@@ -144,4 +147,9 @@ public class PowerCellBlock extends BaseEntityBlock {
return createTickerHelper(pBlockEntityType, ModBlockEntities.POWER_CELL_BE.get(),
((level, blockPos, blockState, powerCellBlockEntity) -> powerCellBlockEntity.tick(level, blockPos, blockState)));
}
@Override
public VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) {
return SHAPE;
}
}
@@ -11,6 +11,7 @@ import dev.architectury.registry.menu.MenuRegistry;
import net.cmr.jurassicrevived.block.entity.ModBlockEntities;
import net.cmr.jurassicrevived.block.entity.custom.TankBlockEntity;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.registries.Registries;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.server.level.ServerPlayer;
@@ -18,6 +19,7 @@ import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.BaseEntityBlock;
import net.minecraft.world.level.block.Block;
@@ -28,6 +30,7 @@ import net.minecraft.world.level.block.entity.BlockEntityTicker;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.VoxelShape;
import org.jetbrains.annotations.Nullable;
@@ -143,4 +146,9 @@ public class TankBlock extends BaseEntityBlock {
return createTickerHelper(pBlockEntityType, ModBlockEntities.TANK_BE.get(),
((level, blockPos, blockState, tankBlockEntity) -> tankBlockEntity.tick(level, blockPos, blockState)));
}
@Override
public VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) {
return SHAPE;
}
}
@@ -270,18 +270,9 @@ public class TankBlockEntity extends BlockEntity implements ExtendedMenuProvider
protected void saveAdditional(CompoundTag pTag, HolderLookup.Provider pRegistries) {
super.saveAdditional(pTag, pRegistries);
pTag.put("Inventory", itemHandler.createTag(pRegistries));
if (!fluidStack.isEmpty()) {
// Manually save fluid stack to avoid issues with Architectury's default serialization
CompoundTag fluidTag = new CompoundTag();
fluidTag.putString("id", BuiltInRegistries.FLUID.getKey(fluidStack.getFluid()).toString());
fluidTag.putLong("amount", fluidStack.getAmount());
if (fluidStack.getPatch() != null && !fluidStack.getPatch().isEmpty()) {
// Fallback to standard save if components are present
pTag.put("Fluid", FluidStack.CODEC.encodeStart(NbtOps.INSTANCE, fluidStack).getOrThrow());
} else {
pTag.put("Fluid", fluidTag);
}
}
CompoundTag fluidTag = new CompoundTag();
fluidStack.write(fluidTag, pRegistries);
pTag.put("Fluid", fluidTag);
}
@Override
@@ -289,21 +280,7 @@ public class TankBlockEntity extends BlockEntity implements ExtendedMenuProvider
super.loadAdditional(pTag, pRegistries);
itemHandler.fromTag(pTag.getList("Inventory", 10), pRegistries);
if (pTag.contains("Fluid")) {
CompoundTag fluidTag = pTag.getCompound("Fluid");
if (fluidTag.contains("id") && fluidTag.contains("amount")) {
// Manual load
try {
// Use ResourceLocation.parse for 1.21.1
net.minecraft.world.level.material.Fluid fluid = BuiltInRegistries.FLUID.get(ResourceLocation.parse(fluidTag.getString("id")));
long amount = fluidTag.getLong("amount");
this.fluidStack = FluidStack.create(fluid, amount);
} catch (Exception e) {
this.fluidStack = FluidStack.empty();
}
} else {
// Standard load
this.fluidStack = FluidStack.CODEC.parse(NbtOps.INSTANCE, fluidTag).result().orElse(FluidStack.empty());
}
this.fluidStack = FluidStack.read(pTag.getCompound("Fluid"), pRegistries);
} else {
this.fluidStack = FluidStack.empty();
}
@@ -17,8 +17,7 @@ import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.inventory.InventoryMenu;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.material.FluidState;
import java.util.Objects;
import net.minecraft.world.level.material.Fluids;
// Credits to TurtyWurty
// Under MIT-License: https://github.com/DaRealTurtyWurty/1.20-Tutorial-Mod?tab=MIT-1-ov-file#readme
@@ -36,49 +35,119 @@ public class TankBlockEntityRenderer implements BlockEntityRenderer<TankBlockEnt
if (level == null)
return;
BlockPos pos = pBlockEntity.getBlockPos();
TextureAtlasSprite sprite = FluidStackHooks.getStillTexture(fluidStack);
if (isMissing(sprite)) {
sprite = FluidStackHooks.getStillTexture(fluidStack.getFluid());
}
if (isMissing(sprite)) {
if (fluidStack.getFluid() == Fluids.WATER) {
sprite = Minecraft.getInstance().getTextureAtlas(InventoryMenu.BLOCK_ATLAS)
.apply(new ResourceLocation("block/water_still"));
}
}
if (isMissing(sprite)) return;
// Use Architectury Hooks instead of IClientFluidTypeExtensions
ResourceLocation stillTexture = Objects.requireNonNull(FluidStackHooks.getStillTexture(fluidStack)).atlasLocation();
int tintColor = FluidStackHooks.getColor(fluidStack);
FluidState state = fluidStack.getFluid().defaultFluidState();
TextureAtlasSprite sprite = Minecraft.getInstance().getTextureAtlas(InventoryMenu.BLOCK_ATLAS).apply(stillTexture);
// Adjust height calculation to be platform-independent
// (Assuming you'll update TankBlockEntity to use a multi-loader tank system)
float height = (((float) fluidStack.getAmount() / 64000f) * 0.625f) + 0.25f;
VertexConsumer builder = pBuffer.getBuffer(ItemBlockRenderTypes.getRenderLayer(state));
// Top Texture
drawQuad(builder, pPoseStack, 0.1f, height, 0.1f, 0.9f, height, 0.9f, sprite.getU0(), sprite.getV0(), sprite.getU1(), sprite.getV1(), pPackedLight, tintColor);
drawQuad(builder, pPoseStack, 0.1f, 0, 0.1f, 0.9f, height, 0.1f, sprite.getU0(), sprite.getV0(), sprite.getU1(), sprite.getV1(), pPackedLight, tintColor);
// Adjust Y bounds to match tank model (2 pixels from bottom, 2 pixels from top)
float MIN_Y = 0.125f; // 2/16
float MAX_Y = 0.875f; // 14/16
float MIN_X = 0.1f;
float MAX_X = 0.9f;
float MIN_Z = 0.1f;
float MAX_Z = 0.9f;
pPoseStack.pushPose();
pPoseStack.mulPose(Axis.XP.rotationDegrees(180));
pPoseStack.translate(0, -0.9f, -1f);
drawQuad(builder, pPoseStack, 0.1f, height, 0.1f, 0.9f, height, 0.9f, sprite.getU0(), sprite.getV0(), sprite.getU1(), sprite.getV1(), pPackedLight, tintColor);
pPoseStack.popPose();
float fluidHeight = ((float) fluidStack.getAmount() / 64000f) * (MAX_Y - MIN_Y);
float yTop = MIN_Y + fluidHeight;
pPoseStack.pushPose();
pPoseStack.mulPose(Axis.YP.rotationDegrees(180));
pPoseStack.translate(-1f, 0, -1.8f);
drawQuad(builder, pPoseStack, 0.1f, 0, 0.9f, 0.9f, height, 0.9f, sprite.getU0(), sprite.getV0(), sprite.getU1(), sprite.getV1(), pPackedLight, tintColor);
pPoseStack.popPose();
// UV coordinates mapping
float uMin = sprite.getU(MIN_X * 16);
float uMax = sprite.getU(MAX_X * 16);
float vMinZ = sprite.getV(MIN_Z * 16);
float vMaxZ = sprite.getV(MAX_Z * 16);
pPoseStack.pushPose();
pPoseStack.mulPose(Axis.YP.rotationDegrees(90));
pPoseStack.translate(-1f, 0, 0);
drawQuad(builder, pPoseStack, 0.1f, 0, 0.1f, 0.9f, height, 0.1f, sprite.getU0(), sprite.getV0(), sprite.getU1(), sprite.getV1(), pPackedLight, tintColor);
pPoseStack.popPose();
// V coords for sides
float vTop = sprite.getV((1 - yTop) * 16);
float vBottom = sprite.getV((1 - MIN_Y) * 16);
pPoseStack.pushPose();
pPoseStack.mulPose(Axis.YN.rotationDegrees(90));
pPoseStack.translate(0, 0, -1f);
drawQuad(builder, pPoseStack, 0.1f, 0, 0.1f, 0.9f, height, 0.1f, sprite.getU0(), sprite.getV0(), sprite.getU1(), sprite.getV1(), pPackedLight, tintColor);
pPoseStack.popPose();
// Top Face (visible from above)
// Normal +Y
// CCW: (MIN_X, MAX_Z) -> (MAX_X, MAX_Z) -> (MAX_X, MIN_Z) -> (MIN_X, MIN_Z)
addQuad(builder, pPoseStack,
MIN_X, yTop, MAX_Z, uMin, vMaxZ,
MAX_X, yTop, MAX_Z, uMax, vMaxZ,
MAX_X, yTop, MIN_Z, uMax, vMinZ,
MIN_X, yTop, MIN_Z, uMin, vMinZ,
pPackedLight, tintColor);
// Bottom Face (visible from below)
// Normal -Y
// CCW looking from below: (MIN_X, MIN_Z) -> (MAX_X, MIN_Z) -> (MAX_X, MAX_Z) -> (MIN_X, MAX_Z)
addQuad(builder, pPoseStack,
MIN_X, MIN_Y, MIN_Z, uMin, vMinZ,
MAX_X, MIN_Y, MIN_Z, uMax, vMinZ,
MAX_X, MIN_Y, MAX_Z, uMax, vMaxZ,
MIN_X, MIN_Y, MAX_Z, uMin, vMaxZ,
pPackedLight, tintColor);
// North Face (Z=MIN_Z) - Visible from North (-Z)
// Normal -Z
// Vertices: (MAX_X, yTop) -> (MAX_X, MIN_Y) -> (MIN_X, MIN_Y) -> (MIN_X, yTop)
addQuad(builder, pPoseStack,
MAX_X, yTop, MIN_Z, uMax, vTop,
MAX_X, MIN_Y, MIN_Z, uMax, vBottom,
MIN_X, MIN_Y, MIN_Z, uMin, vBottom,
MIN_X, yTop, MIN_Z, uMin, vTop,
pPackedLight, tintColor);
// South Face (Z=MAX_Z) - Visible from South (+Z)
// Normal +Z
// Vertices: (MIN_X, yTop) -> (MIN_X, MIN_Y) -> (MAX_X, MIN_Y) -> (MAX_X, yTop)
addQuad(builder, pPoseStack,
MIN_X, yTop, MAX_Z, uMin, vTop,
MIN_X, MIN_Y, MAX_Z, uMin, vBottom,
MAX_X, MIN_Y, MAX_Z, uMax, vBottom,
MAX_X, yTop, MAX_Z, uMax, vTop,
pPackedLight, tintColor);
// West Face (X=MIN_X) - Visible from West (-X)
// Normal -X
// Vertices: (MIN_X, yTop, MIN_Z) -> (MIN_X, MIN_Y, MIN_Z) -> (MIN_X, MIN_Y, MAX_Z) -> (MIN_X, yTop, MAX_Z)
addQuad(builder, pPoseStack,
MIN_X, yTop, MIN_Z, uMin, vTop,
MIN_X, MIN_Y, MIN_Z, uMin, vBottom,
MIN_X, MIN_Y, MAX_Z, uMax, vBottom,
MIN_X, yTop, MAX_Z, uMax, vTop,
pPackedLight, tintColor);
// East Face (X=MAX_X) - Visible from East (+X)
// Normal +X
// Vertices: (MAX_X, yTop, MAX_Z) -> (MAX_X, MIN_Y, MAX_Z) -> (MAX_X, MIN_Y, MIN_Z) -> (MAX_X, yTop, MIN_Z)
addQuad(builder, pPoseStack,
MAX_X, yTop, MAX_Z, uMax, vTop,
MAX_X, MIN_Y, MAX_Z, uMax, vBottom,
MAX_X, MIN_Y, MIN_Z, uMin, vBottom,
MAX_X, yTop, MIN_Z, uMin, vTop,
pPackedLight, tintColor);
}
private static boolean isMissing(TextureAtlasSprite sprite) {
return sprite == null || sprite.atlasLocation().getPath().contains("missingno");
}
private static void addQuad(VertexConsumer builder, PoseStack poseStack,
float x0, float y0, float z0, float u0, float v0,
float x1, float y1, float z1, float u1, float v1,
float x2, float y2, float z2, float u2, float v2,
float x3, float y3, float z3, float u3, float v3,
int packedLight, int color) {
drawVertex(builder, poseStack, x0, y0, z0, u0, v0, packedLight, color);
drawVertex(builder, poseStack, x1, y1, z1, u1, v1, packedLight, color);
drawVertex(builder, poseStack, x2, y2, z2, u2, v2, packedLight, color);
drawVertex(builder, poseStack, x3, y3, z3, u3, v3, packedLight, color);
}
//? if >1.20.1 {
@@ -104,11 +173,4 @@ public class TankBlockEntityRenderer implements BlockEntityRenderer<TankBlockEnt
.endVertex();
}
//?}
private static void drawQuad(VertexConsumer builder, PoseStack poseStack, float x0, float y0, float z0, float x1, float y1, float z1, float u0, float v0, float u1, float v1, int packedLight, int color) {
drawVertex(builder, poseStack, x0, y0, z0, u0, v0, packedLight, color);
drawVertex(builder, poseStack, x0, y1, z1, u0, v1, packedLight, color);
drawVertex(builder, poseStack, x1, y1, z1, u1, v1, packedLight, color);
drawVertex(builder, poseStack, x1, y0, z0, u1, v0, packedLight, color);
}
}
@@ -79,6 +79,8 @@ public class FluidTankRenderer {
}
TextureAtlasSprite fluidStillSprite = getStillFluidSprite(fluidStack);
if (fluidStillSprite == null) return;
int fluidColor = (int) FluidStackHooks.getColor(fluidStack);
long amount = fluidStack.getAmount();
@@ -91,34 +93,49 @@ public class FluidTankRenderer {
scaledAmount = height;
}
drawTiledSprite(guiGraphics, width, height, fluidColor, scaledAmount, fluidStillSprite);
drawTiledSprite(guiGraphics, width, height, fluidColor, (int) scaledAmount, fluidStillSprite);
}
private TextureAtlasSprite getStillFluidSprite(FluidStack fluidStack) {
ResourceLocation fluidStill = FluidStackHooks.getStillTexture(fluidStack).atlasLocation();
return Minecraft.getInstance().getTextureAtlas(InventoryMenu.BLOCK_ATLAS).apply(fluidStill);
TextureAtlasSprite sprite = FluidStackHooks.getStillTexture(fluidStack);
if (isMissing(sprite)) {
sprite = FluidStackHooks.getStillTexture(fluidStack.getFluid());
}
if (isMissing(sprite)) {
// Fallback for water
if (fluidStack.getFluid() == Fluids.WATER) {
sprite = Minecraft.getInstance().getTextureAtlas(InventoryMenu.BLOCK_ATLAS)
.apply(new ResourceLocation("block/water_still"));
}
}
return sprite;
}
private static void drawTiledSprite(GuiGraphics guiGraphics, final int tiledWidth, final int tiledHeight, int color, long scaledAmount, TextureAtlasSprite sprite) {
private static boolean isMissing(TextureAtlasSprite sprite) {
return sprite == null || sprite.atlasLocation().getPath().contains("missingno");
}
private static void drawTiledSprite(GuiGraphics guiGraphics, final int tiledWidth, final int tiledHeight, int color, int scaledAmount, TextureAtlasSprite sprite) {
RenderSystem.setShaderTexture(0, InventoryMenu.BLOCK_ATLAS);
Matrix4f matrix = guiGraphics.pose().last().pose();
setGLColorFromInt(color);
final int xTileCount = tiledWidth / TEXTURE_SIZE;
final int xRemainder = tiledWidth - (xTileCount * TEXTURE_SIZE);
final long yTileCount = scaledAmount / TEXTURE_SIZE;
final long yRemainder = scaledAmount - (yTileCount * TEXTURE_SIZE);
final int yTileCount = scaledAmount / TEXTURE_SIZE;
final int yRemainder = scaledAmount - (yTileCount * TEXTURE_SIZE);
final int yStart = tiledHeight;
for (int xTile = 0; xTile <= xTileCount; xTile++) {
for (int yTile = 0; yTile <= yTileCount; yTile++) {
int width = (xTile == xTileCount) ? xRemainder : TEXTURE_SIZE;
long height = (yTile == yTileCount) ? yRemainder : TEXTURE_SIZE;
int height = (yTile == yTileCount) ? yRemainder : TEXTURE_SIZE;
int x = (xTile * TEXTURE_SIZE);
int y = yStart - ((yTile + 1) * TEXTURE_SIZE);
if (width > 0 && height > 0) {
long maskTop = TEXTURE_SIZE - height;
int maskTop = TEXTURE_SIZE - height;
int maskRight = TEXTURE_SIZE - width;
drawTextureWithMasking(matrix, x, y, sprite, maskTop, maskRight, 100);
@@ -136,7 +153,7 @@ public class FluidTankRenderer {
RenderSystem.setShaderColor(red, green, blue, alpha);
}
private static void drawTextureWithMasking(Matrix4f matrix, float xCoord, float yCoord, TextureAtlasSprite textureSprite, long maskTop, long maskRight, float zLevel) {
private static void drawTextureWithMasking(Matrix4f matrix, float xCoord, float yCoord, TextureAtlasSprite textureSprite, int maskTop, int maskRight, float zLevel) {
float uMin = textureSprite.getU0();
float uMax = textureSprite.getU1();
float vMin = textureSprite.getV0();
@@ -171,8 +188,6 @@ public class FluidTankRenderer {
Fluid fluidType = fluidStack.getFluid();
try {
if (fluidType.isSame(Fluids.EMPTY)) {
tooltip.add(Component.literal("Empty"));
tooltip.add(Component.translatable("jurassicrevived.tooltip.liquid.amount.with.capacity", 0, nf.format(capacity)).withStyle(ChatFormatting.GRAY));
return tooltip;
}