Rewrite block entity renderers#22
Conversation
# Conflicts: # src/client/java/dev/turtywurty/industria/renderer/block/CentrifugalConcentratorBlockEntityRenderer.java
Todo: Remove map entry when BE is destroyed
# Conflicts: # src/client/java/dev/turtywurty/industria/renderer/block/CentrifugalConcentratorBlockEntityRenderer.java
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
…riaBlockEntityRenderer.java
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
…KilnBlockEntityRenderer.java Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
…KilnBlockEntityRenderer.java Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Added item and particles to Centrifuge BER
# Conflicts: # src/client/java/dev/turtywurty/industria/renderer/block/CentrifugalConcentratorBlockEntityRenderer.java # src/client/java/dev/turtywurty/industria/renderer/block/RotaryKilnBlockEntityRenderer.java # src/main/java/dev/turtywurty/industria/blockentity/RotaryKilnControllerBlockEntity.java
WalkthroughAdded a base renderer API (renderModel and matrixStackToWorldPosition), renamed/redirected many block renderers to a renderModel entry point, refactored fluid rendering APIs, added collision-driven smoke to the rotary kiln, and made a small WrappedFluidStorage deserialization fallback change. Changes
Sequence Diagram(s)sequenceDiagram
participant RendererBase as IndustriaBlockEntityRenderer
participant SubRenderer as Concrete Renderer
participant Camera as Camera
RendererBase->>SubRenderer: render(..., matrices, ...)
note right of RendererBase: push matrices, setupBlockEntityTransformations
RendererBase->>SubRenderer: renderModel(entity, tickDelta, matrices,...)
SubRenderer-->>RendererBase: model drawing
RendererBase->>SubRenderer: onRender(entity, tickDelta, matrices,...)
note right of RendererBase: wireframe if looked-at, postRender, pop
sequenceDiagram
participant GameLoop as Game Loop
participant KilnRenderer as RotaryKilnBlockEntityRenderer
participant Box2D as Physics World (RendererData)
participant Contact as ContactListener
participant World as Minecraft World
GameLoop->>KilnRenderer: render(...)
KilnRenderer->>Box2D: stepPhysics(clamped delta)
Box2D->>Contact: beginContact(item ↔ any)
Contact-->>Box2D: collidedBodies.add(itemBody)
KilnRenderer->>Box2D: for each itemBody in collidedBodies
alt collision detected
Box2D->>KilnRenderer: remove body from set
KilnRenderer->>World: addParticle(SMOKE, worldPosition)
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
Tip 🔌 Remote MCP (Model Context Protocol) integration is now available!Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats. ✨ Finishing Touches
🧪 Generate unit tests
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (4)
src/client/java/dev/turtywurty/industria/renderer/block/IndustriaBlockEntityRenderer.java (1)
101-112: Optional: accept cameraPos to avoid client lookup and improve testability.Minor improvement: overload this helper to accept a cameraPos argument (and have the current method delegate). This avoids repeated MinecraftClient lookups and makes the method usable in non-standard render contexts.
Example:
// Overload protected Vector3f matrixStackToWorldPosition(MatrixStack matrices, Vec3d cameraPos) { Vector3f pos = matrices.peek().getPositionMatrix().transformPosition(0, 0, 0, new Vector3f()); return new Vector3f((float) (pos.x() + cameraPos.x), (float) (pos.y() + cameraPos.y), (float) (pos.z() + cameraPos.z)); } // Current method delegates protected Vector3f matrixStackToWorldPosition(MatrixStack matrices) { Vec3d cam = MinecraftClient.getInstance().gameRenderer.getCamera().getPos(); return matrixStackToWorldPosition(matrices, cam); }src/client/java/dev/turtywurty/industria/renderer/block/CentrifugalConcentratorBlockEntityRenderer.java (1)
113-115: Throttle particle emission to avoid excessive client-side load.As written, each frame spawns NUM_SPINNING_ITEMS bubbles. Consider a simple time-based throttle so effects remain visible without overwhelming particles.
Apply within this hunk:
- Vector3f pos = matrixStackToWorldPosition(matrices); - entity.getWorld().addParticleClient(ParticleTypes.BUBBLE, pos.x, pos.y + 0.25, pos.z, 0, 0, 0); + // Emit roughly every 3 ticks per item; offset by i to stagger + if ((entity.getWorld().getTime() + i) % 3 == 0) { + Vector3f pos = matrixStackToWorldPosition(matrices); + entity.getWorld().addParticleClient(ParticleTypes.BUBBLE, pos.x, pos.y + 0.25, pos.z, 0, 0, 0); + }src/client/java/dev/turtywurty/industria/renderer/block/RotaryKilnBlockEntityRenderer.java (2)
118-121: Collision-driven smoke is good; consider tying intensity to collision strength.Currently, every begin-contact triggers one smoke puff. You can make effects feel more physical by emitting only for impactful contacts (or emitting more smoke for larger impulses).
Option A (minimal): gate puffs by linear speed
if (rendererData.collidedBodies.remove(body)) { Vec2 v = body.getLinearVelocity(); if (v.lengthSquared() > 0.25f) { // speed > 0.5 m/s Vector3f position = matrixStackToWorldPosition(matrices); entity.getWorld().addParticleClient(ParticleTypes.SMOKE, position.x, position.y, position.z, 0, 0, 0); } }Option B (preferred): use contact impulse in postSolve (see next comment) to decide when to add to collidedBodies based on an impulse threshold.
174-195: Use postSolve impulse to gate smoke on “significant” collisions.Leverage ContactImpulse to add bodies to collidedBodies only when the normal impulse exceeds a threshold. This avoids puffs from grazing contacts.
Apply within this hunk:
box2dWorld.setContactListener(new ContactListener() { @Override public void beginContact(Contact contact) { - Body a = contact.getFixtureA().getBody(); - Body b = contact.getFixtureB().getBody(); - - if (a.getUserData() != null && a.getUserData().equals("item")) - collidedBodies.add(a); - - if (b.getUserData() != null && b.getUserData().equals("item")) - collidedBodies.add(b); + // No-op: we’ll decide in postSolve based on impulse magnitude } @Override public void endContact(Contact contact) {} @Override public void preSolve(Contact contact, Manifold oldManifold) {} @Override - public void postSolve(Contact contact, ContactImpulse impulse) {} + public void postSolve(Contact contact, ContactImpulse impulse) { + // Compute max normal impulse across contact points + float maxImpulse = 0f; + float[] normals = impulse.normalImpulses; + for (int i = 0; i < normals.length; i++) { + maxImpulse = Math.max(maxImpulse, normals[i]); + } + + // Threshold tuneable; ~0.75–1.5 feels reasonable + if (maxImpulse > 1.0f) { + Body a = contact.getFixtureA().getBody(); + Body b = contact.getFixtureB().getBody(); + if ("item".equals(a.getUserData())) collidedBodies.add(a); + if ("item".equals(b.getUserData())) collidedBodies.add(b); + } + } });This produces smoke when collisions are “hard” enough and avoids visual noise.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (3)
src/client/java/dev/turtywurty/industria/renderer/block/CentrifugalConcentratorBlockEntityRenderer.java(1 hunks)src/client/java/dev/turtywurty/industria/renderer/block/IndustriaBlockEntityRenderer.java(1 hunks)src/client/java/dev/turtywurty/industria/renderer/block/RotaryKilnBlockEntityRenderer.java(5 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build (21)
🔇 Additional comments (4)
src/client/java/dev/turtywurty/industria/renderer/block/IndustriaBlockEntityRenderer.java (1)
101-112: LGTM: Shared world-position helper is correct and useful.Transforming the local origin and adding the camera position is the right way to derive a world-space coordinate from the current MatrixStack state in BE renderers. Good extraction for reuse.
src/client/java/dev/turtywurty/industria/renderer/block/CentrifugalConcentratorBlockEntityRenderer.java (1)
113-115: LGTM: Using the shared helper removes duplication and clarifies intent.The switch to matrixStackToWorldPosition(matrices) is correct and aligns with the new base utility.
src/client/java/dev/turtywurty/industria/renderer/block/RotaryKilnBlockEntityRenderer.java (2)
86-87: LGTM: Clamping deltaTime stabilizes the Box2D simulation.The [0.01, 0.1]s clamp guards against tunneling and unstable steps on slow/fast frames.
137-138: LGTM: Tagging item bodies with userData simplifies contact filtering.Marking item bodies with "item" is a clean, low-overhead way to identify them in the contact listener.
| private final Body barrelBody; | ||
|
|
||
| private long lastRenderTime = System.nanoTime(); | ||
| private final Set<Body> collidedBodies = Collections.synchronizedSet(new HashSet<>()); |
There was a problem hiding this comment.
Fix memory leak: collidedBodies can retain destroyed Body references.
Bodies removed from recipeToBodyMap and destroyed are never pruned from collidedBodies, so those Body references leak. Ensure you remove the body from collidedBodies when destroying and periodically prune stale entries.
Proposed fixes (outside this hunk):
- When destroying a body (in renderItems(), during map cleanup), also drop it from collidedBodies:
if (!entity.getRecipes().contains(entry.getKey())) {
rendererData.collidedBodies.remove(entry.getValue()); // prune from signal set
box2dWorld.destroyBody(entry.getValue());
iterator.remove();
}- Additionally, add a safety prune early in renderItems() after you get recipeToBodyMap:
rendererData.collidedBodies.retainAll(recipeToBodyMap.values());These two changes prevent unbounded growth in collidedBodies across recipe churn.
🤖 Prompt for AI Agents
In
src/client/java/dev/turtywurty/industria/renderer/block/RotaryKilnBlockEntityRenderer.java
around line 170, collidedBodies currently holds Body references that are
destroyed/removed from recipeToBodyMap causing a memory leak; when you destroy
or remove a body you must also remove it from collidedBodies (call
collidedBodies.remove(body) at the same point you destroy the body and remove it
from the map), and additionally prune stale entries at the start of
renderItems() after obtaining recipeToBodyMap by retaining only current bodies
(call collidedBodies.retainAll(recipeToBodyMap.values())) to prevent unbounded
growth.
Started rework in IndustriaBlockEntityRenderer to have 2 render functions for flipped model and the rest
There was a problem hiding this comment.
Actionable comments posted: 9
🔭 Outside diff range comments (1)
src/client/java/dev/turtywurty/industria/renderer/block/CentrifugalConcentratorBlockEntityRenderer.java (1)
31-49: Rotation math mixes units (revolutions vs radians) and will render incorrectly.
entity.bowlRotationis used as an angle (radians) later (e.g., with Math.TAU and asyaw), but it is incremented in revolutions and wrapped by 360f (degrees). This produces wrong rotation speeds and jitter across frames.Fix by accumulating radians and wrapping by τ:
- if (progress == 0 || Double.isNaN(progress)) { - entity.bowlRotation = 0f; - } else { - entity.bowlRotation = (entity.bowlRotation + (rpm / 60f / 20f) * tickDelta) % 360f; - } + if (progress == 0 || Double.isNaN(progress)) { + entity.bowlRotation = 0f; + } else { + // rpm = revolutions per minute; per tick = rpm / 1200; convert to radians and scale by tickDelta + float deltaRad = (rpm / 60f) * ((float) Math.TAU) / 20f * tickDelta; + entity.bowlRotation = (entity.bowlRotation + deltaRad) % (float) Math.TAU; + }
🧹 Nitpick comments (23)
src/client/java/dev/turtywurty/industria/renderer/block/IndustriaBlockEntityRenderer.java (1)
110-121: Use the already-available cameraPos to avoid global lookups and improve correctness.
matrixStackToWorldPositionqueries the camera viaMinecraftClienteven thoughrender(...)already receivescameraPos. Prefer an overload that takescameraPos(and have the current method delegate), which also makes the utility usable in non-standard call sites.Suggested addition:
protected Vector3f matrixStackToWorldPosition(MatrixStack matrices, Vec3d cameraPos) { Vector3f pos = matrices.peek().getPositionMatrix().transformPosition(0, 0, 0, new Vector3f()); return new Vector3f((float) (pos.x() + cameraPos.x), (float) (pos.y() + cameraPos.y), (float) (pos.z() + cameraPos.z)); } // Backward-compatible delegate protected Vector3f matrixStackToWorldPosition(MatrixStack matrices) { Vec3d cameraPos = MinecraftClient.getInstance().gameRenderer.getCamera().getPos(); return matrixStackToWorldPosition(matrices, cameraPos); }src/client/java/dev/turtywurty/industria/renderer/block/MultiblockIOBlockEntityRenderer.java (1)
72-85: Avoid per-frame allocations for colorsgetColor creates a new float[] on every call. Cache constant colors to reduce GC pressure when hitboxes are visible.
Proposed approach (add these constants near the top of the class):
private static final float[] COLOR_ITEM = {0.0F, 0.0F, 0.0F}; private static final float[] COLOR_ENERGY = {1.0F, 1.0F, 51 / 255F}; private static final float[] COLOR_FLUID = {135 / 255F, 206 / 255F, 250 / 255F}; private static final float[] COLOR_SLURRY = {139 / 255F, 69 / 255F, 19 / 255F}; private static final float[] COLOR_HEAT = {1.0F, 127 / 255F, 80 / 255F}; private static final float[] COLOR_GAS = {58 / 255F, 159 / 255F, 2 / 255F};Then change getColor to return these constants:
- if (type == TransferType.ITEM) { - return new float[]{0.0F, 0.0F, 0.0F}; - } else if (type == TransferType.ENERGY) { - return new float[]{1.0F, 1.0F, 51 / 255F}; - } else if (type == TransferType.FLUID) { - return new float[]{135 / 255F, 206 / 255F, 250 / 255F}; - } else if (type == TransferType.SLURRY) { - return new float[]{139 / 255F, 69 / 255F, 19 / 255F}; - } else if (type == TransferType.HEAT) { - return new float[]{1.0F, 127 / 255F, 80 / 255F}; - } else if (type == TransferType.GAS) { - return new float[]{58 / 255F, 159 / 255F, 2 / 255F}; - } + if (type == TransferType.ITEM) { + return COLOR_ITEM; + } else if (type == TransferType.ENERGY) { + return COLOR_ENERGY; + } else if (type == TransferType.FLUID) { + return COLOR_FLUID; + } else if (type == TransferType.SLURRY) { + return COLOR_SLURRY; + } else if (type == TransferType.HEAT) { + return COLOR_HEAT; + } else if (type == TransferType.GAS) { + return COLOR_GAS; + }src/client/java/dev/turtywurty/industria/renderer/block/MotorBlockEntityRenderer.java (1)
23-30: Restore spinRod pitch to previous value and keep code consistentSmall robustness/readability improvement: save and restore the previous pitch instead of hard-coding 0. Also prefer using this.model consistently.
Apply this diff:
protected void renderModel(MotorBlockEntity entity, float tickDelta, MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, int overlay) { float rotationSpeed = entity.getRotationSpeed(); entity.rodRotation += rotationSpeed * tickDelta; - this.model.getMotorParts().spinRod().pitch = entity.rodRotation; - model.render(matrices, vertexConsumers.getBuffer(model.getLayer(MotorModel.TEXTURE_LOCATION)), light, overlay); - this.model.getMotorParts().spinRod().pitch = 0; + float prevSpinRodPitch = this.model.getMotorParts().spinRod().pitch; + this.model.getMotorParts().spinRod().pitch = entity.rodRotation; + this.model.render(matrices, vertexConsumers.getBuffer(this.model.getLayer(MotorModel.TEXTURE_LOCATION)), light, overlay); + this.model.getMotorParts().spinRod().pitch = prevSpinRodPitch; }Optional: bound entity.rodRotation to [0, 2π) each frame to avoid unbounded growth over long sessions:
entity.rodRotation = (entity.rodRotation % ((float) (Math.PI * 2)));src/client/java/dev/turtywurty/industria/renderer/block/CrusherBlockEntityRenderer.java (1)
27-47: Model state save/restore and progressive roll — goodCapturing and restoring roll on all four parts prevents state bleed between frames. Using entity.getProgress()/100.0F is clear.
If getProgress can exceed 100 or be negative, consider clamping to [0, 100] before dividing to keep roll in a predictable range.
src/client/java/dev/turtywurty/industria/renderer/block/DrillBlockEntityRenderer.java (4)
62-80: Clarify motor rotation update and avoid compound assignment gotchasThe chained assignment obscures intent. Compute delta explicitly, update clientMotorRotation, then apply to parts. This keeps behavior and improves readability.
Apply this diff:
- if (!entity.isDrilling() && !entity.isRetracting()) { - entity.clientMotorRotation = 0; - } - - parts.rodGear().pitch += entity.clientMotorRotation += (entity.isDrilling() ? 0.03f : entity.isRetracting() ? -0.03f : 0); - parts.connectingGear().pitch = -entity.clientMotorRotation; + if (!entity.isDrilling() && !entity.isRetracting()) { + entity.clientMotorRotation = 0f; + } + float delta = entity.isDrilling() ? 0.03f : (entity.isRetracting() ? -0.03f : 0f); + entity.clientMotorRotation += delta; + parts.rodGear().pitch += entity.clientMotorRotation; + parts.connectingGear().pitch = -entity.clientMotorRotation;Optional: consider bounding entity.clientMotorRotation to [0, 2π) to avoid runaway values over time:
entity.clientMotorRotation = (float) (entity.clientMotorRotation % (Math.PI * 2));
95-110: Restore cable scaling using previous absolute scales to prevent driftUsing +/- cableScaleFactor assumes a known base scale. Save and restore exact scales to avoid cumulative floating-point drift or unexpected base changes.
Apply this diff:
- float prevCableMainPitch = cableMain.pitch; - cableMain.pitch = entity.clientMotorRotation; - float cableScaleFactor = 0.5f - (progress / 2f); - - cableMain.xScale -= cableScaleFactor; - cableMain.yScale -= cableScaleFactor; - cableMain.zScale -= cableScaleFactor; + float prevCableMainPitch = cableMain.pitch; + cableMain.pitch = entity.clientMotorRotation; + float cableScaleFactor = 0.5f - (progress / 2f); + float prevXScale = cableMain.xScale, prevYScale = cableMain.yScale, prevZScale = cableMain.zScale; + cableMain.xScale = prevXScale - cableScaleFactor; + cableMain.yScale = prevYScale - cableScaleFactor; + cableMain.zScale = prevZScale - cableScaleFactor; this.cableModel.render(matrices, vertexConsumers.getBuffer(this.cableModel.getLayer(DrillCableModel.TEXTURE_LOCATION)), light, overlay); - cableMain.xScale += cableScaleFactor; - cableMain.yScale += cableScaleFactor; - cableMain.zScale += cableScaleFactor; + cableMain.xScale = prevXScale; + cableMain.yScale = prevYScale; + cableMain.zScale = prevZScale; cableMain.pitch = prevCableMainPitch;
121-130: Simplify angleOffset logic (nested ternary is redundant)Both branches of the outer ternary yield the same outcome; the logic can be reduced for clarity without changing behavior.
Apply this diff:
- float angleOffset = (float) (entity.isRetracting() ? - -entity.clientMotorRotation < 0 ? -Math.PI / 4 : -Math.PI / 4 - Math.PI / 2 : - -entity.clientMotorRotation < 0 ? -3 * Math.PI / 4 + Math.PI / 2 : -3 * Math.PI / 4); + float quarterPi = (float) (Math.PI / 4.0); + float angleOffset = (-entity.clientMotorRotation < 0) ? -quarterPi : -3.0f * quarterPi; - float angle = (float) (-entity.clientMotorRotation % (Math.PI / 2f)) + angleOffset; + float angle = (float) ((-entity.clientMotorRotation) % (Math.PI / 2.0)) + angleOffset;
156-159: onRender visibility differs from other renderersThis override is public while others are protected. It compiles (wider visibility is allowed), but consider making it protected for consistency with the base API.
src/client/java/dev/turtywurty/industria/renderer/block/CentrifugalConcentratorBlockEntityRenderer.java (1)
89-129: Clamp fill percent and minor robustness.
- Add a clamp to keep
fillPercentwithin [0,1] to avoid overflow rendering on sync glitches.- Early returns are fine, but also consider guarding
entity.getWorld()to avoid NPE when computingfluidColor.- float fillPercent = fluidTank.amount / (float) fluidTank.getCapacity(); + float fillPercent = MathHelper.clamp(fluidTank.amount / (float) fluidTank.getCapacity(), 0f, 1f); @@ - int fluidColor = FluidVariantRendering.getColor(fluidVariant, entity.getWorld(), entity.getPos()); + int fluidColor = FluidVariantRendering.getColor(fluidVariant, entity.getWorld(), entity.getPos());(If
entity.getWorld()can be null during early client init, add a null check and default color.)src/client/java/dev/turtywurty/industria/renderer/block/WindTurbineBlockEntityRenderer.java (1)
25-34: Propeller rotation is frame-rate dependent; incorporate tickDelta or time-based rotation.Accumulating a fixed amount per render call speeds up with FPS. Use tickDelta or world time to stabilize.
- float outputPercentage = getEnergyPerTickPercent(entity); - entity.setPropellerRotation(entity.getPropellerRotation() + (outputPercentage * 0.25f)); + float outputPercentage = getEnergyPerTickPercent(entity); + float delta = outputPercentage * 0.25f * tickDelta; // scale by partial tick + entity.setPropellerRotation((entity.getPropellerRotation() + delta) % (float) Math.TAU);src/client/java/dev/turtywurty/industria/renderer/block/OilPumpJackBlockEntityRenderer.java (1)
30-76: Client rotation accumulates per frame; stabilize by ticks or time.
clientRotation += 0.1f * tickDeltainside render accumulates based on render calls, not ticks, causing FPS-dependent speed and inconsistent 2π crossing logic forreverseCounterWeights.Two options:
- Minimum change: scale by ticks using world time and keep crossing detection.
- if (entity.isRunning()) { - clientRotation = clientRotation + 0.1f * tickDelta; + if (entity.isRunning()) { + // Advance by a fixed rad/tick scaled by tickDelta. Consider moving this to a client tick instead. + clientRotation = clientRotation + 0.1f * tickDelta; if (clientRotation > Math.PI * 2) { clientRotation -= (float) (Math.PI * 2); entity.reverseCounterWeights = !entity.reverseCounterWeights; } entity.clientRotation = clientRotation; }
- Better: update
clientRotationon a per-tick client-side hook (block entity client tick) and only interpolate in renderer.If you want, I can provide a small client-tick updater to keep renderer purely presentational.
src/client/java/dev/turtywurty/industria/renderer/block/FluidTankBlockEntityRenderer.java (1)
19-22: Empty renderModel hook — add a brief note or remove override if intentionally unusedIf this renderer has no model to draw (fluid-only), consider adding a short comment to clarify intent or remove the override to avoid confusion for future readers.
Apply this diff to add an intent comment:
@Override protected void renderModel(FluidTankBlockEntity entity, float tickDelta, MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, int overlay) { - + // Intentionally left empty: fluid tank renders via InWorldFluidRenderingComponent only. }src/client/java/dev/turtywurty/industria/renderer/block/ShakingTableBlockEntityRenderer.java (3)
29-37: Use tickDelta and proper angular frequency (2πf) for smooth, FPS-independent shakingCurrent code uses world time in ticks and πf, which both skews the frequency (by ~2x) and ignores tickDelta. Compute using seconds and 2πf, and handle null world gracefully.
Apply this diff:
-private float getShakeOffset(ShakingTableBlockEntity entity) { - float shakesPerSecond = entity.getRecipeFrequency(); - float time = entity.getWorld().getTime(); - float frequency = shakesPerSecond * (float) Math.PI; - float shakeAmount = 2f; - return (float) Math.sin(time * frequency) * shakeAmount; -} +private float getShakeOffset(ShakingTableBlockEntity entity, float tickDelta) { + var world = entity.getWorld(); + if (world == null) return 0f; + + float shakesPerSecond = entity.getRecipeFrequency(); // f in Hz + float timeSeconds = (world.getTime() + tickDelta) / 20.0f; // ticks -> seconds + float angularFrequency = (float) (Math.PI * 2.0) * shakesPerSecond; // ω = 2πf + float shakeAmount = 2f; + return MathHelper.sin(timeSeconds * angularFrequency) * shakeAmount; +}Note: This switches to MathHelper.sin for consistency with other math in this class.
46-48: Update call site to pass tickDelta to getShakeOffsetApply this diff:
- if (progress > 0 && progress < maxProgress) { - this.model.getModelParts().table().originZ += getShakeOffset(entity); - } + if (progress > 0 && progress < maxProgress) { + this.model.getModelParts().table().originZ += getShakeOffset(entity, tickDelta); + }
55-57: Pass tickDelta through for consistent shake application across all sub-rendersApply this diff:
- protected void onRender(ShakingTableBlockEntity entity, float tickDelta, MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, int overlay) { - float shakeOffset = getShakeOffset(entity); + protected void onRender(ShakingTableBlockEntity entity, float tickDelta, MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, int overlay) { + float shakeOffset = getShakeOffset(entity, tickDelta);src/client/java/dev/turtywurty/industria/renderer/block/MixerBlockEntityRenderer.java (3)
27-33: Avoid frame-rate–dependent stirring rotation; derive yaw from world timeMutating entity.stirringRotation in the renderer ties speed to frame rate. Compute yaw from world time (with tickDelta) for determinism and remove the side effect. Also gate rotation on isMixing to avoid spinning when idle.
Apply this diff:
-@Override -protected void renderModel(MixerBlockEntity entity, float tickDelta, MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, int overlay) { - this.model.getMixerParts().stirring_rods().yaw = entity.stirringRotation; - this.model.getMixerParts().main().render(matrices, vertexConsumers.getBuffer(this.model.getLayer(MixerModel.TEXTURE_LOCATION)), light, overlay); - this.model.getMixerParts().stirring_rods().yaw = 0.0F; -} +@Override +protected void renderModel(MixerBlockEntity entity, float tickDelta, MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, int overlay) { + var world = entity.getWorld(); + float time = world != null ? world.getTime() + tickDelta : 0f; // ticks + partials + boolean isMixing = entity.isMixing(); + float rotationPerTick = 0.1f; // radians per tick (matches previous visual speed) + float yaw = isMixing ? time * rotationPerTick : 0f; + + this.model.getMixerParts().stirring_rods().yaw = yaw; + this.model.getMixerParts().main().render(matrices, vertexConsumers.getBuffer(this.model.getLayer(MixerModel.TEXTURE_LOCATION)), light, overlay); + this.model.getMixerParts().stirring_rods().yaw = 0.0F; +}
35-41: Remove per-frame mutation of entity.stirringRotation in the rendererOnce yaw derives from time, this side effect is no longer needed and avoids FPS dependence.
Apply this diff:
- boolean isMixing = entity.isMixing(); - if (isMixing) { - entity.stirringRotation = (entity.stirringRotation + 0.1f) % 360; - } + boolean isMixing = entity.isMixing();
85-91: Rotation units: rotation() expects radians; confirm intended spin rateYou’re passing worldTime * 0.25f into rotation(), which is radians, i.e., ~14.3° per tick (~286°/s). If you intended degrees, use rotationDegrees(...). Otherwise, all good.
Apply this alternative if degrees were intended:
- matrices.multiply(RotationAxis.POSITIVE_Y.rotation(entity.getWorld().getTime() * 0.25f)); - matrices.multiply(RotationAxis.POSITIVE_X.rotation(entity.getWorld().getTime() * 0.25f)); - matrices.multiply(RotationAxis.POSITIVE_Z.rotation(entity.getWorld().getTime() * 0.25f)); + float deg = entity.getWorld().getTime() * 0.25f; + matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(deg)); + matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees(deg)); + matrices.multiply(RotationAxis.POSITIVE_Z.rotationDegrees(deg));src/client/java/dev/turtywurty/industria/renderer/block/ClarifierBlockEntityRenderer.java (5)
71-92: Hard-coded output stack (DIAMOND_BLOCK x64) — replace with actual state or gate behind debug
This looks like placeholder/debug content. Consider wiring it to entity.getOutputInventory() (or similar) before merge, or annotate with a clear TODO and debug flag to avoid shipping obvious placeholders.If you want, I can help wire this to the real inventory API and keep a fallback for empty stacks.
95-137: Current output item uses world-time animation and DIAMOND_BLOCK — hook into real progress
This is a nice animation scaffold, but “nextOutput” and “itemProgress” should come from the block entity’s actual next output and processing progress. Passing tickDelta through allows smooth interpolation.Proposed changes:
- Pass tickDelta as a parameter into renderCurrentOutputItem.
- Replace world-time progress with (float) entity.getProgress() / entity.getMaxProgress().
- Replace hard-coded item with entity.getNextOutputItemStack().
I can provide a patch if you confirm the appropriate getters.
139-162: Input fluid rendering is commented out and replaced with a water placeholder — migrate to the new API
The previous top-face/tiled calls are commented. Align with the new InWorldFluidRenderingComponent API used elsewhere (renderFluidTank(...), or the new directional method if available) and drive the variant/amount from the entity’s SyncingFluidStorage.Would you like me to draft the migration to renderFluidTank with the correct bounds for the input basin?
164-194: Output fluid rendering likewise uses a placeholder — re-enable real output fluid
Same as input fluid: re-enable using the actual output tank and migrate to the new API method rather than leaving placeholders/comments.As a checklist:
- Use entity.getOutputFluidTank()
- Guard for isResourceBlank/amount <= 0
- Compute fluid height from capacity and amount
- Call renderFluidTank(...) with the clarifier’s output channel bounds
101-133: Consider tickDelta for item animation smoothing
If you keep time-based animation here, using (world.getTime() + tickDelta) will reduce judder at low FPS.Apply this diff to the progress calculation:
- float itemProgress = (blockEntity.getWorld().getTime() / 100f) % 1; + float itemProgress = ((blockEntity.getWorld().getTime() + tickDelta) / 100f) % 1;Note: This requires passing tickDelta into renderCurrentOutputItem.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (20)
src/client/java/dev/turtywurty/industria/renderer/block/ArcFurnaceBlockEntityRenderer.java(1 hunks)src/client/java/dev/turtywurty/industria/renderer/block/CentrifugalConcentratorBlockEntityRenderer.java(2 hunks)src/client/java/dev/turtywurty/industria/renderer/block/ClarifierBlockEntityRenderer.java(5 hunks)src/client/java/dev/turtywurty/industria/renderer/block/CrusherBlockEntityRenderer.java(2 hunks)src/client/java/dev/turtywurty/industria/renderer/block/CrystallizerBlockEntityRenderer.java(2 hunks)src/client/java/dev/turtywurty/industria/renderer/block/DigesterBlockEntityRenderer.java(1 hunks)src/client/java/dev/turtywurty/industria/renderer/block/DrillBlockEntityRenderer.java(2 hunks)src/client/java/dev/turtywurty/industria/renderer/block/ElectrolyzerBlockEntityRenderer.java(1 hunks)src/client/java/dev/turtywurty/industria/renderer/block/FluidTankBlockEntityRenderer.java(2 hunks)src/client/java/dev/turtywurty/industria/renderer/block/IndustriaBlockEntityRenderer.java(3 hunks)src/client/java/dev/turtywurty/industria/renderer/block/MixerBlockEntityRenderer.java(2 hunks)src/client/java/dev/turtywurty/industria/renderer/block/MotorBlockEntityRenderer.java(1 hunks)src/client/java/dev/turtywurty/industria/renderer/block/MultiblockIOBlockEntityRenderer.java(3 hunks)src/client/java/dev/turtywurty/industria/renderer/block/OilPumpJackBlockEntityRenderer.java(3 hunks)src/client/java/dev/turtywurty/industria/renderer/block/RotaryKilnBlockEntityRenderer.java(7 hunks)src/client/java/dev/turtywurty/industria/renderer/block/ShakingTableBlockEntityRenderer.java(7 hunks)src/client/java/dev/turtywurty/industria/renderer/block/UpgradeStationBlockEntityRenderer.java(1 hunks)src/client/java/dev/turtywurty/industria/renderer/block/WindTurbineBlockEntityRenderer.java(1 hunks)src/client/java/dev/turtywurty/industria/util/InWorldFluidRenderingComponent.java(2 hunks)src/main/java/dev/turtywurty/industria/blockentity/util/fluid/WrappedFluidStorage.java(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- src/client/java/dev/turtywurty/industria/renderer/block/RotaryKilnBlockEntityRenderer.java
🧰 Additional context used
🧬 Code Graph Analysis (13)
src/client/java/dev/turtywurty/industria/renderer/block/DigesterBlockEntityRenderer.java (1)
src/client/java/dev/turtywurty/industria/model/DigesterModel.java (1)
DigesterModel(13-31)
src/client/java/dev/turtywurty/industria/renderer/block/ElectrolyzerBlockEntityRenderer.java (1)
src/client/java/dev/turtywurty/industria/model/ElectrolyzerModel.java (1)
ElectrolyzerModel(13-32)
src/client/java/dev/turtywurty/industria/renderer/block/UpgradeStationBlockEntityRenderer.java (1)
src/client/java/dev/turtywurty/industria/model/UpgradeStationModel.java (1)
UpgradeStationModel(13-28)
src/client/java/dev/turtywurty/industria/renderer/block/ShakingTableBlockEntityRenderer.java (1)
src/client/java/dev/turtywurty/industria/model/ShakingTableModel.java (1)
ShakingTableModel(9-181)
src/client/java/dev/turtywurty/industria/renderer/block/CrystallizerBlockEntityRenderer.java (1)
src/client/java/dev/turtywurty/industria/model/CrystallizerModel.java (1)
CrystallizerModel(9-38)
src/client/java/dev/turtywurty/industria/renderer/block/WindTurbineBlockEntityRenderer.java (1)
src/client/java/dev/turtywurty/industria/model/WindTurbineModel.java (1)
WindTurbineModel(9-58)
src/client/java/dev/turtywurty/industria/renderer/block/OilPumpJackBlockEntityRenderer.java (1)
src/client/java/dev/turtywurty/industria/model/OilPumpJackModel.java (1)
OilPumpJackModel(9-343)
src/client/java/dev/turtywurty/industria/renderer/block/MotorBlockEntityRenderer.java (1)
src/client/java/dev/turtywurty/industria/model/MotorModel.java (1)
MotorModel(9-53)
src/client/java/dev/turtywurty/industria/renderer/block/MultiblockIOBlockEntityRenderer.java (1)
src/main/java/dev/turtywurty/industria/multiblock/TransferType.java (1)
TransferType(40-287)
src/client/java/dev/turtywurty/industria/renderer/block/ClarifierBlockEntityRenderer.java (1)
src/main/java/dev/turtywurty/industria/init/list/TagList.java (2)
Fluids(30-38)Items(13-20)
src/client/java/dev/turtywurty/industria/util/InWorldFluidRenderingComponent.java (1)
src/main/java/dev/turtywurty/industria/init/list/TagList.java (1)
Fluids(30-38)
src/client/java/dev/turtywurty/industria/renderer/block/MixerBlockEntityRenderer.java (1)
src/client/java/dev/turtywurty/industria/model/MixerModel.java (1)
MixerModel(9-64)
src/client/java/dev/turtywurty/industria/renderer/block/DrillBlockEntityRenderer.java (3)
src/client/java/dev/turtywurty/industria/model/DrillMotorModel.java (1)
DrillMotorModel(9-70)src/client/java/dev/turtywurty/industria/model/DrillFrameModel.java (1)
DrillFrameModel(9-92)src/client/java/dev/turtywurty/industria/model/DrillCableModel.java (1)
DrillCableModel(9-36)
🔇 Additional comments (28)
src/client/java/dev/turtywurty/industria/renderer/block/IndustriaBlockEntityRenderer.java (2)
84-85: New renderModel hook — LGTM.Good separation of concerns: transforms orchestrated in base
render(...), static model draw lives inrenderModel(...).
186-196: Javadoc parameter rename: looks good.The updated parameter names (“modelPart”) improve clarity and match the code.
src/client/java/dev/turtywurty/industria/renderer/block/ElectrolyzerBlockEntityRenderer.java (2)
18-21: Migration to renderModel is correct.Rendering is now encapsulated in
renderModel(...)andonRender(...)is intentionally empty. This aligns with the new base renderer flow.
22-24: Confirm that onRender being empty is intentional.If any dynamic overlays/effects were previously drawn in
onRender(...), they won’t appear now unless moved torenderModel(...)or kept inonRender(...).src/client/java/dev/turtywurty/industria/renderer/block/DigesterBlockEntityRenderer.java (2)
19-21: Migration to renderModel is correct.Model rendering moved into
renderModel(...)using the model’s root part and correct layer;onRender(...)is intentionally empty.
23-25: Confirm that onRender being empty is intentional.If there were dynamic extras previously, ensure they are either retained in
onRender(...)or folded intorenderModel(...).src/client/java/dev/turtywurty/industria/renderer/block/MultiblockIOBlockEntityRenderer.java (2)
23-26: No-op renderModel override aligns with new renderer lifecycleLeaving renderModel empty here is fine since this renderer only draws debug visuals in onRender.
30-30: Hitbox guard refactor is fineSingle-line early return improves readability without changing behavior.
src/client/java/dev/turtywurty/industria/renderer/block/UpgradeStationBlockEntityRenderer.java (4)
13-14: Migration to IndustriaBlockEntityRenderer looks goodExtending the common base matches the new renderModel/onRender lifecycle used across the PR.
18-20: Constructor now defers to base context — goodConstructing the model via context layer is correct and consistent.
23-26: Confirm transforms are handled by the base rendererrenderModel now only renders geometry; ensure IndustriaBlockEntityRenderer applies the expected push/pop, translation, and facing rotation that used to live here.
Would you confirm that IndustriaBlockEntityRenderer performs the block-facing transform before calling renderModel?
28-31: onRender hook addedNo-op hook is fine; provides a place for future overlays or debug visuals without polluting model rendering.
src/client/java/dev/turtywurty/industria/renderer/block/MotorBlockEntityRenderer.java (2)
13-14: Renderer migrated to the new base — LGTMAdopts the same lifecycle as other renderers in this PR.
32-35: onRender hook presentNo-op hook aligns with the new lifecycle and keeps model rendering focused.
src/client/java/dev/turtywurty/industria/renderer/block/CrusherBlockEntityRenderer.java (2)
16-24: Base-class migration and model init — goodRenderer now extends IndustriaBlockEntityRenderer and initializes the model via the layer; consistent with the rest of the refactor.
49-53: onRender hook addedNo-op is fine for now; consistent lifecycle.
src/client/java/dev/turtywurty/industria/renderer/block/DrillBlockEntityRenderer.java (2)
45-60: Render flow decomposition is solidSplitting motor/frame/cable/drill into helpers improves readability and testability. Sequence in renderModel is coherent.
140-146: Line rendering sequence: verify intended segmentsYou push three vertices before the translate (A -> B -> B). If the duplicate B is only to “end” the previous segment, consider either omitting it or adding a short comment to make intent clear for future maintainers.
Is the duplicate vertex at Line 142 intentional to reset the segment for the next cable section? If not, dropping it will avoid a degenerate segment.
src/client/java/dev/turtywurty/industria/renderer/block/OilPumpJackBlockEntityRenderer.java (1)
84-88: Confirm base-class handles block facing transforms.Previous versions rotated by
Properties.HORIZONTAL_FACING; this renderer no longer applies facing transforms. IfIndustriaBlockEntityRenderernow standardizes transforms, all good; otherwise this model may render un-rotated.src/client/java/dev/turtywurty/industria/renderer/block/CrystallizerBlockEntityRenderer.java (2)
68-72: Model rendering hook looks good.Adopts the new renderModel entry point cleanly and delegates buffer retrieval correctly.
104-118: Switched to renderFluidTank API correctly; verify upstream fluid renderer fixes.The calls and bounds look consistent. Note that current InWorldFluidRenderingComponent has guards disabled and forces water; once fixed per my comment there, these calls should render the correct fluids and fill amounts.
src/client/java/dev/turtywurty/industria/renderer/block/FluidTankBlockEntityRenderer.java (1)
27-33: Verify fluid-tank translation & boundsComparing with the other block-entity renderers’
renderFluidTankcalls, this one is unique in that it does:matrices.translate(-0.5, -1.5, -0.5); this.fluidRenderingComponent.renderFluidTank( …, 3/16f, 0, 3/16f, 13/16f, 15, 13/16f );No other renderer applies a
(-0.5, -1.5, -0.5)shift, and the other calls all pass their Y-max as a float in block-space (e.g.1.999f), not an integer “15”. Please verify that:
- The BE-renderer’s incoming
matricesorigin is where you expect (typically block-origin at the bottom northwest corner).- You actually want to move down
1.5blocks and back0.5blocks—if not, flip the signs on Y (and/or Z) to position the tank correctly.- The min/max bounds (
3/16f → 13/16f) are in the intended [0…1] block-space region of the tank.- The “15” height isn’t meant to be
15/16f. If the API expects block-space floats, change it to15/16f.If any of these are off, consider reverting to something like:
-matrices.translate(-0.5, -1.5, -0.5); +matrices.translate(-0.5f, 1.5f, -0.5f); // or adjust Z sign as needed … - 3/16f, 0, 3/16f, - 13/16f, 15, 13/16f); + 3/16f, 0, 3/16f, + 13/16f, 15/16f, 13/16f);…once you confirm how your fluid-rendering component interprets those coordinates.
src/client/java/dev/turtywurty/industria/renderer/block/ShakingTableBlockEntityRenderer.java (2)
106-138: Surface fluid math uses coordinates outside the usual ±1 block range — confirm intentminX is set to 1 + 1/16f and z2 to 1 + 2/16f, taking coordinates outside the conventional [−1, 1] cube. If renderFluidTank expects block-local coordinates within the model’s bounds, this could cull or misplace the fluid. If intentional (to match the model’s geometry), ignore. Otherwise, re-evaluate the bounds and height derivation.
78-104: Item rendering path looks solid; consistent push/pop and world-seeded randomness
LGTM. The per-item transforms are nicely isolated, and using ItemDisplayContext.GROUND fits the presentation.src/client/java/dev/turtywurty/industria/renderer/block/MixerBlockEntityRenderer.java (2)
62-80: Guard against index mismatch between input stacks and mixingItemPositionsAccessing entity.mixingItemPositions.get(index) assumes that list is at least inputInventory.heldStacks.size(). If they diverge (e.g., slots > configured positions), this will throw.
If needed, clamp the index or pre-size mixingItemPositions to the inventory’s size before rendering.
97-104: Fluid rendering call aligns with new API
LGTM. The bounds/maxHeightPixels usage is consistent with other renderers in this PR.src/client/java/dev/turtywurty/industria/renderer/block/ClarifierBlockEntityRenderer.java (2)
55-58: Model render split looks good
The renderModel hook cleanly renders the static root part, consistent with your refactor across renderers.
61-69: Good early-return guard for null world
Avoids NPEs in downstream per-frame rendering. Keep this pattern where world can be null.
| @Override | ||
| protected void renderModel(ArcFurnaceBlockEntity entity, float tickDelta, MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, int overlay) { | ||
|
|
||
| } |
There was a problem hiding this comment.
Empty renderModel causes the Arc Furnace to render nothing.
With the base class now rendering via renderModel(...), leaving this method empty results in no visual output for the block entity (and likely its item form, once renderForItem is fixed).
Port the previous model draw into renderModel(...). If you need a temporary stopgap while migrating, delegate to onRender(...) (assuming it still contains the prior rendering code), e.g.:
@Override
protected void renderModel(ArcFurnaceBlockEntity entity, float tickDelta, MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, int overlay) {
-
+ // TEMP: delegate to onRender until the model is fully migrated here
+ onRender(entity, tickDelta, matrices, vertexConsumers, light, overlay);
}However, since onRender(...) is also empty here, the real fix is to move the actual model rendering code into this method (similar to Electrolyzer/Digester).
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| @Override | |
| protected void renderModel(ArcFurnaceBlockEntity entity, float tickDelta, MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, int overlay) { | |
| } | |
| @Override | |
| protected void renderModel(ArcFurnaceBlockEntity entity, float tickDelta, MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, int overlay) { | |
| // TEMP: delegate to onRender until the model is fully migrated here | |
| onRender(entity, tickDelta, matrices, vertexConsumers, light, overlay); | |
| } |
🤖 Prompt for AI Agents
In
src/client/java/dev/turtywurty/industria/renderer/block/ArcFurnaceBlockEntityRenderer.java
around lines 13–16, renderModel(...) is empty which prevents the Arc Furnace
from being drawn; port the previous model-draw code into this method (or, as a
temporary measure, call the old onRender(...) implementation if it still
contains the drawing logic) making sure to perform the same matrix push/pop,
translations/rotations, and use the provided VertexConsumerProvider, light and
overlay parameters so the model renders correctly for both block entity and item
contexts.
| private void renderInputItems(CentrifugalConcentratorBlockEntity entity, float tickDelta, MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, int overlay) { | ||
| ItemStack stackInSlot = Items.DIAMOND.getDefaultStack(); //entity.getInputInventory().getStackInSlot(0); | ||
| if(stackInSlot.isEmpty()) return; | ||
|
|
||
| float radius = 1.1f; | ||
|
|
||
| for (int i = 0; i < NUM_SPINNING_ITEMS; i++) { | ||
| float angle = -entity.bowlRotation + (float) Math.TAU / NUM_SPINNING_ITEMS * i; | ||
|
|
||
| float x = (float) Math.cos(angle) * radius; | ||
| float y = (float) Math.sin(angle * 3) * 0.02f - 0.2f; | ||
| float z = (float) Math.sin(angle) * radius; | ||
|
|
||
| matrices.push(); | ||
| matrices.translate(x, y, z); | ||
| matrices.multiply(RotationAxis.POSITIVE_Y.rotation(angle + (float) Math.PI / 2f)); | ||
| matrices.scale(0.5f, 0.5f, 0.5f); | ||
| this.context.getItemRenderer().renderItem(stackInSlot, ItemDisplayContext.NONE, light, overlay, matrices, vertexConsumers, entity.getWorld(), 0); | ||
|
|
||
| Vector3f pos = matrixStackToWorldPosition(matrices); | ||
| entity.getWorld().addParticleClient(ParticleTypes.BUBBLE, pos.x, pos.y + 0.3f, pos.z, 0, 0, 0); | ||
|
|
||
| matrices.pop(); | ||
| } | ||
| } |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
Remove debug item and throttle particles to avoid permanent diamonds and heavy client load.
Items.DIAMOND.getDefaultStack()forces diamonds to render always; use the actual inventory stack.- Spawning bubbles every frame per item is expensive. Add a light throttle.
- Guard against null world.
- ItemStack stackInSlot = Items.DIAMOND.getDefaultStack(); //entity.getInputInventory().getStackInSlot(0);
+ if (entity.getWorld() == null) return;
+ ItemStack stackInSlot = entity.getInputInventory().getStackInSlot(0);
if(stackInSlot.isEmpty()) return;
@@
- Vector3f pos = matrixStackToWorldPosition(matrices);
- entity.getWorld().addParticleClient(ParticleTypes.BUBBLE, pos.x, pos.y + 0.3f, pos.z, 0, 0, 0);
+ // Light throttle: 1 particle per item every 4 ticks, staggered by index
+ long time = entity.getWorld().getTime();
+ if (((time + i) & 3) == 0) {
+ Vector3f pos = matrixStackToWorldPosition(matrices);
+ entity.getWorld().addParticleClient(ParticleTypes.BUBBLE, pos.x, pos.y + 0.3f, pos.z, 0, 0, 0);
+ }🏁 Script executed:
#!/bin/bash
# Verify the availability of getInputInventory and inputInventory fields/methods in the codebase
rg -n "getInputInventory" -C3
rg -n "inputInventory" -C3Length of output: 44259
Remove debug diamond, use real input stack, throttle particles, and guard null world
Please update src/client/java/dev/turtywurty/industria/renderer/block/CentrifugalConcentratorBlockEntityRenderer.java as follows:
• At the top of renderInputItems(...), bail out if entity.getWorld() is null and grab the real stack:
- ItemStack stackInSlot = Items.DIAMOND.getDefaultStack(); //entity.getInputInventory().getStackInSlot(0);
+ if (entity.getWorld() == null) return;
+ ItemStack stackInSlot = entity.getInputInventory().getStackInSlot(0);
if (stackInSlot.isEmpty()) return;• Around the bubble spawn, throttle to once per item every 4 ticks:
- Vector3f pos = matrixStackToWorldPosition(matrices);
- entity.getWorld().addParticleClient(ParticleTypes.BUBBLE,
- pos.x, pos.y + 0.3f, pos.z,
- 0, 0, 0);
+ long time = entity.getWorld().getTime();
+ if (((time + i) & 3) == 0) { // ¼ frequency, staggered by index
+ Vector3f pos = matrixStackToWorldPosition(matrices);
+ entity.getWorld().addParticleClient(ParticleTypes.BUBBLE,
+ pos.x, pos.y + 0.3f, pos.z,
+ 0, 0, 0);
+ }– Lines to update:
• 63–65: Replace debug DIAMOND stack & add null‐world guard
• 74–80: Wrap addParticleClient in time‐based throttle
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| private void renderInputItems(CentrifugalConcentratorBlockEntity entity, float tickDelta, MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, int overlay) { | |
| ItemStack stackInSlot = Items.DIAMOND.getDefaultStack(); //entity.getInputInventory().getStackInSlot(0); | |
| if(stackInSlot.isEmpty()) return; | |
| float radius = 1.1f; | |
| for (int i = 0; i < NUM_SPINNING_ITEMS; i++) { | |
| float angle = -entity.bowlRotation + (float) Math.TAU / NUM_SPINNING_ITEMS * i; | |
| float x = (float) Math.cos(angle) * radius; | |
| float y = (float) Math.sin(angle * 3) * 0.02f - 0.2f; | |
| float z = (float) Math.sin(angle) * radius; | |
| matrices.push(); | |
| matrices.translate(x, y, z); | |
| matrices.multiply(RotationAxis.POSITIVE_Y.rotation(angle + (float) Math.PI / 2f)); | |
| matrices.scale(0.5f, 0.5f, 0.5f); | |
| this.context.getItemRenderer().renderItem(stackInSlot, ItemDisplayContext.NONE, light, overlay, matrices, vertexConsumers, entity.getWorld(), 0); | |
| Vector3f pos = matrixStackToWorldPosition(matrices); | |
| entity.getWorld().addParticleClient(ParticleTypes.BUBBLE, pos.x, pos.y + 0.3f, pos.z, 0, 0, 0); | |
| matrices.pop(); | |
| } | |
| } | |
| private void renderInputItems(CentrifugalConcentratorBlockEntity entity, float tickDelta, MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, int overlay) { | |
| if (entity.getWorld() == null) return; | |
| ItemStack stackInSlot = entity.getInputInventory().getStackInSlot(0); | |
| if (stackInSlot.isEmpty()) return; | |
| float radius = 1.1f; | |
| for (int i = 0; i < NUM_SPINNING_ITEMS; i++) { | |
| float angle = -entity.bowlRotation + (float) Math.TAU / NUM_SPINNING_ITEMS * i; | |
| float x = (float) Math.cos(angle) * radius; | |
| float y = (float) Math.sin(angle * 3) * 0.02f - 0.2f; | |
| float z = (float) Math.sin(angle) * radius; | |
| matrices.push(); | |
| matrices.translate(x, y, z); | |
| matrices.multiply(RotationAxis.POSITIVE_Y.rotation(angle + (float) Math.PI / 2f)); | |
| matrices.scale(0.5f, 0.5f, 0.5f); | |
| this.context.getItemRenderer().renderItem(stackInSlot, ItemDisplayContext.NONE, light, overlay, matrices, vertexConsumers, entity.getWorld(), 0); | |
| long time = entity.getWorld().getTime(); | |
| if (((time + i) & 3) == 0) { // ¼ frequency, staggered by index | |
| Vector3f pos = matrixStackToWorldPosition(matrices); | |
| entity.getWorld().addParticleClient(ParticleTypes.BUBBLE, | |
| pos.x, pos.y + 0.3f, pos.z, | |
| 0, 0, 0); | |
| } | |
| matrices.pop(); | |
| } | |
| } |
| World world = entity.getWorld(); | ||
|
|
||
| this.motorModel.render(matrices, vertexConsumers.getBuffer(this.motorModel.getLayer(DrillMotorModel.TEXTURE_LOCATION)), light, overlay); | ||
| int worldBottom = world == null ? 0 : world.getBottomY(); | ||
| int startY = entity.getPos().getY() + 2; | ||
| float currentY = entity.getDrillYOffset() - 1 + startY; | ||
|
|
||
| parts.rodGear().pitch = prevRodGear; | ||
| parts.connectingGear().pitch = connectingGear; | ||
| } | ||
| } | ||
| float progress = 1 - (startY - currentY) / (startY - worldBottom); | ||
|
|
There was a problem hiding this comment.
🛠️ Refactor suggestion
Guard against divide-by-zero and clamp progress to [0,1]
In extremely shallow worlds (or future level settings), startY - worldBottom could be 0; also, progress can drift outside [0,1]. Clamp and guard to avoid NaN/Infinity affecting downstream math and scaling.
Apply this diff:
- int worldBottom = world == null ? 0 : world.getBottomY();
- int startY = entity.getPos().getY() + 2;
- float currentY = entity.getDrillYOffset() - 1 + startY;
-
- float progress = 1 - (startY - currentY) / (startY - worldBottom);
+ int worldBottom = world == null ? 0 : world.getBottomY();
+ int startY = entity.getPos().getY() + 2;
+ float currentY = entity.getDrillYOffset() - 1 + startY;
+ int denom = Math.max(1, startY - worldBottom);
+ float progress = 1f - (startY - currentY) / (float) denom;
+ progress = Math.max(0f, Math.min(1f, progress));📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| World world = entity.getWorld(); | |
| this.motorModel.render(matrices, vertexConsumers.getBuffer(this.motorModel.getLayer(DrillMotorModel.TEXTURE_LOCATION)), light, overlay); | |
| int worldBottom = world == null ? 0 : world.getBottomY(); | |
| int startY = entity.getPos().getY() + 2; | |
| float currentY = entity.getDrillYOffset() - 1 + startY; | |
| parts.rodGear().pitch = prevRodGear; | |
| parts.connectingGear().pitch = connectingGear; | |
| } | |
| } | |
| float progress = 1 - (startY - currentY) / (startY - worldBottom); | |
| World world = entity.getWorld(); | |
| int worldBottom = world == null ? 0 : world.getBottomY(); | |
| int startY = entity.getPos().getY() + 2; | |
| float currentY = entity.getDrillYOffset() - 1 + startY; | |
| int denom = Math.max(1, startY - worldBottom); | |
| float progress = 1f - (startY - currentY) / (float) denom; | |
| progress = Math.max(0f, Math.min(1f, progress)); |
🤖 Prompt for AI Agents
In
src/client/java/dev/turtywurty/industria/renderer/block/DrillBlockEntityRenderer.java
around lines 50 to 57, the computation of progress uses (startY - worldBottom)
as a denominator which can be zero in very shallow worlds and the resulting
progress may fall outside [0,1]; update the code to compute the denominator
safely (use a max(1, startY - worldBottom) or check for zero and fall back to 1)
and then clamp the resulting progress to the range [0.0f, 1.0f] before any
further math so you never produce NaN/Infinity or values outside the expected
bounds.
|
|
||
| @Override | ||
| public final void render(T entity, float tickDelta, MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, int overlay, Vec3d cameraPos) { | ||
| matrices.push(); | ||
| setupBlockEntityTransformations(matrices, entity); | ||
|
|
||
| matrices.push(); | ||
| matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees(180)); | ||
| renderModel(entity, tickDelta, matrices, vertexConsumers, light, overlay); | ||
| matrices.pop(); | ||
|
|
||
| onRender(entity, tickDelta, matrices, vertexConsumers, light, overlay); | ||
|
|
||
| if (isPlayerLookingAt(entity.getPos())) { | ||
| List<ModelPart> wireframe = getModelParts(); | ||
| if (!wireframe.isEmpty()) { | ||
| boolean isHighContrast = isHighContrast(); | ||
| renderWireframe(wireframe, matrices, vertexConsumers, isHighContrast); | ||
| } | ||
| } | ||
|
|
||
| matrices.pop(); | ||
| postRender(entity, tickDelta, matrices, vertexConsumers, light, overlay); | ||
| } |
There was a problem hiding this comment.
renderForItem currently renders nothing for migrated renderers and also has a push/pop imbalance.
With the new pattern, subclasses moved their rendering into renderModel(...) and often left onRender(...) empty. renderForItem(...) still calls only onRender(...), so items won’t render. Additionally, renderForItem(...) does a matrices.pop() without a corresponding push() in that method.
Proposed fix (replace the whole method; note this change occurs outside the shown hunk):
public final void renderForItem(T entity, float tickDelta, MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, int overlay) {
matrices.push();
setupBlockEntityTransformations(matrices, entity);
matrices.push();
matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees(180));
renderModel(entity, tickDelta, matrices, vertexConsumers, light, overlay);
matrices.pop();
// Optionally keep this if item renders need extra overlays/effects
onRender(entity, tickDelta, matrices, vertexConsumers, light, overlay);
matrices.pop();
postRender(entity, tickDelta, matrices, vertexConsumers, light, overlay);
}This mirrors the world render path and restores item rendering for classes that implemented renderModel(...).
🤖 Prompt for AI Agents
In
src/client/java/dev/turtywurty/industria/renderer/block/IndustriaBlockEntityRenderer.java
around lines 59-82, renderForItem currently calls only onRender (so migrated
renderers that moved drawing into renderModel render nothing) and contains a
stray matrices.pop() without a matching push; replace renderForItem with a
method that mirrors the world render path: push the matrices, call
setupBlockEntityTransformations(matrices, entity), push again, rotate 180° on X,
call renderModel(entity, tickDelta, matrices, vertexConsumers, light, overlay),
pop the inner matrix, optionally call onRender for extra overlays/effects, pop
the outer matrix, then call postRender(entity, tickDelta, matrices,
vertexConsumers, light, overlay) so item rendering works and push/pop are
balanced.
| float alpha = (float) (Math.sin(entity.getWorld().getTime() % 20) * 0.5 + 0.5F); | ||
| VertexRendering.drawBox( | ||
| matrices, | ||
| vertexConsumer, | ||
| -0.1F + xOffset, | ||
| -0.1F + yOffset, | ||
| -0.1F + zOffset, | ||
| 0.1F + xOffset, | ||
| 0.1F + yOffset, | ||
| 0.1F + zOffset, | ||
| 1.0F, | ||
| 1.0F, | ||
| 1.0F, | ||
| alpha); | ||
| VertexRendering.drawBox(matrices, vertexConsumer, | ||
| -0.1F + xOffset, -0.1F + yOffset, -0.1F + zOffset, | ||
| 0.1F + xOffset, 0.1F + yOffset, 0.1F + zOffset, | ||
| 1.0F, 1.0F, 1.0F, alpha); | ||
| } |
There was a problem hiding this comment.
Potential NPE when computing alpha from world time
entity.getWorld() can be null on the client in edge cases (e.g., very early lifecycle). Use a null-safe read for the time.
Apply this diff:
- float alpha = (float) (Math.sin(entity.getWorld().getTime() % 20) * 0.5 + 0.5F);
+ long time = entity.getWorld() != null ? entity.getWorld().getTime() : 0L;
+ float alpha = (float) (Math.sin(time % 20) * 0.5 + 0.5F);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| float alpha = (float) (Math.sin(entity.getWorld().getTime() % 20) * 0.5 + 0.5F); | |
| VertexRendering.drawBox( | |
| matrices, | |
| vertexConsumer, | |
| -0.1F + xOffset, | |
| -0.1F + yOffset, | |
| -0.1F + zOffset, | |
| 0.1F + xOffset, | |
| 0.1F + yOffset, | |
| 0.1F + zOffset, | |
| 1.0F, | |
| 1.0F, | |
| 1.0F, | |
| alpha); | |
| VertexRendering.drawBox(matrices, vertexConsumer, | |
| -0.1F + xOffset, -0.1F + yOffset, -0.1F + zOffset, | |
| 0.1F + xOffset, 0.1F + yOffset, 0.1F + zOffset, | |
| 1.0F, 1.0F, 1.0F, alpha); | |
| } | |
| // Null-safe world time for alpha calculation | |
| long time = entity.getWorld() != null ? entity.getWorld().getTime() : 0L; | |
| float alpha = (float) (Math.sin(time % 20) * 0.5 + 0.5F); | |
| VertexRendering.drawBox(matrices, vertexConsumer, | |
| -0.1F + xOffset, -0.1F + yOffset, -0.1F + zOffset, | |
| 0.1F + xOffset, 0.1F + yOffset, 0.1F + zOffset, | |
| 1.0F, 1.0F, 1.0F, alpha); |
🤖 Prompt for AI Agents
In
src/client/java/dev/turtywurty/industria/renderer/block/MultiblockIOBlockEntityRenderer.java
around lines 61 to 66, computing alpha uses entity.getWorld().getTime() which
can NPE if getWorld() is null; replace with a null-safe read (e.g., read world
into a local variable and use its getTime() only when non-null, otherwise use a
safe fallback like 0L or client tick count) and then compute alpha from that
safe time value so the renderer never dereferences a null world.
| public void renderFluidTank(@Nullable SingleFluidStorage fluidTank, VertexConsumerProvider vertexConsumers, MatrixStack matrices, int light, int overlay, World world, BlockPos pos, float x1, float y1, float z1, float x2, float maxHeightPixels, float z2, int color, ColorMode colorMode, IndeterminateBoolean drawTopFace) { | ||
| // if (fluidTank == null || fluidTank.isResourceBlank() || fluidTank.amount <= 0) return; | ||
|
|
||
| FluidVariant fluidVariant = fluidTank.getResource(); | ||
| FluidVariant fluidVariant = FluidVariant.of(Fluids.WATER); //fluidTank.getResource(); | ||
| long amount = fluidTank.amount; | ||
| long capacity = fluidTank.getCapacity(); | ||
| float fillPercentage = (float) amount / capacity; | ||
| float fillPercentage = (float) (Math.sin(world.getTime() / 20.0) * 0.5 + 0.5); | ||
| ; //(float) amount / capacity; | ||
| fillPercentage = MathHelper.clamp(fillPercentage, 0.0F, 1.0F); | ||
|
|
||
| if(this.shouldDebugAmount) { | ||
| fillPercentage = (float) (Math.sin(world.getTime() / 64.0) * 0.5 + 0.5); | ||
| if (this.shouldDebugAmount) { | ||
| fillPercentage = (float) (Math.sin(world.getTime() / 64f) * 0.5 + 0.5); | ||
| } |
There was a problem hiding this comment.
Null/resource guards removed and tank values ignored—this will NPE and always render water.
- The early guard is commented out, but
fluidTank.amountandfluidTank.getCapacity()are still read—NPE iffluidTankis null. FluidVariant fluidVariant = FluidVariant.of(Fluids.WATER)ignores the actual tank resource.fillPercentageis driven byworld.getTime()instead of the tank value, except under debug.
Restore guards and use tank contents; keep the sine path only for debug.
- // if (fluidTank == null || fluidTank.isResourceBlank() || fluidTank.amount <= 0) return;
-
- FluidVariant fluidVariant = FluidVariant.of(Fluids.WATER); //fluidTank.getResource();
- long amount = fluidTank.amount;
- long capacity = fluidTank.getCapacity();
- float fillPercentage = (float) (Math.sin(world.getTime() / 20.0) * 0.5 + 0.5);
- ; //(float) amount / capacity;
+ if (fluidTank == null || fluidTank.isResourceBlank() || fluidTank.amount <= 0) return;
+
+ FluidVariant fluidVariant = fluidTank.getResource();
+ long amount = fluidTank.amount;
+ long capacity = fluidTank.getCapacity();
+ float fillPercentage = (float) amount / (float) capacity;
fillPercentage = MathHelper.clamp(fillPercentage, 0.0F, 1.0F);
if (this.shouldDebugAmount) {
fillPercentage = (float) (Math.sin(world.getTime() / 64f) * 0.5 + 0.5);
}If amount is not publicly accessible for your storage type, replace with its accessor (e.g., getAmount()).
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| public void renderFluidTank(@Nullable SingleFluidStorage fluidTank, VertexConsumerProvider vertexConsumers, MatrixStack matrices, int light, int overlay, World world, BlockPos pos, float x1, float y1, float z1, float x2, float maxHeightPixels, float z2, int color, ColorMode colorMode, IndeterminateBoolean drawTopFace) { | |
| // if (fluidTank == null || fluidTank.isResourceBlank() || fluidTank.amount <= 0) return; | |
| FluidVariant fluidVariant = fluidTank.getResource(); | |
| FluidVariant fluidVariant = FluidVariant.of(Fluids.WATER); //fluidTank.getResource(); | |
| long amount = fluidTank.amount; | |
| long capacity = fluidTank.getCapacity(); | |
| float fillPercentage = (float) amount / capacity; | |
| float fillPercentage = (float) (Math.sin(world.getTime() / 20.0) * 0.5 + 0.5); | |
| ; //(float) amount / capacity; | |
| fillPercentage = MathHelper.clamp(fillPercentage, 0.0F, 1.0F); | |
| if(this.shouldDebugAmount) { | |
| fillPercentage = (float) (Math.sin(world.getTime() / 64.0) * 0.5 + 0.5); | |
| if (this.shouldDebugAmount) { | |
| fillPercentage = (float) (Math.sin(world.getTime() / 64f) * 0.5 + 0.5); | |
| } | |
| public void renderFluidTank(@Nullable SingleFluidStorage fluidTank, VertexConsumerProvider vertexConsumers, MatrixStack matrices, int light, int overlay, World world, BlockPos pos, float x1, float y1, float z1, float x2, float maxHeightPixels, float z2, int color, ColorMode colorMode, IndeterminateBoolean drawTopFace) { | |
| if (fluidTank == null || fluidTank.isResourceBlank() || fluidTank.amount <= 0) return; | |
| FluidVariant fluidVariant = fluidTank.getResource(); | |
| long amount = fluidTank.amount; | |
| long capacity = fluidTank.getCapacity(); | |
| float fillPercentage = (float) amount / (float) capacity; | |
| fillPercentage = MathHelper.clamp(fillPercentage, 0.0F, 1.0F); | |
| if (this.shouldDebugAmount) { | |
| fillPercentage = (float) (Math.sin(world.getTime() / 64f) * 0.5 + 0.5); | |
| } |
🤖 Prompt for AI Agents
In
src/client/java/dev/turtywurty/industria/util/InWorldFluidRenderingComponent.java
around lines 43 to 55, restore the early null/resource guards and stop
hardcoding WATER and time-based fill: check if fluidTank is null or
resource-blank (or amount/capacity invalid) and return early to avoid NPE;
obtain the actual FluidVariant from the tank via its accessor (e.g.,
fluidTank.getResource() or equivalent) instead of FluidVariant.of(Fluids.WATER);
read amount and capacity via their public accessors (e.g.,
fluidTank.getAmount()/getCapacity() if fields are not public), compute
fillPercentage as (float)amount / (float)capacity and clamp it, and only
override fillPercentage with the sine/world-time debug value when
shouldDebugAmount is true. Ensure you handle zero capacity to avoid division by
zero.
| public void renderFace(Direction direction, @Nullable FluidVariant fluidVariant, VertexConsumerProvider vertexConsumers, MatrixStack matrices, int light, int overlay, World world, BlockPos pos, float left, float right, float up, float down, float depth, int color, ColorMode colorMode, UnaryOperator<RenderLayer> wrapRenderLayer) { | ||
| if (fluidVariant == null) return; | ||
|
|
||
| Sprite stillSprite = FluidVariantRendering.getSprite(fluidVariant); | ||
| if (stillSprite == null) return; | ||
|
|
||
| int fluidColor = FluidVariantRendering.getColor(fluidVariant, world, pos); | ||
| fluidColor = ColorMode.modifyColor(fluidColor, color, colorMode); | ||
|
|
||
| Sprite stillSprite = FluidVariantRendering.getSprite(fluidVariant); | ||
| if(stillSprite == null) | ||
| return; | ||
|
|
||
| RenderLayer renderLayer = RenderLayer.getItemEntityTranslucentCull(stillSprite.getAtlasId()); | ||
| renderLayer = wrapRenderLayer.apply(renderLayer); | ||
| VertexConsumer vertexConsumer = vertexConsumers.getBuffer(renderLayer); | ||
|
|
||
| matrices.push(); | ||
| matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees(180)); | ||
|
|
||
| if (FluidVariantAttributes.isLighterThanAir(fluidVariant)) { | ||
| matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees(180)); | ||
| } | ||
|
|
||
| MatrixStack.Entry entry = matrices.peek(); | ||
|
|
||
| int blockLight = (light >> 4) & 0xF; | ||
| int luminosity = Math.max(blockLight, FluidVariantAttributes.getLuminance(fluidVariant)); | ||
| light = (light & 0xF00000) | (luminosity << 4); | ||
|
|
||
| drawTiledXYQuad(vertexConsumer, entry, x1, y1, z1, x2, y2, z2, stillSprite, fluidColor, light, overlay, 0.0F, 1.0F, -1.0F); | ||
| renderDirectionalTiledQuad(direction, vertexConsumer, matrices, left, depth, up, right, down, stillSprite, fluidColor, light, overlay); | ||
|
|
||
| matrices.pop(); | ||
| } | ||
|
|
There was a problem hiding this comment.
Parameter order bug when delegating to renderDirectionalTiledQuad leads to warped geometry.
renderDirectionalTiledQuad(direction, ..., left, depth, up, right, down, ...) mixes up right/down/depth. It must be left, right, up, down, depth.
- renderDirectionalTiledQuad(direction, vertexConsumer, matrices, left, depth, up, right, down, stillSprite, fluidColor, light, overlay);
+ renderDirectionalTiledQuad(direction, vertexConsumer, matrices, left, right, up, down, depth, stillSprite, fluidColor, light, overlay);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| public void renderFace(Direction direction, @Nullable FluidVariant fluidVariant, VertexConsumerProvider vertexConsumers, MatrixStack matrices, int light, int overlay, World world, BlockPos pos, float left, float right, float up, float down, float depth, int color, ColorMode colorMode, UnaryOperator<RenderLayer> wrapRenderLayer) { | |
| if (fluidVariant == null) return; | |
| Sprite stillSprite = FluidVariantRendering.getSprite(fluidVariant); | |
| if (stillSprite == null) return; | |
| int fluidColor = FluidVariantRendering.getColor(fluidVariant, world, pos); | |
| fluidColor = ColorMode.modifyColor(fluidColor, color, colorMode); | |
| Sprite stillSprite = FluidVariantRendering.getSprite(fluidVariant); | |
| if(stillSprite == null) | |
| return; | |
| RenderLayer renderLayer = RenderLayer.getItemEntityTranslucentCull(stillSprite.getAtlasId()); | |
| renderLayer = wrapRenderLayer.apply(renderLayer); | |
| VertexConsumer vertexConsumer = vertexConsumers.getBuffer(renderLayer); | |
| matrices.push(); | |
| matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees(180)); | |
| if (FluidVariantAttributes.isLighterThanAir(fluidVariant)) { | |
| matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees(180)); | |
| } | |
| MatrixStack.Entry entry = matrices.peek(); | |
| int blockLight = (light >> 4) & 0xF; | |
| int luminosity = Math.max(blockLight, FluidVariantAttributes.getLuminance(fluidVariant)); | |
| light = (light & 0xF00000) | (luminosity << 4); | |
| drawTiledXYQuad(vertexConsumer, entry, x1, y1, z1, x2, y2, z2, stillSprite, fluidColor, light, overlay, 0.0F, 1.0F, -1.0F); | |
| renderDirectionalTiledQuad(direction, vertexConsumer, matrices, left, depth, up, right, down, stillSprite, fluidColor, light, overlay); | |
| matrices.pop(); | |
| } | |
| public void renderFace(Direction direction, @Nullable FluidVariant fluidVariant, VertexConsumerProvider vertexConsumers, MatrixStack matrices, int light, int overlay, World world, BlockPos pos, float left, float right, float up, float down, float depth, int color, ColorMode colorMode, UnaryOperator<RenderLayer> wrapRenderLayer) { | |
| if (fluidVariant == null) return; | |
| Sprite stillSprite = FluidVariantRendering.getSprite(fluidVariant); | |
| if (stillSprite == null) return; | |
| int fluidColor = FluidVariantRendering.getColor(fluidVariant, world, pos); | |
| fluidColor = ColorMode.modifyColor(fluidColor, color, colorMode); | |
| RenderLayer renderLayer = RenderLayer.getItemEntityTranslucentCull(stillSprite.getAtlasId()); | |
| renderLayer = wrapRenderLayer.apply(renderLayer); | |
| VertexConsumer vertexConsumer = vertexConsumers.getBuffer(renderLayer); | |
| matrices.push(); | |
| if (FluidVariantAttributes.isLighterThanAir(fluidVariant)) { | |
| matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees(180)); | |
| } | |
| int blockLight = (light >> 4) & 0xF; | |
| int luminosity = Math.max(blockLight, FluidVariantAttributes.getLuminance(fluidVariant)); | |
| light = (light & 0xF00000) | (luminosity << 4); | |
| renderDirectionalTiledQuad(direction, vertexConsumer, matrices, left, right, up, down, depth, stillSprite, fluidColor, light, overlay); | |
| matrices.pop(); | |
| } |
🤖 Prompt for AI Agents
In
src/client/java/dev/turtywurty/industria/util/InWorldFluidRenderingComponent.java
around lines 136 to 163, the call to renderDirectionalTiledQuad passes
parameters in the wrong order (left, depth, up, right, down), which warps
geometry; change the argument order to left, right, up, down, depth so it
matches the method signature (left, right, up, down, depth) when delegating the
values to renderDirectionalTiledQuad.
| for (int k = 0; k < 4; k++) { | ||
| vertexConsumer.vertex(entry, vertices[k][0], vertices[k][1], vertices[k][2]) | ||
| .color(color).texture(uvs[k][0], uvs[k][1]).light(light).overlay(overlay).normal(entry, normal); | ||
| } | ||
| } |
There was a problem hiding this comment.
Incorrect VertexConsumer.normal(...) overload used; likely a compile error.
normal(entry, normal) does not match the Yarn/vanilla overloads. Pass components explicitly.
- vertexConsumer.vertex(entry, vertices[k][0], vertices[k][1], vertices[k][2])
- .color(color).texture(uvs[k][0], uvs[k][1]).light(light).overlay(overlay).normal(entry, normal);
+ vertexConsumer.vertex(entry, vertices[k][0], vertices[k][1], vertices[k][2])
+ .color(color)
+ .texture(uvs[k][0], uvs[k][1])
+ .light(light)
+ .overlay(overlay)
+ .normal(entry, normal.x(), normal.y(), normal.z());📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| for (int k = 0; k < 4; k++) { | |
| vertexConsumer.vertex(entry, vertices[k][0], vertices[k][1], vertices[k][2]) | |
| .color(color).texture(uvs[k][0], uvs[k][1]).light(light).overlay(overlay).normal(entry, normal); | |
| } | |
| } | |
| for (int k = 0; k < 4; k++) { | |
| vertexConsumer.vertex(entry, vertices[k][0], vertices[k][1], vertices[k][2]) | |
| .color(color) | |
| .texture(uvs[k][0], uvs[k][1]) | |
| .light(light) | |
| .overlay(overlay) | |
| .normal(entry, normal.x(), normal.y(), normal.z()); | |
| } |
🤖 Prompt for AI Agents
In
src/client/java/dev/turtywurty/industria/util/InWorldFluidRenderingComponent.java
around lines 289 to 293, the call to vertexConsumer.normal(entry, normal) uses
the wrong overload and won't compile; extract the normal's X/Y/Z components and
call the normal overload that takes three floats (or three floats with the
proper matrix if your API requires), e.g. compute float nx, ny, nz from the
existing normal object and replace .normal(entry, normal) with the appropriate
.normal(nx, ny, nz) (or .normal(entry, nx, ny, nz) if your VertexConsumer
expects the matrix first) so the explicit components are passed to the correct
overload.
| if (storage instanceof SingleFluidStorage singleFluidStorage) { | ||
| singleFluidStorage.amount = view.getLong("Amount", 0L); | ||
| singleFluidStorage.variant = view.read("Fluid", FluidVariant.CODEC).orElseThrow(); | ||
| singleFluidStorage.variant = view.read("Fluid", FluidVariant.CODEC).orElse(FluidVariant.blank()); | ||
| } else { |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Prevent inconsistent state: zero amount when Fluid is missing (blank variant).
Defaulting to FluidVariant.blank() avoids crashes, but leaving a non-zero amount with a blank fluid is an inconsistent state that can break consumers (e.g., renderers assuming amount>0 implies non-blank fluid). Set amount to 0 when the read variant is blank.
Apply this diff:
- singleFluidStorage.amount = view.getLong("Amount", 0L);
- singleFluidStorage.variant = view.read("Fluid", FluidVariant.CODEC).orElse(FluidVariant.blank());
+ FluidVariant variant = view.read("Fluid", FluidVariant.CODEC).orElse(FluidVariant.blank());
+ long amount = view.getLong("Amount", 0L);
+ if (variant.isBlank()) {
+ amount = 0L; // ensure no stray amount for "empty" fluid
+ }
+ singleFluidStorage.variant = variant;
+ singleFluidStorage.amount = amount;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (storage instanceof SingleFluidStorage singleFluidStorage) { | |
| singleFluidStorage.amount = view.getLong("Amount", 0L); | |
| singleFluidStorage.variant = view.read("Fluid", FluidVariant.CODEC).orElseThrow(); | |
| singleFluidStorage.variant = view.read("Fluid", FluidVariant.CODEC).orElse(FluidVariant.blank()); | |
| } else { | |
| if (storage instanceof SingleFluidStorage singleFluidStorage) { | |
| FluidVariant variant = view.read("Fluid", FluidVariant.CODEC).orElse(FluidVariant.blank()); | |
| long amount = view.getLong("Amount", 0L); | |
| if (variant.isBlank()) { | |
| amount = 0L; // ensure no stray amount for "empty" fluid | |
| } | |
| singleFluidStorage.variant = variant; | |
| singleFluidStorage.amount = amount; | |
| } else { |
🤖 Prompt for AI Agents
In
src/main/java/dev/turtywurty/industria/blockentity/util/fluid/WrappedFluidStorage.java
around lines 54 to 57, when deserializing a SingleFluidStorage you currently
default to FluidVariant.blank() but still restore a non-zero amount which yields
an inconsistent state; read the FluidVariant first, if it is blank then set
singleFluidStorage.amount = 0L and singleFluidStorage.variant =
FluidVariant.blank(), otherwise set singleFluidStorage.variant to the read
variant and restore amount from view.getLong("Amount", 0L).
Summary by CodeRabbit
New Features
Bug Fixes
Other Changes