trurwsuieghfdskg

This commit is contained in:
2025-06-08 00:02:04 +09:00
commit 56c8ad5bc2
14172 changed files with 1139672 additions and 0 deletions

View File

@@ -0,0 +1,223 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import net.minecraft.Util;
import net.minecraft.client.renderer.RenderType;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
import java.util.BitSet;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
/**
* An immutable ordered set (not implementing {@link java.util.Set}) of chunk {@linkplain RenderType render types}.
* <p>
* Considerably speeds up lookups and merges of sets of chunk {@linkplain RenderType render types}.
* Users should cache their instances of this class whenever possible, as instantiating it is cheap, but not free.
*/
public sealed class ChunkRenderTypeSet implements Iterable<RenderType>
{
private static final List<RenderType> CHUNK_RENDER_TYPES_LIST = RenderType.chunkBufferLayers();
private static final RenderType[] CHUNK_RENDER_TYPES = CHUNK_RENDER_TYPES_LIST.toArray(new RenderType[0]);
private static final ChunkRenderTypeSet NONE = new None();
private static final ChunkRenderTypeSet ALL = new All();
public static ChunkRenderTypeSet none()
{
return NONE;
}
public static ChunkRenderTypeSet all()
{
return ALL;
}
public static ChunkRenderTypeSet of(RenderType... renderTypes)
{
return of(Arrays.asList(renderTypes));
}
public static ChunkRenderTypeSet of(Collection<RenderType> renderTypes)
{
if (renderTypes.isEmpty())
return none();
return of((Iterable<RenderType>) renderTypes);
}
private static ChunkRenderTypeSet of(Iterable<RenderType> renderTypes)
{
var bits = new BitSet();
for (RenderType renderType : renderTypes)
{
int index = renderType.getChunkLayerId();
Preconditions.checkArgument(index >= 0, "Attempted to create chunk render type set with a non-chunk render type: " + renderType);
bits.set(index);
}
return new ChunkRenderTypeSet(bits);
}
public static ChunkRenderTypeSet union(ChunkRenderTypeSet... sets)
{
return union(Arrays.asList(sets));
}
public static ChunkRenderTypeSet union(Collection<ChunkRenderTypeSet> sets)
{
if (sets.isEmpty())
return none();
return union((Iterable<ChunkRenderTypeSet>) sets);
}
public static ChunkRenderTypeSet union(Iterable<ChunkRenderTypeSet> sets)
{
var bits = new BitSet();
for (var set : sets)
bits.or(set.bits);
return new ChunkRenderTypeSet(bits);
}
public static ChunkRenderTypeSet intersection(ChunkRenderTypeSet... sets)
{
return intersection(Arrays.asList(sets));
}
public static ChunkRenderTypeSet intersection(Collection<ChunkRenderTypeSet> sets)
{
if (sets.isEmpty())
return all();
return intersection((Iterable<ChunkRenderTypeSet>) sets);
}
public static ChunkRenderTypeSet intersection(Iterable<ChunkRenderTypeSet> sets)
{
var bits = new BitSet();
bits.set(0, CHUNK_RENDER_TYPES.length);
for (var set : sets)
bits.and(set.bits);
return new ChunkRenderTypeSet(bits);
}
private final BitSet bits;
private ChunkRenderTypeSet(BitSet bits)
{
this.bits = bits;
}
public boolean isEmpty()
{
return bits.isEmpty();
}
public boolean contains(RenderType renderType)
{
int id = renderType.getChunkLayerId();
return id >= 0 && bits.get(id);
}
@NotNull
@Override
public Iterator<RenderType> iterator()
{
return new IteratorImpl();
}
public List<RenderType> asList()
{
return ImmutableList.copyOf(this);
}
private final class IteratorImpl implements Iterator<RenderType>
{
private int index = bits.nextSetBit(0);
@Override
public boolean hasNext()
{
return index >= 0;
}
@Override
public RenderType next()
{
var renderType = CHUNK_RENDER_TYPES[index];
index = bits.nextSetBit(index + 1);
return renderType;
}
}
private static final class None extends ChunkRenderTypeSet
{
private None()
{
super(new BitSet());
}
@Override
public boolean isEmpty()
{
return true;
}
@Override
public boolean contains(RenderType renderType)
{
return false;
}
@NotNull
@Override
public Iterator<RenderType> iterator()
{
return Collections.emptyIterator();
}
@Override
public List<RenderType> asList()
{
return List.of();
}
}
private static final class All extends ChunkRenderTypeSet
{
private All()
{
super(Util.make(new BitSet(), bits -> bits.set(0, CHUNK_RENDER_TYPES.length)));
}
@Override
public boolean isEmpty(){
return false;
}
@Override
public boolean contains(RenderType renderType)
{
return renderType.getChunkLayerId() >= 0; // Could just return true for efficiency purposes, but checking is near-free
}
@NotNull
@Override
public Iterator<RenderType> iterator()
{
return CHUNK_RENDER_TYPES_LIST.iterator();
}
@Override
public List<RenderType> asList()
{
return CHUNK_RENDER_TYPES_LIST;
}
}
}

View File

@@ -0,0 +1,207 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.ParseResults;
import com.mojang.brigadier.StringReader;
import com.mojang.brigadier.builder.ArgumentBuilder;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.suggestion.SuggestionProvider;
import com.mojang.brigadier.tree.CommandNode;
import com.mojang.brigadier.tree.RootCommandNode;
import net.minecraft.ChatFormatting;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ClientPacketListener;
import net.minecraft.client.player.LocalPlayer;
import net.minecraft.commands.CommandBuildContext;
import net.minecraft.commands.CommandRuntimeException;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.SharedSuggestionProvider;
import net.minecraft.commands.synchronization.SuggestionProviders;
import net.minecraft.network.chat.*;
import net.minecraftforge.client.event.ClientPlayerNetworkEvent;
import net.minecraftforge.client.event.RegisterClientCommandsEvent;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.server.command.CommandHelper;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.ApiStatus;
import java.util.IdentityHashMap;
import java.util.Map;
public class ClientCommandHandler
{
private static final Logger LOGGER = LogManager.getLogger();
private static CommandDispatcher<CommandSourceStack> commands = null;
public static void init()
{
MinecraftForge.EVENT_BUS.addListener(ClientCommandHandler::handleClientPlayerLogin);
}
private static void handleClientPlayerLogin(ClientPlayerNetworkEvent.LoggingIn event)
{
final ClientPacketListener connection = event.getPlayer().connection;
// Some custom server implementations do not send ClientboundCommandsPacket, so we provide a fallback:
// Must set this, so that suggestions for client-only commands work, if server never sends commands packet
connection.commands = mergeServerCommands(new CommandDispatcher<>(), CommandBuildContext.simple(connection.registryAccess(), connection.enabledFeatures()));
}
/*
* For internal use
*
* Merges command dispatcher use for suggestions to the command dispatcher used for client commands so they can be sent to the server, and vice versa so client commands appear
* with server commands in suggestions
*/
@ApiStatus.Internal
public static CommandDispatcher<SharedSuggestionProvider> mergeServerCommands(CommandDispatcher<SharedSuggestionProvider> serverCommands, CommandBuildContext buildContext)
{
CommandDispatcher<CommandSourceStack> commandsTemp = new CommandDispatcher<>();
MinecraftForge.EVENT_BUS.post(new RegisterClientCommandsEvent(commandsTemp, buildContext));
// Copies the client commands into another RootCommandNode so that redirects can't be used with server commands
commands = new CommandDispatcher<>();
copy(commandsTemp.getRoot(), commands.getRoot());
// Copies the server commands into another RootCommandNode so that redirects can't be used with client commands
RootCommandNode<SharedSuggestionProvider> serverCommandsRoot = serverCommands.getRoot();
CommandDispatcher<SharedSuggestionProvider> newServerCommands = new CommandDispatcher<>();
copy(serverCommandsRoot, newServerCommands.getRoot());
// Copies the client side commands into the server side commands to be used for suggestions
CommandHelper.mergeCommandNode(commands.getRoot(), newServerCommands.getRoot(), new IdentityHashMap<>(), getSource(), (context) -> 0, (suggestions) -> {
SuggestionProvider<SharedSuggestionProvider> suggestionProvider = SuggestionProviders
.safelySwap((SuggestionProvider<SharedSuggestionProvider>) (SuggestionProvider<?>) suggestions);
if (suggestionProvider == SuggestionProviders.ASK_SERVER)
{
suggestionProvider = (context, builder) -> {
ClientCommandSourceStack source = getSource();
StringReader reader = new StringReader(context.getInput());
if (reader.canRead() && reader.peek() == '/')
{
reader.skip();
}
ParseResults<CommandSourceStack> parse = commands.parse(reader, source);
return commands.getCompletionSuggestions(parse);
};
}
return suggestionProvider;
});
return newServerCommands;
}
/**
* @return The command dispatcher for client side commands
*/
public static CommandDispatcher<CommandSourceStack> getDispatcher()
{
return commands;
}
/**
* @return A {@link ClientCommandSourceStack} for the player in the current client
*/
public static ClientCommandSourceStack getSource()
{
LocalPlayer player = Minecraft.getInstance().player;
return new ClientCommandSourceStack(player, player.position(), player.getRotationVector(), player.getPermissionLevel(),
player.getName().getString(), player.getDisplayName(), player);
}
/**
*
* Creates a deep copy of the sourceNode while keeping the redirects referring to the old command tree
*
* @param sourceNode
* the original
* @param resultNode
* the result
*/
private static <S> void copy(CommandNode<S> sourceNode, CommandNode<S> resultNode)
{
Map<CommandNode<S>, CommandNode<S>> newNodes = new IdentityHashMap<>();
newNodes.put(sourceNode, resultNode);
for (CommandNode<S> child : sourceNode.getChildren())
{
CommandNode<S> copy = newNodes.computeIfAbsent(child, innerChild ->
{
ArgumentBuilder<S, ?> builder = innerChild.createBuilder();
CommandNode<S> innerCopy = builder.build();
copy(innerChild, innerCopy);
return innerCopy;
});
resultNode.addChild(copy);
}
}
/**
* Always try to execute the cached parsing of a typed command as a clientside command. Requires that the execute field of the commands to be set to send to server so that they aren't
* treated as client command's that do nothing.
*
* {@link net.minecraft.commands.Commands#performCommand(ParseResults, String)} for reference
*
* @param command the full command to execute, no preceding slash
* @return {@code false} leaves the message to be sent to the server, while {@code true} means it should be caught before LocalPlayer#sendCommand
*/
public static boolean runCommand(String command)
{
StringReader reader = new StringReader(command);
ClientCommandSourceStack source = getSource();
try
{
commands.execute(reader, source);
}
catch (CommandRuntimeException execution)// Probably thrown by the command
{
Minecraft.getInstance().player.sendSystemMessage(Component.literal("").append(execution.getComponent()).withStyle(ChatFormatting.RED));
}
catch (CommandSyntaxException syntax)// Usually thrown by the CommandDispatcher
{
if (syntax.getType() == CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherUnknownCommand() || syntax.getType() == CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherUnknownArgument())
{
// in case of unknown command, let the server try and handle it
return false;
}
Minecraft.getInstance().player.sendSystemMessage(
Component.literal("").append(ComponentUtils.fromMessage(syntax.getRawMessage())).withStyle(ChatFormatting.RED));
if (syntax.getInput() != null && syntax.getCursor() >= 0)
{
int position = Math.min(syntax.getInput().length(), syntax.getCursor());
MutableComponent details = Component.literal("")
.withStyle(ChatFormatting.GRAY)
.withStyle((style) -> style
.withClickEvent(new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, reader.getString())));
if (position > 10)
{
details.append("...");
}
details.append(syntax.getInput().substring(Math.max(0, position - 10), position));
if (position < syntax.getInput().length())
{
details.append(Component.literal(syntax.getInput().substring(position)).withStyle(ChatFormatting.RED, ChatFormatting.UNDERLINE));
}
details.append(Component.translatable("command.context.here").withStyle(ChatFormatting.RED, ChatFormatting.ITALIC));
Minecraft.getInstance().player.sendSystemMessage(Component.literal("").append(details).withStyle(ChatFormatting.RED));
}
}
catch (Exception generic)// Probably thrown by the command
{
MutableComponent message = Component.literal(generic.getMessage() == null ? generic.getClass().getName() : generic.getMessage());
Minecraft.getInstance().player.sendSystemMessage(Component.translatable("command.failed")
.withStyle(ChatFormatting.RED)
.withStyle((style) -> style
.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, message))));
LOGGER.error("Error executing client command \"{}\"", command, generic);
}
return true;
}
}

View File

@@ -0,0 +1,154 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client;
import net.minecraft.Util;
import net.minecraft.advancements.Advancement;
import net.minecraft.client.Minecraft;
import net.minecraft.commands.CommandSource;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.core.RegistryAccess;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.item.crafting.RecipeManager;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.Vec2;
import net.minecraft.world.phys.Vec3;
import net.minecraft.world.scores.Scoreboard;
import java.util.Collection;
import java.util.Set;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* overrides for {@link CommandSourceStack} so that the methods will run successfully client side
*/
public class ClientCommandSourceStack extends CommandSourceStack
{
public ClientCommandSourceStack(CommandSource source, Vec3 position, Vec2 rotation, int permission, String plainTextName, Component displayName,
Entity executing)
{
super(source, position, rotation, null, permission, plainTextName, displayName, null, executing);
}
/**
* Sends a success message without attempting to get the server side list of admins
*/
@Override
public void sendSuccess(Supplier<Component> message, boolean sendToAdmins)
{
Minecraft.getInstance().player.sendSystemMessage(message.get());
}
/**
* {@return the list of teams from the client side}
*/
@Override
public Collection<String> getAllTeams()
{
return Minecraft.getInstance().level.getScoreboard().getTeamNames();
}
/**
* {@return the list of online player names from the client side}
*/
@Override
public Collection<String> getOnlinePlayerNames()
{
return Minecraft.getInstance().getConnection().getOnlinePlayers().stream().map((player) -> player.getProfile().getName()).collect(Collectors.toList());
}
/**
* {@return a {@link Stream} of recipe ids that are available on the client}
*/
@Override
public Stream<ResourceLocation> getRecipeNames()
{
return Minecraft.getInstance().getConnection().getRecipeManager().getRecipeIds();
}
/**
* {@return a set of {@link ResourceKey} for levels from the client side}
*/
@Override
public Set<ResourceKey<Level>> levels()
{
return Minecraft.getInstance().getConnection().levels();
}
/**
* {@return the {@link RegistryAccess} from the client side}
*/
@Override
public RegistryAccess registryAccess()
{
return Minecraft.getInstance().getConnection().registryAccess();
}
/**
* {@return the scoreboard from the client side}
*/
@Override
public Scoreboard getScoreboard()
{
return Minecraft.getInstance().level.getScoreboard();
}
/**
* {@return the advancement from the id from the client side where the advancement needs to be visible to the player}
*/
@Override
public Advancement getAdvancement(ResourceLocation id)
{
return Minecraft.getInstance().getConnection().getAdvancements().getAdvancements().get(id);
}
/**
* {@return the {@link RecipeManager} from the client side}
*/
@Override
public RecipeManager getRecipeManager()
{
return Minecraft.getInstance().getConnection().getRecipeManager();
}
/**
* {@return the level from the client side}
*/
@Override
public Level getUnsidedLevel()
{
return Minecraft.getInstance().level;
}
/**
* @throws UnsupportedOperationException
* because the server isn't available on the client
*/
@Override
public MinecraftServer getServer()
{
throw new UnsupportedOperationException("Attempted to get server in client command");
}
/**
* @throws UnsupportedOperationException
* because the server side level isn't available on the client side
*/
@Override
public ServerLevel getLevel()
{
throw new UnsupportedOperationException("Attempted to get server level in client command");
}
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client;
import net.minecraft.client.renderer.RenderType;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.client.event.ModelEvent;
import net.minecraftforge.client.event.RegisterClientReloadListenersEvent;
import net.minecraftforge.client.event.RegisterNamedRenderTypesEvent;
import net.minecraftforge.client.model.CompositeModel;
import net.minecraftforge.client.model.DynamicFluidContainerModel;
import net.minecraftforge.client.model.ElementsModel;
import net.minecraftforge.client.model.EmptyModel;
import net.minecraftforge.client.model.ItemLayerModel;
import net.minecraftforge.client.model.SeparateTransformsModel;
import net.minecraftforge.client.model.obj.ObjLoader;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
@Mod.EventBusSubscriber(value = Dist.CLIENT, bus = Mod.EventBusSubscriber.Bus.MOD, modid = "forge")
public class ClientForgeMod
{
@SubscribeEvent
public static void onRegisterGeometryLoaders(ModelEvent.RegisterGeometryLoaders event)
{
event.register("empty", EmptyModel.LOADER);
event.register("elements", ElementsModel.Loader.INSTANCE);
event.register("obj", ObjLoader.INSTANCE);
event.register("fluid_container", DynamicFluidContainerModel.Loader.INSTANCE);
event.register("composite", CompositeModel.Loader.INSTANCE);
event.register("item_layers", ItemLayerModel.Loader.INSTANCE);
event.register("separate_transforms", SeparateTransformsModel.Loader.INSTANCE);
}
@SubscribeEvent
public static void onRegisterReloadListeners(RegisterClientReloadListenersEvent event)
{
event.registerReloadListener(ObjLoader.INSTANCE);
}
@SubscribeEvent
public static void onRegisterNamedRenderTypes(RegisterNamedRenderTypesEvent event)
{
event.register("item_unlit", RenderType.translucent(), ForgeRenderTypes.ITEM_UNSORTED_UNLIT_TRANSLUCENT.get());
}
}

View File

@@ -0,0 +1,51 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client;
import com.google.common.collect.ImmutableList;
import net.minecraft.client.color.block.BlockTintCache;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.world.level.ColorResolver;
import net.minecraftforge.client.event.RegisterColorHandlersEvent;
import net.minecraftforge.fml.ModLoader;
import org.jetbrains.annotations.ApiStatus;
import java.util.Map;
/**
* Manager for custom {@link ColorResolver} instances, collected via {@link RegisterColorHandlersEvent.ColorResolvers}.
*/
public final class ColorResolverManager
{
private static ImmutableList<ColorResolver> colorResolvers;
@ApiStatus.Internal
public static void init()
{
ImmutableList.Builder<ColorResolver> builder = ImmutableList.builder();
ModLoader.get().postEvent(new RegisterColorHandlersEvent.ColorResolvers(builder));
colorResolvers = builder.build();
}
/**
* Register a {@link BlockTintCache} for every registered {@link ColorResolver} into the given target map.
*
* @param level the level to use
* @param target the map to populate
*/
public static void registerBlockTintCaches(ClientLevel level, Map<ColorResolver, BlockTintCache> target)
{
for (var resolver : colorResolvers)
{
target.put(resolver, new BlockTintCache(pos -> level.calculateBlockTint(pos, resolver)));
}
}
private ColorResolverManager()
{
}
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.screens.Screen;
import net.minecraftforge.fml.ModList;
import net.minecraftforge.fml.IExtensionPoint;
import net.minecraftforge.forgespi.language.IModInfo;
import java.util.Optional;
import java.util.function.BiFunction;
import java.util.function.Function;
public class ConfigScreenHandler
{
/**
* @param screenFunction A function that takes the {@link Minecraft} client instance and the mods screen as
* arguments and returns your config screen to show when the player clicks the config button
* for your mod on the mods screen.
* <p>You should call {@link Minecraft#setScreen(Screen)} with the provided client instance
* and mods screen for the action of your close button.</p>
*/
public record ConfigScreenFactory(BiFunction<Minecraft, Screen, Screen> screenFunction) implements IExtensionPoint<ConfigScreenFactory> {
/**
* @param screenFunction A function that takes the mods screen as an argument and returns your config screen to
* show when the player clicks the config button for your mod on the mods screen.
* <p>You should call {@link Minecraft#setScreen(Screen)} with the provided mods screen
* for the action of your close button, using {@link Screen#minecraft} to get the client
* instance.</p>
*/
public ConfigScreenFactory(Function<Screen, Screen> screenFunction) {
this((mcClient, modsScreen) -> screenFunction.apply(modsScreen));
}
}
public static Optional<BiFunction<Minecraft, Screen, Screen>> getScreenFactoryFor(IModInfo selectedMod)
{
return ModList.get().getModContainerById(selectedMod.getModId()).
flatMap(mc -> mc.getCustomExtension(ConfigScreenFactory.class).map(ConfigScreenFactory::screenFunction));
}
}

View File

@@ -0,0 +1,83 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client;
import java.util.IdentityHashMap;
import java.util.Map;
import net.minecraft.client.Minecraft;
import net.minecraft.client.searchtree.SearchRegistry;
import net.minecraft.world.item.CreativeModeTab;
import net.minecraft.world.item.CreativeModeTabs;
import net.minecraft.world.item.ItemStack;
import net.minecraftforge.common.CreativeModeTabRegistry;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
public class CreativeModeTabSearchRegistry
{
private static final Map<CreativeModeTab, SearchRegistry.Key<ItemStack>> NAME_SEARCH_KEYS = new IdentityHashMap<>();
private static final Map<CreativeModeTab, SearchRegistry.Key<ItemStack>> TAG_SEARCH_KEYS = new IdentityHashMap<>();
public static Map<CreativeModeTab, SearchRegistry.Key<ItemStack>> getNameSearchKeys() {
Map<CreativeModeTab, SearchRegistry.Key<ItemStack>> nameSearchKeys = new IdentityHashMap<>();
nameSearchKeys.put(CreativeModeTabs.searchTab(), getNameSearchKey(CreativeModeTabs.searchTab()));
for (CreativeModeTab tab : CreativeModeTabRegistry.getSortedCreativeModeTabs())
{
SearchRegistry.Key<ItemStack> nameSearchKey = getNameSearchKey(tab);
if (nameSearchKey != null)
nameSearchKeys.put(tab, nameSearchKey);
}
return nameSearchKeys;
}
public static Map<CreativeModeTab, SearchRegistry.Key<ItemStack>> getTagSearchKeys() {
Map<CreativeModeTab, SearchRegistry.Key<ItemStack>> tagSearchKeys = new IdentityHashMap<>();
tagSearchKeys.put(CreativeModeTabs.searchTab(), getTagSearchKey(CreativeModeTabs.searchTab()));
for (CreativeModeTab tab : CreativeModeTabRegistry.getSortedCreativeModeTabs())
{
SearchRegistry.Key<ItemStack> tagSearchKey = getTagSearchKey(tab);
if (tagSearchKey != null)
tagSearchKeys.put(tab, tagSearchKey);
}
return tagSearchKeys;
}
@Nullable
public static SearchRegistry.Key<ItemStack> getNameSearchKey(CreativeModeTab tab)
{
if (tab == CreativeModeTabs.searchTab())
return SearchRegistry.CREATIVE_NAMES;
if (!tab.hasSearchBar())
return null;
return NAME_SEARCH_KEYS.computeIfAbsent(tab, k -> new SearchRegistry.Key<>());
}
@Nullable
public static SearchRegistry.Key<ItemStack> getTagSearchKey(CreativeModeTab tab)
{
if (tab == CreativeModeTabs.searchTab())
return SearchRegistry.CREATIVE_TAGS;
if (!tab.hasSearchBar())
return null;
return TAG_SEARCH_KEYS.computeIfAbsent(tab, k -> new SearchRegistry.Key<>());
}
// We do it this way to prevent unwanted classloading
@ApiStatus.Internal
public static void createSearchTrees() {
Minecraft.getInstance().createSearchTrees();
}
}

View File

@@ -0,0 +1,64 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client;
import com.google.common.collect.ImmutableMap;
import net.minecraft.client.renderer.DimensionSpecialEffects;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.dimension.BuiltinDimensionTypes;
import net.minecraftforge.client.event.RegisterDimensionSpecialEffectsEvent;
import net.minecraftforge.fml.ModLoader;
import org.jetbrains.annotations.ApiStatus;
import java.util.HashMap;
import java.util.Map;
/**
* Manager for {@link DimensionSpecialEffects} instances.
* <p>
* Provides a lookup by dimension type.
*/
public final class DimensionSpecialEffectsManager
{
private static ImmutableMap<ResourceLocation, DimensionSpecialEffects> EFFECTS;
private static DimensionSpecialEffects DEFAULT_EFFECTS;
/**
* Finds the {@link DimensionSpecialEffects} for a given dimension type, or the default if none is registered.
*/
public static DimensionSpecialEffects getForType(ResourceLocation type)
{
return EFFECTS.getOrDefault(type, DEFAULT_EFFECTS);
}
@ApiStatus.Internal
public static void init()
{
var effects = new HashMap<ResourceLocation, DimensionSpecialEffects>();
DEFAULT_EFFECTS = preRegisterVanillaEffects(effects);
var event = new RegisterDimensionSpecialEffectsEvent(effects);
ModLoader.get().postEventWrapContainerInModOrder(event);
EFFECTS = ImmutableMap.copyOf(effects);
}
/**
* Pre-registers vanilla dimension effects and returns the default fallback effects instance.
* <p>
* Borrowed from {@link DimensionSpecialEffects#EFFECTS}.
*/
private static DimensionSpecialEffects preRegisterVanillaEffects(Map<ResourceLocation, DimensionSpecialEffects> effects)
{
var overworldEffects = new DimensionSpecialEffects.OverworldEffects();
effects.put(BuiltinDimensionTypes.OVERWORLD_EFFECTS, overworldEffects);
effects.put(BuiltinDimensionTypes.NETHER_EFFECTS, new DimensionSpecialEffects.NetherEffects());
effects.put(BuiltinDimensionTypes.END_EFFECTS, new DimensionSpecialEffects.EndEffects());
return overworldEffects;
}
private DimensionSpecialEffectsManager()
{
}
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client;
import com.google.common.collect.ImmutableMap;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.EntityType;
import net.minecraftforge.client.event.RegisterEntitySpectatorShadersEvent;
import net.minecraftforge.fml.ModLoader;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
import java.util.HashMap;
import java.util.Map;
/**
* Manager for entity spectator mode shaders.
* <p>
* Provides a lookup.
*/
public final class EntitySpectatorShaderManager
{
private static Map<EntityType<?>, ResourceLocation> SHADERS;
/**
* Finds the path to the spectator mode shader used for the specified entity type, or null if none is registered.
*/
@Nullable
public static ResourceLocation get(EntityType<?> entityType)
{
return SHADERS.get(entityType);
}
@ApiStatus.Internal
public static void init()
{
var shaders = new HashMap<EntityType<?>, ResourceLocation>();
var event = new RegisterEntitySpectatorShadersEvent(shaders);
ModLoader.get().postEventWrapContainerInModOrder(event);
SHADERS = ImmutableMap.copyOf(shaders);
}
private EntitySpectatorShaderManager()
{
}
}

View File

@@ -0,0 +1,14 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client;
public record ExtendedServerListData(String type, boolean isCompatible, int numberOfMods, String extraReason, boolean truncated)
{
public ExtendedServerListData(String type, boolean isCompatible, int numberOfMods, String extraReason)
{
this(type, isCompatible, numberOfMods, extraReason, false);
}
}

View File

@@ -0,0 +1,35 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client;
import java.util.HashMap;
import java.util.Map;
import org.jetbrains.annotations.Nullable;
import net.minecraft.client.particle.FireworkParticles;
import net.minecraft.world.item.FireworkRocketItem;
/**
* Keeps track of custom firework shape types, because Particle is client side only this can't be on the Shape itself.
* So sometime during your client initalization call register.
*/
public class FireworkShapeFactoryRegistry {
private static final Map<FireworkRocketItem.Shape, Factory> factories = new HashMap<>();
public static interface Factory {
void build(FireworkParticles.Starter starter, boolean trail, boolean flicker, int[] colors, int[] fadecolors);
}
public static void register(FireworkRocketItem.Shape shape, Factory factory) {
factories.put(shape, factory);
}
@Nullable
public static Factory get(FireworkRocketItem.Shape shape) {
return factories.get(shape);
}
}

View File

@@ -0,0 +1,383 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client;
import com.mojang.blaze3d.systems.RenderSystem;
import net.minecraft.Util;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.RenderStateShard;
import net.minecraft.client.renderer.RenderStateShard.TextureStateShard;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.texture.TextureAtlas;
import com.mojang.blaze3d.vertex.DefaultVertexFormat;
import com.mojang.blaze3d.vertex.VertexFormat;
import net.minecraft.client.renderer.texture.TextureManager;
import net.minecraft.resources.ResourceLocation;
import net.minecraftforge.common.util.NonNullLazy;
import net.minecraftforge.common.util.NonNullSupplier;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;
import net.minecraft.client.renderer.RenderStateShard.ShaderStateShard;
import net.minecraft.client.renderer.RenderType.CompositeState;
@SuppressWarnings("deprecation")
public enum ForgeRenderTypes
{
ITEM_LAYERED_SOLID(()-> getItemLayeredSolid(TextureAtlas.LOCATION_BLOCKS)),
ITEM_LAYERED_CUTOUT(()-> getItemLayeredCutout(TextureAtlas.LOCATION_BLOCKS)),
ITEM_LAYERED_CUTOUT_MIPPED(()-> getItemLayeredCutoutMipped(TextureAtlas.LOCATION_BLOCKS)),
ITEM_LAYERED_TRANSLUCENT(()-> getItemLayeredTranslucent(TextureAtlas.LOCATION_BLOCKS)),
ITEM_UNSORTED_TRANSLUCENT(()-> getUnsortedTranslucent(TextureAtlas.LOCATION_BLOCKS)),
ITEM_UNLIT_TRANSLUCENT(()-> getUnlitTranslucent(TextureAtlas.LOCATION_BLOCKS)),
ITEM_UNSORTED_UNLIT_TRANSLUCENT(()-> getUnlitTranslucent(TextureAtlas.LOCATION_BLOCKS, false)),
TRANSLUCENT_ON_PARTICLES_TARGET(() -> getTranslucentParticlesTarget(TextureAtlas.LOCATION_BLOCKS));
public static boolean enableTextTextureLinearFiltering = false;
/**
* @return A RenderType fit for multi-layer solid item rendering.
*/
public static RenderType getItemLayeredSolid(ResourceLocation textureLocation)
{
return Internal.LAYERED_ITEM_SOLID.apply(textureLocation);
}
/**
* @return A RenderType fit for multi-layer cutout item item rendering.
*/
public static RenderType getItemLayeredCutout(ResourceLocation textureLocation)
{
return Internal.LAYERED_ITEM_CUTOUT.apply(textureLocation);
}
/**
* @return A RenderType fit for multi-layer cutout-mipped item rendering.
*/
public static RenderType getItemLayeredCutoutMipped(ResourceLocation textureLocation)
{
return Internal.LAYERED_ITEM_CUTOUT_MIPPED.apply(textureLocation);
}
/**
* @return A RenderType fit for multi-layer translucent item rendering.
*/
public static RenderType getItemLayeredTranslucent(ResourceLocation textureLocation)
{
return Internal.LAYERED_ITEM_TRANSLUCENT.apply(textureLocation);
}
/**
* @return A RenderType fit for translucent item/entity rendering, but with depth sorting disabled.
*/
public static RenderType getUnsortedTranslucent(ResourceLocation textureLocation)
{
return Internal.UNSORTED_TRANSLUCENT.apply(textureLocation);
}
/**
* @return A RenderType fit for translucent item/entity rendering, but with diffuse lighting disabled
* so that fullbright quads look correct.
*/
public static RenderType getUnlitTranslucent(ResourceLocation textureLocation)
{
return Internal.UNLIT_TRANSLUCENT_SORTED.apply(textureLocation);
}
/**
* @return A RenderType fit for translucent item/entity rendering, but with diffuse lighting disabled
* so that fullbright quads look correct.
* @param sortingEnabled If false, depth sorting will not be performed.
*/
public static RenderType getUnlitTranslucent(ResourceLocation textureLocation, boolean sortingEnabled)
{
return (sortingEnabled ? Internal.UNLIT_TRANSLUCENT_SORTED : Internal.UNLIT_TRANSLUCENT_UNSORTED).apply(textureLocation);
}
/**
* @return Same as {@link RenderType#entityCutout(ResourceLocation)}, but with mipmapping enabled.
*/
public static RenderType getEntityCutoutMipped(ResourceLocation textureLocation)
{
return Internal.LAYERED_ITEM_CUTOUT_MIPPED.apply(textureLocation);
}
/**
* @return Replacement of {@link RenderType#text(ResourceLocation)}, but with optional linear texture filtering.
*/
public static RenderType getText(ResourceLocation locationIn)
{
return Internal.TEXT.apply(locationIn);
}
/**
* @return Replacement of {@link RenderType#textIntensity(ResourceLocation)}, but with optional linear texture filtering.
*/
public static RenderType getTextIntensity(ResourceLocation locationIn)
{
return Internal.TEXT_INTENSITY.apply(locationIn);
}
/**
* @return Replacement of {@link RenderType#textPolygonOffset(ResourceLocation)}, but with optional linear texture filtering.
*/
public static RenderType getTextPolygonOffset(ResourceLocation locationIn)
{
return Internal.TEXT_POLYGON_OFFSET.apply(locationIn);
}
/**
* @return Replacement of {@link RenderType#textIntensityPolygonOffset(ResourceLocation)}, but with optional linear texture filtering.
*/
public static RenderType getTextIntensityPolygonOffset(ResourceLocation locationIn)
{
return Internal.TEXT_INTENSITY_POLYGON_OFFSET.apply(locationIn);
}
/**
* @return Replacement of {@link RenderType#textSeeThrough(ResourceLocation)}, but with optional linear texture filtering.
*/
public static RenderType getTextSeeThrough(ResourceLocation locationIn)
{
return Internal.TEXT_SEETHROUGH.apply(locationIn);
}
/**
* @return Replacement of {@link RenderType#textIntensitySeeThrough(ResourceLocation)}, but with optional linear texture filtering.
*/
public static RenderType getTextIntensitySeeThrough(ResourceLocation locationIn)
{
return Internal.TEXT_INTENSITY_SEETHROUGH.apply(locationIn);
}
/**
* @return A variation of {@link RenderType#translucent()} that uses {@link OutputStateShard#PARTICLES_TARGET} to allow fabulous transparency sorting when using {@link RenderLevelStageEvent}
*/
public static RenderType getTranslucentParticlesTarget(ResourceLocation locationIn)
{
return Internal.TRANSLUCENT_PARTICLES_TARGET.apply(locationIn);
}
// ----------------------------------------
// Implementation details below this line
// ----------------------------------------
private final NonNullSupplier<RenderType> renderTypeSupplier;
ForgeRenderTypes(NonNullSupplier<RenderType> renderTypeSupplier)
{
// Wrap in a Lazy<> to avoid running the supplier more than once.
this.renderTypeSupplier = NonNullLazy.of(renderTypeSupplier);
}
public RenderType get()
{
return renderTypeSupplier.get();
}
private static class Internal extends RenderType
{
private static final ShaderStateShard RENDERTYPE_ENTITY_TRANSLUCENT_UNLIT_SHADER = new ShaderStateShard(ForgeHooksClient.ClientEvents::getEntityTranslucentUnlitShader);
private Internal(String name, VertexFormat fmt, VertexFormat.Mode glMode, int size, boolean doCrumbling, boolean depthSorting, Runnable onEnable, Runnable onDisable)
{
super(name, fmt, glMode, size, doCrumbling, depthSorting, onEnable, onDisable);
throw new IllegalStateException("This class must not be instantiated");
}
public static Function<ResourceLocation, RenderType> UNSORTED_TRANSLUCENT = Util.memoize(Internal::unsortedTranslucent);
private static RenderType unsortedTranslucent(ResourceLocation textureLocation)
{
final boolean sortingEnabled = false;
CompositeState renderState = CompositeState.builder()
.setShaderState(RenderType.RENDERTYPE_ENTITY_TRANSLUCENT_SHADER)
.setTextureState(new TextureStateShard(textureLocation, false, false))
.setTransparencyState(TRANSLUCENT_TRANSPARENCY)
.setCullState(NO_CULL)
.setLightmapState(LIGHTMAP)
.setOverlayState(OVERLAY)
.createCompositeState(true);
return create("forge_entity_unsorted_translucent", DefaultVertexFormat.NEW_ENTITY, VertexFormat.Mode.QUADS, 256, true, sortingEnabled, renderState);
}
private static final BiFunction<ResourceLocation, Boolean, RenderType> ENTITY_TRANSLUCENT = Util.memoize((p_173227_, p_173228_) -> {
RenderType.CompositeState rendertype$compositestate = RenderType.CompositeState.builder()
.setShaderState(RENDERTYPE_ENTITY_TRANSLUCENT_SHADER)
.setTextureState(new RenderStateShard.TextureStateShard(p_173227_, false, false))
.setTransparencyState(TRANSLUCENT_TRANSPARENCY)
.setCullState(NO_CULL)
.setLightmapState(LIGHTMAP)
.setOverlayState(OVERLAY)
.createCompositeState(p_173228_);
return create("entity_translucent", DefaultVertexFormat.NEW_ENTITY, VertexFormat.Mode.QUADS, 256, true, true, rendertype$compositestate);
});
public static Function<ResourceLocation, RenderType> UNLIT_TRANSLUCENT_SORTED = Util.memoize(tex -> Internal.unlitTranslucent(tex, true));
public static Function<ResourceLocation, RenderType> UNLIT_TRANSLUCENT_UNSORTED = Util.memoize(tex -> Internal.unlitTranslucent(tex, false));
private static RenderType unlitTranslucent(ResourceLocation textureLocation, boolean sortingEnabled)
{
CompositeState renderState = CompositeState.builder()
.setShaderState(RENDERTYPE_ENTITY_TRANSLUCENT_UNLIT_SHADER)
.setTextureState(new TextureStateShard(textureLocation, false, false))
.setTransparencyState(TRANSLUCENT_TRANSPARENCY)
.setCullState(NO_CULL)
.setLightmapState(LIGHTMAP)
.setOverlayState(OVERLAY)
.createCompositeState(true);
return create("forge_entity_unlit_translucent", DefaultVertexFormat.NEW_ENTITY, VertexFormat.Mode.QUADS, 256, true, sortingEnabled, renderState);
}
public static Function<ResourceLocation, RenderType> LAYERED_ITEM_SOLID = Util.memoize(Internal::layeredItemSolid);
private static RenderType layeredItemSolid(ResourceLocation locationIn) {
RenderType.CompositeState rendertype$state = RenderType.CompositeState.builder()
.setShaderState(RenderType.RENDERTYPE_ENTITY_SOLID_SHADER)
.setTextureState(new RenderStateShard.TextureStateShard(locationIn, false, false))
.setTransparencyState(NO_TRANSPARENCY)
.setLightmapState(LIGHTMAP)
.setOverlayState(OVERLAY)
.createCompositeState(true);
return create("forge_item_entity_solid", DefaultVertexFormat.NEW_ENTITY, VertexFormat.Mode.QUADS, 256, true, false, rendertype$state);
}
public static Function<ResourceLocation, RenderType> LAYERED_ITEM_CUTOUT = Util.memoize(Internal::layeredItemCutout);
private static RenderType layeredItemCutout(ResourceLocation locationIn) {
RenderType.CompositeState rendertype$state = RenderType.CompositeState.builder()
.setShaderState(RenderType.RENDERTYPE_ENTITY_CUTOUT_SHADER)
.setTextureState(new RenderStateShard.TextureStateShard(locationIn, false, false))
.setTransparencyState(NO_TRANSPARENCY)
.setLightmapState(LIGHTMAP)
.setOverlayState(OVERLAY)
.createCompositeState(true);
return create("forge_item_entity_cutout", DefaultVertexFormat.NEW_ENTITY, VertexFormat.Mode.QUADS, 256, true, false, rendertype$state);
}
public static Function<ResourceLocation, RenderType> LAYERED_ITEM_CUTOUT_MIPPED = Util.memoize(Internal::layeredItemCutoutMipped);
private static RenderType layeredItemCutoutMipped(ResourceLocation locationIn) {
RenderType.CompositeState rendertype$state = RenderType.CompositeState.builder()
.setShaderState(RenderType.RENDERTYPE_ENTITY_SMOOTH_CUTOUT_SHADER)
.setTextureState(new RenderStateShard.TextureStateShard(locationIn, false, true))
.setTransparencyState(NO_TRANSPARENCY)
.setLightmapState(LIGHTMAP)
.setOverlayState(OVERLAY)
.createCompositeState(true);
return create("forge_item_entity_cutout_mipped", DefaultVertexFormat.NEW_ENTITY, VertexFormat.Mode.QUADS, 256, true, false, rendertype$state);
}
public static Function<ResourceLocation, RenderType> LAYERED_ITEM_TRANSLUCENT = Util.memoize(Internal::layeredItemTranslucent);
private static RenderType layeredItemTranslucent(ResourceLocation locationIn) {
RenderType.CompositeState rendertype$state = RenderType.CompositeState.builder()
.setShaderState(RenderType.RENDERTYPE_ENTITY_TRANSLUCENT_SHADER)
.setTextureState(new RenderStateShard.TextureStateShard(locationIn, false, false))
.setTransparencyState(TRANSLUCENT_TRANSPARENCY)
.setLightmapState(LIGHTMAP)
.setOverlayState(OVERLAY)
.createCompositeState(true);
return create("forge_item_entity_translucent_cull", DefaultVertexFormat.NEW_ENTITY, VertexFormat.Mode.QUADS, 256, true, true, rendertype$state);
}
public static Function<ResourceLocation, RenderType> TEXT = Util.memoize(Internal::getText);
private static RenderType getText(ResourceLocation locationIn) {
RenderType.CompositeState rendertype$state = RenderType.CompositeState.builder()
.setShaderState(RENDERTYPE_TEXT_SHADER)
.setTextureState(new CustomizableTextureState(locationIn, () -> ForgeRenderTypes.enableTextTextureLinearFiltering, () -> false))
.setTransparencyState(TRANSLUCENT_TRANSPARENCY)
.setLightmapState(LIGHTMAP)
.createCompositeState(false);
return create("forge_text", DefaultVertexFormat.POSITION_COLOR_TEX_LIGHTMAP, VertexFormat.Mode.QUADS, 256, false, true, rendertype$state);
}
public static Function<ResourceLocation, RenderType> TEXT_INTENSITY = Util.memoize(Internal::getTextIntensity);
private static RenderType getTextIntensity(ResourceLocation locationIn) {
RenderType.CompositeState rendertype$state = RenderType.CompositeState.builder()
.setShaderState(RENDERTYPE_TEXT_INTENSITY_SHADER)
.setTextureState(new CustomizableTextureState(locationIn, () -> ForgeRenderTypes.enableTextTextureLinearFiltering, () -> false))
.setTransparencyState(TRANSLUCENT_TRANSPARENCY)
.setLightmapState(LIGHTMAP)
.createCompositeState(false);
return create("text_intensity", DefaultVertexFormat.POSITION_COLOR_TEX_LIGHTMAP, VertexFormat.Mode.QUADS, 256, false, true, rendertype$state);
}
public static Function<ResourceLocation, RenderType> TEXT_POLYGON_OFFSET = Util.memoize(Internal::getTextPolygonOffset);
private static RenderType getTextPolygonOffset(ResourceLocation locationIn) {
RenderType.CompositeState rendertype$state = RenderType.CompositeState.builder()
.setShaderState(RENDERTYPE_TEXT_SHADER)
.setTextureState(new CustomizableTextureState(locationIn, () -> ForgeRenderTypes.enableTextTextureLinearFiltering, () -> false))
.setTransparencyState(TRANSLUCENT_TRANSPARENCY)
.setLightmapState(LIGHTMAP)
.setLayeringState(POLYGON_OFFSET_LAYERING)
.createCompositeState(false);
return create("text_intensity", DefaultVertexFormat.POSITION_COLOR_TEX_LIGHTMAP, VertexFormat.Mode.QUADS, 256, false, true, rendertype$state);
}
public static Function<ResourceLocation, RenderType> TEXT_INTENSITY_POLYGON_OFFSET = Util.memoize(Internal::getTextIntensityPolygonOffset);
private static RenderType getTextIntensityPolygonOffset(ResourceLocation locationIn) {
RenderType.CompositeState rendertype$state = RenderType.CompositeState.builder()
.setShaderState(RENDERTYPE_TEXT_INTENSITY_SHADER)
.setTextureState(new CustomizableTextureState(locationIn, () -> ForgeRenderTypes.enableTextTextureLinearFiltering, () -> false))
.setTransparencyState(TRANSLUCENT_TRANSPARENCY)
.setLightmapState(LIGHTMAP)
.setLayeringState(POLYGON_OFFSET_LAYERING)
.createCompositeState(false);
return create("text_intensity", DefaultVertexFormat.POSITION_COLOR_TEX_LIGHTMAP, VertexFormat.Mode.QUADS, 256, false, true, rendertype$state);
}
public static Function<ResourceLocation, RenderType> TEXT_SEETHROUGH = Util.memoize(Internal::getTextSeeThrough);
private static RenderType getTextSeeThrough(ResourceLocation locationIn) {
RenderType.CompositeState rendertype$state = RenderType.CompositeState.builder()
.setShaderState(RENDERTYPE_TEXT_SEE_THROUGH_SHADER)
.setTextureState(new CustomizableTextureState(locationIn, () -> ForgeRenderTypes.enableTextTextureLinearFiltering, () -> false))
.setTransparencyState(TRANSLUCENT_TRANSPARENCY)
.setLightmapState(LIGHTMAP)
.setDepthTestState(NO_DEPTH_TEST)
.setWriteMaskState(COLOR_WRITE)
.createCompositeState(false);
return create("forge_text_see_through", DefaultVertexFormat.POSITION_COLOR_TEX_LIGHTMAP, VertexFormat.Mode.QUADS, 256, false, true, rendertype$state);
}
public static Function<ResourceLocation, RenderType> TEXT_INTENSITY_SEETHROUGH = Util.memoize(Internal::getTextIntensitySeeThrough);
private static RenderType getTextIntensitySeeThrough(ResourceLocation locationIn) {
RenderType.CompositeState rendertype$state = RenderType.CompositeState.builder()
.setShaderState(RENDERTYPE_TEXT_INTENSITY_SEE_THROUGH_SHADER)
.setTextureState(new CustomizableTextureState(locationIn, () -> ForgeRenderTypes.enableTextTextureLinearFiltering, () -> false))
.setTransparencyState(TRANSLUCENT_TRANSPARENCY)
.setLightmapState(LIGHTMAP)
.setDepthTestState(NO_DEPTH_TEST)
.setWriteMaskState(COLOR_WRITE)
.createCompositeState(false);
return create("forge_text_see_through", DefaultVertexFormat.POSITION_COLOR_TEX_LIGHTMAP, VertexFormat.Mode.QUADS, 256, false, true, rendertype$state);
}
public static Function<ResourceLocation, RenderType> TRANSLUCENT_PARTICLES_TARGET = Util.memoize(Internal::getTranslucentParticlesTarget);
private static RenderType getTranslucentParticlesTarget(ResourceLocation locationIn) {
RenderType.CompositeState rendertype$state = RenderType.CompositeState.builder()
.setShaderState(RENDERTYPE_TRANSLUCENT_SHADER)
.setTextureState(new RenderStateShard.TextureStateShard(locationIn, false, true))
.setTransparencyState(TRANSLUCENT_TRANSPARENCY)
.setLightmapState(LIGHTMAP)
.setOutputState(PARTICLES_TARGET)
.createCompositeState(true);
return create("forge_translucent_particles_target", DefaultVertexFormat.BLOCK, VertexFormat.Mode.QUADS, 2097152, true, true, rendertype$state);
}
}
private static class CustomizableTextureState extends TextureStateShard
{
private CustomizableTextureState(ResourceLocation resLoc, Supplier<Boolean> blur, Supplier<Boolean> mipmap)
{
super(resLoc, blur.get(), mipmap.get());
this.setupState = () -> {
this.blur = blur.get();
this.mipmap = mipmap.get();
TextureManager texturemanager = Minecraft.getInstance().getTextureManager();
texturemanager.getTexture(resLoc).setFilter(this.blur, this.mipmap);
RenderSystem.setShaderTexture(0, resLoc);
};
}
}
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client;
import net.minecraft.client.model.HumanoidModel;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.entity.HumanoidArm;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.item.ItemStack;
import net.minecraftforge.client.extensions.common.IClientItemExtensions;
/**
* An ArmPose that can be defined by the user.
* Register one by creating a custom {@link net.minecraft.client.model.HumanoidModel.ArmPose}
* and returning it in {@link IClientItemExtensions#getArmPose(LivingEntity, InteractionHand, ItemStack)}.
*/
@FunctionalInterface
public interface IArmPoseTransformer
{
/**
* This method should be used to apply all wanted transformations to the player when the ArmPose is active.
* You can use {@link LivingEntity#getTicksUsingItem()} and {@link LivingEntity#getUseItemRemainingTicks()} for moving animations.
*
* @param model The humanoid model
* @param entity The humanoid entity
* @param arm Arm to pose
*/
void applyTransform(HumanoidModel<?> model, LivingEntity entity, HumanoidArm arm);
}

View File

@@ -0,0 +1,28 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client;
import net.minecraft.client.gui.Font;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.ItemLike;
import net.minecraftforge.client.event.RegisterItemDecorationsEvent;
/**
* An ItemDecorator that is used to render something on specific items, when the DurabilityBar and StackCount is rendered.
* Add it to an item using {@linkplain RegisterItemDecorationsEvent#register(ItemLike, IItemDecorator)}.
*/
public interface IItemDecorator
{
/**
* Is called after {@linkplain GuiGraphics#renderItemDecorations(Font, ItemStack, int, int, String)} is done rendering.
* The StackCount is rendered at blitOffset+200 so use the blitOffset with caution.
* <p>
* The RenderState during this call will be: enableTexture, enableDepthTest, enableBlend and defaultBlendFunc
* @return true if you have modified the RenderState and it has to be reset for other ItemDecorators
*/
boolean render(GuiGraphics guiGraphics, Font font, ItemStack stack, int xOffset, int yOffset);
}

View File

@@ -0,0 +1,73 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import net.minecraft.client.gui.GuiGraphics;
import org.jetbrains.annotations.ApiStatus;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.mojang.blaze3d.systems.RenderSystem;
import net.minecraft.client.gui.Font;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraftforge.client.event.RegisterItemDecorationsEvent;
import net.minecraftforge.fml.ModLoader;
@ApiStatus.Internal
public final class ItemDecoratorHandler
{
private final List<IItemDecorator> itemDecorators;
private static Map<Item, ItemDecoratorHandler> DECORATOR_LOOKUP = ImmutableMap.of();
private static final ItemDecoratorHandler EMPTY = new ItemDecoratorHandler();
private ItemDecoratorHandler()
{
this.itemDecorators = ImmutableList.of();
}
private ItemDecoratorHandler(List<IItemDecorator> itemDecorators)
{
this.itemDecorators = ImmutableList.copyOf(itemDecorators);
}
public static void init()
{
var decorators = new HashMap<Item, List<IItemDecorator>>();
var event = new RegisterItemDecorationsEvent(decorators);
ModLoader.get().postEventWrapContainerInModOrder(event);
var builder = new ImmutableMap.Builder<Item, ItemDecoratorHandler>();
decorators.forEach((item, itemDecorators) -> builder.put(item, new ItemDecoratorHandler(itemDecorators)));
DECORATOR_LOOKUP = builder.build();
}
public static ItemDecoratorHandler of(ItemStack stack)
{
return DECORATOR_LOOKUP.getOrDefault(stack.getItem(), EMPTY);
}
public void render(GuiGraphics guiGraphics, Font font, ItemStack stack, int xOffset, int yOffset)
{
resetRenderState();
for (IItemDecorator itemDecorator : itemDecorators)
{
if (itemDecorator.render(guiGraphics, font, stack, xOffset, yOffset))
resetRenderState();
}
}
private void resetRenderState()
{
RenderSystem.enableDepthTest();
RenderSystem.enableBlend();
RenderSystem.defaultBlendFunc();
}
}

View File

@@ -0,0 +1,62 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client;
import com.google.common.collect.ImmutableMap;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.resources.ResourceLocation;
import net.minecraftforge.client.event.RegisterNamedRenderTypesEvent;
import net.minecraftforge.fml.ModLoader;
import org.jetbrains.annotations.ApiStatus;
import java.util.HashMap;
import java.util.Map;
/**
* Manager for named {@link RenderType render types}.
* <p>
* Provides a lookup.
*/
public final class NamedRenderTypeManager
{
private static ImmutableMap<ResourceLocation, RenderTypeGroup> RENDER_TYPES;
/**
* Finds the {@link RenderTypeGroup} for a given name, or the {@link RenderTypeGroup#EMPTY empty group} if not found.
*/
public static RenderTypeGroup get(ResourceLocation name)
{
return RENDER_TYPES.getOrDefault(name, RenderTypeGroup.EMPTY);
}
@ApiStatus.Internal
public static void init()
{
var renderTypes = new HashMap<ResourceLocation, RenderTypeGroup>();
preRegisterVanillaRenderTypes(renderTypes);
var event = new RegisterNamedRenderTypesEvent(renderTypes);
ModLoader.get().postEventWrapContainerInModOrder(event);
RENDER_TYPES = ImmutableMap.copyOf(renderTypes);
}
/**
* Pre-registers vanilla render types.
*/
private static void preRegisterVanillaRenderTypes(Map<ResourceLocation, RenderTypeGroup> blockRenderTypes)
{
blockRenderTypes.put(new ResourceLocation("solid"), new RenderTypeGroup(RenderType.solid(), ForgeRenderTypes.ITEM_LAYERED_SOLID.get()));
blockRenderTypes.put(new ResourceLocation("cutout"), new RenderTypeGroup(RenderType.cutout(), ForgeRenderTypes.ITEM_LAYERED_CUTOUT.get()));
// Generally entity/item rendering shouldn't use mipmaps, so cutout_mipped has them off by default. To enforce them, use cutout_mipped_all.
blockRenderTypes.put(new ResourceLocation("cutout_mipped"), new RenderTypeGroup(RenderType.cutoutMipped(), ForgeRenderTypes.ITEM_LAYERED_CUTOUT.get()));
blockRenderTypes.put(new ResourceLocation("cutout_mipped_all"), new RenderTypeGroup(RenderType.cutoutMipped(), ForgeRenderTypes.ITEM_LAYERED_CUTOUT_MIPPED.get()));
blockRenderTypes.put(new ResourceLocation("translucent"), new RenderTypeGroup(RenderType.translucent(), ForgeRenderTypes.ITEM_LAYERED_TRANSLUCENT.get()));
blockRenderTypes.put(new ResourceLocation("tripwire"), new RenderTypeGroup(RenderType.tripwire(), ForgeRenderTypes.ITEM_LAYERED_TRANSLUCENT.get()));
}
private NamedRenderTypeManager()
{
}
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client;
import java.util.HashMap;
import java.util.Map;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
import net.minecraft.client.gui.screens.worldselection.PresetEditor;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.level.levelgen.presets.WorldPreset;
import net.minecraftforge.client.event.RegisterPresetEditorsEvent;
import net.minecraftforge.fml.ModLoader;
public final class PresetEditorManager
{
private PresetEditorManager() {} // Utility class
private static Map<ResourceKey<WorldPreset>, PresetEditor> editors = Map.of();
@SuppressWarnings("deprecation")
@ApiStatus.Internal
static void init()
{
// Start with the vanilla entries
Map<ResourceKey<WorldPreset>, PresetEditor> gatheredEditors = new HashMap<>();
// Vanilla's map uses Optional<ResourceKey>s as its keys.
// As far as we can tell there's no good reason for this, so we'll just use regular keys.
PresetEditor.EDITORS.forEach((k, v) -> k.ifPresent(key -> gatheredEditors.put(key, v)));
// Gather mods' entries
RegisterPresetEditorsEvent event = new RegisterPresetEditorsEvent(gatheredEditors);
ModLoader.get().postEventWrapContainerInModOrder(event);
editors = gatheredEditors;
}
/**
* {@return the PresetEditor for the given WorldPreset key, or null if no such PresetEditor exists}
*
* @param key ResourceKey for the specified WorldPreset/PresetEditor.
*/
@Nullable
public static PresetEditor get(ResourceKey<WorldPreset> key)
{
return editors.get(key);
}
}

View File

@@ -0,0 +1,79 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import net.minecraft.client.RecipeBookCategories;
import net.minecraft.world.inventory.RecipeBookType;
import net.minecraft.world.item.crafting.Recipe;
import net.minecraft.world.item.crafting.RecipeType;
import net.minecraftforge.client.event.RegisterRecipeBookCategoriesEvent;
import net.minecraftforge.fml.ModLoader;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
/**
* Manager for {@link RecipeBookType recipe book types} and {@link RecipeBookCategories categories}.
* <p>
* Provides a recipe category lookup.
*/
public final class RecipeBookManager
{
// Not using ConcurrentHashMap here because it's slower for lookups, so we only use it during init
private static final Map<RecipeBookCategories, List<RecipeBookCategories>> AGGREGATE_CATEGORIES = new HashMap<>();
private static final Map<RecipeBookType, List<RecipeBookCategories>> TYPE_CATEGORIES = new HashMap<>();
private static final Map<RecipeType<?>, Function<Recipe<?>, RecipeBookCategories>> RECIPE_CATEGORY_LOOKUPS = new HashMap<>();
private static final Map<RecipeBookCategories, List<RecipeBookCategories>> AGGREGATE_CATEGORIES_VIEW = Collections.unmodifiableMap(AGGREGATE_CATEGORIES);
/**
* Finds the category the specified recipe should display in, or null if none.
*/
@Nullable
public static <T extends Recipe<?>> RecipeBookCategories findCategories(RecipeType<T> type, T recipe)
{
var lookup = RECIPE_CATEGORY_LOOKUPS.get(type);
return lookup != null ? lookup.apply(recipe) : null;
}
@ApiStatus.Internal
public static Map<RecipeBookCategories, List<RecipeBookCategories>> getAggregateCategories()
{
return AGGREGATE_CATEGORIES_VIEW;
}
@ApiStatus.Internal
public static List<RecipeBookCategories> getCustomCategoriesOrEmpty(RecipeBookType recipeBookType)
{
return TYPE_CATEGORIES.getOrDefault(recipeBookType, List.of());
}
@ApiStatus.Internal
public static void init()
{
// The ImmutableMap is the patched out value of AGGREGATE_CATEGORIES
var aggregateCategories = new HashMap<>(ImmutableMap.of(
RecipeBookCategories.CRAFTING_SEARCH, ImmutableList.of(RecipeBookCategories.CRAFTING_EQUIPMENT, RecipeBookCategories.CRAFTING_BUILDING_BLOCKS, RecipeBookCategories.CRAFTING_MISC, RecipeBookCategories.CRAFTING_REDSTONE),
RecipeBookCategories.FURNACE_SEARCH, ImmutableList.of(RecipeBookCategories.FURNACE_FOOD, RecipeBookCategories.FURNACE_BLOCKS, RecipeBookCategories.FURNACE_MISC),
RecipeBookCategories.BLAST_FURNACE_SEARCH, ImmutableList.of(RecipeBookCategories.BLAST_FURNACE_BLOCKS, RecipeBookCategories.BLAST_FURNACE_MISC),
RecipeBookCategories.SMOKER_SEARCH, ImmutableList.of(RecipeBookCategories.SMOKER_FOOD)
));
var typeCategories = new HashMap<RecipeBookType, ImmutableList<RecipeBookCategories>>();
var recipeCategoryLookups = new HashMap<RecipeType<?>, Function<Recipe<?>, RecipeBookCategories>>();
var event = new RegisterRecipeBookCategoriesEvent(aggregateCategories, typeCategories, recipeCategoryLookups);
ModLoader.get().postEventWrapContainerInModOrder(event);
AGGREGATE_CATEGORIES.putAll(aggregateCategories);
TYPE_CATEGORIES.putAll(typeCategories);
RECIPE_CATEGORY_LOOKUPS.putAll(recipeCategoryLookups);
}
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client;
import net.minecraft.client.renderer.RenderType;
/**
* A set of functionally equivalent shaders. One using {@link com.mojang.blaze3d.vertex.DefaultVertexFormat#BLOCK},
* and the other two using {@link com.mojang.blaze3d.vertex.DefaultVertexFormat#NEW_ENTITY}.
* {@code entityFabulous} may support custom render targets and other aspects of the fabulous pipeline, or can otherwise
* be the same as {@code entity}.
*/
public record RenderTypeGroup(RenderType block, RenderType entity, RenderType entityFabulous)
{
public static RenderTypeGroup EMPTY = new RenderTypeGroup(null, null, null);
public RenderTypeGroup
{
if ((block == null) != (entity == null) || (block == null) != (entityFabulous == null))
throw new IllegalArgumentException("The render types in a group must either be all null, or all non-null.");
}
public RenderTypeGroup(RenderType block, RenderType entity)
{
this(block, entity, entity);
}
/**
* {@return true if this group has render types or not. It either has all, or none}
*/
public boolean isEmpty()
{
// We throw an exception in the constructor if nullability doesn't match, so checking this is enough
return block == null;
}
}

View File

@@ -0,0 +1,76 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client;
import com.mojang.blaze3d.vertex.DefaultVertexFormat;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.ItemBlockRenderTypes;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.Sheets;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.util.RandomSource;
import net.minecraft.world.item.BlockItem;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraftforge.client.model.data.ModelData;
import org.jetbrains.annotations.NotNull;
/**
* Provides helper functions replacing those in {@link ItemBlockRenderTypes}.
*/
public final class RenderTypeHelper
{
/**
* Provides a {@link RenderType} using {@link DefaultVertexFormat#NEW_ENTITY} for the given {@link DefaultVertexFormat#BLOCK} format.
* This should be called for each {@link RenderType} returned by {@link BakedModel#getRenderTypes(BlockState, RandomSource, ModelData)}.
* <p>
* Mimics the behavior of vanilla's {@link ItemBlockRenderTypes#getRenderType(BlockState, boolean)}.
*/
@NotNull
public static RenderType getEntityRenderType(RenderType chunkRenderType, boolean cull)
{
if (chunkRenderType != RenderType.translucent())
return Sheets.cutoutBlockSheet();
return cull || !Minecraft.useShaderTransparency() ? Sheets.translucentCullBlockSheet() : Sheets.translucentItemSheet();
}
/**
* Provides a {@link RenderType} fit for rendering moving blocks given the specified chunk render type.
* This should be called for each {@link RenderType} returned by {@link BakedModel#getRenderTypes(BlockState, RandomSource, ModelData)}.
* <p>
* Mimics the behavior of vanilla's {@link ItemBlockRenderTypes#getMovingBlockRenderType(BlockState)}.
*/
@NotNull
public static RenderType getMovingBlockRenderType(RenderType renderType)
{
if (renderType == RenderType.translucent())
return RenderType.translucentMovingBlock();
return renderType;
}
/**
* Provides a fallback {@link RenderType} for the given {@link ItemStack} in the case that none is explicitly specified.
* <p>
* Mimics the behavior of vanilla's {@link ItemBlockRenderTypes#getRenderType(ItemStack, boolean)}
* but removes the need to query the model again if the item is a {@link BlockItem}.
*/
@NotNull
public static RenderType getFallbackItemRenderType(ItemStack stack, BakedModel model, boolean cull)
{
if (stack.getItem() instanceof BlockItem blockItem)
{
var renderTypes = model.getRenderTypes(blockItem.getBlock().defaultBlockState(), RandomSource.create(42), ModelData.EMPTY);
if (renderTypes.contains(RenderType.translucent()))
return getEntityRenderType(RenderType.translucent(), cull);
return Sheets.cutoutBlockSheet();
}
return cull ? Sheets.translucentCullBlockSheet() : Sheets.translucentItemSheet();
}
private RenderTypeHelper()
{
}
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client;
import java.util.BitSet;
public final class StencilManager
{
private static final BitSet BITS = new BitSet(8);
/**
* Reserve a stencil bit for use in rendering
* <p>
* Note: you must check the {@link com.mojang.blaze3d.pipeline.RenderTarget} you are working with to
* determine if stencil bits are enabled on it before use.
*
* @return A bit, or -1 if no further stencil bits are available
*/
public static int reserveBit()
{
int bit = BITS.nextClearBit(0);
if (bit >= 0)
BITS.set(bit);
return bit;
}
/**
* Release the stencil bit for other use
*
* @param bit The bit obtained from {@link #reserveBit()}
*/
public static void releaseBit(int bit)
{
if (bit >= 0 && bit < BITS.length())
BITS.clear(bit);
}
private StencilManager()
{
}
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client.event;
import com.google.common.base.Strings;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.eventbus.api.Cancelable;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.fml.LogicalSide;
import org.jetbrains.annotations.ApiStatus;
/**
* Fired when the client is about to send a chat message to the server.
*
* <p>This event is {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.
* If the event is cancelled, the chat message will not be sent to the server.</p>
*
* <p>This event is fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
**/
@Cancelable
public class ClientChatEvent extends Event
{
private String message;
private final String originalMessage;
@ApiStatus.Internal
public ClientChatEvent(String message)
{
this.setMessage(message);
this.originalMessage = Strings.nullToEmpty(message);
this.message = this.originalMessage;
}
/**
* {@return the message that will be sent to the server, if the event is not cancelled. This can be changed by mods}
*/
public String getMessage()
{
return this.message;
}
/**
* Sets the new message to be sent to the server, if the event is not cancelled.
*
* @param message the new message to be sent
*/
public void setMessage(String message)
{
this.message = Strings.nullToEmpty(message);
}
/**
* {@return the original message that was to be sent to the server. This cannot be changed by mods}
*/
public String getOriginalMessage()
{
return this.originalMessage;
}
}

View File

@@ -0,0 +1,150 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client.event;
import net.minecraft.Util;
import net.minecraft.network.chat.ChatType;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.PlayerChatMessage;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.eventbus.api.Cancelable;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.fml.LogicalSide;
import org.jetbrains.annotations.ApiStatus;
import java.util.UUID;
/**
* Fired when a chat message is received on the client.
* This can be used for filtering and detecting messages with specific words or phrases, and suppressing them.
*
* <p>This event is {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.
* If the event is cancelled, the message is not displayed in the chat message window.</p>
*
* <p>This event is fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*
* @see ChatType
*/
@Cancelable
public class ClientChatReceivedEvent extends Event
{
private Component message;
private final ChatType.Bound boundChatType;
private final UUID sender;
@ApiStatus.Internal
public ClientChatReceivedEvent(ChatType.Bound boundChatType, Component message, UUID sender)
{
this.boundChatType = boundChatType;
this.message = message;
this.sender = sender;
}
/**
* {@return the message that will be displayed in the chat message window, if the event is not cancelled}
*/
public Component getMessage()
{
return message;
}
/**
* Sets the new message to be displayed in the chat message window, if the event is not cancelled.
*
* @param message the new message to be displayed
*/
public void setMessage(Component message)
{
this.message = message;
}
/**
* {@return the bound chat type of the chat message}.
* This contains the chat type, display name of the sender, and nullable target name depending on the chat type.
*/
public ChatType.Bound getBoundChatType()
{
return this.boundChatType;
}
/**
* {@return the message sender}.
* This will be {@link Util#NIL_UUID} if the message is a system message.
*/
public UUID getSender()
{
return this.sender;
}
/**
* {@return {@code true} if the message was sent by the system, {@code false} otherwise}
*/
public boolean isSystem()
{
return this.sender.equals(Util.NIL_UUID);
}
/**
* Fired when a player chat message is received on the client.
*
* <p>This event is {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.
* If the event is cancelled, the message is not displayed in the chat message window.</p>
*
* <p>This event is fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*
* @see ChatType
*/
public static class Player extends ClientChatReceivedEvent
{
private final PlayerChatMessage playerChatMessage;
@ApiStatus.Internal
public Player(ChatType.Bound boundChatType, Component message, PlayerChatMessage playerChatMessage, UUID sender)
{
super(boundChatType, message, sender);
this.playerChatMessage = playerChatMessage;
}
/**
* {@return the full player chat message}.
* This contains the sender UUID, various signing data, and the optional unsigned contents.
*/
public PlayerChatMessage getPlayerChatMessage()
{
return this.playerChatMessage;
}
}
/**
* Fired when a system chat message is received on the client.
*
* <p>This event is {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.
* If the event is cancelled, the message is not displayed in the chat message window or in the overlay.</p>
*
* <p>This event is fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
public static class System extends ClientChatReceivedEvent
{
private final boolean overlay;
@ApiStatus.Internal
public System(ChatType.Bound boundChatType, Component message, boolean overlay)
{
super(boundChatType, message, Util.NIL_UUID);
this.overlay = overlay;
}
/**
* {@return whether the message goes to the overlay}
*/
public boolean isOverlay()
{
return this.overlay;
}
}
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client.event;
import net.minecraft.client.multiplayer.PlayerInfo;
import net.minecraft.world.level.GameType;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.eventbus.api.Cancelable;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.fml.LogicalSide;
import org.jetbrains.annotations.ApiStatus;
/**
* Fired when the client player is notified of a change of {@link GameType} from the server.
*
* <p>This event is not {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.</p>
*
* <p>This event is fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
public class ClientPlayerChangeGameTypeEvent extends Event
{
private final PlayerInfo info;
private final GameType currentGameType;
private final GameType newGameType;
@ApiStatus.Internal
public ClientPlayerChangeGameTypeEvent(PlayerInfo info, GameType currentGameType, GameType newGameType)
{
this.info = info;
this.currentGameType = currentGameType;
this.newGameType = newGameType;
}
/**
* {@return the client player information}
*/
public PlayerInfo getInfo()
{
return info;
}
/**
* {@return the current game type of the player}
*/
public GameType getCurrentGameType()
{
return currentGameType;
}
/**
* {@return the new game type of the player}
*/
public GameType getNewGameType()
{
return newGameType;
}
}

View File

@@ -0,0 +1,186 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client.event;
import net.minecraft.client.multiplayer.MultiPlayerGameMode;
import net.minecraft.client.player.LocalPlayer;
import net.minecraft.network.Connection;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.eventbus.api.Cancelable;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.fml.LogicalSide;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
/**
* Fired for different client connectivity events.
* See the various subclasses to listen for specific events.
*
* <p>These events are fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*
* @see LoggingIn
* @see LoggingOut
* @see Clone
**/
public abstract class ClientPlayerNetworkEvent extends Event
{
private final MultiPlayerGameMode multiPlayerGameMode;
private final LocalPlayer player;
private final Connection connection;
@ApiStatus.Internal
protected ClientPlayerNetworkEvent(final MultiPlayerGameMode multiPlayerGameMode, final LocalPlayer player, final Connection connection)
{
this.multiPlayerGameMode = multiPlayerGameMode;
this.player = player;
this.connection = connection;
}
/**
* {@return the multiplayer game mode controller for the player}
*/
public MultiPlayerGameMode getMultiPlayerGameMode()
{
return multiPlayerGameMode;
}
/**
* {@return the player instance}
*/
public LocalPlayer getPlayer()
{
return player;
}
/**
* {@return the network connection for the player}
*/
public Connection getConnection()
{
return connection;
}
/**
* Fired when the client player logs in to the server. The player should be initialized.
*
* <p>This event is not {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.</p>
*
* <p>This event is fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
public static class LoggingIn extends ClientPlayerNetworkEvent
{
@ApiStatus.Internal
public LoggingIn(final MultiPlayerGameMode controller, final LocalPlayer player, final Connection networkManager)
{
super(controller, player, networkManager);
}
}
/**
* Fired when the client player logs out. This event may also fire when a new integrated server is being created.
*
* <p>This event is not {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.</p>
*
* <p>This event is fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
@SuppressWarnings("NullableProblems")
// Shush IntelliJ, we override non-nullables as nullables in this specific event; see later comment
public static class LoggingOut extends ClientPlayerNetworkEvent
{
@ApiStatus.Internal
public LoggingOut(@Nullable final MultiPlayerGameMode controller, @Nullable final LocalPlayer player, @Nullable final Connection networkManager)
{
//noinspection ConstantConditions we know these are nullable, but we don't want to annotate the super as nullable since this is the only event with nullables
super(controller, player, networkManager);
}
/**
* {@return the multiplayer game mode controller for the player, may be {@code null}}. This may be {@code null}
* in certain situations such as the creating a new integrated server (singleplayer world) or connecting to
* a multiplayer server.
*/
@Nullable
@Override
public MultiPlayerGameMode getMultiPlayerGameMode()
{
return super.getMultiPlayerGameMode();
}
/**
* {@return the player instance, may be {@code null}}. This may be {@code null}
* in certain situations such as the creating a new integrated server (singleplayer world) or connecting to
* a multiplayer server.
*/
@Nullable
@Override
public LocalPlayer getPlayer()
{
return super.getPlayer();
}
/**
* {@return the network connection for the player, may be {@code null}}. This may be {@code null}
* in certain situations such as the creating a new integrated server (singleplayer world) or connecting to
* a multiplayer server.
*/
@Nullable
@Override
public Connection getConnection()
{
return super.getConnection();
}
}
/**
* Fired when the client player respawns, creating a new player instance to replace the old player instance.
*
* <p>This event is not {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.</p>
*
* <p>This event is fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
public static class Clone extends ClientPlayerNetworkEvent
{
private final LocalPlayer oldPlayer;
@ApiStatus.Internal
public Clone(final MultiPlayerGameMode pc, final LocalPlayer oldPlayer, final LocalPlayer newPlayer, final Connection networkManager)
{
super(pc, newPlayer, networkManager);
this.oldPlayer = oldPlayer;
}
/**
* {@return the previous player instance}
*/
public LocalPlayer getOldPlayer()
{
return oldPlayer;
}
/**
* {@return the newly created player instance}
*/
public LocalPlayer getNewPlayer()
{
return super.getPlayer();
}
/**
* {@return the newly created player instance}
*
* @see #getNewPlayer()
*/
@Override
public LocalPlayer getPlayer()
{
return super.getPlayer();
}
}
}

View File

@@ -0,0 +1,74 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client.event;
import net.minecraft.client.Minecraft;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.player.Player;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.eventbus.api.Cancelable;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.fml.LogicalSide;
import org.jetbrains.annotations.ApiStatus;
/**
* Fired after the field of vision (FOV) modifier for the player is calculated to allow developers to adjust it further.
*
* <p>This event is not {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.</p>
*
* <p>This event is fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*
* @see ViewportEvent.ComputeFov
*/
public class ComputeFovModifierEvent extends Event
{
private final Player player;
private final float fovModifier;
private float newFovModifier;
@ApiStatus.Internal
public ComputeFovModifierEvent(Player player, float fovModifier)
{
this.player = player;
this.fovModifier = fovModifier;
this.setNewFovModifier((float) Mth.lerp(Minecraft.getInstance().options.fovEffectScale().get(), 1.0F, fovModifier));
}
/**
* {@return the player affected by this event}
*/
public Player getPlayer()
{
return player;
}
/**
* {@return the original field of vision (FOV) of the player, before any modifications or interpolation}
*/
public float getFovModifier()
{
return fovModifier;
}
/**
* {@return the current field of vision (FOV) of the player}
*/
public float getNewFovModifier()
{
return newFovModifier;
}
/**
* Sets the new field of vision (FOV) of the player.
*
* @param newFovModifier the new field of vision (FOV)
*/
public void setNewFovModifier(float newFovModifier)
{
this.newFovModifier = newFovModifier;
}
}

View File

@@ -0,0 +1,132 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client.event;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.eventbus.api.Cancelable;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.fml.LogicalSide;
import org.jetbrains.annotations.ApiStatus;
/**
* Fired for hooking into {@link AbstractContainerScreen} events.
* See the subclasses to listen for specific events.
*
* <p>These events are fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*
* @see Render.Foreground
* @see Render.Background
*/
public abstract class ContainerScreenEvent extends Event
{
private final AbstractContainerScreen<?> containerScreen;
@ApiStatus.Internal
protected ContainerScreenEvent(AbstractContainerScreen<?> containerScreen)
{
this.containerScreen = containerScreen;
}
/**
* {@return the container screen}
*/
public AbstractContainerScreen<?> getContainerScreen()
{
return containerScreen;
}
/**
* Fired every time an {@link AbstractContainerScreen} renders.
* See the two subclasses to listen for foreground or background rendering.
*
* <p>These events are fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*
* @see Foreground
* @see Background
*/
public static abstract class Render extends ContainerScreenEvent
{
private final GuiGraphics guiGraphics;
private final int mouseX;
private final int mouseY;
@ApiStatus.Internal
protected Render(AbstractContainerScreen<?> guiContainer, GuiGraphics guiGraphics, int mouseX, int mouseY)
{
super(guiContainer);
this.guiGraphics = guiGraphics;
this.mouseX = mouseX;
this.mouseY = mouseY;
}
/**
* {@return the gui graphics used for rendering}
*/
public GuiGraphics getGuiGraphics()
{
return guiGraphics;
}
/**
* {@return the X coordinate of the mouse pointer}
*/
public int getMouseX()
{
return mouseX;
}
/**
* {@return the Y coordinate of the mouse pointer}
*/
public int getMouseY()
{
return mouseY;
}
/**
* Fired after the container screen's foreground layer and elements are drawn, but
* before rendering the tooltips and the item stack being dragged by the player.
*
* <p>This can be used for rendering elements that must be above other screen elements, but
* below tooltips and the dragged stack, such as slot or item stack specific overlays.</p>
*
* <p>This event is not {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.</p>
*
* <p>This event is fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
public static class Foreground extends Render
{
@ApiStatus.Internal
public Foreground(AbstractContainerScreen<?> guiContainer, GuiGraphics guiGraphics, int mouseX, int mouseY)
{
super(guiContainer, guiGraphics, mouseX, mouseY);
}
}
/**
* Fired after the container screen's background layer and elements are drawn.
* This can be used for rendering new background elements.
*
* <p>This event is not {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.</p>
*
* <p>This event is fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
public static class Background extends Render
{
@ApiStatus.Internal
public Background(AbstractContainerScreen<?> guiContainer, GuiGraphics guiGraphics, int mouseX, int mouseY)
{
super(guiContainer, guiGraphics, mouseX, mouseY);
}
}
}
}

View File

@@ -0,0 +1,221 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client.event;
import com.mojang.blaze3d.platform.Window;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.components.LerpingBossEvent;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.eventbus.api.Cancelable;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.fml.LogicalSide;
import org.jetbrains.annotations.ApiStatus;
import java.util.ArrayList;
/**
* Fired when an overlay is about to be rendered to the screen to allow the user to modify it.
*
* @see BossEventProgress
* @see DebugText
* @see Chat
*/
public abstract class CustomizeGuiOverlayEvent extends Event
{
private final Window window;
private final GuiGraphics guiGraphics;
private final float partialTick;
@ApiStatus.Internal
protected CustomizeGuiOverlayEvent(Window window, GuiGraphics guiGraphics, float partialTick)
{
this.window = window;
this.guiGraphics = guiGraphics;
this.partialTick = partialTick;
}
public Window getWindow()
{
return window;
}
public GuiGraphics getGuiGraphics()
{
return guiGraphics;
}
public float getPartialTick()
{
return partialTick;
}
/**
* Fired <b>before</b> a boss health bar is rendered to the screen.
*
* <p>This event is {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.
* Cancelling this event will prevent the given bar from rendering.</p>
*
* <p>This event is fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
@Cancelable
public static class BossEventProgress extends CustomizeGuiOverlayEvent
{
private final LerpingBossEvent bossEvent;
private final int x;
private final int y;
private int increment;
@ApiStatus.Internal
public BossEventProgress(Window window, GuiGraphics guiGraphics, float partialTick, LerpingBossEvent bossEvent, int x, int y, int increment)
{
super(window, guiGraphics, partialTick);
this.bossEvent = bossEvent;
this.x = x;
this.y = y;
this.increment = increment;
}
/**
* @return the boss health bar currently being rendered
*/
public LerpingBossEvent getBossEvent()
{
return bossEvent;
}
/**
* {@return the X position of the boss health bar}
*/
public int getX()
{
return x;
}
/**
* {@return the Y position of the boss health bar}
*/
public int getY()
{
return y;
}
/**
* {@return the Y position increment before rendering the next boss health bar}
*/
public int getIncrement()
{
return increment;
}
/**
* Sets the Y position increment before rendering the next boss health bar.
*
* @param increment the new Y position increment
*/
public void setIncrement(int increment)
{
this.increment = increment;
}
}
/**
* Fired <b>before</b> textual information is rendered to the debug screen.
* This can be used to add or remove text information.
*
* <p>This event is not {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.</p>
*
* <p>This event is fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
public static class DebugText extends CustomizeGuiOverlayEvent
{
private final ArrayList<String> left;
private final ArrayList<String> right;
@ApiStatus.Internal
public DebugText(Window window, GuiGraphics guiGraphics, float partialTick, ArrayList<String> left, ArrayList<String> right)
{
super(window, guiGraphics, partialTick);
this.left = left;
this.right = right;
}
/**
* @return the modifiable list of text to render on the left side
*/
public ArrayList<String> getLeft()
{
return left;
}
/**
* @return the modifiable list of text to render on the right side
*/
public ArrayList<String> getRight()
{
return right;
}
}
/**
* Fired <b>before</b> the chat messages overlay is rendered to the screen.
*
* <p>This event is not {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.<p/>
*
* <p>This event is fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
public static class Chat extends CustomizeGuiOverlayEvent
{
private int posX;
private int posY;
@ApiStatus.Internal
public Chat(Window window, GuiGraphics guiGraphics, float partialTick, int posX, int posY)
{
super(window, guiGraphics, partialTick);
this.setPosX(posX);
this.setPosY(posY);
}
/**
* @return the X position of the chat messages overlay
*/
public int getPosX()
{
return posX;
}
/**
* Sets the new X position for rendering the chat messages overlay
*
* @param posX the new X position
*/
public void setPosX(int posX)
{
this.posX = posX;
}
/**
* @return the Y position of the chat messages overlay
*/
public int getPosY()
{
return posY;
}
/**
* Sets the new Y position for rendering the chat messages overlay
*
* @param posY the new y position
*/
public void setPosY(int posY)
{
this.posY = posY;
}
}
}

View File

@@ -0,0 +1,272 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client.event;
import com.google.common.collect.ImmutableMap;
import net.minecraft.client.model.EntityModel;
import net.minecraft.client.model.SkullModel;
import net.minecraft.client.model.SkullModelBase;
import net.minecraft.client.model.geom.EntityModelSet;
import net.minecraft.client.model.geom.ModelLayerLocation;
import net.minecraft.client.model.geom.ModelLayers;
import net.minecraft.client.model.geom.builders.LayerDefinition;
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider;
import net.minecraft.client.renderer.blockentity.BlockEntityRenderers;
import net.minecraft.client.renderer.entity.EntityRenderer;
import net.minecraft.client.renderer.entity.EntityRendererProvider;
import net.minecraft.client.renderer.entity.EntityRenderers;
import net.minecraft.client.renderer.entity.LivingEntityRenderer;
import net.minecraft.client.renderer.entity.player.PlayerRenderer;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.block.SkullBlock;
import net.minecraft.world.level.block.SkullBlock.Type;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraftforge.client.ForgeHooksClient;
import net.minecraftforge.eventbus.api.Cancelable;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.fml.LogicalSide;
import net.minecraftforge.fml.event.IModBusEvent;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
import java.util.Map;
import java.util.Set;
import java.util.function.Supplier;
/**
* Fired for on different events/actions relating to {@linkplain EntityRenderer entity renderers}.
* See the various subclasses for listening to different events.
*
* <p>These events are fired on the {@linkplain FMLJavaModLoadingContext#getModEventBus() mod-specific event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*
* @see EntityRenderersEvent.RegisterLayerDefinitions
* @see EntityRenderersEvent.RegisterRenderers
* @see EntityRenderersEvent.AddLayers
*/
public abstract class EntityRenderersEvent extends Event implements IModBusEvent
{
@ApiStatus.Internal
protected EntityRenderersEvent()
{
}
/**
* Fired for registering layer definitions at the appropriate time.
*
* <p>This event is not {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.</p>
*
* <p>This event is fired on the {@linkplain FMLJavaModLoadingContext#getModEventBus() mod-specific event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
public static class RegisterLayerDefinitions extends EntityRenderersEvent
{
@ApiStatus.Internal
public RegisterLayerDefinitions()
{
}
/**
* Registers a layer definition supplier with the given {@link ModelLayerLocation}.
* These will be inserted into the main layer definition map for entity model layers at the appropriate time.
*
* @param layerLocation the model layer location, which should be used in conjunction with
* {@link EntityRendererProvider.Context#bakeLayer(ModelLayerLocation)}
* @param supplier a supplier to create a {@link LayerDefinition}, generally a static method reference in
* the entity model class
*/
public void registerLayerDefinition(ModelLayerLocation layerLocation, Supplier<LayerDefinition> supplier)
{
ForgeHooksClient.registerLayerDefinition(layerLocation, supplier);
}
}
/**
* Fired for registering entity and block entity renderers at the appropriate time.
* For registering entity renderer layers to existing entity renderers (whether vanilla or registered through this
* event), listen for the {@link AddLayers} event instead.
*
* <p>This event is not {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.</p>
*
* <p>This event is fired on the {@linkplain FMLJavaModLoadingContext#getModEventBus() mod-specific event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
public static class RegisterRenderers extends EntityRenderersEvent
{
@ApiStatus.Internal
public RegisterRenderers()
{
}
/**
* Registers an entity renderer for the given entity type.
*
* @param entityType the entity type to register a renderer for
* @param entityRendererProvider the renderer provider
*/
public <T extends Entity> void registerEntityRenderer(EntityType<? extends T> entityType, EntityRendererProvider<T> entityRendererProvider)
{
EntityRenderers.register(entityType, entityRendererProvider);
}
/**
* Registers a block entity renderer for the given block entity type.
*
* @param blockEntityType the block entity type to register a renderer for
* @param blockEntityRendererProvider the renderer provider
*/
public <T extends BlockEntity> void registerBlockEntityRenderer(BlockEntityType<? extends T> blockEntityType, BlockEntityRendererProvider<T> blockEntityRendererProvider)
{
BlockEntityRenderers.register(blockEntityType, blockEntityRendererProvider);
}
}
/**
* Fired for registering entity renderer layers at the appropriate time, after the entity and player renderers maps
* have been created.
*
* <p>This event is not {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.</p>
*
* <p>This event is fired on the {@linkplain FMLJavaModLoadingContext#getModEventBus() mod-specific event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
public static class AddLayers extends EntityRenderersEvent
{
private final Map<EntityType<?>, EntityRenderer<?>> renderers;
private final Map<String, EntityRenderer<? extends Player>> skinMap;
private final EntityRendererProvider.Context context;
@ApiStatus.Internal
public AddLayers(Map<EntityType<?>, EntityRenderer<?>> renderers, Map<String, EntityRenderer<? extends Player>> playerRenderers, EntityRendererProvider.Context context)
{
this.renderers = renderers;
this.skinMap = playerRenderers;
this.context = context;
}
/**
* {@return the set of player skin names which have a renderer}
* <p>
* Minecraft provides two default skin names: {@code default} for the
* {@linkplain ModelLayers#PLAYER regular player model} and {@code slim} for the
* {@linkplain ModelLayers#PLAYER_SLIM slim player model}.
*/
public Set<String> getSkins()
{
return skinMap.keySet();
}
/**
* Returns a player skin renderer for the given skin name.
*
* @param skinName the name of the skin to get the renderer for
* @param <R> the type of the skin renderer, usually {@link PlayerRenderer}
* @return the skin renderer, or {@code null} if no renderer is registered for that skin name
* @see #getSkins()
*/
@SuppressWarnings("unchecked")
public <R extends EntityRenderer<? extends Player>> @Nullable R getPlayerSkin(String skinName)
{
return (R) skinMap.get(skinName);
}
/** @deprecated Use getEntitySkin, return type down graded to EntityRenderer instead of LivingEntityRenderer */
@Nullable
@SuppressWarnings("unchecked")
public <R extends LivingEntityRenderer<? extends Player, ? extends EntityModel<? extends Player>>> R getSkin(String skinName)
{
return skinMap.get(skinName) instanceof LivingEntityRenderer<?,?> renderer ? (R) renderer : null;
}
/**
* Returns an entity renderer for the given entity type.
*
* @param entityType the entity type to return a renderer for
* @param <T> the type of entity the renderer is for
* @param <R> the type of the renderer
* @return the renderer, or {@code null} if no renderer is registered for that entity type
*/
@SuppressWarnings("unchecked")
public <T extends Entity, R extends EntityRenderer<T>> @Nullable R getEntityRenderer(EntityType<? extends T> entityType)
{
return (R) renderers.get(entityType);
}
/** @deprecated Use getEntityRenderer, return type down graded to EntityRenderer instead of LivingEntityRenderer */
@Nullable
@SuppressWarnings("unchecked")
public <T extends LivingEntity, R extends LivingEntityRenderer<T, ? extends EntityModel<T>>> R getRenderer(EntityType<? extends T> entityType)
{
return renderers.get(entityType) instanceof LivingEntityRenderer<?,?> renderer ? (R) renderer : null;
}
/**
* {@return the set of entity models}
*/
public EntityModelSet getEntityModels()
{
return this.context.getModelSet();
}
/**
* {@return the context for the entity renderer provider}
*/
public EntityRendererProvider.Context getContext()
{
return context;
}
}
/**
* Fired for registering additional {@linkplain net.minecraft.client.model.SkullModelBase skull models} at the appropriate time.
*
* <p>This event is not {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.</p>
*
* <p>This event is fired on the {@linkplain FMLJavaModLoadingContext#getModEventBus() mod-specific event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
public static class CreateSkullModels extends EntityRenderersEvent
{
private final ImmutableMap.Builder<Type, SkullModelBase> builder;
private final EntityModelSet entityModelSet;
@ApiStatus.Internal
public CreateSkullModels(ImmutableMap.Builder<Type, SkullModelBase> builder, EntityModelSet entityModelSet)
{
this.builder = builder;
this.entityModelSet = entityModelSet;
}
/**
* {@return the set of entity models}
*/
public EntityModelSet getEntityModelSet()
{
return entityModelSet;
}
/**
* Registers the constructor for a skull block with the given {@link SkullBlock.Type}.
* These will be inserted into the maps used by the item, entity, and block model renderers at the appropriate
* time.
*
* @param type a unique skull type; an exception will be thrown later if multiple mods (including vanilla)
* register models for the same type
* @param model the skull model instance. A typical implementation will simply bake a model using
* {@link EntityModelSet#bakeLayer(ModelLayerLocation)} and pass it to the constructor for
* {@link SkullModel}.
*/
public void registerSkullModel(SkullBlock.Type type, SkullModelBase model)
{
builder.put(type, model);
}
}
}

View File

@@ -0,0 +1,393 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client.event;
import com.mojang.blaze3d.platform.InputConstants;
import net.minecraft.client.KeyMapping;
import net.minecraft.world.InteractionHand;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.eventbus.api.Cancelable;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.fml.LogicalSide;
import org.jetbrains.annotations.ApiStatus;
import org.lwjgl.glfw.GLFW;
/**
* Fired when an input is detected from the user's input devices.
* See the various subclasses to listen for specific devices and inputs.
*
* @see InputEvent.MouseButton
* @see MouseScrollingEvent
* @see Key
* @see InteractionKeyMappingTriggered
*/
public abstract class InputEvent extends Event
{
@ApiStatus.Internal
protected InputEvent()
{
}
/**
* Fired when a mouse button is pressed/released. Sub-events get fired {@link Pre before} and {@link Post after} this happens.
*
* <p>These events are fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*
* @see <a href="https://www.glfw.org/docs/latest/input_guide.html#input_mouse_button" target="_top">the online GLFW documentation</a>
* @see Pre
* @see Post
*/
public static abstract class MouseButton extends InputEvent
{
private final int button;
private final int action;
private final int modifiers;
@ApiStatus.Internal
protected MouseButton(int button, int action, int modifiers)
{
this.button = button;
this.action = action;
this.modifiers = modifiers;
}
/**
* {@return the mouse button's input code}
*
* @see GLFW mouse constants starting with 'GLFW_MOUSE_BUTTON_'
* @see <a href="https://www.glfw.org/docs/latest/group__buttons.html" target="_top">the online GLFW documentation</a>
*/
public int getButton()
{
return this.button;
}
/**
* {@return the mouse button's action}
*
* @see InputConstants#PRESS
* @see InputConstants#RELEASE
*/
public int getAction()
{
return this.action;
}
/**
* {@return a bit field representing the active modifier keys}
*
* @see InputConstants#MOD_CONTROL CTRL modifier key bit
* @see GLFW#GLFW_MOD_SHIFT SHIFT modifier key bit
* @see GLFW#GLFW_MOD_ALT ALT modifier key bit
* @see GLFW#GLFW_MOD_SUPER SUPER modifier key bit
* @see GLFW#GLFW_KEY_CAPS_LOCK CAPS LOCK modifier key bit
* @see GLFW#GLFW_KEY_NUM_LOCK NUM LOCK modifier key bit
* @see <a href="https://www.glfw.org/docs/latest/group__mods.html" target="_top">the online GLFW documentation</a>
*/
public int getModifiers()
{
return this.modifiers;
}
/**
* Fired when a mouse button is pressed/released, <b>before</b> being processed by vanilla.
*
* <p>This event is {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.
* If the event is cancelled, then the mouse event will not be processed by vanilla (e.g. keymappings and screens) </p>
*
* <p>This event is fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*
* @see <a href="https://www.glfw.org/docs/latest/input_guide.html#input_mouse_button" target="_top">the online GLFW documentation</a>
*/
@Cancelable
public static class Pre extends MouseButton
{
@ApiStatus.Internal
public Pre(int button, int action, int modifiers)
{
super(button, action, modifiers);
}
}
/**
* Fired when a mouse button is pressed/released, <b>after</b> processing.
*
* <p>This event is not {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.</p>
*
* <p>This event is fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*
* @see <a href="https://www.glfw.org/docs/latest/input_guide.html#input_mouse_button" target="_top">the online GLFW documentation</a>
*/
public static class Post extends MouseButton
{
@ApiStatus.Internal
public Post(int button, int action, int modifiers)
{
super(button, action, modifiers);
}
}
}
/**
* Fired when a mouse scroll wheel is used outside of a screen and a player is loaded, <b>before</b> being
* processed by vanilla.
*
* <p>This event is {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.
* If the event is cancelled, then the mouse scroll event will not be processed further.</p>
*
* <p>This event is fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*
* @see <a href="https://www.glfw.org/docs/latest/input_guide.html#input_mouse_button" target="_top">the online GLFW documentation</a>
*/
@Cancelable
public static class MouseScrollingEvent extends InputEvent
{
private final double scrollDelta;
private final double mouseX;
private final double mouseY;
private final boolean leftDown;
private final boolean middleDown;
private final boolean rightDown;
@ApiStatus.Internal
public MouseScrollingEvent(double scrollDelta, boolean leftDown, boolean middleDown, boolean rightDown, double mouseX, double mouseY)
{
this.scrollDelta = scrollDelta;
this.leftDown = leftDown;
this.middleDown = middleDown;
this.rightDown = rightDown;
this.mouseX = mouseX;
this.mouseY = mouseY;
}
/**
* {@return the amount of change / delta of the mouse scroll}
*/
public double getScrollDelta()
{
return this.scrollDelta;
}
/**
* {@return {@code true} if the left mouse button is pressed}
*/
public boolean isLeftDown()
{
return this.leftDown;
}
/**
* {@return {@code true} if the right mouse button is pressed}
*/
public boolean isRightDown()
{
return this.rightDown;
}
/**
* {@return {@code true} if the middle mouse button is pressed}
*/
public boolean isMiddleDown()
{
return this.middleDown;
}
/**
* {@return the X position of the mouse cursor}
*/
public double getMouseX()
{
return this.mouseX;
}
/**
* {@return the Y position of the mouse cursor}
*/
public double getMouseY()
{
return this.mouseY;
}
}
/**
* Fired when a keyboard key input occurs, such as pressing, releasing, or repeating a key.
*
* <p>This event is not {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.</p>
*
* <p>This event is fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
public static class Key extends InputEvent
{
private final int key;
private final int scanCode;
private final int action;
private final int modifiers;
@ApiStatus.Internal
public Key(int key, int scanCode, int action, int modifiers)
{
this.key = key;
this.scanCode = scanCode;
this.action = action;
this.modifiers = modifiers;
}
/**
* {@return the {@code GLFW} (platform-agnostic) key code}
*
* @see InputConstants input constants starting with {@code KEY_}
* @see GLFW key constants starting with {@code GLFW_KEY_}
* @see <a href="https://www.glfw.org/docs/latest/group__keys.html" target="_top">the online GLFW documentation</a>
*/
public int getKey()
{
return this.key;
}
/**
* {@return the platform-specific scan code}
* <p>
* The scan code is unique for every key, regardless of whether it has a key code.
* Scan codes are platform-specific but consistent over time, so keys will have different scan codes depending
* on the platform but they are safe to save to disk as custom key bindings.
*
* @see InputConstants#getKey(int, int)
*/
public int getScanCode()
{
return this.scanCode;
}
/**
* {@return the mouse button's action}
*
* @see InputConstants#PRESS
* @see InputConstants#RELEASE
* @see InputConstants#REPEAT
*/
public int getAction()
{
return this.action;
}
/**
* {@return a bit field representing the active modifier keys}
*
* @see InputConstants#MOD_CONTROL CTRL modifier key bit
* @see GLFW#GLFW_MOD_SHIFT SHIFT modifier key bit
* @see GLFW#GLFW_MOD_ALT ALT modifier key bit
* @see GLFW#GLFW_MOD_SUPER SUPER modifier key bit
* @see GLFW#GLFW_KEY_CAPS_LOCK CAPS LOCK modifier key bit
* @see GLFW#GLFW_KEY_NUM_LOCK NUM LOCK modifier key bit
* @see <a href="https://www.glfw.org/docs/latest/group__mods.html" target="_top">the online GLFW documentation</a>
*/
public int getModifiers()
{
return this.modifiers;
}
}
/**
* Fired when a keymapping that by default involves clicking the mouse buttons is triggered.
*
* <p>The key bindings that trigger this event are:</p>
* <ul>
* <li><b>Use Item</b> - defaults to <em>left mouse click</em></li>
* <li><b>Pick Block</b> - defaults to <em>middle mouse click</em></li>
* <li><b>Attack</b> - defaults to <em>right mouse click</em></li>
* </ul>
*
* <p>This event is {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.
* If this event is cancelled, then the keymapping's action is not processed further, and the hand will be swung
* according to {@link #shouldSwingHand()}.</p>
*
* <p>This event is fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
@Cancelable
public static class InteractionKeyMappingTriggered extends InputEvent
{
private final int button;
private final KeyMapping keyMapping;
private final InteractionHand hand;
private boolean handSwing = true;
@ApiStatus.Internal
public InteractionKeyMappingTriggered(int button, KeyMapping keyMapping, InteractionHand hand)
{
this.button = button;
this.keyMapping = keyMapping;
this.hand = hand;
}
/**
* Sets whether to swing the hand. This takes effect whether or not the event is cancelled.
*
* @param value whether to swing the hand
*/
public void setSwingHand(boolean value)
{
handSwing = value;
}
/**
* {@return whether to swing the hand; always takes effect, regardless of cancellation}
*/
public boolean shouldSwingHand()
{
return handSwing;
}
/**
* {@return the hand that caused the input}
* <p>
* The event will be called for both hands if this is a use item input regardless
* of both event's cancellation.
* Will always be {@link InteractionHand#MAIN_HAND} if this is an attack or pick block input.
*/
public InteractionHand getHand()
{
return hand;
}
/**
* {@return {@code true} if the mouse button is the left mouse button}
*/
public boolean isAttack()
{
return button == 0;
}
/**
* {@return {@code true} if the mouse button is the right mouse button}
*/
public boolean isUseItem()
{
return button == 1;
}
/**
* {@return {@code true} if the mouse button is the middle mouse button}
*/
public boolean isPickBlock()
{
return button == 2;
}
/**
* {@return the key mapping which triggered this event}
*/
public KeyMapping getKeyMapping()
{
return keyMapping;
}
}
}

View File

@@ -0,0 +1,186 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client.event;
import com.google.common.base.Preconditions;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.ModelBakery;
import net.minecraft.client.resources.model.ModelManager;
import net.minecraft.resources.ResourceLocation;
import net.minecraftforge.client.model.geometry.IGeometryLoader;
import net.minecraftforge.eventbus.api.Cancelable;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.fml.LogicalSide;
import net.minecraftforge.fml.ModLoadingContext;
import net.minecraftforge.fml.event.IModBusEvent;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import org.jetbrains.annotations.ApiStatus;
import java.util.Map;
import java.util.Set;
/**
* Houses events related to models.
*/
public abstract class ModelEvent extends Event
{
@ApiStatus.Internal
protected ModelEvent()
{
}
/**
* Fired while the {@link ModelManager} is reloading models, after the model registry is set up, but before it's
* passed to the {@link net.minecraft.client.renderer.block.BlockModelShaper} for caching.
*
* <p>
* This event is fired from a worker thread and it is therefore not safe to access anything outside the
* model registry and {@link ModelBakery} provided in this event.<br>
* The {@link ModelManager} firing this event is not fully set up with the latest data when this event fires and
* must therefore not be accessed in this event.
* </p>
*
* <p>This event is not {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.</p>
*
* <p>This event is fired on the {@linkplain FMLJavaModLoadingContext#getModEventBus() mod-specific event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
public static class ModifyBakingResult extends ModelEvent implements IModBusEvent
{
private final Map<ResourceLocation, BakedModel> models;
private final ModelBakery modelBakery;
@ApiStatus.Internal
public ModifyBakingResult(Map<ResourceLocation, BakedModel> models, ModelBakery modelBakery)
{
this.models = models;
this.modelBakery = modelBakery;
}
/**
* @return the modifiable registry map of models and their model names
*/
public Map<ResourceLocation, BakedModel> getModels()
{
return models;
}
/**
* @return the model loader
*/
public ModelBakery getModelBakery()
{
return modelBakery;
}
}
/**
* Fired when the {@link ModelManager} is notified of the resource manager reloading.
* Called after the model registry is set up and cached in the {@link net.minecraft.client.renderer.block.BlockModelShaper}.<br>
* The model registry given by this event is unmodifiable. To modify the model registry, use
* {@link ModelEvent.ModifyBakingResult} instead.
*
* <p>This event is not {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.</p>
*
* <p>This event is fired on the {@linkplain FMLJavaModLoadingContext#getModEventBus() mod-specific event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
public static class BakingCompleted extends ModelEvent implements IModBusEvent
{
private final ModelManager modelManager;
private final Map<ResourceLocation, BakedModel> models;
private final ModelBakery modelBakery;
@ApiStatus.Internal
public BakingCompleted(ModelManager modelManager, Map<ResourceLocation, BakedModel> models, ModelBakery modelBakery)
{
this.modelManager = modelManager;
this.models = models;
this.modelBakery = modelBakery;
}
/**
* @return the model manager
*/
public ModelManager getModelManager()
{
return modelManager;
}
/**
* @return an unmodifiable view of the registry map of models and their model names
*/
public Map<ResourceLocation, BakedModel> getModels()
{
return models;
}
/**
* @return the model loader
*/
public ModelBakery getModelBakery()
{
return modelBakery;
}
}
/**
* Fired when the {@link net.minecraft.client.resources.model.ModelBakery} is notified of the resource manager reloading.
* Allows developers to register models to be loaded, along with their dependencies.
*
* <p>This event is not {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.</p>
*
* <p>This event is fired on the {@linkplain FMLJavaModLoadingContext#getModEventBus() mod-specific event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
public static class RegisterAdditional extends ModelEvent implements IModBusEvent
{
private final Set<ResourceLocation> models;
@ApiStatus.Internal
public RegisterAdditional(Set<ResourceLocation> models)
{
this.models = models;
}
/**
* Registers a model to be loaded, along with its dependencies.
*/
public void register(ResourceLocation model)
{
models.add(model);
}
}
/**
* Allows users to register their own {@link IGeometryLoader geometry loaders} for use in block/item models.
*
* <p>This event is not {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.</p>
*
* <p>This event is fired on the {@linkplain FMLJavaModLoadingContext#getModEventBus() mod-specific event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
public static class RegisterGeometryLoaders extends ModelEvent implements IModBusEvent
{
private final Map<ResourceLocation, IGeometryLoader<?>> loaders;
@ApiStatus.Internal
public RegisterGeometryLoaders(Map<ResourceLocation, IGeometryLoader<?>> loaders)
{
this.loaders = loaders;
}
/**
* Registers a new geometry loader.
*/
public void register(String name, IGeometryLoader<?> loader)
{
var key = new ResourceLocation(ModLoadingContext.get().getActiveNamespace(), name);
Preconditions.checkArgument(!loaders.containsKey(key), "Geometry loader already registered: " + key);
loaders.put(key, loader);
}
}
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client.event;
import net.minecraft.client.player.Input;
import net.minecraft.world.entity.player.Player;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.entity.player.PlayerEvent;
import net.minecraftforge.eventbus.api.Cancelable;
import net.minecraftforge.fml.LogicalSide;
import org.jetbrains.annotations.ApiStatus;
/**
* <p>Fired after the player's movement inputs are updated.</p>
*
* <p>This event is not {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.</p>
*
* <p>This event is fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
public class MovementInputUpdateEvent extends PlayerEvent
{
private final Input input;
@ApiStatus.Internal
public MovementInputUpdateEvent(Player player, Input input)
{
super(player);
this.input = input;
}
/**
* {@return the player's movement inputs}
*/
public Input getInput()
{
return input;
}
}

View File

@@ -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.minecraft.world.item.crafting.RecipeManager;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.eventbus.api.Cancelable;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.fml.LogicalSide;
import org.jetbrains.annotations.ApiStatus;
/**
* Fired when the {@link RecipeManager} has received and synced the recipes from the server to the client.
*
* <p>This event is not {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.</p>
*
* <p>This event is fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
public class RecipesUpdatedEvent extends Event
{
private final RecipeManager recipeManager;
@ApiStatus.Internal
public RecipesUpdatedEvent(RecipeManager recipeManager)
{
this.recipeManager = recipeManager;
}
/**
* {@return the recipe manager}
*/
public RecipeManager getRecipeManager()
{
return recipeManager;
}
}

View File

@@ -0,0 +1,64 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client.event;
import com.mojang.brigadier.CommandDispatcher;
import net.minecraft.commands.CommandBuildContext;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.arguments.ObjectiveArgument;
import net.minecraft.commands.arguments.ResourceLocationArgument;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.eventbus.api.Cancelable;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.fml.LogicalSide;
import org.jetbrains.annotations.ApiStatus;
/**
* Fired to allow mods to register client commands.
*
* <p>Some command arguments behave differently for the client commands dispatcher:</p>
* <ul>
* <li>{@link ResourceLocationArgument#getAdvancement(com.mojang.brigadier.context.CommandContext, String)} only returns
* advancements that are shown on the advancements screen.
* <li>{@link ObjectiveArgument#getObjective(com.mojang.brigadier.context.CommandContext, String)} only returns
* objectives that are displayed to the player.
* </ul>
*
* <p>This event is not {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.</p>
*
* <p>This event is fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*
* @see net.minecraftforge.event.RegisterCommandsEvent
*/
public class RegisterClientCommandsEvent extends Event
{
private final CommandDispatcher<CommandSourceStack> dispatcher;
private final CommandBuildContext context;
@ApiStatus.Internal
public RegisterClientCommandsEvent(CommandDispatcher<CommandSourceStack> dispatcher, CommandBuildContext context)
{
this.dispatcher = dispatcher;
this.context = context;
}
/**
* {@return the command dispatcher for registering commands to be executed on the client}
*/
public CommandDispatcher<CommandSourceStack> getDispatcher()
{
return dispatcher;
}
/**
* {@return the context to build the commands for}
*/
public CommandBuildContext getBuildContext()
{
return context;
}
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client.event;
import net.minecraft.client.Minecraft;
import net.minecraft.server.packs.resources.PreparableReloadListener;
import net.minecraft.server.packs.resources.ReloadableResourceManager;
import net.minecraftforge.event.AddReloadListenerEvent;
import net.minecraftforge.eventbus.api.Cancelable;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.fml.LogicalSide;
import net.minecraftforge.fml.event.IModBusEvent;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import org.jetbrains.annotations.ApiStatus;
/**
* Fired to allow mods to register their reload listeners on the client-side resource manager.
* This event is fired once during the construction of the {@link Minecraft} instance.
*
* <p>For registering reload listeners on the server-side resource manager, see {@link AddReloadListenerEvent}.</p>
*
* <p>This event is not {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.</p>
*
* <p>This event is fired on the {@linkplain FMLJavaModLoadingContext#getModEventBus() mod-specific event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
public class RegisterClientReloadListenersEvent extends Event implements IModBusEvent
{
private final ReloadableResourceManager resourceManager;
@ApiStatus.Internal
public RegisterClientReloadListenersEvent(ReloadableResourceManager resourceManager)
{
this.resourceManager = resourceManager;
}
/**
* Registers the given reload listener to the client-side resource manager.
*
* @param reloadListener the reload listener
*/
public void registerReloadListener(PreparableReloadListener reloadListener)
{
resourceManager.registerReloadListener(reloadListener);
}
}

View File

@@ -0,0 +1,47 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client.event;
import net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipComponent;
import net.minecraft.world.inventory.tooltip.TooltipComponent;
import net.minecraftforge.eventbus.api.Cancelable;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.fml.LogicalSide;
import net.minecraftforge.fml.event.IModBusEvent;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import org.jetbrains.annotations.ApiStatus;
import java.util.Map;
import java.util.function.Function;
/**
* Allows users to register custom {@link net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipComponent}
* factories for their {@link net.minecraft.world.inventory.tooltip.TooltipComponent} types.
*
* <p>This event is not {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.
*
* <p>This event is fired on the {@linkplain FMLJavaModLoadingContext#getModEventBus() mod-specific event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
public class RegisterClientTooltipComponentFactoriesEvent extends Event implements IModBusEvent
{
private final Map<Class<? extends TooltipComponent>, Function<TooltipComponent, ClientTooltipComponent>> factories;
@ApiStatus.Internal
public RegisterClientTooltipComponentFactoriesEvent(Map<Class<? extends TooltipComponent>, Function<TooltipComponent, ClientTooltipComponent>> factories)
{
this.factories = factories;
}
/**
* Registers a {@link ClientTooltipComponent} factory for a {@link TooltipComponent}.
*/
@SuppressWarnings("unchecked")
public <T extends TooltipComponent> void register(Class<T> type, Function<? super T, ? extends ClientTooltipComponent> factory)
{
factories.put(type, (Function<TooltipComponent, ClientTooltipComponent>) factory);
}
}

View File

@@ -0,0 +1,155 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client.event;
import com.google.common.collect.ImmutableList;
import net.minecraft.client.color.block.BlockColor;
import net.minecraft.client.color.block.BlockColors;
import net.minecraft.client.color.item.ItemColor;
import net.minecraft.client.color.item.ItemColors;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.ColorResolver;
import net.minecraft.world.level.ItemLike;
import net.minecraftforge.eventbus.api.Cancelable;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.fml.LogicalSide;
import net.minecraftforge.fml.event.IModBusEvent;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import org.jetbrains.annotations.ApiStatus;
/**
* Fired for registering block and item color handlers at the appropriate time.
* See the two subclasses for registering block or item color handlers.
*
* <p>These events are fired on the {@linkplain FMLJavaModLoadingContext#getModEventBus() mod-specific event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*
* @see RegisterColorHandlersEvent.Block
* @see RegisterColorHandlersEvent.Item
*/
public abstract class RegisterColorHandlersEvent extends Event implements IModBusEvent
{
@ApiStatus.Internal
protected RegisterColorHandlersEvent()
{
}
/**
* Fired for registering block color handlers.
*
* <p>This event is not {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.</p>
*
* <p>This event is fired on the {@linkplain FMLJavaModLoadingContext#getModEventBus() mod-specific event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
public static class Block extends RegisterColorHandlersEvent
{
private final BlockColors blockColors;
@ApiStatus.Internal
public Block(BlockColors blockColors)
{
this.blockColors = blockColors;
}
/**
* {@return the block colors registry}
*
* @see BlockColors#register(BlockColor, net.minecraft.world.level.block.Block...)
*/
public BlockColors getBlockColors()
{
return blockColors;
}
/**
* Registers a {@link BlockColor} instance for a set of blocks.
*
* @param blockColor The color provider
* @param blocks The blocks
*/
public void register(BlockColor blockColor, net.minecraft.world.level.block.Block... blocks)
{
blockColors.register(blockColor, blocks);
}
}
/**
* Fired for registering item color handlers.
*
* <p>The block colors should only be used for referencing or delegating item colors to their respective block
* colors. Use {@link RegisterColorHandlersEvent.Block} for registering your block color handlers.</p>
*
* <p>This event is not {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.</p>
*
* <p>This event is fired on the {@linkplain FMLJavaModLoadingContext#getModEventBus() mod-specific event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
public static class Item extends RegisterColorHandlersEvent
{
private final ItemColors itemColors;
private final BlockColors blockColors;
@ApiStatus.Internal
public Item(ItemColors itemColors, BlockColors blockColors)
{
this.itemColors = itemColors;
this.blockColors = blockColors;
}
/**
* {@return the item colors registry}
*
* @see ItemColors#register(ItemColor, ItemLike...)
*/
public ItemColors getItemColors()
{
return itemColors;
}
/**
* {@return the block colors registry}
* This should only be used for referencing or delegating item colors to their respective block colors.
*/
public BlockColors getBlockColors()
{
return blockColors;
}
/**
* Registers a {@link ItemColor} instance for a set of blocks.
*
* @param itemColor The color provider
* @param items The items
*/
public void register(ItemColor itemColor, ItemLike... items)
{
itemColors.register(itemColor, items);
}
}
/**
* Allows registration of custom {@link ColorResolver} implementations to be used with
* {@link net.minecraft.world.level.BlockAndTintGetter#getBlockTint(BlockPos, ColorResolver)}.
*/
public static class ColorResolvers extends RegisterColorHandlersEvent
{
private final ImmutableList.Builder<ColorResolver> builder;
@ApiStatus.Internal
public ColorResolvers(ImmutableList.Builder<ColorResolver> builder)
{
this.builder = builder;
}
public void register(ColorResolver resolver)
{
this.builder.add(resolver);
}
}
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client.event;
import net.minecraft.client.renderer.DimensionSpecialEffects;
import net.minecraft.resources.ResourceLocation;
import net.minecraftforge.eventbus.api.Cancelable;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.fml.LogicalSide;
import net.minecraftforge.fml.event.IModBusEvent;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import org.jetbrains.annotations.ApiStatus;
import java.util.Map;
/**
* Allows users to register custom {@link DimensionSpecialEffects} for their dimensions.
*
* <p>This event is not {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.
*
* <p>This event is fired on the {@linkplain FMLJavaModLoadingContext#getModEventBus() mod-specific event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
public class RegisterDimensionSpecialEffectsEvent extends Event implements IModBusEvent
{
private final Map<ResourceLocation, DimensionSpecialEffects> effects;
@ApiStatus.Internal
public RegisterDimensionSpecialEffectsEvent(Map<ResourceLocation, DimensionSpecialEffects> effects)
{
this.effects = effects;
}
/**
* Registers the effects for a given dimension type.
*/
public void register(ResourceLocation dimensionType, DimensionSpecialEffects effects)
{
this.effects.put(dimensionType, effects);
}
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client.event;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.EntityType;
import net.minecraftforge.eventbus.api.Cancelable;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.fml.LogicalSide;
import net.minecraftforge.fml.event.IModBusEvent;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import org.jetbrains.annotations.ApiStatus;
import java.util.Map;
/**
* Allows users to register custom shaders to be used when the player spectates a certain kind of entity.
* Vanilla examples of this are the green effect for creepers and the invert effect for endermen.
*
* <p>This event is not {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.
*
* <p>This event is fired on the {@linkplain FMLJavaModLoadingContext#getModEventBus() mod-specific event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
public class RegisterEntitySpectatorShadersEvent extends Event implements IModBusEvent
{
private final Map<EntityType<?>, ResourceLocation> shaders;
@ApiStatus.Internal
public RegisterEntitySpectatorShadersEvent(Map<EntityType<?>, ResourceLocation> shaders)
{
this.shaders = shaders;
}
/**
* Registers a spectator shader for a given entity type.
*/
public void register(EntityType<?> entityType, ResourceLocation shader)
{
shaders.put(entityType, shader);
}
}

View File

@@ -0,0 +1,118 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client.event;
import com.google.common.base.Preconditions;
import net.minecraft.resources.ResourceLocation;
import net.minecraftforge.client.gui.overlay.IGuiOverlay;
import net.minecraftforge.client.gui.overlay.VanillaGuiOverlay;
import net.minecraftforge.eventbus.api.Cancelable;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.fml.LogicalSide;
import net.minecraftforge.fml.ModLoadingContext;
import net.minecraftforge.fml.event.IModBusEvent;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.Map;
/**
* Allows users to register custom {@link IGuiOverlay GUI overlays}.
*
* <p>This event is not {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.
*
* <p>This event is fired on the {@linkplain FMLJavaModLoadingContext#getModEventBus() mod-specific event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
public class RegisterGuiOverlaysEvent extends Event implements IModBusEvent
{
private final Map<ResourceLocation, IGuiOverlay> overlays;
private final List<ResourceLocation> orderedOverlays;
@ApiStatus.Internal
public RegisterGuiOverlaysEvent(Map<ResourceLocation, IGuiOverlay> overlays, List<ResourceLocation> orderedOverlays)
{
this.overlays = overlays;
this.orderedOverlays = orderedOverlays;
}
/**
* Registers an overlay that renders below all others.
*
* @param id A unique resource id for this overlay
* @param overlay The overlay
*/
public void registerBelowAll(@NotNull String id, @NotNull IGuiOverlay overlay)
{
register(Ordering.BEFORE, null, id, overlay);
}
/**
* Registers an overlay that renders below another.
*
* @param other The id of the overlay to render below. This must be an overlay you have already registered or a
* {@link VanillaGuiOverlay vanilla overlay}. Do not use other mods' overlays.
* @param id A unique resource id for this overlay
* @param overlay The overlay
*/
public void registerBelow(@NotNull ResourceLocation other, @NotNull String id, @NotNull IGuiOverlay overlay)
{
register(Ordering.BEFORE, other, id, overlay);
}
/**
* Registers an overlay that renders above another.
*
* @param other The id of the overlay to render above. This must be an overlay you have already registered or a
* {@link VanillaGuiOverlay vanilla overlay}. Do not use other mods' overlays.
* @param id A unique resource id for this overlay
* @param overlay The overlay
*/
public void registerAbove(@NotNull ResourceLocation other, @NotNull String id, @NotNull IGuiOverlay overlay)
{
register(Ordering.AFTER, other, id, overlay);
}
/**
* Registers an overlay that renders above all others.
*
* @param id A unique resource id for this overlay
* @param overlay The overlay
*/
public void registerAboveAll(@NotNull String id, @NotNull IGuiOverlay overlay)
{
register(Ordering.AFTER, null, id, overlay);
}
private void register(@NotNull Ordering ordering, @Nullable ResourceLocation other, @NotNull String id, @NotNull IGuiOverlay overlay)
{
var key = new ResourceLocation(ModLoadingContext.get().getActiveNamespace(), id);
Preconditions.checkArgument(!overlays.containsKey(key), "Overlay already registered: " + key);
int insertPosition;
if (other == null)
{
insertPosition = ordering == Ordering.BEFORE ? 0 : overlays.size();
}
else
{
int otherIndex = orderedOverlays.indexOf(other);
Preconditions.checkState(otherIndex >= 0, "Attempted to order against an unregistered overlay. Only order against vanilla's and your own.");
insertPosition = otherIndex + (ordering == Ordering.BEFORE ? 0 : 1);
}
overlays.put(key, overlay);
orderedOverlays.add(insertPosition, key);
}
private enum Ordering
{
BEFORE, AFTER
}
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client.event;
import net.minecraft.world.item.Item;
import net.minecraft.world.level.ItemLike;
import net.minecraftforge.client.IItemDecorator;
import net.minecraftforge.eventbus.api.Cancelable;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.fml.LogicalSide;
import net.minecraftforge.fml.event.IModBusEvent;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import org.jetbrains.annotations.ApiStatus;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* Allows users to register custom {@linkplain IItemDecorator IItemDecorator} to Items.
*
* <p>This event is not {@linkplain Cancelable cancelable}, and does not {@linkplain HasResult have a result}.
*
* <p>This event is fired on the {@linkplain FMLJavaModLoadingContext#getModEventBus() mod-specific event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
public class RegisterItemDecorationsEvent extends Event implements IModBusEvent
{
private final Map<Item, List<IItemDecorator>> decorators;
@ApiStatus.Internal
public RegisterItemDecorationsEvent(Map<Item, List<IItemDecorator>> decorators)
{
this.decorators = decorators;
}
/**
* Register an ItemDecorator to an Item
*/
public void register(ItemLike itemLike, IItemDecorator decorator)
{
List<IItemDecorator> itemDecoratorList = decorators.computeIfAbsent(itemLike.asItem(), item -> new ArrayList<>());
itemDecoratorList.add(decorator);
}
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client.event;
import net.minecraft.client.KeyMapping;
import net.minecraft.client.Options;
import net.minecraftforge.eventbus.api.Cancelable;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.fml.LogicalSide;
import net.minecraftforge.fml.event.IModBusEvent;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import org.apache.commons.lang3.ArrayUtils;
import org.jetbrains.annotations.ApiStatus;
/**
* Allows users to register custom {@link net.minecraft.client.KeyMapping key mappings}.
*
* <p>This event is not {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.
*
* <p>This event is fired on the {@linkplain FMLJavaModLoadingContext#getModEventBus() mod-specific event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
public class RegisterKeyMappingsEvent extends Event implements IModBusEvent
{
private final Options options;
@ApiStatus.Internal
public RegisterKeyMappingsEvent(Options options)
{
this.options = options;
}
/**
* Registers a new key mapping.
*/
public void register(KeyMapping key)
{
options.keyMappings = ArrayUtils.add(options.keyMappings, key);
}
}

View File

@@ -0,0 +1,72 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client.event;
import com.google.common.base.Preconditions;
import com.mojang.blaze3d.vertex.DefaultVertexFormat;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.resources.ResourceLocation;
import net.minecraftforge.client.RenderTypeGroup;
import net.minecraftforge.eventbus.api.Cancelable;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.fml.LogicalSide;
import net.minecraftforge.fml.ModLoadingContext;
import net.minecraftforge.fml.event.IModBusEvent;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import org.jetbrains.annotations.ApiStatus;
import java.util.Map;
/**
* Allows users to register custom named {@link RenderType render types}.
*
* <p>This event is not {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.
*
* <p>This event is fired on the {@linkplain FMLJavaModLoadingContext#getModEventBus() mod-specific event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
public class RegisterNamedRenderTypesEvent extends Event implements IModBusEvent
{
private final Map<ResourceLocation, RenderTypeGroup> renderTypes;
@ApiStatus.Internal
public RegisterNamedRenderTypesEvent(Map<ResourceLocation, RenderTypeGroup> renderTypes)
{
this.renderTypes = renderTypes;
}
/**
* Registers a named {@link RenderTypeGroup}.
*
* @param name The name
* @param blockRenderType One of the values returned by {@link RenderType#chunkBufferLayers()}
* @param entityRenderType A {@link RenderType} using {@link DefaultVertexFormat#NEW_ENTITY}
*/
public void register(String name, RenderType blockRenderType, RenderType entityRenderType)
{
register(name, blockRenderType, entityRenderType, entityRenderType);
}
/**
* Registers a named {@link RenderTypeGroup}.
*
* @param name The name
* @param blockRenderType One of the values returned by {@link RenderType#chunkBufferLayers()}
* @param entityRenderType A {@link RenderType} using {@link DefaultVertexFormat#NEW_ENTITY}
* @param fabulousEntityRenderType A {@link RenderType} using {@link DefaultVertexFormat#NEW_ENTITY} for use when
* "fabulous" rendering is enabled
*/
public void register(String name, RenderType blockRenderType, RenderType entityRenderType, RenderType fabulousEntityRenderType)
{
var key = new ResourceLocation(ModLoadingContext.get().getActiveNamespace(), name);
Preconditions.checkArgument(!renderTypes.containsKey(key), "Render type already registered: " + key);
Preconditions.checkArgument(blockRenderType.format() == DefaultVertexFormat.BLOCK, "The block render type must use the BLOCK vertex format.");
Preconditions.checkArgument(blockRenderType.getChunkLayerId() >= 0, "Only chunk render types can be used for block rendering. Query RenderType#chunkBufferLayers() for a list.");
Preconditions.checkArgument(entityRenderType.format() == DefaultVertexFormat.NEW_ENTITY, "The entity render type must use the NEW_ENTITY vertex format.");
Preconditions.checkArgument(fabulousEntityRenderType.format() == DefaultVertexFormat.NEW_ENTITY, "The fabulous entity render type must use the NEW_ENTITY vertex format.");
renderTypes.put(key, new RenderTypeGroup(blockRenderType, entityRenderType, fabulousEntityRenderType));
}
}

View File

@@ -0,0 +1,92 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client.event;
import net.minecraft.client.particle.ParticleEngine;
import net.minecraft.client.particle.ParticleProvider;
import net.minecraft.client.particle.TextureSheetParticle;
import net.minecraft.core.particles.ParticleOptions;
import net.minecraft.core.particles.ParticleType;
import net.minecraftforge.eventbus.api.Cancelable;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.fml.LogicalSide;
import net.minecraftforge.fml.event.IModBusEvent;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import net.minecraftforge.registries.RegisterEvent;
import org.jetbrains.annotations.ApiStatus;
/**
* Fired for registering particle providers at the appropriate time.
*
* <p>{@link ParticleType}s must be registered during {@link RegisterEvent} as usual;
* this event is only for the {@link ParticleProvider}s.</p>
*
* <p>This event is not {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.</p>
*
* <p>This event is fired on the {@linkplain FMLJavaModLoadingContext#getModEventBus() mod-specific event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
public class RegisterParticleProvidersEvent extends Event implements IModBusEvent
{
private final ParticleEngine particleEngine;
@ApiStatus.Internal
public RegisterParticleProvidersEvent(ParticleEngine particleEngine)
{
this.particleEngine = particleEngine;
}
/**
* <p>Registers a ParticleProvider for a non-json-based ParticleType.
* These particles do not receive a list of texture sprites to use for rendering themselves.</p>
*
* <p>There must be <strong>no</strong> particle json with an ID matching the ParticleType,
* or a redundant texture list error will occur when particle jsons load.</p>
*
* @param <T> ParticleOptions used by the ParticleType and ParticleProvider.
* @param type ParticleType to register a ParticleProvider for.
* @param provider ParticleProvider function responsible for providing that ParticleType's particles.
*/
@SuppressWarnings("deprecation")
public <T extends ParticleOptions> void registerSpecial(ParticleType<T> type, ParticleProvider<T> provider) {
particleEngine.register(type, provider);
}
/**
* <p>Registers a ParticleProvider for a json-based ParticleType with a single texture;
* the resulting {@link TextureSheetParticle}s will use that texture when created.</p>
*
* <p>A particle json with an ID matching the ParticleType <strong>must exist</strong> in the <code>particles</code> asset folder,
* or a missing texture list error will occur when particle jsons load.</p>
*
* @param <T> ParticleOptions used by the ParticleType and Sprite function.
* @param type ParticleType to register a ParticleProvider for.
* @param sprite Sprite function responsible for providing that ParticleType's particles.
*/
@SuppressWarnings("deprecation")
public <T extends ParticleOptions> void registerSprite(ParticleType<T> type, ParticleProvider.Sprite<T> sprite)
{
particleEngine.register(type, sprite);
}
/**
* <p>Registers a ParticleProvider for a json-based ParticleType.
* Particle jsons define a list of texture sprites which the particle can use to render itself.</p>
*
* <p>A particle json with an ID matching the ParticleType <strong>must exist</strong> in the <code>particles</code> asset folder,
* or a missing texture list error will occur when particle jsons load.</p>
*
* @param <T> ParticleOptions used by the ParticleType and SpriteParticleRegistration function.
* @param type ParticleType to register a particle provider for.
* @param registration SpriteParticleRegistration function responsible for providing that ParticleType's particles.
*/
@SuppressWarnings("deprecation")
public <T extends ParticleOptions> void registerSpriteSet(ParticleType<T> type, ParticleEngine.SpriteParticleRegistration<T> registration)
{
particleEngine.register(type, registration);
}
}

View File

@@ -0,0 +1,55 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client.event;
import java.util.Map;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.ApiStatus;
import net.minecraft.client.gui.screens.worldselection.PresetEditor;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.level.levelgen.presets.WorldPreset;
import net.minecraftforge.eventbus.api.Cancelable;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.fml.LogicalSide;
import net.minecraftforge.fml.ModLoadingContext;
import net.minecraftforge.fml.event.IModBusEvent;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
/**
* <p>Event for registering {@link PresetEditor} screen factories for world presets.</p>
*
* <p>This event is not {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.
*
* <p>This event is fired on the {@linkplain FMLJavaModLoadingContext#getModEventBus() mod-specific event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
public class RegisterPresetEditorsEvent extends Event implements IModBusEvent
{
private static final Logger LOGGER = LogManager.getLogger();
private final Map<ResourceKey<WorldPreset>, PresetEditor> editors;
@ApiStatus.Internal
public RegisterPresetEditorsEvent(Map<ResourceKey<WorldPreset>, PresetEditor> editors)
{
this.editors = editors;
}
/**
* Registers a PresetEditor for a given world preset key.
*/
public void register(ResourceKey<WorldPreset> key, PresetEditor editor)
{
PresetEditor old = this.editors.put(key, editor);
if (old != null)
{
LOGGER.debug("PresetEditor {} overridden by mod {}", key.location(), ModLoadingContext.get().getActiveNamespace());
}
}
}

View File

@@ -0,0 +1,72 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client.event;
import com.google.common.collect.ImmutableList;
import net.minecraft.client.RecipeBookCategories;
import net.minecraft.world.inventory.RecipeBookType;
import net.minecraft.world.item.crafting.Recipe;
import net.minecraft.world.item.crafting.RecipeType;
import net.minecraftforge.eventbus.api.Cancelable;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.fml.LogicalSide;
import net.minecraftforge.fml.event.IModBusEvent;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import org.jetbrains.annotations.ApiStatus;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
/**
* Allows users to register custom categories for the vanilla recipe book, making it usable in modded GUIs.
*
* <p>This event is not {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.
*
* <p>This event is fired on the {@linkplain FMLJavaModLoadingContext#getModEventBus() mod-specific event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
public class RegisterRecipeBookCategoriesEvent extends Event implements IModBusEvent
{
private final Map<RecipeBookCategories, ImmutableList<RecipeBookCategories>> aggregateCategories;
private final Map<RecipeBookType, ImmutableList<RecipeBookCategories>> typeCategories;
private final Map<RecipeType<?>, Function<Recipe<?>, RecipeBookCategories>> recipeCategoryLookups;
@ApiStatus.Internal
public RegisterRecipeBookCategoriesEvent(
Map<RecipeBookCategories, ImmutableList<RecipeBookCategories>> aggregateCategories,
Map<RecipeBookType, ImmutableList<RecipeBookCategories>> typeCategories,
Map<RecipeType<?>, Function<Recipe<?>, RecipeBookCategories>> recipeCategoryLookups)
{
this.aggregateCategories = aggregateCategories;
this.typeCategories = typeCategories;
this.recipeCategoryLookups = recipeCategoryLookups;
}
/**
* Registers the list of categories that compose an aggregate category.
*/
public void registerAggregateCategory(RecipeBookCategories category, List<RecipeBookCategories> others)
{
aggregateCategories.put(category, ImmutableList.copyOf(others));
}
/**
* Registers the list of categories that compose a recipe book.
*/
public void registerBookCategories(RecipeBookType type, List<RecipeBookCategories> categories)
{
typeCategories.put(type, ImmutableList.copyOf(categories));
}
/**
* Registers a category lookup for a certain recipe type.
*/
public void registerRecipeCategoryFinder(RecipeType<?> type, Function<Recipe<?>, RecipeBookCategories> lookup)
{
recipeCategoryLookups.put(type, lookup);
}
}

View File

@@ -0,0 +1,67 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client.event;
import com.mojang.datafixers.util.Pair;
import net.minecraft.client.renderer.ShaderInstance;
import net.minecraft.server.packs.resources.ResourceManager;
import net.minecraft.server.packs.resources.ResourceProvider;
import net.minecraftforge.eventbus.api.Cancelable;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.fml.LogicalSide;
import net.minecraftforge.fml.event.IModBusEvent;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import org.jetbrains.annotations.ApiStatus;
import java.util.List;
import java.util.function.Consumer;
/**
* Fired to allow mods to register custom {@linkplain ShaderInstance shaders}.
* This event is fired after the default Minecraft shaders have been registered.
*
* <p>This event is not {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.</p>
*
* <p>This event is fired on the {@linkplain FMLJavaModLoadingContext#getModEventBus() mod-specific event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
public class RegisterShadersEvent extends Event implements IModBusEvent
{
private final ResourceProvider resourceProvider;
private final List<Pair<ShaderInstance, Consumer<ShaderInstance>>> shaderList;
@ApiStatus.Internal
public RegisterShadersEvent(ResourceProvider resourceProvider, List<Pair<ShaderInstance, Consumer<ShaderInstance>>> shaderList)
{
this.resourceProvider = resourceProvider;
this.shaderList = shaderList;
}
/**
* {@return the client-side resource provider}
*/
public ResourceProvider getResourceProvider()
{
return resourceProvider;
}
/**
* Registers a shader, and a callback for when the shader is loaded.
*
* <p>When creating a {@link ShaderInstance}, pass in the {@linkplain #getResourceProvider()
* client-side resource provider} as the resource provider.</p>
*
* <p>Mods should not store the shader instance passed into this method. Instead, mods should store the shader
* passed into the registered load callback.</p>
*
* @param shaderInstance a shader
* @param onLoaded a callback for when the shader is loaded
*/
public void registerShader(ShaderInstance shaderInstance, Consumer<ShaderInstance> onLoaded)
{
shaderList.add(Pair.of(shaderInstance, onLoaded));
}
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client.event;
import com.google.common.base.Preconditions;
import net.minecraft.resources.ResourceLocation;
import net.minecraftforge.client.textures.ITextureAtlasSpriteLoader;
import net.minecraftforge.eventbus.api.Cancelable;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.fml.LogicalSide;
import net.minecraftforge.fml.ModLoadingContext;
import net.minecraftforge.fml.event.IModBusEvent;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import org.jetbrains.annotations.ApiStatus;
import java.util.Map;
/**
* Allows users to register custom {@link ITextureAtlasSpriteLoader texture atlas sprite loaders}.
*
* <p>This event is not {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.
*
* <p>This event is fired on the {@linkplain FMLJavaModLoadingContext#getModEventBus() mod-specific event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
public class RegisterTextureAtlasSpriteLoadersEvent extends Event implements IModBusEvent
{
private final Map<ResourceLocation, ITextureAtlasSpriteLoader> loaders;
@ApiStatus.Internal
public RegisterTextureAtlasSpriteLoadersEvent(Map<ResourceLocation, ITextureAtlasSpriteLoader> loaders)
{
this.loaders = loaders;
}
/**
* Registers a custom {@link ITextureAtlasSpriteLoader sprite loader}.
*/
public void register(String name, ITextureAtlasSpriteLoader loader)
{
var key = new ResourceLocation(ModLoadingContext.get().getActiveNamespace(), name);
Preconditions.checkArgument(!loaders.containsKey(key), "Sprite loader already registered: " + key);
loaders.put(key, loader);
}
}

View File

@@ -0,0 +1,91 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client.event;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.player.AbstractClientPlayer;
import net.minecraft.client.renderer.LightTexture;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.world.entity.HumanoidArm;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.eventbus.api.Cancelable;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.fml.LogicalSide;
import org.jetbrains.annotations.ApiStatus;
/**
* Fired before the player's arm is rendered in first person. This is a more targeted version of {@link RenderHandEvent},
* and can be used to replace the rendering of the player's arm, such as for rendering armor on the arm or outright
* replacing the arm with armor.
*
* <p>This event is {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.
* If this event is cancelled, then the arm will not be rendered.</p>
*
* <p>This event is fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
@Cancelable
public class RenderArmEvent extends Event
{
private final PoseStack poseStack;
private final MultiBufferSource multiBufferSource;
private final int packedLight;
private final AbstractClientPlayer player;
private final HumanoidArm arm;
@ApiStatus.Internal
public RenderArmEvent(PoseStack poseStack, MultiBufferSource multiBufferSource, int packedLight, AbstractClientPlayer player, HumanoidArm arm)
{
this.poseStack = poseStack;
this.multiBufferSource = multiBufferSource;
this.packedLight = packedLight;
this.player = player;
this.arm = arm;
}
/**
* {@return the arm being rendered}
*/
public HumanoidArm getArm()
{
return arm;
}
/**
* {@return the pose stack used for rendering}
*/
public PoseStack getPoseStack()
{
return poseStack;
}
/**
* {@return the source of rendering buffers}
*/
public MultiBufferSource getMultiBufferSource()
{
return multiBufferSource;
}
/**
* {@return the amount of packed (sky and block) light for rendering}
*
* @see LightTexture
*/
public int getPackedLight()
{
return packedLight;
}
/**
* {@return the client player that is having their arm rendered} In general, this will be the same as
* {@link net.minecraft.client.Minecraft#player}.
*/
public AbstractClientPlayer getPlayer()
{
return player;
}
}

View File

@@ -0,0 +1,106 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client.event;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.core.BlockPos;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.eventbus.api.Cancelable;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.fml.LogicalSide;
import org.jetbrains.annotations.ApiStatus;
/**
* Fired before a block texture will be overlaid on the player's view.
*
* <p>This event is {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.
* If this event is cancelled, then the overlay will not be rendered.</p>
*
* <p>This event is fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
@Cancelable
public class RenderBlockScreenEffectEvent extends Event
{
/**
* The type of the block overlay to be rendered.
*
* @see RenderBlockScreenEffectEvent
*/
public enum OverlayType
{
/**
* The type of the overlay when the player is burning / on fire.
*/
FIRE,
/**
* The type of overlay when the player is suffocating inside a solid block.
*/
BLOCK,
/**
* The type of overlay when the player is underwater.
*/
WATER
}
private final Player player;
private final PoseStack poseStack;
private final OverlayType overlayType;
private final BlockState blockState;
private final BlockPos blockPos;
@ApiStatus.Internal
public RenderBlockScreenEffectEvent(Player player, PoseStack poseStack, OverlayType type, BlockState block, BlockPos blockPos)
{
this.player = player;
this.poseStack = poseStack;
this.overlayType = type;
this.blockState = block;
this.blockPos = blockPos;
}
/**
* {@return the player which the overlay will apply to}
*/
public Player getPlayer()
{
return player;
}
/**
* {@return the pose stack used for rendering}
*/
public PoseStack getPoseStack()
{
return poseStack;
}
/**
* {@return the type of the overlay}
*/
public OverlayType getOverlayType()
{
return overlayType;
}
/**
* {@return the block which the overlay is gotten from}
*/
public BlockState getBlockState()
{
return blockState;
}
/**
* {@return the position of the block which the overlay is gotten from}
*/
public BlockPos getBlockPos()
{
return blockPos;
}
}

View File

@@ -0,0 +1,90 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client.event;
import com.mojang.blaze3d.platform.Window;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.eventbus.api.Cancelable;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.fml.LogicalSide;
import org.jetbrains.annotations.ApiStatus;
/**
* Fired when the HUD is rendered to the screen.
* See the two subclasses for listening to the two possible phases.
*
* @see Pre
* @see Post
*/
public abstract class RenderGuiEvent extends Event
{
private final Window window;
private final GuiGraphics guiGraphics;
private final float partialTick;
@ApiStatus.Internal
protected RenderGuiEvent(Window window, GuiGraphics guiGraphics, float partialTick)
{
this.window = window;
this.guiGraphics = guiGraphics;
this.partialTick = partialTick;
}
public Window getWindow()
{
return window;
}
public GuiGraphics getGuiGraphics()
{
return guiGraphics;
}
public float getPartialTick()
{
return partialTick;
}
/**
* Fired <b>before</b> the HUD is rendered to the screen.
*
* <p>This event is {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.
* If this event is cancelled, then the overlay will not be rendered, and the corresponding {@link Post} event will
* not be fired.</p>
*
* <p>This event is fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*
* @see Post
*/
@Cancelable
public static class Pre extends RenderGuiEvent
{
@ApiStatus.Internal
public Pre(Window window, GuiGraphics guiGraphics, float partialTick)
{
super(window, guiGraphics, partialTick);
}
}
/**
* Fired <b>after</b> the HUD is rendered to the screen, if the corresponding {@link Pre} is not cancelled.
*
* <p>This event is not {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.</p>
*
* <p>This event is fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
public static class Post extends RenderGuiEvent
{
@ApiStatus.Internal
public Post(Window window, GuiGraphics guiGraphics, float partialTick)
{
super(window, guiGraphics, partialTick);
}
}
}

View File

@@ -0,0 +1,100 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client.event;
import com.mojang.blaze3d.platform.Window;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraftforge.client.gui.overlay.NamedGuiOverlay;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.eventbus.api.Cancelable;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.fml.LogicalSide;
import org.jetbrains.annotations.ApiStatus;
/**
* Fired when an overlay is rendered to the screen.
* See the two subclasses for listening to the two possible phases.
*
* <p>An overlay that is not normally active cannot be forced to render. In such cases, this event will not fire.</p>
*
* @see Pre
* @see Post
*/
public abstract class RenderGuiOverlayEvent extends Event
{
private final Window window;
private final GuiGraphics guiGraphics;
private final float partialTick;
private final NamedGuiOverlay overlay;
@ApiStatus.Internal
protected RenderGuiOverlayEvent(Window window, GuiGraphics guiGraphics, float partialTick, NamedGuiOverlay overlay)
{
this.window = window;
this.guiGraphics = guiGraphics;
this.partialTick = partialTick;
this.overlay = overlay;
}
public Window getWindow()
{
return window;
}
public GuiGraphics getGuiGraphics()
{
return guiGraphics;
}
public float getPartialTick()
{
return partialTick;
}
public NamedGuiOverlay getOverlay()
{
return overlay;
}
/**
* Fired <b>before</b> a GUI overlay is rendered to the screen.
*
* <p>This event is {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.
* If this event is cancelled, then the overlay will not be rendered, and the corresponding {@link Post} event will
* not be fired.</p>
*
* <p>This event is fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*
* @see Post
*/
@Cancelable
public static class Pre extends RenderGuiOverlayEvent
{
@ApiStatus.Internal
public Pre(Window window, GuiGraphics guiGraphics, float partialTick, NamedGuiOverlay overlay)
{
super(window, guiGraphics, partialTick, overlay);
}
}
/**
* Fired <b>after</b> an GUI overlay is rendered to the screen, if the corresponding {@link Pre} is not cancelled.
*
* <p>This event is not {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.</p>
*
* <p>This event is fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
public static class Post extends RenderGuiOverlayEvent
{
@ApiStatus.Internal
public Post(Window window, GuiGraphics guiGraphics, float partialTick, NamedGuiOverlay overlay)
{
super(window, guiGraphics, partialTick, overlay);
}
}
}

View File

@@ -0,0 +1,132 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client.event;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.renderer.LightTexture;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.item.ItemStack;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.eventbus.api.Cancelable;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.fml.LogicalSide;
import org.jetbrains.annotations.ApiStatus;
/**
* Fired before a hand is rendered in the first person view.
*
* <p>This event is {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.
* If this event is cancelled, then the hand will not be rendered.</p>
*
* <p>This event is fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*
* @see RenderArmEvent
*/
@Cancelable
public class RenderHandEvent extends Event
{
private final InteractionHand hand;
private final PoseStack poseStack;
private final MultiBufferSource multiBufferSource;
private final int packedLight;
private final float partialTick;
private final float interpolatedPitch;
private final float swingProgress;
private final float equipProgress;
private final ItemStack stack;
@ApiStatus.Internal
public RenderHandEvent(InteractionHand hand, PoseStack poseStack, MultiBufferSource multiBufferSource, int packedLight,
float partialTick, float interpolatedPitch,
float swingProgress, float equipProgress, ItemStack stack)
{
this.hand = hand;
this.poseStack = poseStack;
this.multiBufferSource = multiBufferSource;
this.packedLight = packedLight;
this.partialTick = partialTick;
this.interpolatedPitch = interpolatedPitch;
this.swingProgress = swingProgress;
this.equipProgress = equipProgress;
this.stack = stack;
}
/**
* {@return the hand being rendered}
*/
public InteractionHand getHand()
{
return hand;
}
/**
* {@return the pose stack used for rendering}
*/
public PoseStack getPoseStack()
{
return poseStack;
}
/**
* {@return the source of rendering buffers}
*/
public MultiBufferSource getMultiBufferSource()
{
return multiBufferSource;
}
/**
* {@return the amount of packed (sky and block) light for rendering}
*
* @see LightTexture
*/
public int getPackedLight()
{
return packedLight;
}
/**
* {@return the partial tick}
*/
public float getPartialTick()
{
return partialTick;
}
/**
* {@return the interpolated pitch of the player entity}
*/
public float getInterpolatedPitch()
{
return interpolatedPitch;
}
/**
* {@return the swing progress of the hand being rendered}
*/
public float getSwingProgress()
{
return swingProgress;
}
/**
* {@return the progress of the equip animation, from {@code 0.0} to {@code 1.0}}
*/
public float getEquipProgress()
{
return equipProgress;
}
/**
* {@return the item stack to be rendered}
*/
public ItemStack getItemStack()
{
return stack;
}
}

View File

@@ -0,0 +1,150 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client.event;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.Camera;
import net.minecraft.client.renderer.LevelRenderer;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.EntityHitResult;
import net.minecraft.world.phys.HitResult;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.eventbus.api.Cancelable;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.fml.LogicalSide;
import org.jetbrains.annotations.ApiStatus;
/**
* Fired before a selection highlight is rendered.
* See the two subclasses to listen for blocks or entities.
*
* @see Block
* @see Entity
*/
@Cancelable
public abstract class RenderHighlightEvent extends Event
{
private final LevelRenderer levelRenderer;
private final Camera camera;
private final HitResult target;
private final float partialTick;
private final PoseStack poseStack;
private final MultiBufferSource multiBufferSource;
@ApiStatus.Internal
protected RenderHighlightEvent(LevelRenderer levelRenderer, Camera camera, HitResult target, float partialTick, PoseStack poseStack, MultiBufferSource multiBufferSource)
{
this.levelRenderer = levelRenderer;
this.camera = camera;
this.target = target;
this.partialTick = partialTick;
this.poseStack = poseStack;
this.multiBufferSource = multiBufferSource;
}
/**
* {@return the level renderer}
*/
public LevelRenderer getLevelRenderer()
{
return levelRenderer;
}
/**
* {@return the camera information}
*/
public Camera getCamera()
{
return camera;
}
/**
* {@return the hit result which triggered the selection highlight}
*/
public HitResult getTarget()
{
return target;
}
/**
* {@return the partial tick}
*/
public float getPartialTick()
{
return partialTick;
}
/**
* {@return the pose stack used for rendering}
*/
public PoseStack getPoseStack()
{
return poseStack;
}
/**
* {@return the source of rendering buffers}
*/
public MultiBufferSource getMultiBufferSource()
{
return multiBufferSource;
}
/**
* Fired before a block's selection highlight is rendered.
*
* <p>This event is {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.
* If the event is cancelled, then the selection highlight will not be rendered.</p>
*
* <p>This event is fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
@Cancelable
public static class Block extends RenderHighlightEvent
{
@ApiStatus.Internal
public Block(LevelRenderer levelRenderer, Camera camera, BlockHitResult target, float partialTick, PoseStack poseStack, MultiBufferSource bufferSource)
{
super(levelRenderer, camera, target, partialTick, poseStack, bufferSource);
}
/**
* {@return the block hit result}
*/
@Override
public BlockHitResult getTarget()
{
return (BlockHitResult) super.target;
}
}
/**
* Fired before an entity's selection highlight is rendered.
*
* <p>This event is not {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.</p>
*
* <p>This event is fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
public static class Entity extends RenderHighlightEvent
{
@ApiStatus.Internal
public Entity(LevelRenderer levelRenderer, Camera camera, EntityHitResult target, float partialTick, PoseStack poseStack, MultiBufferSource bufferSource)
{
super(levelRenderer, camera, target, partialTick, poseStack, bufferSource);
}
/**
* {@return the entity hit result}
*/
@Override
public EntityHitResult getTarget()
{
return (EntityHitResult) super.target;
}
}
}

View File

@@ -0,0 +1,103 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client.event;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.renderer.LightTexture;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.entity.ItemFrameRenderer;
import net.minecraft.world.entity.decoration.ItemFrame;
import net.minecraft.world.item.ItemStack;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.eventbus.api.Cancelable;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.fml.LogicalSide;
import org.jetbrains.annotations.ApiStatus;
/**
* Fired before an item stack is rendered in an item frame.
* This can be used to prevent normal rendering or add custom rendering.
*
* <p>This event is {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.
* If the event is cancelled, then the item stack will not be rendered</p>
*
* <p>This event is fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*
* @see ItemFrameRenderer
*/
@Cancelable
public class RenderItemInFrameEvent extends Event
{
private final ItemStack itemStack;
private final ItemFrame itemFrameEntity;
private final ItemFrameRenderer<?> renderer;
private final PoseStack poseStack;
private final MultiBufferSource multiBufferSource;
private final int packedLight;
@ApiStatus.Internal
public RenderItemInFrameEvent(ItemFrame itemFrame, ItemFrameRenderer<?> renderItemFrame, PoseStack poseStack,
MultiBufferSource multiBufferSource, int packedLight)
{
itemStack = itemFrame.getItem();
itemFrameEntity = itemFrame;
renderer = renderItemFrame;
this.poseStack = poseStack;
this.multiBufferSource = multiBufferSource;
this.packedLight = packedLight;
}
/**
* {@return the item stack being rendered}
*/
public ItemStack getItemStack()
{
return itemStack;
}
/**
* {@return the item frame entity}
*/
public ItemFrame getItemFrameEntity()
{
return itemFrameEntity;
}
/**
* {@return the renderer for the item frame entity}
*/
public ItemFrameRenderer<?> getRenderer()
{
return renderer;
}
/**
* {@return the pose stack used for rendering}
*/
public PoseStack getPoseStack()
{
return poseStack;
}
/**
* {@return the source of rendering buffers}
*/
public MultiBufferSource getMultiBufferSource()
{
return multiBufferSource;
}
/**
* {@return the amount of packed (sky and block) light for rendering}
*
* @see LightTexture
*/
public int getPackedLight()
{
return packedLight;
}
}

View File

@@ -0,0 +1,251 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client.event;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.Nullable;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.Camera;
import net.minecraft.client.renderer.GameRenderer;
import net.minecraft.client.renderer.LevelRenderer;
import net.minecraft.client.renderer.LightTexture;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.culling.Frustum;
import net.minecraft.resources.ResourceLocation;
import net.minecraftforge.client.ForgeRenderTypes;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.eventbus.api.Cancelable;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.fml.LogicalSide;
import net.minecraftforge.fml.event.IModBusEvent;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import org.joml.Matrix4f;
/**
* Fires at various times during LevelRenderer.renderLevel.
* Check {@link #getStage} to render during the appropriate time for your use case.
*
* <p>This event is not {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}. </p>
*
* <p>This event is fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}. </p>
*/
public class RenderLevelStageEvent extends Event
{
private final Stage stage;
private final LevelRenderer levelRenderer;
private final PoseStack poseStack;
private final Matrix4f projectionMatrix;
private final int renderTick;
private final float partialTick;
private final Camera camera;
private final Frustum frustum;
public RenderLevelStageEvent(Stage stage, LevelRenderer levelRenderer, PoseStack poseStack, Matrix4f projectionMatrix, int renderTick, float partialTick, Camera camera, Frustum frustum)
{
this.stage = stage;
this.levelRenderer = levelRenderer;
this.poseStack = poseStack;
this.projectionMatrix = projectionMatrix;
this.renderTick = renderTick;
this.partialTick = partialTick;
this.camera = camera;
this.frustum = frustum;
}
/**
* {@return the current {@linkplain Stage stage} that is being rendered. Check this before doing rendering to ensure
* that rendering happens at the appropriate time.}
*/
public Stage getStage()
{
return stage;
}
/**
* {@return the level renderer}
*/
public LevelRenderer getLevelRenderer()
{
return levelRenderer;
}
/**
* {@return the pose stack used for rendering}
*/
public PoseStack getPoseStack()
{
return poseStack;
}
/**
* {@return the projection matrix}
*/
public Matrix4f getProjectionMatrix()
{
return projectionMatrix;
}
/**
* {@return the current "ticks" value in the {@linkplain LevelRenderer level renderer}}
*/
public int getRenderTick()
{
return renderTick;
}
/**
* {@return the current partialTick value used for rendering}
*/
public float getPartialTick()
{
return partialTick;
}
/**
* {@return the camera}
*/
public Camera getCamera()
{
return camera;
}
/**
* {@return the frustum}
*/
public Frustum getFrustum()
{
return frustum;
}
/**
* Use to create a custom {@linkplain RenderLevelStageEvent.Stage stages}.
* Fired after the LevelRenderer has been created.
*
* <p>This event is not {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}. </p>
*
* <p>This event is fired on the {@linkplain FMLJavaModLoadingContext#getModEventBus() mod-specific event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}. </p>
*/
public static class RegisterStageEvent extends Event implements IModBusEvent
{
/**
* @param name The name of your Stage.
* @param renderType
* If not null, called automatically by LevelRenderer.renderChunkLayer if the RenderType passed into it matches this one.
* If null, needs to be called manually by whoever implements it.
*
* @throws IllegalArgumentException if the RenderType passed is already mapped to a Stage.
*/
public Stage register(ResourceLocation name, @Nullable RenderType renderType) throws IllegalArgumentException
{
return Stage.register(name, renderType);
}
}
/**
* A time during level rendering for you to render custom things into the world.
* @see RegisterStageEvent
*/
public static class Stage
{
private static final Map<RenderType, Stage> RENDER_TYPE_STAGES = new HashMap<>();
/**
* Use this to render custom objects into the skybox.
* Called regardless of if they sky actually renders or not.
*/
public static final Stage AFTER_SKY = register("after_sky", null);
/**
* Use this to render custom block-like geometry into the world.
*/
public static final Stage AFTER_SOLID_BLOCKS = register("after_solid_blocks", RenderType.solid());
/**
* Use this to render custom block-like geometry into the world.
*/
public static final Stage AFTER_CUTOUT_MIPPED_BLOCKS_BLOCKS = register("after_cutout_mipped_blocks", RenderType.cutoutMipped());
/**
* Use this to render custom block-like geometry into the world.
*/
public static final Stage AFTER_CUTOUT_BLOCKS = register("after_cutout_blocks", RenderType.cutout());
/**
* Use this to render custom block-like geometry into the world.
*/
public static final Stage AFTER_ENTITIES = register("after_entities", null);
/**
* Use this to render custom block-like geometry into the world.
*/
public static final Stage AFTER_BLOCK_ENTITIES = register("after_block_entities", null);
/**
* Use this to render custom block-like geometry into the world.
* Due to how transparency sorting works, this stage may not work properly with translucency. If you intend to render translucency,
* try using {@link #AFTER_TRIPWIRE_BLOCKS} or {@link #AFTER_PARTICLES}.
* Although this is called within a fabulous graphics target, it does not function properly in many cases.
*/
public static final Stage AFTER_TRANSLUCENT_BLOCKS = register("after_translucent_blocks", RenderType.translucent());
/**
* Use this to render custom block-like geometry into the world.
*/
public static final Stage AFTER_TRIPWIRE_BLOCKS = register("after_tripwire_blocks", RenderType.tripwire());
/**
* Use this to render custom effects into the world, such as custom entity-like objects or special rendering effects.
* Called within a fabulous graphics target.
* Happens after entities render.
*
* @see ForgeRenderTypes#TRANSLUCENT_ON_PARTICLES_TARGET
*/
public static final Stage AFTER_PARTICLES = register("after_particles", null);
/**
* Use this to render custom weather effects into the world.
* Called within a fabulous graphics target.
*/
public static final Stage AFTER_WEATHER = register("after_weather", null);
/**
* Use this to render after everything in the level has been rendered.
* Called after {@link LevelRenderer#renderLevel(PoseStack, float, long, boolean, Camera, GameRenderer, LightTexture, Matrix4f)} finishes.
*/
public static final Stage AFTER_LEVEL = register("after_level", null);
private final String name;
private Stage(String name)
{
this.name = name;
}
private static Stage register(ResourceLocation name, @Nullable RenderType renderType) throws IllegalArgumentException
{
Stage stage = new Stage(name.toString());
if (renderType != null && RENDER_TYPE_STAGES.putIfAbsent(renderType, stage) != null)
throw new IllegalArgumentException("Attempted to replace an existing RenderLevelStageEvent.Stage for a RenderType: Stage = " + stage + ", RenderType = " + renderType);
return stage;
}
private static Stage register(String name, @Nullable RenderType renderType) throws IllegalArgumentException
{
return register(new ResourceLocation(name), renderType);
}
@Override
public String toString()
{
return this.name;
}
/**
* {@return the {@linkplain Stage stage} bound to the {@linkplain RenderType render type}, or null if no value is present}
*/
@Nullable
public static Stage fromRenderType(RenderType renderType)
{
return RENDER_TYPE_STAGES.get(renderType);
}
}
}

View File

@@ -0,0 +1,148 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client.event;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.model.EntityModel;
import net.minecraft.client.renderer.LightTexture;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.entity.LivingEntityRenderer;
import net.minecraft.world.entity.LivingEntity;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.eventbus.api.Cancelable;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.fml.LogicalSide;
import org.jetbrains.annotations.ApiStatus;
/**
* Fired when a {@link LivingEntity} is rendered.
* See the two subclasses to listen for before and after rendering.
*
* <p>Despite this event's use of generic type parameters, this is not a {@link net.minecraftforge.eventbus.api.GenericEvent},
* and should not be treated as such (such as using generic-specific listeners, which may cause a {@link ClassCastException}).</p>
*
* @param <T> the living entity that is being rendered
* @param <M> the model for the living entity
* @see RenderLivingEvent.Pre
* @see RenderLivingEvent.Post
* @see RenderPlayerEvent
* @see LivingEntityRenderer
*/
public abstract class RenderLivingEvent<T extends LivingEntity, M extends EntityModel<T>> extends Event
{
private final LivingEntity entity;
private final LivingEntityRenderer<T, M> renderer;
private final float partialTick;
private final PoseStack poseStack;
private final MultiBufferSource multiBufferSource;
private final int packedLight;
@ApiStatus.Internal
protected RenderLivingEvent(LivingEntity entity, LivingEntityRenderer<T, M> renderer, float partialTick, PoseStack poseStack,
MultiBufferSource multiBufferSource, int packedLight)
{
this.entity = entity;
this.renderer = renderer;
this.partialTick = partialTick;
this.poseStack = poseStack;
this.multiBufferSource = multiBufferSource;
this.packedLight = packedLight;
}
/**
* @return the living entity being rendered
*/
public LivingEntity getEntity()
{
return entity;
}
/**
* @return the renderer for the living entity
*/
public LivingEntityRenderer<T, M> getRenderer()
{
return renderer;
}
/**
* {@return the partial tick}
*/
public float getPartialTick()
{
return partialTick;
}
/**
* {@return the pose stack used for rendering}
*/
public PoseStack getPoseStack()
{
return poseStack;
}
/**
* {@return the source of rendering buffers}
*/
public MultiBufferSource getMultiBufferSource()
{
return multiBufferSource;
}
/**
* {@return the amount of packed (sky and block) light for rendering}
*
* @see LightTexture
*/
public int getPackedLight()
{
return packedLight;
}
/**
* Fired <b>before</b> an entity is rendered.
* This can be used to render additional effects or suppress rendering.
*
* <p>This event is {@linkplain Cancelable cancelable}, and does not {@linkplain HasResult have a result}.
* If this event is cancelled, then the entity will not be rendered and the corresponding
* {@link RenderLivingEvent.Post} will not be fired.</p>
*
* <p>This event is fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*
* @param <T> the living entity that is being rendered
* @param <M> the model for the living entity
*/
@Cancelable
public static class Pre<T extends LivingEntity, M extends EntityModel<T>> extends RenderLivingEvent<T, M>
{
@ApiStatus.Internal
public Pre(LivingEntity entity, LivingEntityRenderer<T, M> renderer, float partialTick, PoseStack poseStack, MultiBufferSource multiBufferSource, int packedLight)
{
super(entity, renderer, partialTick, poseStack, multiBufferSource, packedLight);
}
}
/**
* Fired <b>after</b> an entity is rendered, if the corresponding {@link RenderLivingEvent.Post} is not cancelled.
*
* <p>This event is not {@linkplain Cancelable cancelable}, and does not {@linkplain HasResult have a result}.</p>
*
* <p>This event is fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*
* @param <T> the living entity that was rendered
* @param <M> the model for the living entity
*/
public static class Post<T extends LivingEntity, M extends EntityModel<T>> extends RenderLivingEvent<T, M>
{
@ApiStatus.Internal
public Post(LivingEntity entity, LivingEntityRenderer<T, M> renderer, float partialTick, PoseStack poseStack, MultiBufferSource multiBufferSource, int packedLight)
{
super(entity, renderer, partialTick, poseStack, multiBufferSource, packedLight);
}
}
}

View File

@@ -0,0 +1,126 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client.event;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.entity.EntityRenderer;
import net.minecraft.network.chat.Component;
import net.minecraft.world.entity.Entity;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.entity.EntityEvent;
import net.minecraftforge.eventbus.api.Cancelable;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.fml.LogicalSide;
import org.jetbrains.annotations.ApiStatus;
/**
* Fired before an entity renderer renders the nameplate of an entity.
*
* <p>This event is not {@linkplain Cancelable cancellable}, and {@linkplain HasResult has a result}.</p>
* <ul>
* <li>{@link Result#ALLOW} - the nameplate will be forcibly rendered.</li>
* <li>{@link Result#DEFAULT} - the vanilla logic will be used.</li>
* <li>{@link Result#DENY} - the nameplate will not be rendered.</li>
* </ul>
*
* <p>This event is fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*
* @see EntityRenderer
*/
@Event.HasResult
public class RenderNameTagEvent extends EntityEvent
{
private Component nameplateContent;
private final Component originalContent;
private final EntityRenderer<?> entityRenderer;
private final PoseStack poseStack;
private final MultiBufferSource multiBufferSource;
private final int packedLight;
private final float partialTick;
@ApiStatus.Internal
public RenderNameTagEvent(Entity entity, Component content, EntityRenderer<?> entityRenderer, PoseStack poseStack, MultiBufferSource multiBufferSource, int packedLight, float partialTick)
{
super(entity);
this.originalContent = content;
this.setContent(this.originalContent);
this.entityRenderer = entityRenderer;
this.poseStack = poseStack;
this.multiBufferSource = multiBufferSource;
this.packedLight = packedLight;
this.partialTick = partialTick;
}
/**
* Sets the new text on the nameplate.
*
* @param contents the new text
*/
public void setContent(Component contents)
{
this.nameplateContent = contents;
}
/**
* {@return the text on the nameplate that will be rendered, if the event is not {@link Result#DENY DENIED}}
*/
public Component getContent()
{
return this.nameplateContent;
}
/**
* {@return the original text on the nameplate}
*/
public Component getOriginalContent()
{
return this.originalContent;
}
/**
* {@return the entity renderer rendering the nameplate}
*/
public EntityRenderer<?> getEntityRenderer()
{
return this.entityRenderer;
}
/**
* {@return the pose stack used for rendering}
*/
public PoseStack getPoseStack()
{
return this.poseStack;
}
/**
* {@return the source of rendering buffers}
*/
public MultiBufferSource getMultiBufferSource()
{
return this.multiBufferSource;
}
/**
* {@return the amount of packed (sky and block) light for rendering}
*
* @see net.minecraft.client.renderer.LightTexture
*/
public int getPackedLight()
{
return this.packedLight;
}
/**
* {@return the partial tick}
*/
public float getPartialTick()
{
return this.partialTick;
}
}

View File

@@ -0,0 +1,125 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client.event;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.renderer.LightTexture;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.entity.player.PlayerRenderer;
import net.minecraft.world.entity.player.Player;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.entity.player.PlayerEvent;
import net.minecraftforge.eventbus.api.Cancelable;
import net.minecraftforge.fml.LogicalSide;
import org.jetbrains.annotations.ApiStatus;
/**
* Fired when a player is being rendered.
* See the two subclasses for listening for before and after rendering.
*
* @see RenderPlayerEvent.Pre
* @see RenderPlayerEvent.Post
* @see PlayerRenderer
*/
public abstract class RenderPlayerEvent extends PlayerEvent
{
private final PlayerRenderer renderer;
private final float partialTick;
private final PoseStack poseStack;
private final MultiBufferSource multiBufferSource;
private final int packedLight;
@ApiStatus.Internal
protected RenderPlayerEvent(Player player, PlayerRenderer renderer, float partialTick, PoseStack poseStack, MultiBufferSource multiBufferSource, int packedLight)
{
super(player);
this.renderer = renderer;
this.partialTick = partialTick;
this.poseStack = poseStack;
this.multiBufferSource = multiBufferSource;
this.packedLight = packedLight;
}
/**
* {@return the player entity renderer}
*/
public PlayerRenderer getRenderer()
{
return renderer;
}
/**
* {@return the partial tick}
*/
public float getPartialTick()
{
return partialTick;
}
/**
* {@return the pose stack used for rendering}
*/
public PoseStack getPoseStack()
{
return poseStack;
}
/**
* {@return the source of rendering buffers}
*/
public MultiBufferSource getMultiBufferSource()
{
return multiBufferSource;
}
/**
* {@return the amount of packed (sky and block) light for rendering}
*
* @see LightTexture
*/
public int getPackedLight()
{
return packedLight;
}
/**
* Fired <b>before</b> the player is rendered.
* This can be used for rendering additional effects or suppressing rendering.
*
* <p>This event is {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.
* If this event is cancelled, then the player will not be rendered and the corresponding
* {@link RenderPlayerEvent.Post} will not be fired.</p>
*
* <p>This event is fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
@Cancelable
public static class Pre extends RenderPlayerEvent
{
@ApiStatus.Internal
public Pre(Player player, PlayerRenderer renderer, float partialTick, PoseStack poseStack, MultiBufferSource multiBufferSource, int packedLight)
{
super(player, renderer, partialTick, poseStack, multiBufferSource, packedLight);
}
}
/**
* Fired <b>after</b> the player is rendered, if the corresponding {@link RenderPlayerEvent.Pre} is not cancelled.
*
* <p>This event is not {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.</p>
*
* <p>This event is fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
public static class Post extends RenderPlayerEvent
{
@ApiStatus.Internal
public Post(Player player, PlayerRenderer renderer, float partialTick, PoseStack poseStack, MultiBufferSource multiBufferSource, int packedLight)
{
super(player, renderer, partialTick, poseStack, multiBufferSource, packedLight);
}
}
}

View File

@@ -0,0 +1,429 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client.event;
import com.mojang.datafixers.util.Either;
import net.minecraft.client.gui.Font;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipComponent;
import net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipPositioner;
import net.minecraft.network.chat.FormattedText;
import net.minecraft.world.inventory.tooltip.TooltipComponent;
import net.minecraft.world.item.ItemStack;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.entity.player.ItemTooltipEvent;
import net.minecraftforge.eventbus.api.Cancelable;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.fml.LogicalSide;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import java.util.Collections;
import java.util.List;
/**
* Fired during tooltip rendering.
* See the various subclasses for listening to specific events.
*
* @see RenderTooltipEvent.GatherComponents
* @see RenderTooltipEvent.Pre
* @see RenderTooltipEvent.Color
*/
public abstract class RenderTooltipEvent extends Event
{
@NotNull
protected final ItemStack itemStack;
protected final GuiGraphics graphics;
protected int x;
protected int y;
protected Font font;
protected final List<ClientTooltipComponent> components;
@ApiStatus.Internal
protected RenderTooltipEvent(@NotNull ItemStack itemStack, GuiGraphics graphics, int x, int y, @NotNull Font font, @NotNull List<ClientTooltipComponent> components)
{
this.itemStack = itemStack;
this.graphics = graphics;
this.components = Collections.unmodifiableList(components);
this.x = x;
this.y = y;
this.font = font;
}
/**
* {@return the item stack which the tooltip is being rendered for, or an {@linkplain ItemStack#isEmpty() empty
* item stack} if there is no associated item stack}
*/
@NotNull
public ItemStack getItemStack()
{
return itemStack;
}
/**
* {@return the graphics helper for the gui}
*/
public GuiGraphics getGraphics()
{
return this.graphics;
}
/**
* {@return the unmodifiable list of tooltip components}
*
* <p>Use {@link ItemTooltipEvent} or {@link GatherComponents} to modify tooltip contents or components.</p>
*/
@NotNull
public List<ClientTooltipComponent> getComponents()
{
return components;
}
/**
* {@return the X position of the tooltip box} By default, this is the mouse X position.
*/
public int getX()
{
return x;
}
/**
* {@return the Y position of the tooltip box} By default, this is the mouse Y position.
*/
public int getY()
{
return y;
}
/**
* {@return The font used to render the text}
*/
@NotNull
public Font getFont()
{
return font;
}
/**
* Fired when a tooltip gathers the {@link TooltipComponent}s to be rendered, before any text wrapping or processing.
* The list of components and the maximum width of the tooltip can be modified through this event.
*
* <p>This event is {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.
* If this event is cancelled, then the list of components will be empty, causing the tooltip to not be rendered and
* the corresponding {@link RenderTooltipEvent.Pre} and {@link RenderTooltipEvent.Color} to not be fired.</p>
*
* <p>This event is fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
@Cancelable
public static class GatherComponents extends Event
{
private final ItemStack itemStack;
private final int screenWidth;
private final int screenHeight;
private final List<Either<FormattedText, TooltipComponent>> tooltipElements;
private int maxWidth;
@ApiStatus.Internal
public GatherComponents(ItemStack itemStack, int screenWidth, int screenHeight, List<Either<FormattedText, TooltipComponent>> tooltipElements, int maxWidth)
{
this.itemStack = itemStack;
this.screenWidth = screenWidth;
this.screenHeight = screenHeight;
this.tooltipElements = tooltipElements;
this.maxWidth = maxWidth;
}
/**
* {@return the item stack which the tooltip is being rendered for, or an {@linkplain ItemStack#isEmpty() empty
* item stack} if there is no associated item stack}
*/
public ItemStack getItemStack()
{
return itemStack;
}
/**
* {@return the width of the screen}.
* The lines of text within the tooltip are wrapped to be within the screen width, and the tooltip box itself
* is moved to be within the screen width.
*/
public int getScreenWidth()
{
return screenWidth;
}
/**
* {@return the height of the screen}
* The tooltip box is moved to be within the screen height.
*/
public int getScreenHeight()
{
return screenHeight;
}
/**
* {@return the modifiable list of elements to be rendered on the tooltip} These elements can be either
* formatted text or custom tooltip components.
*/
public List<Either<FormattedText, TooltipComponent>> getTooltipElements()
{
return tooltipElements;
}
/**
* {@return the maximum width of the tooltip when being rendered}
*
* <p>A value of {@code -1} means an unlimited maximum width. However, an unlimited maximum width will still
* be wrapped to be within the screen bounds.</p>
*/
public int getMaxWidth()
{
return maxWidth;
}
/**
* Sets the maximum width of the tooltip. Use {@code -1} for unlimited maximum width.
*
* @param maxWidth the new maximum width
*/
public void setMaxWidth(int maxWidth)
{
this.maxWidth = maxWidth;
}
}
/**
* Fired <b>before</b> the tooltip is rendered.
* This can be used to modify the positioning and font of the tooltip.
*
* <p>This event is {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.
* If this event is cancelled, then the tooltip will not be rendered and the corresponding
* {@link RenderTooltipEvent.Color} will not be fired.</p>
*
* <p>This event is fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
@Cancelable
public static class Pre extends RenderTooltipEvent
{
private final int screenWidth;
private final int screenHeight;
private final ClientTooltipPositioner positioner;
@ApiStatus.Internal
public Pre(@NotNull ItemStack stack, GuiGraphics graphics, int x, int y, int screenWidth, int screenHeight, @NotNull Font font, @NotNull List<ClientTooltipComponent> components, @NotNull ClientTooltipPositioner positioner)
{
super(stack, graphics, x, y, font, components);
this.screenWidth = screenWidth;
this.screenHeight = screenHeight;
this.positioner = positioner;
}
/**
* {@return the width of the screen}.
* The lines of text within the tooltip are wrapped to be within the screen width, and the tooltip box itself
* is moved to be within the screen width.
*/
public int getScreenWidth()
{
return screenWidth;
}
/**
* {@return the height of the screen}
* The tooltip box is moved to be within the screen height.
*/
public int getScreenHeight()
{
return screenHeight;
}
public ClientTooltipPositioner getTooltipPositioner()
{
return positioner;
}
/**
* Sets the font to be used to render text.
*
* @param fr the new font
*/
public void setFont(@NotNull Font fr)
{
this.font = fr;
}
/**
* Sets the X origin of the tooltip.
*
* @param x the new X origin
*/
public void setX(int x)
{
this.x = x;
}
/**
* Sets the Y origin of the tooltip.
*
* @param y the new Y origin
*/
public void setY(int y)
{
this.y = y;
}
}
/**
* Fired when the colours for the tooltip background are determined.
* This can be used to modify the background color and the border's gradient colors.
*
* <p>This event is not {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.</p>
*
* <p>This event is fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
public static class Color extends RenderTooltipEvent
{
private final int originalBackground;
private final int originalBorderStart;
private final int originalBorderEnd;
private int backgroundStart;
private int backgroundEnd;
private int borderStart;
private int borderEnd;
@ApiStatus.Internal
public Color(@NotNull ItemStack stack, GuiGraphics graphics, int x, int y, @NotNull Font fr, int background, int borderStart, int borderEnd, @NotNull List<ClientTooltipComponent> components)
{
super(stack, graphics, x, y, fr, components);
this.originalBackground = background;
this.originalBorderStart = borderStart;
this.originalBorderEnd = borderEnd;
this.backgroundStart = background;
this.backgroundEnd = background;
this.borderStart = borderStart;
this.borderEnd = borderEnd;
}
/**
* {@return the gradient start color for the tooltip background (top edge)}
*/
public int getBackgroundStart()
{
return backgroundStart;
}
/**
* {@return the gradient end color for the tooltip background (bottom edge)}
*/
public int getBackgroundEnd()
{
return backgroundEnd;
}
/**
* Sets the new color for the tooltip background. This sets both the gradient start and end color for the
* background to this color.
*
* @param background the new color for the tooltip background
*/
public void setBackground(int background)
{
this.backgroundStart = background;
this.backgroundEnd = background;
}
/**
* Sets the new start color for the gradient of the tooltip background (top edge).
*
* @param backgroundStart the new start color for the tooltip background
*/
public void setBackgroundStart(int backgroundStart)
{
this.backgroundStart = backgroundStart;
}
/**
* Sets the new end color for the gradient of the tooltip background (bottom edge).
*
* @param backgroundEnd the new end color for the tooltip background
*/
public void setBackgroundEnd(int backgroundEnd)
{
this.backgroundEnd = backgroundEnd;
}
/**
* {@return the gradient start color for the tooltip border (top edge)}
*/
public int getBorderStart()
{
return borderStart;
}
/**
* Sets the new start color for the gradient of the tooltip border (top edge).
*
* @param borderStart the new start color for the tooltip border
*/
public void setBorderStart(int borderStart)
{
this.borderStart = borderStart;
}
/**
* {@return the gradient end color for the tooltip border (bottom edge)}
*/
public int getBorderEnd()
{
return borderEnd;
}
/**
* Sets the new end color for the gradient of the tooltip border (bottom edge).
*
* @param borderEnd the new end color for the tooltip border
*/
public void setBorderEnd(int borderEnd)
{
this.borderEnd = borderEnd;
}
/**
* {@return the original tooltip background's gradient start color (top edge)}
*/
public int getOriginalBackgroundStart()
{
return originalBackground;
}
/**
* {@return the original tooltip background's gradient end color (bottom edge)}
*/
public int getOriginalBackgroundEnd()
{
return originalBackground;
}
/**
* {@return the original tooltip border's gradient start color (top edge)}
*/
public int getOriginalBorderStart()
{
return originalBorderStart;
}
/**
* {@return the original tooltip border's gradient end color (bottom edge)}
*/
public int getOriginalBorderEnd()
{
return originalBorderEnd;
}
}
}

View File

@@ -0,0 +1,112 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client.event;
import com.mojang.blaze3d.platform.NativeImage;
import net.minecraft.client.Screenshot;
import net.minecraft.network.chat.Component;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.eventbus.api.Cancelable;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.fml.LogicalSide;
import org.jetbrains.annotations.ApiStatus;
import java.io.File;
import java.io.IOException;
/**
* Fired when a screenshot is taken, but before it is written to disk.
*
* <p>This event is {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.
* If this event is cancelled, then the screenshot is not written to disk, and the message in the event will be posted
* to the player's chat.</p>
*
* <p>This event is fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*
* @see Screenshot
*/
@Cancelable
public class ScreenshotEvent extends Event
{
public static final Component DEFAULT_CANCEL_REASON = Component.literal("Screenshot canceled");
private final NativeImage image;
private File screenshotFile;
private Component resultMessage = null;
@ApiStatus.Internal
public ScreenshotEvent(NativeImage image, File screenshotFile)
{
this.image = image;
this.screenshotFile = screenshotFile;
try
{
this.screenshotFile = screenshotFile.getCanonicalFile(); // FORGE: Fix errors on Windows with paths that include \.\
} catch (IOException ignored)
{
}
}
/**
* {@return the in-memory image of the screenshot}
*/
public NativeImage getImage()
{
return image;
}
/**
* @return the file where the screenshot will be saved to
*/
public File getScreenshotFile()
{
return screenshotFile;
}
/**
* Sets the new file where the screenshot will be saved to.
*
* @param screenshotFile the new filepath
*/
public void setScreenshotFile(File screenshotFile)
{
this.screenshotFile = screenshotFile;
}
/**
* {@return the custom cancellation message, or {@code null} if no custom message is set}
*/
public Component getResultMessage()
{
return resultMessage;
}
/**
* Sets the new custom cancellation message used to inform the player.
* It may be {@code null}, in which case the {@linkplain #DEFAULT_CANCEL_REASON default cancel reason} will be used.
*
* @param resultMessage the new result message
*/
public void setResultMessage(Component resultMessage)
{
this.resultMessage = resultMessage;
}
/**
* Returns the cancellation message to be used in informing the player.
*
* <p>If there is no custom message given ({@link #getResultMessage()} returns {@code null}), then
* the message will be the {@linkplain #DEFAULT_CANCEL_REASON default cancel reason message}.</p>
*
* @return the cancel message for the player
*/
public Component getCancelMessage()
{
return getResultMessage() != null ? getResultMessage() : DEFAULT_CANCEL_REASON;
}
}

View File

@@ -0,0 +1,93 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client.event;
import net.minecraft.client.renderer.texture.TextureAtlas;
import net.minecraftforge.eventbus.api.Cancelable;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.fml.LogicalSide;
import net.minecraftforge.fml.event.IModBusEvent;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import org.jetbrains.annotations.ApiStatus;
import java.util.Set;
/**
* Fired after a texture atlas is stitched together.
*
* @see TextureStitchEvent.Post
* @see TextureAtlas
*/
public class TextureStitchEvent extends Event implements IModBusEvent
{
private final TextureAtlas atlas;
@ApiStatus.Internal
public TextureStitchEvent(TextureAtlas atlas)
{
this.atlas = atlas;
}
/**
* {@return the texture atlas}
*/
public TextureAtlas getAtlas()
{
return atlas;
}
// Use atlas info JSON files instead
// /**
// * <p>Fired <b>before</b> a texture atlas is stitched together.
// * This can be used to add custom sprites to be stitched into the atlas.</p>
// *
// * <p>This event is not {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.</p>
// *
// * <p>This event is fired on the {@linkplain FMLJavaModLoadingContext#getModEventBus()} mod-specific event bus},
// * only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
// */
// public static class Pre extends TextureStitchEvent
// {
// private final Set<ResourceLocation> sprites;
//
// @ApiStatus.Internal
// public Pre(TextureAtlas map, Set<ResourceLocation> sprites)
// {
// super(map);
// this.sprites = sprites;
// }
//
// /**
// * Adds a sprite to be stitched into the texture atlas.
// *
// * <p>Callers should check that the atlas which the event is fired for is the atlas they wish to stitch the
// * sprite into, as otherwise they would be stitching the sprite into all atlases.</p>
// *
// * @param sprite the location of the sprite
// */
// public boolean addSprite(ResourceLocation sprite)
// {
// return this.sprites.add(sprite);
// }
// }
/**
* Fired <b>after</b> a texture atlas is stitched together and all textures therein has been loaded.
*
* <p>This event is not {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.</p>
*
* <p>This event is fired on the {@linkplain FMLJavaModLoadingContext#getModEventBus()} mod-specific event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
public static class Post extends TextureStitchEvent
{
@ApiStatus.Internal
public Post(TextureAtlas map)
{
super(map);
}
}
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client.event;
import net.minecraft.client.gui.components.toasts.Toast;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.eventbus.api.Cancelable;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.fml.LogicalSide;
/**
* Fired when the client queues a {@link Toast} message to be shown onscreen.
* Toasts are small popups that appear on the top right of the screen, for certain actions such as unlocking Advancements and Recipes.
*
* <p>This event is {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.
* Cancelling the event stops the toast from being queued, which means it never renders.</p>
*
* <p>This event is fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
@Cancelable
public class ToastAddEvent extends Event
{
private final Toast toast;
public ToastAddEvent(Toast toast)
{
this.toast = toast;
}
public Toast getToast()
{
return toast;
}
}

View File

@@ -0,0 +1,400 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client.event;
import com.mojang.blaze3d.shaders.FogShape;
import net.minecraft.client.Camera;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.FogRenderer.FogMode;
import net.minecraft.client.renderer.GameRenderer;
import net.minecraft.world.level.material.FogType;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.eventbus.api.Cancelable;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.fml.LogicalSide;
import org.jetbrains.annotations.ApiStatus;
/**
* Fired for hooking into the entity view rendering in {@link GameRenderer}.
* These can be used for customizing the visual features visible to the player.
* See the various subclasses for listening to different features.
*
* <p>These events are fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*
* @see RenderFog
* @see ComputeFogColor
* @see ComputeCameraAngles
* @see ComputeFov
*/
public abstract class ViewportEvent extends Event
{
private final GameRenderer renderer;
private final Camera camera;
private final double partialTick;
@ApiStatus.Internal
public ViewportEvent(GameRenderer renderer, Camera camera, double partialTick)
{
this.renderer = renderer;
this.camera = camera;
this.partialTick = partialTick;
}
/**
* {@return the game renderer}
*/
public GameRenderer getRenderer()
{
return renderer;
}
/**
* {@return the camera information}
*/
public Camera getCamera()
{
return camera;
}
/**
* {@return the partial tick}
*/
public double getPartialTick()
{
return partialTick;
}
/**
* Fired for <b>rendering</b> custom fog. The plane distances are based on the player's render distance.
*
* <p>This event is {@linkplain Cancelable cancellable}, and {@linkplain HasResult has a result}. <br/>
* The event must be cancelled for any changes to the plane distances to take effect.</p>
*
* <p>This event is fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
@Cancelable
public static class RenderFog extends ViewportEvent
{
private final FogMode mode;
private final FogType type;
private float farPlaneDistance;
private float nearPlaneDistance;
private FogShape fogShape;
@ApiStatus.Internal
public RenderFog(FogMode mode, FogType type, Camera camera, float partialTicks, float nearPlaneDistance, float farPlaneDistance, FogShape fogShape)
{
super(Minecraft.getInstance().gameRenderer, camera, partialTicks);
this.mode = mode;
this.type = type;
setFarPlaneDistance(farPlaneDistance);
setNearPlaneDistance(nearPlaneDistance);
setFogShape(fogShape);
}
/**
* {@return the mode of fog being rendered}
*/
public FogMode getMode()
{
return mode;
}
/**
* {@return the type of fog being rendered}
*/
public FogType getType()
{
return type;
}
/**
* {@return the distance to the far plane where the fog ends}
*/
public float getFarPlaneDistance()
{
return farPlaneDistance;
}
/**
* {@return the distance to the near plane where the fog starts}
*/
public float getNearPlaneDistance()
{
return nearPlaneDistance;
}
/**
* {@return the shape of the fog being rendered}
*/
public FogShape getFogShape()
{
return fogShape;
}
/**
* Sets the distance to the far plane of the fog.
*
* @param distance the new distance to the far place
* @see #scaleFarPlaneDistance(float)
*/
public void setFarPlaneDistance(float distance)
{
farPlaneDistance = distance;
}
/**
* Sets the distance to the near plane of the fog.
*
* @param distance the new distance to the near plane
* @see #scaleNearPlaneDistance(float)
*/
public void setNearPlaneDistance(float distance)
{
nearPlaneDistance = distance;
}
/**
* Sets the new shape of the fog being rendered. The new shape will only take effect if the event is cancelled.
*
* @param shape the new shape of the fog
*/
public void setFogShape(FogShape shape)
{
fogShape = shape;
}
/**
* Scales the distance to the far plane of the fog by a given factor.
*
* @param factor the factor to scale the far plane distance by
*/
public void scaleFarPlaneDistance(float factor)
{
farPlaneDistance *= factor;
}
/**
* Scales the distance to the near plane of the fog by a given factor.
*
* @param factor the factor to scale the near plane distance by
*/
public void scaleNearPlaneDistance(float factor)
{
nearPlaneDistance *= factor;
}
}
/**
* Fired for customizing the <b>color</b> of the fog visible to the player.
*
* <p>This event is not {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.</p>
*
* <p>This event is fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
public static class ComputeFogColor extends ViewportEvent
{
private float red;
private float green;
private float blue;
@ApiStatus.Internal
public ComputeFogColor(Camera camera, float partialTicks, float red, float green, float blue)
{
super(Minecraft.getInstance().gameRenderer, camera, partialTicks);
this.setRed(red);
this.setGreen(green);
this.setBlue(blue);
}
/**
* {@return the red color value of the fog}
*/
public float getRed()
{
return red;
}
/**
* Sets the new red color value of the fog.
*
* @param red the new red color value
*/
public void setRed(float red)
{
this.red = red;
}
/**
* {@return the green color value of the fog}
*/
public float getGreen()
{
return green;
}
/**
* Sets the new green color value of the fog.
*
* @param green the new blue color value
*/
public void setGreen(float green)
{
this.green = green;
}
/**
* {@return the blue color value of the fog}
*/
public float getBlue()
{
return blue;
}
/**
* Sets the new blue color value of the fog.
*
* @param blue the new blue color value
*/
public void setBlue(float blue)
{
this.blue = blue;
}
}
/**
* Fired to allow altering the angles of the player's camera.
* This can be used to alter the player's view for different effects, such as applying roll.
*
* <p>This event is not {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.</p>
*
* <p>This event is fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
public static class ComputeCameraAngles extends ViewportEvent
{
private float yaw;
private float pitch;
private float roll;
@ApiStatus.Internal
public ComputeCameraAngles(GameRenderer renderer, Camera camera, double renderPartialTicks, float yaw, float pitch, float roll)
{
super(renderer, camera, renderPartialTicks);
this.setYaw(yaw);
this.setPitch(pitch);
this.setRoll(roll);
}
/**
* {@return the yaw of the player's camera}
*/
public float getYaw()
{
return yaw;
}
/**
* Sets the yaw of the player's camera.
*
* @param yaw the new yaw
*/
public void setYaw(float yaw)
{
this.yaw = yaw;
}
/**
* {@return the pitch of the player's camera}
*/
public float getPitch()
{
return pitch;
}
/**
* Sets the pitch of the player's camera.
*
* @param pitch the new pitch
*/
public void setPitch(float pitch)
{
this.pitch = pitch;
}
/**
* {@return the roll of the player's camera}
*/
public float getRoll()
{
return roll;
}
/**
* Sets the roll of the player's camera.
*
* @param roll the new roll
*/
public void setRoll(float roll)
{
this.roll = roll;
}
}
/**
* Fired for altering the raw field of view (FOV).
* This is after the FOV settings are applied, and before modifiers such as the Nausea effect.
*
* <p>This event is not {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.</p>
*
* <p>This event is fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*
* @see ComputeFovModifierEvent
*/
public static class ComputeFov extends ViewportEvent
{
private final boolean usedConfiguredFov;
private double fov;
@ApiStatus.Internal
public ComputeFov(GameRenderer renderer, Camera camera, double renderPartialTicks, double fov, boolean usedConfiguredFov)
{
super(renderer, camera, renderPartialTicks);
this.usedConfiguredFov = usedConfiguredFov;
this.setFOV(fov);
}
/**
* {@return the raw field of view value}
*/
public double getFOV()
{
return fov;
}
/**
* Sets the field of view value.
*
* @param fov the new FOV value
*/
public void setFOV(double fov)
{
this.fov = fov;
}
/**
* {@return whether the base fov value started with a constant or was sourced from the fov set in the options}
*/
public boolean usedConfiguredFov()
{
return usedConfiguredFov;
}
}
}

View File

@@ -0,0 +1,17 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
/**
* Events fired only on the client-side, chiefly related to rendering and user interfaces (screens and input).
*/
@FieldsAreNonnullByDefault
@MethodsReturnNonnullByDefault
@ParametersAreNonnullByDefault
package net.minecraftforge.client.event;
import net.minecraft.FieldsAreNonnullByDefault;
import net.minecraft.MethodsReturnNonnullByDefault;
import javax.annotation.ParametersAreNonnullByDefault;

View File

@@ -0,0 +1,80 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client.event.sound;
import net.minecraft.client.resources.sounds.SoundInstance;
import net.minecraft.client.sounds.SoundEngine;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.eventbus.api.Cancelable;
import net.minecraftforge.fml.LogicalSide;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
/**
* Fired when a sound is about to be played by the sound engine. This fires before the sound is played and before any
* checks on the sound (such as a zeroed volume, an empty {@link net.minecraft.client.resources.sounds.Sound}, and
* others). This can be used to change or prevent (by passing {@code null)} a sound from being played through
* {@link #setSound(SoundInstance)}).
*
* <p>This event is not {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.</p>
*
* <p>This event is fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*
* @see PlaySoundSourceEvent
* @see PlayStreamingSourceEvent
*/
public class PlaySoundEvent extends SoundEvent
{
private final String name;
private final SoundInstance originalSound;
@Nullable
private SoundInstance sound;
@ApiStatus.Internal
public PlaySoundEvent(SoundEngine manager, SoundInstance sound)
{
super(manager);
this.originalSound = sound;
this.name = sound.getLocation().getPath();
this.setSound(sound);
}
/**
* {@return the name of the original sound} This is equivalent to the path of the location of the original sound.
*/
public String getName()
{
return name;
}
/**
* {@return the original sound that was to be played}
*/
public SoundInstance getOriginalSound()
{
return originalSound;
}
/**
* {@return the sound to be played, or {@code null} if no sound will be played}
*/
@Nullable
public SoundInstance getSound()
{
return sound;
}
/**
* Sets the sound to be played, which may be {@code null} to prevent any sound from being played.
*
* @param newSound the new sound to be played, or {@code null} for no sound
*/
public void setSound(@Nullable SoundInstance newSound)
{
this.sound = newSound;
}
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client.event.sound;
import com.mojang.blaze3d.audio.Channel;
import net.minecraft.client.resources.sounds.SoundInstance;
import net.minecraft.client.sounds.SoundEngine;
import net.minecraftforge.client.event.sound.SoundEvent.SoundSourceEvent;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.eventbus.api.Cancelable;
import net.minecraftforge.fml.LogicalSide;
import org.jetbrains.annotations.ApiStatus;
/**
* Fired when a <em>non-streaming</em> sound is being played. A non-streaming sound is loaded fully into memory
* in a buffer before being played, and used for most sounds of short length such as sound effects for clicking
* buttons.
*
* <p>This event is not {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.</p>
*
* <p>This event is fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*
* @see PlayStreamingSourceEvent
*/
public class PlaySoundSourceEvent extends SoundSourceEvent
{
@ApiStatus.Internal
public PlaySoundSourceEvent(SoundEngine engine, SoundInstance sound, Channel channel)
{
super(engine, sound, channel);
}
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client.event.sound;
import com.mojang.blaze3d.audio.Channel;
import net.minecraft.client.resources.sounds.SoundInstance;
import net.minecraft.client.sounds.SoundEngine;
import net.minecraftforge.client.event.sound.SoundEvent.SoundSourceEvent;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.eventbus.api.Cancelable;
import net.minecraftforge.fml.LogicalSide;
import org.jetbrains.annotations.ApiStatus;
/**
* Fired when a <em>streaming</em> sound is being played. A streaming sound is streamed directly from its source
* (such as a file), and used for sounds of long length which are unsuitable to keep fully loaded in-memory in a buffer
* (as is done for regular non-streaming sounds), such as background music or music discs.
*
* <p>This event is not {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.</p>
*
* <p>This event is fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*
* @see PlayStreamingSourceEvent
*/
public class PlayStreamingSourceEvent extends SoundSourceEvent
{
@ApiStatus.Internal
public PlayStreamingSourceEvent(SoundEngine engine, SoundInstance sound, Channel channel)
{
super(engine, sound, channel);
}
}

View File

@@ -0,0 +1,31 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client.event.sound;
import net.minecraft.client.sounds.SoundEngine;
import net.minecraftforge.eventbus.api.Cancelable;
import net.minecraftforge.fml.LogicalSide;
import net.minecraftforge.fml.event.IModBusEvent;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import org.jetbrains.annotations.ApiStatus;
/**
* Fired when the {@link SoundEngine} is constructed or (re)loaded, such as during game initialization or when the sound
* output device is changed.
*
* <p>This event is not {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.</p>
*
* <p>This event is fired on the {@linkplain FMLJavaModLoadingContext#getModEventBus() mod-specific event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
public class SoundEngineLoadEvent extends SoundEvent implements IModBusEvent
{
@ApiStatus.Internal
public SoundEngineLoadEvent(SoundEngine manager)
{
super(manager);
}
}

View File

@@ -0,0 +1,92 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client.event.sound;
import net.minecraft.client.resources.sounds.SoundInstance;
import net.minecraft.client.sounds.SoundEngine;
import com.mojang.blaze3d.audio.Channel;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.fml.LogicalSide;
import org.jetbrains.annotations.ApiStatus;
/**
* Superclass for sound related events.
*
* <p>These events are fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*
* @see SoundSourceEvent
* @see PlaySoundEvent
* @see SoundEngineLoadEvent
*/
public abstract class SoundEvent extends Event
{
private final SoundEngine engine;
@ApiStatus.Internal
protected SoundEvent(SoundEngine engine)
{
this.engine = engine;
}
/**
* {@return the sound engine}
*/
public SoundEngine getEngine()
{
return engine;
}
/**
* Superclass for when a sound has started to play on an audio channel.
*
* <p>These events are fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*
* @see PlaySoundSourceEvent
* @see PlayStreamingSourceEvent
*/
public static abstract class SoundSourceEvent extends SoundEvent
{
private final SoundInstance sound;
private final Channel channel;
private final String name;
@ApiStatus.Internal
protected SoundSourceEvent(SoundEngine engine, SoundInstance sound, Channel channel)
{
super(engine);
this.name = sound.getLocation().getPath();
this.sound = sound;
this.channel = channel;
}
/**
* {@return the sound being played}
*/
public SoundInstance getSound()
{
return sound;
}
/**
* {@return the audio channel on which the sound is playing on}
*/
public Channel getChannel()
{
return channel;
}
/**
* {@return the name of the sound being played} This is equivalent to the path of the location of the original sound.
*/
public String getName()
{
return name;
}
}
}

View File

@@ -0,0 +1,17 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
/**
* Client-only events relating to sounds.
*/
@FieldsAreNonnullByDefault
@MethodsReturnNonnullByDefault
@ParametersAreNonnullByDefault
package net.minecraftforge.client.event.sound;
import net.minecraft.FieldsAreNonnullByDefault;
import net.minecraft.MethodsReturnNonnullByDefault;
import javax.annotation.ParametersAreNonnullByDefault;

View File

@@ -0,0 +1,117 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client.extensions;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.renderer.ItemBlockRenderTypes;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.block.model.BakedQuad;
import net.minecraft.client.renderer.block.model.ItemTransforms;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.util.RandomSource;
import net.minecraft.world.item.ItemDisplayContext;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.BlockAndTintGetter;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraftforge.client.ChunkRenderTypeSet;
import net.minecraftforge.client.RenderTypeHelper;
import net.minecraftforge.client.model.data.ModelData;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
/**
* Extension interface for {@link IForgeBakedModel}.
*/
public interface IForgeBakedModel
{
private BakedModel self()
{
return (BakedModel) this;
}
/**
* A null {@link RenderType} is used for the breaking overlay as well as non-standard rendering, so models should return all their quads.
*/
@NotNull
default List<BakedQuad> getQuads(@Nullable BlockState state, @Nullable Direction side, @NotNull RandomSource rand, @NotNull ModelData data, @Nullable RenderType renderType)
{
return self().getQuads(state, side, rand);
}
default boolean useAmbientOcclusion(BlockState state)
{
return self().useAmbientOcclusion();
}
default boolean useAmbientOcclusion(BlockState state, RenderType renderType)
{
return self().useAmbientOcclusion(state);
}
/**
* Applies a transform for the given {@link ItemTransforms.TransformType} and {@code applyLeftHandTransform}, and
* returns the model to be rendered.
*/
default BakedModel applyTransform(ItemDisplayContext transformType, PoseStack poseStack, boolean applyLeftHandTransform)
{
self().getTransforms().getTransform(transformType).apply(applyLeftHandTransform, poseStack);
return self();
}
default @NotNull ModelData getModelData(@NotNull BlockAndTintGetter level, @NotNull BlockPos pos, @NotNull BlockState state, @NotNull ModelData modelData)
{
return modelData;
}
default TextureAtlasSprite getParticleIcon(@NotNull ModelData data)
{
return self().getParticleIcon();
}
/**
* Gets the set of {@link RenderType render types} to use when drawing this block in the level.
* Supported types are those returned by {@link RenderType#chunkBufferLayers()}.
* <p>
* By default, defers query to {@link ItemBlockRenderTypes}.
*/
default ChunkRenderTypeSet getRenderTypes(@NotNull BlockState state, @NotNull RandomSource rand, @NotNull ModelData data)
{
return ItemBlockRenderTypes.getRenderLayers(state);
}
/**
* Gets an ordered list of {@link RenderType render types} to use when drawing this item.
* All render types using the {@link com.mojang.blaze3d.vertex.DefaultVertexFormat#NEW_ENTITY} format are supported.
* <p>
* This method will only be called on the models returned by {@link #getRenderPasses(ItemStack, boolean)}.
* <p>
* By default, defers query to {@link ItemBlockRenderTypes}.
*
* @see #getRenderPasses(ItemStack, boolean)
*/
default List<RenderType> getRenderTypes(ItemStack itemStack, boolean fabulous)
{
return List.of(RenderTypeHelper.getFallbackItemRenderType(itemStack, self(), fabulous));
}
/**
* Gets an ordered list of baked models used to render this model as an item.
* Each of those models' render types will be queried via {@link #getRenderTypes(ItemStack, boolean)}.
* <p>
* By default, returns the model itself.
*
* @see #getRenderTypes(ItemStack, boolean)
*/
default List<BakedModel> getRenderPasses(ItemStack itemStack, boolean fabulous)
{
return List.of(self());
}
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client.extensions;
import net.minecraft.core.Direction;
import net.minecraft.world.level.BlockAndTintGetter;
/**
* Extension interface for {@link BlockAndTintGetter}.
*/
public interface IForgeBlockAndTintGetter
{
private BlockAndTintGetter self()
{
return (BlockAndTintGetter) this;
}
/**
* Computes the shade for a given normal.
* Alternate version of the vanilla method taking in a {@link Direction}.
*/
default float getShade(float normalX, float normalY, float normalZ, boolean shade)
{
return self().getShade(Direction.getNearest(normalX, normalY, normalZ), shade);
}
}

View File

@@ -0,0 +1,81 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client.extensions;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.Camera;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.renderer.DimensionSpecialEffects;
import net.minecraft.client.renderer.LightTexture;
import org.joml.Matrix4f;
import org.joml.Vector3f;
/**
* Extension interface for {@link DimensionSpecialEffects}.
*/
public interface IForgeDimensionSpecialEffects
{
private DimensionSpecialEffects self()
{
return (DimensionSpecialEffects) this;
}
/**
* Renders the clouds of this dimension.
*
* @return true to prevent vanilla cloud rendering
*/
default boolean renderClouds(ClientLevel level, int ticks, float partialTick, PoseStack poseStack, double camX, double camY, double camZ, Matrix4f projectionMatrix)
{
return false;
}
/**
* Renders the sky of this dimension.
*
* @return true to prevent vanilla sky rendering
*/
default boolean renderSky(ClientLevel level, int ticks, float partialTick, PoseStack poseStack, Camera camera, Matrix4f projectionMatrix, boolean isFoggy, Runnable setupFog)
{
return false;
}
/**
* Renders the snow and rain effects of this dimension.
*
* @return true to prevent vanilla snow and rain rendering
*/
default boolean renderSnowAndRain(ClientLevel level, int ticks, float partialTick, LightTexture lightTexture, double camX, double camY, double camZ)
{
return false;
}
/**
* Ticks the rain of this dimension.
*
* @return true to prevent vanilla rain ticking
*/
default boolean tickRain(ClientLevel level, int ticks, Camera camera)
{
return false;
}
/**
* Allows for manipulating the coloring of the lightmap texture.
* Will be called for each 16*16 combination of sky/block light values.
*
* @param level The current level (client-side).
* @param partialTicks Progress between ticks.
* @param skyDarken Current darkness of the sky (can be used to calculate sky light).
* @param blockLightRedFlicker Block light flicker factor (red color) (can be used to calculate block light).
* @param skyLight Sky light brightness (accounting for sky darkness).
* @param pixelX X-coordinate of the lightmap texture (block).
* @param pixelY Y-coordinate of the lightmap texture (sky).
* @param colors The color values that will be used: [r, g, b].
* @see LightTexture#updateLightTexture(float)
*/
default void adjustLightmapColors(ClientLevel level, float partialTicks, float skyDarken, float blockLightRedFlicker, float skyLight, int pixelX, int pixelY, Vector3f colors) {}
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client.extensions;
import net.minecraft.client.gui.Font;
import net.minecraft.network.chat.FormattedText;
/**
* Extension interface for {@link Font}.
*/
public interface IForgeFont
{
FormattedText ELLIPSIS = FormattedText.of("...");
Font self();
/**
* If the width of the text exceeds {@code maxWidth}, an ellipse is added and the text is substringed.
*
* @param text the text to ellipsize if needed
* @param maxWidth the maximum width of the text
* @return the ellipsized text
*/
default FormattedText ellipsize(FormattedText text, int maxWidth)
{
final Font self = self();
final int strWidth = self.width(text);
final int ellipsisWidth = self.width(ELLIPSIS);
if (strWidth > maxWidth)
{
if (ellipsisWidth >= maxWidth) return self.substrByWidth(text, maxWidth);
return FormattedText.composite(
self.substrByWidth(text, maxWidth - ellipsisWidth),
ELLIPSIS
);
}
return text;
}
}

View File

@@ -0,0 +1,198 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client.extensions;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.resources.ResourceLocation;
/**
* Extension interface for {@link GuiGraphics}.
*/
public interface IForgeGuiGraphics
{
private GuiGraphics self()
{
return (GuiGraphics) this;
}
int DEFAULT_BACKGROUND_COLOR = 0xF0100010;
int DEFAULT_BORDER_COLOR_START = 0x505000FF;
int DEFAULT_BORDER_COLOR_END = (DEFAULT_BORDER_COLOR_START & 0xFEFEFE) >> 1 | DEFAULT_BORDER_COLOR_START & 0xFF000000;
String UNDO_CHAR = "\u21B6";
String RESET_CHAR = "\u2604";
String VALID = "\u2714";
String INVALID = "\u2715";
int[] TEXT_COLOR_CODES = new int[] { 0, 170, 43520, 43690, 11141120, 11141290, 16755200, 11184810, 5592405, 5592575, 5635925, 5636095, 16733525, 16733695, 16777045, 16777215,
0, 42, 10752, 10794, 2752512, 2752554, 2763264, 2763306, 1381653, 1381695, 1392405, 1392447, 4134165, 4134207, 4144917, 4144959 };
default int getColorFromFormattingCharacter(char c, boolean isLighter)
{
return TEXT_COLOR_CODES[isLighter ? "0123456789abcdef".indexOf(c) : "0123456789abcdef".indexOf(c) + 16];
}
/**
* Draws a textured box of any size (smallest size is borderSize * 2 square)
* based on a fixed size textured box with continuous borders and filler.
*
* @param texture the ResourceLocation object that contains the desired image
* @param x x-axis offset
* @param y y-axis offset
* @param u bound resource location image x offset
* @param v bound resource location image y offset
* @param width the desired box width
* @param height the desired box height
* @param textureWidth the width of the box texture in the resource location image
* @param textureHeight the height of the box texture in the resource location image
* @param borderSize the size of the box's borders
*/
default void blitWithBorder(ResourceLocation texture, int x, int y, int u, int v, int width, int height, int textureWidth, int textureHeight, int borderSize)
{
this.blitWithBorder(texture, x, y, u, v, width, height, textureWidth, textureHeight, borderSize, borderSize, borderSize, borderSize);
}
/**
* Draws a textured box of any size (smallest size is borderSize * 2 square)
* based on a fixed size textured box with continuous borders and filler.
*
* @param texture the ResourceLocation object that contains the desired image
* @param x x-axis offset
* @param y y-axis offset
* @param u bound resource location image x offset
* @param v bound resource location image y offset
* @param width the desired box width
* @param height the desired box height
* @param textureWidth the width of the box texture in the resource location image
* @param textureHeight the height of the box texture in the resource location image
* @param topBorder the size of the box's top border
* @param bottomBorder the size of the box's bottom border
* @param leftBorder the size of the box's left border
* @param rightBorder the size of the box's right border
*/
default void blitWithBorder(ResourceLocation texture, int x, int y, int u, int v, int width, int height, int textureWidth, int textureHeight, int topBorder, int bottomBorder, int leftBorder, int rightBorder)
{
int fillerWidth = textureWidth - leftBorder - rightBorder;
int fillerHeight = textureHeight - topBorder - bottomBorder;
int canvasWidth = width - leftBorder - rightBorder;
int canvasHeight = height - topBorder - bottomBorder;
int xPasses = canvasWidth / fillerWidth;
int remainderWidth = canvasWidth % fillerWidth;
int yPasses = canvasHeight / fillerHeight;
int remainderHeight = canvasHeight % fillerHeight;
// Draw Border
// Top Left
self().blit(texture, x, y, u, v, leftBorder, topBorder);
// Top Right
self().blit(texture, x + leftBorder + canvasWidth, y, u + leftBorder + fillerWidth, v, rightBorder, topBorder);
// Bottom Left
self().blit(texture, x, y + topBorder + canvasHeight, u, v + topBorder + fillerHeight, leftBorder, bottomBorder);
// Bottom Right
self().blit(texture, x + leftBorder + canvasWidth, y + topBorder + canvasHeight, u + leftBorder + fillerWidth, v + topBorder + fillerHeight, rightBorder, bottomBorder);
for (int i = 0; i < xPasses + (remainderWidth > 0 ? 1 : 0); i++)
{
// Top Border
self().blit(texture, x + leftBorder + (i * fillerWidth), y, u + leftBorder, v, (i == xPasses ? remainderWidth : fillerWidth), topBorder);
// Bottom Border
self().blit(texture, x + leftBorder + (i * fillerWidth), y + topBorder + canvasHeight, u + leftBorder, v + topBorder + fillerHeight, (i == xPasses ? remainderWidth : fillerWidth), bottomBorder);
// Throw in some filler for good measure
for (int j = 0; j < yPasses + (remainderHeight > 0 ? 1 : 0); j++)
self().blit(texture, x + leftBorder + (i * fillerWidth), y + topBorder + (j * fillerHeight), u + leftBorder, v + topBorder, (i == xPasses ? remainderWidth : fillerWidth), (j == yPasses ? remainderHeight : fillerHeight));
}
// Side Borders
for (int j = 0; j < yPasses + (remainderHeight > 0 ? 1 : 0); j++)
{
// Left Border
self().blit(texture, x, y + topBorder + (j * fillerHeight), u, v + topBorder, leftBorder, (j == yPasses ? remainderHeight : fillerHeight));
// Right Border
self().blit(texture, x + leftBorder + canvasWidth, y + topBorder + (j * fillerHeight), u + leftBorder + fillerWidth, v + topBorder, rightBorder, (j == yPasses ? remainderHeight : fillerHeight));
}
}
default void blitInscribed(ResourceLocation texture, int x, int y, int boundsWidth, int boundsHeight, int rectWidth, int rectHeight)
{
this.blitInscribed(texture, x, y, boundsWidth, boundsHeight, rectWidth, rectHeight, true, true);
}
default void blitInscribed(ResourceLocation texture, int x, int y, int boundsWidth, int boundsHeight, int rectWidth, int rectHeight, boolean centerX, boolean centerY)
{
if (rectWidth * boundsHeight > rectHeight * boundsWidth)
{
int h = boundsHeight;
boundsHeight = (int) (boundsWidth * ((double) rectHeight / rectWidth));
if (centerY) y += (h - boundsHeight) / 2;
}
else
{
int w = boundsWidth;
boundsWidth = (int) (boundsHeight * ((double) rectWidth / rectHeight));
if (centerX) x += (w - boundsWidth) / 2;
}
self().blit(texture, x, y, boundsWidth, boundsHeight, 0.0f,0.0f, rectWidth, rectHeight, rectWidth, rectHeight);
}
/**
* Version of {@link GuiGraphics#blitNineSliced(ResourceLocation, int, int, int, int, int, int, int, int, int)} that supports specifying the texture's size.
*/
default void blitNineSlicedSized(ResourceLocation texture, int x, int y, int width, int height, int sliceSize, int uWidth, int vHeight, int uOffset, int vOffset,
int textureWidth, int textureHeight)
{
blitNineSlicedSized(texture, x, y, width, height, sliceSize, sliceSize, uWidth, vHeight, uOffset, vOffset, textureWidth, textureHeight);
}
/**
* Version of {@link GuiGraphics#blitNineSliced(ResourceLocation, int, int, int, int, int, int, int, int, int, int)} that supports specifying the texture's size.
*/
default void blitNineSlicedSized(ResourceLocation texture, int x, int y, int width, int height, int sliceWidth, int sliceHeight, int uWidth, int vHeight,
int uOffset, int vOffset, int textureWidth, int textureHeight)
{
blitNineSlicedSized(texture, x, y, width, height, sliceWidth, sliceHeight, sliceWidth, sliceHeight, uWidth, vHeight, uOffset, vOffset, textureWidth, textureHeight);
}
/**
* Version of {@link GuiGraphics#blitNineSliced(ResourceLocation, int, int, int, int, int, int, int, int, int, int, int, int)} that supports specifying the texture's size.
*/
default void blitNineSlicedSized(ResourceLocation texture, int x, int y, int width, int height, int cornerWidth, int cornerHeight, int edgeWidth, int edgeHeight,
int uWidth, int vHeight, int uOffset, int vOffset, int textureWidth, int textureHeight)
{
cornerWidth = Math.min(cornerWidth, width / 2);
edgeWidth = Math.min(edgeWidth, width / 2);
cornerHeight = Math.min(cornerHeight, height / 2);
edgeHeight = Math.min(edgeHeight, height / 2);
GuiGraphics self = self();
if (width == uWidth && height == vHeight)
{
self.blit(texture, x, y, uOffset, vOffset, width, height, textureWidth, textureHeight);
}
else if (height == vHeight)
{
self.blit(texture, x, y, uOffset, vOffset, cornerWidth, height, textureWidth, textureHeight);
self.blitRepeating(texture, x + cornerWidth, y, width - edgeWidth - cornerWidth, height, uOffset + cornerWidth, vOffset, uWidth - edgeWidth - cornerWidth, vHeight, textureWidth, textureHeight);
self.blit(texture, x + width - edgeWidth, y, uOffset + uWidth - edgeWidth, vOffset, edgeWidth, height, textureWidth, textureHeight);
}
else if (width == uWidth)
{
self.blit(texture, x, y, uOffset, vOffset, width, cornerHeight, textureWidth, textureHeight);
self.blitRepeating(texture, x, y + cornerHeight, width, height - edgeHeight - cornerHeight, uOffset, vOffset + cornerHeight, uWidth, vHeight - edgeHeight - cornerHeight, textureWidth, textureHeight);
self.blit(texture, x, y + height - edgeHeight, uOffset, vOffset + vHeight - edgeHeight, width, edgeHeight, textureWidth, textureHeight);
}
else
{
self.blit(texture, x, y, uOffset, vOffset, cornerWidth, cornerHeight, textureWidth, textureHeight);
self.blitRepeating(texture, x + cornerWidth, y, width - edgeWidth - cornerWidth, cornerHeight, uOffset + cornerWidth, vOffset, uWidth - edgeWidth - cornerWidth, cornerHeight, textureWidth, textureHeight);
self.blit(texture, x + width - edgeWidth, y, uOffset + uWidth - edgeWidth, vOffset, edgeWidth, cornerHeight, textureWidth, textureHeight);
self.blit(texture, x, y + height - edgeHeight, uOffset, vOffset + vHeight - edgeHeight, cornerWidth, edgeHeight, textureWidth, textureHeight);
self.blitRepeating(texture, x + cornerWidth, y + height - edgeHeight, width - edgeWidth - cornerWidth, edgeHeight, uOffset + cornerWidth, vOffset + vHeight - edgeHeight, uWidth - edgeWidth - cornerWidth, edgeHeight, textureWidth, textureHeight);
self.blit(texture, x + width - edgeWidth, y + height - edgeHeight, uOffset + uWidth - edgeWidth, vOffset + vHeight - edgeHeight, edgeWidth, edgeHeight, textureWidth, textureHeight);
self.blitRepeating(texture, x, y + cornerHeight, cornerWidth, height - edgeHeight - cornerHeight, uOffset, vOffset + cornerHeight, cornerWidth, vHeight - edgeHeight - cornerHeight, textureWidth, textureHeight);
self.blitRepeating(texture, x + cornerWidth, y + cornerHeight, width - edgeWidth - cornerWidth, height - edgeHeight - cornerHeight, uOffset + cornerWidth, vOffset + cornerHeight, uWidth - edgeWidth - cornerWidth, vHeight - edgeHeight - cornerHeight, textureWidth, textureHeight);
self.blitRepeating(texture, x + width - edgeWidth, y + cornerHeight, cornerWidth, height - edgeHeight - cornerHeight, uOffset + uWidth - edgeWidth, vOffset + cornerHeight, edgeWidth, vHeight - edgeHeight - cornerHeight, textureWidth, textureHeight);
}
}
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client.extensions;
import net.minecraft.client.KeyMapping;
import com.mojang.blaze3d.platform.InputConstants;
import net.minecraftforge.client.settings.IKeyConflictContext;
import net.minecraftforge.client.settings.KeyModifier;
import org.jetbrains.annotations.NotNull;
/**
* Extension interface for {@link KeyMapping}.
*/
public interface IForgeKeyMapping {
private KeyMapping self() {
return (KeyMapping)this;
}
@NotNull InputConstants.Key getKey();
/**
* {@return true if the key conflict context and modifier are active and the keyCode matches this binding, false otherwise}
*/
default boolean isActiveAndMatches(InputConstants.Key keyCode) {
return keyCode != InputConstants.UNKNOWN && keyCode.equals(getKey()) && getKeyConflictContext().isActive() && getKeyModifier().isActive(getKeyConflictContext());
}
default void setToDefault() {
setKeyModifierAndCode(getDefaultKeyModifier(), self().getDefaultKey());
}
void setKeyConflictContext(IKeyConflictContext keyConflictContext);
IKeyConflictContext getKeyConflictContext();
KeyModifier getDefaultKeyModifier();
KeyModifier getKeyModifier();
void setKeyModifierAndCode(KeyModifier keyModifier, InputConstants.Key keyCode);
default boolean isConflictContextAndModifierActive() {
return getKeyConflictContext().isActive() && getKeyModifier().isActive(getKeyConflictContext());
}
/**
* Returns true when one of the bindings' key codes conflicts with the other's modifier.
*/
default boolean hasKeyModifierConflict(KeyMapping other) {
if (getKeyConflictContext().conflicts(other.getKeyConflictContext()) || other.getKeyConflictContext().conflicts(getKeyConflictContext())) {
if (getKeyModifier().matches(other.getKey()) || other.getKeyModifier().matches(getKey()))
return true;
}
return false;
}
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client.extensions;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.screens.Screen;
import net.minecraftforge.client.ForgeHooksClient;
import java.util.Locale;
/**
* Extension interface for {@link IForgeMinecraft}.
*/
public interface IForgeMinecraft
{
private Minecraft self()
{
return (Minecraft) this;
}
/**
* Pushes a screen as a new GUI layer.
*
* @param screen the new GUI layer
*/
default void pushGuiLayer(Screen screen)
{
ForgeHooksClient.pushGuiLayer(self(), screen);
}
/**
* Pops a GUI layer from the screen.
*/
default void popGuiLayer()
{
ForgeHooksClient.popGuiLayer(self());
}
/**
* Retrieves the {@link Locale} set by the player.
* Useful for creating string and number formatters.
*/
default Locale getLocale()
{
return self().getLanguageManager().getJavaLocale();
}
}

View File

@@ -0,0 +1,22 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client.extensions;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.Material;
import net.minecraft.client.resources.model.ModelState;
import net.minecraft.resources.ResourceLocation;
import org.jetbrains.annotations.Nullable;
import java.util.function.Function;
public interface IForgeModelBaker
{
@Nullable
BakedModel bake(ResourceLocation location, ModelState state, Function<Material, TextureAtlasSprite> sprites);
Function<Material, TextureAtlasSprite> getModelTextureGetter();
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client.extensions;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.math.Transformation;
import org.joml.Vector3f;
/**
* Extension interface for {@link com.mojang.blaze3d.vertex.PoseStack}.
*/
public interface IForgePoseStack
{
private PoseStack self()
{
return (PoseStack) this;
}
/**
* Pushes and applies the {@code transformation} to this pose stack. <br>
* The effects of this method can be reversed by a corresponding {@link PoseStack#popPose()} call.
*
* @param transformation the transformation to push
*/
default void pushTransformation(Transformation transformation)
{
final PoseStack self = self();
self.pushPose();
Vector3f trans = transformation.getTranslation();
self.translate(trans.x(), trans.y(), trans.z());
self.mulPose(transformation.getLeftRotation());
Vector3f scale = transformation.getScale();
self.scale(scale.x(), scale.y(), scale.z());
self.mulPose(transformation.getRightRotation());
}
}

View File

@@ -0,0 +1,69 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client.extensions;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
import com.mojang.blaze3d.vertex.VertexFormatElement;
import net.minecraft.client.renderer.block.model.BakedQuad;
import net.minecraftforge.client.model.IQuadTransformer;
import org.joml.Matrix3f;
import org.joml.Vector3f;
import java.nio.ByteBuffer;
/**
* Extension interface for {@link VertexConsumer}.
*/
public interface IForgeVertexConsumer
{
private VertexConsumer self()
{
return (VertexConsumer) this;
}
/**
* Consumes an unknown {@link VertexFormatElement} as a raw int data array.
* <p>
* If the consumer needs to store the data for later use, it must copy it. There are no guarantees on immutability.
*/
default VertexConsumer misc(VertexFormatElement element, int... rawData)
{
return self();
}
/**
* Variant with no per-vertex shading.
*/
default void putBulkData(PoseStack.Pose pose, BakedQuad bakedQuad, float red, float green, float blue, float alpha, int packedLight, int packedOverlay, boolean readExistingColor)
{
self().putBulkData(pose, bakedQuad, new float[] { 1.0F, 1.0F, 1.0F, 1.0F }, red, green, blue, alpha, new int[] { packedLight, packedLight, packedLight, packedLight }, packedOverlay, readExistingColor);
}
default int applyBakedLighting(int packedLight, ByteBuffer data)
{
int bl = packedLight & 0xFFFF;
int sl = (packedLight >> 16) & 0xFFFF;
int offset = IQuadTransformer.UV2 * 4; // int offset for vertex 0 * 4 bytes per int
int blBaked = Short.toUnsignedInt(data.getShort(offset));
int slBaked = Short.toUnsignedInt(data.getShort(offset + 2));
bl = Math.max(bl, blBaked);
sl = Math.max(sl, slBaked);
return bl | (sl << 16);
}
default void applyBakedNormals(Vector3f generated, ByteBuffer data, Matrix3f normalTransform)
{
byte nx = data.get(28);
byte ny = data.get(29);
byte nz = data.get(30);
if (nx != 0 || ny != 0 || nz != 0)
{
generated.set(nx / 127f, ny / 127f, nz / 127f);
generated.mul(normalTransform);
}
}
}

View File

@@ -0,0 +1,131 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client.extensions.common;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.particle.ParticleEngine;
import net.minecraft.core.BlockPos;
import net.minecraft.tags.FluidTags;
import net.minecraft.world.effect.MobEffects;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.item.enchantment.EnchantmentHelper;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.phys.HitResult;
import net.minecraftforge.fml.LogicalSide;
import org.joml.Vector3d;
import java.util.function.Consumer;
/**
* {@linkplain LogicalSide#CLIENT Client-only} extensions to {@link Block}.
*
* @see Block#initializeClient(Consumer)
*/
public interface IClientBlockExtensions
{
IClientBlockExtensions DEFAULT = new IClientBlockExtensions() { };
static IClientBlockExtensions of(BlockState state)
{
return of(state.getBlock());
}
static IClientBlockExtensions of(Block block)
{
return block.getRenderPropertiesInternal() instanceof IClientBlockExtensions e ? e : DEFAULT;
}
/**
* Spawn a digging particle effect in the level, this is a wrapper
* around EffectRenderer.addBlockHitEffects to allow the block more
* control over the particles. Useful when you have entirely different
* texture sheets for different sides/locations in the level.
*
* @param state The current state
* @param level The current level
* @param target The target the player is looking at {x/y/z/side/sub}
* @param manager A reference to the current particle manager.
* @return True to prevent vanilla digging particles form spawning.
*/
default boolean addHitEffects(BlockState state, Level level, HitResult target, ParticleEngine manager)
{
return false;
}
/**
* Spawn particles for when the block is destroyed. Due to the nature
* of how this is invoked, the x/y/z locations are not always guaranteed
* to host your block. So be sure to do proper sanity checks before assuming
* that the location is this block.
*
* @param Level The current Level
* @param pos Position to spawn the particle
* @param manager A reference to the current particle manager.
* @return True to prevent vanilla break particles from spawning.
*/
default boolean addDestroyEffects(BlockState state, Level Level, BlockPos pos, ParticleEngine manager)
{
return !state.shouldSpawnParticlesOnBreak();
}
/**
* NOT CURRENTLY IMPLEMENTED
* <p>
* Use this to change the fog color used when the entity is "inside" a material.
* Vec3d is used here as "r/g/b" 0 - 1 values.
*
* @param level The level.
* @param pos The position at the entity viewport.
* @param state The state at the entity viewport.
* @param entity the entity
* @param originalColor The current fog color, You are not expected to use this, Return as the default if applicable.
* @return The new fog color.
*/
default Vector3d getFogColor(BlockState state, LevelReader level, BlockPos pos, Entity entity, Vector3d originalColor, float partialTick)
{
FluidState fluidState = level.getFluidState(pos);
if (fluidState.is(FluidTags.WATER))
{
float f12 = 0.0F;
if (entity instanceof LivingEntity)
{
LivingEntity ent = (LivingEntity) entity;
f12 = (float) EnchantmentHelper.getRespiration(ent) * 0.2F;
if (ent.hasEffect(MobEffects.WATER_BREATHING))
{
f12 = f12 * 0.3F + 0.6F;
}
}
return new Vector3d(0.02F + f12, 0.02F + f12, 0.2F + f12);
}
else if (fluidState.is(FluidTags.LAVA))
{
return new Vector3d(0.6F, 0.1F, 0.0F);
}
return originalColor;
}
/**
* Returns true if the breaking particles created from the {@link BlockState} passed should be tinted with biome colors.
*
* @param state The state of this block
* @param level The level the particles are spawning in
* @param pos The position of the block
* @return {@code true} if the particles should be tinted.
*/
default boolean areBreakingParticlesTinted(BlockState state, ClientLevel level, BlockPos pos)
{
return !state.is(Blocks.GRASS_BLOCK);
}
}

View File

@@ -0,0 +1,355 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client.extensions.common;
import com.mojang.blaze3d.shaders.FogShape;
import com.mojang.blaze3d.vertex.PoseStack;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.stream.Stream;
import net.minecraft.client.Camera;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.renderer.FogRenderer;
import net.minecraft.client.renderer.ScreenEffectRenderer;
import net.minecraft.core.BlockPos;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.BlockAndTintGetter;
import net.minecraft.world.level.material.Fluid;
import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.level.material.Fluids;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.FluidType;
import net.minecraftforge.fml.LogicalSide;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.joml.Vector3f;
/**
* {@linkplain LogicalSide#CLIENT Client-only} extensions to {@link FluidType}.
*
* @see FluidType#initializeClient(Consumer)
*/
public interface IClientFluidTypeExtensions
{
IClientFluidTypeExtensions DEFAULT = new IClientFluidTypeExtensions() { };
static IClientFluidTypeExtensions of(FluidState state)
{
return of(state.getFluidType());
}
static IClientFluidTypeExtensions of(Fluid fluid)
{
return of(fluid.getFluidType());
}
static IClientFluidTypeExtensions of(FluidType type)
{
return type.getRenderPropertiesInternal() instanceof IClientFluidTypeExtensions props ? props : DEFAULT;
}
/* Default Accessors */
/**
* Returns the tint applied to the fluid's textures.
*
* <p>The result represents a 32-bit integer where each 8-bits represent
* the alpha, red, green, and blue channel respectively.
*
* @return the tint applied to the fluid's textures in ARGB format
*/
default int getTintColor()
{
return 0xFFFFFFFF;
}
/**
* Returns the reference of the texture to apply to a source fluid.
*
* <p>This should return a reference to the texture and not the actual
* texture itself (e.g. {@code minecraft:block/water_still} will point to
* {@code assets/minecraft/textures/block/water_still.png}).
*
* <p>Important: This method should only return {@code null} for {@link Fluids#EMPTY}.
* All other implementations must define this property.
*
* @return the reference of the texture to apply to a source fluid
*/
default ResourceLocation getStillTexture()
{
return null;
}
/**
* Returns the reference of the texture to apply to a flowing fluid.
*
* <p>This should return a reference to the texture and not the actual
* texture itself (e.g. {@code minecraft:block/water_flow} will point to
* {@code assets/minecraft/textures/block/water_flow.png}).
*
* <p>Important: This method should only return {@code null} for {@link Fluids#EMPTY}.
* All other implementations must define this property.
*
* @return the reference of the texture to apply to a flowing fluid
*/
default ResourceLocation getFlowingTexture()
{
return null;
}
/**
* Returns the reference of the texture to apply to a fluid directly touching
* a non-opaque block other than air. If no reference is specified, either
* {@code #getStillTexture} or {@code #getFlowingTexture} will be applied
* instead.
*
* <p>This should return a reference to the texture and not the actual
* texture itself (e.g. {@code minecraft:block/water_overlay} will point to
* {@code assets/minecraft/textures/block/water_overlay.png}).
*
* @return the reference of the texture to apply to a fluid directly touching
* a non-opaque block
*/
@Nullable
default ResourceLocation getOverlayTexture()
{
return null;
}
// Add entries to assets/minecraft/atlases/blocks.json if your texture location is not already covered by the default atlas search locations.
// /**
// * Returns a stream of textures applied to a fluid.
// *
// * <p>This is used by the {@link net.minecraft.client.resources.model.ModelBakery} to load in all textures that
// * can be applied on reload.
// *
// * @return a stream of textures applied to a fluid
// */
// default Stream<ResourceLocation> getTextures()
// {
// return Stream.of(this.getStillTexture(), this.getFlowingTexture(), this.getOverlayTexture())
// .filter(Objects::nonNull);
// }
/**
* Returns the location of the texture to apply to the camera when it is
* within the fluid. If no location is specified, no overlay will be applied.
*
* <p>This should return a location to the texture and not a reference
* (e.g. {@code minecraft:textures/misc/underwater.png} will use the texture
* at {@code assets/minecraft/textures/misc/underwater.png}).
*
* @param mc the client instance
* @return the location of the texture to apply to the camera when it is
* within the fluid
*/
@Nullable
default ResourceLocation getRenderOverlayTexture(Minecraft mc)
{
return null;
}
/**
* Renders {@code #getRenderOverlayTexture} onto the camera when within
* the fluid.
*
* @param mc the client instance
* @param poseStack the transformations representing the current rendering position
*/
default void renderOverlay(Minecraft mc, PoseStack poseStack)
{
ResourceLocation texture = this.getRenderOverlayTexture(mc);
if (texture != null)
ScreenEffectRenderer.renderFluid(mc, poseStack, texture);
}
/**
* Modifies the color of the fog when the camera is within the fluid.
*
* <p>The result expects a three float vector representing the red, green,
* and blue channels respectively. Each channel should be between [0,1].
*
* @param camera the camera instance
* @param partialTick the delta time of where the current frame is within a tick
* @param level the level the camera is located in
* @param renderDistance the render distance of the client
* @param darkenWorldAmount the amount to darken the world by
* @param fluidFogColor the current color of the fog
* @return the color of the fog
*/
@NotNull
default Vector3f modifyFogColor(Camera camera, float partialTick, ClientLevel level, int renderDistance, float darkenWorldAmount, Vector3f fluidFogColor)
{
return fluidFogColor;
}
/**
* Modifies how the fog is currently being rendered when the camera is
* within a fluid.
*
* @param camera the camera instance
* @param mode the type of fog being rendered
* @param renderDistance the render distance of the client
* @param partialTick the delta time of where the current frame is within a tick
* @param nearDistance the near plane of where the fog starts to render
* @param farDistance the far plane of where the fog ends rendering
* @param shape the shape of the fog being rendered
*/
default void modifyFogRender(Camera camera, FogRenderer.FogMode mode, float renderDistance, float partialTick, float nearDistance, float farDistance, FogShape shape)
{
}
/* Level-Based Accessors */
/**
* Returns the reference of the texture to apply to a source fluid.
*
* <p>This should return a reference to the texture and not the actual
* texture itself (e.g. {@code minecraft:block/water_still} will point to
* {@code assets/minecraft/textures/block/water_still.png}).
*
* <p>Important: This method should only return {@code null} for {@link Fluids#EMPTY}.
* All other implementations must define this property.
*
* @param state the state of the fluid
* @param getter the getter the fluid can be obtained from
* @param pos the position of the fluid
* @return the reference of the texture to apply to a source fluid
*/
default ResourceLocation getStillTexture(FluidState state, BlockAndTintGetter getter, BlockPos pos)
{
return this.getStillTexture();
}
/**
* Returns the reference of the texture to apply to a flowing fluid.
*
* <p>This should return a reference to the texture and not the actual
* texture itself (e.g. {@code minecraft:block/water_flow} will point to
* {@code assets/minecraft/textures/block/water_flow.png}).
*
* <p>Important: This method should only return {@code null} for {@link Fluids#EMPTY}.
* All other implementations must define this property.
*
* @param state the state of the fluid
* @param getter the getter the fluid can be obtained from
* @param pos the position of the fluid
* @return the reference of the texture to apply to a flowing fluid
*/
default ResourceLocation getFlowingTexture(FluidState state, BlockAndTintGetter getter, BlockPos pos)
{
return this.getFlowingTexture();
}
/**
* Returns the reference of the texture to apply to a fluid directly touching
* a non-opaque block other than air. If no reference is specified, either
* {@code #getStillTexture} or {@code #getFlowingTexture} will be applied
* instead.
*
* <p>This should return a reference to the texture and not the actual
* texture itself (e.g. {@code minecraft:block/water_overlay} will point to
* {@code assets/minecraft/textures/block/water_overlay.png}).
*
* @param state the state of the fluid
* @param getter the getter the fluid can be obtained from
* @param pos the position of the fluid
* @return the reference of the texture to apply to a fluid directly touching
* a non-opaque block
*/
default ResourceLocation getOverlayTexture(FluidState state, BlockAndTintGetter getter, BlockPos pos)
{
return this.getOverlayTexture();
}
/**
* Returns the tint applied to the fluid's textures.
*
* <p>The result represents a 32-bit integer where each 8-bits represent
* the alpha, red, green, and blue channel respectively.
*
* @param state the state of the fluid
* @param getter the getter the fluid can be obtained from
* @param pos the position of the fluid
* @return the tint applied to the fluid's textures in ARGB format
*/
default int getTintColor(FluidState state, BlockAndTintGetter getter, BlockPos pos)
{
return this.getTintColor();
}
/* Stack-Based Accessors */
/**
* Returns the tint applied to the fluid's textures.
*
* <p>The result represents a 32-bit integer where each 8-bits represent
* the alpha, red, green, and blue channel respectively.
*
* @param stack the stack the fluid is in
* @return the tint applied to the fluid's textures in ARGB format
*/
default int getTintColor(FluidStack stack)
{
return this.getTintColor();
}
/**
* Returns the reference of the texture to apply to a source fluid.
*
* <p>This should return a reference to the texture and not the actual
* texture itself (e.g. {@code minecraft:block/water_still} will point to
* {@code assets/minecraft/textures/block/water_still.png}).
*
* <p>Important: This method should only return {@code null} for {@link Fluids#EMPTY}.
* All other implementations must define this property.
*
* @param stack the stack the fluid is in
* @return the reference of the texture to apply to a source fluid
*/
default ResourceLocation getStillTexture(FluidStack stack)
{
return this.getStillTexture();
}
/**
* Returns the reference of the texture to apply to a flowing fluid.
*
* <p>This should return a reference to the texture and not the actual
* texture itself (e.g. {@code minecraft:block/water_flow} will point to
* {@code assets/minecraft/textures/block/water_flow.png}).
*
* <p>Important: This method should only return {@code null} for {@link Fluids#EMPTY}.
* All other implementations must define this property.
*
* @param stack the stack the fluid is in
* @return the reference of the texture to apply to a flowing fluid
*/
default ResourceLocation getFlowingTexture(FluidStack stack)
{
return this.getFlowingTexture();
}
/**
* Returns the reference of the texture to apply to a fluid directly touching
* a non-opaque block other than air. If no reference is specified, either
* {@code #getStillTexture} or {@code #getFlowingTexture} will be applied
* instead.
*
* <p>This should return a reference to the texture and not the actual
* texture itself (e.g. {@code minecraft:block/water_overlay} will point to
* {@code assets/minecraft/textures/block/water_overlay.png}).
*
* @param stack the stack the fluid is in
* @return the reference of the texture to apply to a fluid directly touching
* a non-opaque block
*/
default ResourceLocation getOverlayTexture(FluidStack stack)
{
return this.getOverlayTexture();
}
}

View File

@@ -0,0 +1,181 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client.extensions.common;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.Font;
import net.minecraft.client.model.HumanoidModel;
import net.minecraft.client.model.Model;
import net.minecraft.client.player.LocalPlayer;
import net.minecraft.client.renderer.BlockEntityWithoutLevelRenderer;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.entity.HumanoidArm;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraftforge.client.ForgeHooksClient;
import net.minecraftforge.client.IArmPoseTransformer;
import net.minecraftforge.fml.LogicalSide;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.function.Consumer;
/**
* {@linkplain LogicalSide#CLIENT Client-only} extensions to {@link Item}.
*
* @see Item#initializeClient(Consumer)
*/
public interface IClientItemExtensions
{
IClientItemExtensions DEFAULT = new IClientItemExtensions() { };
static IClientItemExtensions of(ItemStack stack)
{
return of(stack.getItem());
}
static IClientItemExtensions of(Item item)
{
return item.getRenderPropertiesInternal() instanceof IClientItemExtensions e ? e : DEFAULT;
}
/**
* Returns the font used to render data related to this item as specified in the {@code context}.
* Return {@code null} to use the default font.
*
* @param stack The item stack
* @param context The context in which the font will be used
* @return A {@link Font} or null to use the default
*/
@Nullable
default Font getFont(ItemStack stack, FontContext context)
{
return null;
}
/**
* This method returns an ArmPose that can be defined using the {@link net.minecraft.client.model.HumanoidModel.ArmPose#create(String, boolean, IArmPoseTransformer)} method.
* This allows for creating custom item use animations.
*
* @param entityLiving The entity holding the item
* @param hand The hand the ArmPose will be applied to
* @param itemStack The stack being held
* @return A custom ArmPose that can be used to define movement of the arm
*/
@Nullable
default HumanoidModel.ArmPose getArmPose(LivingEntity entityLiving, InteractionHand hand, ItemStack itemStack)
{
return null;
}
/**
* Called right before when client applies transformations to item in hand and render it.
*
* @param poseStack The pose stack
* @param player The player holding the item, it's always main client player
* @param arm The arm holding the item
* @param itemInHand The held item
* @param partialTick Partial tick time, useful for interpolation
* @param equipProcess Equip process time, Ranging from 0.0 to 1.0. 0.0 when it's done equipping
* @param swingProcess Swing process time, Ranging from 0.0 to 1.0. 0.0 when it's done swinging
* @return true if it should skip applying other transforms and go straight to rendering
*/
default boolean applyForgeHandTransform(PoseStack poseStack, LocalPlayer player, HumanoidArm arm, ItemStack itemInHand, float partialTick, float equipProcess, float swingProcess) {
return false;
}
/**
* Queries the humanoid armor model for this item when it's equipped.
*
* @param livingEntity The entity wearing the armor
* @param itemStack The item stack
* @param equipmentSlot The slot the item is in
* @param original The original armor model. Will have attributes set.
* @return A HumanoidModel to be rendered. Relevant properties are to be copied over by the caller.
* @see #getGenericArmorModel(LivingEntity, ItemStack, EquipmentSlot, HumanoidModel)
*/
@NotNull
default HumanoidModel<?> getHumanoidArmorModel(LivingEntity livingEntity, ItemStack itemStack, EquipmentSlot equipmentSlot, HumanoidModel<?> original)
{
return original;
}
/**
* Queries the armor model for this item when it's equipped. Useful in place of
* {@link #getHumanoidArmorModel(LivingEntity, ItemStack, EquipmentSlot, HumanoidModel)} for wrapping the original
* model or returning anything non-standard.
* <p>
* If you override this method you are responsible for copying any properties you care about from the original model.
*
* @param livingEntity The entity wearing the armor
* @param itemStack The item stack
* @param equipmentSlot The slot the item is in
* @param original The original armor model. Will have attributes set.
* @return A Model to be rendered. Relevant properties must be copied over manually.
* @see #getHumanoidArmorModel(LivingEntity, ItemStack, EquipmentSlot, HumanoidModel)
*/
@NotNull
default Model getGenericArmorModel(LivingEntity livingEntity, ItemStack itemStack, EquipmentSlot equipmentSlot, HumanoidModel<?> original)
{
HumanoidModel<?> replacement = getHumanoidArmorModel(livingEntity, itemStack, equipmentSlot, original);
if (replacement != original)
{
ForgeHooksClient.copyModelProperties(original, replacement);
return replacement;
}
return original;
}
/**
* Called when the client starts rendering the HUD, and is wearing this item in the helmet slot.
* <p>
* This is where pumpkins would render their overlay.
*
* @param stack The item stack
* @param player The player entity
* @param width The viewport width
* @param height Viewport height
* @param partialTick Partial tick time, useful for interpolation
*/
default void renderHelmetOverlay(ItemStack stack, Player player, int width, int height, float partialTick)
{
}
/**
* Queries this item's renderer.
* <p>
* Only used if {@link BakedModel#isCustomRenderer()} returns {@code true} or {@link BlockState#getRenderShape()}
* returns {@link net.minecraft.world.level.block.RenderShape#ENTITYBLOCK_ANIMATED}.
* <p>
* By default, returns vanilla's block entity renderer.
*/
default BlockEntityWithoutLevelRenderer getCustomRenderer()
{
return Minecraft.getInstance().getItemRenderer().getBlockEntityRenderer();
}
enum FontContext
{
/**
* Used to display the amount of items in the {@link ItemStack}.
*/
ITEM_COUNT,
/**
* Used to display text in the {@link net.minecraft.world.inventory.tooltip.TooltipComponent}.
*/
TOOLTIP,
/**
* Used to display the item name above the hotbar when the player selects it.
*/
SELECTED_ITEM_NAME
}
}

View File

@@ -0,0 +1,106 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client.extensions.common;
import net.minecraft.client.gui.Gui;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.screens.inventory.EffectRenderingInventoryScreen;
import net.minecraft.world.effect.MobEffect;
import net.minecraft.world.effect.MobEffectInstance;
import net.minecraftforge.fml.LogicalSide;
import java.util.function.Consumer;
/**
* {@linkplain LogicalSide#CLIENT Client-only} extensions to {@link MobEffect}.
*
* @see MobEffect#initializeClient(Consumer)
*/
public interface IClientMobEffectExtensions
{
IClientMobEffectExtensions DEFAULT = new IClientMobEffectExtensions() { };
static IClientMobEffectExtensions of(MobEffectInstance instance)
{
return of(instance.getEffect());
}
static IClientMobEffectExtensions of(MobEffect effect)
{
return effect.getEffectRendererInternal() instanceof IClientMobEffectExtensions r ? r : DEFAULT;
}
/**
* Queries whether the given effect should be shown in the player's inventory.
* <p>
* By default, this returns {@code true}.
*/
default boolean isVisibleInInventory(MobEffectInstance instance)
{
return true;
}
/**
* Queries whether the given effect should be shown in the HUD.
* <p>
* By default, this returns {@code true}.
*/
default boolean isVisibleInGui(MobEffectInstance instance)
{
return true;
}
/**
* Renders the icon of the specified effect in the player's inventory.
* This can be used to render icons from your own texture sheet.
*
* @param instance The effect instance
* @param screen The effect-rendering screen
* @param guiGraphics The gui graphics
* @param x The x coordinate
* @param y The y coordinate
* @param blitOffset The blit offset
* @return true to prevent default rendering, false otherwise
*/
default boolean renderInventoryIcon(MobEffectInstance instance, EffectRenderingInventoryScreen<?> screen, GuiGraphics guiGraphics, int x, int y, int blitOffset)
{
return false;
}
/**
* Renders the text of the specified effect in the player's inventory.
*
* @param instance The effect instance
* @param screen The effect-rendering screen
* @param guiGraphics The gui graphics
* @param x The x coordinate
* @param y The y coordinate
* @param blitOffset The blit offset
* @return true to prevent default rendering, false otherwise
*/
default boolean renderInventoryText(MobEffectInstance instance, EffectRenderingInventoryScreen<?> screen, GuiGraphics guiGraphics, int x, int y, int blitOffset)
{
return false;
}
/**
* Renders the icon of the specified effect on the player's HUD.
* This can be used to render icons from your own texture sheet.
*
* @param instance The effect instance
* @param gui The gui
* @param guiGraphics The gui graphics
* @param x The x coordinate
* @param y The y coordinate
* @param z The z depth
* @param alpha The alpha value. Blinks when the effect is about to run out
* @return true to prevent default rendering, false otherwise
*/
default boolean renderGuiIcon(MobEffectInstance instance, Gui gui, GuiGraphics guiGraphics, int x, int y, float z, float alpha)
{
return false;
}
}

View File

@@ -0,0 +1,26 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
/**
* Extension interfaces for {@link net.minecraftforge.api.distmarker.Dist#CLIENT client}-only classes.
*
* <p>Extension interfaces allow the convenient addition of methods to the target class, as the target class is patched
* to implement the interface. Because of this, these interfaces must only be implemented by the target classes, and must
* not be implemented manually by mods.</p>
*
* <p>These interfaces hold at least one method: the {@code self()} method which casts the object into the type of the
* targeted class, to allow methods in the extension interface to reference methods from the target class. Additional
* methods are usually {@code default} with an implementation in the interface itself, but methods may be implemented
* in the target class instead if it requires access to (patched-in or original) fields in the instance.</p>
*/
@FieldsAreNonnullByDefault
@MethodsReturnNonnullByDefault
@ParametersAreNonnullByDefault
package net.minecraftforge.client.extensions;
import net.minecraft.FieldsAreNonnullByDefault;
import net.minecraft.MethodsReturnNonnullByDefault;
import javax.annotation.ParametersAreNonnullByDefault;

View File

@@ -0,0 +1,50 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client.gui;
import com.google.common.collect.ImmutableMap;
import net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipComponent;
import net.minecraft.world.inventory.tooltip.TooltipComponent;
import net.minecraftforge.client.event.RegisterClientTooltipComponentFactoriesEvent;
import net.minecraftforge.fml.ModLoader;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
import java.util.HashMap;
import java.util.function.Function;
/**
* Manager for {@link ClientTooltipComponent} factories.
* <p>
* Provides a lookup.
*/
public final class ClientTooltipComponentManager
{
private static ImmutableMap<Class<? extends TooltipComponent>, Function<TooltipComponent, ClientTooltipComponent>> FACTORIES;
/**
* Creates a client component for the given argument, or null if unsupported.
*/
@Nullable
public static ClientTooltipComponent createClientTooltipComponent(TooltipComponent component)
{
var factory = FACTORIES.get(component.getClass());
return factory != null ? factory.apply(component) : null;
}
@ApiStatus.Internal
public static void init()
{
var factories = new HashMap<Class<? extends TooltipComponent>, Function<TooltipComponent, ClientTooltipComponent>>();
var event = new RegisterClientTooltipComponentFactoriesEvent(factories);
ModLoader.get().postEventWrapContainerInModOrder(event);
FACTORIES = ImmutableMap.copyOf(factories);
}
private ClientTooltipComponentManager()
{
}
}

View File

@@ -0,0 +1,69 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client.gui;
import net.minecraft.world.item.CreativeModeTab;
import net.minecraftforge.common.CreativeModeTabRegistry;
import net.minecraftforge.common.util.ConcatenatedListView;
import java.util.ArrayList;
import java.util.List;
public final class CreativeTabsScreenPage
{
private final List<CreativeModeTab> tabs;
private final List<CreativeModeTab> topTabs;
private final List<CreativeModeTab> bottomTabs;
private final List<CreativeModeTab> visibleTabs;
public CreativeTabsScreenPage(List<CreativeModeTab> tabs)
{
this.tabs = tabs;
this.topTabs = new ArrayList<>();
this.bottomTabs = new ArrayList<>();
this.visibleTabs = ConcatenatedListView.of(tabs, CreativeModeTabRegistry.getDefaultTabs());
int maxLength = 10;
int topLength = maxLength / 2;
int length = tabs.size();
for (int i = 0; i < length; i++)
{
CreativeModeTab tab = tabs.get(i);
(i < topLength ? this.topTabs : this.bottomTabs).add(tab);
}
}
public List<CreativeModeTab> getVisibleTabs()
{
return this.visibleTabs.stream().filter(CreativeModeTab::shouldDisplay).toList();
}
public boolean isTop(CreativeModeTab tab)
{
if (!this.tabs.contains(tab))
return CreativeModeTabRegistry.getDefaultTabs().indexOf(tab) < (CreativeModeTabRegistry.getDefaultTabs().size() / 2);
return this.topTabs.contains(tab);
}
public int getColumn(CreativeModeTab tab)
{
// if (!this.tabs.contains(tab)) {
// return CreativeModeTabs.tabs().indexOf(tab) % 6;
// }
// return this.topTabs.contains(tab) ? this.topTabs.indexOf(tab) : this.bottomTabs.indexOf(tab);
if (!this.tabs.contains(tab))
return (CreativeModeTabRegistry.getDefaultTabs().indexOf(tab) % (CreativeModeTabRegistry.getDefaultTabs().size() / 2)) + 5;
return this.topTabs.contains(tab) ? this.topTabs.indexOf(tab) : this.bottomTabs.indexOf(tab);
}
public CreativeModeTab getDefaultTab()
{
return this.tabs.get(0);
}
}

View File

@@ -0,0 +1,161 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client.gui;
import com.google.common.base.Strings;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.Font;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.screens.ErrorScreen;
import net.minecraft.client.gui.components.ObjectSelectionList;
import net.minecraft.util.FormattedCharSequence;
import net.minecraft.Util;
import net.minecraft.network.chat.Component;
import net.minecraft.ChatFormatting;
import net.minecraftforge.common.ForgeI18n;
import net.minecraftforge.fml.LoadingFailedException;
import net.minecraftforge.fml.ModLoadingException;
import net.minecraftforge.fml.ModLoadingWarning;
import net.minecraftforge.client.gui.widget.ExtendedButton;
import net.minecraftforge.fml.loading.FMLPaths;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
public class LoadingErrorScreen extends ErrorScreen {
private static final Logger LOGGER = LogManager.getLogger();
private final Path modsDir;
private final Path logFile;
private final List<ModLoadingException> modLoadErrors;
private final List<ModLoadingWarning> modLoadWarnings;
private final Path dumpedLocation;
private LoadingEntryList entryList;
private Component errorHeader;
private Component warningHeader;
public LoadingErrorScreen(LoadingFailedException loadingException, List<ModLoadingWarning> warnings, final File dumpedLocation)
{
super(Component.literal("Loading Error"), null);
this.modLoadWarnings = warnings;
this.modLoadErrors = loadingException == null ? Collections.emptyList() : loadingException.getErrors();
this.modsDir = FMLPaths.MODSDIR.get();
this.logFile = FMLPaths.GAMEDIR.get().resolve(Paths.get("logs","latest.log"));
this.dumpedLocation = dumpedLocation != null ? dumpedLocation.toPath() : null;
}
@Override
public void init()
{
super.init();
this.clearWidgets();
this.errorHeader = Component.literal(ChatFormatting.RED + ForgeI18n.parseMessage("fml.loadingerrorscreen.errorheader", this.modLoadErrors.size()) + ChatFormatting.RESET);
this.warningHeader = Component.literal(ChatFormatting.YELLOW + ForgeI18n.parseMessage("fml.loadingerrorscreen.warningheader", this.modLoadErrors.size()) + ChatFormatting.RESET);
int yOffset = 46;
this.addRenderableWidget(new ExtendedButton(50, this.height - yOffset, this.width / 2 - 55, 20, Component.literal(ForgeI18n.parseMessage("fml.button.open.mods.folder")), b -> Util.getPlatform().openFile(modsDir.toFile())));
this.addRenderableWidget(new ExtendedButton(this.width / 2 + 5, this.height - yOffset, this.width / 2 - 55, 20, Component.literal(ForgeI18n.parseMessage("fml.button.open.file", logFile.getFileName())), b -> Util.getPlatform().openFile(logFile.toFile())));
if (this.modLoadErrors.isEmpty()) {
this.addRenderableWidget(new ExtendedButton(this.width / 4, this.height - 24, this.width / 2, 20, Component.literal(ForgeI18n.parseMessage("fml.button.continue.launch")), b -> {
this.minecraft.setScreen(null);
}));
} else {
this.addRenderableWidget(new ExtendedButton(this.width / 4, this.height - 24, this.width / 2, 20, Component.literal(ForgeI18n.parseMessage("fml.button.open.file", dumpedLocation.getFileName())), b -> Util.getPlatform().openFile(dumpedLocation.toFile())));
}
this.entryList = new LoadingEntryList(this, this.modLoadErrors, this.modLoadWarnings);
this.addWidget(this.entryList);
this.setFocused(this.entryList);
}
@Override
public void render(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTick)
{
this.renderBackground(guiGraphics);
this.entryList.render(guiGraphics, mouseX, mouseY, partialTick);
drawMultiLineCenteredString(guiGraphics, font, this.modLoadErrors.isEmpty() ? warningHeader : errorHeader, this.width / 2, 10);
this.renderables.forEach(button -> button.render(guiGraphics, mouseX, mouseY, partialTick));
}
private void drawMultiLineCenteredString(GuiGraphics guiGraphics, Font fr, Component str, int x, int y) {
for (FormattedCharSequence s : fr.split(str, this.width)) {
guiGraphics.drawString(fr, s, (float) (x - fr.width(s) / 2.0), (float) y, 0xFFFFFF, true);
y+=fr.lineHeight;
}
}
public static class LoadingEntryList extends ObjectSelectionList<LoadingEntryList.LoadingMessageEntry> {
LoadingEntryList(final LoadingErrorScreen parent, final List<ModLoadingException> errors, final List<ModLoadingWarning> warnings) {
super(Objects.requireNonNull(parent.minecraft), parent.width, parent.height, 35, parent.height - 50,
Math.max(
errors.stream().mapToInt(error -> parent.font.split(Component.literal(error.getMessage() != null ? error.getMessage() : ""), parent.width - 20).size()).max().orElse(0),
warnings.stream().mapToInt(warning -> parent.font.split(Component.literal(warning.formatToString() != null ? warning.formatToString() : ""), parent.width - 20).size()).max().orElse(0)
) * parent.minecraft.font.lineHeight + 8);
boolean both = !errors.isEmpty() && !warnings.isEmpty();
if (both)
addEntry(new LoadingMessageEntry(parent.errorHeader, true));
errors.forEach(e->addEntry(new LoadingMessageEntry(Component.literal(e.formatToString()))));
if (both) {
int maxChars = (this.width - 10) / parent.minecraft.font.width("-");
addEntry(new LoadingMessageEntry(Component.literal("\n" + Strings.repeat("-", maxChars) + "\n")));
addEntry(new LoadingMessageEntry(parent.warningHeader, true));
}
warnings.forEach(w->addEntry(new LoadingMessageEntry(Component.literal(w.formatToString()))));
}
@Override
protected int getScrollbarPosition()
{
return this.getRight() - 6;
}
@Override
public int getRowWidth()
{
return this.width;
}
public class LoadingMessageEntry extends ObjectSelectionList.Entry<LoadingMessageEntry> {
private final Component message;
private final boolean center;
LoadingMessageEntry(final Component message) {
this(message, false);
}
LoadingMessageEntry(final Component message, final boolean center) {
this.message = Objects.requireNonNull(message);
this.center = center;
}
@Override
public Component getNarration() {
return Component.translatable("narrator.select", message);
}
@Override
public void render(GuiGraphics guiGraphics, int entryIdx, int top, int left, final int entryWidth, final int entryHeight, final int mouseX, final int mouseY, final boolean p_194999_5_, final float partialTick) {
Font font = Minecraft.getInstance().font;
final List<FormattedCharSequence> strings = font.split(message, LoadingEntryList.this.width - 20);
int y = top + 2;
for (FormattedCharSequence string : strings)
{
if (center)
guiGraphics.drawString(font, string, left + (width / 2F) - font.width(string) / 2F, (float) y, 0xFFFFFF, false);
else
guiGraphics.drawString(font, string, left + 5, y, 0xFFFFFF, false);
y += font.lineHeight;
}
}
}
}
}

View File

@@ -0,0 +1,534 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client.gui;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map.Entry;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
import com.mojang.logging.LogUtils;
import net.minecraft.client.gui.narration.NarrationElementOutput;
import net.minecraft.server.packs.resources.IoSupplier;
import net.minecraftforge.client.ConfigScreenHandler;
import net.minecraftforge.client.gui.widget.ModListWidget;
import net.minecraftforge.client.gui.widget.ScrollPanel;
import net.minecraftforge.fml.loading.moddiscovery.ModFileInfo;
import net.minecraftforge.resource.PathPackResources;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.maven.artifact.versioning.ComparableVersion;
import com.mojang.blaze3d.systems.RenderSystem;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.Font;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.client.gui.components.EditBox;
import net.minecraft.client.gui.components.Button;
import net.minecraft.client.gui.components.ObjectSelectionList;
import com.mojang.blaze3d.vertex.Tesselator;
import net.minecraft.client.renderer.texture.DynamicTexture;
import com.mojang.blaze3d.platform.NativeImage;
import net.minecraft.client.renderer.texture.TextureManager;
import net.minecraft.util.FormattedCharSequence;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.Util;
import net.minecraftforge.common.ForgeHooks;
import net.minecraftforge.common.util.Size2i;
import net.minecraftforge.common.ForgeI18n;
import net.minecraftforge.common.util.MavenVersionStringHelper;
import net.minecraftforge.fml.ModContainer;
import net.minecraftforge.fml.ModList;
import net.minecraftforge.fml.VersionChecker;
import net.minecraftforge.fml.loading.FMLPaths;
import net.minecraftforge.fml.loading.StringUtils;
import net.minecraftforge.resource.ResourcePackLoader;
import net.minecraftforge.forgespi.language.IModInfo;
import net.minecraft.locale.Language;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.Style;
import org.slf4j.Logger;
import net.minecraft.client.gui.narration.NarratableEntry.NarrationPriority;
public class ModListScreen extends Screen
{
private static String stripControlCodes(String value) { return net.minecraft.util.StringUtil.stripColor(value); }
private static final Logger LOGGER = LogUtils.getLogger();
private enum SortType implements Comparator<IModInfo>
{
NORMAL,
A_TO_Z{ @Override protected int compare(String name1, String name2){ return name1.compareTo(name2); }},
Z_TO_A{ @Override protected int compare(String name1, String name2){ return name2.compareTo(name1); }};
Button button;
protected int compare(String name1, String name2){ return 0; }
@Override
public int compare(IModInfo o1, IModInfo o2) {
String name1 = StringUtils.toLowerCase(stripControlCodes(o1.getDisplayName()));
String name2 = StringUtils.toLowerCase(stripControlCodes(o2.getDisplayName()));
return compare(name1, name2);
}
Component getButtonText() {
return Component.translatable("fml.menu.mods." + StringUtils.toLowerCase(name()));
}
}
private static final int PADDING = 6;
private static final int BUTTON_MARGIN = 1;
private static final int NUM_BUTTONS = SortType.values().length;
private final Screen parentScreen;
private ModListWidget modList;
private InfoPanel modInfo;
private ModListWidget.ModEntry selected = null;
private int listWidth;
private List<IModInfo> mods;
private final List<IModInfo> unsortedMods;
private Button configButton, openModsFolderButton, doneButton;
private String lastFilterText = "";
private EditBox search;
private boolean sorted = false;
private SortType sortType = SortType.NORMAL;
public ModListScreen(Screen parentScreen)
{
super(Component.translatable("fml.menu.mods.title"));
this.parentScreen = parentScreen;
this.mods = ModList.get().getMods();
this.unsortedMods = List.copyOf(this.mods);
}
class InfoPanel extends ScrollPanel {
private ResourceLocation logoPath;
private Size2i logoDims = new Size2i(0, 0);
private List<FormattedCharSequence> lines = Collections.emptyList();
InfoPanel(Minecraft mcIn, int widthIn, int heightIn, int topIn)
{
super(mcIn, widthIn, heightIn, topIn, modList.getRight() + PADDING);
}
void setInfo(List<String> lines, ResourceLocation logoPath, Size2i logoDims)
{
this.logoPath = logoPath;
this.logoDims = logoDims;
this.lines = resizeContent(lines);
}
void clearInfo()
{
this.logoPath = null;
this.logoDims = new Size2i(0, 0);
this.lines = Collections.emptyList();
}
private List<FormattedCharSequence> resizeContent(List<String> lines)
{
List<FormattedCharSequence> ret = new ArrayList<>();
for (String line : lines)
{
if (line == null)
{
ret.add(null);
continue;
}
Component chat = ForgeHooks.newChatWithLinks(line, false);
int maxTextLength = this.width - 12;
if (maxTextLength >= 0)
{
ret.addAll(Language.getInstance().getVisualOrder(font.getSplitter().splitLines(chat, maxTextLength, Style.EMPTY)));
}
}
return ret;
}
@Override
public int getContentHeight()
{
int height = 50;
height += (lines.size() * font.lineHeight);
if (height < this.bottom - this.top - 8)
height = this.bottom - this.top - 8;
return height;
}
@Override
protected int getScrollAmount()
{
return font.lineHeight * 3;
}
@Override
protected void drawPanel(GuiGraphics guiGraphics, int entryRight, int relativeY, Tesselator tess, int mouseX, int mouseY)
{
if (logoPath != null) {
RenderSystem.enableBlend();
RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F);
// Draw the logo image inscribed in a rectangle with width entryWidth (minus some padding) and height 50
int headerHeight = 50;
guiGraphics.blitInscribed(logoPath, left + PADDING, relativeY, width - (PADDING * 2), headerHeight, logoDims.width, logoDims.height, false, true);
relativeY += headerHeight + PADDING;
}
for (FormattedCharSequence line : lines)
{
if (line != null)
{
RenderSystem.enableBlend();
guiGraphics.drawString(ModListScreen.this.font, line, left + PADDING, relativeY, 0xFFFFFF);
RenderSystem.disableBlend();
}
relativeY += font.lineHeight;
}
final Style component = findTextLine(mouseX, mouseY);
if (component!=null) {
guiGraphics.renderComponentHoverEffect(ModListScreen.this.font, component, mouseX, mouseY);
}
}
private Style findTextLine(final int mouseX, final int mouseY) {
if (!isMouseOver(mouseX, mouseY))
return null;
double offset = (mouseY - top - PADDING - border) + scrollDistance;
if (logoPath != null) {
offset -= 50;
}
if (offset <= 0)
return null;
int lineIdx = (int) (offset / font.lineHeight);
if (lineIdx >= lines.size() || lineIdx < 0)
return null;
FormattedCharSequence line = lines.get(lineIdx);
if (line != null)
{
return font.getSplitter().componentStyleAtWidth(line, mouseX - left - border);
}
return null;
}
@Override
public boolean mouseClicked(final double mouseX, final double mouseY, final int button) {
final Style component = findTextLine((int) mouseX, (int) mouseY);
if (component != null) {
ModListScreen.this.handleComponentClicked(component);
return true;
}
return super.mouseClicked(mouseX, mouseY, button);
}
@Override
public NarrationPriority narrationPriority() {
return NarrationPriority.NONE;
}
@Override
public void updateNarration(NarrationElementOutput p_169152_) {
}
}
@Override
public void init()
{
for (IModInfo mod : mods)
{
listWidth = Math.max(listWidth,getFontRenderer().width(mod.getDisplayName()) + 10);
listWidth = Math.max(listWidth,getFontRenderer().width(MavenVersionStringHelper.artifactVersionToString(mod.getVersion())) + 5);
}
listWidth = Math.max(Math.min(listWidth, width / 3), 100);
listWidth += listWidth % NUM_BUTTONS != 0 ? (NUM_BUTTONS - listWidth % NUM_BUTTONS) : 0;
int modInfoWidth = this.width - this.listWidth - (PADDING * 3);
int doneButtonWidth = Math.min(modInfoWidth, 200);
int y = this.height - 20 - PADDING;
int fullButtonHeight = PADDING + 20 + PADDING;
doneButton = Button.builder(Component.translatable("gui.done"), b -> ModListScreen.this.onClose())
.bounds(((listWidth + PADDING + this.width - doneButtonWidth) / 2), y, doneButtonWidth, 20)
.build();
openModsFolderButton = Button.builder(Component.translatable("fml.menu.mods.openmodsfolder"), b -> Util.getPlatform().openFile(FMLPaths.MODSDIR.get().toFile()))
.bounds(6, y, this.listWidth, 20)
.build();
y -= 20 + PADDING;
configButton = Button.builder(Component.translatable("fml.menu.mods.config"), b -> ModListScreen.this.displayModConfig())
.bounds(6, y, this.listWidth, 20)
.build();
y -= 14 + PADDING;
search = new EditBox(getFontRenderer(), PADDING + 1, y, listWidth - 2, 14, Component.translatable("fml.menu.mods.search"));
this.modList = new ModListWidget(this, listWidth, fullButtonHeight, search.getY() - getFontRenderer().lineHeight - PADDING);
this.modList.setLeftPos(6);
this.modInfo = new InfoPanel(this.minecraft, modInfoWidth, this.height - PADDING - fullButtonHeight, PADDING);
this.addRenderableWidget(modList);
this.addRenderableWidget(modInfo);
this.addRenderableWidget(search);
this.addRenderableWidget(doneButton);
this.addRenderableWidget(configButton);
this.addRenderableWidget(openModsFolderButton);
search.setFocused(false);
search.setCanLoseFocus(true);
configButton.active = false;
final int width = listWidth / NUM_BUTTONS;
int x = PADDING;
addRenderableWidget(SortType.NORMAL.button = Button.builder(SortType.NORMAL.getButtonText(), b -> resortMods(SortType.NORMAL))
.bounds(x, PADDING, width - BUTTON_MARGIN, 20)
.build());
x += width + BUTTON_MARGIN;
addRenderableWidget(SortType.A_TO_Z.button = Button.builder(SortType.A_TO_Z.getButtonText(), b -> resortMods(SortType.A_TO_Z))
.bounds(x, PADDING, width - BUTTON_MARGIN, 20)
.build());
x += width + BUTTON_MARGIN;
addRenderableWidget(SortType.Z_TO_A.button = Button.builder(SortType.Z_TO_A.getButtonText(), b -> resortMods(SortType.Z_TO_A))
.bounds(x, PADDING, width - BUTTON_MARGIN, 20)
.build());
resortMods(SortType.NORMAL);
updateCache();
}
private void displayModConfig()
{
if (selected == null) return;
try
{
ConfigScreenHandler.getScreenFactoryFor(selected.getInfo())
.map(f -> f.apply(this.minecraft, this))
.ifPresent(newScreen -> this.minecraft.setScreen(newScreen));
}
catch (final Exception e)
{
LOGGER.error("There was a critical issue trying to build the config GUI for {}", selected.getInfo().getModId(), e);
}
}
@Override
public void tick()
{
search.tick();
modList.setSelected(selected);
if (!search.getValue().equals(lastFilterText))
{
reloadMods();
sorted = false;
}
if (!sorted)
{
reloadMods();
mods.sort(sortType);
modList.refreshList();
if (selected != null)
{
selected = modList.children().stream()
.filter(e -> e.getInfo() == selected.getInfo())
.findFirst()
.orElse(null);
updateCache();
}
sorted = true;
}
}
public <T extends ObjectSelectionList.Entry<T>> void buildModList(Consumer<T> modListViewConsumer, Function<IModInfo, T> newEntry)
{
for (IModInfo mod : mods) {
modListViewConsumer.accept(newEntry.apply(mod));
}
}
private void reloadMods()
{
this.mods = this.unsortedMods
.stream()
.filter(mi ->
StringUtils.toLowerCase(stripControlCodes(mi.getDisplayName()))
.contains(StringUtils.toLowerCase(search.getValue()))
).collect(Collectors.toList());
lastFilterText = search.getValue();
}
private void resortMods(SortType newSort)
{
this.sortType = newSort;
for (SortType sort : SortType.values())
{
if (sort.button != null)
sort.button.active = sortType != sort;
}
sorted = false;
}
@Override
public void render(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTick)
{
this.modList.render(guiGraphics, mouseX, mouseY, partialTick);
if (this.modInfo != null)
this.modInfo.render(guiGraphics, mouseX, mouseY, partialTick);
Component text = Component.translatable("fml.menu.mods.search");
int x = modList.getLeft() + ((modList.getRight() - modList.getLeft()) / 2) - (getFontRenderer().width(text) / 2);
this.search.render(guiGraphics, mouseX , mouseY, partialTick);
super.render(guiGraphics, mouseX, mouseY, partialTick);
guiGraphics.drawString(getFontRenderer(), text.getVisualOrderText(), x, search.getY() - getFontRenderer().lineHeight, 0xFFFFFF, false);
}
public Minecraft getMinecraftInstance()
{
return minecraft;
}
public Font getFontRenderer()
{
return font;
}
public void setSelected(ModListWidget.ModEntry entry)
{
this.selected = entry == this.selected ? null : entry;
updateCache();
}
private void updateCache()
{
if (selected == null) {
this.configButton.active = false;
this.modInfo.clearInfo();
return;
}
IModInfo selectedMod = selected.getInfo();
this.configButton.active = ConfigScreenHandler.getScreenFactoryFor(selectedMod).isPresent();
List<String> lines = new ArrayList<>();
VersionChecker.CheckResult vercheck = VersionChecker.getResult(selectedMod);
@SuppressWarnings("resource")
Pair<ResourceLocation, Size2i> logoData = selectedMod.getLogoFile().map(logoFile->
{
TextureManager tm = this.minecraft.getTextureManager();
final PathPackResources resourcePack = ResourcePackLoader.getPackFor(selectedMod.getModId())
.orElse(ResourcePackLoader.getPackFor("forge").
orElseThrow(()->new RuntimeException("Can't find forge, WHAT!")));
try
{
NativeImage logo = null;
IoSupplier<InputStream> logoResource = resourcePack.getRootResource(logoFile);
if (logoResource != null)
logo = NativeImage.read(logoResource.get());
if (logo != null)
{
return Pair.of(tm.register("modlogo", new DynamicTexture(logo) {
@Override
public void upload() {
this.bind();
NativeImage td = this.getPixels();
// Use custom "blur" value which controls texture filtering (nearest-neighbor vs linear)
this.getPixels().upload(0, 0, 0, 0, 0, td.getWidth(), td.getHeight(), selectedMod.getLogoBlur(), false, false, false);
}
}), new Size2i(logo.getWidth(), logo.getHeight()));
}
}
catch (IOException e) { }
return Pair.<ResourceLocation, Size2i>of(null, new Size2i(0, 0));
}).orElse(Pair.of(null, new Size2i(0, 0)));
lines.add(selectedMod.getDisplayName());
lines.add(ForgeI18n.parseMessage("fml.menu.mods.info.version", MavenVersionStringHelper.artifactVersionToString(selectedMod.getVersion())));
lines.add(ForgeI18n.parseMessage("fml.menu.mods.info.idstate", selectedMod.getModId(), ModList.get().getModContainerById(selectedMod.getModId()).
map(ModContainer::getCurrentState).map(Object::toString).orElse("NONE")));
selectedMod.getConfig().getConfigElement("credits").ifPresent(credits->
lines.add(ForgeI18n.parseMessage("fml.menu.mods.info.credits", credits)));
selectedMod.getConfig().getConfigElement("authors").ifPresent(authors ->
lines.add(ForgeI18n.parseMessage("fml.menu.mods.info.authors", authors)));
selectedMod.getConfig().getConfigElement("displayURL").ifPresent(displayURL ->
lines.add(ForgeI18n.parseMessage("fml.menu.mods.info.displayurl", displayURL)));
if (selectedMod.getOwningFile() == null || selectedMod.getOwningFile().getMods().size() == 1)
lines.add(ForgeI18n.parseMessage("fml.menu.mods.info.nochildmods"));
else
lines.add(ForgeI18n.parseMessage("fml.menu.mods.info.childmods", selectedMod.getOwningFile().getMods().stream().map(IModInfo::getDisplayName).collect(Collectors.joining(","))));
if (vercheck.status().isOutdated())
lines.add(ForgeI18n.parseMessage("fml.menu.mods.info.updateavailable", vercheck.url() == null ? "" : vercheck.url()));
lines.add(ForgeI18n.parseMessage("fml.menu.mods.info.license", ((ModFileInfo) selectedMod.getOwningFile()).getLicense()));
lines.add(null);
lines.add(selectedMod.getDescription());
/* Removed because people bitched that this information was misleading.
lines.add(null);
if (FMLEnvironment.secureJarsEnabled) {
lines.add(ForgeI18getOwningFile().getFile().n.parseMessage("fml.menu.mods.info.signature", selectedMod.getOwningFile().getCodeSigningFingerprint().orElse(ForgeI18n.parseMessage("fml.menu.mods.info.signature.unsigned"))));
lines.add(ForgeI18n.parseMessage("fml.menu.mods.info.trust", selectedMod.getOwningFile().getTrustData().orElse(ForgeI18n.parseMessage("fml.menu.mods.info.trust.noauthority"))));
} else {
lines.add(ForgeI18n.parseMessage("fml.menu.mods.info.securejardisabled"));
}
*/
if (vercheck.status().isOutdated() && !vercheck.changes().isEmpty())
{
lines.add(null);
lines.add(ForgeI18n.parseMessage("fml.menu.mods.info.changelogheader"));
for (Entry<ComparableVersion, String> entry : vercheck.changes().entrySet())
{
lines.add(" " + entry.getKey() + ":");
lines.add(entry.getValue());
lines.add(null);
}
}
modInfo.setInfo(lines, logoData.getLeft(), logoData.getRight());
}
@Override
public void resize(Minecraft mc, int width, int height)
{
String s = this.search.getValue();
SortType sort = this.sortType;
ModListWidget.ModEntry selected = this.selected;
this.init(mc, width, height);
this.search.setValue(s);
this.selected = selected;
if (!this.search.getValue().isEmpty())
reloadMods();
if (sort != SortType.NORMAL)
resortMods(sort);
updateCache();
}
@Override
public void onClose()
{
this.minecraft.setScreen(this.parentScreen);
}
}

View File

@@ -0,0 +1,294 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client.gui;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.stream.Collectors;
import net.minecraftforge.client.gui.widget.ScrollPanel;
import org.apache.commons.lang3.tuple.Pair;
import com.mojang.blaze3d.vertex.Tesselator;
import net.minecraft.ChatFormatting;
import net.minecraft.Util;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.components.Button;
import net.minecraft.client.gui.components.MultiLineLabel;
import net.minecraft.client.gui.narration.NarrationElementOutput;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.network.chat.ClickEvent;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.HoverEvent;
import net.minecraft.network.chat.HoverEvent.Action;
import net.minecraft.network.chat.MutableComponent;
import net.minecraft.network.chat.Style;
import net.minecraft.network.chat.TextColor;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.FormattedCharSequence;
import net.minecraftforge.common.ForgeI18n;
import net.minecraftforge.fml.ModList;
import net.minecraftforge.fml.loading.FMLPaths;
import net.minecraftforge.network.ConnectionData.ModMismatchData;
import net.minecraftforge.network.NetworkRegistry;
import net.minecraft.client.gui.narration.NarratableEntry.NarrationPriority;
public class ModMismatchDisconnectedScreen extends Screen
{
private final Component reason;
private MultiLineLabel message = MultiLineLabel.EMPTY;
private final Screen parent;
private int textHeight;
private final ModMismatchData modMismatchData;
private final Path modsDir;
private final Path logFile;
private final int listHeight;
private final Map<ResourceLocation, Pair<String, String>> presentModData;
private final List<ResourceLocation> missingModData;
private final Map<ResourceLocation, String> mismatchedModData;
private final List<String> allModIds;
private final Map<String, String> presentModUrls;
private final boolean mismatchedDataFromServer;
public ModMismatchDisconnectedScreen(Screen parentScreen, Component title, Component reason, ModMismatchData modMismatchData)
{
super(title);
this.parent = parentScreen;
this.reason = reason;
this.modMismatchData = modMismatchData;
this.modsDir = FMLPaths.MODSDIR.get();
this.logFile = FMLPaths.GAMEDIR.get().resolve(Paths.get("logs","latest.log"));
this.listHeight = modMismatchData.containsMismatches() ? 140 : 0;
this.mismatchedDataFromServer = modMismatchData.mismatchedDataFromServer();
this.presentModData = modMismatchData.presentModData();
this.missingModData = modMismatchData.mismatchedModData().entrySet().stream().filter(e -> e.getValue().equals(NetworkRegistry.ABSENT.version())).map(Entry::getKey).collect(Collectors.toList());
this.mismatchedModData = modMismatchData.mismatchedModData().entrySet().stream().filter(e -> !e.getValue().equals(NetworkRegistry.ABSENT.version())).collect(Collectors.toMap(Entry::getKey, Entry::getValue));
this.allModIds = presentModData.keySet().stream().map(ResourceLocation::getNamespace).distinct().collect(Collectors.toList());
this.presentModUrls = ModList.get().getMods().stream().filter(info -> allModIds.contains(info.getModId())).map(info -> Pair.of(info.getModId(), (String)info.getConfig().getConfigElement("displayURL").orElse(""))).collect(Collectors.toMap(Pair::getLeft, Pair::getRight));
}
@Override
protected void init()
{
this.message = MultiLineLabel.create(this.font, this.reason, this.width - 50);
this.textHeight = this.message.getLineCount() * 9;
int listLeft = Math.max(8, this.width / 2 - 220);
int listWidth = Math.min(440, this.width - 16);
int upperButtonHeight = Math.min((this.height + this.listHeight + this.textHeight) / 2 + 10, this.height - 50);
int lowerButtonHeight = Math.min((this.height + this.listHeight + this.textHeight) / 2 + 35, this.height - 25);
if (modMismatchData.containsMismatches())
this.addRenderableWidget(new MismatchInfoPanel(minecraft, listWidth, listHeight, (this.height - this.listHeight) / 2, listLeft));
int buttonWidth = Math.min(210, this.width / 2 - 20);
this.addRenderableWidget(Button.builder(Component.literal(ForgeI18n.parseMessage("fml.button.open.file", logFile.getFileName())), button -> Util.getPlatform().openFile(logFile.toFile()))
.bounds(Math.max(this.width / 4 - buttonWidth / 2, listLeft), upperButtonHeight, buttonWidth, 20)
.build());
this.addRenderableWidget(Button.builder(Component.literal(ForgeI18n.parseMessage("fml.button.open.mods.folder")), button -> Util.getPlatform().openFile(modsDir.toFile()))
.bounds(Math.min(this.width * 3 / 4 - buttonWidth / 2, listLeft + listWidth - buttonWidth), upperButtonHeight, buttonWidth, 20)
.build());
this.addRenderableWidget(Button.builder(Component.translatable("gui.toMenu"), button -> this.minecraft.setScreen(this.parent))
.bounds((this.width - buttonWidth) / 2, lowerButtonHeight, buttonWidth, 20)
.build());
}
@Override
public void render(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTicks)
{
this.renderBackground(guiGraphics);
int textYOffset = modMismatchData.containsMismatches() ? 18 : 0;
guiGraphics.drawCenteredString(this.font, this.title, this.width / 2, (this.height - this.listHeight - this.textHeight) / 2 - textYOffset - 9 * 2, 0xAAAAAA);
this.message.renderCentered(guiGraphics, this.width / 2, (this.height - this.listHeight - this.textHeight) / 2 - textYOffset);
super.render(guiGraphics, mouseX, mouseY, partialTicks);
}
class MismatchInfoPanel extends ScrollPanel
{
private final List<Pair<FormattedCharSequence, Pair<FormattedCharSequence, FormattedCharSequence>>> lineTable;
private final int contentSize;
private final int nameIndent = 10;
private final int tableWidth = width - border * 2 - 6 - nameIndent;
private final int nameWidth = tableWidth * 3 / 5;
private final int versionWidth = (tableWidth - nameWidth) / 2;
public MismatchInfoPanel(Minecraft client, int width, int height, int top, int left)
{
super(client, width, height, top, left);
//The raw list of the strings in a table row, the components may still be too long for the final table and will be split up later. The first row element may have a style assigned to it that will be used for the whole content row.
List<Pair<MutableComponent, Pair<String, String>>> rawTable = new ArrayList<>();
if (!missingModData.isEmpty())
{
//The header of the section, colored in gray
rawTable.add(Pair.of(Component.literal(ForgeI18n.parseMessage(mismatchedDataFromServer ? "fml.modmismatchscreen.missingmods.server" : "fml.modmismatchscreen.missingmods.client")).withStyle(ChatFormatting.GRAY), null));
//This table section contains the mod name and mod version of each mod that has a missing remote counterpart (if the mod is missing on the server, the client mod version is displayed, and vice versa)
rawTable.add(Pair.of(Component.literal(ForgeI18n.parseMessage("fml.modmismatchscreen.table.modname")).withStyle(ChatFormatting.UNDERLINE), Pair.of("", ForgeI18n.parseMessage(mismatchedDataFromServer ? "fml.modmismatchscreen.table.youhave" : "fml.modmismatchscreen.table.youneed"))));
int i = 0;
for (ResourceLocation mod : missingModData) {
rawTable.add(Pair.of(toModNameComponent(mod, presentModData.get(mod).getLeft(), i), Pair.of("", presentModData.getOrDefault(mod, Pair.of("", "")).getRight())));
if (++i >= 10) {
//If too many missing mod entries are present, append a line referencing how to see the full list and stop rendering any more entries
rawTable.add(Pair.of(Component.literal(ForgeI18n.parseMessage("fml.modmismatchscreen.additional", missingModData.size() - i)).withStyle(ChatFormatting.ITALIC), Pair.of("", "")));
break;
}
}
rawTable.add(Pair.of(Component.literal(" "), null)); //padding
}
if (!mismatchedModData.isEmpty())
{
//The header of the table section, colored in gray
rawTable.add(Pair.of(Component.literal(ForgeI18n.parseMessage("fml.modmismatchscreen.mismatchedmods")).withStyle(ChatFormatting.GRAY), null));
//This table section contains the mod name and both mod versions of each mod that has a mismatching client and server version
rawTable.add(Pair.of(Component.literal(ForgeI18n.parseMessage("fml.modmismatchscreen.table.modname")).withStyle(ChatFormatting.UNDERLINE), Pair.of(ForgeI18n.parseMessage(mismatchedDataFromServer ? "fml.modmismatchscreen.table.youhave" : "fml.modmismatchscreen.table.serverhas"), ForgeI18n.parseMessage(mismatchedDataFromServer ? "fml.modmismatchscreen.table.serverhas" : "fml.modmismatchscreen.table.youhave"))));
int i = 0;
for (Map.Entry<ResourceLocation, String> modData : mismatchedModData.entrySet()) {
rawTable.add(Pair.of(toModNameComponent(modData.getKey(), presentModData.get(modData.getKey()).getLeft(), i), Pair.of(presentModData.getOrDefault(modData.getKey(), Pair.of("", "")).getRight(), modData.getValue())));
if (++i >= 10) {
//If too many mismatched mod entries are present, append a line referencing how to see the full list and stop rendering any more entries
rawTable.add(Pair.of(Component.literal(ForgeI18n.parseMessage("fml.modmismatchscreen.additional", mismatchedModData.size() - i)).withStyle(ChatFormatting.ITALIC), Pair.of("", "")));
break;
}
}
rawTable.add(Pair.of(Component.literal(" "), null)); //padding
}
this.lineTable = rawTable.stream().flatMap(p -> splitLineToWidth(p.getKey(), p.getValue()).stream()).collect(Collectors.toList());
this.contentSize = lineTable.size();
}
/**
* Splits the raw name and version strings, making them use multiple lines if needed, to fit within the table dimensions.
* The style assigned to the name element is then applied to the entire content row.
* @param name The first element of the content row, usually representing a table section header or the name of a mod entry
* @param versions The last two elements of the content row, usually representing the mod versions. If either one or both of them are not given, the first element may take up more space within the table.
* @return A list of table rows consisting of 3 elements each which consist of the same content as was given by the parameters, but split up to fit within the table dimensions.
*/
private List<Pair<FormattedCharSequence, Pair<FormattedCharSequence, FormattedCharSequence>>> splitLineToWidth(MutableComponent name, Pair<String, String> versions)
{
Style style = name.getStyle();
int versionColumns = versions == null ? 0 : (versions.getLeft().isEmpty() ? (versions.getRight().isEmpty() ? 0 : 1) : 2);
int adaptedNameWidth = nameWidth + versionWidth * (2 - versionColumns) - 4; //the name width may be expanded when the version column string is missing
List<FormattedCharSequence> nameLines = font.split(name, adaptedNameWidth);
List<FormattedCharSequence> clientVersionLines = font.split(Component.literal(versions != null ? versions.getLeft() : "").setStyle(style), versionWidth - 4);
List<FormattedCharSequence> serverVersionLines = font.split(Component.literal(versions != null ? versions.getRight() : "").setStyle(style), versionWidth - 4);
List<Pair<FormattedCharSequence, Pair<FormattedCharSequence, FormattedCharSequence>>> splitLines = new ArrayList<>();
int rowsOccupied = Math.max(nameLines.size(), Math.max(clientVersionLines.size(), serverVersionLines.size()));
for (int i = 0; i < rowsOccupied; i++) {
splitLines.add(Pair.of(i < nameLines.size() ? nameLines.get(i) : FormattedCharSequence.EMPTY, versions == null ? null : Pair.of(i < clientVersionLines.size() ? clientVersionLines.get(i) : FormattedCharSequence.EMPTY, i < serverVersionLines.size() ? serverVersionLines.get(i) : FormattedCharSequence.EMPTY)));
}
return splitLines;
}
/**
* Adds a style information to the given mod name string. The style assigned to the returned component contains the color of the mod name,
* a hover event containing the given id, and an optional click event, which opens the homepage of mod, if present.
* @param id An id that gets displayed in the hover event. Depending on the origin it may only consist of a namespace (the mod id) or a namespace + path (a channel id associated with the mod).
* @param modName The name of the mod. It will be rendered as the main text component.
* @param color Defines the color of the returned style information. An odd number will result in a yellow, an even one in a gold color. This color variation makes it easier for users to distinguish different mod entries.
* @return A component with the mod name as the main text component, and an assigned style which will be used for the whole content row.
*/
private MutableComponent toModNameComponent(ResourceLocation id, String modName, int color)
{
String modId = id.getNamespace();
String tooltipId = id.getPath().isEmpty() ? id.getNamespace() : id.toString();
return Component.literal(modName).withStyle(color % 2 == 0 ? ChatFormatting.GOLD : ChatFormatting.YELLOW)
.withStyle(s -> s.withHoverEvent(new HoverEvent(Action.SHOW_TEXT, Component.literal(tooltipId + (!presentModUrls.getOrDefault(modId, "").isEmpty() ? "\n" + ForgeI18n.parseMessage("fml.modmismatchscreen.homepage") : "")))))
.withStyle(s -> s.withClickEvent(!presentModUrls.getOrDefault(modId, "").isEmpty() ? new ClickEvent(ClickEvent.Action.OPEN_URL, presentModUrls.get(modId)) : null));
}
@Override
protected int getContentHeight()
{
int height = contentSize * (font.lineHeight + 3);
if (height < bottom - top - 4)
height = bottom - top - 4;
return height;
}
@Override
protected void drawPanel(GuiGraphics guiGraphics, int entryRight, int relativeY, Tesselator tess, int mouseX, int mouseY)
{
int i = 0;
for (Pair<FormattedCharSequence, Pair<FormattedCharSequence, FormattedCharSequence>> line : lineTable) {
FormattedCharSequence name = line.getLeft();
Pair<FormattedCharSequence, FormattedCharSequence> versions = line.getRight();
//Since font#draw does not respect the color of the given component, we have to read it out here and then use it as the last parameter
int color = Optional.ofNullable(font.getSplitter().componentStyleAtWidth(name, 0)).map(Style::getColor).map(TextColor::getValue).orElse(0xFFFFFF);
//Only indent the given name if a version string is present. This makes it easier to distinguish table section headers and mod entries
int nameLeft = left + border + (versions == null ? 0 : nameIndent);
guiGraphics.drawString(font, name, nameLeft, relativeY + i * 12, color, false);
if (versions != null)
{
guiGraphics.drawString(font, versions.getLeft(), left + border + nameIndent + nameWidth, relativeY + i * 12, color, false);
guiGraphics.drawString(font, versions.getRight(), left + border + nameIndent + nameWidth + versionWidth, relativeY + i * 12, color, false);
}
i++;
}
}
@Override
public void render(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTicks)
{
super.render(guiGraphics, mouseX, mouseY, partialTicks);
Style style = getComponentStyleAt(mouseX, mouseY);
if (style != null && style.getHoverEvent() != null)
{
guiGraphics.renderComponentHoverEffect(font, style, mouseX, mouseY);
}
}
public Style getComponentStyleAt(double x, double y)
{
if (this.isMouseOver(x, y))
{
double relativeY = y - this.top + this.scrollDistance - border;
int slotIndex = (int)(relativeY + (border / 2)) / 12;
if (slotIndex < contentSize)
{
//The relative x needs to take the potentially missing indent of the row into account. It does that by checking if the line has a version associated to it
double relativeX = x - left - border - (lineTable.get(slotIndex).getRight() == null ? 0 : nameIndent);
if (relativeX >= 0)
return font.getSplitter().componentStyleAtWidth(lineTable.get(slotIndex).getLeft(), (int)relativeX);
}
}
return null;
}
@Override
public boolean mouseClicked(final double mouseX, final double mouseY, final int button)
{
Style style = getComponentStyleAt(mouseX, mouseY);
if (style != null) {
handleComponentClicked(style);
return true;
}
return super.mouseClicked(mouseX, mouseY, button);
}
@Override
public NarrationPriority narrationPriority()
{
return NarrationPriority.NONE;
}
@Override
public void updateNarration(NarrationElementOutput output) {
}
}
}

View File

@@ -0,0 +1,259 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client.gui;
import com.mojang.blaze3d.vertex.VertexFormat;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.renderer.GameRenderer;
import com.mojang.blaze3d.vertex.Tesselator;
import com.mojang.blaze3d.vertex.BufferBuilder;
import com.mojang.blaze3d.vertex.DefaultVertexFormat;
import net.minecraft.resources.ResourceLocation;
import com.mojang.blaze3d.systems.RenderSystem;
import org.joml.Matrix4f;
/**
* This class provides several methods and constants used by the Config GUI classes.
*
* @author bspkrs
* @deprecated Use extension methods in {@link net.minecraftforge.client.extensions.IForgeGuiGraphics} instead
*/
@Deprecated(forRemoval = true)
public class ScreenUtils
{
public static final int DEFAULT_BACKGROUND_COLOR = 0xF0100010;
public static final int DEFAULT_BORDER_COLOR_START = 0x505000FF;
public static final int DEFAULT_BORDER_COLOR_END = (DEFAULT_BORDER_COLOR_START & 0xFEFEFE) >> 1 | DEFAULT_BORDER_COLOR_START & 0xFF000000;
public static final String UNDO_CHAR = "\u21B6";
public static final String RESET_CHAR = "\u2604";
public static final String VALID = "\u2714";
public static final String INVALID = "\u2715";
public static int[] TEXT_COLOR_CODES = new int[] { 0, 170, 43520, 43690, 11141120, 11141290, 16755200, 11184810, 5592405, 5592575, 5635925, 5636095, 16733525, 16733695, 16777045, 16777215,
0, 42, 10752, 10794, 2752512, 2752554, 2763264, 2763306, 1381653, 1381695, 1392405, 1392447, 4134165, 4134207, 4144917, 4144959 };
public static int getColorFromFormattingCharacter(char c, boolean isLighter)
{
return TEXT_COLOR_CODES[isLighter ? "0123456789abcdef".indexOf(c) : "0123456789abcdef".indexOf(c) + 16];
}
/**
* Draws a textured box of any size (smallest size is borderSize * 2 square) based on a fixed size textured box with continuous borders
* and filler. It is assumed that the desired texture ResourceLocation object has been bound using
* Minecraft.getMinecraft().getTextureManager().bindTexture(resourceLocation).
*
* @param guiGraphics the gui graphics
* @param x x axis offset
* @param y y axis offset
* @param u bound resource location image x offset
* @param v bound resource location image y offset
* @param width the desired box width
* @param height the desired box height
* @param textureWidth the width of the box texture in the resource location image
* @param textureHeight the height of the box texture in the resource location image
* @param borderSize the size of the box's borders
* @param zLevel the zLevel to draw at
*/
public static void blitWithBorder(GuiGraphics guiGraphics, int x, int y, int u, int v, int width, int height, int textureWidth, int textureHeight,
int borderSize, float zLevel)
{
blitWithBorder(guiGraphics, x, y, u, v, width, height, textureWidth, textureHeight, borderSize, borderSize, borderSize, borderSize, zLevel);
}
/**
* Draws a textured box of any size (smallest size is borderSize * 2 square) based on a fixed size textured box with continuous borders
* and filler. The provided ResourceLocation object will be bound using
* Minecraft.getMinecraft().getTextureManager().bindTexture(resourceLocation).
*
* @param guiGraphics the gui graphics
* @param res the ResourceLocation object that contains the desired image
* @param x x axis offset
* @param y y axis offset
* @param u bound resource location image x offset
* @param v bound resource location image y offset
* @param width the desired box width
* @param height the desired box height
* @param textureWidth the width of the box texture in the resource location image
* @param textureHeight the height of the box texture in the resource location image
* @param borderSize the size of the box's borders
* @param zLevel the zLevel to draw at
*/
public static void blitWithBorder(GuiGraphics guiGraphics, ResourceLocation res, int x, int y, int u, int v, int width, int height, int textureWidth, int textureHeight,
int borderSize, float zLevel)
{
blitWithBorder(guiGraphics, res, x, y, u, v, width, height, textureWidth, textureHeight, borderSize, borderSize, borderSize, borderSize, zLevel);
}
/**
* Draws a textured box of any size (smallest size is borderSize * 2 square) based on a fixed size textured box with continuous borders
* and filler. The provided ResourceLocation object will be bound using
* Minecraft.getMinecraft().getTextureManager().bindTexture(resourceLocation).
*
* @param guiGraphics the gui graphics
* @param res the ResourceLocation object that contains the desired image
* @param x x axis offset
* @param y y axis offset
* @param u bound resource location image x offset
* @param v bound resource location image y offset
* @param width the desired box width
* @param height the desired box height
* @param textureWidth the width of the box texture in the resource location image
* @param textureHeight the height of the box texture in the resource location image
* @param topBorder the size of the box's top border
* @param bottomBorder the size of the box's bottom border
* @param leftBorder the size of the box's left border
* @param rightBorder the size of the box's right border
* @param zLevel the zLevel to draw at
*/
public static void blitWithBorder(GuiGraphics guiGraphics, ResourceLocation res, int x, int y, int u, int v, int width, int height, int textureWidth, int textureHeight,
int topBorder, int bottomBorder, int leftBorder, int rightBorder, float zLevel)
{
RenderSystem.setShader(GameRenderer::getPositionTexShader);
RenderSystem.setShaderTexture(0, res);
blitWithBorder(guiGraphics, x, y, u, v, width, height, textureWidth, textureHeight, topBorder, bottomBorder, leftBorder, rightBorder, zLevel);
}
/**
* Draws a textured box of any size (smallest size is borderSize * 2 square) based on a fixed size textured box with continuous borders
* and filler. It is assumed that the desired texture ResourceLocation object has been bound using
* Minecraft.getMinecraft().getTextureManager().bindTexture(resourceLocation).
*
* @param guiGraphics the gui graphics
* @param x x axis offset
* @param y y axis offset
* @param u bound resource location image x offset
* @param v bound resource location image y offset
* @param width the desired box width
* @param height the desired box height
* @param textureWidth the width of the box texture in the resource location image
* @param textureHeight the height of the box texture in the resource location image
* @param topBorder the size of the box's top border
* @param bottomBorder the size of the box's bottom border
* @param leftBorder the size of the box's left border
* @param rightBorder the size of the box's right border
* @param zLevel the zLevel to draw at
*/
public static void blitWithBorder(GuiGraphics guiGraphics, int x, int y, int u, int v, int width, int height, int textureWidth, int textureHeight,
int topBorder, int bottomBorder, int leftBorder, int rightBorder, float zLevel)
{
RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F);
RenderSystem.enableBlend();
RenderSystem.defaultBlendFunc();
int fillerWidth = textureWidth - leftBorder - rightBorder;
int fillerHeight = textureHeight - topBorder - bottomBorder;
int canvasWidth = width - leftBorder - rightBorder;
int canvasHeight = height - topBorder - bottomBorder;
int xPasses = canvasWidth / fillerWidth;
int remainderWidth = canvasWidth % fillerWidth;
int yPasses = canvasHeight / fillerHeight;
int remainderHeight = canvasHeight % fillerHeight;
// Draw Border
// Top Left
drawTexturedModalRect(guiGraphics, x, y, u, v, leftBorder, topBorder, zLevel);
// Top Right
drawTexturedModalRect(guiGraphics, x + leftBorder + canvasWidth, y, u + leftBorder + fillerWidth, v, rightBorder, topBorder, zLevel);
// Bottom Left
drawTexturedModalRect(guiGraphics, x, y + topBorder + canvasHeight, u, v + topBorder + fillerHeight, leftBorder, bottomBorder, zLevel);
// Bottom Right
drawTexturedModalRect(guiGraphics, x + leftBorder + canvasWidth, y + topBorder + canvasHeight, u + leftBorder + fillerWidth, v + topBorder + fillerHeight, rightBorder, bottomBorder, zLevel);
for (int i = 0; i < xPasses + (remainderWidth > 0 ? 1 : 0); i++)
{
// Top Border
drawTexturedModalRect(guiGraphics, x + leftBorder + (i * fillerWidth), y, u + leftBorder, v, (i == xPasses ? remainderWidth : fillerWidth), topBorder, zLevel);
// Bottom Border
drawTexturedModalRect(guiGraphics, x + leftBorder + (i * fillerWidth), y + topBorder + canvasHeight, u + leftBorder, v + topBorder + fillerHeight, (i == xPasses ? remainderWidth : fillerWidth), bottomBorder, zLevel);
// Throw in some filler for good measure
for (int j = 0; j < yPasses + (remainderHeight > 0 ? 1 : 0); j++)
drawTexturedModalRect(guiGraphics, x + leftBorder + (i * fillerWidth), y + topBorder + (j * fillerHeight), u + leftBorder, v + topBorder, (i == xPasses ? remainderWidth : fillerWidth), (j == yPasses ? remainderHeight : fillerHeight), zLevel);
}
// Side Borders
for (int j = 0; j < yPasses + (remainderHeight > 0 ? 1 : 0); j++)
{
// Left Border
drawTexturedModalRect(guiGraphics, x, y + topBorder + (j * fillerHeight), u, v + topBorder, leftBorder, (j == yPasses ? remainderHeight : fillerHeight), zLevel);
// Right Border
drawTexturedModalRect(guiGraphics, x + leftBorder + canvasWidth, y + topBorder + (j * fillerHeight), u + leftBorder + fillerWidth, v + topBorder, rightBorder, (j == yPasses ? remainderHeight : fillerHeight), zLevel);
}
}
/**
* @deprecated Use {@link GuiGraphics#blit(ResourceLocation, int, int, int, int, int, int)} instead
*/
@Deprecated(forRemoval = true)
public static void drawTexturedModalRect(GuiGraphics guiGraphics, int x, int y, int u, int v, int width, int height, float zLevel)
{
final float uScale = 1f / 0x100;
final float vScale = 1f / 0x100;
Tesselator tessellator = Tesselator.getInstance();
BufferBuilder wr = tessellator.getBuilder();
wr.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_TEX);
Matrix4f matrix = guiGraphics.pose().last().pose();
wr.vertex(matrix, x , y + height, zLevel).uv( u * uScale, ((v + height) * vScale)).endVertex();
wr.vertex(matrix, x + width, y + height, zLevel).uv((u + width) * uScale, ((v + height) * vScale)).endVertex();
wr.vertex(matrix, x + width, y , zLevel).uv((u + width) * uScale, ( v * vScale)).endVertex();
wr.vertex(matrix, x , y , zLevel).uv( u * uScale, ( v * vScale)).endVertex();
tessellator.end();
}
/**
* @deprecated Use {@link GuiGraphics#fillGradient(int, int, int, int, int, int, int)} instead
*/
@Deprecated(forRemoval = true)
public static void drawGradientRect(Matrix4f mat, int zLevel, int left, int top, int right, int bottom, int startColor, int endColor)
{
float startAlpha = (float)(startColor >> 24 & 255) / 255.0F;
float startRed = (float)(startColor >> 16 & 255) / 255.0F;
float startGreen = (float)(startColor >> 8 & 255) / 255.0F;
float startBlue = (float)(startColor & 255) / 255.0F;
float endAlpha = (float)(endColor >> 24 & 255) / 255.0F;
float endRed = (float)(endColor >> 16 & 255) / 255.0F;
float endGreen = (float)(endColor >> 8 & 255) / 255.0F;
float endBlue = (float)(endColor & 255) / 255.0F;
RenderSystem.enableDepthTest();
RenderSystem.enableBlend();
RenderSystem.defaultBlendFunc();
RenderSystem.setShader(GameRenderer::getPositionColorShader);
Tesselator tessellator = Tesselator.getInstance();
BufferBuilder buffer = tessellator.getBuilder();
buffer.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_COLOR);
buffer.vertex(mat, right, top, zLevel).color(startRed, startGreen, startBlue, startAlpha).endVertex();
buffer.vertex(mat, left, top, zLevel).color(startRed, startGreen, startBlue, startAlpha).endVertex();
buffer.vertex(mat, left, bottom, zLevel).color( endRed, endGreen, endBlue, endAlpha).endVertex();
buffer.vertex(mat, right, bottom, zLevel).color( endRed, endGreen, endBlue, endAlpha).endVertex();
tessellator.end();
RenderSystem.disableBlend();
}
public static void blitInscribed(GuiGraphics guiGraphics, ResourceLocation texture, int x, int y, int boundsWidth, int boundsHeight, int rectWidth, int rectHeight)
{
blitInscribed(guiGraphics, texture, x, y, boundsWidth, boundsHeight, rectWidth, rectHeight, true, true);
}
public static void blitInscribed(GuiGraphics guiGraphics, ResourceLocation texture, int x, int y, int boundsWidth, int boundsHeight, int rectWidth, int rectHeight, boolean centerX, boolean centerY)
{
if (rectWidth * boundsHeight > rectHeight * boundsWidth) {
int h = boundsHeight;
boundsHeight = (int) (boundsWidth * ((double) rectHeight / rectWidth));
if (centerY) y += (h - boundsHeight) / 2;
} else {
int w = boundsWidth;
boundsWidth = (int) (boundsHeight * ((double) rectWidth / rectHeight));
if (centerX) x += (w - boundsWidth) / 2;
}
guiGraphics.blit(texture, x, y, boundsWidth, boundsHeight, 0.0f,0.0f, rectWidth, rectHeight, rectWidth, rectHeight);
}
}

View File

@@ -0,0 +1,71 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client.gui;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.components.Button;
import net.minecraft.client.gui.screens.TitleScreen;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraftforge.fml.loading.FMLConfig;
import net.minecraftforge.versions.forge.ForgeVersion;
import net.minecraftforge.fml.VersionChecker;
import net.minecraftforge.client.loading.ClientModLoader;
public class TitleScreenModUpdateIndicator extends Screen
{
private static final ResourceLocation VERSION_CHECK_ICONS = new ResourceLocation(ForgeVersion.MOD_ID, "textures/gui/version_check_icons.png");
private final Button modButton;
private VersionChecker.Status showNotification = null;
private boolean hasCheckedForUpdates = false;
public TitleScreenModUpdateIndicator(Button modButton)
{
super(Component.translatable("forge.menu.updatescreen.title"));
this.modButton = modButton;
}
@Override
public void init()
{
if (!hasCheckedForUpdates)
{
if (modButton != null)
{
showNotification = ClientModLoader.checkForUpdates();
}
hasCheckedForUpdates = true;
}
}
@Override
public void render(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTick)
{
if (showNotification == null || !showNotification.shouldDraw() || !FMLConfig.getBoolConfigValue(FMLConfig.ConfigValue.VERSION_CHECK))
{
return;
}
int x = modButton.getX();
int y = modButton.getY();
int w = modButton.getWidth();
int h = modButton.getHeight();
guiGraphics.blit(VERSION_CHECK_ICONS, x + w - (h / 2 + 4), y + (h / 2 - 4), showNotification.getSheetOffset() * 8, (showNotification.isAnimated() && ((System.currentTimeMillis() / 800 & 1) == 1)) ? 8 : 0, 8, 8, 64, 16);
}
public static TitleScreenModUpdateIndicator init(TitleScreen guiMainMenu, Button modButton)
{
TitleScreenModUpdateIndicator titleScreenModUpdateIndicator = new TitleScreenModUpdateIndicator(modButton);
titleScreenModUpdateIndicator.resize(guiMainMenu.getMinecraft(), guiMainMenu.width, guiMainMenu.height);
titleScreenModUpdateIndicator.init();
return titleScreenModUpdateIndicator;
}
}

View File

@@ -0,0 +1,694 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client.gui.overlay;
import com.mojang.blaze3d.platform.Window;
import com.mojang.blaze3d.systems.RenderSystem;
import net.minecraft.Util;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.Font;
import net.minecraft.client.gui.Gui;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.components.DebugScreenOverlay;
import net.minecraft.client.multiplayer.ClientPacketListener;
import net.minecraft.client.renderer.GameRenderer;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.resources.language.I18n;
import net.minecraft.util.Mth;
import net.minecraft.util.StringUtil;
import net.minecraft.world.effect.MobEffects;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.PlayerRideableJumping;
import net.minecraft.world.entity.ai.attributes.AttributeInstance;
import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.food.FoodData;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.scores.Objective;
import net.minecraftforge.client.event.CustomizeGuiOverlayEvent;
import net.minecraftforge.client.event.RenderGuiEvent;
import net.minecraftforge.client.event.RenderGuiOverlayEvent;
import net.minecraftforge.client.extensions.common.IClientItemExtensions;
import net.minecraftforge.common.ForgeMod;
import net.minecraftforge.common.MinecraftForge;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.ArrayList;
import java.util.List;
/**
* Forge wrapper around {@link Gui} to be able to render {@link IGuiOverlay HUD overlays}.
*/
public class ForgeGui extends Gui
{
private static final Logger LOGGER = LogManager.getLogger();
private static final int WHITE = 0xFFFFFF;
/*
* If the Euclidean distance to the moused-over block in meters is less than this value, the "Looking at" text will appear on the debug overlay.
*/
public static double rayTraceDistance = 20.0D;
public int leftHeight = 39;
public int rightHeight = 39;
private Font font = null;
private final ForgeDebugScreenOverlay debugOverlay;
public ForgeGui(Minecraft mc)
{
super(mc, mc.getItemRenderer());
debugOverlay = new ForgeDebugScreenOverlay(mc);
}
public Minecraft getMinecraft()
{
return minecraft;
}
public void setupOverlayRenderState(boolean blend, boolean depthTest)
{
if (blend)
{
RenderSystem.enableBlend();
RenderSystem.defaultBlendFunc();
}
else
{
RenderSystem.disableBlend();
}
if (depthTest)
{
RenderSystem.enableDepthTest();
}
else
{
RenderSystem.disableDepthTest();
}
RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F);
RenderSystem.setShader(GameRenderer::getPositionTexShader);
}
@Override
public void render(GuiGraphics guiGraphics, float partialTick)
{
this.screenWidth = this.minecraft.getWindow().getGuiScaledWidth();
this.screenHeight = this.minecraft.getWindow().getGuiScaledHeight();
rightHeight = 39;
leftHeight = 39;
if (MinecraftForge.EVENT_BUS.post(new RenderGuiEvent.Pre(minecraft.getWindow(), guiGraphics, partialTick)))
{
return;
}
font = minecraft.font;
this.random.setSeed(tickCount * 312871L);
GuiOverlayManager.getOverlays().forEach(entry -> {
try
{
IGuiOverlay overlay = entry.overlay();
if (pre(entry, guiGraphics)) return;
overlay.render(this, guiGraphics, partialTick, screenWidth, screenHeight);
post(entry, guiGraphics);
} catch (Exception e)
{
LOGGER.error("Error rendering overlay '{}'", entry.id(), e);
}
});
RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F);
MinecraftForge.EVENT_BUS.post(new RenderGuiEvent.Post(minecraft.getWindow(), guiGraphics, partialTick));
}
public boolean shouldDrawSurvivalElements()
{
return minecraft.gameMode.canHurtPlayer() && minecraft.getCameraEntity() instanceof Player;
}
protected void renderSubtitles(GuiGraphics guiGraphics)
{
this.subtitleOverlay.render(guiGraphics);
}
protected void renderBossHealth(GuiGraphics guiGraphics)
{
RenderSystem.defaultBlendFunc();
minecraft.getProfiler().push("bossHealth");
this.bossOverlay.render(guiGraphics);
minecraft.getProfiler().pop();
}
void renderSpyglassOverlay(GuiGraphics guiGraphics)
{
float deltaFrame = this.minecraft.getDeltaFrameTime();
this.scopeScale = Mth.lerp(0.5F * deltaFrame, this.scopeScale, 1.125F);
if (this.minecraft.options.getCameraType().isFirstPerson())
{
if (this.minecraft.player.isScoping())
{
this.renderSpyglassOverlay(guiGraphics, this.scopeScale);
}
else
{
this.scopeScale = 0.5F;
}
}
}
void renderHelmet(float partialTick, GuiGraphics guiGraphics)
{
ItemStack itemstack = this.minecraft.player.getInventory().getArmor(3);
if (this.minecraft.options.getCameraType().isFirstPerson() && !itemstack.isEmpty())
{
Item item = itemstack.getItem();
if (item == Blocks.CARVED_PUMPKIN.asItem())
{
renderTextureOverlay(guiGraphics, PUMPKIN_BLUR_LOCATION, 1.0F);
}
else
{
IClientItemExtensions.of(item).renderHelmetOverlay(itemstack, minecraft.player, this.screenWidth, this.screenHeight, partialTick);
}
}
}
void renderFrostbite(GuiGraphics guiGraphics)
{
if (this.minecraft.player.getTicksFrozen() > 0)
{
this.renderTextureOverlay(guiGraphics, POWDER_SNOW_OUTLINE_LOCATION, this.minecraft.player.getPercentFrozen());
}
}
protected void renderArmor(GuiGraphics guiGraphics, int width, int height)
{
minecraft.getProfiler().push("armor");
RenderSystem.enableBlend();
int left = width / 2 - 91;
int top = height - leftHeight;
int level = minecraft.player.getArmorValue();
for (int i = 1; level > 0 && i < 20; i += 2)
{
if (i < level)
{
guiGraphics.blit(GUI_ICONS_LOCATION, left, top, 34, 9, 9, 9);
}
else if (i == level)
{
guiGraphics.blit(GUI_ICONS_LOCATION, left, top, 25, 9, 9, 9);
}
else if (i > level)
{
guiGraphics.blit(GUI_ICONS_LOCATION, left, top, 16, 9, 9, 9);
}
left += 8;
}
leftHeight += 10;
RenderSystem.disableBlend();
minecraft.getProfiler().pop();
}
@Override
protected void renderPortalOverlay(GuiGraphics guiGraphics, float alpha)
{
if (alpha > 0.0F)
{
super.renderPortalOverlay(guiGraphics, alpha);
}
}
protected void renderAir(int width, int height, GuiGraphics guiGraphics)
{
minecraft.getProfiler().push("air");
Player player = (Player) this.minecraft.getCameraEntity();
RenderSystem.enableBlend();
int left = width / 2 + 91;
int top = height - rightHeight;
int air = player.getAirSupply();
if (player.isEyeInFluidType(ForgeMod.WATER_TYPE.get()) || air < 300)
{
int full = Mth.ceil((double) (air - 2) * 10.0D / 300.0D);
int partial = Mth.ceil((double) air * 10.0D / 300.0D) - full;
for (int i = 0; i < full + partial; ++i)
{
guiGraphics.blit(GUI_ICONS_LOCATION, left - i * 8 - 9, top, (i < full ? 16 : 25), 18, 9, 9);
}
rightHeight += 10;
}
RenderSystem.disableBlend();
minecraft.getProfiler().pop();
}
public void renderHealth(int width, int height, GuiGraphics guiGraphics)
{
minecraft.getProfiler().push("health");
RenderSystem.enableBlend();
Player player = (Player) this.minecraft.getCameraEntity();
int health = Mth.ceil(player.getHealth());
boolean highlight = healthBlinkTime > (long) tickCount && (healthBlinkTime - (long) tickCount) / 3L % 2L == 1L;
if (health < this.lastHealth && player.invulnerableTime > 0)
{
this.lastHealthTime = Util.getMillis();
this.healthBlinkTime = (long) (this.tickCount + 20);
}
else if (health > this.lastHealth && player.invulnerableTime > 0)
{
this.lastHealthTime = Util.getMillis();
this.healthBlinkTime = (long) (this.tickCount + 10);
}
if (Util.getMillis() - this.lastHealthTime > 1000L)
{
this.lastHealth = health;
this.displayHealth = health;
this.lastHealthTime = Util.getMillis();
}
this.lastHealth = health;
int healthLast = this.displayHealth;
AttributeInstance attrMaxHealth = player.getAttribute(Attributes.MAX_HEALTH);
float healthMax = Math.max((float) attrMaxHealth.getValue(), Math.max(healthLast, health));
int absorb = Mth.ceil(player.getAbsorptionAmount());
int healthRows = Mth.ceil((healthMax + absorb) / 2.0F / 10.0F);
int rowHeight = Math.max(10 - (healthRows - 2), 3);
this.random.setSeed((long) (tickCount * 312871));
int left = width / 2 - 91;
int top = height - leftHeight;
leftHeight += (healthRows * rowHeight);
if (rowHeight != 10) leftHeight += 10 - rowHeight;
int regen = -1;
if (player.hasEffect(MobEffects.REGENERATION))
{
regen = this.tickCount % Mth.ceil(healthMax + 5.0F);
}
this.renderHearts(guiGraphics, player, left, top, rowHeight, regen, healthMax, health, healthLast, absorb, highlight);
RenderSystem.disableBlend();
minecraft.getProfiler().pop();
}
public void renderFood(int width, int height, GuiGraphics guiGraphics)
{
minecraft.getProfiler().push("food");
Player player = (Player) this.minecraft.getCameraEntity();
RenderSystem.enableBlend();
int left = width / 2 + 91;
int top = height - rightHeight;
rightHeight += 10;
boolean unused = false;// Unused flag in vanilla, seems to be part of a 'fade out' mechanic
FoodData stats = minecraft.player.getFoodData();
int level = stats.getFoodLevel();
for (int i = 0; i < 10; ++i)
{
int idx = i * 2 + 1;
int x = left - i * 8 - 9;
int y = top;
int icon = 16;
byte background = 0;
if (minecraft.player.hasEffect(MobEffects.HUNGER))
{
icon += 36;
background = 13;
}
if (unused) background = 1; //Probably should be a += 1 but vanilla never uses this
if (player.getFoodData().getSaturationLevel() <= 0.0F && tickCount % (level * 3 + 1) == 0)
{
y = top + (random.nextInt(3) - 1);
}
guiGraphics.blit(GUI_ICONS_LOCATION, x, y, 16 + background * 9, 27, 9, 9);
if (idx < level)
guiGraphics.blit(GUI_ICONS_LOCATION, x, y, icon + 36, 27, 9, 9);
else if (idx == level)
guiGraphics.blit(GUI_ICONS_LOCATION, x, y, icon + 45, 27, 9, 9);
}
RenderSystem.disableBlend();
minecraft.getProfiler().pop();
}
protected void renderSleepFade(int width, int height, GuiGraphics guiGraphics)
{
if (minecraft.player.getSleepTimer() > 0)
{
minecraft.getProfiler().push("sleep");
int sleepTime = minecraft.player.getSleepTimer();
float opacity = (float) sleepTime / 100.0F;
if (opacity > 1.0F)
{
opacity = 1.0F - (float) (sleepTime - 100) / 10.0F;
}
int color = (int) (220.0F * opacity) << 24 | 1052704;
guiGraphics.fill(RenderType.guiOverlay(), 0, 0, width, height, color);
minecraft.getProfiler().pop();
}
}
protected void renderExperience(int x, GuiGraphics guiGraphics)
{
guiGraphics.setColor(1.0F, 1.0F, 1.0F, 1.0F);
RenderSystem.disableBlend();
if (minecraft.gameMode.hasExperience())
{
super.renderExperienceBar(guiGraphics, x);
}
RenderSystem.enableBlend();
guiGraphics.setColor(1.0F, 1.0F, 1.0F, 1.0F);
}
@Override
public void renderJumpMeter(PlayerRideableJumping playerRideableJumping, GuiGraphics guiGraphics, int x)
{
guiGraphics.setColor(1.0F, 1.0F, 1.0F, 1.0F);
RenderSystem.disableBlend();
super.renderJumpMeter(playerRideableJumping, guiGraphics, x);
RenderSystem.enableBlend();
minecraft.getProfiler().pop();
guiGraphics.setColor(1.0F, 1.0F, 1.0F, 1.0F);
}
protected void renderHUDText(int width, int height, GuiGraphics guiGraphics)
{
minecraft.getProfiler().push("forgeHudText");
RenderSystem.defaultBlendFunc();
var listL = new ArrayList<String>();
var listR = new ArrayList<String>();
if (minecraft.isDemo())
{
long time = minecraft.level.getGameTime();
if (time >= 120500L)
{
listR.add(I18n.get("demo.demoExpired"));
}
else
{
listR.add(I18n.get("demo.remainingTime", StringUtil.formatTickDuration((int) (120500L - time))));
}
}
if (this.minecraft.options.renderDebug)
{
debugOverlay.update();
listL.addAll(debugOverlay.getLeft());
listR.addAll(debugOverlay.getRight());
}
var event = new CustomizeGuiOverlayEvent.DebugText(minecraft.getWindow(), guiGraphics, minecraft.getFrameTime(), listL, listR);
MinecraftForge.EVENT_BUS.post(event);
int top = 2;
for (String msg : listL)
{
if (msg != null && !msg.isEmpty())
{
guiGraphics.fill(1, top - 1, 2 + font.width(msg) + 1, top + font.lineHeight - 1, -1873784752);
guiGraphics.drawString(font, msg, 2, top, 14737632, false);
}
top += font.lineHeight;
}
top = 2;
for (String msg : listR)
{
if (msg != null && !msg.isEmpty())
{
int w = font.width(msg);
int left = width - 2 - w;
guiGraphics.fill(left - 1, top - 1, left + w + 1, top + font.lineHeight - 1, -1873784752);
guiGraphics.drawString(font, msg, left, top, 14737632, false);
}
top += font.lineHeight;
}
minecraft.getProfiler().pop();
}
protected void renderFPSGraph(GuiGraphics guiGraphics)
{
if (this.minecraft.options.renderDebug && this.minecraft.options.renderFpsChart)
{
this.debugOverlay.render(guiGraphics);
}
}
@Override
public void clearCache()
{
super.clearCache();
this.debugOverlay.clearChunkCache();
}
protected void renderRecordOverlay(int width, int height, float partialTick, GuiGraphics guiGraphics)
{
if (overlayMessageTime > 0)
{
minecraft.getProfiler().push("overlayMessage");
float hue = (float) overlayMessageTime - partialTick;
int opacity = (int) (hue * 255.0F / 20.0F);
if (opacity > 255) opacity = 255;
if (opacity > 8)
{
//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);
guiGraphics.pose().pushPose();
//If y shift is smaller less than the default y level, just render it at the base y level
guiGraphics.pose().translate(width / 2D, height - Math.max(yShift, 68), 0.0D);
RenderSystem.enableBlend();
RenderSystem.defaultBlendFunc();
int color = (animateOverlayMessageColor ? Mth.hsvToRgb(hue / 50.0F, 0.7F, 0.6F) & WHITE : WHITE);
int messageWidth = font.width(overlayMessageString);
drawBackdrop(guiGraphics, font, -4, messageWidth, 16777215 | (opacity << 24));
guiGraphics.drawString(font, overlayMessageString.getVisualOrderText(), -messageWidth / 2, -4, color | (opacity << 24));
RenderSystem.disableBlend();
guiGraphics.pose().popPose();
}
minecraft.getProfiler().pop();
}
}
protected void renderTitle(int width, int height, float partialTick, GuiGraphics guiGraphics)
{
if (title != null && titleTime > 0)
{
minecraft.getProfiler().push("titleAndSubtitle");
float age = (float) this.titleTime - partialTick;
int opacity = 255;
if (titleTime > titleFadeOutTime + titleStayTime)
{
float f3 = (float) (titleFadeInTime + titleStayTime + titleFadeOutTime) - age;
opacity = (int) (f3 * 255.0F / (float) titleFadeInTime);
}
if (titleTime <= titleFadeOutTime) opacity = (int) (age * 255.0F / (float) this.titleFadeOutTime);
opacity = Mth.clamp(opacity, 0, 255);
if (opacity > 8)
{
guiGraphics.pose().pushPose();
guiGraphics.pose().translate(width / 2D, height / 2D, 0.0D);
RenderSystem.enableBlend();
RenderSystem.defaultBlendFunc();
guiGraphics.pose().pushPose();
guiGraphics.pose().scale(4.0F, 4.0F, 4.0F);
int l = opacity << 24 & -16777216;
guiGraphics.drawString(this.font, this.title.getVisualOrderText(), -this.getFont().width(this.title) / 2, -10, 16777215 | l, true);
guiGraphics.pose().popPose();
if (this.subtitle != null)
{
guiGraphics.pose().pushPose();
guiGraphics.pose().scale(2.0F, 2.0F, 2.0F);
guiGraphics.drawString(this.font, this.subtitle.getVisualOrderText(), -this.getFont().width(this.subtitle) / 2, 5, 16777215 | l, true);
guiGraphics.pose().popPose();
}
RenderSystem.disableBlend();
guiGraphics.pose().popPose();
}
this.minecraft.getProfiler().pop();
}
}
protected void renderChat(int width, int height, GuiGraphics guiGraphics)
{
minecraft.getProfiler().push("chat");
Window window = minecraft.getWindow();
var event = new CustomizeGuiOverlayEvent.Chat(window, guiGraphics, minecraft.getFrameTime(), 0, height - 40);
MinecraftForge.EVENT_BUS.post(event);
guiGraphics.pose().pushPose();
// We give the absolute Y position of the chat component in the event and account for the chat component's own offsetting here.
guiGraphics.pose().translate(event.getPosX(), (event.getPosY() - height + 40) / chat.getScale(), 0.0D);
int mouseX = Mth.floor(minecraft.mouseHandler.xpos() * window.getGuiScaledWidth() / window.getScreenWidth());
int mouseY = Mth.floor(minecraft.mouseHandler.ypos() * window.getGuiScaledHeight() / window.getScreenHeight());
chat.render(guiGraphics, tickCount, mouseX, mouseY);
guiGraphics.pose().popPose();
minecraft.getProfiler().pop();
}
protected void renderPlayerList(int width, int height, GuiGraphics guiGraphics)
{
Objective scoreobjective = this.minecraft.level.getScoreboard().getDisplayObjective(0);
ClientPacketListener handler = minecraft.player.connection;
if (minecraft.options.keyPlayerList.isDown() && (!minecraft.isLocalServer() || handler.getOnlinePlayers().size() > 1 || scoreobjective != null))
{
this.tabList.setVisible(true);
this.tabList.render(guiGraphics, width, this.minecraft.level.getScoreboard(), scoreobjective);
}
else
{
this.tabList.setVisible(false);
}
}
protected void renderHealthMount(int width, int height, GuiGraphics guiGraphics)
{
Player player = (Player) minecraft.getCameraEntity();
Entity tmp = player.getVehicle();
if (!(tmp instanceof LivingEntity)) return;
boolean unused = false;
int left_align = width / 2 + 91;
minecraft.getProfiler().popPush("mountHealth");
RenderSystem.enableBlend();
LivingEntity mount = (LivingEntity) tmp;
int health = (int) Math.ceil((double) mount.getHealth());
float healthMax = mount.getMaxHealth();
int hearts = (int) (healthMax + 0.5F) / 2;
if (hearts > 30) hearts = 30;
final int MARGIN = 52;
final int BACKGROUND = MARGIN + (unused ? 1 : 0);
final int HALF = MARGIN + 45;
final int FULL = MARGIN + 36;
for (int heart = 0; hearts > 0; heart += 20)
{
int top = height - rightHeight;
int rowCount = Math.min(hearts, 10);
hearts -= rowCount;
for (int i = 0; i < rowCount; ++i)
{
int x = left_align - i * 8 - 9;
guiGraphics.blit(GUI_ICONS_LOCATION, x, top, BACKGROUND, 9, 9, 9);
if (i * 2 + 1 + heart < health)
guiGraphics.blit(GUI_ICONS_LOCATION, x, top, FULL, 9, 9, 9);
else if (i * 2 + 1 + heart == health)
guiGraphics.blit(GUI_ICONS_LOCATION, x, top, HALF, 9, 9, 9);
}
rightHeight += 10;
}
RenderSystem.disableBlend();
}
//Helper macros
private boolean pre(NamedGuiOverlay overlay, GuiGraphics guiGraphics)
{
return MinecraftForge.EVENT_BUS.post(new RenderGuiOverlayEvent.Pre(minecraft.getWindow(), guiGraphics, minecraft.getFrameTime(), overlay));
}
private void post(NamedGuiOverlay overlay, GuiGraphics guiGraphics)
{
MinecraftForge.EVENT_BUS.post(new RenderGuiOverlayEvent.Post(minecraft.getWindow(), guiGraphics, minecraft.getFrameTime(), overlay));
}
private static class ForgeDebugScreenOverlay extends DebugScreenOverlay
{
private final Minecraft mc;
private ForgeDebugScreenOverlay(Minecraft mc)
{
super(mc);
this.mc = mc;
}
public void update()
{
Entity entity = this.mc.getCameraEntity();
this.block = entity.pick(rayTraceDistance, 0.0F, false);
this.liquid = entity.pick(rayTraceDistance, 0.0F, true);
}
@Override
protected void drawGameInformation(GuiGraphics guiGraphics)
{
// Replicate the depth test state "leak" caused by the text that is rendered here in vanilla
// being flushed when the graphs start drawing (PR #9539)
RenderSystem.disableDepthTest();
}
@Override
protected void drawSystemInformation(GuiGraphics guiGraphics) {}
private List<String> getLeft()
{
List<String> ret = this.getGameInformation();
ret.add("");
boolean flag = this.mc.getSingleplayerServer() != null;
ret.add("Debug: Pie [shift]: " + (this.mc.options.renderDebugCharts ? "visible" : "hidden") + (flag ? " FPS + TPS" : " FPS") + " [alt]: " + (this.mc.options.renderFpsChart ? "visible" : "hidden"));
ret.add("For help: press F3 + Q");
return ret;
}
private List<String> getRight()
{
return this.getSystemInformation();
}
}
}

View File

@@ -0,0 +1,85 @@
/*
* 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.google.common.collect.ImmutableMap;
import net.minecraft.resources.ResourceLocation;
import net.minecraftforge.client.event.RegisterGuiOverlaysEvent;
import net.minecraftforge.fml.ModLoader;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.function.Function;
/**
* Manager for {@linkplain IGuiOverlay HUD overlays}.
* <p>
* Provides a lookup by ID, as well as all registered {@link IGuiOverlay overlays}.
*/
public final class GuiOverlayManager
{
private static ImmutableList<NamedGuiOverlay> OVERLAYS;
private static ImmutableMap<ResourceLocation, NamedGuiOverlay> OVERLAYS_BY_NAME;
/**
* Retrieves an ordered list of all registered overlays.
*/
public static ImmutableList<NamedGuiOverlay> getOverlays()
{
return OVERLAYS;
}
/**
* Finds the overlay corresponding to a given ID.
* Do not call this before {@link RegisterGuiOverlaysEvent} has finished firing.
*/
@Nullable
public static NamedGuiOverlay findOverlay(ResourceLocation id)
{
return OVERLAYS_BY_NAME.get(id);
}
@ApiStatus.Internal
public static void init()
{
var overlays = new HashMap<ResourceLocation, IGuiOverlay>();
var orderedOverlays = new ArrayList<ResourceLocation>();
preRegisterVanillaOverlays(overlays, orderedOverlays);
var event = new RegisterGuiOverlaysEvent(overlays, orderedOverlays);
ModLoader.get().postEventWrapContainerInModOrder(event);
OVERLAYS = orderedOverlays.stream()
.map(id -> new NamedGuiOverlay(id, overlays.get(id)))
.collect(ImmutableList.toImmutableList());
OVERLAYS_BY_NAME = OVERLAYS.stream()
.collect(ImmutableMap.toImmutableMap(NamedGuiOverlay::id, Function.identity()));
assignVanillaOverlayTypes();
}
/**
* Pre-registers vanilla overlays so they are available for ordering.
*/
private static void preRegisterVanillaOverlays(HashMap<ResourceLocation, IGuiOverlay> overlays, ArrayList<ResourceLocation> orderedOverlays)
{
for (var entry : VanillaGuiOverlay.values())
{
overlays.put(entry.id(), entry.overlay);
orderedOverlays.add(entry.id());
}
}
private static void assignVanillaOverlayTypes()
{
for (var entry : VanillaGuiOverlay.values())
entry.type = OVERLAYS_BY_NAME.get(entry.id());
}
private GuiOverlayManager()
{
}
}

View File

@@ -0,0 +1,20 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client.gui.overlay;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraftforge.client.event.RegisterGuiOverlaysEvent;
/**
* A HUD overlay.
*
* @see RegisterGuiOverlaysEvent
*/
@FunctionalInterface
public interface IGuiOverlay
{
void render(ForgeGui gui, GuiGraphics guiGraphics, float partialTick, int screenWidth, int screenHeight);
}

View File

@@ -0,0 +1,24 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client.gui.overlay;
import net.minecraft.resources.ResourceLocation;
import org.jetbrains.annotations.ApiStatus;
/**
* An object representation of an {@link IGuiOverlay overlay} with a name.
* <p>
* Useful to identify overlays in {@link net.minecraftforge.client.event.RenderGuiOverlayEvent}.
* <p>
* Users should not be instantiating this themselves. Retrieve from {@link GuiOverlayManager}.
*/
public record NamedGuiOverlay(ResourceLocation id, IGuiOverlay overlay)
{
@ApiStatus.Internal
public NamedGuiOverlay
{
}
}

View File

@@ -0,0 +1,236 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client.gui.overlay;
import com.mojang.blaze3d.systems.RenderSystem;
import net.minecraft.client.Minecraft;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.Mth;
import net.minecraft.world.effect.MobEffects;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.PlayerRideableJumping;
import net.minecraft.world.level.GameType;
import net.minecraft.world.scores.Objective;
import net.minecraft.world.scores.PlayerTeam;
import net.minecraft.world.scores.Scoreboard;
import org.jetbrains.annotations.NotNull;
import org.lwjgl.opengl.GL11;
/**
* All the vanilla {@linkplain IGuiOverlay HUD overlays} in the order that they render.
*/
public enum VanillaGuiOverlay
{
VIGNETTE("vignette", (gui, guiGraphics, partialTick, screenWidth, screenHeight) -> {
if (Minecraft.useFancyGraphics())
{
gui.setupOverlayRenderState(true, false);
gui.renderVignette(guiGraphics, gui.getMinecraft().getCameraEntity());
}
}),
SPYGLASS("spyglass", (gui, guiGraphics, partialTick, screenWidth, screenHeight) -> {
gui.setupOverlayRenderState(true, false);
gui.renderSpyglassOverlay(guiGraphics);
}),
HELMET("helmet", (gui, guiGraphics, partialTick, screenWidth, screenHeight) -> {
gui.setupOverlayRenderState(true, false);
gui.renderHelmet(partialTick, guiGraphics);
}),
FROSTBITE("frostbite", (gui, guiGraphics, partialTick, screenWidth, screenHeight) -> {
gui.setupOverlayRenderState(true, false);
gui.renderFrostbite(guiGraphics);
}),
PORTAL("portal", (gui, guiGraphics, partialTick, screenWidth, screenHeight) -> {
float f1 = Mth.lerp(partialTick, gui.getMinecraft().player.oSpinningEffectIntensity, gui.getMinecraft().player.spinningEffectIntensity);
if (f1 > 0.0F && !gui.getMinecraft().player.hasEffect(MobEffects.CONFUSION)) {
gui.setupOverlayRenderState(true, false);
gui.renderPortalOverlay(guiGraphics, f1);
}
}),
HOTBAR("hotbar", (gui, guiGraphics, partialTick, screenWidth, screenHeight) -> {
if (!gui.getMinecraft().options.hideGui)
{
gui.setupOverlayRenderState(true, false);
if (gui.getMinecraft().gameMode.getPlayerMode() == GameType.SPECTATOR)
{
gui.getSpectatorGui().renderHotbar(guiGraphics);
}
else
{
gui.renderHotbar(partialTick, guiGraphics);
}
}
}),
CROSSHAIR("crosshair", (gui, guiGraphics, partialTick, screenWidth, screenHeight) -> {
if (!gui.getMinecraft().options.hideGui)
{
gui.setupOverlayRenderState(true, false);
guiGraphics.pose().pushPose();
guiGraphics.pose().translate(0, 0, -90);
gui.renderCrosshair(guiGraphics);
guiGraphics.pose().popPose();
}
}),
BOSS_EVENT_PROGRESS("boss_event_progress", (gui, guiGraphics, partialTick, screenWidth, screenHeight) -> {
if (!gui.getMinecraft().options.hideGui)
{
gui.setupOverlayRenderState(true, false);
guiGraphics.pose().pushPose();
guiGraphics.pose().translate(0, 0, -90);
gui.renderBossHealth(guiGraphics);
guiGraphics.pose().popPose();
}
}),
PLAYER_HEALTH("player_health", (gui, guiGraphics, partialTick, screenWidth, screenHeight) -> {
if (!gui.getMinecraft().options.hideGui && gui.shouldDrawSurvivalElements())
{
gui.setupOverlayRenderState(true, false);
gui.renderHealth(screenWidth, screenHeight, guiGraphics);
}
}),
ARMOR_LEVEL("armor_level", (gui, guiGraphics, partialTick, screenWidth, screenHeight) -> {
if (!gui.getMinecraft().options.hideGui && gui.shouldDrawSurvivalElements())
{
gui.setupOverlayRenderState(true, false);
gui.renderArmor(guiGraphics, screenWidth, screenHeight);
}
}),
FOOD_LEVEL("food_level", (gui, guiGraphics, partialTick, screenWidth, screenHeight) -> {
Entity vehicle = gui.getMinecraft().player.getVehicle();
boolean isMounted = vehicle != null && vehicle.showVehicleHealth();
if (!isMounted && !gui.getMinecraft().options.hideGui && gui.shouldDrawSurvivalElements())
{
gui.setupOverlayRenderState(true, false);
gui.renderFood(screenWidth, screenHeight, guiGraphics);
}
}),
AIR_LEVEL("air_level", (gui, guiGraphics, partialTick, screenWidth, screenHeight) -> {
if (!gui.getMinecraft().options.hideGui && gui.shouldDrawSurvivalElements())
{
gui.setupOverlayRenderState(true, false);
gui.renderAir(screenWidth, screenHeight, guiGraphics);
}
}),
MOUNT_HEALTH("mount_health", (gui, guiGraphics, partialTick, screenWidth, screenHeight) -> {
if (!gui.getMinecraft().options.hideGui && gui.shouldDrawSurvivalElements())
{
gui.setupOverlayRenderState(true, false);
gui.renderHealthMount(screenWidth, screenHeight, guiGraphics);
}
}),
JUMP_BAR("jump_bar", (gui, guiGraphics, partialTick, screenWidth, screenHeight) -> {
PlayerRideableJumping playerRideableJumping = gui.getMinecraft().player.jumpableVehicle();
if (playerRideableJumping != null && !gui.getMinecraft().options.hideGui)
{
gui.setupOverlayRenderState(true, false);
gui.renderJumpMeter(playerRideableJumping, guiGraphics, screenWidth / 2 - 91);
}
}),
EXPERIENCE_BAR("experience_bar", (gui, guiGraphics, partialTick, screenWidth, screenHeight) -> {
if (gui.getMinecraft().player.jumpableVehicle() == null && !gui.getMinecraft().options.hideGui)
{
gui.setupOverlayRenderState(true, false);
gui.renderExperience(screenWidth / 2 - 91, guiGraphics);
}
}),
ITEM_NAME("item_name", (gui, guiGraphics, partialTick, screenWidth, screenHeight) -> {
if (!gui.getMinecraft().options.hideGui)
{
gui.setupOverlayRenderState(true, false);
if (gui.getMinecraft().gameMode.getPlayerMode() != GameType.SPECTATOR)
{
gui.renderSelectedItemName(guiGraphics, Math.max(gui.leftHeight, gui.rightHeight));
}
else if (gui.getMinecraft().player.isSpectator())
{
gui.getSpectatorGui().renderTooltip(guiGraphics);
}
}
}),
SLEEP_FADE("sleep_fade", (gui, guiGraphics, partialTick, screenWidth, screenHeight) -> {
gui.renderSleepFade(screenWidth, screenHeight, guiGraphics);
}),
POTION_ICONS("potion_icons", (gui, guiGraphics, partialTick, screenWidth, screenHeight) -> {
gui.renderEffects(guiGraphics);
}),
DEBUG_TEXT("debug_text", (gui, guiGraphics, partialTick, screenWidth, screenHeight) -> {
gui.renderHUDText(screenWidth, screenHeight, guiGraphics);
}),
FPS_GRAPH("fps_graph", (gui, guiGraphics, partialTick, screenWidth, screenHeight) -> {
gui.renderFPSGraph(guiGraphics);
}),
RECORD_OVERLAY("record_overlay", (gui, guiGraphics, partialTick, screenWidth, screenHeight) -> {
if (!gui.getMinecraft().options.hideGui)
{
gui.renderRecordOverlay(screenWidth, screenHeight, partialTick, guiGraphics);
}
}),
TITLE_TEXT("title_text", (gui, guiGraphics, partialTick, screenWidth, screenHeight) -> {
if (!gui.getMinecraft().options.hideGui)
{
gui.renderTitle(screenWidth, screenHeight, partialTick, guiGraphics);
}
}),
SUBTITLES("subtitles", (gui, guiGraphics, partialTick, screenWidth, screenHeight) -> {
if (!gui.getMinecraft().options.hideGui)
{
gui.renderSubtitles(guiGraphics);
}
}),
SCOREBOARD("scoreboard", (gui, guiGraphics, partialTick, screenWidth, screenHeight) -> {
Scoreboard scoreboard = gui.getMinecraft().level.getScoreboard();
Objective objective = null;
PlayerTeam scoreplayerteam = scoreboard.getPlayersTeam(gui.getMinecraft().player.getScoreboardName());
if (scoreplayerteam != null)
{
int slot = scoreplayerteam.getColor().getId();
if (slot >= 0) objective = scoreboard.getDisplayObjective(3 + slot);
}
Objective scoreobjective1 = objective != null ? objective : scoreboard.getDisplayObjective(1);
if (scoreobjective1 != null)
{
gui.displayScoreboardSidebar(guiGraphics, scoreobjective1);
}
}),
CHAT_PANEL("chat_panel", (gui, guiGraphics, partialTick, screenWidth, screenHeight) -> {
RenderSystem.enableBlend();
RenderSystem.blendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, 1, 0);
gui.renderChat(screenWidth, screenHeight, guiGraphics);
}),
PLAYER_LIST("player_list", (gui, guiGraphics, partialTick, screenWidth, screenHeight) -> {
RenderSystem.enableBlend();
RenderSystem.blendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, 1, 0);
gui.renderPlayerList(screenWidth, screenHeight, guiGraphics);
});
private final ResourceLocation id;
final IGuiOverlay overlay;
NamedGuiOverlay type;
VanillaGuiOverlay(String id, IGuiOverlay overlay)
{
this.id = new ResourceLocation("minecraft", id);
this.overlay = overlay;
}
@NotNull
public ResourceLocation id()
{
return id;
}
public NamedGuiOverlay type()
{
return type;
}
}

Some files were not shown because too many files have changed in this diff Show More