Compare commits

...
3 Commits
Author SHA1 Message Date
LexManos 9ccfaf4812 1.21.5 RB 1 2025-08-27 12:02:50 -07:00
Paint_Ninja 3ed3d2b9cf Update to EventBus 6.2.32 (#10644)
- Performance improvements, especially for posting to zero or one listeners on an event
- New Event#cancel() convenience method
- Only the monitor priority is tracked on the Forge bus now
2025-08-27 17:36:13 +01:00
Matt 3d374fe732 [1.21.5] Add ForgeLayeredDraw & ModifyOverlayLayersEvent as API to making gui layers. (#10556) 2025-07-25 10:44:49 -07:00
15 changed files with 716 additions and 7 deletions

No files matched your search

+1 -1
View File
@@ -2,7 +2,7 @@
MinecraftForge
=============
[![Stable Release](https://img.shields.io/badge/dynamic/json?url=https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json&label=Stable&prefix=1.21.4-&query=$.promos["1.21.4-recommended"]&color=brightgreen&logo=data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyBjbGFzcz0ic3ZnLWlubGluZS0tZmEgZmEtc3RhciBmYS13LTE4IiBhcmlhLWhpZGRlbj0idHJ1ZSIgZGF0YS1pY29uPSJzdGFyIiBkYXRhLXByZWZpeD0iZmFzIiBmb2N1c2FibGU9ImZhbHNlIiByb2xlPSJpbWciIHZpZXdCb3g9IjAgMCA1NzYgNTEyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgo8cGF0aCBkPSJNMjU5LjMgMTcuOEwxOTQgMTUwLjIgNDcuOSAxNzEuNWMtMjYuMiAzLjgtMzYuNyAzNi4xLTE3LjcgNTQuNmwxMDUuNyAxMDMtMjUgMTQ1LjVjLTQuNSAyNi4zIDIzLjIgNDYgNDYuNCAzMy43TDI4OCA0MzkuNmwxMzAuNyA2OC43YzIzLjIgMTIuMiA1MC45LTcuNCA0Ni40LTMzLjdsLTI1LTE0NS41IDEwNS43LTEwM2MxOS0xOC41IDguNS01MC44LTE3LjctNTQuNkwzODIgMTUwLjIgMzE2LjcgMTcuOGMtMTEuNy0yMy42LTQ1LjYtMjMuOS01Ny40IDB6IiBmaWxsPSJ3aGl0ZSIvPgo8L3N2Zz4K)][Download]
[![Stable Release](https://img.shields.io/badge/dynamic/json?url=https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json&label=Stable&prefix=1.21.5-&query=$.promos["1.21.5-recommended"]&color=brightgreen&logo=data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyBjbGFzcz0ic3ZnLWlubGluZS0tZmEgZmEtc3RhciBmYS13LTE4IiBhcmlhLWhpZGRlbj0idHJ1ZSIgZGF0YS1pY29uPSJzdGFyIiBkYXRhLXByZWZpeD0iZmFzIiBmb2N1c2FibGU9ImZhbHNlIiByb2xlPSJpbWciIHZpZXdCb3g9IjAgMCA1NzYgNTEyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgo8cGF0aCBkPSJNMjU5LjMgMTcuOEwxOTQgMTUwLjIgNDcuOSAxNzEuNWMtMjYuMiAzLjgtMzYuNyAzNi4xLTE3LjcgNTQuNmwxMDUuNyAxMDMtMjUgMTQ1LjVjLTQuNSAyNi4zIDIzLjIgNDYgNDYuNCAzMy43TDI4OCA0MzkuNmwxMzAuNyA2OC43YzIzLjIgMTIuMiA1MC45LTcuNCA0Ni40LTMzLjdsLTI1LTE0NS41IDEwNS43LTEwM2MxOS0xOC41IDguNS01MC44LTE3LjctNTQuNkwzODIgMTUwLjIgMzE2LjcgMTcuOGMtMTEuNy0yMy42LTQ1LjYtMjMuOS01Ny40IDB6IiBmaWxsPSJ3aGl0ZSIvPgo8L3N2Zz4K)][Download]
[![Latest Release](https://img.shields.io/badge/dynamic/json?url=https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json&label=Latest&prefix=1.21.5-&query=$.promos["1.21.5-latest"]&color=blue&logo=data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyBjbGFzcz0ic3ZnLWlubGluZS0tZmEgZmEtYnVnIGZhLXctMTYiIGFyaWEtaGlkZGVuPSJ0cnVlIiBkYXRhLWljb249ImJ1ZyIgZGF0YS1wcmVmaXg9ImZhcyIgZm9jdXNhYmxlPSJmYWxzZSIgcm9sZT0iaW1nIiB2aWV3Qm94PSIwIDAgNTEyIDUxMiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTUxMS45ODggMjg4LjljLS40NzggMTcuNDMtMTUuMjE3IDMxLjEtMzIuNjUzIDMxLjFINDI0djE2YzAgMjEuODY0LTQuODgyIDQyLjU4NC0xMy42IDYxLjE0NWw2MC4yMjggNjAuMjI4YzEyLjQ5NiAxMi40OTcgMTIuNDk2IDMyLjc1OCAwIDQ1LjI1NS0xMi40OTggMTIuNDk3LTMyLjc1OSAxMi40OTYtNDUuMjU2IDBsLTU0LjczNi01NC43MzZDMzQ1Ljg4NiA0NjcuOTY1IDMxNC4zNTEgNDgwIDI4MCA0ODBWMjM2YzAtNi42MjctNS4zNzMtMTItMTItMTJoLTI0Yy02LjYyNyAwLTEyIDUuMzczLTEyIDEydjI0NGMtMzQuMzUxIDAtNjUuODg2LTEyLjAzNS05MC42MzYtMzIuMTA4bC01NC43MzYgNTQuNzM2Yy0xMi40OTggMTIuNDk3LTMyLjc1OSAxMi40OTYtNDUuMjU2IDAtMTIuNDk2LTEyLjQ5Ny0xMi40OTYtMzIuNzU4IDAtNDUuMjU1bDYwLjIyOC02MC4yMjhDOTIuODgyIDM3OC41ODQgODggMzU3Ljg2NCA4OCAzMzZ2LTE2SDMyLjY2NkMxNS4yMyAzMjAgLjQ5MSAzMDYuMzMuMDEzIDI4OC45LS40ODQgMjcwLjgxNiAxNC4wMjggMjU2IDMyIDI1Nmg1NnYtNTguNzQ1bC00Ni42MjgtNDYuNjI4Yy0xMi40OTYtMTIuNDk3LTEyLjQ5Ni0zMi43NTggMC00NS4yNTUgMTIuNDk4LTEyLjQ5NyAzMi43NTgtMTIuNDk3IDQ1LjI1NiAwTDE0MS4yNTUgMTYwaDIyOS40ODlsNTQuNjI3LTU0LjYyN2MxMi40OTgtMTIuNDk3IDMyLjc1OC0xMi40OTcgNDUuMjU2IDAgMTIuNDk2IDEyLjQ5NyAxMi40OTYgMzIuNzU4IDAgNDUuMjU1TDQyNCAxOTcuMjU1VjI1Nmg1NmMxNy45NzIgMCAzMi40ODQgMTQuODE2IDMxLjk4OCAzMi45ek0yNTcgMGMtNjEuODU2IDAtMTEyIDUwLjE0NC0xMTIgMTEyaDIyNEMzNjkgNTAuMTQ0IDMxOC44NTYgMCAyNTcgMHoiIGZpbGw9IndoaXRlIi8+Cjwvc3ZnPgo=
)][Download] [![Discord](https://img.shields.io/discord/313125603924639766.svg?color=%237289da&label=Discord&logo=discord&logoColor=%237289da)][Discord] [![Support](https://img.shields.io/badge/Patreon-Support-orange.svg?logo=Patreon)][Patreon]
@@ -9,6 +9,40 @@
private final SubtitleOverlay subtitleOverlay;
private final SpectatorGui spectatorGui;
private final PlayerTabOverlay tabList;
@@ -168,7 +_,7 @@
private int lastBubblePopSoundPlayed;
private float autosaveIndicatorValue;
private float lastAutosaveIndicatorValue;
- private final LayeredDraw layers = new LayeredDraw();
+ private final LayeredDraw layers = new net.minecraftforge.client.gui.overlay.ForgeLayeredDraw(net.minecraftforge.client.gui.overlay.ForgeLayeredDraw.VANILLA_ROOT, net.minecraftforge.client.gui.overlay.ForgeLayeredDraw.COMBINE_LIST);
private float scopeScale;
public Gui(Minecraft p_330021_) {
@@ -180,14 +_,14 @@
this.bossOverlay = new BossHealthOverlay(p_330021_);
this.subtitleOverlay = new SubtitleOverlay(p_330021_);
this.resetTitleTimes();
- LayeredDraw layereddraw = new LayeredDraw()
+ LayeredDraw layereddraw = new net.minecraftforge.client.gui.overlay.ForgeLayeredDraw(net.minecraftforge.client.gui.overlay.ForgeLayeredDraw.PRE_SLEEP_STACK, net.minecraftforge.client.gui.overlay.ForgeLayeredDraw.PRE_LIST)
.add(this::renderCameraOverlays)
.add(this::renderCrosshair)
.add(this::renderHotbarAndDecorations)
.add(this::renderExperienceLevel)
.add(this::renderEffects)
.add((p_325313_, p_342678_) -> this.bossOverlay.render(p_325313_));
- LayeredDraw layereddraw1 = new LayeredDraw()
+ LayeredDraw layereddraw1 = new net.minecraftforge.client.gui.overlay.ForgeLayeredDraw(net.minecraftforge.client.gui.overlay.ForgeLayeredDraw.POST_SLEEP_STACK,net.minecraftforge.client.gui.overlay.ForgeLayeredDraw.POST_LIST)
.add(this::renderDemoOverlay)
.add((p_325315_, p_344521_) -> {
if (this.debugOverlay.showDebugScreen()) {
@@ -204,6 +_,7 @@
.add(layereddraw, () -> !p_330021_.options.hideGui)
.add(this::renderSleepOverlay)
.add(layereddraw1, () -> !p_330021_.options.hideGui);
+ ((net.minecraftforge.client.gui.overlay.ForgeLayeredDraw) layers).resolveLayers();
}
public void resetTitleTimes() {
@@ -347,7 +_,7 @@
Window window = this.minecraft.getWindow();
int i = Mth.floor(this.minecraft.mouseHandler.getScaledXPos(window));
+1 -1
View File
@@ -32,7 +32,7 @@ dependencyResolutionManagement {
library('accesstransformers', 'net.minecraftforge:accesstransformers:8.2.2')
library('coremods', 'net.minecraftforge:coremods:5.2.6')
library('nashorn', 'org.openjdk.nashorn:nashorn-core:15.4') // Needed by coremods, because the JRE no longer ships JS
library('eventbus', 'net.minecraftforge:eventbus:6.2.27')
library('eventbus', 'net.minecraftforge:eventbus:6.2.32')
library('typetools', 'net.jodah:typetools:0.6.3') // Needed by EventBus because of lambdas
library('mergetool-api', 'net.minecraftforge:mergetool-api:1.0')
@@ -0,0 +1,40 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client.event;
import net.minecraftforge.client.gui.overlay.ForgeLayeredDraw;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.fml.event.IModBusEvent;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
/**
* Fired when the {@linkplain ForgeLayeredDraw#VANILLA_ROOT}'s order is resolved during{@link ForgeLayeredDraw#resolveLayers().
* This can be used to add additional or move gui layers and entire layer stacks as needed.
*
* <p> This event is not {@linkplain net.minecraftforge.eventbus.api.Cancelable cancellable} and does not {@linkplain net.minecraftforge.eventbus.api.Event.HasResult have a result}</p>
*
* <p> This event is fired on the {@linkplain IModBusEvent mod event bus},
* only on the {@linkplain net.minecraftforge.fml.LogicalSide logical client}.</p>
*/
public final class AddGuiOverlayLayersEvent extends Event implements IModBusEvent {
@NotNull
private final ForgeLayeredDraw layeredDraw;
@ApiStatus.Internal
public AddGuiOverlayLayersEvent(ForgeLayeredDraw layeredDraw) {
this.layeredDraw = layeredDraw;
}
/**
* Get the layered draw.
* @return the draw layer stack
*/
@NotNull
public ForgeLayeredDraw getLayeredDraw() {
return layeredDraw;
}
}
@@ -10,6 +10,7 @@ import java.util.List;
import java.util.Map;
import java.util.function.Function;
import net.minecraftforge.client.gui.overlay.ForgeLayeredDraw;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.TickEvent;
import net.minecraftforge.eventbus.api.Cancelable;
@@ -303,4 +304,8 @@ public final class ForgeEventFactoryClient {
// This is on the mod bus because it happens during the initial texture reload, which is while the Forge bus is shut down.
return fireModBus(new ModelEvent.RegisterModelStateDefinitions());
}
public static void onComputeLayerOrder(ForgeLayeredDraw layeredDraw) {
fireModBus(new AddGuiOverlayLayersEvent(layeredDraw));
}
}
@@ -0,0 +1,426 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client.gui.overlay;
import com.google.common.collect.ImmutableList;
import com.mojang.logging.LogUtils;
import net.minecraft.FieldsAreNonnullByDefault;
import net.minecraft.MethodsReturnNonnullByDefault;
import net.minecraft.client.gui.Gui;
import net.minecraft.client.gui.LayeredDraw;
import net.minecraft.resources.ResourceLocation;
import net.minecraftforge.client.event.ForgeEventFactoryClient;
import net.minecraftforge.client.event.AddGuiOverlayLayersEvent;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
import javax.annotation.ParametersAreNonnullByDefault;
import java.util.*;
import java.util.function.BooleanSupplier;
/**
* As vanilla has switched to a layered drawing system for overlays, this system replaces ForgeGui and its associated headaches.
* Vanilla will now have resource locations to represent its render layers which modders can order against.
* This class is effectively a pseudo-registry for Layers. Add what you need during {@linkplain AddGuiOverlayLayersEvent}
* Layers must be uniquely named per ForgeLayeredDraw, but may be duplicated across different instances.
* Any change which would result in a duplicate will not be applied.
* All methods which return a {@linkplain ForgeLayeredDraw} will return its caller's instance.
* To select a specific instance, use {@linkplain ForgeLayeredDraw#locateStack(ResourceLocation)} before adding
* or use the methods that include a stack identifier.
*/
@FieldsAreNonnullByDefault
@MethodsReturnNonnullByDefault
@ParametersAreNonnullByDefault
public final class ForgeLayeredDraw extends LayeredDraw {
private static int unknown = 0;
private final Map<ResourceLocation, Layer> namedLayers = new HashMap<>();
private final Map<ResourceLocation, Map.Entry<LayeredDraw, BooleanSupplier>> subLayerStacks = new HashMap<>();
private final List<ResourceLocation> order = new LinkedList<>();
private final List<ResourceLocation> expectedNames = new ArrayList<>();
private final ResourceLocation name;
public static final ResourceLocation PRE_SLEEP_STACK = ResourceLocation.withDefaultNamespace("pre_sleep_phase");
public static final ResourceLocation CAMERA_OVERLAY = ResourceLocation.withDefaultNamespace("camera_overlay");
public static final ResourceLocation CROSSHAIR = ResourceLocation.withDefaultNamespace("crosshair");
public static final ResourceLocation HOTBAR = ResourceLocation.withDefaultNamespace("hotbar");
public static final ResourceLocation EXPERIENCE = ResourceLocation.withDefaultNamespace("experience");
public static final ResourceLocation POTION_EFFECTS = ResourceLocation.withDefaultNamespace("potion_effects");
public static final ResourceLocation BOSS_OVERLAY = ResourceLocation.withDefaultNamespace("boss_overlay");
public static final ResourceLocation POST_SLEEP_STACK = ResourceLocation.withDefaultNamespace("post_sleep_phase");
public static final ResourceLocation DEMO_OVERLAY = ResourceLocation.withDefaultNamespace("demo");
public static final ResourceLocation DEBUG_OVERLAY = ResourceLocation.withDefaultNamespace("debug");
public static final ResourceLocation SCOREBOARD = ResourceLocation.withDefaultNamespace("scoreboard");
public static final ResourceLocation HOTBAR_MESSAGE = ResourceLocation.withDefaultNamespace("hotbar_message");
public static final ResourceLocation TITLE_OVERLAY = ResourceLocation.withDefaultNamespace("title");
public static final ResourceLocation CHAT_OVERLAY = ResourceLocation.withDefaultNamespace("chat_overlay");
public static final ResourceLocation TAB_LIST = ResourceLocation.withDefaultNamespace("tab_list");
public static final ResourceLocation SUBTITLE_OVERLAY = ResourceLocation.withDefaultNamespace("subtitle");
public static final ResourceLocation VANILLA_ROOT = ResourceLocation.withDefaultNamespace("vanilla_root");
public static final ResourceLocation SLEEP_OVERLAY = ResourceLocation.withDefaultNamespace("sleep_overlay");
public static final ImmutableList<ResourceLocation> PRE_LIST = ImmutableList.of(CAMERA_OVERLAY, CROSSHAIR, HOTBAR, EXPERIENCE, POTION_EFFECTS, BOSS_OVERLAY);
public static final ImmutableList<ResourceLocation> POST_LIST = ImmutableList.of(DEMO_OVERLAY, DEBUG_OVERLAY, SCOREBOARD, HOTBAR_MESSAGE, TITLE_OVERLAY, CHAT_OVERLAY, TAB_LIST, SUBTITLE_OVERLAY);
public static final ImmutableList<ResourceLocation> COMBINE_LIST = ImmutableList.of(PRE_SLEEP_STACK, SLEEP_OVERLAY, POST_SLEEP_STACK);
/**
* Creates a stack of pre-named layers for vanilla.
* Not intended for modder use. Use {@linkplain ForgeLayeredDraw(ResourceLocation, Boolean, String...)} instead.
* @param name Stack identifier
* @param layers locations of layers to add
*/
@ApiStatus.Internal
public ForgeLayeredDraw(ResourceLocation name, List<ResourceLocation> layers) {
this.name = name;
expectedNames.addAll(layers);
}
/**
* Creates an empty draw list. Add entries with {@linkplain ForgeLayeredDraw#add(ResourceLocation, Layer)}
* @param name marker for which phase this is.
*/
public ForgeLayeredDraw(ResourceLocation name) {
this.name = name;
}
/**
* Adds a full, pre-named draw stack.
* @param layeredDraw layer stack to be added.
* @param booleanSupplier requirement for it to render.
* @deprecated Modders should be using {@linkplain ForgeLayeredDraw#add(ResourceLocation, LayeredDraw, BooleanSupplier)}
* @return this
*/
@Deprecated
@Override
public ForgeLayeredDraw add(LayeredDraw layeredDraw, BooleanSupplier booleanSupplier) {
return add(getNextPreName(), layeredDraw, booleanSupplier);
}
/**
* Adds a full draw stack with its provided condition.
* 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
* @param supplier condition for this stack to render
* @return this
*/
public ForgeLayeredDraw add(ResourceLocation name, LayeredDraw layeredDraw, BooleanSupplier supplier) {
if (isNameAvailable(name)) {
subLayerStacks.put(name, Map.entry(layeredDraw, supplier));
order.add(name);
} else {
nameTakenWarning(name);
}
return this;
}
/**
* Adds a layer with an already known name provided by {@linkplain ForgeLayeredDraw(ResourceLocation, List)}
* The layer will be rendered last (on top) of already added layers.
* @param layer layer render code, see {@linkplain Layer} and example usages in {@linkplain Gui}
* @deprecated Use {@linkplain ForgeLayeredDraw#add(ResourceLocation, Layer)} for adding individual layers.
* @return this
*/
@Deprecated
@Override
public LayeredDraw add(Layer layer) {
add(getNextPreName(), layer);
return this;
}
/**
* 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.
* @param name RL for other mods to order against.
* @param layer layer render code, see {@linkplain Layer} and example usages in {@linkplain Gui}
* @return this
*/
public ForgeLayeredDraw add(ResourceLocation targetStack, ResourceLocation name, Layer layer) {
locateStack(targetStack).ifPresentOrElse((stack) -> stack.add(name, layer), () -> stackNotPresentWarning(targetStack));
return this;
}
/**
* Helper method, assumes the intended draw stack instance is the caller.
* Use any of the non-deprecated add methods if you want a specific instance,
* or call {@linkplain ForgeLayeredDraw#locateStack(ResourceLocation)} to get a reference to an instance.
* @param name RL of layer to add
* @param layer layer render code, see {@linkplain Layer} and example usages in {@linkplain Gui}
* @return this
*/
public ForgeLayeredDraw add(ResourceLocation name, Layer layer) {
if (isNameAvailable(name)) {
namedLayers.put(name, layer);
order.add(name);
} else {
nameTakenWarning(name);
}
return this;
}
/**
* Use to specify where your custom draw stack should go. Can also be used to re-order layers.
* Both target and destination must be in the same draw stack.
* @param expectedStack Which ForgeLayeredDraw the target is expected to be in. If you already have a reference to it, you can use {@linkplain ForgeLayeredDraw#move(ResourceLocation, ResourceLocation, LayerOffset)}
* @param target layer name to move
* @param destination layer name to order against
* @return this
*/
public ForgeLayeredDraw putAbove(ResourceLocation expectedStack, ResourceLocation target, ResourceLocation destination) {
locateStack(expectedStack).ifPresentOrElse((stack) -> stack.move(target, destination, LayerOffset.ABOVE), () -> stackNotPresentWarning(expectedStack));
return this;
}
/**
* Use to specify where your custom draw stack should go. Can also be used to re-order layers.
* Both target and destination must be in the same draw stack.
* @param expectedStack Which ForgeLayeredDraw the target is expected to be in. If you already have a reference to it, you can use {@linkplain ForgeLayeredDraw#move(ResourceLocation, ResourceLocation, LayerOffset)}
* @param target layer name to move
* @param destination layer name to order against
* @return this
*/
public ForgeLayeredDraw putBelow(ResourceLocation expectedStack, ResourceLocation target, ResourceLocation destination) {
locateStack(expectedStack).ifPresentOrElse((stack) -> stack.move(target, destination, LayerOffset.BELOW), () -> stackNotPresentWarning(expectedStack));
return this;
}
/**
* Helper method, assumes the intended draw stack instance is the caller.
* Moves pre-existing names around within the order.
* @param target Layer being moved.
* @param destination Layer being ordered against
* @param offset Self-explanatory
* @return this
*/
public ForgeLayeredDraw move(ResourceLocation target, ResourceLocation destination, LayerOffset offset) {
if (!order.contains(target)) {
layerNotPresentWarning(target);
return this;
}
int loc = order.indexOf(destination);
if (loc == -1) {
layerNotPresentWarning(destination);
return this;
}
order.remove(target);
order.add(loc + (offset == LayerOffset.ABOVE ? 1 : 0), target);
return this;
}
/**
* Adds an overlay layer to be rendered above the other provided layer.
* To render "above" another layer means thisLayer will be rendered after otherLayer
* If the current stack does not contain otherLayer, no changes will be made.
* @param newLayer name of the layer to be added
* @param otherLayer name of the layer being ordered against
* @param layer layer render code, see {@linkplain Layer} and example usages in {@linkplain Gui}
* @return this
*/
public ForgeLayeredDraw addAbove(ResourceLocation expectedStack, ResourceLocation newLayer, ResourceLocation otherLayer, Layer layer) {
locateStack(expectedStack).ifPresentOrElse((stack) -> {
if (!stack.isNameAvailable(otherLayer)) {
stack.add(newLayer, layer).move(newLayer, otherLayer, LayerOffset.ABOVE);
} else {
layerNotPresentWarning(otherLayer);
}
}, () -> stackNotPresentWarning(expectedStack));
return this;
}
/**
* Helper method, assumes intended draw stack is the caller
*/
public ForgeLayeredDraw addAbove(ResourceLocation newLayer, ResourceLocation otherLayer, Layer layer) {
return addAbove(name, newLayer, otherLayer, layer);
}
/**
* Adds an overlay layer to be rendered below the other provided layer.
* To render "below" another layer means thisLayer will be rendered before otherLayer
* If the current stack does not contain otherLayer, no changes will be made.
* @param newLayer name of the layer to be added
* @param otherLayer name of the layer being ordered against
* @param layer layer render code, see {@linkplain Layer} and example usages in {@linkplain Gui}
* @return this
*/
public ForgeLayeredDraw addBelow(ResourceLocation expectedStack, ResourceLocation newLayer, ResourceLocation otherLayer, Layer layer) {
locateStack(expectedStack).ifPresentOrElse((stack) -> {
if (!stack.isNameAvailable(otherLayer)) {
stack.add(newLayer, layer).move(newLayer, otherLayer, LayerOffset.BELOW);
} else {
layerNotPresentWarning(otherLayer);
}
}, () -> stackNotPresentWarning(expectedStack));
return this;
}
/**
* Helper method, assumes intended draw stack is the caller
*/
public ForgeLayeredDraw addBelow(ResourceLocation newLayer, ResourceLocation otherLayer, Layer layer) {
return addBelow(name, newLayer, otherLayer, layer);
}
/**
* Add a new layer that will only be rendered when the condition is met. The layer will be added
* at the end of the list, which means it will render last (on top) of already added layers
* @param name name of the layer to be added
* @param layer render code of layer being added
* @param condition supplier for the condition
* @return this
*/
public ForgeLayeredDraw addWithCondition(ResourceLocation targetStack, ResourceLocation name, Layer layer, BooleanSupplier condition) {
locateStack(targetStack).ifPresentOrElse((stack) -> stack.addWithCondition(name, layer, condition), () -> stackNotPresentWarning(targetStack));
return this;
}
/**
* Assumes the correct stack is the caller of this method, otherwise functions the same as other method.
* @param name name of layer to be added
* @param layer render code of layer being added
* @param condition supplier for the condition
* @return this
*/
public ForgeLayeredDraw addWithCondition(ResourceLocation name, Layer layer, BooleanSupplier condition) {
add(name, layer).addConditionTo(name, condition);
return this;
}
/**
* Add a condition to a pre-existing layer in the specified stack, its render order is not changed.
* If the target is not present, no changes are made.
* @param targetStack name of the draw stack the target is expected to be in
* @param targetLayer name of layer to add a condition to
* @param condition supplier for the condition
* @return this
*/
public ForgeLayeredDraw addConditionTo(ResourceLocation targetStack, ResourceLocation targetLayer, BooleanSupplier condition) {
locateStack(targetStack).ifPresentOrElse((stack) -> stack.addConditionTo(targetLayer, condition), () -> stackNotPresentWarning(targetStack));
return this;
}
/**
* Assumes the correct stack is the caller of this method.
* Use {@linkplain ForgeLayeredDraw#addConditionTo(ResourceLocation, ResourceLocation, BooleanSupplier)}
* if you do not have a reference to the draw stack you want.
* @param targetLayer name of layer to add condition to
* @param condition supplier for the condition
* @return this
*/
public ForgeLayeredDraw addConditionTo(ResourceLocation targetLayer, BooleanSupplier condition) {
var result = namedLayers.computeIfPresent(targetLayer,
(name, layer) -> (guiGraphics, deltaTracker) -> {
if (condition.getAsBoolean()) {
layer.render(guiGraphics, deltaTracker);
}
});
if (result == null) {
layerNotPresentWarning(targetLayer);
}
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.
* @return this
*/
@ApiStatus.Internal
public ForgeLayeredDraw resolveLayers() {
if (!order.isEmpty()) {
ForgeEventFactoryClient.onComputeLayerOrder(this);
}
if (namedLayers.size() + subLayerStacks.size() < order.size()) {
LogUtils.getLogger().warn("Found {} unbound pre-defined layer names when resolving gui overlay order. This is not an error, but potentially indicates a mod directly modifying Gui instead of using this api.", order.size() - namedLayers.size());
}
resolveNested();
order.clear();
return this;
}
/**
* Resolve the layer order per stack, recursively as needed.
* Parent layer stack {@linkplain ForgeLayeredDraw#VANILLA_ROOT} holds the results.
*/
private void resolveNested() {
for (ResourceLocation layerName : order) {
if (subLayerStacks.containsKey(layerName)) {
var entry = subLayerStacks.get(layerName);
if (entry.getKey() instanceof ForgeLayeredDraw resolveable) {
resolveable.resolveNested();
// Although unlikely, a LayeredDraw could be in the list, and we can't call resolveNested on one.
}
super.add((gg, tr) -> {
if (entry.getValue().getAsBoolean()) entry.getKey().render(gg,tr);
});
} else {
super.add(namedLayers.get(layerName));
}
}
}
/**
* Attempt to locate a particular draw stack. Search starts at caller's instance. For global search call on VANILLA_ROOT
* i.e. {@linkplain net.minecraftforge.client.event.AddGuiOverlayLayersEvent#getLayeredDraw()}
* Entries which don't extend ForgeLayeredDraw may not have the proper fields to support internal list adjustment, so they are skipped.
* @param targetStack Name of ForgeLayeredDraw to find
* @return Filled Optional if target exists, empty otherwise.
*/
public Optional<ForgeLayeredDraw> locateStack(ResourceLocation targetStack) {
if (!name.equals(targetStack)) {
for (Map.Entry<LayeredDraw, BooleanSupplier> value : subLayerStacks.values()) {
if (value.getKey() instanceof ForgeLayeredDraw searchable) {
var res = searchable.locateStack(targetStack);
if(res.isPresent()) return res;
}
}
return Optional.empty();
} else {
return Optional.of(this);
}
}
@Nullable
public LayeredDraw getChild(ResourceLocation childName) {
return locateStack(childName).orElse(null);
}
@Nullable
public Layer getLayer(ResourceLocation layerName) {
return namedLayers.get(layerName);
}
private ResourceLocation getNextPreName() {
return expectedNames.isEmpty() ? ResourceLocation.fromNamespaceAndPath("unknown", "layer_" + unknown++) : expectedNames.removeFirst();
}
public ResourceLocation getName() {
return name;
}
private void stackNotPresentWarning(ResourceLocation stackName) {
LogUtils.getLogger().warn("Target stack {} was not present anywhere. Is your ResourceLocation correct?", stackName);
}
private void layerNotPresentWarning(ResourceLocation layer) {
LogUtils.getLogger().warn("Expected layer {} was not found in stack {}, no layer modifications have been made.", layer, name);
}
private void nameTakenWarning(ResourceLocation layer) {
LogUtils.getLogger().warn("Name {} was already present in {} and cannot be re-used.", layer, name);
}
private boolean isNameAvailable(ResourceLocation name) {
return !namedLayers.containsKey(name) && !subLayerStacks.containsKey(name);
}
public enum LayerOffset {
ABOVE,
BELOW
}
}
@@ -29,7 +29,7 @@ import net.minecraftforge.server.command.ForgeCommand;
import net.minecraftforge.server.permission.events.PermissionGatherEvent;
import net.minecraftforge.server.command.ConfigCommand;
public class ForgeInternalHandler {
public final class ForgeInternalHandler {
@SubscribeEvent(priority = EventPriority.HIGH)
public void onEntityJoinWorld(EntityJoinLevelEvent event) {
Entity entity = event.getEntity();
@@ -111,8 +111,8 @@ public class ForgeInternalHandler {
@SubscribeEvent(priority = EventPriority.HIGHEST)
public void builtinMobSpawnBlocker(EntityJoinLevelEvent event) {
if(event.getEntity() instanceof Mob mob && mob.isSpawnCancelled())
event.setCanceled(true);
if (event.getEntity() instanceof Mob mob && mob.isSpawnCancelled())
event.cancel();
}
@SubscribeEvent
@@ -11,6 +11,7 @@ import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.client.ClientCommandHandler;
import net.minecraftforge.client.ConfigScreenHandler;
import net.minecraftforge.eventbus.api.BusBuilder;
import net.minecraftforge.eventbus.api.EventPriority;
import net.minecraftforge.eventbus.api.IEventBus;
import net.minecraftforge.fml.ModLoadingContext;
import net.minecraftforge.fml.config.IConfigSpec;
@@ -30,11 +31,15 @@ import java.util.function.Supplier;
public class MinecraftForge {
/**
* The EventBus for all the Forge Events.
*
* <br>
* Events marked with {@link net.minecraftforge.fml.event.IModBusEvent}
* belong on the ModBus and not this bus
*/
public static final IEventBus EVENT_BUS = BusBuilder.builder().startShutdown().useModLauncher().build();
public static final IEventBus EVENT_BUS = BusBuilder.builder()
.startShutdown()
.useModLauncher()
.setPhasesToTrack(EventPriority.MONITOR)
.build();
static final ForgeInternalHandler INTERNAL_HANDLER = new ForgeInternalHandler();
private static final Logger LOGGER = LogManager.getLogger();
Binary file not shown.
@@ -0,0 +1,7 @@
{
"type": "minecraft:function",
"environment": "minecraft:default",
"function": "forge:modify_overlay_test/add_condition",
"max_ticks": 100,
"structure": "forge:empty3x3x3"
}
@@ -0,0 +1,7 @@
{
"type": "minecraft:function",
"environment": "minecraft:default",
"function": "forge:modify_overlay_test/full_stack_condition",
"max_ticks": 100,
"structure": "forge:empty3x3x3"
}
@@ -0,0 +1,7 @@
{
"type": "minecraft:function",
"environment": "minecraft:default",
"function": "forge:modify_overlay_test/not_present_in_stack",
"max_ticks": 100,
"structure": "forge:empty3x3x3"
}
@@ -0,0 +1,7 @@
{
"type": "minecraft:function",
"environment": "minecraft:default",
"function": "forge:modify_overlay_test/ordered_layers",
"max_ticks": 100,
"structure": "forge:empty3x3x3"
}
@@ -0,0 +1,170 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.debug.client;
import net.minecraft.client.gui.LayeredDraw;
import net.minecraft.gametest.framework.GameTestHelper;
import net.minecraft.resources.ResourceLocation;
import net.minecraftforge.client.event.AddGuiOverlayLayersEvent;
import net.minecraftforge.client.gui.overlay.ForgeLayeredDraw;
import net.minecraftforge.common.extensions.IForgeGameTestHelper;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import net.minecraftforge.gametest.GameTest;
import net.minecraftforge.gametest.GameTestNamespace;
import net.minecraftforge.test.BaseTestMod;
import java.util.List;
import java.util.Map;
import java.util.function.BooleanSupplier;
import static net.minecraftforge.client.gui.overlay.ForgeLayeredDraw.*;
@GameTestNamespace("forge")
@Mod(ModifyOverlayTest.MODID)
public class ModifyOverlayTest extends BaseTestMod {
public static final String MODID = "modify_overlay_test";
private static final ResourceLocation myStackName = name("my_stack_name");
private static final ForgeLayeredDraw myLayerStack = new ForgeLayeredDraw(myStackName);
private static final Layer notAddedLayer = (gg,tr) -> {};
private static final Layer layerA = (gg,tr) -> {};
private static final ResourceLocation layerAName = name("layer_a");
private static final Layer layerB = (gg,tr) -> {};
private static final ResourceLocation layerBName = name("layer_b");
private static final Layer layerC = (gg,tr) -> {};
private static final ResourceLocation layerCName = name("layer_c");
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");
private static final IForgeGameTestHelper.BoolFlag enableConditionFlag = new IForgeGameTestHelper.BoolFlag("en_cond_flag");
private static final IForgeGameTestHelper.BoolFlag enableConditionStackFlag = new IForgeGameTestHelper.BoolFlag("en_cond_stack_flag");
private static ForgeLayeredDraw drawStack;
public ModifyOverlayTest(FMLJavaModLoadingContext context) {
super(context);
context.getModEventBus().addListener(this::overlayTestListener);
}
@GameTest
public static void not_present_in_stack(GameTestHelper helper) {
// Test that we can't order against non-existent layers.
List<Layer> internalLayersList = null;
try {
internalLayersList = getInternalLayersList(drawStack);
} catch (Exception e) {
helper.fail("Threw a " + e.getMessage() + " when trying to get the inner layer list.");
}
helper.assertFalse(internalLayersList.contains(notAddedLayer), "Found our layer when we shouldn't have. Not good!");
helper.succeed();
}
@GameTest
public static void add_condition(GameTestHelper helper) {
// Test that we can add conditions to pre-existing layers.
helper.assertFalse(detectConditionFlag.getBool(), "Conditional rendering ran when it shouldn't have.");
enableConditionFlag.set(true);
helper.runAfterDelay(5, () -> {
boolean result = detectConditionFlag.getBool();// Just in case of weird race conditions.
enableConditionFlag.set(false);
helper.assertTrue(result, "");
helper.succeed();
});
}
@GameTest
public static void ordered_layers(GameTestHelper helper) {
// Test that layers are in the correct order.
List<Layer> internalLayersList = null;
Map<ResourceLocation, Layer> check = null;
try {
Class<?> cls = drawStack.getClass();
var field = cls.getDeclaredField("subLayerStacks");
var field1 = cls.getDeclaredField("namedLayers");
field.setAccessible(true);
field1.setAccessible(true);
Map<ResourceLocation, Map.Entry<LayeredDraw, BooleanSupplier>> VROOT = ((Map<ResourceLocation, Map.Entry<LayeredDraw, BooleanSupplier>>) field.get(drawStack));
var PSS = ((ForgeLayeredDraw) VROOT.get(PRE_SLEEP_STACK).getKey());
check = ((Map<ResourceLocation, Layer>) field1.get(PSS));
internalLayersList = getInternalLayersList(PSS);
} catch (Exception e) {
helper.fail("Threw a " + e.getMessage() + " when trying to get the inner layer list.");
}
int locationOfPotionEffects = internalLayersList.indexOf(check.get(POTION_EFFECTS));
int locationOfLayerA = internalLayersList.indexOf(check.get(layerAName));
int locationOfLayerB = internalLayersList.indexOf(check.get(layerBName));
int locationOfLayerC = internalLayersList.indexOf(check.get(layerCName));
helper.assertTrue(locationOfPotionEffects == locationOfLayerA - 1, "Layer offset from vanilla -> A was incorrect");
helper.assertTrue(locationOfLayerB == locationOfLayerA + 1, "Layer offset from A -> B was incorrect");
helper.assertTrue(locationOfLayerC == locationOfLayerB + 1, "Layer offset from B -> C was incorrect");
helper.succeed();
}
@GameTest
public static void full_stack_condition(GameTestHelper helper) {
// Test conditional rendering & whether a full stack can be added in the first place.
helper.assertFalse(detectConditionStackFlag.getBool(), "Conditional rendering ran when it shouldn't have.");
enableConditionStackFlag.set(true);
helper.runAfterDelay(5, () -> {
boolean result = detectConditionStackFlag.getBool();// Just in case of weird race conditions.
enableConditionStackFlag.set(false);
helper.assertTrue(result, "");
helper.succeed();
});
}
private void overlayTestListener(AddGuiOverlayLayersEvent event) {
drawStack = event.getLayeredDraw();
var layeredDraw = event.getLayeredDraw();
layeredDraw.addAbove(VANILLA_ROOT, name("i_won_t_exist"), name("non_existent_target_layer"), notAddedLayer);
var pEffects = layeredDraw.locateStack(PRE_SLEEP_STACK).get();
// Test if layers may be ordered against other layers.
// 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.addBelow(PRE_SLEEP_STACK, layerAName, layerBName, layerA);
layeredDraw.addConditionTo(PRE_SLEEP_STACK, BOSS_OVERLAY, () -> {
if (enableConditionFlag.getBool()) {
detectConditionFlag.set(true);
return true;
} else {
detectConditionFlag.set(false);
return false;
}
});
myLayerStack.add(name("my_inner_layer_name"), (gg, tr) -> {
detectConditionStackFlag.set(true);
});
// Demonstrates that entire stacks can have conditions attached
event.getLayeredDraw()
.add(myStackName, myLayerStack, () -> {
if (enableConditionStackFlag.getBool()) {
return true;
} else {
detectConditionStackFlag.set(false);
return false;
}
});
}
private static ResourceLocation name(String name) {
return ResourceLocation.fromNamespaceAndPath(MODID, name);
}
@SuppressWarnings("unchecked")
private static List<Layer> getInternalLayersList(ForgeLayeredDraw stack) throws ClassCastException, NoSuchFieldException, IllegalAccessException {
Class<?> cls = stack.getClass().getSuperclass();
var layersField = cls.getDeclaredField("layers");
layersField.setAccessible(true);
return (List<Layer>) layersField.get(stack);
}
}
@@ -0,0 +1 @@
clientSideOnly=true