Files
NeoForge/patches/net/minecraft/client/gui/Hud.java.patch
T
b160270bd5 Port to 26.3 (#3403)
Co-authored-by: XFactHD <[email protected]>
Co-authored-by: Technici4n <[email protected]>
Co-authored-by: sciwhiz12 <[email protected]>
Co-authored-by: Matyrobbrt <[email protected]>
Co-authored-by: Marc Hermans <[email protected]>
Co-authored-by: Sara Freimer <[email protected]>
2026-09-16 03:08:09 +08:00

424 lines
22 KiB
Diff

--- a/net/minecraft/client/gui/Hud.java
+++ b/net/minecraft/client/gui/Hud.java
@@ -86,6 +_,8 @@
import org.jspecify.annotations.Nullable;
import net.neoforged.fml.common.asm.enumextension.IExtensibleEnum;
+// Neo: Exceptionally add a static wildcard import to make the patch bearable, and comments to avoid the detection by spotless rules.
+/* space for import change */ import static net.neoforged.neoforge.client.gui.VanillaGuiLayers.* /* space for wildcard import */;
@OnlyIn(Dist.CLIENT)
public class Hud {
@@ -181,6 +_,11 @@
private Pair<Hud.ContextualInfo, ContextualBar> contextualInfoBar = Pair.of(Hud.ContextualInfo.EMPTY, ContextualBar.EMPTY);
private final Map<Hud.ContextualInfo, Supplier<ContextualBar>> contextualInfoBars;
private float scopeScale;
+ private final net.neoforged.neoforge.client.gui.GuiLayerManager layerManager = new net.neoforged.neoforge.client.gui.GuiLayerManager();
+ /// Neo: This variable controls the height of overlays on the left of the hotbar (e.g. health, armor).
+ public int leftHeight;
+ /// Neo: This variable controls the height of overlays on the right of the hotbar (e.g. food, vehicle health, air).
+ public int rightHeight;
public Hud(Minecraft minecraft) {
this.minecraft = minecraft;
@@ -201,6 +_,7 @@
() -> new JumpableVehicleBar(minecraft)
);
this.resetTitleTimes();
+ this.registerVanillaLayers();
}
public void registerReloadListeners(ReloadableResourceManager resourceManager) {
@@ -224,28 +_,48 @@
public void extractRenderState(GuiGraphicsExtractor graphics, DeltaTracker deltaTracker) {
this.minecraft.gameRenderer.gameRenderState().guiRenderState.isHudHidden = this.isHidden;
if (!(this.minecraft.gui.screen() instanceof LevelLoadingScreen)) {
- if (!this.isHidden) {
- this.extractCameraOverlays(graphics, deltaTracker);
- this.extractCrosshair(graphics, deltaTracker);
- graphics.nextStratum();
- this.extractHotbarAndDecorations(graphics, deltaTracker);
- this.extractEffects(graphics, deltaTracker);
- this.extractBossOverlay(graphics, deltaTracker);
- }
-
- this.extractSleepOverlay(graphics, deltaTracker);
- if (!this.isHidden) {
- this.extractDemoOverlay(graphics, deltaTracker);
- this.extractScoreboardSidebar(graphics, deltaTracker);
- this.extractOverlayMessage(graphics, deltaTracker);
- this.extractTitle(graphics, deltaTracker);
- this.extractChat(graphics, deltaTracker);
- this.extractTabList(graphics, deltaTracker);
+ leftHeight = 39;
+ rightHeight = 39;
+ updateContextualBarRenderer();
+ layerManager.render(graphics, deltaTracker);
+ }
+ }
+
+ private void registerVanillaLayers() {
+ java.util.function.BooleanSupplier hudVisible = () -> !this.isHidden;
+ java.util.function.BooleanSupplier survivalVisible = () -> this.minecraft.gameMode.canHurtPlayer() && hudVisible.getAsBoolean();
+
+ layerManager.add(CAMERA_OVERLAYS, this::extractCameraOverlays, hudVisible);
+ layerManager.add(CROSSHAIR, this::extractCrosshair, hudVisible);
+ layerManager.add(AFTER_CAMERA_DECORATIONS, (guiGraphics, _) -> guiGraphics.nextStratum(), hudVisible);
+ // TODO: Split this up again into its pieces
+ layerManager.add(HOTBAR, this::extractHotbar, hudVisible);
+ layerManager.add(PLAYER_HEALTH, (guiGraphics, _) -> extractHealthLevel(guiGraphics), survivalVisible);
+ layerManager.add(ARMOR_LEVEL, (guiGraphics, _) -> extractArmorLevel(guiGraphics), survivalVisible);
+ layerManager.add(FOOD_LEVEL, (guiGraphics, _) -> extractFoodLevel(guiGraphics), survivalVisible);
+ layerManager.add(VEHICLE_HEALTH, (guiGraphics, _) -> extractVehicleHealth(guiGraphics), hudVisible);
+ layerManager.add(AIR_LEVEL, (guiGraphics, _) -> extractAirLevel(guiGraphics), survivalVisible);
+ layerManager.add(CONTEXTUAL_INFO_BAR_BACKGROUND, this::extractContextualInfoBarBackground, hudVisible);
+ layerManager.add(EXPERIENCE_LEVEL, this::extractExperienceLevel, hudVisible);
+ layerManager.add(CONTEXTUAL_INFO_BAR, this::extractContextualInfoBar, hudVisible);
+ layerManager.add(SELECTED_ITEM_NAME, this::maybeExtractSelectedItemName, hudVisible);
+ layerManager.add(SPECTATOR_TOOLTIP, this::maybeExtractSpectatorTooltip, hudVisible);
+ layerManager.add(EFFECTS, this::extractEffects, hudVisible);
+ layerManager.add(BOSS_OVERLAY, this::extractBossOverlay, hudVisible);
+ layerManager.add(SLEEP_OVERLAY, this::extractSleepOverlay);
+ layerManager.add(DEMO_OVERLAY, this::extractDemoOverlay, hudVisible);
+ layerManager.add(SCOREBOARD_SIDEBAR, this::extractScoreboardSidebar, hudVisible);
+ layerManager.add(OVERLAY_MESSAGE, this::extractOverlayMessage, hudVisible);
+ layerManager.add(TITLE, this::extractTitle, hudVisible);
+ layerManager.add(CHAT, this::extractChat, hudVisible);
+ layerManager.add(TAB_LIST, this::extractTabList, hudVisible);
+ layerManager.add(SUBTITLE_OVERLAY, (graphics, _) -> {
+ if (!this.isHidden) {
this.extractSubtitleOverlay(graphics, this.minecraft.gui.screen() == null || this.minecraft.gui.screen().isInGameUi());
} else if (this.minecraft.gui.screen() != null && this.minecraft.gui.screen().isInGameUi()) {
this.extractSubtitleOverlay(graphics, true);
}
- }
+ });
}
private void extractBossOverlay(GuiGraphicsExtractor graphics, DeltaTracker deltaTracker) {
@@ -289,8 +_,12 @@
for (EquipmentSlot slot : EquipmentSlot.values()) {
ItemStack item = player.getItemBySlot(slot);
Equippable equippable = item.get(DataComponents.EQUIPPABLE);
- if (equippable != null && equippable.slot() == slot && equippable.cameraOverlay().isPresent()) {
+ if (equippable != null && equippable.slot() == slot) {
+ if (equippable.cameraOverlay().isPresent()) {
this.extractTextureOverlay(graphics, equippable.cameraOverlay().get().withPath(p -> "textures/" + p + ".png"), 1.0F);
+ }
+
+ net.neoforged.neoforge.client.extensions.common.IClientItemExtensions.of(item).renderFirstPersonOverlay(item, slot, this.minecraft.player, graphics, deltaTracker);
}
}
}
@@ -343,7 +_,11 @@
if (alpha > 0) {
graphics.nextStratum();
graphics.pose().pushMatrix();
- graphics.pose().translate(graphics.guiWidth() / 2, graphics.guiHeight() - 68);
+ // Include a shift based on the bar height plus the difference between the height that renderSelectedItemName
+ // renders at (59) and the height that the overlay/status bar renders at (68) by default
+ int yShift = Math.max(leftHeight, rightHeight) + (68 - 59);
+ // If y shift is less than the default y level, just render it at the base y level
+ graphics.pose().translate(graphics.guiWidth() / 2, graphics.guiHeight() - Math.max(yShift, 68));
int color;
if (this.animateOverlayMessageColor) {
color = Mth.hsvToArgb(t / 50.0F, 0.7F, 0.6F, alpha);
@@ -404,10 +_,19 @@
private void extractChat(GuiGraphicsExtractor graphics, DeltaTracker deltaTracker) {
if (this.minecraft.player != null && !this.chat.isChatFocused()) {
Window window = this.minecraft.getWindow();
+ // Neo: Allow customizing position of chat component
+ var chatBottomMargin = 40; // See ChatComponent#BOTTOM_MARGIN (used in translate calls in ChatComponent#render)
+ var event = net.neoforged.neoforge.common.NeoForge.EVENT_BUS.post(
+ new net.neoforged.neoforge.client.event.CustomizeGuiOverlayEvent.Chat(window, graphics, deltaTracker, 0, graphics.guiHeight() - chatBottomMargin)
+ );
+ // The event is given the absolute Y position; account for chat component's own offsetting here
+ graphics.pose().pushMatrix();
+ graphics.pose().translate(event.getPosX(), (event.getPosY() - graphics.guiHeight() + chatBottomMargin) / (float) this.chat.getScale());
int mouseX = Mth.floor(this.minecraft.mouseHandler.getScaledXPos(window));
int mouseY = Mth.floor(this.minecraft.mouseHandler.getScaledYPos(window));
graphics.nextStratum();
this.chat.extractRenderState(graphics, this.getFont(), this.tickCount, mouseX, mouseY, ChatComponent.DisplayMode.BACKGROUND, false);
+ graphics.pose().popMatrix();
}
}
@@ -501,6 +_,8 @@
int harmfulCount = 0;
for (MobEffectInstance instance : Ordering.natural().reverse().sortedCopy(activeEffects)) {
+ var renderer = net.neoforged.neoforge.client.extensions.common.IClientMobEffectExtensions.of(instance);
+ if (!renderer.isVisibleInGui(instance)) continue;
Holder<MobEffect> effect = instance.getEffect();
if (instance.showIcon()) {
int x = graphics.guiWidth();
@@ -532,6 +_,7 @@
}
}
+ if (renderer.extractHudIcon(instance, this, graphics, x + 3, y + 3, 18, 18, ARGB.white(alpha))) continue;
graphics.blitSprite(RenderPipelines.GUI_TEXTURED, getMobEffectSprite(effect), x + 3, y + 3, 18, 18, ARGB.white(alpha));
}
}
@@ -542,32 +_,51 @@
return effect.unwrapKey().map(ResourceKey::identifier).map(id -> id.withPrefix("mob_effect/")).orElseGet(MissingTextureAtlasSprite::getLocation);
}
- private void extractHotbarAndDecorations(GuiGraphicsExtractor graphics, DeltaTracker deltaTracker) {
+ // Neo: Split off into separate method to wrap in a layer
+ private void extractHotbar(GuiGraphicsExtractor graphics, DeltaTracker deltaTracker) {
if (this.minecraft.gameMode.getPlayerMode() == GameType.SPECTATOR) {
this.spectatorGui.extractHotbar(graphics);
} else {
this.extractItemHotbar(graphics, deltaTracker);
}
-
- if (this.minecraft.gameMode.canHurtPlayer()) {
- this.extractPlayerHealth(graphics);
- }
-
- this.extractVehicleHealth(graphics);
+ }
+
+ // Neo: Wrap in a method to call it when re-rendering
+ private void updateContextualBarRenderer() {
Hud.ContextualInfo nextContextualInfo = this.nextContextualInfoState();
if (nextContextualInfo != this.contextualInfoBar.getFirst()) {
this.contextualInfoBar = Pair.of(nextContextualInfo, this.contextualInfoBars.get(nextContextualInfo).get());
}
+ }
+ // Neo: Split off into separate method to wrap in a layer
+ private void extractContextualInfoBarBackground(GuiGraphicsExtractor graphics, DeltaTracker deltaTracker) {
this.contextualInfoBar.getSecond().extractBackground(graphics, deltaTracker);
+ }
+
+ // Neo: Split off into separate method to wrap in a layer
+ private void extractExperienceLevel(GuiGraphicsExtractor graphics, DeltaTracker deltaTracker) {
if (this.minecraft.gameMode.hasExperience() && this.minecraft.player.experienceLevel > 0) {
ContextualBar.extractExperienceLevel(graphics, this.minecraft.font, this.minecraft.player.experienceLevel);
}
+ }
+ // Neo: Split off into separate method to wrap in a layer
+ private void extractContextualInfoBar(GuiGraphicsExtractor graphics, DeltaTracker deltaTracker) {
this.contextualInfoBar.getSecond().extractRenderState(graphics, deltaTracker);
+ }
+
+ // Neo: Split off into separate method to wrap in a layer
+ private void maybeExtractSelectedItemName(GuiGraphicsExtractor graphics, DeltaTracker deltaTracker) {
if (this.minecraft.gameMode.getPlayerMode() != GameType.SPECTATOR) {
- this.extractSelectedItemName(graphics);
- } else if (this.minecraft.player.isSpectator()) {
+ this.extractSelectedItemName(graphics, Math.max(this.leftHeight, this.rightHeight));
+ }
+ }
+
+ // Neo: Split off into separate method to wrap in a layer
+ private void maybeExtractSpectatorTooltip(GuiGraphicsExtractor graphics, DeltaTracker deltaTracker) {
+ // Neo: Note this is originally mutually exclusive with the condition in maybeRenderSelectedItemName
+ if (this.minecraft.gameMode.getPlayerMode() == GameType.SPECTATOR && this.minecraft.player.isSpectator()) {
this.spectatorGui.extractAction(graphics);
}
}
@@ -634,15 +_,20 @@
}
private void extractSelectedItemName(GuiGraphicsExtractor graphics) {
+ extractSelectedItemName(graphics, 0);
+ }
+
+ private void extractSelectedItemName(GuiGraphicsExtractor graphics, int yShift) {
if (this.toolHighlightTimer > 0 && !this.lastToolHighlight.isEmpty()) {
- MutableComponent str = Component.empty().append(this.lastToolHighlight.getHoverName()).withStyle(this.lastToolHighlight.getRarity().color());
+ MutableComponent str = Component.empty().append(this.lastToolHighlight.getHoverName()).withStyle(this.lastToolHighlight.getRarity().getStyleModifier());
if (this.lastToolHighlight.has(DataComponents.CUSTOM_NAME)) {
str.withStyle(ChatFormatting.ITALIC);
}
- int strWidth = this.getFont().width(str);
+ Component highlightTip = this.lastToolHighlight.getHighlightTip(str);
+ int strWidth = this.getFont().width(highlightTip);
int x = (graphics.guiWidth() - strWidth) / 2;
- int y = graphics.guiHeight() - 59;
+ int y = graphics.guiHeight() - Math.max(yShift, 59);
if (!this.minecraft.gameMode.canHurtPlayer()) {
y += 14;
}
@@ -653,7 +_,13 @@
}
if (alpha > 0) {
- graphics.textWithBackdrop(this.getFont(), str, x, y, strWidth, ARGB.white(alpha));
+ Font font = net.neoforged.neoforge.client.extensions.common.IClientItemExtensions.of(lastToolHighlight).getFont(lastToolHighlight, net.neoforged.neoforge.client.extensions.common.IClientItemExtensions.FontContext.SELECTED_ITEM_NAME);
+ if (font == null) {
+ graphics.textWithBackdrop(this.getFont(), highlightTip, x, y, strWidth, ARGB.white(alpha));
+ } else {
+ x = (graphics.guiWidth() - font.width(highlightTip)) / 2;
+ graphics.textWithBackdrop(font, highlightTip, x, y, strWidth, ARGB.white(alpha));
+ }
}
}
}
@@ -770,7 +_,15 @@
return (int)Math.ceil(hearts / 10.0);
}
+ @Deprecated // Neo: Split up into different layers
private void extractPlayerHealth(GuiGraphicsExtractor graphics) {
+ extractHealthLevel(graphics);
+ extractArmorLevel(graphics);
+ extractFoodLevel(graphics);
+ extractAirLevel(graphics);
+ }
+
+ private void extractHealthLevel(GuiGraphicsExtractor graphics) {
Player player = this.getCameraPlayer();
if (player != null) {
int currentHealth = Mth.ceil(player.getHealth());
@@ -794,33 +_,62 @@
this.random.setSeed(this.tickCount * 312871);
int xLeft = graphics.guiWidth() / 2 - 91;
int xRight = graphics.guiWidth() / 2 + 91;
- int yLineBase = graphics.guiHeight() - 39;
+ int yLineBase = graphics.guiHeight() - leftHeight;
float maxHealth = Math.max((float)player.getAttributeValue(Attributes.MAX_HEALTH), Math.max(oldHealth, currentHealth));
int totalAbsorption = Mth.ceil(player.getAbsorptionAmount());
int numHealthRows = Mth.ceil((maxHealth + totalAbsorption) / 2.0F / 10.0F);
int healthRowHeight = Math.max(10 - (numHealthRows - 2), 3);
int yLineAir = yLineBase - 10;
+ leftHeight += (numHealthRows - 1) * healthRowHeight + 10;
int heartOffsetIndex = -1;
if (player.hasEffect(MobEffects.REGENERATION)) {
heartOffsetIndex = this.tickCount % Mth.ceil(maxHealth + 5.0F);
}
- Profiler.get().push("armor");
- extractArmor(graphics, player, yLineBase, numHealthRows, healthRowHeight, xLeft);
- Profiler.get().popPush("health");
+ Profiler.get().push("health");
this.extractHearts(
graphics, player, xLeft, yLineBase, healthRowHeight, heartOffsetIndex, maxHealth, currentHealth, oldHealth, totalAbsorption, blink
);
+ Profiler.get().pop();
+ }
+ }
+
+ private void extractArmorLevel(GuiGraphicsExtractor graphics) {
+ Player player = this.getCameraPlayer();
+ if (player != null) {
+ int l = graphics.guiWidth() / 2 - 91;
+ Profiler.get().push("armor");
+ extractArmor(graphics, player, graphics.guiHeight() - leftHeight + 10, 1, 0, l);
+ Profiler.get().pop();
+ if (player.getArmorValue() > 0) {
+ leftHeight += 10;
+ }
+ }
+ }
+
+ private void extractFoodLevel(GuiGraphicsExtractor graphics) {
+ Player player = this.getCameraPlayer();
+ if (player != null) {
LivingEntity vehicleWithHearts = this.getPlayerVehicleWithHealth();
int vehicleHearts = this.getVehicleMaxHearts(vehicleWithHearts);
if (vehicleHearts == 0) {
- Profiler.get().popPush("food");
+ Profiler.get().push("food");
+ int xRight = graphics.guiWidth() / 2 + 91;
+ int yLineBase = graphics.guiHeight() - rightHeight;
this.extractFood(graphics, player, yLineBase, xRight);
- yLineAir -= 10;
+ rightHeight += 10;
+ Profiler.get().pop();
}
+ }
+ }
- Profiler.get().popPush("air");
- this.extractAirBubbles(graphics, player, vehicleHearts, yLineAir, xRight);
+ private void extractAirLevel(GuiGraphicsExtractor graphics) {
+ Player player = this.getCameraPlayer();
+ if (player != null) {
+ int xRight = graphics.guiWidth() / 2 + 91;
+ int yLineAir = graphics.guiHeight() - rightHeight;
+ Profiler.get().push("air");
+ this.extractAirBubbles(graphics, player, 10, yLineAir, xRight);
Profiler.get().pop();
}
}
@@ -909,7 +_,7 @@
private void extractAirBubbles(GuiGraphicsExtractor graphics, Player player, int vehicleHearts, int yLineAir, int xRight) {
int maxAirSupplyTicks = player.getMaxAirSupply();
int currentAirSupplyTicks = Math.clamp(player.getAirSupply(), 0, maxAirSupplyTicks);
- boolean isUnderWater = player.isEyeInFluid(FluidTags.WATER);
+ boolean isUnderWater = player.getFluidInteraction().isEyeInFluidMatching(player, (e, type, _) -> e.canDrownInFluidType(type));
if (isUnderWater || currentAirSupplyTicks < maxAirSupplyTicks) {
yLineAir = this.getAirBubbleYLine(vehicleHearts, yLineAir);
int fullAirBubbles = getCurrentAirSupplyBubble(currentAirSupplyTicks, maxAirSupplyTicks, -2);
@@ -933,6 +_,8 @@
graphics.blitSprite(RenderPipelines.GUI_TEXTURED, AIR_EMPTY_SPRITE, airBubbleXPos, yLineAir + wobbleYOffset, 9, 9);
}
}
+
+ rightHeight += 10;
}
}
@@ -1000,7 +_,7 @@
if (hearts != 0) {
int currentHealth = (int)Math.ceil(vehicleWithHearts.getHealth());
Profiler.get().popPush("mountHealth");
- int yLine1 = graphics.guiHeight() - 39;
+ int yLine1 = graphics.guiHeight() - rightHeight;
int xRight = graphics.guiWidth() / 2 + 91;
int yo = yLine1;
@@ -1021,6 +_,7 @@
}
yo -= 10;
+ rightHeight += 10;
}
}
}
@@ -1053,7 +_,9 @@
int top = (graphics.guiHeight() - height) / 2;
int right = left + width;
int bottom = top + height;
- graphics.blit(RenderPipelines.GUI_TEXTURED, SPYGLASS_SCOPE_LOCATION, left, top, 0.0F, 0.0F, width, height, width, height);
+ ItemStack useItem = this.minecraft.player.getUseItem();
+ var clientItemExtensions = net.neoforged.neoforge.client.extensions.common.IClientItemExtensions.of(useItem);
+ graphics.blit(RenderPipelines.GUI_TEXTURED, clientItemExtensions.getScopeOverlayTexture(useItem), left, top, 0.0F, 0.0F, width, height, width, height);
graphics.fill(RenderPipelines.GUI, 0, bottom, graphics.guiWidth(), graphics.guiHeight(), -16777216);
graphics.fill(RenderPipelines.GUI, 0, 0, graphics.guiWidth(), top, -16777216);
graphics.fill(RenderPipelines.GUI, 0, top, left, bottom, -16777216);
@@ -1202,7 +_,7 @@
this.toolHighlightTimer = 0;
} else if (this.lastToolHighlight.isEmpty()
|| !selected.is(this.lastToolHighlight.getItem())
- || !selected.getHoverName().equals(this.lastToolHighlight.getHoverName())) {
+ || (!selected.getHoverName().equals(this.lastToolHighlight.getHoverName()) || !selected.getHighlightTip(selected.getHoverName()).equals(this.lastToolHighlight.getHighlightTip(this.lastToolHighlight.getHoverName())))) {
this.toolHighlightTimer = (int)(40.0 * this.minecraft.options.notificationDisplayTime().get());
} else if (this.toolHighlightTimer > 0) {
this.toolHighlightTimer--;
@@ -1479,7 +_,18 @@
type = NORMAL;
}
+ type = net.neoforged.neoforge.client.ClientHooks.firePlayerHeartTypeEvent(player, type);
+
return type;
}
+
+ public static net.neoforged.fml.common.asm.enumextension.ExtensionInfo getExtensionInfo() {
+ return net.neoforged.fml.common.asm.enumextension.ExtensionInfo.nonExtended(Hud.HeartType.class);
+ }
+ }
+
+ @org.jetbrains.annotations.ApiStatus.Internal
+ public void initModdedOverlays() {
+ this.layerManager.initModdedLayers();
}
}