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 * @return A {@link CompletableFuture} that completes at said time
*/ */
public static CompletableFuture<Void> runLater(Runnable workToEnqueue) { 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()); return CompletableFuture.runAsync(workToEnqueue, deferredExecutor).exceptionally(DeferredWorkQueue.handleException());
} }
@ -154,7 +154,7 @@ public class DeferredWorkQueue
* @return A {@link CompletableFuture} that completes at said time * @return A {@link CompletableFuture} that completes at said time
*/ */
public static <T> CompletableFuture<T> getLater(Supplier<T> workToEnqueue) { 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()); 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.Minecraft;
import net.minecraft.client.gui.GuiScreen; import net.minecraft.client.gui.GuiScreen;
import net.minecraft.resources.IResourcePack; import net.minecraft.resources.IResourcePack;
import net.minecraftforge.fml.network.FMLPlayMessages;
import net.minecraftforge.fml.packs.ModFileResourcePack; import net.minecraftforge.fml.packs.ModFileResourcePack;
import java.util.function.BiFunction; import java.util.function.BiFunction;
import java.util.function.Function;
public class ExtensionPoint<T> 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<>(); 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 Class<T> type;
private ExtensionPoint() { private ExtensionPoint() {

View file

@ -115,11 +115,11 @@ public abstract class ModContainer
} }
@SuppressWarnings("unchecked") @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()); 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); extensionPoints.put(point, extension);
} }

View file

