diff --git a/patches/minecraft/net/minecraft/client/gui/Gui.java.patch b/patches/minecraft/net/minecraft/client/gui/Gui.java.patch index fb02ee442c..dba9af4ad4 100644 --- a/patches/minecraft/net/minecraft/client/gui/Gui.java.patch +++ b/patches/minecraft/net/minecraft/client/gui/Gui.java.patch @@ -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()); diff --git a/src/main/java/net/minecraftforge/client/gui/overlay/ForgeLayeredDraw.java b/src/main/java/net/minecraftforge/client/gui/overlay/ForgeLayeredDraw.java index 932f7a96f8..31fd022204 100644 --- a/src/main/java/net/minecraftforge/client/gui/overlay/ForgeLayeredDraw.java +++ b/src/main/java/net/minecraftforge/client/gui/overlay/ForgeLayeredDraw.java @@ -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 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 NEVER 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) diff --git a/src/test/generated/modify_overlay_test/data/forge/test_instance/modify_overlay_test/replace_renderer.json b/src/test/generated/modify_overlay_test/data/forge/test_instance/modify_overlay_test/replace_renderer.json new file mode 100644 index 0000000000..f018811795 --- /dev/null +++ b/src/test/generated/modify_overlay_test/data/forge/test_instance/modify_overlay_test/replace_renderer.json @@ -0,0 +1,7 @@ +{ + "type": "minecraft:function", + "environment": "minecraft:default", + "function": "forge:modify_overlay_test/replace_renderer", + "max_ticks": 100, + "structure": "forge:empty3x3x3" +} \ No newline at end of file diff --git a/src/test/java/net/minecraftforge/debug/client/ModifyOverlayTest.java b/src/test/java/net/minecraftforge/debug/client/ModifyOverlayTest.java index 4dd35eb55c..269b9f6e9e 100644 --- a/src/test/java/net/minecraftforge/debug/client/ModifyOverlayTest.java +++ b/src/test/java/net/minecraftforge/debug/client/ModifyOverlayTest.java @@ -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()) {