跳到主要内容

自定义物品提示框

img

首先是html部分,模仿了雨中冒险2的设计,并且加上了淡入淡出。

<body>
<div id="tooltip"></div>
</body>

<style>
body {
opacity: 0;
pointer-events: none;
}

body.shown {
animation: tooltip-fade-in 0.2s ease-out forwards;
}

body.hiding {
animation: tooltip-fade-out 0.2s ease forwards;
}

@keyframes tooltip-fade-in {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}

@keyframes tooltip-fade-out {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}

#tooltip {
position: fixed;
box-shadow: 0 0 3px #44000000;
background-color: rgba(100, 100, 100, 0.3);
backdrop-filter: blur(32px);
font-family: "default";
transform: translateZ(1000px);
display: flex;
flex-direction: column;
font-size: 9px;
color: #ddd;
text-stroke: 3px #666;
padding-bottom: 2px;
margin-left: 11px;
min-height: 31px;
}

#tooltip span {
padding-left: 4px;
}

#tooltip span:first-child {
/*--red: #e55c5c;*/
--red: rgb(153, 51, 51);
--blue: rgb(103, 186, 255);
--purple: #c84df0;
--green: #60d560;
--gold: #ffe661;
--gray: #aaa;
color: #fff;
background-color: var(--red);
font-size: 11px;
line-height: 16px;
margin-bottom: 4px;
min-width: 120px;
text-stroke: 3px #777;
}
</style>

然后是逻辑部分,拦截原版tooltip然后在同样的位置渲染新的tooltip。

KJS写法:

// startup_scripts
let DOC_PATH = "test/tooltip.html";
let document;
let tooltipRoot;
let lastStack;
let lastSeenAt;

ForgeEvents.onEvent("net.minecraftforge.event.entity.player.ItemTooltipEvent", event => {
ensureTooltip();
let stack = event.getItemStack();
if (!isSameItem(stack) || !"shown".equals(document.body.getAttribute("class"))) rebuildTooltip(stack, event);
lastStack = stack;
lastSeenAt = new Date().getTime();
});

ForgeEvents.onEvent("net.minecraftforge.client.event.RenderTooltipEvent$Pre", event => {
ensureTooltip();
if (lastSeenAt === 0 || tooltipRoot == null) return;
lastSeenAt = new Date().getTime();
event.setCanceled(true);
tooltipRoot.setAttribute("style", "left: " + event.getX() + "px; top: " + event.getY() + "px;");
if (!"shown".equals(document.body.getAttribute("class"))) document.body.setAttribute("class", "shown");
});

function ensureTooltip() {
if (ApricityUI.getDocument(DOC_PATH).isEmpty()) ApricityUI.createDocument(DOC_PATH);
if (!ApricityUI.getDocument(DOC_PATH).isEmpty()) {
if (document == null) document = ApricityUI.getDocument(DOC_PATH).get(0);
tooltipRoot = document.querySelector("#tooltip");
}
}

global.tooltipTick = function () {
ensureTooltip();
if (lastSeenAt === 0) return;
if (new Date().getTime() - lastSeenAt <= 50) return;
document.body.setAttribute("class", "hiding");
lastSeenAt = 0;
}

function isSameItem(stack) {
return lastStack != null && stack.serializeNBT().equals(lastStack.serializeNBT());
}

function rebuildTooltip(stack, event) {
if (tooltipRoot == null) return;
tooltipRoot.children.forEach(e => e.remove());
if (event.getToolTip().size() <= 1) event.getToolTip().add(Component.literal("该物品暂无更多描述。"));
event.getToolTip().forEach(component => {
let span = document.createElement("SPAN");
span.innerText = component.getString();
tooltipRoot.append(span);
});
applyTooltipRarityStyle(stack);
}

function applyTooltipRarityStyle(stack) {
if (tooltipRoot == null || tooltipRoot.children.isEmpty() || stack == null) return;
let title = tooltipRoot.children.get(0);
if (title == null) return;
title.setAttribute("style", "background-color: " + resolveRarityColor(stack.getRarity()) + ";");
}

