Compare commits

...
Author SHA1 Message Date
Mika Hensel 480384be53 Address review, fix raytracing world leak 2026-11-07 01:09:07 +01:00
Mika Hensel fcbf509206 Fix billboards in the entity icon 2026-10-28 22:24:19 +01:00
Mika Hensel dad013bbe3 Add 3D entity icon to the tooltip 2026-10-28 21:54:53 +01:00
6 changed files with 193 additions and 15 deletions

No files matched your search

@@ -16,6 +16,7 @@ import mcp.mobius.waila.handlers.HUDHandlerBlocks;
import mcp.mobius.waila.handlers.HUDHandlerEntities;
import mcp.mobius.waila.handlers.VanillaTooltipHandler;
import mcp.mobius.waila.handlers.nei.NEIHandler;
import mcp.mobius.waila.overlay.EntityIconRenderer;
import mcp.mobius.waila.overlay.tooltiprenderers.TTRenderAspectString;
import mcp.mobius.waila.overlay.tooltiprenderers.TTRenderBar;
import mcp.mobius.waila.overlay.tooltiprenderers.TTRenderFluidBar;
@@ -53,6 +54,7 @@ public class ProxyClient extends ProxyServer {
ModuleRegistrar.instance().addConfig("General", "general.showents");
ModuleRegistrar.instance().addConfig("General", "general.showhp");
ModuleRegistrar.instance().addConfig("General", "general.showcrop");
ModuleRegistrar.instance().addConfig("General", EntityIconRenderer.CFG_SHOW, true);
ModuleRegistrar.instance().registerTooltipRenderer("waila.health", new TTRenderHealth());
ModuleRegistrar.instance().registerTooltipRenderer("waila.stack", new TTRenderStack());
@@ -0,0 +1,162 @@
package mcp.mobius.waila.overlay;
import java.nio.FloatBuffer;
import java.util.HashSet;
import java.util.Set;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.ScaledResolution;
import net.minecraft.client.renderer.ActiveRenderInfo;
import net.minecraft.client.renderer.GLAllocation;
import net.minecraft.client.renderer.RenderHelper;
import net.minecraft.client.renderer.entity.RenderManager;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import mcp.mobius.waila.api.impl.ConfigHandler;
import mcp.mobius.waila.utils.WailaExceptionHandler;
/**
* Draws a small turning 3D model of an entity as the tooltip icon. Based on {@code GuiInventory.func_147046_a} and the
* entity icon of WDMla (https://github.com/GTNewHorizons/WDMla).
*/
public final class EntityIconRenderer {
public static final String CFG_SHOW = "general.showentityicon";
/** Degrees per second */
private static final float TURN_SPEED = 45f;
private static final Set<Class<?>> failedClasses = new HashSet<>();
private static final FloatBuffer modelview = GLAllocation.createDirectFloatBuffer(16);
private EntityIconRenderer() {}
public static boolean isEnabled() {
return ConfigHandler.instance().getConfig(CFG_SHOW, true);
}
/**
* Draws the entity in the square with the top left corner (x, y) and the side length size.
*/
public static void render(int x, int y, int size, Entity entity) {
if (entity == null || failedClasses.contains(entity.getClass())) return;
final Minecraft mc = Minecraft.getMinecraft();
// The body of most mobs is longer than the bounding box is wide, and the model turns
float extent = Math.max(Math.max(entity.height, entity.width * 1.5f), 0.1f);
float scale = size / extent;
float angle = (Minecraft.getSystemTime() % 360000L) / 1000f * TURN_SPEED;
float yaw = entity.rotationYaw;
float prevYaw = entity.prevRotationYaw;
float pitch = entity.rotationPitch;
float prevPitch = entity.prevRotationPitch;
float bodyYaw = 0, prevBodyYaw = 0, headYaw = 0, prevHeadYaw = 0;
EntityLivingBase living = entity instanceof EntityLivingBase l ? l : null;
if (living != null) {
bodyYaw = living.renderYawOffset;
prevBodyYaw = living.prevRenderYawOffset;
headYaw = living.rotationYawHead;
prevHeadYaw = living.prevRotationYawHead;
}
float playerViewY = RenderManager.instance.playerViewY;
float infoX = ActiveRenderInfo.rotationX, infoZ = ActiveRenderInfo.rotationZ;
float infoYZ = ActiveRenderInfo.rotationYZ, infoXZ = ActiveRenderInfo.rotationXZ,
infoXY = ActiveRenderInfo.rotationXY;
boolean hideGui = mc.gameSettings.hideGUI;
// Entity renderers change blend, depth, alpha and lighting state (so save all of it)
int matrixDepth = GL11.glGetInteger(GL11.GL_MODELVIEW_STACK_DEPTH);
int attribDepth = GL11.glGetInteger(GL11.GL_ATTRIB_STACK_DEPTH);
GL11.glPushMatrix();
GL11.glPushAttrib(GL11.GL_ALL_ATTRIB_BITS);
try {
// Models can be larger than their bounding box, do not draw over the text
ScaledResolution res = new ScaledResolution(mc, mc.displayWidth, mc.displayHeight);
float factor = res.getScaleFactor() * OverlayConfig.scale;
int margin = 3;
GL11.glEnable(GL11.GL_SCISSOR_TEST);
GL11.glScissor(
(int) ((x - margin) * factor),
(int) (mc.displayHeight - (y + size + margin) * factor),
(int) ((size + 2 * margin) * factor),
(int) ((size + 2 * margin) * factor));
GL11.glEnable(GL11.GL_COLOR_MATERIAL);
GL11.glEnable(GL11.GL_DEPTH_TEST);
GL11.glEnable(GL12.GL_RESCALE_NORMAL);
GL11.glColor4f(1f, 1f, 1f, 1f);
GL11.glTranslatef(x + size / 2f, y + size / 2f + entity.height * scale / 2f, 100f);
GL11.glScalef(-scale, scale, scale);
GL11.glRotatef(180f, 0f, 0f, 1f);
GL11.glRotatef(-15f, 1f, 0f, 0f);
GL11.glRotatef(135f, 0f, 1f, 0f);
RenderHelper.enableStandardItemLighting();
GL11.glRotatef(-135f + angle, 0f, 1f, 0f);
GL11.glTranslatef(0f, entity.yOffset, 0f);
entity.rotationYaw = entity.prevRotationYaw = 0f;
entity.rotationPitch = entity.prevRotationPitch = 0f;
if (living != null) {
living.renderYawOffset = living.prevRenderYawOffset = 0f;
living.rotationYawHead = living.prevRotationYawHead = 0f;
}
RenderManager.instance.playerViewY = 180f;
setBillboardAxes();
// Hide name tags
mc.gameSettings.hideGUI = true;
RenderManager.instance.renderEntityWithPosYaw(entity, 0d, 0d, 0d, 0f, 1f);
} catch (Exception | LinkageError e) {
failedClasses.add(entity.getClass());
WailaExceptionHandler.handleErr(e, "EntityIconRenderer | " + entity.getClass().getName(), null);
} finally {
mc.gameSettings.hideGUI = hideGui;
RenderManager.instance.playerViewY = playerViewY;
ActiveRenderInfo.rotationX = infoX;
ActiveRenderInfo.rotationZ = infoZ;
ActiveRenderInfo.rotationYZ = infoYZ;
ActiveRenderInfo.rotationXZ = infoXZ;
ActiveRenderInfo.rotationXY = infoXY;
entity.rotationYaw = yaw;
entity.prevRotationYaw = prevYaw;
entity.rotationPitch = pitch;
entity.prevRotationPitch = prevPitch;
if (living != null) {
living.renderYawOffset = bodyYaw;
living.prevRenderYawOffset = prevBodyYaw;
living.rotationYawHead = headYaw;
living.prevRotationYawHead = prevHeadYaw;
}
// A renderer can leave unbalanced pushes behind, but should never pop the caller's. Query each
// stack once and pop down to where it started.
for (int i = GL11.glGetInteger(GL11.GL_ATTRIB_STACK_DEPTH) - attribDepth; i > 0; i--) GL11.glPopAttrib();
for (int i = GL11.glGetInteger(GL11.GL_MODELVIEW_STACK_DEPTH) - matrixDepth; i > 0; i--) GL11.glPopMatrix();
}
}
/**
* Billboards (like Thaumcraft wisps) face the camera with the axes in {@link ActiveRenderInfo}, which belong to the
* world view. Point them at the screen of the icon instead.
*/
private static void setBillboardAxes() {
modelview.clear();
GL11.glGetFloat(GL11.GL_MODELVIEW_MATRIX, modelview);
// The rows of the rotation are the screen right and down axes in model space, all with the same length
float len = (float) Math.sqrt(
modelview.get(0) * modelview.get(0) + modelview.get(4) * modelview.get(4)
+ modelview.get(8) * modelview.get(8));
// Vanilla stores the left axis in rotationX/Z and the up axis in rotationYZ/XZ/XY
ActiveRenderInfo.rotationX = -modelview.get(0) / len;
ActiveRenderInfo.rotationZ = -modelview.get(8) / len;
ActiveRenderInfo.rotationYZ = -modelview.get(1) / len;
ActiveRenderInfo.rotationXZ = -modelview.get(5) / len;
ActiveRenderInfo.rotationXY = -modelview.get(9) / len;
}
}
@@ -2,6 +2,7 @@ package mcp.mobius.waila.overlay;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.RenderHelper;
import net.minecraft.entity.Entity;
import net.minecraft.util.MovingObjectPosition;
import org.lwjgl.opengl.GL11;
@@ -11,8 +12,6 @@ import mcp.mobius.waila.api.impl.ConfigHandler;
public class OverlayRenderer {
private static boolean hasBlending;
private static boolean hasDepthTest;
private static int boundTexIndex;
public static boolean isOverlayVisible() {
@@ -69,8 +68,11 @@ public class OverlayRenderer {
GL11.glEnable(GL12.GL_RESCALE_NORMAL);
Entity entity = tooltip.entity;
if (tooltip.hasIcon && tooltip.stack != null && tooltip.stack.getItem() != null)
DisplayUtil.renderStack(tooltip.x + 5, tooltip.y + tooltip.h / 2 - 8, tooltip.stack);
else if (tooltip.hasIcon && entity != null)
EntityIconRenderer.render(tooltip.x + 5, tooltip.y + tooltip.h / 2 - 8, 16, entity);
loadGLState();
GL11.glPopMatrix();
@@ -78,17 +80,12 @@ public class OverlayRenderer {
}
private static void saveGLState() {
hasBlending = GL11.glGetBoolean(GL11.GL_BLEND);
hasDepthTest = GL11.glGetBoolean(GL11.GL_DEPTH_TEST);
boundTexIndex = GL11.glGetInteger(GL11.GL_TEXTURE_BINDING_2D);
GL11.glPushAttrib(GL11.GL_CURRENT_BIT);
// Icons turn on lighting, lights, color material and rescale normal
GL11.glPushAttrib(GL11.GL_CURRENT_BIT | GL11.GL_ENABLE_BIT | GL11.GL_LIGHTING_BIT | GL11.GL_COLOR_BUFFER_BIT);
}
private static void loadGLState() {
if (hasBlending) GL11.glEnable(GL11.GL_BLEND);
else GL11.glDisable(GL11.GL_BLEND);
if (hasDepthTest) GL11.glEnable(GL11.GL_DEPTH_TEST);
else GL11.glDisable(GL11.GL_DEPTH_TEST);
GL11.glBindTexture(GL11.GL_TEXTURE_2D, boundTexIndex);
GL11.glPopAttrib();
}
@@ -14,6 +14,7 @@ import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import net.minecraft.entity.Entity;
import net.minecraft.item.ItemStack;
import net.minecraftforge.common.config.Configuration;
@@ -49,6 +50,7 @@ public class Tooltip {
Point pos;
boolean hasIcon = false;
ItemStack stack;
Entity entity;
IWailaCommonAccessor accessor = DataAccessorCommon.instance;
@@ -106,6 +108,11 @@ public class Tooltip {
this.stack = stack;
}
public Tooltip(List<String> textData, Entity entity) {
this(textData, entity != null && EntityIconRenderer.isEnabled());
this.entity = entity;
}
public Tooltip(List<String> textData, boolean hasIcon) {
if (hasIcon) hasIcon = ConfigHandler.instance().showIcon();
@@ -23,9 +23,15 @@ import mcp.mobius.waila.utils.Constants;
public class WailaTickHandler {
private Tooltip tooltip = null;
private static Tooltip tooltip = null;
private static World lastWorld = null;
private final MetaDataProvider handler = new MetaDataProvider();
private static void reset() {
tooltip = null;
RayTracing.instance().clear();
}
@SubscribeEvent
public void tickRender(TickEvent.RenderTickEvent event) {
if (event.phase == TickEvent.Phase.END) {
@@ -37,15 +43,18 @@ public class WailaTickHandler {
public void tickClient(TickEvent.ClientTickEvent event) {
if (event.phase == TickEvent.Phase.START) return;
Minecraft mc = Minecraft.getMinecraft();
// The tooltip and the ray trace target hold the entity, which holds the world
if (mc.theWorld != lastWorld) reset();
lastWorld = mc.theWorld;
if (!OverlayRenderer.isOverlayVisible()) {
this.tooltip = null;
RayTracing.instance().clear();
reset();
return;
}
RayTracing.instance().fire();
MovingObjectPosition target = RayTracing.instance().getTarget();
Minecraft mc = Minecraft.getMinecraft();
EntityPlayer player = mc.thePlayer;
World world = mc.theWorld;
@@ -96,7 +105,7 @@ public class WailaTickHandler {
currenttip.addAll(currenttipBody);
currenttip.addAll(currenttipTail);
this.tooltip = new Tooltip(currenttip, targetStack);
tooltip = new Tooltip(currenttip, targetStack);
}
} else if (target != null && target.typeOfHit == MovingObjectPosition.MovingObjectType.ENTITY) {
DataAccessorCommon accessor = DataAccessorCommon.instance;
@@ -142,7 +151,7 @@ public class WailaTickHandler {
currenttip.addAll(currenttipBody);
currenttip.addAll(currenttipTail);
this.tooltip = new Tooltip(currenttip, false);
tooltip = new Tooltip(currenttip, targetEnt);
}
}
@@ -153,6 +153,7 @@ option.stevescarts.shifttoggle=Shift toggle
option.general.showents=Show entities
option.general.showhp=Show entities health
option.general.showcrop=Show crop growth
option.general.showentityicon=Show entity icon
option.thaumcraft.aspects=Show aspects
nei.options.keys.showenchant=Show enchanting values