From 1d7c04daeba2ab2941cfe79aedc338d157a827c2 Mon Sep 17 00:00:00 2001 From: Vincent Lee Date: Sun, 1 Apr 2018 03:42:55 -0500 Subject: [PATCH] Add a FML outbound target for all players tracking a point or entity in the world. Closes #3677 (#4631) --- .../management/PlayerChunkMapEntry.java.patch | 10 ++ .../fml/common/network/FMLEventChannel.java | 28 ++++ .../common/network/FMLOutboundHandler.java | 81 ++++++++++++ .../simpleimpl/SimpleNetworkWrapper.java | 32 +++++ .../debug/TrackingTargetTest.java | 122 ++++++++++++++++++ 5 files changed, 273 insertions(+) create mode 100644 src/test/java/net/minecraftforge/debug/TrackingTargetTest.java diff --git a/patches/minecraft/net/minecraft/server/management/PlayerChunkMapEntry.java.patch b/patches/minecraft/net/minecraft/server/management/PlayerChunkMapEntry.java.patch index b4a9796e1f..78684574e6 100644 --- a/patches/minecraft/net/minecraft/server/management/PlayerChunkMapEntry.java.patch +++ b/patches/minecraft/net/minecraft/server/management/PlayerChunkMapEntry.java.patch @@ -150,3 +150,13 @@ { this.func_187273_a(this.field_187282_b.func_72688_a().func_175625_s(blockpos1)); } +@@ -311,4 +348,9 @@ + + return d0; + } ++ ++ public List getWatchingPlayers() ++ { ++ return func_187274_e() ? java.util.Collections.unmodifiableList(field_187283_c) : java.util.Collections.emptyList(); ++ } + } diff --git a/src/main/java/net/minecraftforge/fml/common/network/FMLEventChannel.java b/src/main/java/net/minecraftforge/fml/common/network/FMLEventChannel.java index cf75d2f374..bb87206e5c 100644 --- a/src/main/java/net/minecraftforge/fml/common/network/FMLEventChannel.java +++ b/src/main/java/net/minecraftforge/fml/common/network/FMLEventChannel.java @@ -25,6 +25,7 @@ import io.netty.channel.ChannelHandlerContext; import java.util.EnumMap; import net.minecraft.client.network.NetHandlerPlayClient; +import net.minecraft.entity.Entity; import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.network.NetHandlerPlayServer; import net.minecraftforge.fml.common.FMLCommonHandler; @@ -177,6 +178,33 @@ public class FMLEventChannel { channels.get(Side.SERVER).writeAndFlush(pkt).addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); } + /** + * Send to all tracking the point + * The {@code range} field of the {@link NetworkRegistry.TargetPoint} is ignored. + * @param pkt + * @param point + */ + public void sendToAllTracking(FMLProxyPacket pkt, NetworkRegistry.TargetPoint point) + { + channels.get(Side.SERVER).attr(FMLOutboundHandler.FML_MESSAGETARGET).set(FMLOutboundHandler.OutboundTarget.TRACKING_POINT); + channels.get(Side.SERVER).attr(FMLOutboundHandler.FML_MESSAGETARGETARGS).set(point); + channels.get(Side.SERVER).writeAndFlush(pkt).addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); + } + + /** + * Send to all tracking the entity + * This is not equivalent to {@link #sendToAllTracking(FMLProxyPacket, NetworkRegistry.TargetPoint)} + * because entities have different tracking distances based on their type. + * @param pkt + * @param entity + */ + public void sendToAllTracking(FMLProxyPacket pkt, Entity entity) + { + channels.get(Side.SERVER).attr(FMLOutboundHandler.FML_MESSAGETARGET).set(FMLOutboundHandler.OutboundTarget.TRACKING_ENTITY); + channels.get(Side.SERVER).attr(FMLOutboundHandler.FML_MESSAGETARGETARGS).set(entity); + channels.get(Side.SERVER).writeAndFlush(pkt).addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); + } + /** * Send to all in a dimension * @param pkt diff --git a/src/main/java/net/minecraftforge/fml/common/network/FMLOutboundHandler.java b/src/main/java/net/minecraftforge/fml/common/network/FMLOutboundHandler.java index a84b9ef485..9d1f1445b8 100644 --- a/src/main/java/net/minecraftforge/fml/common/network/FMLOutboundHandler.java +++ b/src/main/java/net/minecraftforge/fml/common/network/FMLOutboundHandler.java @@ -26,9 +26,17 @@ 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; @@ -231,6 +239,79 @@ public class FMLOutboundHandler extends ChannelOutboundHandlerAdapter { 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 diff --git a/src/main/java/net/minecraftforge/fml/common/network/simpleimpl/SimpleNetworkWrapper.java b/src/main/java/net/minecraftforge/fml/common/network/simpleimpl/SimpleNetworkWrapper.java index b4cda09f48..9f1a5d9a74 100644 --- a/src/main/java/net/minecraftforge/fml/common/network/simpleimpl/SimpleNetworkWrapper.java +++ b/src/main/java/net/minecraftforge/fml/common/network/simpleimpl/SimpleNetworkWrapper.java @@ -24,6 +24,7 @@ import io.netty.channel.ChannelFutureListener; import java.lang.reflect.Method; import java.util.EnumMap; +import net.minecraft.entity.Entity; import net.minecraft.util.IThreadListener; import io.netty.channel.ChannelHandler; @@ -263,6 +264,37 @@ public class SimpleNetworkWrapper { channels.get(Side.SERVER).writeAndFlush(message).addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); } + /** + * Sends this message to everyone tracking a point. + * The {@link IMessageHandler} for this message type should be on the CLIENT side. + * The {@code range} field of the {@link TargetPoint} is ignored. + * + * @param message The message to send + * @param point The tracked {@link TargetPoint} around which to send + */ + public void sendToAllTracking(IMessage message, NetworkRegistry.TargetPoint point) + { + channels.get(Side.SERVER).attr(FMLOutboundHandler.FML_MESSAGETARGET).set(FMLOutboundHandler.OutboundTarget.TRACKING_POINT); + channels.get(Side.SERVER).attr(FMLOutboundHandler.FML_MESSAGETARGETARGS).set(point); + channels.get(Side.SERVER).writeAndFlush(message).addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); + } + + /** + * Sends this message to everyone tracking an entity. + * The {@link IMessageHandler} for this message type should be on the CLIENT side. + * This is not equivalent to {@link #sendToAllTracking(IMessage, TargetPoint)} + * because entities have different tracking distances based on their type. + * + * @param message The message to send + * @param entity The tracked entity around which to send + */ + public void sendToAllTracking(IMessage message, Entity entity) + { + channels.get(Side.SERVER).attr(FMLOutboundHandler.FML_MESSAGETARGET).set(FMLOutboundHandler.OutboundTarget.TRACKING_ENTITY); + channels.get(Side.SERVER).attr(FMLOutboundHandler.FML_MESSAGETARGETARGS).set(entity); + channels.get(Side.SERVER).writeAndFlush(message).addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); + } + /** * Send this message to everyone within the supplied dimension. * The {@link IMessageHandler} for this message type should be on the CLIENT side. diff --git a/src/test/java/net/minecraftforge/debug/TrackingTargetTest.java b/src/test/java/net/minecraftforge/debug/TrackingTargetTest.java new file mode 100644 index 0000000000..f121ce59b3 --- /dev/null +++ b/src/test/java/net/minecraftforge/debug/TrackingTargetTest.java @@ -0,0 +1,122 @@ +package net.minecraftforge.debug; + +import io.netty.buffer.ByteBuf; +import net.minecraft.entity.item.EntityItemFrame; +import net.minecraft.init.Items; +import net.minecraftforge.event.entity.EntityJoinWorldEvent; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.event.FMLPreInitializationEvent; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.common.gameevent.TickEvent; +import net.minecraftforge.fml.common.network.NetworkRegistry; +import net.minecraftforge.fml.common.network.simpleimpl.IMessage; +import net.minecraftforge.fml.common.network.simpleimpl.IMessageHandler; +import net.minecraftforge.fml.common.network.simpleimpl.MessageContext; +import net.minecraftforge.fml.common.network.simpleimpl.SimpleNetworkWrapper; +import net.minecraftforge.fml.relauncher.Side; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.Collections; +import java.util.Set; +import java.util.WeakHashMap; + +@Mod(modid = TrackingTargetTest.ID, name = "Tracking Target Test", acceptableRemoteVersions = "*") +@Mod.EventBusSubscriber +public class TrackingTargetTest +{ + public static final String ID = "trackingtargettest"; + private static boolean ENABLED = false; + private static final SimpleNetworkWrapper NET = new SimpleNetworkWrapper(ID); + private static final Logger LOGGER = LogManager.getLogger(ID); + + @Mod.EventHandler + public static void preinit(FMLPreInitializationEvent evt) + { + NET.registerMessage(TestMessageHandler.class, TestMessage.class, 0, Side.CLIENT); + NET.registerMessage(TestEntityMessageHandler.class, TestEntityMessage.class, 1, Side.CLIENT); + } + + // Every 3 seconds, send a message to all players tracking overworld (500, 500). + // If you move sufficiently far away (i.e greater than the server render distance) from (500, 500), you should stop receiving the messages. + @SubscribeEvent + public static void tick(TickEvent.WorldTickEvent evt) + { + if (ENABLED && evt.side == Side.SERVER && evt.phase == TickEvent.Phase.END) + { + if (evt.world.getWorldTime() % 60 == 0) + { + NetworkRegistry.TargetPoint pt = new NetworkRegistry.TargetPoint(0, 500, 0, 500, -1); + NET.sendToAllTracking(new TestMessage(), pt); + } + } + } + + public static class TestMessage implements IMessage + { + @Override + public void fromBytes(ByteBuf buf) {} + + @Override + public void toBytes(ByteBuf buf) {} + } + + public static class TestMessageHandler implements IMessageHandler + { + @Override + public IMessage onMessage(TestMessage message, MessageContext ctx) + { + LOGGER.info("Received tracking point test message"); + return null; + } + } + + // Every 3 seconds, send a message to all players tracking any item frame with a stick in it + // If you move sufficiently far away from the frame, you should stop receiving the messages. + private static final Set FRAMES = Collections.newSetFromMap(new WeakHashMap<>()); + @SubscribeEvent + public static void frameJoin(EntityJoinWorldEvent evt) + { + if (ENABLED && !evt.getWorld().isRemote && evt.getEntity() instanceof EntityItemFrame) + { + FRAMES.add((EntityItemFrame) evt.getEntity()); + } + } + + @SubscribeEvent + public static void tickEntity(TickEvent.WorldTickEvent evt) + { + if (ENABLED && evt.side == Side.SERVER && evt.phase == TickEvent.Phase.END) + { + if (evt.world.getWorldTime() % 60 == 0) + { + for (EntityItemFrame frame : FRAMES) + { + if (!frame.isDead && !frame.getDisplayedItem().isEmpty() && frame.getDisplayedItem().getItem() == Items.STICK) + { + NET.sendToAllTracking(new TestEntityMessage(), frame); + } + } + } + } + } + + public static class TestEntityMessage implements IMessage + { + @Override + public void fromBytes(ByteBuf buf) {} + + @Override + public void toBytes(ByteBuf buf) {} + } + + public static class TestEntityMessageHandler implements IMessageHandler + { + @Override + public IMessage onMessage(TestEntityMessage message, MessageContext ctx) + { + LOGGER.info("Received tracking point test entity message"); + return null; + } + } +}