function resolveRarityColor(rarity) {
if (rarity == null) return "var(--blue)";
switch (rarity) {
case "UNCOMMON": return "var(--green)";
case "RARE": return "var(--purple)";
case "EPIC": return "var(--red)";
default: return "var(--blue)";
}
}
// client_scripts
PlayerEvents.tick(global.tooltipTick);

Java写法:

package com.sighs.apricityui.event;

import com.sighs.apricityui.init.Document;
import com.sighs.apricityui.init.Element;
import net.minecraft.network.chat.Component;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Rarity;
import net.minecraftforge.client.event.RenderTooltipEvent;
import net.minecraftforge.event.TickEvent;
import net.minecraftforge.event.entity.player.ItemTooltipEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;

public class Test {
private static final String DOC_PATH = "test/tooltip.html";
private static final long TOOLTIP_HIDE_DELAY_MS = 50L;
private static final String RARITY_COMMON_COLOR = "var(--blue)";
private static final String RARITY_UNCOMMON_COLOR = "var(--green)";
private static final String RARITY_RARE_COLOR = "var(--purple)";
private static final String RARITY_EPIC_COLOR = "var(--red)";

private static Document document;
private static Element tooltipRoot;
private static ItemStack lastStack;
private static long lastSeenAt;

@SubscribeEvent
public static void tick(TickEvent.ClientTickEvent event) {
ensureTooltip();
if (lastSeenAt == 0) return;
if (System.currentTimeMillis() - lastSeenAt <= TOOLTIP_HIDE_DELAY_MS) return;
document.body.setAttribute("class", "hiding");
lastSeenAt = 0;
}

@SubscribeEvent
public static void tooltip(ItemTooltipEvent event) {
ensureTooltip();
ItemStack stack = event.getItemStack();
if (!isSameItem(stack) || !"shown".equals(document.body.getAttribute("class"))) {
rebuildTooltip(stack, event);
}
lastStack = stack;
lastSeenAt = System.currentTimeMillis();
}

@SubscribeEvent
public static void tooltip(RenderTooltipEvent.Pre event) {
ensureTooltip();
if (lastSeenAt == 0 || tooltipRoot == null) return;

lastSeenAt = System.currentTimeMillis();
event.setCanceled(true);
tooltipRoot.setAttribute("style", "left: " + event.getX() + "px; top: " + event.getY() + "px;");
if (!"shown".equals(document.body.getAttribute("class"))) {
document.body.setAttribute("class", "shown");
}
}

private static void ensureTooltip() {
if (Document.get(DOC_PATH).isEmpty()) {
Document.create(DOC_PATH);
}
if (!Document.get(DOC_PATH).isEmpty()) {
if (document == null) document = Document.get(DOC_PATH).get(0);
tooltipRoot = document.querySelector("#tooltip");
}
}

private static boolean isSameItem(ItemStack stack) {
return lastStack != null && stack.serializeNBT().equals(lastStack.serializeNBT());
}

private static void rebuildTooltip(ItemStack stack, ItemTooltipEvent event) {
if (tooltipRoot == null) return;
tooltipRoot.children.forEach(Element::remove);
if (event.getToolTip().size() <= 1) event.getToolTip().add(Component.literal("该物品暂无更多描述。"));
event.getToolTip().forEach(component -> {
Element span = document.createElement("SPAN");
span.innerText = component.getString();
tooltipRoot.append(span);
});
applyTooltipRarityStyle(stack);
}

private static void applyTooltipRarityStyle(ItemStack stack) {
if (tooltipRoot == null || tooltipRoot.children.isEmpty() || stack == null) return;
Element title = tooltipRoot.children.get(0);
if (title == null) return;
title.setAttribute("style", "background-color: " + resolveRarityColor(stack.getRarity()) + ";");
}

private static String resolveRarityColor(Rarity rarity) {
if (rarity == null) return RARITY_COMMON_COLOR;
return switch (rarity) {
case UNCOMMON -> RARITY_UNCOMMON_COLOR;
case RARE -> RARITY_RARE_COLOR;
case EPIC -> RARITY_EPIC_COLOR;
default -> RARITY_COMMON_COLOR;
};
}
}