359 lines
14 KiB
Java
359 lines
14 KiB
Java
/*
|
|
* Minecraft Forge
|
|
* Copyright (c) 2016-2018.
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation version 2.1
|
|
* of the License.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
package net.minecraftforge.fml.common.network;
|
|
|
|
import io.netty.channel.ChannelHandler;
|
|
import io.netty.channel.ChannelHandlerContext;
|
|
import io.netty.handler.codec.MessageToMessageCodec;
|
|
import io.netty.util.AttributeKey;
|
|
|
|
import java.util.Collection;
|
|
import java.util.EnumMap;
|
|
import java.util.Map;
|
|
import java.util.Set;
|
|
import java.util.stream.Collectors;
|
|
|
|
import net.minecraft.entity.player.EntityPlayer;
|
|
import net.minecraft.entity.player.EntityPlayerMP;
|
|
import net.minecraft.inventory.Container;
|
|
import net.minecraft.network.INetHandler;
|
|
import net.minecraft.world.World;
|
|
import net.minecraftforge.api.distmarker.Dist;
|
|
import net.minecraftforge.fml.LogicalSide;
|
|
import net.minecraftforge.fml.common.FMLCommonHandler;
|
|
import net.minecraftforge.fml.common.FMLLog;
|
|
import net.minecraftforge.fml.ModContainer;
|
|
import net.minecraftforge.fml.common.network.handshake.NetworkDispatcher;
|
|
import net.minecraftforge.fml.common.network.internal.FMLProxyPacket;
|
|
import net.minecraftforge.fml.common.network.internal.NetworkModHolder;
|
|
import net.minecraftforge.fml.common.network.simpleimpl.SimpleNetworkWrapper;
|
|
|
|
import com.google.common.collect.ImmutableMap;
|
|
import com.google.common.collect.Maps;
|
|
|
|
import javax.annotation.Nullable;
|
|
|
|
/**
|
|
* @author cpw
|
|
*
|
|
*/
|
|
public class NetworkRegistry
|
|
{
|
|
public static final NetworkRegistry INSTANCE = new NetworkRegistry();
|
|
private EnumMap<LogicalSide,Map<String,FMLEmbeddedChannel>> channels = Maps.newEnumMap(LogicalSide.class);
|
|
private Map<ModContainer, NetworkModHolder> registry = Maps.newHashMap();
|
|
private Map<ModContainer, IGuiHandler> serverGuiHandlers = Maps.newHashMap();
|
|
private Map<ModContainer, IGuiHandler> clientGuiHandlers = Maps.newHashMap();
|
|
|
|
/**
|
|
* Set in the {@link ChannelHandlerContext}
|
|
*/
|
|
public static final AttributeKey<String> FML_CHANNEL = AttributeKey.valueOf("fml:channelName");
|
|
public static final AttributeKey<LogicalSide> CHANNEL_SOURCE = AttributeKey.valueOf("fml:channelSource");
|
|
public static final AttributeKey<ModContainer> MOD_CONTAINER = AttributeKey.valueOf("fml:modContainer");
|
|
public static final AttributeKey<INetHandler> NET_HANDLER = AttributeKey.valueOf("fml:netHandler");
|
|
|
|
// Version 1: ServerHello only contains this value as a byte
|
|
// Version 2: ServerHello additionally contains a 4 byte (int) dimension for the logging in client
|
|
public static final byte FML_PROTOCOL = 2;
|
|
|
|
private NetworkRegistry()
|
|
{
|
|
channels.put(LogicalSide.CLIENT, Maps.<String,FMLEmbeddedChannel>newConcurrentMap());
|
|
channels.put(LogicalSide.SERVER, Maps.<String,FMLEmbeddedChannel>newConcurrentMap());
|
|
}
|
|
|
|
/**
|
|
* Represents a target point for the ALLROUNDPOINT target.
|
|
*
|
|
* @author cpw
|
|
*
|
|
*/
|
|
public static class TargetPoint {
|
|
/**
|
|
* A target point
|
|
* @param dimension The dimension to target
|
|
* @param x The X coordinate
|
|
* @param y The Y coordinate
|
|
* @param z The Z coordinate
|
|
* @param range The range
|
|
*/
|
|
public TargetPoint(int dimension, double x, double y, double z, double range)
|
|
{
|
|
this.x = x;
|
|
this.y = y;
|
|
this.z = z;
|
|
this.range = range;
|
|
this.dimension = dimension;
|
|
}
|
|
public final double x;
|
|
public final double y;
|
|
public final double z;
|
|
public final double range;
|
|
public final int dimension;
|
|
}
|
|
/**
|
|
* Create a new synchronous message channel pair based on netty.
|
|
*
|
|
* There are two preconstructed models available:
|
|
* <ul>
|
|
* <li> {@link #newSimpleChannel(String)} provides {@link SimpleNetworkWrapper}, a simple implementation of a netty handler, suitable for those who don't
|
|
* wish to dive too deeply into netty.
|
|
* <li> {@link #newEventDrivenChannel(String)} (String)} provides {@link FMLEventChannel} an event driven implementation, with lower level
|
|
* access to the network data stream, for those with advanced bitbanging needs that don't wish to poke netty too hard.
|
|
* <li> Alternatively, simply use the netty features provided here and implement the full power of the netty stack.
|
|
* </ul>
|
|
*
|
|
* There are two channels created : one for each logical Dist (conDistred as the source of an outbound message)
|
|
* The returned map will contain a value for each logical Dist, though both will only be working in the
|
|
* integrated server case.
|
|
*
|
|
* The channel expects to read and write using {@link FMLProxyPacket}. All operation is synchronous, as the
|
|
* asynchronous behaviour occurs at a lower level in netty.
|
|
*
|
|
* The first handler in the pipeline is special and should not be removed or moved from the head - it transforms
|
|
* packets from the outbound of this pipeline into custom packets, based on the current {@link AttributeKey} value
|
|
* {@link FMLOutboundHandler#FML_MESSAGETARGET} and {@link FMLOutboundHandler#FML_MESSAGETARGETARGS} set on the channel.
|
|
* For the client to server channel (source Dist : CLIENT) this is fixed as "TOSERVER". For SERVER to CLIENT packets,
|
|
* several possible values exist.
|
|
*
|
|
* Mod Messages should be transformed using a something akin to a {@link MessageToMessageCodec}. FML provides
|
|
* a utility codec, {@link FMLIndexedMessageToMessageCodec} that transforms from {@link FMLProxyPacket} to a mod
|
|
* message using a message discriminator byte. This is optional, but highly recommended for use.
|
|
*
|
|
* Note also that the handlers supplied need to be {@link ChannelHandler.Sharable} - they are injected into two
|
|
* channels.
|
|
*
|
|
* @param name
|
|
* @param handlers
|
|
* @return
|
|
*/
|
|
public EnumMap<LogicalSide,FMLEmbeddedChannel> newChannel(String name, ChannelHandler... handlers)
|
|
{
|
|
if (channels.get(LogicalSide.CLIENT).containsKey(name) || channels.get(LogicalSide.SERVER).containsKey(name) || name.startsWith("MC|") || name.startsWith("\u0001") || name.startsWith("FML"))
|
|
{
|
|
throw new RuntimeException("That channel is already registered");
|
|
}
|
|
EnumMap<LogicalSide, FMLEmbeddedChannel> result = Maps.newEnumMap(LogicalSide.class);
|
|
|
|
for (LogicalSide side : LogicalSide.values())
|
|
{
|
|
FMLEmbeddedChannel channel = new FMLEmbeddedChannel(name, side, handlers);
|
|
channels.get(side).put(name,channel);
|
|
result.put(side, channel);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Construct a new {@link SimpleNetworkWrapper} for the channel.
|
|
*
|
|
* @param name The name of the channel
|
|
* @return A {@link SimpleNetworkWrapper} for handling this channel
|
|
*/
|
|
public SimpleNetworkWrapper newSimpleChannel(String name)
|
|
{
|
|
return new SimpleNetworkWrapper(name);
|
|
}
|
|
/**
|
|
* Construct a new {@link FMLEventChannel} for the channel.
|
|
*
|
|
* @param name The name of the channel
|
|
* @return An {@link FMLEventChannel} for handling this channel
|
|
*/
|
|
public FMLEventChannel newEventDrivenChannel(String name)
|
|
{
|
|
return new FMLEventChannel(name);
|
|
}
|
|
/**
|
|
* INTERNAL Create a new channel pair with the specified name and channel handlers.
|
|
* This is used internally in forge and FML
|
|
*
|
|
* @param container The container to associate the channel with
|
|
* @param name The name for the channel
|
|
* @param handlers Some {@link ChannelHandler} for the channel
|
|
* @return an {@link EnumMap} of the pair of channels. keys are {@link Dist}. There will always be two entries.
|
|
*/
|
|
public EnumMap<LogicalSide,FMLEmbeddedChannel> newChannel(ModContainer container, String name, ChannelHandler... handlers)
|
|
{
|
|
if (channels.get(Dist.CLIENT).containsKey(name) || channels.get(Dist.DEDICATED_SERVER).containsKey(name) || name.startsWith("MC|") || name.startsWith("\u0001") || (name.startsWith("FML") && !("FML".equals(container.getModId()))))
|
|
{
|
|
throw new RuntimeException("That channel is already registered");
|
|
}
|
|
EnumMap<LogicalSide,FMLEmbeddedChannel> result = Maps.newEnumMap(LogicalSide.class);
|
|
|
|
for (LogicalSide Dist : LogicalSide.values())
|
|
{
|
|
FMLEmbeddedChannel channel = new FMLEmbeddedChannel(container, name, Dist, handlers);
|
|
channels.get(Dist).put(name,channel);
|
|
result.put(Dist, channel);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
public FMLEmbeddedChannel getChannel(String name, Dist source)
|
|
{
|
|
return channels.get(source).get(name);
|
|
}
|
|
/**
|
|
* Register an {@link IGuiHandler} for the supplied mod object.
|
|
*
|
|
* @param mod The mod to handle GUIs for
|
|
* @param handler A handler for creating GUI related objects
|
|
*/
|
|
public void registerGuiHandler(Object mod, IGuiHandler handler)
|
|
{
|
|
ModContainer mc = FMLCommonHandler.instance().findContainerFor(mod);
|
|
if (mc == null)
|
|
{
|
|
FMLLog.log.error("Mod of type {} attempted to register a gui network handler during a construction phase", mod.getClass().getName());
|
|
throw new RuntimeException("Invalid attempt to create a GUI during mod construction. Use an EventHandler instead");
|
|
}
|
|
serverGuiHandlers.put(mc, handler);
|
|
clientGuiHandlers.put(mc, handler);
|
|
}
|
|
|
|
/**
|
|
* INTERNAL method for accessing the Gui registry
|
|
* @param mc Mod Container
|
|
* @param player Player
|
|
* @param modGuiId guiId
|
|
* @param world World
|
|
* @param x X coord
|
|
* @param y Y coord
|
|
* @param z Z coord
|
|
* @return The server Dist GUI object (An instance of {@link Container})
|
|
*/
|
|
@Nullable
|
|
public Container getRemoteGuiContainer(ModContainer mc, EntityPlayerMP player, int modGuiId, World world, int x, int y, int z)
|
|
{
|
|
IGuiHandler handler = serverGuiHandlers.get(mc);
|
|
|
|
if (handler != null)
|
|
{
|
|
return (Container)handler.getServerGuiElement(modGuiId, player, world, x, y, z);
|
|
}
|
|
else
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* INTERNAL method for accessing the Gui registry
|
|
* @param mc Mod Container
|
|
* @param player Player
|
|
* @param modGuiId guiId
|
|
* @param world World
|
|
* @param x X coord
|
|
* @param y Y coord
|
|
* @param z Z coord
|
|
* @return The client Dist GUI object (An instance of {@link net.minecraft.client.gui.Gui})
|
|
*/
|
|
@Nullable
|
|
public Object getLocalGuiContainer(ModContainer mc, EntityPlayer player, int modGuiId, World world, int x, int y, int z)
|
|
{
|
|
IGuiHandler handler = clientGuiHandlers.get(mc);
|
|
return handler.getClientGuiElement(modGuiId, player, world, x, y, z);
|
|
}
|
|
|
|
/**
|
|
* Is there a channel with this name on this Dist?
|
|
* @param channelName The name
|
|
* @param source the Dist
|
|
* @return if there's a channel
|
|
*/
|
|
public boolean hasChannel(String channelName, Dist source)
|
|
{
|
|
return channels.get(source).containsKey(channelName);
|
|
}
|
|
|
|
/**
|
|
* INTERNAL method for registering a mod as a network capable thing
|
|
* @param fmlModContainer The fml mod container
|
|
* @param clazz a class
|
|
* @param remoteVersionRange the acceptable remote range
|
|
* @param asmHarvestedData internal data
|
|
*/
|
|
/*
|
|
public void register(ModContainer fmlModContainer, Class<?> clazz, @Nullable String remoteVersionRange, ASMDataTable asmHarvestedData)
|
|
{
|
|
NetworkModHolder networkModHolder = new NetworkModHolder(fmlModContainer, clazz, remoteVersionRange, asmHarvestedData);
|
|
registry.put(fmlModContainer, networkModHolder);
|
|
networkModHolder.testVanillaAcceptance();
|
|
}
|
|
*/
|
|
|
|
/*
|
|
public boolean isVanillaAccepted(Dist from)
|
|
{
|
|
return registry.values().stream()
|
|
.allMatch(mod -> mod.acceptsVanilla(from));
|
|
}
|
|
|
|
public Collection<String> getRequiredMods(Dist from)
|
|
{
|
|
return registry.values().stream()
|
|
.filter(mod -> !mod.acceptsVanilla(from))
|
|
.map(mod -> mod.getContainer().getModId())
|
|
.sorted()
|
|
.collect(Collectors.toList());
|
|
}
|
|
*/
|
|
|
|
public Map<ModContainer,NetworkModHolder> registry()
|
|
{
|
|
return ImmutableMap.copyOf(registry);
|
|
}
|
|
|
|
/**
|
|
* All the valid channel names for a Dist
|
|
* @param Dist the Dist
|
|
* @return the set of channel names
|
|
*/
|
|
public Set<String> channelNamesFor(Dist Dist)
|
|
{
|
|
return channels.get(Dist).keySet();
|
|
}
|
|
|
|
/**
|
|
* INTERNAL fire a handshake to all channels
|
|
* @param networkDispatcher The dispatcher firing
|
|
* @param origin which Dist the dispatcher is on
|
|
*/
|
|
public void fireNetworkHandshake(NetworkDispatcher networkDispatcher, Dist origin)
|
|
{
|
|
/*
|
|
NetworkHandshakeEstablished handshake = new NetworkHandshakeEstablished(networkDispatcher, networkDispatcher.getNetHandler(), origin);
|
|
for (FMLEmbeddedChannel channel : channels.get(origin).values())
|
|
{
|
|
channel.attr(FMLOutboundHandler.FML_MESSAGETARGET).set(OutboundTarget.DISPATCHER);
|
|
channel.attr(FMLOutboundHandler.FML_MESSAGETARGETARGS).set(networkDispatcher);
|
|
channel.pipeline().fireUserEventTriggered(handshake);
|
|
}
|
|
*/
|
|
}
|
|
|
|
public void cleanAttributes()
|
|
{
|
|
channels.values().forEach(map -> map.values().forEach(FMLEmbeddedChannel::cleanAttributes));
|
|
}
|
|
}
|