/* * 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.ChannelHandlerContext; import io.netty.channel.ChannelOutboundHandlerAdapter; import io.netty.channel.ChannelPromise; import io.netty.channel.embedded.EmbeddedChannel; import io.netty.util.AttributeKey; import java.util.List; import java.util.Set; import net.minecraft.entity.Entity; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.network.NetworkManager; import net.minecraft.server.management.PlayerChunkMap; import net.minecraft.server.management.PlayerChunkMapEntry; import net.minecraft.util.math.MathHelper; import net.minecraft.world.WorldServer; import net.minecraftforge.common.DimensionManager; import net.minecraftforge.common.util.FakePlayer; import net.minecraftforge.fml.common.FMLCommonHandler; import net.minecraftforge.fml.common.network.NetworkRegistry.TargetPoint; import net.minecraftforge.fml.common.network.handshake.NetworkDispatcher; import net.minecraftforge.fml.common.network.internal.FMLProxyPacket; import net.minecraftforge.api.distmarker.Dist; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; import javax.annotation.Nullable; public class FMLOutboundHandler extends ChannelOutboundHandlerAdapter { public static final AttributeKey FML_MESSAGETARGET = AttributeKey.valueOf("fml:outboundTarget"); public static final AttributeKey FML_MESSAGETARGETARGS = AttributeKey.valueOf("fml:outboundTargetArgs"); public enum OutboundTarget { /** * The packet is sent nowhere. It will be on the {@link EmbeddedChannel#outboundMessages()} Queue. * * @author cpw * */ NOWHERE(Sets.immutableEnumSet(Side.CLIENT, Side.SERVER)) { @Override public void validateArgs(Object args) { // NOOP } @Override @Nullable public List selectNetworks(Object args, ChannelHandlerContext context, FMLProxyPacket packet) { return null; } }, /** * The packet is sent to the {@link NetworkDispatcher} supplied as an argument. * * @author cpw * */ DISPATCHER(Sets.immutableEnumSet(Side.SERVER)) { @Override public void validateArgs(Object args) { if (!(args instanceof NetworkDispatcher)) { throw new RuntimeException("DISPATCHER expects a NetworkDispatcher"); } } @Override public List selectNetworks(Object args, ChannelHandlerContext context, FMLProxyPacket packet) { return ImmutableList.of((NetworkDispatcher)args); } }, /** * The packet is sent to the originator of the packet. This requires the inbound packet * to have it's originator information set. * * @author cpw * */ REPLY(Sets.immutableEnumSet(Side.SERVER)) { @Override public void validateArgs(Object args) { // NOOP } @Override public List selectNetworks(Object args, ChannelHandlerContext context, FMLProxyPacket packet) { return ImmutableList.of(packet.getDispatcher()); } }, /** * The packet is sent to the {@link EntityPlayerMP} supplied as an argument. * * @author cpw * */ PLAYER(Sets.immutableEnumSet(Side.SERVER)) { @Override public void validateArgs(Object args) { if (!(args instanceof EntityPlayerMP)) { throw new RuntimeException("PLAYER target expects a Player arg"); } } @Override public List selectNetworks(Object args, ChannelHandlerContext context, FMLProxyPacket packet) { EntityPlayerMP player = (EntityPlayerMP) args; NetworkDispatcher dispatcher = (player == null || player instanceof FakePlayer) ? null : player.connection.netManager.channel().attr(NetworkDispatcher.FML_DISPATCHER).get(); return dispatcher == null ? ImmutableList.of() : ImmutableList.of(dispatcher); } }, /** * The packet is dispatched to all players connected to the server. * @author cpw * */ ALL(Sets.immutableEnumSet(Side.SERVER)) { @Override public void validateArgs(Object args) { } @Override public List selectNetworks(Object args, ChannelHandlerContext context, FMLProxyPacket packet) { ImmutableList.Builder builder = ImmutableList.builder(); for (EntityPlayerMP player : FMLCommonHandler.instance().getMinecraftServerInstance().getPlayerList().getPlayers()) { NetworkDispatcher dispatcher = player.connection.netManager.channel().attr(NetworkDispatcher.FML_DISPATCHER).get(); if (dispatcher != null) builder.add(dispatcher); } return builder.build(); } }, /** * The packet is sent to all players in the dimension identified by the integer argument. * @author cpw * */ DIMENSION(Sets.immutableEnumSet(Side.SERVER)) { @Override public void validateArgs(Object args) { if (!(args instanceof Integer)) { throw new RuntimeException("DIMENSION expects an integer argument"); } } @Override public List selectNetworks(Object args, ChannelHandlerContext context, FMLProxyPacket packet) { int dimension = (Integer)args; ImmutableList.Builder builder = ImmutableList.builder(); for (EntityPlayerMP player : FMLCommonHandler.instance().getMinecraftServerInstance().getPlayerList().getPlayers()) { if (dimension == player.dimension) { NetworkDispatcher dispatcher = player.connection.netManager.channel().attr(NetworkDispatcher.FML_DISPATCHER).get(); // Null dispatchers may exist for fake players - skip them if (dispatcher != null) builder.add(dispatcher); } } return builder.build(); } }, /** * The packet is sent to all players within range of the {@link TargetPoint} argument supplied. * * @author cpw * */ ALLAROUNDPOINT(Sets.immutableEnumSet(Side.SERVER)) { @Override public void validateArgs(Object args) { if (!(args instanceof TargetPoint)) { throw new RuntimeException("ALLAROUNDPOINT expects a TargetPoint argument"); } } @Override public List selectNetworks(Object args, ChannelHandlerContext context, FMLProxyPacket packet) { TargetPoint tp = (TargetPoint)args; ImmutableList.Builder builder = ImmutableList.builder(); for (EntityPlayerMP player : FMLCommonHandler.instance().getMinecraftServerInstance().getPlayerList().getPlayers()) { if (player.dimension == tp.dimension) { double d4 = tp.x - player.posX; double d5 = tp.y - player.posY; double d6 = tp.z - player.posZ; if (d4 * d4 + d5 * d5 + d6 * d6 < tp.range * tp.range) { NetworkDispatcher dispatcher = player.connection.netManager.channel().attr(NetworkDispatcher.FML_DISPATCHER).get(); if (dispatcher != null) builder.add(dispatcher); } } } return builder.build(); } }, /** * The packet is sent to all players that are watching the Chunk containing the supplied {@link TargetPoint}. * The {@code range} field of the {@link TargetPoint} is ignored. */ TRACKING_POINT(Sets.immutableEnumSet(Side.SERVER)) { @Override public void validateArgs(Object args) { if (!(args instanceof TargetPoint)) { throw new RuntimeException("TRACKING_POINT expects a TargetPoint argument"); } } @Nullable @Override public List selectNetworks(Object args, ChannelHandlerContext context, FMLProxyPacket packet) { TargetPoint tp = (TargetPoint)args; WorldServer world = DimensionManager.getWorld(tp.dimension); if (world == null) { return ImmutableList.of(); } PlayerChunkMapEntry entry = world.getPlayerChunkMap().getEntry(MathHelper.floor(tp.x) >> 4, MathHelper.floor(tp.z) >> 4); if (entry == null) { return ImmutableList.of(); } ImmutableList.Builder builder = ImmutableList.builder(); for (EntityPlayerMP player : entry.getWatchingPlayers()) { NetworkDispatcher dispatcher = player.connection.netManager.channel().attr(NetworkDispatcher.FML_DISPATCHER).get(); if (dispatcher != null) builder.add(dispatcher); } return builder.build(); } }, /** * The packet is sent to all players tracking the supplied {@link Entity}. This is different from {@link #TRACKING_POINT} because Entities * can have different tracking distances depending on their type. */ TRACKING_ENTITY(Sets.immutableEnumSet(Side.SERVER)) { @Override public void validateArgs(Object args) { if (!(args instanceof Entity)) { throw new RuntimeException("TRACKING_ENTITY expects an Entity argument"); } } @Nullable @Override public List selectNetworks(Object args, ChannelHandlerContext context, FMLProxyPacket packet) { Entity e = (Entity)args; Set players = FMLCommonHandler.instance().getMinecraftServerInstance() .getWorld(e.dimension).getEntityTracker().getTrackingPlayers(e); ImmutableList.Builder builder = ImmutableList.builder(); for (EntityPlayer player : players) { NetworkDispatcher dispatcher = ((EntityPlayerMP) player).connection.netManager.channel().attr(NetworkDispatcher.FML_DISPATCHER).get(); if (dispatcher != null) builder.add(dispatcher); } return builder.build(); } }, /** * The packet is sent to the server this client is currently conversing with. * @author cpw * */ TOSERVER(Sets.immutableEnumSet(Side.CLIENT)) { @Override public void validateArgs(Object args) { } @Override public List selectNetworks(Object args, ChannelHandlerContext context, FMLProxyPacket packet) { NetworkManager clientConnection = FMLCommonHandler.instance().getClientToServerNetworkManager(); return clientConnection == null || clientConnection.channel().attr(NetworkDispatcher.FML_DISPATCHER).get() == null ? ImmutableList.of() : ImmutableList.of(clientConnection.channel().attr(NetworkDispatcher.FML_DISPATCHER).get()); } }; private OutboundTarget(ImmutableSet sides) { this.allowed = sides; } public final ImmutableSet allowed; public abstract void validateArgs(Object args); @Nullable public abstract List selectNetworks(Object args, ChannelHandlerContext context, FMLProxyPacket packet); } @Override public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { if (!(msg instanceof FMLProxyPacket)) { return; } FMLProxyPacket pkt = (FMLProxyPacket) msg; OutboundTarget outboundTarget; Object args = null; NetworkDispatcher dispatcher = ctx.channel().attr(NetworkDispatcher.FML_DISPATCHER).get(); // INTERNAL message callback - let it pass out if (dispatcher != null) { ctx.write(msg, promise); return; } outboundTarget = ctx.channel().attr(FML_MESSAGETARGET).get(); Side channelSide = ctx.channel().attr(NetworkRegistry.CHANNEL_SOURCE).get(); if (outboundTarget != null && outboundTarget.allowed.contains(channelSide)) { args = ctx.channel().attr(FML_MESSAGETARGETARGS).get(); outboundTarget.validateArgs(args); } else if (channelSide == Side.CLIENT) { outboundTarget = OutboundTarget.TOSERVER; } else { throw new FMLNetworkException("Packet arrived at the outbound handler without a valid target!"); } List dispatchers = outboundTarget.selectNetworks(args, ctx, pkt); // This will drop the messages into the output queue at the embedded channel if (dispatchers == null) { ctx.write(msg, promise); return; } for (NetworkDispatcher targetDispatcher : dispatchers) { pkt.payload().retain(); targetDispatcher.sendProxy(pkt); } pkt.payload().release(); } }