ForgePatch/src/main/java/net/minecraftforge/fml/network/NetworkHooks.java

259 lines
12 KiB
Java

/*
* Minecraft Forge
* Copyright (c) 2016-2020.
*
* 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.network;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import net.minecraft.tags.ITagCollection;
import net.minecraft.tags.ITagCollectionSupplier;
import net.minecraft.util.text.StringTextComponent;
import net.minecraftforge.fml.common.thread.EffectiveSide;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import io.netty.buffer.Unpooled;
import net.minecraft.entity.Entity;
import net.minecraft.entity.player.ServerPlayerEntity;
import net.minecraft.inventory.container.Container;
import net.minecraft.inventory.container.ContainerType;
import net.minecraft.inventory.container.INamedContainerProvider;
import net.minecraft.network.IPacket;
import net.minecraft.network.NetworkManager;
import net.minecraft.network.PacketBuffer;
import net.minecraft.network.handshake.client.CHandshakePacket;
import net.minecraft.network.login.ServerLoginNetHandler;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.BlockPos;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.entity.player.PlayerContainerEvent;
import net.minecraftforge.fml.config.ConfigTracker;
public class NetworkHooks
{
private static final Logger LOGGER = LogManager.getLogger();
public static String getFMLVersion(final String ip)
{
return ip.contains("\0") ? Objects.equals(ip.split("\0")[1], FMLNetworkConstants.NETVERSION) ? FMLNetworkConstants.NETVERSION : ip.split("\0")[1] : FMLNetworkConstants.NOVERSION;
}
public static ConnectionType getConnectionType(final Supplier<NetworkManager> connection)
{
return getConnectionType(connection.get().channel());
}
public static ConnectionType getConnectionType(ChannelHandlerContext context)
{
return getConnectionType(context.channel());
}
private static ConnectionType getConnectionType(Channel channel)
{
return ConnectionType.forVersionFlag(channel.attr(FMLNetworkConstants.FML_NETVERSION).get());
}
public static IPacket<?> getEntitySpawningPacket(Entity entity)
{
return FMLNetworkConstants.playChannel.toVanillaPacket(new FMLPlayMessages.SpawnEntity(entity), NetworkDirection.PLAY_TO_CLIENT);
}
public static boolean onCustomPayload(final ICustomPacket<?> packet, final NetworkManager manager) {
return NetworkRegistry.findTarget(packet.getName()).
filter(ni->validateSideForProcessing(packet, ni, manager)).
map(ni->ni.dispatch(packet.getDirection(), packet, manager)).orElse(Boolean.FALSE);
}
private static boolean validateSideForProcessing(final ICustomPacket<?> packet, final NetworkInstance ni, final NetworkManager manager) {
if (packet.getDirection().getReceptionSide() != EffectiveSide.get()) {
manager.closeChannel(new StringTextComponent("Illegal packet received, terminating connection"));
return false;
}
return true;
}
public static void validatePacketDirection(final NetworkDirection packetDirection, final Optional<NetworkDirection> expectedDirection, final NetworkManager connection) {
if (packetDirection != expectedDirection.orElse(packetDirection)) {
connection.closeChannel(new StringTextComponent("Illegal packet received, terminating connection"));
throw new IllegalStateException("Invalid packet received, aborting connection");
}
}
public static void registerServerLoginChannel(NetworkManager manager, CHandshakePacket packet)
{
manager.channel().attr(FMLNetworkConstants.FML_NETVERSION).set(packet.getFMLVersion());
FMLHandshakeHandler.registerHandshake(manager, NetworkDirection.LOGIN_TO_CLIENT);
}
public synchronized static void registerClientLoginChannel(NetworkManager manager)
{
manager.channel().attr(FMLNetworkConstants.FML_NETVERSION).set(FMLNetworkConstants.NOVERSION);
FMLHandshakeHandler.registerHandshake(manager, NetworkDirection.LOGIN_TO_SERVER);
}
public synchronized static void sendMCRegistryPackets(NetworkManager manager, String direction) {
final Set<ResourceLocation> resourceLocations = NetworkRegistry.buildChannelVersions().keySet().stream().
filter(rl -> !Objects.equals(rl.getNamespace(), "minecraft")).
collect(Collectors.toSet());
FMLMCRegisterPacketHandler.INSTANCE.addChannels(resourceLocations, manager);
FMLMCRegisterPacketHandler.INSTANCE.sendRegistry(manager, NetworkDirection.valueOf(direction));
}
//TODO Dimensions..
/* public synchronized static void sendDimensionDataPacket(NetworkManager manager, ServerPlayerEntity player) {
// don't send vanilla dims
if (player.dimension.isVanilla()) return;
// don't sent to local - we already have a valid dim registry locally
if (manager.isLocalChannel()) return;
FMLNetworkConstants.playChannel.sendTo(new FMLPlayMessages.DimensionInfoMessage(player.dimension), manager, NetworkDirection.PLAY_TO_CLIENT);
}*/
public static boolean isVanillaConnection(NetworkManager manager)
{
if (manager == null || manager.channel() == null) throw new NullPointerException("ARGH! Network Manager is null (" + manager != null ? "CHANNEL" : "MANAGER"+")" );
return getConnectionType(() -> manager) == ConnectionType.VANILLA;
}
public static void handleClientLoginSuccess(NetworkManager manager) {
if (isVanillaConnection(manager)) {
LOGGER.info("Connected to a vanilla server. Catching up missing behaviour.");
ConfigTracker.INSTANCE.loadDefaultServerConfigs();
} else {
LOGGER.info("Connected to a modded server.");
}
}
public static boolean tickNegotiation(ServerLoginNetHandler netHandlerLoginServer, NetworkManager networkManager, ServerPlayerEntity player)
{
return FMLHandshakeHandler.tickLogin(networkManager);
}
/**
* Request to open a GUI on the client, from the server
*
* Refer to {@link net.minecraftforge.fml.ExtensionPoint#GUIFACTORY} for how to provide a function to consume
* these GUI requests on the client.
*
* 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 A supplier of container properties including the registry name of the container
*/
public static void openGui(ServerPlayerEntity player, INamedContainerProvider containerSupplier)
{
openGui(player, containerSupplier, buf -> {});
}
/**
* Request to open a GUI on the client, from the server
*
* Refer to {@link net.minecraftforge.fml.ExtensionPoint#GUIFACTORY} for how to provide a function to consume
* these GUI requests on the client.
*
* 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 A supplier of container properties including the registry name of the container
* @param pos A block pos, which will be encoded into the auxillary data for this request
*/
public static void openGui(ServerPlayerEntity player, INamedContainerProvider containerSupplier, BlockPos pos)
{
openGui(player, containerSupplier, buf -> buf.writeBlockPos(pos));
}
/**
* Request to open a GUI on the client, from the server
*
* Refer to {@link net.minecraftforge.fml.ExtensionPoint#GUIFACTORY} for how to provide a function to consume
* these GUI requests on the client.
*
* 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.
* The maximum size for #extraDataWriter is 32600 bytes.
*
* @param player The player to open the GUI for
* @param containerSupplier A supplier of container properties including the registry name of the container
* @param extraDataWriter Consumer to write any additional data the GUI needs
*/
public static void openGui(ServerPlayerEntity player, INamedContainerProvider containerSupplier, Consumer<PacketBuffer> extraDataWriter)
{
if (player.world.isRemote) return;
player.closeContainer();
player.getNextWindowId();
int openContainerId = player.currentWindowId;
PacketBuffer extraData = new PacketBuffer(Unpooled.buffer());
extraDataWriter.accept(extraData);
extraData.readerIndex(0); // reset to beginning in case modders read for whatever reason
PacketBuffer output = new PacketBuffer(Unpooled.buffer());
output.writeVarInt(extraData.readableBytes());
output.writeBytes(extraData);
if (output.readableBytes() > 32600 || output.readableBytes() < 1) {
throw new IllegalArgumentException("Invalid PacketBuffer for openGui, found "+ output.readableBytes()+ " bytes");
}
Container c = containerSupplier.createMenu(openContainerId, player.inventory, player);
ContainerType<?> type = c.getType();
FMLPlayMessages.OpenContainer msg = new FMLPlayMessages.OpenContainer(type, openContainerId, containerSupplier.getDisplayName(), output);
FMLNetworkConstants.playChannel.sendTo(msg, player.connection.getNetworkManager(), NetworkDirection.PLAY_TO_CLIENT);
player.openContainer = c;
player.openContainer.addListener(player);
MinecraftForge.EVENT_BUS.post(new PlayerContainerEvent.Open(player, c));
}
/**
* Syncs the custom tag types attached to a {@link ITagCollectionSupplier} to all connected players.
* @param tagCollectionSupplier The tag collection supplier containing the custom tags
*/
public static void syncCustomTagTypes(ITagCollectionSupplier tagCollectionSupplier)
{
Map<ResourceLocation, ITagCollection<?>> customTagTypes = tagCollectionSupplier.getCustomTagTypes();
if (!customTagTypes.isEmpty())
{
FMLNetworkConstants.playChannel.send(PacketDistributor.ALL.noArg(), new FMLPlayMessages.SyncCustomTagTypes(customTagTypes));
}
}
/**
* Syncs the custom tag types attached to a {@link ITagCollectionSupplier} to the given player.
* @param player The player to sync the custom tags to.
* @param tagCollectionSupplier The tag collection supplier containing the custom tags
*/
public static void syncCustomTagTypes(ServerPlayerEntity player, ITagCollectionSupplier tagCollectionSupplier)
{
Map<ResourceLocation, ITagCollection<?>> customTagTypes = tagCollectionSupplier.getCustomTagTypes();
if (!customTagTypes.isEmpty())
{
FMLNetworkConstants.playChannel.sendTo(new FMLPlayMessages.SyncCustomTagTypes(customTagTypes), player.connection.getNetworkManager(), NetworkDirection.PLAY_TO_CLIENT);
}
}
}