@ -19,11 +19,13 @@
package net.minecraftforge.fml; package net.minecraftforge.fml;
public class ModThreadContext import java.util.function.Supplier;
{
private static ThreadLocal<ModThreadContext> context = ThreadLocal.withInitial(ModThreadContext::new);
public static ModThreadContext get() { public class ModLoadingContext
{
private static ThreadLocal<ModLoadingContext> context = ThreadLocal.withInitial(ModLoadingContext::new);
public static ModLoadingContext get() {
return context.get(); return context.get();
} }
@ -36,4 +38,15 @@ public class ModThreadContext
public ModContainer getActiveContainer() { public ModContainer getActiveContainer() {
return activeContainer == null ? DefaultModContainers.MINECRAFT : activeContainer; 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) public static Optional<BiFunction<Minecraft, GuiScreen, GuiScreen>> getGuiFactoryFor(ModInfo selectedMod)
{ {
return ModList.get().getModContainerById(selectedMod.getModId()). 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.ModContainer;
import net.minecraftforge.fml.ModLoadingException; import net.minecraftforge.fml.ModLoadingException;
import net.minecraftforge.fml.ModLoadingStage; 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.IModInfo;
import net.minecraftforge.forgespi.language.ModFileScanData; import net.minecraftforge.forgespi.language.ModFileScanData;
@ -94,7 +94,7 @@ public class FMLModContainer extends ModContainer
private void beforeEvent(LifecycleEventProvider.LifecycleEvent lifecycleEvent) { private void beforeEvent(LifecycleEventProvider.LifecycleEvent lifecycleEvent) {
FMLModLoadingContext.get().setActiveContainer(this); FMLModLoadingContext.get().setActiveContainer(this);
ModThreadContext.get().setActiveContainer(this); ModLoadingContext.get().setActiveContainer(this);
} }
private void fireEvent(LifecycleEventProvider.LifecycleEvent lifecycleEvent) { private void fireEvent(LifecycleEventProvider.LifecycleEvent lifecycleEvent) {
@ -113,7 +113,7 @@ public class FMLModContainer extends ModContainer
} }
private void afterEvent(LifecycleEventProvider.LifecycleEvent lifecycleEvent) { private void afterEvent(LifecycleEventProvider.LifecycleEvent lifecycleEvent) {
ModThreadContext.get().setActiveContainer(null); ModLoadingContext.get().setActiveContainer(null);
FMLModLoadingContext.get().setActiveContainer(null); FMLModLoadingContext.get().setActiveContainer(null);
if (getCurrentState() == ModLoadingStage.ERROR) { if (getCurrentState() == ModLoadingStage.ERROR) {
LOGGER.error(LOADING,"An error occurred while dispatching event {} to {}", lifecycleEvent.fromStage(), getModId()); 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(); 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) { 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) { 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() public IEventBus getModEventBus()
{ {
return getActiveContainer().getEventBus(); 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() public FMLModContainer getActiveContainer()
{ {
return activeContainer; return activeContainer;

View file

@ -19,20 +19,19 @@
package net.minecraftforge.fml.network; package net.minecraftforge.fml.network;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.entity.Entity; import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityTracker; import net.minecraft.entity.EntityTracker;
import net.minecraft.entity.EntityType; import net.minecraft.entity.EntityType;
import net.minecraft.network.PacketBuffer; import net.minecraft.network.PacketBuffer;
import net.minecraft.util.ResourceLocation; import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.MathHelper;
import net.minecraftforge.fml.ExtensionPoint;
import net.minecraftforge.fml.ModList;
import net.minecraftforge.fml.common.registry.IEntityAdditionalSpawnData; import net.minecraftforge.fml.common.registry.IEntityAdditionalSpawnData;
import java.util.UUID; import java.util.UUID;
import java.util.function.Function;
import java.util.function.Supplier; import java.util.function.Supplier;
public class FMLPlayMessages public class FMLPlayMessages
@ -166,9 +165,9 @@ public class FMLPlayMessages
{ {
private final ResourceLocation id; private final ResourceLocation id;
private final int windowId; 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.id = id;
this.windowId = windowId; this.windowId = windowId;
@ -179,27 +178,34 @@ public class FMLPlayMessages
{ {
buf.writeResourceLocation(msg.id); buf.writeResourceLocation(msg.id);
buf.writeVarInt(msg.windowId); buf.writeVarInt(msg.windowId);
buf.writeByteArray(msg.additionalData); buf.writeBytes(msg.additionalData);
} }
public static OpenContainer decode(PacketBuffer buf) 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) public static void handle(OpenContainer msg, Supplier<NetworkEvent.Context> ctx)
{ {
ctx.get().enqueueWork(() -> { ctx.get().enqueueWork(() -> ModList.get().getModContainerById(msg.id.getNamespace()).ifPresent(mc->
Supplier<Function<ByteBuf, GuiScreen>> sup = NetworkRegistry.guiHandlers.get(msg.id); mc.getCustomExtension(ExtensionPoint.GUIFACTORY).map(f -> f.apply(msg)).ifPresent(gui-> {
if (sup != null) { Minecraft.getInstance().displayGuiScreen(gui);
GuiScreen gui = sup.get().apply(Unpooled.wrappedBuffer(msg.additionalData)); Minecraft.getInstance().player.openContainer.windowId = msg.windowId;
if (gui != null) { })));
Minecraft.getInstance().displayGuiScreen(gui);
Minecraft.getInstance().player.openContainer.windowId = msg.windowId;
}
}
});
ctx.get().setPacketHandled(true); 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; package net.minecraftforge.fml.network;
import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled;
import net.minecraft.entity.Entity; import net.minecraft.entity.Entity;
import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.inventory.Container; import net.minecraft.inventory.Container;
import net.minecraft.network.NetHandlerPlayServer; import net.minecraft.network.NetHandlerPlayServer;
import net.minecraft.network.NetworkManager; import net.minecraft.network.NetworkManager;
import net.minecraft.network.Packet; import net.minecraft.network.Packet;
import net.minecraft.network.PacketBuffer;
import net.minecraft.network.handshake.client.CPacketHandshake; import net.minecraft.network.handshake.client.CPacketHandshake;
import net.minecraft.server.network.NetHandlerLoginServer; import net.minecraft.server.network.NetHandlerLoginServer;
import net.minecraft.util.ResourceLocation; import net.minecraft.util.ResourceLocation;
@ -87,10 +88,22 @@ public class NetworkHooks
return FMLHandshakeHandler.tickLogin(networkManager); 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()); if (player.world.isRemote) return;
Container c = container.createContainer(player.inventory, player); ResourceLocation id = new ResourceLocation(containerSupplier.getGuiID());
Container c = containerSupplier.createContainer(player.inventory, player);
player.closeScreen(); player.closeScreen();
player.getNextWindowId(); player.getNextWindowId();
player.openContainer = c; player.openContainer = c;
@ -98,14 +111,13 @@ public class NetworkHooks
player.openContainer.addListener(player); player.openContainer.addListener(player);
MinecraftForge.EVENT_BUS.post(new PlayerContainerEvent.Open(player, c)); MinecraftForge.EVENT_BUS.post(new PlayerContainerEvent.Open(player, c));
byte[] additional;
if (extraData == null) { if (extraData == null) {
additional = new byte[0]; extraData = new PacketBuffer(Unpooled.buffer());
} else {
additional = new byte[extraData.readableBytes()];
extraData.readBytes(additional);
} }
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); FMLPlayHandler.channel.sendTo(msg, player.connection.getNetworkManager(), NetworkDirection.PLAY_TO_CLIENT);
} }
} }

View file

@ -19,8 +19,6 @@
package net.minecraftforge.fml.network; 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.NBTTagCompound;
import net.minecraft.nbt.NBTTagList; import net.minecraft.nbt.NBTTagList;
import net.minecraft.network.NetworkManager; 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.Marker;
import org.apache.logging.log4j.MarkerManager; import org.apache.logging.log4j.MarkerManager;
import java.util.*; import java.util.ArrayList;
import java.util.concurrent.ConcurrentHashMap; 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.BiFunction;
import java.util.function.Function;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.function.Supplier; import java.util.function.Supplier;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -51,21 +51,6 @@ public class NetworkRegistry
private static final Marker NETREGISTRY = MarkerManager.getMarker("NETREGISTRY"); private static final Marker NETREGISTRY = MarkerManager.getMarker("NETREGISTRY");
private static Map<ResourceLocation, NetworkInstance> instances = new HashMap<>(); 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 * 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 java.util.Map.Entry;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import net.minecraftforge.fml.ModThreadContext; import net.minecraftforge.fml.ModLoadingContext;
import net.minecraftforge.fml.common.ObfuscationReflectionHelper; import net.minecraftforge.fml.common.ObfuscationReflectionHelper;
import net.minecraftforge.fml.loading.AdvancedLogMessageAdapter; import net.minecraftforge.fml.loading.AdvancedLogMessageAdapter;
import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.Validate;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.google.common.collect.ArrayListMultimap; 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) 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); 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;
import net.minecraftforge.event.RegistryEvent.MissingMappings; import net.minecraftforge.event.RegistryEvent.MissingMappings;
import net.minecraftforge.fml.LifecycleEventProvider; import net.minecraftforge.fml.LifecycleEventProvider;
import net.minecraftforge.fml.ModThreadContext; import net.minecraftforge.fml.ModLoadingContext;
import net.minecraftforge.fml.StartupQuery; import net.minecraftforge.fml.StartupQuery;
import net.minecraftforge.fml.common.EnhancedRuntimeException; import net.minecraftforge.fml.common.EnhancedRuntimeException;
import net.minecraftforge.fml.common.registry.VillagerRegistry.VillagerProfession; 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.commons.lang3.Validate;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.Marker;
import org.apache.logging.log4j.MarkerManager;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.lang.reflect.Field; import java.lang.reflect.Field;
@ -66,7 +64,6 @@ import java.util.function.Consumer;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static net.minecraftforge.fml.Logging.CORE;
import static net.minecraftforge.registries.ForgeRegistry.REGISTRIES; import static net.minecraftforge.registries.ForgeRegistry.REGISTRIES;
/** /**
@ -828,7 +825,7 @@ public class GameData
int index = name.lastIndexOf(':'); int index = name.lastIndexOf(':');
String oldPrefix = index == -1 ? "" : name.substring(0, index).toLowerCase(Locale.ROOT); String oldPrefix = index == -1 ? "" : name.substring(0, index).toLowerCase(Locale.ROOT);
name = index == -1 ? name : name.substring(index + 1); 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) 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); 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);