[26.1.2] Split hotbar rendering into components, allow replacing renders (#10824)

This commit is contained in:
Matt 2026-06-09 14:21:51 -04:00 committed by GitHub
parent 6665cdcf4e
commit d3476f3f80
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 101 additions and 3 deletions

View file

@ -54,13 +54,27 @@
graphics.blitSprite(RenderPipelines.GUI_TEXTURED, getMobEffectSprite(effect), x + 3, y + 3, 18, 18, ARGB.white(alpha));
}
}
@@ -610,15 +_,23 @@
@@ -610,15 +_,37 @@
}
public void extractSelectedItemName(final GuiGraphicsExtractor graphics) {
+ renderSelectedItemName(graphics, 0);
+ }
+
+ // Forge: Maintain vanilla parity during FLD#BACKGROUND
+ public final void updateContextualInfo(final GuiGraphicsExtractor graphics, DeltaTracker deltaTracker) {
+ Gui.ContextualInfo nextContextualInfo = this.nextContextualInfoState();
+ if (nextContextualInfo != this.contextualInfoBar.getKey()) {
+ this.contextualInfoBar = Pair.of(nextContextualInfo, this.contextualInfoBarRenderers.get(nextContextualInfo).get());
+ }
+
+ this.contextualInfoBar.getValue().extractBackground(graphics, deltaTracker);
+ }
+ // Forge: Same as above but for FLD#CONTEXTUAL_INFO
+ public final void extractContextualInfoState(final GuiGraphicsExtractor graphics, DeltaTracker deltaTracker) {
+ this.contextualInfoBar.getValue().extractRenderState(graphics, deltaTracker);
+ }
+
+ public void renderSelectedItemName(final GuiGraphicsExtractor graphics, int yShift) {
if (this.toolHighlightTimer > 0 && !this.lastToolHighlight.isEmpty()) {
MutableComponent str = Component.empty().append(this.lastToolHighlight.getHoverName()).withStyle(this.lastToolHighlight.getRarity().color());

View file

@ -10,6 +10,7 @@ import net.minecraft.client.DeltaTracker;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.Gui;
import net.minecraft.client.gui.GuiGraphicsExtractor;
import net.minecraft.client.gui.contextualbar.ContextualBarRenderer;
import net.minecraft.resources.Identifier;
import net.minecraftforge.client.event.ForgeEventFactoryClient;
import net.minecraftforge.client.event.AddGuiOverlayLayersEvent;
@ -38,14 +39,27 @@ public final class ForgeLayeredDraw implements ForgeLayer {
private final List<ForgeLayer> bakedLayers = new ArrayList<>();
private final Identifier name;
// Begin pre-sleep overlay
public static final Identifier PRE_SLEEP_STACK = Identifier.withDefaultNamespace("pre_sleep_phase");
public static final Identifier CAMERA_OVERLAY = Identifier.withDefaultNamespace("camera_overlay");
public static final Identifier CROSSHAIR = Identifier.withDefaultNamespace("crosshair");
public static final Identifier CHANGE_STRATUM = Identifier.withDefaultNamespace("stratum_change");
// Begin hotbar
public static final Identifier HOTBAR_AND_DECOS = Identifier.withDefaultNamespace("hotbar");
public static final Identifier ITEM_HOTBAR = Identifier.withDefaultNamespace("item_hotbar");
public static final Identifier SPECTATOR_HOTBAR = Identifier.withDefaultNamespace("spectator_hotbar");
public static final Identifier HEALTH_BAR = Identifier.withDefaultNamespace("health_bar");
public static final Identifier VEHICLE_HEALTH = Identifier.withDefaultNamespace("vehicle_health");
public static final Identifier BACKGROUND = Identifier.withDefaultNamespace("background"); // Any layer needing contextual info should order at some point after this layer.
public static final Identifier EXPERIENCE_LEVEL = Identifier.withDefaultNamespace("experience_level");
public static final Identifier CONTEXTUAL_INFO = Identifier.withDefaultNamespace("contextual_info");
public static final Identifier SELECTED_ITEM_NAME = Identifier.withDefaultNamespace("selected_item_name");
public static final Identifier SPECTATOR_ACTION = Identifier.withDefaultNamespace("spectator_action");
// End hotbar
public static final Identifier POTION_EFFECTS = Identifier.withDefaultNamespace("potion_effects");
public static final Identifier BOSS_OVERLAY = Identifier.withDefaultNamespace("boss_overlay");
// End pre-sleep overlay
// Begin post-sleep overlay
public static final Identifier POST_SLEEP_STACK = Identifier.withDefaultNamespace("post_sleep_phase");
public static final Identifier DEMO_OVERLAY = Identifier.withDefaultNamespace("demo");
public static final Identifier DEBUG_OVERLAY = Identifier.withDefaultNamespace("debug");
@ -55,6 +69,7 @@ public final class ForgeLayeredDraw implements ForgeLayer {
public static final Identifier CHAT_OVERLAY = Identifier.withDefaultNamespace("chat_overlay");
public static final Identifier TAB_LIST = Identifier.withDefaultNamespace("tab_list");
public static final Identifier SUBTITLE_OVERLAY = Identifier.withDefaultNamespace("subtitle");
// End post-sleep overlay
public static final Identifier VANILLA_ROOT = Identifier.withDefaultNamespace("vanilla_root");
public static final Identifier SLEEP_OVERLAY = Identifier.withDefaultNamespace("sleep_overlay");
@ -87,6 +102,17 @@ public final class ForgeLayeredDraw implements ForgeLayer {
return this;
}
/**
* Adds a full draw stack and assumes condition is always true.
* Use {@linkplain ForgeLayeredDraw#putAbove} and {@linkplain ForgeLayeredDraw#putBelow} for fine location adjustment.
* @param name RL of the name to identify this stack with.
* @param layeredDraw the draw stack
* @return this
*/
public ForgeLayeredDraw add(Identifier name, ForgeLayeredDraw layeredDraw) {
return add(name, layeredDraw, () -> true);
}
/**
* Add a layer to the layer list. This layer will be at the end of the list, which means
* it will be rendered last (on top) of already added layers.
@ -279,6 +305,28 @@ public final class ForgeLayeredDraw implements ForgeLayer {
return this;
}
/**
* Replaces the renderer of a single layer and logs whodunnit, this is not recommended for obvious reasons.
* Will not work if target is a ForgeLayeredDraw
* Prefer {@linkplain ForgeLayeredDraw#addConditionTo}
* @param expectedLocation Layer stack where the target should be.
* @param targetLayer Target whose renderer should be replaced.
* @param replacementRenderer Renderer to use instead.
* @return this
*/
public ForgeLayeredDraw replace(Identifier expectedLocation, Identifier targetLayer, ForgeLayer replacementRenderer) {
locateStack(expectedLocation).ifPresentOrElse((stack) -> {
if (stack.namedLayers.get(targetLayer) != null) {
stack.namedLayers.put(targetLayer, replacementRenderer);
LogUtils.getLogger().debug("ForgeLayer {} in {} was replaced by {}.", targetLayer, expectedLocation, replacementRenderer);
} else {
LogUtils.getLogger().debug("ForgeLayer {} in {} was attempted to be replaced by {}, but it did not exist.", targetLayer, expectedLocation, replacementRenderer);
}
}, () -> stackNotPresentWarning(expectedLocation));
return this;
}
/**
* Propagate the layer order down to the inner render list after providing modders an opportunity to alter the list as they wish.
* @apiNote Modders should <emph>NEVER</emph> be calling this method.
@ -381,11 +429,22 @@ public final class ForgeLayeredDraw implements ForgeLayer {
@ApiStatus.Internal
public static void init(Gui gui, Minecraft minecraft) {
BooleanSupplier spectator = () -> minecraft.gameMode.isSpectator();
var hotbarCluster = new ForgeLayeredDraw(HOTBAR_AND_DECOS)
.addWithCondition(SPECTATOR_HOTBAR, (gg, dt) -> gui.getSpectatorGui().extractHotbar(gg), spectator)
.addWithCondition(ITEM_HOTBAR, gui::extractItemHotbar, () -> !spectator.getAsBoolean())
.addWithCondition(HEALTH_BAR, (gg, dt) -> gui.extractPlayerHealth(gg), () -> minecraft.gameMode.canHurtPlayer())
.add(VEHICLE_HEALTH, (gg, dt) -> gui.extractVehicleHealth(gg))
.add(BACKGROUND, gui::updateContextualInfo)
.addWithCondition(EXPERIENCE_LEVEL, (gg, dt) -> ContextualBarRenderer.extractExperienceLevel(gg, minecraft.font, minecraft.player.experienceLevel), () -> minecraft.gameMode.hasExperience() && minecraft.player.experienceLevel > 0)
.add(CONTEXTUAL_INFO, gui::extractContextualInfoState)
.addWithCondition(SELECTED_ITEM_NAME, (gg, dt) -> gui.extractSelectedItemName(gg), () -> !spectator.getAsBoolean())
.addWithCondition(SPECTATOR_ACTION, (gg, dt) -> gui.getSpectatorGui().extractAction(gg), spectator);
var preSleepDraw = new ForgeLayeredDraw(PRE_SLEEP_STACK)
.add(CAMERA_OVERLAY, gui::extractCameraOverlays)
.add(CROSSHAIR, gui::extractCrosshair)
.add(CHANGE_STRATUM, (gg, dt) -> gg.nextStratum())
.add(HOTBAR_AND_DECOS, gui::extractHotbarAndDecorations)
.add(HOTBAR_AND_DECOS, hotbarCluster)
.add(POTION_EFFECTS, gui::extractEffects)
.add(BOSS_OVERLAY, gui::extractBossOverlay);
var postSleepDraw = new ForgeLayeredDraw(POST_SLEEP_STACK)

View file

@ -0,0 +1,7 @@
{
"type": "minecraft:function",
"environment": "minecraft:default",
"function": "forge:modify_overlay_test/replace_renderer",
"max_ticks": 100,
"structure": "forge:empty3x3x3"
}

View file

@ -5,6 +5,10 @@
package net.minecraftforge.debug.client;
import net.minecraft.client.DeltaTracker;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.Gui;
import net.minecraft.client.gui.GuiGraphicsExtractor;
import net.minecraft.gametest.framework.GameTestHelper;
import net.minecraft.resources.Identifier;
import net.minecraftforge.client.event.AddGuiOverlayLayersEvent;
@ -39,6 +43,12 @@ public class ModifyOverlayTest extends BaseTestMod {
private static final ForgeLayer layerC = (gg,tr) -> {};
private static final Identifier layerCName = name("layer_c");
private static final IForgeGameTestHelper.BoolFlag detectReplaceFlag = new IForgeGameTestHelper.BoolFlag("det_replace_flag");
// Wrapper renderer since we just need to change the callee location to test
private static final ForgeLayer replacementRenderer = (gg, tracker) -> {
Minecraft.getInstance().gui.extractEffects(gg, tracker);
detectReplaceFlag.set(true);
};
private static final IForgeGameTestHelper.BoolFlag detectConditionFlag = new IForgeGameTestHelper.BoolFlag("det_cond_flag");
private static final IForgeGameTestHelper.BoolFlag detectConditionStackFlag = new IForgeGameTestHelper.BoolFlag("det_cond_stack_flag");
@ -119,6 +129,13 @@ public class ModifyOverlayTest extends BaseTestMod {
});
}
@GameTest
public static void replace_renderer(GameTestHelper helper) {
helper.assertTrue(detectReplaceFlag.getBool(), "Replace flag was not set.");
detectReplaceFlag.set(false);
helper.succeed();
}
private void overlayTestListener(AddGuiOverlayLayersEvent event) {
drawStack = event.getLayeredDraw();
var layeredDraw = event.getLayeredDraw();
@ -128,6 +145,7 @@ public class ModifyOverlayTest extends BaseTestMod {
// Layers have to be present to be ordered against, of course, but we tested for that already above.
pEffects.addAbove(layerBName, POTION_EFFECTS, layerB);
pEffects.addAbove(layerCName, layerBName, layerC);
layeredDraw.replace(PRE_SLEEP_STACK, POTION_EFFECTS, replacementRenderer);
layeredDraw.addBelow(PRE_SLEEP_STACK, layerAName, layerBName, layerA);
layeredDraw.addConditionTo(PRE_SLEEP_STACK, BOSS_OVERLAY, () -> {
if (enableConditionFlag.getBool()) {