Implement a GUI packet and GUIFACTORY for triggering from the server.

Signed-off-by: cpw <cpw+github@weeksfamily.ca>
This commit is contained in:
cpw 2019-02-13 21:06:39 -05:00
parent 31c0a70f8e
commit 586c24f9d6
No known key found for this signature in database
GPG Key ID: 8EB3DF749553B1B7
12 changed files with 98 additions and 74 deletions

View File

@ -109,7 +109,7 @@ public class DeferredWorkQueue
* @return A {@link CompletableFuture} that completes at said time
*/
public static CompletableFuture<Void> runLater(Runnable workToEnqueue) {
currentOwner.set(ModThreadContext.get().getActiveContainer());
currentOwner.set(ModLoadingContext.get().getActiveContainer());
return CompletableFuture.runAsync(workToEnqueue, deferredExecutor).exceptionally(DeferredWorkQueue.handleException());
}
@ -154,7 +154,7 @@ public class DeferredWorkQueue
* @return A {@link CompletableFuture} that completes at said time
*/
public static <T> CompletableFuture<T> getLater(Supplier<T> workToEnqueue) {
currentOwner.set(ModThreadContext.get().getActiveContainer());
currentOwner.set(ModLoadingContext.get().getActiveContainer());
return CompletableFuture.supplyAsync(workToEnqueue, deferredExecutor).exceptionally(DeferredWorkQueue.handleException());
}

View File

