+ * This also restrict lower priority provider from redirecting the object, + * return {@code null} instead if the caller doesn't redirect. + */ + Result toSelf(); + + /** + * Redirect to nowhere, disabling the tooltip to be displayed. + */ + Result toNowhere(); + + /** + * Redirect to whatever object behind this object, essentially making this object appear invisible. + */ + Result toBehind(); + + /** + * Redirect to specific object at the hit result's position. + *
+ * Note: {@link BlockHitResult#withPosition(BlockPos)} will have the original
+ * {@linkplain HitResult#getLocation() hit location} (not to be confused with
+ * {@linkplain BlockHitResult#getBlockPos() block position}), you need to take
+ * account if the redirect target is not from something you own (e.g. redirecting
+ * to other mod's block).
+ */
+ Result to(HitResult hitResult);
+
+ /**
+ * Redirection result, only here to make sure it's not called multiple times.
+ */
+ @ApiStatus.NonExtendable
+ interface Result {
+
+ }
+
+}
diff --git a/src/main/java/mcp/mobius/waila/gui/hud/TargetRedirector.java b/src/main/java/mcp/mobius/waila/gui/hud/TargetRedirector.java
new file mode 100644
index 000000000..20b7dee3b
--- /dev/null
+++ b/src/main/java/mcp/mobius/waila/gui/hud/TargetRedirector.java
@@ -0,0 +1,51 @@
+package mcp.mobius.waila.gui.hud;
+
+import mcp.mobius.waila.api.ITargetRedirector;
+import net.minecraft.world.phys.HitResult;
+import org.jetbrains.annotations.Nullable;
+
+public class TargetRedirector implements ITargetRedirector {
+
+ private static final TargetRedirector INSTANCE = new TargetRedirector();
+ private static final Result RESULT = new Result();
+
+ public boolean self, nowhere, behind;
+ public @Nullable HitResult to;
+
+ public static TargetRedirector get() {
+ INSTANCE.self = false;
+ INSTANCE.nowhere = false;
+ INSTANCE.behind = false;
+ INSTANCE.to = null;
+ return INSTANCE;
+ }
+
+ @Override
+ public Result toSelf() {
+ self = true;
+ return RESULT;
+ }
+
+ @Override
+ public Result toNowhere() {
+ nowhere = true;
+ return RESULT;
+ }
+
+ @Override
+ public Result toBehind() {
+ behind = true;
+ return RESULT;
+ }
+
+ @Override
+ public Result to(HitResult hitResult) {
+ to = hitResult;
+ return RESULT;
+ }
+
+ public static class Result implements ITargetRedirector.Result {
+
+ }
+
+}
diff --git a/src/main/java/mcp/mobius/waila/gui/hud/TooltipHandler.java b/src/main/java/mcp/mobius/waila/gui/hud/TooltipHandler.java
index 779727f77..bf06954f5 100644
--- a/src/main/java/mcp/mobius/waila/gui/hud/TooltipHandler.java
+++ b/src/main/java/mcp/mobius/waila/gui/hud/TooltipHandler.java
@@ -6,6 +6,7 @@
import mcp.mobius.waila.api.IBlacklistConfig;
import mcp.mobius.waila.api.IBlockComponentProvider;
import mcp.mobius.waila.api.IEntityComponentProvider;
+import mcp.mobius.waila.api.ITargetRedirector;
import mcp.mobius.waila.api.ITheme;
import mcp.mobius.waila.api.IWailaConfig;
import mcp.mobius.waila.api.IWailaConfig.Overlay.Position.Align;
@@ -25,6 +26,7 @@
import net.minecraft.world.level.block.LiquidBlock;
import net.minecraft.world.phys.HitResult;
import net.minecraft.world.phys.Vec3;
+import org.jetbrains.annotations.Nullable;
import static mcp.mobius.waila.api.TooltipPosition.BODY;
import static mcp.mobius.waila.api.TooltipPosition.HEAD;
@@ -40,6 +42,10 @@ public class TooltipHandler {
private static final Tooltip TOOLTIP = new Tooltip();
private static final Component SNEAK_DETAIL = Component.translatable(Tl.Tooltip.SNEAK_FOR_DETAILS).withStyle(ChatFormatting.ITALIC);
+ private enum ProcessResult {
+ CONTINUE, BREAK
+ }
+
public static void tick() {
STATE.render = false;
@@ -87,94 +93,149 @@ public static void tick() {
if (castOrigin == null) return;
for (var target : results) {
- var accessor = ClientAccessor.INSTANCE;
- accessor.set(client.level, player, target, client.cameraEntity, castOrigin, castDirection, pickRange, client.getFrameTime());
+ if (processTarget(target, client, player, castOrigin, castDirection, pickRange, config) == ProcessResult.BREAK) break;
+ }
+ }
- TooltipRenderer.beginBuild(STATE);
+ private static ProcessResult redirectTarget(HitResult target, TargetRedirector redirector, Minecraft client, Player player, Vec3 castOrigin, Vec3 castDirection, float pickRange, WailaConfig.General config) {
+ if (redirector.nowhere) return ProcessResult.BREAK;
+ if (redirector.behind) return ProcessResult.CONTINUE;
- if (target.getType() == HitResult.Type.BLOCK) {
- var block = accessor.getBlock();
+ var redirect = redirector.to;
+ if (redirect == null) return ProcessResult.CONTINUE;
+ if (redirect.getType() == HitResult.Type.MISS) return ProcessResult.CONTINUE;
- if (block instanceof LiquidBlock) {
- if (!PluginConfig.CLIENT.getBoolean(WailaConstants.CONFIG_SHOW_FLUID)) continue;
- } else if (!PluginConfig.CLIENT.getBoolean(WailaConstants.CONFIG_SHOW_BLOCK)) {
- continue;
- }
+ return processTarget(
+ redirect, client, player,
+ castOrigin.subtract(target.getLocation().subtract(redirect.getLocation())),
+ castDirection, pickRange, config);
+ }
- if (IBlacklistConfig.get().contains(block)) continue;
+ private static ProcessResult processTarget(HitResult target, Minecraft client, Player player, Vec3 castOrigin, Vec3 castDirection, float pickRange, WailaConfig.General config) {
+ var accessor = ClientAccessor.INSTANCE;
+ accessor.set(client.level, player, target, client.cameraEntity, castOrigin, castDirection, pickRange, client.getFrameTime());
- var blockEntity = accessor.getBlockEntity();
- if (blockEntity != null && IBlacklistConfig.get().contains(blockEntity)) continue;
+ TooltipRenderer.beginBuild(STATE);
- var state = ComponentHandler.getOverrideBlock(target);
- if (state == IBlockComponentProvider.EMPTY_BLOCK_STATE) continue;
+ if (target.getType() == HitResult.Type.BLOCK) {
+ var block = accessor.getBlock();
+ var blockEntity = accessor.getBlockEntity();
- accessor.setState(state);
+ var redirector = TargetRedirector.get();
+ var redirectPriority = Integer.MAX_VALUE;
+ @Nullable ITargetRedirector.Result redirectResult = null;
- requestBlockData(accessor);
+ for (var entry : Registrar.get().blockRedirect.get(block)) {
+ redirectResult = entry.instance().redirect(redirector, accessor, PluginConfig.CLIENT);
+ redirectPriority = entry.priority();
+ if (redirectResult != null) break;
+ }
- TOOLTIP.clear();
- gatherBlock(accessor, TOOLTIP, HEAD);
- TooltipRenderer.add(TOOLTIP);
+ var hasBeRedirector = false;
+ for (var entry : Registrar.get().blockRedirect.get(blockEntity)) {
+ if (entry.priority() >= redirectPriority) break;
+ if (!hasBeRedirector) {
+ hasBeRedirector = true;
+ redirector = TargetRedirector.get();
+ }
+ redirectResult = entry.instance().redirect(redirector, accessor, PluginConfig.CLIENT);
+ if (redirectResult != null) break;
+ }
- TOOLTIP.clear();
- gatherBlock(accessor, TOOLTIP, BODY);
+ if (redirectResult != null && !redirector.self) {
+ return redirectTarget(target, redirector, client, player, castOrigin, castDirection, pickRange, config);
+ }
- if (config.isShiftForDetails() && !TOOLTIP.isEmpty() && !player.isShiftKeyDown()) {
- if (!config.isHideShiftText()) {
- TooltipRenderer.add(new Line(null).with(SNEAK_DETAIL));
- }
- } else {
- TooltipRenderer.add(TOOLTIP);
- }
+ if (block instanceof LiquidBlock) {
+ if (!PluginConfig.CLIENT.getBoolean(WailaConstants.CONFIG_SHOW_FLUID)) return ProcessResult.CONTINUE;
+ } else if (!PluginConfig.CLIENT.getBoolean(WailaConstants.CONFIG_SHOW_BLOCK)) {
+ return ProcessResult.CONTINUE;
+ }
- TOOLTIP.clear();
- gatherBlock(accessor, TOOLTIP, TAIL);
- } else if (target.getType() == HitResult.Type.ENTITY) {
- if (!PluginConfig.CLIENT.getBoolean(WailaConstants.CONFIG_SHOW_ENTITY)) continue;
+ if (IBlacklistConfig.get().contains(block)) return ProcessResult.CONTINUE;
- var actualEntity = accessor.getEntity();
- if (actualEntity == null) continue;
- if (IBlacklistConfig.get().contains(actualEntity)) continue;
+ if (blockEntity != null && IBlacklistConfig.get().contains(blockEntity)) return ProcessResult.CONTINUE;
- var targetEnt = ComponentHandler.getOverrideEntity(target);
- if (targetEnt == IEntityComponentProvider.EMPTY_ENTITY) continue;
+ var state = ComponentHandler.getOverrideBlock(target);
+ if (state == IBlockComponentProvider.EMPTY_BLOCK_STATE) return ProcessResult.CONTINUE;
- accessor.setEntity(targetEnt);
- if (targetEnt == null) continue;
+ accessor.setState(state);
- requestEntityData(targetEnt, accessor);
+ requestBlockData(accessor);
- TOOLTIP.clear();
- gatherEntity(targetEnt, accessor, TOOLTIP, HEAD);
- TooltipRenderer.add(TOOLTIP);
+ TOOLTIP.clear();
+ gatherBlock(accessor, TOOLTIP, HEAD);
+ TooltipRenderer.add(TOOLTIP);
- TOOLTIP.clear();
- gatherEntity(targetEnt, accessor, TOOLTIP, BODY);
+ TOOLTIP.clear();
+ gatherBlock(accessor, TOOLTIP, BODY);
- if (config.isShiftForDetails() && !TOOLTIP.isEmpty() && !player.isShiftKeyDown()) {
- if (!config.isHideShiftText()) {
- TooltipRenderer.add(new Line(null).with(SNEAK_DETAIL));
- }
- } else {
- TooltipRenderer.add(TOOLTIP);
+ if (config.isShiftForDetails() && !TOOLTIP.isEmpty() && !player.isShiftKeyDown()) {
+ if (!config.isHideShiftText()) {
+ TooltipRenderer.add(new Line(null).with(SNEAK_DETAIL));
}
+ } else {
+ TooltipRenderer.add(TOOLTIP);
+ }
+
+ TOOLTIP.clear();
+ gatherBlock(accessor, TOOLTIP, TAIL);
+ } else if (target.getType() == HitResult.Type.ENTITY) {
+ var actualEntity = accessor.getEntity();
+
+ var redirector = TargetRedirector.get();
+ @Nullable ITargetRedirector.Result redirectResult = null;
+
+ for (var entry : Registrar.get().entityRedirect.get(actualEntity)) {
+ redirectResult = entry.instance().redirect(redirector, accessor, PluginConfig.CLIENT);
+ if (redirectResult != null) break;
+ }
- TOOLTIP.clear();
- gatherEntity(targetEnt, accessor, TOOLTIP, TAIL);
+ if (redirectResult != null && !redirector.self) {
+ return redirectTarget(target, redirector, client, player, castOrigin, castDirection, pickRange, config);
}
+ if (!PluginConfig.CLIENT.getBoolean(WailaConstants.CONFIG_SHOW_ENTITY)) return ProcessResult.CONTINUE;
+
+ if (actualEntity == null) return ProcessResult.CONTINUE;
+ if (IBlacklistConfig.get().contains(actualEntity)) return ProcessResult.CONTINUE;
+
+ var targetEnt = ComponentHandler.getOverrideEntity(target);
+ if (targetEnt == IEntityComponentProvider.EMPTY_ENTITY) return ProcessResult.CONTINUE;
+
+ accessor.setEntity(targetEnt);
+ if (targetEnt == null) return ProcessResult.CONTINUE;
+
+ requestEntityData(targetEnt, accessor);
+
+ TOOLTIP.clear();
+ gatherEntity(targetEnt, accessor, TOOLTIP, HEAD);
TooltipRenderer.add(TOOLTIP);
- if (PluginConfig.CLIENT.getBoolean(WailaConstants.CONFIG_SHOW_ICON)) {
- TooltipRenderer.setIcon(ComponentHandler.getIcon(target));
+ TOOLTIP.clear();
+ gatherEntity(targetEnt, accessor, TOOLTIP, BODY);
+
+ if (config.isShiftForDetails() && !TOOLTIP.isEmpty() && !player.isShiftKeyDown()) {
+ if (!config.isHideShiftText()) {
+ TooltipRenderer.add(new Line(null).with(SNEAK_DETAIL));
+ }
+ } else {
+ TooltipRenderer.add(TOOLTIP);
}
- STATE.render = true;
- TooltipRenderer.endBuild();
+ TOOLTIP.clear();
+ gatherEntity(targetEnt, accessor, TOOLTIP, TAIL);
+ }
+
+ TooltipRenderer.add(TOOLTIP);
- break;
+ if (PluginConfig.CLIENT.getBoolean(WailaConstants.CONFIG_SHOW_ICON)) {
+ TooltipRenderer.setIcon(ComponentHandler.getIcon(target));
}
+
+ STATE.render = true;
+ TooltipRenderer.endBuild();
+ return ProcessResult.BREAK;
}
private static class ConfigTooltipRendererState implements TooltipRenderer.State {
diff --git a/src/main/java/mcp/mobius/waila/registry/Registrar.java b/src/main/java/mcp/mobius/waila/registry/Registrar.java
index 3c497f8df..eb47b2a71 100644
--- a/src/main/java/mcp/mobius/waila/registry/Registrar.java
+++ b/src/main/java/mcp/mobius/waila/registry/Registrar.java
@@ -48,6 +48,7 @@ public class Registrar implements IRegistrar {
private static final Log LOG = Log.create();
+ public final InstanceRegistry