diff --git a/src/main/java/com/minelittlepony/unicopia/BlockDestructionManager.java b/src/main/java/com/minelittlepony/unicopia/BlockDestructionManager.java index 4dca36c5..189a429c 100644 --- a/src/main/java/com/minelittlepony/unicopia/BlockDestructionManager.java +++ b/src/main/java/com/minelittlepony/unicopia/BlockDestructionManager.java @@ -11,6 +11,7 @@ import it.unimi.dsi.fastutil.longs.Long2ObjectMap; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; import net.minecraft.block.BlockState; import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.server.world.ServerWorld; import net.minecraft.server.world.ThreadedAnvilChunkStorage; import net.minecraft.util.math.BlockPos; @@ -120,13 +121,17 @@ public class BlockDestructionManager { MsgBlockDestruction msg = new MsgBlockDestruction(values); - if (Channel.toBuffer(msg).writerIndex() > 1048576) { + if (msg.toBuffer().writerIndex() > 1048576) { throw new IllegalStateException("Payload may not be larger than 1048576 bytes. Here's what we were trying to send: [" + values.size() + "]\n" + Arrays.toString(values.values().stream().mapToInt(Integer::intValue).toArray())); } - players.forEach(player -> Channel.SERVER_BLOCK_DESTRUCTION.send(player, msg)); + players.forEach(player -> { + if (player instanceof ServerPlayerEntity) { + Channel.SERVER_BLOCK_DESTRUCTION.send((ServerPlayerEntity)player, msg); + } + }); } } } diff --git a/src/main/java/com/minelittlepony/unicopia/entity/player/Pony.java b/src/main/java/com/minelittlepony/unicopia/entity/player/Pony.java index b8961e1b..03805341 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/player/Pony.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/player/Pony.java @@ -160,7 +160,7 @@ public class Pony extends Living implements Transmittable, Copieab if (entity instanceof ServerPlayerEntity) { MsgOtherPlayerCapabilities packet = new MsgOtherPlayerCapabilities(full, this); - Channel.SERVER_PLAYER_CAPABILITIES.send(entity, packet); + Channel.SERVER_PLAYER_CAPABILITIES.send((ServerPlayerEntity)entity, packet); Channel.SERVER_OTHER_PLAYER_CAPABILITIES.send(entity.world, packet); } } diff --git a/src/main/java/com/minelittlepony/unicopia/network/Channel.java b/src/main/java/com/minelittlepony/unicopia/network/Channel.java index 1d562159..ba18c24e 100644 --- a/src/main/java/com/minelittlepony/unicopia/network/Channel.java +++ b/src/main/java/com/minelittlepony/unicopia/network/Channel.java @@ -1,114 +1,20 @@ package com.minelittlepony.unicopia.network; -import java.util.function.Function; +import com.minelittlepony.unicopia.util.network.S2CBroadcastPacketType; +import com.minelittlepony.unicopia.util.network.C2SPacketType; +import com.minelittlepony.unicopia.util.network.S2CPacketType; +import com.minelittlepony.unicopia.util.network.SimpleNetworking; -import com.google.common.base.Preconditions; - -import io.netty.buffer.Unpooled; -import net.fabricmc.api.EnvType; -import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; -import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; -import net.fabricmc.loader.api.FabricLoader; -import net.minecraft.entity.player.PlayerEntity; import net.minecraft.util.Identifier; -import net.minecraft.world.World; -import net.minecraft.network.PacketByteBuf; -import net.minecraft.server.network.ServerPlayerEntity; public interface Channel { - SPacketType> CLIENT_PLAYER_ABILITY = clientToServer(new Identifier("unicopia", "player_ability"), MsgPlayerAbility::new); - SPacketType CLIENT_REQUEST_CAPABILITIES = clientToServer(new Identifier("unicopia", "request_capabilities"), MsgRequestCapabilities::new); + S2CPacketType> CLIENT_PLAYER_ABILITY = SimpleNetworking.clientToServer(new Identifier("unicopia", "player_ability"), MsgPlayerAbility::new); + S2CPacketType CLIENT_REQUEST_CAPABILITIES = SimpleNetworking.clientToServer(new Identifier("unicopia", "request_capabilities"), MsgRequestCapabilities::new); + S2CBroadcastPacketType SERVER_OTHER_PLAYER_CAPABILITIES = SimpleNetworking.serverToClients(new Identifier("unicopia", "other_player_capabilities"), MsgOtherPlayerCapabilities::new); - CPacketType SERVER_PLAYER_CAPABILITIES = serverToClient(new Identifier("unicopia", "player_capabilities"), MsgPlayerCapabilities::new); - BPacketType SERVER_OTHER_PLAYER_CAPABILITIES = serverToClients(new Identifier("unicopia", "other_player_capabilities"), MsgOtherPlayerCapabilities::new); - CPacketType SERVER_SPAWN_PROJECTILE = serverToClient(new Identifier("unicopia", "projectile_entity"), MsgSpawnProjectile::new); - - CPacketType SERVER_BLOCK_DESTRUCTION = serverToClient(new Identifier("unicopia", "block_destruction"), MsgBlockDestruction::new); + C2SPacketType SERVER_PLAYER_CAPABILITIES = SimpleNetworking.serverToClient(new Identifier("unicopia", "player_capabilities"), MsgPlayerCapabilities::new); + C2SPacketType SERVER_SPAWN_PROJECTILE = SimpleNetworking.serverToClient(new Identifier("unicopia", "projectile_entity"), MsgSpawnProjectile::new); + C2SPacketType SERVER_BLOCK_DESTRUCTION = SimpleNetworking.serverToClient(new Identifier("unicopia", "block_destruction"), MsgBlockDestruction::new); static void bootstrap() { } - - static SPacketType clientToServer(Identifier id, Function factory) { - ServerPlayNetworking.registerGlobalReceiver(id, (server, player, handler, buffer, responder) -> { - T packet = factory.apply(buffer); - server.execute(() -> packet.handle(player)); - }); - return () -> id; - } - - static CPacketType serverToClient(Identifier id, Function factory) { - if (FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT) { - ClientProxy.register(id, factory); - } - return () -> id; - } - - static BPacketType serverToClients(Identifier id, Function factory) { - if (FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT) { - ClientProxy.register(id, factory); - } - return () -> id; - } - - /** - * A broadcast packet type. Sent by the server to all surrounding players. - */ - interface BPacketType { - Identifier getId(); - - default void send(World world, T packet) { - world.getPlayers().forEach(player -> { - if (player instanceof ServerPlayerEntity) { - ServerPlayNetworking.send((ServerPlayerEntity)player, getId(), toBuffer(packet)); - } - }); - } - } - - /** - * A client packet type. Sent by the server to a specific player. - */ - interface CPacketType { - Identifier getId(); - - default void send(PlayerEntity recipient, T packet) { - ServerPlayNetworking.send(((ServerPlayerEntity)recipient), getId(), toBuffer(packet)); - } - - default net.minecraft.network.Packet toPacket(T packet) { - return ServerPlayNetworking.createS2CPacket(getId(), toBuffer(packet)); - } - } - - /** - * A server packet type. Sent by the client to the server. - */ - interface SPacketType { - Identifier getId(); - - default void send(T packet) { - Preconditions.checkState(FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT, "Client packet send called by the server"); - ClientPlayNetworking.send(getId(), toBuffer(packet)); - } - } - - interface Packet { - void handle(PlayerEntity sender); - - void toBuffer(PacketByteBuf buffer); - } - - static PacketByteBuf toBuffer(Packet packet) { - PacketByteBuf buf = new PacketByteBuf(Unpooled.buffer()); - packet.toBuffer(buf); - return buf; - } - - class ClientProxy { - static void register(Identifier id, Function factory) { - ClientPlayNetworking.registerGlobalReceiver(id, (client, ignore1, buffer, ignore2) -> { - T packet = factory.apply(buffer); - client.execute(() -> packet.handle(client.player)); - }); - } - } } diff --git a/src/main/java/com/minelittlepony/unicopia/network/MsgBlockDestruction.java b/src/main/java/com/minelittlepony/unicopia/network/MsgBlockDestruction.java index c82df842..ed918af7 100644 --- a/src/main/java/com/minelittlepony/unicopia/network/MsgBlockDestruction.java +++ b/src/main/java/com/minelittlepony/unicopia/network/MsgBlockDestruction.java @@ -1,6 +1,7 @@ package com.minelittlepony.unicopia.network; import com.minelittlepony.unicopia.client.ClientBlockDestructionManager; +import com.minelittlepony.unicopia.util.network.Packet; import it.unimi.dsi.fastutil.longs.Long2ObjectMap; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; @@ -8,7 +9,7 @@ import net.minecraft.network.PacketByteBuf; import net.minecraft.client.MinecraftClient; import net.minecraft.entity.player.PlayerEntity; -public class MsgBlockDestruction implements Channel.Packet { +public class MsgBlockDestruction implements Packet { private final Long2ObjectMap destructions; diff --git a/src/main/java/com/minelittlepony/unicopia/network/MsgPlayerAbility.java b/src/main/java/com/minelittlepony/unicopia/network/MsgPlayerAbility.java index 6275147c..d5ffead6 100644 --- a/src/main/java/com/minelittlepony/unicopia/network/MsgPlayerAbility.java +++ b/src/main/java/com/minelittlepony/unicopia/network/MsgPlayerAbility.java @@ -3,13 +3,14 @@ package com.minelittlepony.unicopia.network; import com.minelittlepony.unicopia.ability.Ability; import com.minelittlepony.unicopia.ability.data.Hit; import com.minelittlepony.unicopia.entity.player.Pony; +import com.minelittlepony.unicopia.util.network.Packet; import com.minelittlepony.unicopia.ability.Abilities; import net.minecraft.util.Identifier; -import net.minecraft.entity.player.PlayerEntity; import net.minecraft.network.PacketByteBuf; +import net.minecraft.server.network.ServerPlayerEntity; -public class MsgPlayerAbility implements Channel.Packet { +public class MsgPlayerAbility implements Packet { private final Ability power; @@ -33,7 +34,7 @@ public class MsgPlayerAbility implements Channel.Packet { } @Override - public void handle(PlayerEntity sender) { + public void handle(ServerPlayerEntity sender) { Pony player = Pony.of(sender); if (player == null) { return; diff --git a/src/main/java/com/minelittlepony/unicopia/network/MsgPlayerCapabilities.java b/src/main/java/com/minelittlepony/unicopia/network/MsgPlayerCapabilities.java index 843910f0..6c092b68 100644 --- a/src/main/java/com/minelittlepony/unicopia/network/MsgPlayerCapabilities.java +++ b/src/main/java/com/minelittlepony/unicopia/network/MsgPlayerCapabilities.java @@ -8,6 +8,7 @@ import java.util.UUID; import com.minelittlepony.unicopia.Race; import com.minelittlepony.unicopia.Unicopia; import com.minelittlepony.unicopia.entity.player.Pony; +import com.minelittlepony.unicopia.util.network.Packet; import io.netty.buffer.ByteBufInputStream; import io.netty.buffer.ByteBufOutputStream; @@ -16,7 +17,7 @@ import net.minecraft.entity.player.PlayerEntity; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.NbtIo; -public class MsgPlayerCapabilities implements Channel.Packet { +public class MsgPlayerCapabilities implements Packet { protected final UUID playerId; diff --git a/src/main/java/com/minelittlepony/unicopia/network/MsgRequestCapabilities.java b/src/main/java/com/minelittlepony/unicopia/network/MsgRequestCapabilities.java index 16fb02fa..a372f776 100644 --- a/src/main/java/com/minelittlepony/unicopia/network/MsgRequestCapabilities.java +++ b/src/main/java/com/minelittlepony/unicopia/network/MsgRequestCapabilities.java @@ -3,12 +3,13 @@ package com.minelittlepony.unicopia.network; import com.minelittlepony.unicopia.Race; import com.minelittlepony.unicopia.WorldTribeManager; import com.minelittlepony.unicopia.entity.player.Pony; +import com.minelittlepony.unicopia.util.network.Packet; -import net.minecraft.entity.player.PlayerEntity; import net.minecraft.network.PacketByteBuf; +import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.server.world.ServerWorld; -public class MsgRequestCapabilities implements Channel.Packet { +public class MsgRequestCapabilities implements Packet { private final Race clientPreferredRace; @@ -26,7 +27,7 @@ public class MsgRequestCapabilities implements Channel.Packet { } @Override - public void handle(PlayerEntity sender) { + public void handle(ServerPlayerEntity sender) { Pony player = Pony.of(sender); Race worldDefaultRace = WorldTribeManager.forWorld((ServerWorld)player.getWorld()).getDefaultRace(); diff --git a/src/main/java/com/minelittlepony/unicopia/network/MsgSpawnProjectile.java b/src/main/java/com/minelittlepony/unicopia/network/MsgSpawnProjectile.java index 642b14a3..d18e17e0 100644 --- a/src/main/java/com/minelittlepony/unicopia/network/MsgSpawnProjectile.java +++ b/src/main/java/com/minelittlepony/unicopia/network/MsgSpawnProjectile.java @@ -4,6 +4,7 @@ import java.io.IOException; import java.util.Optional; import com.minelittlepony.unicopia.Owned; +import com.minelittlepony.unicopia.util.network.Packet; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; @@ -14,7 +15,7 @@ import net.minecraft.entity.player.PlayerEntity; import net.minecraft.network.PacketByteBuf; import net.minecraft.network.packet.s2c.play.EntitySpawnS2CPacket; -public class MsgSpawnProjectile extends EntitySpawnS2CPacket implements Channel.Packet { +public class MsgSpawnProjectile extends EntitySpawnS2CPacket implements Packet { MsgSpawnProjectile(PacketByteBuf buffer) { try { diff --git a/src/main/java/com/minelittlepony/unicopia/util/network/C2SPacketType.java b/src/main/java/com/minelittlepony/unicopia/util/network/C2SPacketType.java new file mode 100644 index 00000000..a81010c8 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/util/network/C2SPacketType.java @@ -0,0 +1,21 @@ +package com.minelittlepony.unicopia.util.network; + +import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.util.Identifier; + +/** + * A client packet type. Sent by the server to a specific player. + */ +public interface C2SPacketType> { + Identifier getId(); + + default void send(ServerPlayerEntity recipient, T packet) { + ServerPlayNetworking.send(recipient, getId(), packet.toBuffer()); + } + + default net.minecraft.network.Packet toPacket(T packet) { + return ServerPlayNetworking.createS2CPacket(getId(), packet.toBuffer()); + } +} \ No newline at end of file diff --git a/src/main/java/com/minelittlepony/unicopia/util/network/Packet.java b/src/main/java/com/minelittlepony/unicopia/util/network/Packet.java new file mode 100644 index 00000000..717d1a89 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/util/network/Packet.java @@ -0,0 +1,35 @@ +package com.minelittlepony.unicopia.util.network; + +import io.netty.buffer.Unpooled; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.network.PacketByteBuf; + +/** + * Represents a message that can be either send from the client to the server or back. + */ +public interface Packet

{ + /** + * Called to handle this packet on the receiving end. + * + * @param sender The player who initially sent this packet. + */ + void handle(P sender); + + /** + * Writes this packet to the supplied buffer prior to transmission. + * + * @param buffer The buffer to write to. + */ + void toBuffer(PacketByteBuf buffer); + + /** + * Writes this packet to a new buffer. + * + * @return The resulting buffer for transmission + */ + default PacketByteBuf toBuffer() { + PacketByteBuf buf = new PacketByteBuf(Unpooled.buffer()); + toBuffer(buf); + return buf; + } +} \ No newline at end of file diff --git a/src/main/java/com/minelittlepony/unicopia/util/network/S2CBroadcastPacketType.java b/src/main/java/com/minelittlepony/unicopia/util/network/S2CBroadcastPacketType.java new file mode 100644 index 00000000..06a215a8 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/util/network/S2CBroadcastPacketType.java @@ -0,0 +1,22 @@ +package com.minelittlepony.unicopia.util.network; + +import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.util.Identifier; +import net.minecraft.world.World; + +/** + * A broadcast packet type. Sent by the server to all surrounding players. + */ +public interface S2CBroadcastPacketType> { + Identifier getId(); + + default void send(World world, T packet) { + world.getPlayers().forEach(player -> { + if (player instanceof ServerPlayerEntity) { + ServerPlayNetworking.send((ServerPlayerEntity)player, getId(), packet.toBuffer()); + } + }); + } +} \ No newline at end of file diff --git a/src/main/java/com/minelittlepony/unicopia/util/network/S2CPacketType.java b/src/main/java/com/minelittlepony/unicopia/util/network/S2CPacketType.java new file mode 100644 index 00000000..aa9a4e6f --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/util/network/S2CPacketType.java @@ -0,0 +1,21 @@ +package com.minelittlepony.unicopia.util.network; + +import com.google.common.base.Preconditions; + +import net.fabricmc.api.EnvType; +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; +import net.fabricmc.loader.api.FabricLoader; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.util.Identifier; + +/** + * A server packet type. Sent by the client to the server. + */ +public interface S2CPacketType> { + Identifier getId(); + + default void send(T packet) { + Preconditions.checkState(FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT, "Client packet send called by the server"); + ClientPlayNetworking.send(getId(), packet.toBuffer()); + } +} \ No newline at end of file diff --git a/src/main/java/com/minelittlepony/unicopia/util/network/SimpleNetworking.java b/src/main/java/com/minelittlepony/unicopia/util/network/SimpleNetworking.java new file mode 100644 index 00000000..d96c3b78 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/util/network/SimpleNetworking.java @@ -0,0 +1,96 @@ +package com.minelittlepony.unicopia.util.network; + +import java.util.function.Function; + +import net.fabricmc.api.EnvType; +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; +import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; +import net.fabricmc.loader.api.FabricLoader; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.network.PacketByteBuf; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.util.Identifier; + +/** + * A simplified, side-agnostic, and declaritive wrapper around {@link ServerPlayNetworking} and {@link ClientPlayNetworking} + * designed to bring networking in line with the declaritive/registered nature of other parts of Mojang's echosystem. + *

+ * It is safe to call these methods from either the client or the server, so modders can implement a + * single static PacketTypes class with which they can easily send packets without worrying + * about the complexities of the network thread, which side to register a global receiver on, + * which method to use to send, or even whether their receiver is registered for a given player or not. + *

+ * All of the above is handled in a black-box style by this class. + *

+ *

    + *
  • Packets are automatically reigstered on the appropriate sides.
  • + *
  • Sending is done in the same way by calling `send` on your packet type.
  • + *
  • Your packet's handle method is executed on the main thread where it is safe to interact with the world.
  • + */ +public final class SimpleNetworking { + private SimpleNetworking() {throw new RuntimeException("new SimpleNetworking()");} + /** + * Registers a packet type for transmisison to the server. + *

    + * The returned handle can be used by the client to send messages to the active minecraft server. + *

    + * + * @param The type of packet to implement + * @param id The message's unique used for serialization + * @param factory A constructor returning new instances of the packet type + * + * @return A registered PacketType + */ + public static > S2CPacketType clientToServer(Identifier id, Function factory) { + ServerPlayNetworking.registerGlobalReceiver(id, (server, player, handler, buffer, responder) -> { + T packet = factory.apply(buffer); + server.execute(() -> packet.handle(player)); + }); + return () -> id; + } + /** + * Registers a packet type for transmission to the client. + * + * The returned handle can be used by the server to send messages to a given recipient. + * + * @param The type of packet to implement + * @param id The message's unique used for serialization + * @param factory A constructor returning new instances of the packet type + * + * @return A registered PacketType + */ + public static > C2SPacketType serverToClient(Identifier id, Function factory) { + if (FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT) { + ClientProxy.register(id, factory); + } + return () -> id; + } + /** + * Registers a packet type for transmission to all clients. + * + * The returned handle can be used by the server to broadcast a message to all connected clients in a given dimension. + * + * @param The type of packet to implement + * @param id The message's unique used for serialization + * @param factory A constructor returning new instances of the packet type + * + * @return A registered PacketType + */ + public static > S2CBroadcastPacketType serverToClients(Identifier id, Function factory) { + if (FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT) { + ClientProxy.register(id, factory); + } + return () -> id; + } + // Fabric's APIs are not side-agnostic. + // We punt this to a separate class file to keep it from being eager-loaded on a server environment. + private static final class ClientProxy { + private ClientProxy() {throw new RuntimeException("new ClientProxy()");} + public static > void register(Identifier id, Function factory) { + ClientPlayNetworking.registerGlobalReceiver(id, (client, ignore1, buffer, ignore2) -> { + T packet = factory.apply(buffer); + client.execute(() -> packet.handle(client.player)); + }); + } + } +}