@ -22,14 +22,23 @@ package net.minecraftforge.fml;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.resources.IResourcePack;
import net.minecraftforge.fml.network.FMLPlayMessages;
import net.minecraftforge.fml.packs.ModFileResourcePack;
import java.util.function.BiFunction;
import java.util.function.Function;
public class ExtensionPoint<T>
{
public static final ExtensionPoint<BiFunction<Minecraft, GuiScreen, GuiScreen>> GUIFACTORY = new ExtensionPoint<>();
public static final ExtensionPoint<BiFunction<Minecraft, GuiScreen, GuiScreen>> CONFIGGUIFACTORY = new ExtensionPoint<>();
public static final ExtensionPoint<BiFunction<Minecraft, ModFileResourcePack, IResourcePack>> RESOURCEPACK = new ExtensionPoint<>();
/**
* Register with {@link ModLoadingContext#}
*/
public static final ExtensionPoint<Function<FMLPlayMessages.OpenContainer, GuiScreen>> GUIFACTORY = new ExtensionPoint<>();
private Class<T> type;
private ExtensionPoint() {

View File

@ -115,11 +115,11 @@ public abstract class ModContainer
}
@SuppressWarnings("unchecked")
public <T> Optional<T> getCustomExtension(ExtensionPoint point) {
public <T> Optional<T> getCustomExtension(ExtensionPoint<T> point) {
return Optional.ofNullable((T)extensionPoints.getOrDefault(point,()-> null).get());
}
public <T> void registerExtensionPoint(ExtensionPoint point, Supplier<T> extension)
public <T> void registerExtensionPoint(ExtensionPoint<T> point, Supplier<T> extension)
{
extensionPoints.put(point, extension);
}

View File

@ -19,11 +19,13 @@
package net.minecraftforge.fml;
public class ModThreadContext
{
private static ThreadLocal<ModThreadContext> context = ThreadLocal.withInitial(ModThreadContext::new);
import java.util.function.Supplier;
public static ModThreadContext get() {
public class ModLoadingContext
{
private static ThreadLocal<ModLoadingContext> context = ThreadLocal.withInitial(ModLoadingContext::new);
public static ModLoadingContext get() {
return context.get();
}
@ -36,4 +38,15 @@ public class ModThreadContext
public ModContainer getActiveContainer() {
return activeContainer == null ? DefaultModContainers.MINECRAFT : activeContainer;
}
/**
* Register an {@link ExtensionPoint} with the mod container.
* @param point The extension point to register
* @param extension An extension operator
* @param <T> The type signature of the extension operator
*/
public <T> void registerExtensionPoint(ExtensionPoint<T> point, Supplier<T> extension) {
getActiveContainer().registerExtensionPoint(point, extension);
}
}

View File

@ -33,6 +33,6 @@ public class ConfigGuiHandler
public static Optional<BiFunction<Minecraft, GuiScreen, GuiScreen>> getGuiFactoryFor(ModInfo selectedMod)
{
return ModList.get().getModContainerById(selectedMod.getModId()).
flatMap(mc -> mc.getCustomExtension(ExtensionPoint.GUIFACTORY));
flatMap(mc -> mc.getCustomExtension(ExtensionPoint.CONFIGGUIFACTORY));
}
}

View File

@ -28,7 +28,7 @@ import net.minecraftforge.fml.LifecycleEventProvider;
import net.minecraftforge.fml.ModContainer;
import net.minecraftforge.fml.ModLoadingException;
import net.minecraftforge.fml.ModLoadingStage;
import net.minecraftforge.fml.ModThreadContext;
import net.minecraftforge.fml.ModLoadingContext;
import net.minecraftforge.forgespi.language.IModInfo;
import net.minecraftforge.forgespi.language.ModFileScanData;
@ -94,7 +94,7 @@ public class FMLModContainer extends ModContainer
private void beforeEvent(LifecycleEventProvider.LifecycleEvent lifecycleEvent) {
FMLModLoadingContext.get().setActiveContainer(this);
ModThreadContext.get().setActiveContainer(this);
ModLoadingContext.get().setActiveContainer(this);
}
private void fireEvent(LifecycleEventProvider.LifecycleEvent lifecycleEvent) {
@ -113,7 +113,7 @@ public class FMLModContainer extends ModContainer
}
private void afterEvent(LifecycleEventProvider.LifecycleEvent lifecycleEvent) {
ModThreadContext.get().setActiveContainer(null);
ModLoadingContext.get().setActiveContainer(null);
FMLModLoadingContext.get().setActiveContainer(null);
if (getCurrentState() == ModLoadingStage.ERROR) {
LOGGER.error(LOADING,"An error occurred while dispatching event {} to {}", lifecycleEvent.fromStage(), getModId());

View File

@ -34,23 +34,26 @@ public class FMLModLoadingContext
return context.get();
}
public <T> void registerExtensionPoint(ExtensionPoint<T> point, Supplier<T> extension) {
getActiveContainer().registerExtensionPoint(point, extension);
}
public void registerConfig(ModConfig.Type type, ForgeConfigSpec spec) {
activeContainer.addConfig(new ModConfig(type, spec, activeContainer));
getActiveContainer().addConfig(new ModConfig(type, spec, getActiveContainer()));
}
public void registerConfig(ModConfig.Type type, ForgeConfigSpec spec, String fileName) {
activeContainer.addConfig(new ModConfig(type, spec, activeContainer, fileName));
getActiveContainer().addConfig(new ModConfig(type, spec, getActiveContainer(), fileName));
}
/**
* @return The mod's event bus, to allow subscription to Mod specific events
*/
public IEventBus getModEventBus()
{
return getActiveContainer().getEventBus();
}
/**
* Only valid during {@link net.minecraftforge.fml.event.lifecycle.ModLifecycleEvent} dispatch and Mod construction
* @return the active FML container
*/
public FMLModContainer getActiveContainer()
{
return activeContainer;

View File

@ -19,20 +19,19 @@
package net.minecraftforge.fml.network;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityTracker;
import net.minecraft.entity.EntityType;
import net.minecraft.network.PacketBuffer;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.MathHelper;
import net.minecraftforge.fml.ExtensionPoint;
import net.minecraftforge.fml.ModList;
import net.minecraftforge.fml.common.registry.IEntityAdditionalSpawnData;
import java.util.UUID;
import java.util.function.Function;
import java.util.function.Supplier;
public class FMLPlayMessages
@ -166,9 +165,9 @@ public class FMLPlayMessages
{
private final ResourceLocation id;
private final int windowId;
private final byte[] additionalData;
private final PacketBuffer additionalData;
public OpenContainer(ResourceLocation id, int windowId, byte[] additionalData)
OpenContainer(ResourceLocation id, int windowId, PacketBuffer additionalData)
{
this.id = id;
this.windowId = windowId;
@ -179,27 +178,34 @@ public class FMLPlayMessages
{
buf.writeResourceLocation(msg.id);
buf.writeVarInt(msg.windowId);
buf.writeByteArray(msg.additionalData);
buf.writeBytes(msg.additionalData);
}
public static OpenContainer decode(PacketBuffer buf)
{
return new OpenContainer(buf.readResourceLocation(), buf.readVarInt(), buf.readByteArray());
return new OpenContainer(buf.readResourceLocation(), buf.readVarInt(), new PacketBuffer(Unpooled.wrappedBuffer(buf.readByteArray(32600))));
}
public static void handle(OpenContainer msg, Supplier<NetworkEvent.Context> ctx)
{
ctx.get().enqueueWork(() -> {
Supplier<Function<ByteBuf, GuiScreen>> sup = NetworkRegistry.guiHandlers.get(msg.id);
if (sup != null) {
GuiScreen gui = sup.get().apply(Unpooled.wrappedBuffer(msg.additionalData));
if (gui != null) {
Minecraft.getInstance().displayGuiScreen(gui);
Minecraft.getInstance().player.openContainer.windowId = msg.windowId;
}
}
});
ctx.get().enqueueWork(() -> ModList.get().getModContainerById(msg.id.getNamespace()).ifPresent(mc->
mc.getCustomExtension(ExtensionPoint.GUIFACTORY).map(f -> f.apply(msg)).ifPresent(gui-> {
Minecraft.getInstance().displayGuiScreen(gui);
Minecraft.getInstance().player.openContainer.windowId = msg.windowId;
})));
ctx.get().setPacketHandled(true);
}
public final ResourceLocation getId() {
return this.id;
}
public int getWindowId() {
return windowId;
}
public PacketBuffer getAdditionalData() {
return additionalData;
}
}
}

View File

@ -19,13 +19,14 @@
package net.minecraftforge.fml.network;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import net.minecraft.entity.Entity;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.inventory.Container;
import net.minecraft.network.NetHandlerPlayServer;
import net.minecraft.network.NetworkManager;
import net.minecraft.network.Packet;
import net.minecraft.network.PacketBuffer;
import net.minecraft.network.handshake.client.CPacketHandshake;
import net.minecraft.server.network.NetHandlerLoginServer;
import net.minecraft.util.ResourceLocation;
@ -87,10 +88,22 @@ public class NetworkHooks
return FMLHandshakeHandler.tickLogin(networkManager);
}
public static void openGui(EntityPlayerMP player, IInteractionObject container, @Nullable ByteBuf extraData)
/**
* Server method to tell the client to open a GUI on behalf of the server
*
* The {@link IInteractionObject#getGuiID()} is treated as a {@link ResourceLocation}.
* It should refer to a valid modId namespace, to trigger opening on the client.
* The namespace is directly used to lookup the modId in the client side.
*
* @param player The player to open the GUI for
* @param containerSupplier The Container Supplier
* @param extraData Additional data for the GUI
*/
public static void openGui(EntityPlayerMP player, IInteractionObject containerSupplier, @Nullable PacketBuffer extraData)
{
ResourceLocation id = new ResourceLocation(container.getGuiID());
Container c = container.createContainer(player.inventory, player);
if (player.world.isRemote) return;
ResourceLocation id = new ResourceLocation(containerSupplier.getGuiID());
Container c = containerSupplier.createContainer(player.inventory, player);
player.closeScreen();
player.getNextWindowId();
player.openContainer = c;
@ -98,14 +111,13 @@ public class NetworkHooks
player.openContainer.addListener(player);
MinecraftForge.EVENT_BUS.post(new PlayerContainerEvent.Open(player, c));
byte[] additional;
if (extraData == null) {
additional = new byte[0];
} else {
additional = new byte[extraData.readableBytes()];
extraData.readBytes(additional);
extraData = new PacketBuffer(Unpooled.buffer());
}
FMLPlayMessages.OpenContainer msg = new FMLPlayMessages.OpenContainer(id, player.currentWindowId, additional);
if (extraData.readableBytes() > 32600) {
throw new IllegalArgumentException("GUI Open packet too large : "+ extraData.readableBytes());
}
FMLPlayMessages.OpenContainer msg = new FMLPlayMessages.OpenContainer(id, player.currentWindowId, extraData);
FMLPlayHandler.channel.sendTo(msg, player.connection.getNetworkManager(), NetworkDirection.PLAY_TO_CLIENT);
}
}

View File

@ -19,8 +19,6 @@
package net.minecraftforge.fml.network;
import io.netty.buffer.ByteBuf;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagList;
import net.minecraft.network.NetworkManager;
@ -34,10 +32,12 @@ import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.Marker;
import org.apache.logging.log4j.MarkerManager;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collectors;
@ -51,21 +51,6 @@ public class NetworkRegistry
private static final Marker NETREGISTRY = MarkerManager.getMarker("NETREGISTRY");
private static Map<ResourceLocation, NetworkInstance> instances = new HashMap<>();
static final Map<ResourceLocation, Supplier<Function<ByteBuf, GuiScreen>>> guiHandlers = new ConcurrentHashMap<>();
/**
* Registers a client-side GUI handler for the given ID.
* The function takes any extra data provided to {@link net.minecraft.entity.player.EntityPlayer#openGui}
* and returns a {@link GuiScreen} to display.
* Call this during {@link net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent}.
* This method is safe to call in parallel mod loading
* @param id
* @param handler
*/
public static void registerGui(ResourceLocation id, Supplier<Function<ByteBuf, GuiScreen>> handler)
{
guiHandlers.put(id, handler);
}
/**
* Special value for clientAcceptedVersions and serverAcceptedVersions predicates indicating the other side lacks

View File

@ -28,13 +28,12 @@ import java.util.Map;
import java.util.Map.Entry;
import javax.annotation.Nonnull;
import net.minecraftforge.fml.ModThreadContext;
import net.minecraftforge.fml.ModLoadingContext;
import net.minecraftforge.fml.common.ObfuscationReflectionHelper;
import net.minecraftforge.fml.loading.AdvancedLogMessageAdapter;
import org.apache.commons.lang3.Validate;
import java.util.Set;
import java.util.stream.Collectors;
import com.google.common.base.Preconditions;
import com.google.common.collect.ArrayListMultimap;
@ -280,7 +279,7 @@ public class ForgeRegistry<V extends IForgeRegistryEntry<V>> implements IForgeRe
int add(int id, V value)
{
final String owner = ModThreadContext.get().getActiveContainer().getNamespace();
final String owner = ModLoadingContext.get().getActiveContainer().getNamespace();
return add(id, value, owner);
}

View File

@ -42,7 +42,7 @@ import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.RegistryEvent;
import net.minecraftforge.event.RegistryEvent.MissingMappings;
import net.minecraftforge.fml.LifecycleEventProvider;
import net.minecraftforge.fml.ModThreadContext;
import net.minecraftforge.fml.ModLoadingContext;
import net.minecraftforge.fml.StartupQuery;
import net.minecraftforge.fml.common.EnhancedRuntimeException;
import net.minecraftforge.fml.common.registry.VillagerRegistry.VillagerProfession;
@ -51,8 +51,6 @@ import net.minecraftforge.fml.loading.AdvancedLogMessageAdapter;
import org.apache.commons.lang3.Validate;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.Marker;
import org.apache.logging.log4j.MarkerManager;
import javax.annotation.Nullable;
import java.lang.reflect.Field;
@ -66,7 +64,6 @@ import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import static net.minecraftforge.fml.Logging.CORE;
import static net.minecraftforge.registries.ForgeRegistry.REGISTRIES;
/**
@ -828,7 +825,7 @@ public class GameData
int index = name.lastIndexOf(':');
String oldPrefix = index == -1 ? "" : name.substring(0, index).toLowerCase(Locale.ROOT);
name = index == -1 ? name : name.substring(index + 1);
String prefix = ModThreadContext.get().getActiveContainer().getNamespace();
String prefix = ModLoadingContext.get().getActiveContainer().getNamespace();
if (!oldPrefix.equals(prefix) && oldPrefix.length() > 0)
{
LogManager.getLogger().info("Potentially Dangerous alternative prefix `{}` for name `{}`, expected `{}`. This could be a intended override, but in most cases indicates a broken mod.", oldPrefix, name, prefix);