diff --git a/fabric-containers-v0/src/main/java/net/fabricmc/fabric/impl/client/container/ScreenProviderRegistryImpl.java b/fabric-containers-v0/src/main/java/net/fabricmc/fabric/impl/client/container/ScreenProviderRegistryImpl.java index 50670128b..180fa17ba 100644 --- a/fabric-containers-v0/src/main/java/net/fabricmc/fabric/impl/client/container/ScreenProviderRegistryImpl.java +++ b/fabric-containers-v0/src/main/java/net/fabricmc/fabric/impl/client/container/ScreenProviderRegistryImpl.java @@ -32,7 +32,6 @@ import net.fabricmc.fabric.api.client.screen.ScreenProviderRegistry; import net.fabricmc.fabric.api.container.ContainerFactory; import net.fabricmc.fabric.api.network.ClientSidePacketRegistry; import net.fabricmc.fabric.impl.container.ContainerProviderImpl; -import net.fabricmc.fabric.impl.networking.PacketTypes; public class ScreenProviderRegistryImpl implements ScreenProviderRegistry { /** @@ -68,7 +67,7 @@ public class ScreenProviderRegistryImpl implements ScreenProviderRegistry { } public static void init() { - ClientSidePacketRegistry.INSTANCE.register(PacketTypes.OPEN_CONTAINER, (packetContext, packetByteBuf) -> { + ClientSidePacketRegistry.INSTANCE.register(ContainerProviderImpl.OPEN_CONTAINER, (packetContext, packetByteBuf) -> { Identifier identifier = packetByteBuf.readIdentifier(); int syncId = packetByteBuf.readUnsignedByte(); packetByteBuf.retain(); diff --git a/fabric-containers-v0/src/main/java/net/fabricmc/fabric/impl/container/ContainerProviderImpl.java b/fabric-containers-v0/src/main/java/net/fabricmc/fabric/impl/container/ContainerProviderImpl.java index dd6c15f22..2afe5e086 100644 --- a/fabric-containers-v0/src/main/java/net/fabricmc/fabric/impl/container/ContainerProviderImpl.java +++ b/fabric-containers-v0/src/main/java/net/fabricmc/fabric/impl/container/ContainerProviderImpl.java @@ -33,10 +33,10 @@ import net.minecraft.util.PacketByteBuf; import net.fabricmc.fabric.api.container.ContainerFactory; import net.fabricmc.fabric.api.container.ContainerProviderRegistry; -import net.fabricmc.fabric.impl.networking.PacketTypes; import net.fabricmc.fabric.mixin.container.ServerPlayerEntityAccessor; public class ContainerProviderImpl implements ContainerProviderRegistry { + public static final Identifier OPEN_CONTAINER = new Identifier("fabric", "container/open"); /** * Use the instance provided by ContainerProviderRegistry. */ @@ -91,7 +91,7 @@ public class ContainerProviderImpl implements ContainerProviderRegistry { buf.writeByte(syncId); writer.accept(buf); - player.networkHandler.sendPacket(new CustomPayloadS2CPacket(PacketTypes.OPEN_CONTAINER, buf)); + player.networkHandler.sendPacket(new CustomPayloadS2CPacket(OPEN_CONTAINER, buf)); PacketByteBuf clonedBuf = new PacketByteBuf(buf.duplicate()); clonedBuf.readIdentifier(); diff --git a/fabric-networking-api-v1/build.gradle b/fabric-networking-api-v1/build.gradle new file mode 100644 index 000000000..5ef027b34 --- /dev/null +++ b/fabric-networking-api-v1/build.gradle @@ -0,0 +1,10 @@ +archivesBaseName = "fabric-networking-api-v1" +version = getSubprojectVersion(project, "1.0.0") + +dependencies { + compile project(path: ':fabric-api-base', configuration: 'dev') + + testmodCompile project(path: ':fabric-command-api-v1', configuration: 'dev') + testmodCompile project(path: ':fabric-lifecycle-events-v1', configuration: 'dev') + testmodCompile project(path: ':fabric-key-binding-api-v1', configuration: 'dev') +} diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/client/networking/v1/C2SPlayChannelEvents.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/client/networking/v1/C2SPlayChannelEvents.java new file mode 100644 index 000000000..1972f81a1 --- /dev/null +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/client/networking/v1/C2SPlayChannelEvents.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.api.client.networking.v1; + +import java.util.List; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.ClientPlayNetworkHandler; +import net.minecraft.util.Identifier; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.fabric.api.event.Event; +import net.fabricmc.fabric.api.event.EventFactory; +import net.fabricmc.fabric.api.networking.v1.PacketSender; + +/** + * Offers access to events related to the indication of a connected server's ability to receive packets in certain channels. + */ +@Environment(EnvType.CLIENT) +public final class C2SPlayChannelEvents { + /** + * An event for the client play network handler receiving an update indicating the connected server's ability to receive packets in certain channels. + * This event may be invoked at any time after login and up to disconnection. + */ + public static final Event REGISTER = EventFactory.createArrayBacked(Register.class, callbacks -> (handler, sender, client, channels) -> { + for (Register callback : callbacks) { + callback.onChannelRegister(handler, sender, client, channels); + } + }); + + /** + * An event for the client play network handler receiving an update indicating the connected server's lack of ability to receive packets in certain channels. + * This event may be invoked at any time after login and up to disconnection. + */ + public static final Event UNREGISTER = EventFactory.createArrayBacked(Unregister.class, callbacks -> (handler, sender, client, channels) -> { + for (Unregister callback : callbacks) { + callback.onChannelUnregister(handler, sender, client, channels); + } + }); + + private C2SPlayChannelEvents() { + } + + /** + * @see C2SPlayChannelEvents#REGISTER + */ + @Environment(EnvType.CLIENT) + @FunctionalInterface + public interface Register { + void onChannelRegister(ClientPlayNetworkHandler handler, PacketSender sender, MinecraftClient client, List channels); + } + + /** + * @see C2SPlayChannelEvents#UNREGISTER + */ + @Environment(EnvType.CLIENT) + @FunctionalInterface + public interface Unregister { + void onChannelUnregister(ClientPlayNetworkHandler handler, PacketSender sender, MinecraftClient client, List channels); + } +} diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/client/networking/v1/ClientLoginConnectionEvents.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/client/networking/v1/ClientLoginConnectionEvents.java new file mode 100644 index 000000000..af5ccee64 --- /dev/null +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/client/networking/v1/ClientLoginConnectionEvents.java @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.api.client.networking.v1; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.ClientLoginNetworkHandler; +import net.minecraft.util.Identifier; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.fabric.api.event.Event; +import net.fabricmc.fabric.api.event.EventFactory; + +/** + * Offers access to events related to the connection to a server on the client while the server is processing the client's login request. + */ +@Environment(EnvType.CLIENT) +public final class ClientLoginConnectionEvents { + /** + * Event indicating a connection entered the LOGIN state, ready for registering query request handlers. + * This event may be used by mods to prepare their client side state. + * This event does not guarantee that a login attempt will be successful. + * + * @see ClientLoginNetworking#registerReceiver(Identifier, ClientLoginNetworking.LoginQueryRequestHandler) + */ + public static final Event INIT = EventFactory.createArrayBacked(Init.class, callbacks -> (handler, client) -> { + for (Init callback : callbacks) { + callback.onLoginStart(handler, client); + } + }); + + /** + * An event for when the client has started receiving login queries. + * A client can only start receiving login queries when a server has sent the first login query. + * Vanilla servers will typically never make the client enter this login phase, but it is not a guarantee that the + * connected server is a vanilla server since a modded server or proxy may have no login queries to send to the client + * and therefore bypass the login query phase. + * If this event is fired then it is a sign that a server is not a vanilla server or the server is behind a proxy which + * is capable of handling login queries. + * + *

This event may be used to {@link ClientLoginNetworking.LoginQueryRequestHandler register login query handlers} + * which may be used to send a response to a server. + * + *

No packets should be sent when this event is invoked. + */ + public static final Event QUERY_START = EventFactory.createArrayBacked(QueryStart.class, callbacks -> (handler, client) -> { + for (QueryStart callback : callbacks) { + callback.onLoginQueryStart(handler, client); + } + }); + + /** + * An event for when the client's login process has ended due to disconnection. + * + *

No packets should be sent when this event is invoked. + */ + public static final Event DISCONNECT = EventFactory.createArrayBacked(Disconnect.class, callbacks -> (handler, client) -> { + for (Disconnect callback : callbacks) { + callback.onLoginDisconnect(handler, client); + } + }); + + private ClientLoginConnectionEvents() { + } + + /** + * @see ClientLoginConnectionEvents#INIT + */ + @Environment(EnvType.CLIENT) + @FunctionalInterface + public interface Init { + void onLoginStart(ClientLoginNetworkHandler handler, MinecraftClient client); + } + + /** + * @see ClientLoginConnectionEvents#QUERY_START + */ + @Environment(EnvType.CLIENT) + @FunctionalInterface + public interface QueryStart { + void onLoginQueryStart(ClientLoginNetworkHandler handler, MinecraftClient client); + } + + /** + * @see ClientLoginConnectionEvents#DISCONNECT + */ + @Environment(EnvType.CLIENT) + @FunctionalInterface + public interface Disconnect { + void onLoginDisconnect(ClientLoginNetworkHandler handler, MinecraftClient client); + } +} diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/client/networking/v1/ClientLoginNetworking.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/client/networking/v1/ClientLoginNetworking.java new file mode 100644 index 000000000..acecd16fb --- /dev/null +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/client/networking/v1/ClientLoginNetworking.java @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.api.client.networking.v1; + +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; + +import io.netty.util.concurrent.Future; +import io.netty.util.concurrent.GenericFutureListener; +import org.jetbrains.annotations.Nullable; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.ClientLoginNetworkHandler; +import net.minecraft.network.ClientConnection; +import net.minecraft.util.PacketByteBuf; +import net.minecraft.network.listener.PacketListener; +import net.minecraft.util.Identifier; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.fabric.api.networking.v1.ServerLoginNetworking; +import net.fabricmc.fabric.impl.networking.client.ClientNetworkingImpl; + +/** + * Offers access to login stage client-side networking functionalities. + * + *

The Minecraft login protocol only allows the client to respond to a server's request, but not initiate one of its own. + * + * @see ClientPlayNetworking + * @see ServerLoginNetworking + */ +@Environment(EnvType.CLIENT) +public final class ClientLoginNetworking { + /** + * Registers a handler to a query request channel. + * A global receiver is registered to all connections, in the present and future. + * + *

If a handler is already registered to the {@code channel}, this method will return {@code false}, and no change will be made. + * Use {@link #unregisterGlobalReceiver(Identifier)} to unregister the existing handler. + * + * @param channelName the id of the channel + * @param queryHandler the handler + * @return false if a handler is already registered to the channel + * @see ClientLoginNetworking#unregisterGlobalReceiver(Identifier) + * @see ClientLoginNetworking#registerReceiver(Identifier, LoginQueryRequestHandler) + */ + public static boolean registerGlobalReceiver(Identifier channelName, LoginQueryRequestHandler queryHandler) { + return ClientNetworkingImpl.LOGIN.registerGlobalReceiver(channelName, queryHandler); + } + + /** + * Removes the handler of a query request channel. + * A global receiver is registered to all connections, in the present and future. + * + *

The {@code channel} is guaranteed not to have a handler after this call. + * + * @param channelName the id of the channel + * @return the previous handler, or {@code null} if no handler was bound to the channel + * @see ClientLoginNetworking#registerGlobalReceiver(Identifier, LoginQueryRequestHandler) + * @see ClientLoginNetworking#unregisterReceiver(Identifier) + */ + @Nullable + public static ClientLoginNetworking.LoginQueryRequestHandler unregisterGlobalReceiver(Identifier channelName) { + return ClientNetworkingImpl.LOGIN.unregisterGlobalReceiver(channelName); + } + + /** + * Gets all query request channel names which global receivers are registered for. + * A global receiver is registered to all connections, in the present and future. + * + * @return all channel names which global receivers are registered for. + */ + public static Set getGlobalReceivers() { + return ClientNetworkingImpl.LOGIN.getChannels(); + } + + /** + * Registers a handler to a query request channel. + * + *

If a handler is already registered to the {@code channelName}, this method will return {@code false}, and no change will be made. + * Use {@link #unregisterReceiver(Identifier)} to unregister the existing handler. + * + * @param channelName the id of the channel + * @param queryHandler the handler + * @return false if a handler is already registered to the channel name + * @throws IllegalStateException if the client is not logging in + */ + public static boolean registerReceiver(Identifier channelName, LoginQueryRequestHandler queryHandler) throws IllegalStateException { + final ClientConnection connection = ClientNetworkingImpl.getLoginConnection(); + + if (connection != null) { + final PacketListener packetListener = connection.getPacketListener(); + + if (packetListener instanceof ClientLoginNetworkHandler) { + return ClientNetworkingImpl.getAddon(((ClientLoginNetworkHandler) packetListener)).registerChannel(channelName, queryHandler); + } + } + + throw new IllegalStateException("Cannot register receiver while client is not logging in!"); + } + + /** + * Removes the handler of a query request channel. + * + *

The {@code channelName} is guaranteed not to have a handler after this call. + * + * @param channelName the id of the channel + * @return the previous handler, or {@code null} if no handler was bound to the channel name + * @throws IllegalStateException if the client is not logging in + */ + @Nullable + public static LoginQueryRequestHandler unregisterReceiver(Identifier channelName) throws IllegalStateException { + final ClientConnection connection = ClientNetworkingImpl.getLoginConnection(); + + if (connection != null) { + final PacketListener packetListener = connection.getPacketListener(); + + if (packetListener instanceof ClientLoginNetworkHandler) { + return ClientNetworkingImpl.getAddon(((ClientLoginNetworkHandler) packetListener)).unregisterChannel(channelName); + } + } + + throw new IllegalStateException("Cannot unregister receiver while client is not logging in!"); + } + + private ClientLoginNetworking() { + } + + @Environment(EnvType.CLIENT) + @FunctionalInterface + public interface LoginQueryRequestHandler { + /** + * Handles an incoming query request from a server. + * + *

This method is executed on {@linkplain io.netty.channel.EventLoop netty's event loops}. + * Modification to the game should be {@linkplain net.minecraft.util.thread.ThreadExecutor#submit(Runnable) scheduled} using the provided Minecraft client instance. + * + *

The return value of this method is a completable future that may be used to delay the login process to the server until a task {@link CompletableFuture#isDone() is done}. + * The future should complete in reasonably time to prevent disconnection by the server. + * If your request processes instantly, you may use {@link CompletableFuture#completedFuture(Object)} to wrap your response for immediate sending. + * + * @param client the client + * @param handler the network handler that received this packet + * @param buf the payload of the packet + * @param listenerAdder listeners to be called when the response packet is sent to the server + * @return a completable future which contains the payload to respond to the server with. + * If the future contains {@code null}, then the server will be notified that the client did not understand the query. + */ + CompletableFuture<@Nullable PacketByteBuf> receive(MinecraftClient client, ClientLoginNetworkHandler handler, PacketByteBuf buf, Consumer>> listenerAdder); + } +} diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/client/networking/v1/ClientPlayConnectionEvents.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/client/networking/v1/ClientPlayConnectionEvents.java new file mode 100644 index 000000000..55cfe1f1e --- /dev/null +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/client/networking/v1/ClientPlayConnectionEvents.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.api.client.networking.v1; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.ClientPlayNetworkHandler; +import net.minecraft.util.Identifier; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.fabric.api.event.Event; +import net.fabricmc.fabric.api.event.EventFactory; +import net.fabricmc.fabric.api.networking.v1.PacketSender; + +/** + * Offers access to events related to the connection to a server on a logical client. + */ +@Environment(EnvType.CLIENT) +public final class ClientPlayConnectionEvents { + /** + * Event indicating a connection entered the PLAY state, ready for registering channel handlers. + * + * @see ClientPlayNetworking#registerReceiver(Identifier, ClientPlayNetworking.PlayChannelHandler) + */ + public static final Event INIT = EventFactory.createArrayBacked(Init.class, callbacks -> (handler, client) -> { + for (Init callback : callbacks) { + callback.onPlayInit(handler, client); + } + }); + + /** + * An event for notification when the client play network handler is ready to send packets to the server. + * + *

At this stage, the network handler is ready to send packets to the server. + * Since the client's local state has been setup. + */ + public static final Event JOIN = EventFactory.createArrayBacked(Join.class, callbacks -> (handler, sender, client) -> { + for (Join callback : callbacks) { + callback.onPlayReady(handler, sender, client); + } + }); + + /** + * An event for the disconnection of the client play network handler. + * + *

No packets should be sent when this event is invoked. + */ + public static final Event DISCONNECT = EventFactory.createArrayBacked(Disconnect.class, callbacks -> (handler, client) -> { + for (Disconnect callback : callbacks) { + callback.onPlayDisconnect(handler, client); + } + }); + + private ClientPlayConnectionEvents() { + } + + @Environment(EnvType.CLIENT) + @FunctionalInterface + public interface Init { + void onPlayInit(ClientPlayNetworkHandler handler, MinecraftClient client); + } + + @Environment(EnvType.CLIENT) + @FunctionalInterface + public interface Join { + void onPlayReady(ClientPlayNetworkHandler handler, PacketSender sender, MinecraftClient client); + } + + @Environment(EnvType.CLIENT) + @FunctionalInterface + public interface Disconnect { + void onPlayDisconnect(ClientPlayNetworkHandler handler, MinecraftClient client); + } +} diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/client/networking/v1/ClientPlayNetworking.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/client/networking/v1/ClientPlayNetworking.java new file mode 100644 index 000000000..faf2c8789 --- /dev/null +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/client/networking/v1/ClientPlayNetworking.java @@ -0,0 +1,261 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.api.client.networking.v1; + +import java.util.Objects; +import java.util.Set; + +import org.jetbrains.annotations.Nullable; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.ClientPlayNetworkHandler; +import net.minecraft.network.Packet; +import net.minecraft.util.PacketByteBuf; +import net.minecraft.util.Identifier; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.fabric.api.networking.v1.PacketSender; +import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; +import net.fabricmc.fabric.impl.networking.client.ClientNetworkingImpl; +import net.fabricmc.fabric.impl.networking.client.ClientPlayNetworkAddon; + +/** + * Offers access to play stage client-side networking functionalities. + * + *

Client-side networking functionalities include receiving clientbound packets, + * sending serverbound packets, and events related to client-side network handlers. + * + *

This class should be only used on the physical client and for the logical client. + * + * @see ClientLoginNetworking + * @see ServerPlayNetworking + */ +@Environment(EnvType.CLIENT) +public final class ClientPlayNetworking { + /** + * Registers a handler to a channel. + * A global receiver is registered to all connections, in the present and future. + * + *

If a handler is already registered to the {@code channel}, this method will return {@code false}, and no change will be made. + * Use {@link #unregisterGlobalReceiver(Identifier)} to unregister the existing handler. + * + * @param channelName the id of the channel + * @param channelHandler the handler + * @return false if a handler is already registered to the channel + * @see ClientPlayNetworking#unregisterGlobalReceiver(Identifier) + * @see ClientPlayNetworking#registerReceiver(Identifier, PlayChannelHandler) + */ + public static boolean registerGlobalReceiver(Identifier channelName, PlayChannelHandler channelHandler) { + return ClientNetworkingImpl.PLAY.registerGlobalReceiver(channelName, channelHandler); + } + + /** + * Removes the handler of a channel. + * A global receiver is registered to all connections, in the present and future. + * + *

The {@code channel} is guaranteed not to have a handler after this call. + * + * @param channelName the id of the channel + * @return the previous handler, or {@code null} if no handler was bound to the channel + * @see ClientPlayNetworking#registerGlobalReceiver(Identifier, PlayChannelHandler) + * @see ClientPlayNetworking#unregisterReceiver(Identifier) + */ + @Nullable + public static PlayChannelHandler unregisterGlobalReceiver(Identifier channelName) { + return ClientNetworkingImpl.PLAY.unregisterGlobalReceiver(channelName); + } + + /** + * Gets all channel names which global receivers are registered for. + * A global receiver is registered to all connections, in the present and future. + * + * @return all channel names which global receivers are registered for. + */ + public static Set getGlobalReceivers() { + return ClientNetworkingImpl.PLAY.getChannels(); + } + + /** + * Registers a handler to a channel. + * + *

If a handler is already registered to the {@code channel}, this method will return {@code false}, and no change will be made. + * Use {@link #unregisterReceiver(Identifier)} to unregister the existing handler. + * + *

For example, if you only register a receiver using this method when a {@linkplain ClientLoginNetworking#registerGlobalReceiver(Identifier, ClientLoginNetworking.LoginQueryRequestHandler)} + * login query has been received, you should use {@link ClientPlayConnectionEvents#INIT} to register the channel handler. + * + * @param channelName the id of the channel + * @return false if a handler is already registered to the channel + * @throws IllegalStateException if the client is not connected to a server + * @see ClientPlayConnectionEvents#INIT + */ + public static boolean registerReceiver(Identifier channelName, PlayChannelHandler channelHandler) { + final ClientPlayNetworkAddon addon = ClientNetworkingImpl.getClientPlayAddon(); + + if (addon != null) { + return addon.registerChannel(channelName, channelHandler); + } + + throw new IllegalStateException("Cannot register receiver while not in game!"); + } + + /** + * Removes the handler of a channel. + * + *

The {@code channelName} is guaranteed not to have a handler after this call. + * + * @param channelName the id of the channel + * @return the previous handler, or {@code null} if no handler was bound to the channel + * @throws IllegalStateException if the client is not connected to a server + */ + @Nullable + public static PlayChannelHandler unregisterReceiver(Identifier channelName) throws IllegalStateException { + final ClientPlayNetworkAddon addon = ClientNetworkingImpl.getClientPlayAddon(); + + if (addon != null) { + return addon.unregisterChannel(channelName); + } + + throw new IllegalStateException("Cannot unregister receiver while not in game!"); + } + + /** + * Gets all the channel names that the client can receive packets on. + * + * @return All the channel names that the client can receive packets on + * @throws IllegalStateException if the client is not connected to a server + */ + public static Set getReceived() throws IllegalStateException { + final ClientPlayNetworkAddon addon = ClientNetworkingImpl.getClientPlayAddon(); + + if (addon != null) { + return addon.getReceivableChannels(); + } + + throw new IllegalStateException("Cannot get a list of channels the client can receive packets on while not in game!"); + } + + /** + * Gets all channel names that the connected server declared the ability to receive a packets on. + * + * @return All the channel names the connected server declared the ability to receive a packets on + * @throws IllegalStateException if the client is not connected to a server + */ + public static Set getSendable() throws IllegalStateException { + final ClientPlayNetworkAddon addon = ClientNetworkingImpl.getClientPlayAddon(); + + if (addon != null) { + return addon.getSendableChannels(); + } + + throw new IllegalStateException("Cannot get a list of channels the server can receive packets on while not in game!"); + } + + /** + * Checks if the connected server declared the ability to receive a packet on a specified channel name. + * + * @param channelName the channel name + * @return True if the connected server has declared the ability to receive a packet on the specified channel. + * False if the client is not in game. + */ + public static boolean canSend(Identifier channelName) throws IllegalArgumentException { + // You cant send without a client player, so this is fine + if (MinecraftClient.getInstance().getNetworkHandler() != null) { + return ClientNetworkingImpl.getAddon(MinecraftClient.getInstance().getNetworkHandler()).getSendableChannels().contains(channelName); + } + + return false; + } + + /** + * Creates a packet which may be sent to a the connected server. + * + * @param channelName the channel name + * @param buf the packet byte buf which represents the payload of the packet + * @return a new packet + */ + public static Packet createC2SPacket(Identifier channelName, PacketByteBuf buf) { + Objects.requireNonNull(channelName, "Channel name cannot be null"); + Objects.requireNonNull(buf, "Buf cannot be null"); + + return ClientNetworkingImpl.createPlayC2SPacket(channelName, buf); + } + + /** + * Gets the packet sender which sends packets to the connected server. + * + * @return the client's packet sender + * @throws IllegalStateException if the client is not connected to a server + */ + public static PacketSender getSender() throws IllegalStateException { + // You cant send without a client player, so this is fine + if (MinecraftClient.getInstance().getNetworkHandler() != null) { + return ClientNetworkingImpl.getAddon(MinecraftClient.getInstance().getNetworkHandler()); + } + + throw new IllegalStateException("Cannot get packet sender when not in game!"); + } + + /** + * Sends a packet to the connected server. + * + * @param channelName the channel of the packet + * @param buf the payload of the packet + * @throws IllegalStateException if the client is not connected to a server + */ + public static void send(Identifier channelName, PacketByteBuf buf) throws IllegalStateException { + // You cant send without a client player, so this is fine + if (MinecraftClient.getInstance().getNetworkHandler() != null) { + MinecraftClient.getInstance().getNetworkHandler().sendPacket(createC2SPacket(channelName, buf)); + return; + } + + throw new IllegalStateException("Cannot send packets when not in game!"); + } + + private ClientPlayNetworking() { + } + + @Environment(EnvType.CLIENT) + @FunctionalInterface + public interface PlayChannelHandler { + /** + * Handles an incoming packet. + * + *

This method is executed on {@linkplain io.netty.channel.EventLoop netty's event loops}. + * Modification to the game should be {@linkplain net.minecraft.util.thread.ThreadExecutor#submit(Runnable) scheduled} using the provided Minecraft client instance. + * + *

An example usage of this is to display an overlay message: + *

{@code
+		 * ClientPlayNetworking.registerReceiver(new Identifier("mymod", "overlay"), (client, handler, buf, responseSender) -&rt; {
+		 * 	String message = buf.readString(32767);
+		 *
+		 * 	// All operations on the server or world must be executed on the server thread
+		 * 	client.execute(() -&rt; {
+		 * 		client.inGameHud.setOverlayMessage(message, true);
+		 * 	});
+		 * });
+		 * }
+ * @param client the client + * @param handler the network handler that received this packet + * @param buf the payload of the packet + * @param responseSender the packet sender + */ + void receive(MinecraftClient client, ClientPlayNetworkHandler handler, PacketByteBuf buf, PacketSender responseSender); + } +} diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/client/networking/v1/package-info.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/client/networking/v1/package-info.java new file mode 100644 index 000000000..0c4b8cf4e --- /dev/null +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/client/networking/v1/package-info.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * The Networking API (client side), version 1. + * + *

For login stage networking see {@link net.fabricmc.fabric.api.client.networking.v1.ClientLoginNetworking}. + * For play stage networking see {@link net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking}. + * + *

For events related to connection to a server see {@link net.fabricmc.fabric.api.client.networking.v1.ClientLoginConnectionEvents} for login stage + * or {@link net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents} for play stage. + * + *

For events related to the ability of a server to receive packets on a channel of a specific name see {@link net.fabricmc.fabric.api.client.networking.v1.C2SPlayChannelEvents}. + */ + +package net.fabricmc.fabric.api.client.networking.v1; diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/networking/v1/EntityTrackingEvents.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/networking/v1/EntityTrackingEvents.java new file mode 100644 index 000000000..89856ad7a --- /dev/null +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/networking/v1/EntityTrackingEvents.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.api.networking.v1; + +import net.minecraft.entity.Entity; +import net.minecraft.server.network.ServerPlayerEntity; + +import net.fabricmc.fabric.api.event.Event; +import net.fabricmc.fabric.api.event.EventFactory; + +/** + * Events related to a tracking entities within a player's view distance. + */ +public final class EntityTrackingEvents { + /** + * An event that is called before player starts tracking an entity. + * Typically this occurs when an entity enters a client's view distance. + * This event is called before the player's client is sent the entity's {@link Entity#createSpawnPacket() spawn packet}. + */ + public static final Event START_TRACKING = EventFactory.createArrayBacked(StartTracking.class, callbacks -> (trackedEntity, player) -> { + for (StartTracking callback : callbacks) { + callback.onStartTracking(trackedEntity, player); + } + }); + + /** + * An event that is called after a player has stopped tracking an entity. + * The client at this point was sent a packet to {@link net.minecraft.network.packet.s2c.play.EntitiesDestroyS2CPacket destroy} the entity on the client. + * The entity still exists on the server. + */ + public static final Event STOP_TRACKING = EventFactory.createArrayBacked(StopTracking.class, callbacks -> (trackedEntity, player) -> { + for (StopTracking callback : callbacks) { + callback.onStopTracking(trackedEntity, player); + } + }); + + @FunctionalInterface + public interface StartTracking { + /** + * Called before an entity starts getting tracked by a player. + * + * @param trackedEntity the entity that will be tracked + * @param player the player that will track the entity + */ + void onStartTracking(Entity trackedEntity, ServerPlayerEntity player); + } + + @FunctionalInterface + public interface StopTracking { + /** + * Called after an entity stops getting tracked by a player. + * + * @param trackedEntity the entity that is no longer being tracked + * @param player the player that is no longer tracking the entity + */ + void onStopTracking(Entity trackedEntity, ServerPlayerEntity player); + } + + private EntityTrackingEvents() { + } +} diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/networking/v1/FutureListeners.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/networking/v1/FutureListeners.java new file mode 100644 index 000000000..5eb5644e0 --- /dev/null +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/networking/v1/FutureListeners.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.api.networking.v1; + +import java.util.Objects; + +import io.netty.channel.Channel; +import io.netty.channel.ChannelFutureListener; +import io.netty.channel.local.LocalChannel; +import io.netty.channel.local.LocalServerChannel; +import io.netty.util.concurrent.Future; +import io.netty.util.concurrent.FutureListener; +import io.netty.util.concurrent.GenericFutureListener; + +import net.minecraft.util.PacketByteBuf; + +/** + * Utilities for working with netty's future listeners. + * @see FutureListener + * @see ChannelFutureListener + */ +public final class FutureListeners { + /** + * Returns a future listener that releases a packet byte buf when the buffer has been sent to a remote connection. + * + * @param buf the buffer + * @return the future listener + */ + public static ChannelFutureListener free(PacketByteBuf buf) { + Objects.requireNonNull(buf, "PacketByteBuf cannot be null"); + + return (future) -> { + if (!isLocalChannel(future.channel())) { + buf.release(); + } + }; + } + + /** + * Returns whether a netty channel performs local transportation, or if the message objects in the channel are directly passed than written to and read from a byte buf. + * + * @param channel the channel to check + * @return whether the channel is local + */ + public static boolean isLocalChannel(Channel channel) { + return channel instanceof LocalServerChannel || channel instanceof LocalChannel; + } + + /** + * Combines two future listeners. + * + * @param first the first future listener + * @param second the second future listener + * @param the future type of the first listener, used for casting + * @param the future type of the second listener, used for casting + * @return the combined future listener. + */ + // A, B exist just to allow casting + @SuppressWarnings("unchecked") + public static , B extends Future> GenericFutureListener> union(GenericFutureListener first, GenericFutureListener second) { + // Return an empty future listener in the case of both parameters somehow being null + if (first == null && second == null) { + return future -> { }; + } + + if (first == null) { + return second; + } + + if (second == null) { + return first; + } + + return future -> { + first.operationComplete((A) future); + second.operationComplete((B) future); + }; + } + + private FutureListeners() { + } +} diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/networking/v1/PacketByteBufs.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/networking/v1/PacketByteBufs.java new file mode 100644 index 000000000..587e52e04 --- /dev/null +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/networking/v1/PacketByteBufs.java @@ -0,0 +1,206 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.api.networking.v1; + +import java.util.Objects; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; + +import net.minecraft.util.PacketByteBuf; + +/** + * Helper methods for working with and creating {@link PacketByteBuf}s. + */ +public final class PacketByteBufs { + private static final PacketByteBuf EMPTY_PACKET_BYTE_BUF = new PacketByteBuf(Unpooled.EMPTY_BUFFER); + + /** + * Returns an empty instance of packet byte buf. + * + * @return an empty buf + */ + public static PacketByteBuf empty() { + return EMPTY_PACKET_BYTE_BUF; + } + + /** + * Returns a new heap memory-backed instance of packet byte buf. + * + * @return a new buf + */ + public static PacketByteBuf create() { + return new PacketByteBuf(Unpooled.buffer()); + } + + // Convenience methods for byte buf methods that return a new byte buf + + /** + * Wraps the newly created buf from {@code buf.readBytes} in a packet byte buf. + * + * @param buf the original buf + * @param length the number of bytes to transfer + * @return the transferred bytes + * @see ByteBuf#readBytes(int) + */ + public static PacketByteBuf readBytes(ByteBuf buf, int length) { + Objects.requireNonNull(buf, "ByteBuf cannot be null"); + + return new PacketByteBuf(buf.readBytes(length)); + } + + /** + * Wraps the newly created buf from {@code buf.readSlice} in a packet byte buf. + * + * @param buf the original buf + * @param length the size of the new slice + * @return the newly created slice + * @see ByteBuf#readSlice(int) + */ + public static PacketByteBuf readSlice(ByteBuf buf, int length) { + Objects.requireNonNull(buf, "ByteBuf cannot be null"); + + return new PacketByteBuf(buf.readSlice(length)); + } + + /** + * Wraps the newly created buf from {@code buf.readRetainedSlice} in a packet byte buf. + * + * @param buf the original buf + * @param length the size of the new slice + * @return the newly created slice + * @see ByteBuf#readRetainedSlice(int) + */ + public static PacketByteBuf readRetainedSlice(ByteBuf buf, int length) { + Objects.requireNonNull(buf, "ByteBuf cannot be null"); + + return new PacketByteBuf(buf.readRetainedSlice(length)); + } + + /** + * Wraps the newly created buf from {@code buf.copy} in a packet byte buf. + * + * @param buf the original buf + * @return a copy of the buf + * @see ByteBuf#copy() + */ + public static PacketByteBuf copy(ByteBuf buf) { + Objects.requireNonNull(buf, "ByteBuf cannot be null"); + + return new PacketByteBuf(buf.copy()); + } + + /** + * Wraps the newly created buf from {@code buf.copy} in a packet byte buf. + * + * @param buf the original buf + * @param index the starting index + * @param length the size of the copy + * @return a copy of the buf + * @see ByteBuf#copy(int, int) + */ + public static PacketByteBuf copy(ByteBuf buf, int index, int length) { + Objects.requireNonNull(buf, "ByteBuf cannot be null"); + + return new PacketByteBuf(buf.copy(index, length)); + } + + /** + * Wraps the newly created buf from {@code buf.slice} in a packet byte buf. + * + * @param buf the original buf + * @return a slice of the buf + * @see ByteBuf#slice() + */ + public static PacketByteBuf slice(ByteBuf buf) { + Objects.requireNonNull(buf, "ByteBuf cannot be null"); + + return new PacketByteBuf(buf.slice()); + } + + /** + * Wraps the newly created buf from {@code buf.retainedSlice} in a packet byte buf. + * + * @param buf the original buf + * @return a slice of the buf + * @see ByteBuf#retainedSlice() + */ + public static PacketByteBuf retainedSlice(ByteBuf buf) { + Objects.requireNonNull(buf, "ByteBuf cannot be null"); + + return new PacketByteBuf(buf.retainedSlice()); + } + + /** + * Wraps the newly created buf from {@code buf.slice} in a packet byte buf. + * + * @param buf the original buf + * @param index the starting index + * @param length the size of the copy + * @return a slice of the buf + * @see ByteBuf#slice(int, int) + */ + public static PacketByteBuf slice(ByteBuf buf, int index, int length) { + Objects.requireNonNull(buf, "ByteBuf cannot be null"); + + return new PacketByteBuf(buf.slice(index, length)); + } + + /** + * Wraps the newly created buf from {@code buf.retainedSlice} in a packet byte buf. + * + * @param buf the original buf + * @param index the starting index + * @param length the size of the copy + * @return a slice of the buf + * @see ByteBuf#retainedSlice(int, int) + */ + public static PacketByteBuf retainedSlice(ByteBuf buf, int index, int length) { + Objects.requireNonNull(buf, "ByteBuf cannot be null"); + + return new PacketByteBuf(buf.retainedSlice(index, length)); + } + + /** + * Wraps the newly created buf from {@code buf.duplicate} in a packet byte buf. + * + * @param buf the original buf + * @return a duplicate of the buf + * @see ByteBuf#duplicate() + */ + public static PacketByteBuf duplicate(ByteBuf buf) { + Objects.requireNonNull(buf, "ByteBuf cannot be null"); + + return new PacketByteBuf(buf.duplicate()); + } + + /** + * Wraps the newly created buf from {@code buf.retainedDuplicate} in a packet byte buf. + * + * @param buf the original buf + * @return a duplicate of the buf + * @see ByteBuf#retainedDuplicate() + */ + public static PacketByteBuf retainedDuplicate(ByteBuf buf) { + Objects.requireNonNull(buf, "ByteBuf cannot be null"); + + return new PacketByteBuf(buf.retainedDuplicate()); + } + + private PacketByteBufs() { + } +} diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/networking/v1/PacketSender.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/networking/v1/PacketSender.java new file mode 100644 index 000000000..e4fbe2f54 --- /dev/null +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/networking/v1/PacketSender.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.api.networking.v1; + +import java.util.Objects; + +import io.netty.channel.ChannelFutureListener; +import io.netty.util.concurrent.Future; +import io.netty.util.concurrent.GenericFutureListener; +import org.jetbrains.annotations.Nullable; + +import net.minecraft.network.Packet; +import net.minecraft.util.PacketByteBuf; +import net.minecraft.util.Identifier; + +/** + * Represents something that supports sending packets to channels. + * @see PacketByteBufs + */ +public interface PacketSender { + /** + * Makes a packet for a channel. + * + * @param channelName the id of the channel + * @param buf the content of the packet + */ + Packet createPacket(Identifier channelName, PacketByteBuf buf); + + /** + * Sends a packet. + * + * @param packet the packet + */ + void sendPacket(Packet packet); + + /** + * Sends a packet. + * + * @param packet the packet + * @param callback an optional callback to execute after the packet is sent, may be {@code null}. The callback may also accept a {@link ChannelFutureListener}. + */ + void sendPacket(Packet packet, @Nullable GenericFutureListener> callback); + + /** + * Sends a packet to a channel. + * + * @param channel the id of the channel + * @param buf the content of the packet + */ + default void sendPacket(Identifier channel, PacketByteBuf buf) { + Objects.requireNonNull(channel, "Channel cannot be null"); + Objects.requireNonNull(buf, "Payload cannot be null"); + + this.sendPacket(this.createPacket(channel, buf)); + } + + /** + * Sends a packet to a channel. + * + * @param channel the id of the channel + * @param buf the content of the packet + * @param callback an optional callback to execute after the packet is sent, may be {@code null} + */ + // the generic future listener can accept ChannelFutureListener + default void sendPacket(Identifier channel, PacketByteBuf buf, @Nullable GenericFutureListener> callback) { + Objects.requireNonNull(channel, "Channel cannot be null"); + Objects.requireNonNull(buf, "Payload cannot be null"); + + this.sendPacket(this.createPacket(channel, buf), callback); + } +} diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/networking/v1/PlayerLookup.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/networking/v1/PlayerLookup.java new file mode 100644 index 000000000..8ffcc372f --- /dev/null +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/networking/v1/PlayerLookup.java @@ -0,0 +1,193 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.api.networking.v1; + +import java.util.Collection; +import java.util.Collections; +import java.util.Objects; +import java.util.stream.Collectors; + +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.entity.Entity; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.server.world.ServerChunkManager; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.server.world.ThreadedAnvilChunkStorage; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.ChunkPos; +import net.minecraft.util.math.Vec3d; +import net.minecraft.util.math.Vec3i; +import net.minecraft.world.chunk.ChunkManager; + +import net.fabricmc.fabric.impl.networking.ThreadedAnvilChunkStorageTrackingExtensions; + +/** + * For example, a block entity may use the methods in this class to send a packet to all clients which can see the block entity in order notify clients about a change. + * + *

The word "tracking" means that an entity/chunk on the server is known to a player's client (within in view distance) and the (block) entity should notify tracking clients of changes. + * + *

These methods should only be called on the server thread and only be used on logical a server. + */ +public final class PlayerLookup { + /** + * Gets all the players on the minecraft server. + * + *

The returned collection is immutable. + * + * @param server the server + * @return all players on the server + */ + public static Collection all(MinecraftServer server) { + Objects.requireNonNull(server, "The server cannot be null"); + + // return an immutable collection to guard against accidental removals. + if (server.getPlayerManager() != null) { + return Collections.unmodifiableCollection(server.getPlayerManager().getPlayerList()); + } + + return Collections.emptyList(); + } + + /** + * Gets all the players in a server world. + * + *

The returned collection is immutable. + * + * @param world the server world + * @return the players in the server world + */ + public static Collection world(ServerWorld world) { + Objects.requireNonNull(world, "The world cannot be null"); + + // return an immutable collection to guard against accidental removals. + return Collections.unmodifiableCollection(world.getPlayers()); + } + + /** + * Gets all players tracking a chunk in a server world. + * + * @param world the server world + * @param pos the chunk in question + * @return the players tracking the chunk + */ + public static Collection tracking(ServerWorld world, ChunkPos pos) { + Objects.requireNonNull(world, "The world cannot be null"); + Objects.requireNonNull(pos, "The chunk pos cannot be null"); + + return world.getChunkManager().threadedAnvilChunkStorage.getPlayersWatchingChunk(pos, false).collect(Collectors.toList()); + } + + /** + * Gets all players tracking an entity in a server world. + * + *

The returned collection is immutable. + * + *

Warning: If the provided entity is a player, it is not + * guaranteed by the contract that said player is included in the + * resulting stream. + * + * @param entity the entity being tracked + * @return the players tracking the entity + * @throws IllegalArgumentException if the entity is not in a server world + */ + public static Collection tracking(Entity entity) { + Objects.requireNonNull(entity, "Entity cannot be null"); + ChunkManager manager = entity.world.getChunkManager(); + + if (manager instanceof ServerChunkManager) { + ThreadedAnvilChunkStorage storage = ((ServerChunkManager) manager).threadedAnvilChunkStorage; + + // return an immutable collection to guard against accidental removals. + return Collections.unmodifiableCollection(((ThreadedAnvilChunkStorageTrackingExtensions) storage).fabric_getTrackingPlayers(entity)); + } + + throw new IllegalArgumentException("Only supported on server worlds!"); + } + + /** + * Gets all players tracking a block entity in a server world. + * + * @param blockEntity the block entity + * @return the players tracking the block position + * @throws IllegalArgumentException if the block entity is not in a server world + */ + public static Collection tracking(BlockEntity blockEntity) { + Objects.requireNonNull(blockEntity, "BlockEntity cannot be null"); + + //noinspection ConstantConditions - IJ intrinsics don't know hasWorld == true will result in no null + if (!blockEntity.hasWorld() || blockEntity.getWorld().isClient()) { + throw new IllegalArgumentException("Only supported on server worlds!"); + } + + return tracking((ServerWorld) blockEntity.getWorld(), blockEntity.getPos()); + } + + /** + * Gets all players tracking a block position in a server world. + * + * @param world the server world + * @param pos the block position + * @return the players tracking the block position + */ + public static Collection tracking(ServerWorld world, BlockPos pos) { + Objects.requireNonNull(pos, "BlockPos cannot be null"); + + return tracking(world, new ChunkPos(pos)); + } + + /** + * Gets all players around a position in a world. + * + *

The distance check is done in the three-dimensional space instead of in the horizontal plane. + * + * @param world the world + * @param pos the position + * @param radius the maximum distance from the position in blocks + * @return the players around the position + */ + public static Collection around(ServerWorld world, Vec3d pos, double radius) { + double radiusSq = radius * radius; + + return world(world) + .stream() + .filter((p) -> p.squaredDistanceTo(pos) <= radiusSq) + .collect(Collectors.toList()); + } + + /** + * Gets all players around a position in a world. + * + *

The distance check is done in the three-dimensional space instead of in the horizontal plane. + * + * @param world the world + * @param pos the position (can be a block pos) + * @param radius the maximum distance from the position in blocks + * @return the players around the position + */ + public static Collection around(ServerWorld world, Vec3i pos, double radius) { + double radiusSq = radius * radius; + + return world(world) + .stream() + .filter((p) -> p.squaredDistanceTo(pos.getX(), pos.getY(), pos.getZ()) <= radiusSq) + .collect(Collectors.toList()); + } + + private PlayerLookup() { + } +} diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/networking/v1/S2CPlayChannelEvents.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/networking/v1/S2CPlayChannelEvents.java new file mode 100644 index 000000000..93d2881b2 --- /dev/null +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/networking/v1/S2CPlayChannelEvents.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.api.networking.v1; + +import java.util.List; + +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.network.ServerPlayNetworkHandler; +import net.minecraft.util.Identifier; + +import net.fabricmc.fabric.api.event.Event; +import net.fabricmc.fabric.api.event.EventFactory; + +/** + * Offers access to events related to the indication of a connected client's ability to receive packets in certain channels. + */ +public final class S2CPlayChannelEvents { + /** + * An event for the server play network handler receiving an update indicating the connected client's ability to receive packets in certain channels. + * This event may be invoked at any time after login and up to disconnection. + */ + public static final Event REGISTER = EventFactory.createArrayBacked(Register.class, callbacks -> (handler, sender, server, channels) -> { + for (Register callback : callbacks) { + callback.onChannelRegister(handler, sender, server, channels); + } + }); + + /** + * An event for the server play network handler receiving an update indicating the connected client's lack of ability to receive packets in certain channels. + * This event may be invoked at any time after login and up to disconnection. + */ + public static final Event UNREGISTER = EventFactory.createArrayBacked(Unregister.class, callbacks -> (handler, sender, server, channels) -> { + for (Unregister callback : callbacks) { + callback.onChannelUnregister(handler, sender, server, channels); + } + }); + + private S2CPlayChannelEvents() { + } + + /** + * @see S2CPlayChannelEvents#REGISTER + */ + @FunctionalInterface + public interface Register { + void onChannelRegister(ServerPlayNetworkHandler handler, PacketSender sender, MinecraftServer server, List channels); + } + + /** + * @see S2CPlayChannelEvents#UNREGISTER + */ + @FunctionalInterface + public interface Unregister { + void onChannelUnregister(ServerPlayNetworkHandler handler, PacketSender sender, MinecraftServer server, List channels); + } +} diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/networking/v1/ServerLoginConnectionEvents.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/networking/v1/ServerLoginConnectionEvents.java new file mode 100644 index 000000000..bee731ae2 --- /dev/null +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/networking/v1/ServerLoginConnectionEvents.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.api.networking.v1; + +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.network.ServerLoginNetworkHandler; +import net.minecraft.util.Identifier; + +import net.fabricmc.fabric.api.event.Event; +import net.fabricmc.fabric.api.event.EventFactory; + +/** + * Offers access to events related to the connection to a client on a logical server while a client is logging in. + */ +public final class ServerLoginConnectionEvents { + /** + * Event indicating a connection entered the LOGIN state, ready for registering query response handlers. + * + * @see ServerLoginNetworking#registerReceiver(ServerLoginNetworkHandler, Identifier, ServerLoginNetworking.LoginQueryResponseHandler) + */ + public static final Event INIT = EventFactory.createArrayBacked(Init.class, callbacks -> (handler, server) -> { + for (Init callback : callbacks) { + callback.onLoginInit(handler, server); + } + }); + + /** + * An event for the start of login queries of the server login network handler. + * This event may be used to register {@link ServerLoginNetworking.LoginQueryResponseHandler login query response handlers} + * using {@link ServerLoginNetworking#registerReceiver(ServerLoginNetworkHandler, Identifier, ServerLoginNetworking.LoginQueryResponseHandler)} + * since this event is fired just before the first login query response is processed. + * + *

You may send login queries to the connected client using the provided {@link PacketSender}. + */ + public static final Event QUERY_START = EventFactory.createArrayBacked(QueryStart.class, callbacks -> (handler, server, sender, synchronizer) -> { + for (QueryStart callback : callbacks) { + callback.onLoginStart(handler, server, sender, synchronizer); + } + }); + + /** + * An event for the disconnection of the server login network handler. + * + *

No packets should be sent when this event is invoked. + */ + public static final Event DISCONNECT = EventFactory.createArrayBacked(Disconnect.class, callbacks -> (handler, server) -> { + for (Disconnect callback : callbacks) { + callback.onLoginDisconnect(handler, server); + } + }); + + private ServerLoginConnectionEvents() { + } + + /** + * @see ServerLoginConnectionEvents#INIT + */ + @FunctionalInterface + public interface Init { + void onLoginInit(ServerLoginNetworkHandler handler, MinecraftServer server); + } + + /** + * @see ServerLoginConnectionEvents#QUERY_START + */ + @FunctionalInterface + public interface QueryStart { + void onLoginStart(ServerLoginNetworkHandler handler, MinecraftServer server, PacketSender sender, ServerLoginNetworking.LoginSynchronizer synchronizer); + } + + /** + * @see ServerLoginConnectionEvents#DISCONNECT + */ + @FunctionalInterface + public interface Disconnect { + void onLoginDisconnect(ServerLoginNetworkHandler handler, MinecraftServer server); + } +} diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/networking/v1/ServerLoginNetworking.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/networking/v1/ServerLoginNetworking.java new file mode 100644 index 000000000..fa7d56874 --- /dev/null +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/networking/v1/ServerLoginNetworking.java @@ -0,0 +1,199 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.api.networking.v1; + +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.Future; + +import org.jetbrains.annotations.Nullable; + +import net.minecraft.util.PacketByteBuf; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.network.ServerLoginNetworkHandler; +import net.minecraft.util.Identifier; + +import net.fabricmc.fabric.api.client.networking.v1.ClientLoginNetworking; +import net.fabricmc.fabric.impl.networking.server.ServerLoginNetworkHandlerExtensions; +import net.fabricmc.fabric.impl.networking.server.ServerNetworkingImpl; +import net.fabricmc.fabric.mixin.networking.accessor.ServerLoginNetworkHandlerAccessor; + +/** + * Offers access to login stage server-side networking functionalities. + * + *

Server-side networking functionalities include receiving serverbound query responses and sending clientbound query requests. + * + * @see ServerPlayNetworking + * @see ClientLoginNetworking + */ +public final class ServerLoginNetworking { + /** + * Registers a handler to a query response channel. + * A global receiver is registered to all connections, in the present and future. + * + *

If a handler is already registered to the {@code channel}, this method will return {@code false}, and no change will be made. + * Use {@link #unregisterGlobalReceiver(Identifier)} to unregister the existing handler. + * + * @param channelName the id of the channel + * @param channelHandler the handler + * @return false if a handler is already registered to the channel + * @see ServerLoginNetworking#unregisterGlobalReceiver(Identifier) + * @see ServerLoginNetworking#registerReceiver(ServerLoginNetworkHandler, Identifier, LoginQueryResponseHandler) + */ + public static boolean registerGlobalReceiver(Identifier channelName, LoginQueryResponseHandler channelHandler) { + return ServerNetworkingImpl.LOGIN.registerGlobalReceiver(channelName, channelHandler); + } + + /** + * Removes the handler of a query response channel. + * A global receiver is registered to all connections, in the present and future. + * + *

The {@code channel} is guaranteed not to have a handler after this call. + * + * @param channelName the id of the channel + * @return the previous handler, or {@code null} if no handler was bound to the channel + * @see ServerLoginNetworking#registerGlobalReceiver(Identifier, LoginQueryResponseHandler) + * @see ServerLoginNetworking#unregisterReceiver(ServerLoginNetworkHandler, Identifier) + */ + @Nullable + public static ServerLoginNetworking.LoginQueryResponseHandler unregisterGlobalReceiver(Identifier channelName) { + return ServerNetworkingImpl.LOGIN.unregisterGlobalReceiver(channelName); + } + + /** + * Gets all channel names which global receivers are registered for. + * A global receiver is registered to all connections, in the present and future. + * + * @return all channel names which global receivers are registered for. + */ + public static Set getGlobalReceivers() { + return ServerNetworkingImpl.LOGIN.getChannels(); + } + + /** + * Registers a handler to a query response channel. + * + *

If a handler is already registered to the {@code channelName}, this method will return {@code false}, and no change will be made. + * Use {@link #unregisterReceiver(ServerLoginNetworkHandler, Identifier)} to unregister the existing handler. + * + * @param networkHandler the handler + * @param channelName the id of the channel + * @param responseHandler the handler + * @return false if a handler is already registered to the channel name + */ + public static boolean registerReceiver(ServerLoginNetworkHandler networkHandler, Identifier channelName, LoginQueryResponseHandler responseHandler) { + Objects.requireNonNull(networkHandler, "Network handler cannot be null"); + + return ((ServerLoginNetworkHandlerExtensions) networkHandler).getAddon().registerChannel(channelName, responseHandler); + } + + /** + * Removes the handler of a query response channel. + * + *

The {@code channelName} is guaranteed not to have a handler after this call. + * + * @param channelName the id of the channel + * @return the previous handler, or {@code null} if no handler was bound to the channel name + */ + @Nullable + public static ServerLoginNetworking.LoginQueryResponseHandler unregisterReceiver(ServerLoginNetworkHandler networkHandler, Identifier channelName) { + Objects.requireNonNull(networkHandler, "Network handler cannot be null"); + + return ((ServerLoginNetworkHandlerExtensions) networkHandler).getAddon().unregisterChannel(channelName); + } + + // Helper methods + + /** + * Returns the Minecraft Server of a server login network handler. + * + * @param handler the server login network handler + */ + public static MinecraftServer getServer(ServerLoginNetworkHandler handler) { + Objects.requireNonNull(handler, "Network handler cannot be null"); + + return ((ServerLoginNetworkHandlerAccessor) handler).getServer(); + } + + private ServerLoginNetworking() { + } + + @FunctionalInterface + public interface LoginQueryResponseHandler { + /** + * Handles an incoming query response from a client. + * + *

This method is executed on {@linkplain io.netty.channel.EventLoop netty's event loops}. + * Modification to the game should be {@linkplain net.minecraft.util.thread.ThreadExecutor#submit(Runnable) scheduled} using the provided Minecraft client instance. + * + *

Whether the client understood the query should be checked before reading from the payload of the packet. + * @param server the server + * @param handler the network handler that received this packet, representing the player/client who sent the response + * @param understood whether the client understood the packet + * @param buf the payload of the packet + * @param synchronizer the synchronizer which may be used to delay log-in till a {@link Future} is completed. + * @param responseSender the packet sender + */ + void receive(MinecraftServer server, ServerLoginNetworkHandler handler, boolean understood, PacketByteBuf buf, LoginSynchronizer synchronizer, PacketSender responseSender); + } + + /** + * Allows blocking client log-in until all all futures passed into {@link LoginSynchronizer#waitFor(Future)} are completed. + * + * @apiNote this interface is not intended to be implemented by users of api. + */ + @FunctionalInterface + public interface LoginSynchronizer { + /** + * Allows blocking client log-in until the {@code future} is {@link Future#isDone() done}. + * + *

Since packet reception happens on netty's event loops, this allows handlers to + * perform logic on the Server Thread, etc. For instance, a handler can prepare an + * upcoming query request or check necessary login data on the server thread.

+ * + *

Here is an example where the player log-in is blocked so that a credential check and + * building of a followup query request can be performed properly on the logical server + * thread before the player successfully logs in: + *

{@code
+		 * ServerLoginNetworking.registerGlobalReceiver(CHECK_CHANNEL, (server, handler, understood, buf, synchronizer, responseSender) -> {
+		 * 	if (!understood) {
+		 * 		handler.disconnect(new LiteralText("Only accept clients that can check!"));
+		 * 		return;
+		 * 	}
+		 *
+		 * 	String checkMessage = buf.readString(32767);
+		 *
+		 * 	// Just send the CompletableFuture returned by the server's submit method
+		 * 	synchronizer.waitFor(server.submit(() -> {
+		 * 		LoginInfoChecker checker = LoginInfoChecker.get(server);
+		 *
+		 * 		if (!checker.check(handler.getConnectionInfo(), checkMessage)) {
+		 * 			handler.disconnect(new LiteralText("Invalid credentials!"));
+		 * 			return;
+		 * 		}
+		 *
+		 * 		responseSender.send(UPCOMING_CHECK, checker.buildSecondQueryPacket(handler, checkMessage));
+		 * 	}));
+		 * });
+		 * }
+ * Usually it is enough to pass the return value for {@link net.minecraft.util.thread.ThreadExecutor#submit(Runnable)} for {@code future}.

+ * + * @param future the future that must be done before the player can log in + */ + void waitFor(Future future); + } +} diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/networking/v1/ServerPlayConnectionEvents.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/networking/v1/ServerPlayConnectionEvents.java new file mode 100644 index 000000000..1957126c9 --- /dev/null +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/networking/v1/ServerPlayConnectionEvents.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.api.networking.v1; + +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.network.ServerPlayNetworkHandler; +import net.minecraft.util.Identifier; + +import net.fabricmc.fabric.api.event.Event; +import net.fabricmc.fabric.api.event.EventFactory; + +/** + * Offers access to events related to the connection to a client on a logical server while a client is in game. + */ +public final class ServerPlayConnectionEvents { + /** + * Event indicating a connection entered the PLAY state, ready for registering channel handlers. + * + * @see ServerPlayNetworking#registerReceiver(ServerPlayNetworkHandler, Identifier, ServerPlayNetworking.PlayChannelHandler) + */ + public static final Event INIT = EventFactory.createArrayBacked(Init.class, callbacks -> (handler, server) -> { + for (Init callback : callbacks) { + callback.onPlayInit(handler, server); + } + }); + + /** + * An event for notification when the server play network handler is ready to send packets to the client. + * + *

At this stage, the network handler is ready to send packets to the client. + */ + public static final Event JOIN = EventFactory.createArrayBacked(Join.class, callbacks -> (handler, sender, server) -> { + for (Join callback : callbacks) { + callback.onPlayReady(handler, sender, server); + } + }); + + /** + * An event for the disconnection of the server play network handler. + * + *

No packets should be sent when this event is invoked. + */ + public static final Event DISCONNECT = EventFactory.createArrayBacked(Disconnect.class, callbacks -> (handler, server) -> { + for (Disconnect callback : callbacks) { + callback.onPlayDisconnect(handler, server); + } + }); + + private ServerPlayConnectionEvents() { + } + + @FunctionalInterface + public interface Init { + void onPlayInit(ServerPlayNetworkHandler handler, MinecraftServer server); + } + + @FunctionalInterface + public interface Join { + void onPlayReady(ServerPlayNetworkHandler handler, PacketSender sender, MinecraftServer server); + } + + @FunctionalInterface + public interface Disconnect { + void onPlayDisconnect(ServerPlayNetworkHandler handler, MinecraftServer server); + } +} diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/networking/v1/ServerPlayNetworking.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/networking/v1/ServerPlayNetworking.java new file mode 100644 index 000000000..181938c23 --- /dev/null +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/networking/v1/ServerPlayNetworking.java @@ -0,0 +1,298 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.api.networking.v1; + +import java.util.Objects; +import java.util.Set; + +import org.jetbrains.annotations.Nullable; + +import net.minecraft.network.Packet; +import net.minecraft.util.PacketByteBuf; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.network.ServerPlayNetworkHandler; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.util.Identifier; + +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; +import net.fabricmc.fabric.impl.networking.server.ServerNetworkingImpl; +import net.fabricmc.fabric.impl.networking.server.ServerPlayNetworkHandlerExtensions; + +/** + * Offers access to play stage server-side networking functionalities. + * + *

Server-side networking functionalities include receiving serverbound packets, sending clientbound packets, and events related to server-side network handlers. + * + *

This class should be only used for the logical server. + * + * @see ServerLoginNetworking + * @see ClientPlayNetworking + */ +public final class ServerPlayNetworking { + /** + * Registers a handler to a channel. + * A global receiver is registered to all connections, in the present and future. + * + *

If a handler is already registered to the {@code channel}, this method will return {@code false}, and no change will be made. + * Use {@link #unregisterReceiver(ServerPlayNetworkHandler, Identifier)} to unregister the existing handler. + * + * @param channelName the id of the channel + * @param channelHandler the handler + * @return false if a handler is already registered to the channel + * @see ServerPlayNetworking#unregisterGlobalReceiver(Identifier) + * @see ServerPlayNetworking#registerReceiver(ServerPlayNetworkHandler, Identifier, PlayChannelHandler) + */ + public static boolean registerGlobalReceiver(Identifier channelName, PlayChannelHandler channelHandler) { + return ServerNetworkingImpl.PLAY.registerGlobalReceiver(channelName, channelHandler); + } + + /** + * Removes the handler of a channel. + * A global receiver is registered to all connections, in the present and future. + * + *

The {@code channel} is guaranteed not to have a handler after this call. + * + * @param channelName the id of the channel + * @return the previous handler, or {@code null} if no handler was bound to the channel + * @see ServerPlayNetworking#registerGlobalReceiver(Identifier, PlayChannelHandler) + * @see ServerPlayNetworking#unregisterReceiver(ServerPlayNetworkHandler, Identifier) + */ + @Nullable + public static PlayChannelHandler unregisterGlobalReceiver(Identifier channelName) { + return ServerNetworkingImpl.PLAY.unregisterGlobalReceiver(channelName); + } + + /** + * Gets all channel names which global receivers are registered for. + * A global receiver is registered to all connections, in the present and future. + * + * @return all channel names which global receivers are registered for. + */ + public static Set getGlobalReceivers() { + return ServerNetworkingImpl.PLAY.getChannels(); + } + + /** + * Registers a handler to a channel. + * This method differs from {@link ServerPlayNetworking#registerGlobalReceiver(Identifier, PlayChannelHandler)} since + * the channel handler will only be applied to the player represented by the {@link ServerPlayNetworkHandler}. + * + *

For example, if you only register a receiver using this method when a {@linkplain ServerLoginNetworking#registerGlobalReceiver(Identifier, ServerLoginNetworking.LoginQueryResponseHandler)} + * login response has been received, you should use {@link ServerPlayConnectionEvents#INIT} to register the channel handler. + * + *

If a handler is already registered to the {@code channelName}, this method will return {@code false}, and no change will be made. + * Use {@link #unregisterReceiver(ServerPlayNetworkHandler, Identifier)} to unregister the existing handler. + * + * @param networkHandler the handler + * @param channelName the id of the channel + * @param channelHandler the handler + * @return false if a handler is already registered to the channel name + * @see ServerPlayConnectionEvents#INIT + */ + public static boolean registerReceiver(ServerPlayNetworkHandler networkHandler, Identifier channelName, PlayChannelHandler channelHandler) { + Objects.requireNonNull(networkHandler, "Network handler cannot be null"); + + return ((ServerPlayNetworkHandlerExtensions) networkHandler).getAddon().registerChannel(channelName, channelHandler); + } + + /** + * Removes the handler of a channel. + * + *

The {@code channelName} is guaranteed not to have a handler after this call. + * + * @param channelName the id of the channel + * @return the previous handler, or {@code null} if no handler was bound to the channel name + */ + @Nullable + public static PlayChannelHandler unregisterReceiver(ServerPlayNetworkHandler networkHandler, Identifier channelName) { + Objects.requireNonNull(networkHandler, "Network handler cannot be null"); + + return ServerNetworkingImpl.getAddon(networkHandler).unregisterChannel(channelName); + } + + /** + * Gets all the channel names that the server can receive packets on. + * + * @param player the player + * @return All the channel names that the server can receive packets on + */ + public static Set getReceived(ServerPlayerEntity player) { + Objects.requireNonNull(player, "Server player entity cannot be null"); + + return getReceived(player.networkHandler); + } + + /** + * Gets all the channel names that the server can receive packets on. + * + * @param handler the network handler + * @return All the channel names that the server can receive packets on + */ + public static Set getReceived(ServerPlayNetworkHandler handler) { + Objects.requireNonNull(handler, "Server play network handler cannot be null"); + + return ServerNetworkingImpl.getAddon(handler).getReceivableChannels(); + } + + /** + * Gets all channel names that the connected client declared the ability to receive a packets on. + * + * @param player the player + * @return All the channel names the connected client declared the ability to receive a packets on + */ + public static Set getSendable(ServerPlayerEntity player) { + Objects.requireNonNull(player, "Server player entity cannot be null"); + + return getSendable(player.networkHandler); + } + + /** + * Gets all channel names that a the connected client declared the ability to receive a packets on. + * + * @param handler the network handler + * @return True if the connected client has declared the ability to receive a packet on the specified channel + */ + public static Set getSendable(ServerPlayNetworkHandler handler) { + Objects.requireNonNull(handler, "Server play network handler cannot be null"); + + return ServerNetworkingImpl.getAddon(handler).getSendableChannels(); + } + + /** + * Checks if the connected client declared the ability to receive a packet on a specified channel name. + * + * @param player the player + * @param channelName the channel name + * @return True if the connected client has declared the ability to receive a packet on the specified channel + */ + public static boolean canSend(ServerPlayerEntity player, Identifier channelName) { + Objects.requireNonNull(player, "Server player entity cannot be null"); + + return canSend(player.networkHandler, channelName); + } + + /** + * Checks if the connected client declared the ability to receive a packet on a specified channel name. + * + * @param handler the network handler + * @param channelName the channel name + * @return True if the connected client has declared the ability to receive a packet on the specified channel + */ + public static boolean canSend(ServerPlayNetworkHandler handler, Identifier channelName) { + Objects.requireNonNull(handler, "Server play network handler cannot be null"); + Objects.requireNonNull(channelName, "Channel name cannot be null"); + + return ServerNetworkingImpl.getAddon(handler).getSendableChannels().contains(channelName); + } + + /** + * Creates a packet which may be sent to a the connected client. + * + * @param channelName the channel name + * @param buf the packet byte buf which represents the payload of the packet + * @return a new packet + */ + public static Packet createS2CPacket(Identifier channelName, PacketByteBuf buf) { + Objects.requireNonNull(channelName, "Channel cannot be null"); + Objects.requireNonNull(buf, "Buf cannot be null"); + + return ServerNetworkingImpl.createPlayC2SPacket(channelName, buf); + } + + /** + * Gets the packet sender which sends packets to the connected client. + * + * @param player the player + * @return the packet sender + */ + public static PacketSender getSender(ServerPlayerEntity player) { + Objects.requireNonNull(player, "Server player entity cannot be null"); + + return getSender(player.networkHandler); + } + + /** + * Gets the packet sender which sends packets to the connected client. + * + * @param handler the network handler, representing the connection to the player/client + * @return the packet sender + */ + public static PacketSender getSender(ServerPlayNetworkHandler handler) { + Objects.requireNonNull(handler, "Server play network handler cannot be null"); + + return ServerNetworkingImpl.getAddon(handler); + } + + /** + * Sends a packet to a player. + * + * @param player the player to send the packet to + * @param channelName the channel of the packet + * @param buf the payload of the packet. + */ + public static void send(ServerPlayerEntity player, Identifier channelName, PacketByteBuf buf) { + Objects.requireNonNull(player, "Server player entity cannot be null"); + Objects.requireNonNull(channelName, "Channel name cannot be null"); + Objects.requireNonNull(buf, "Packet byte buf cannot be null"); + + player.networkHandler.sendPacket(createS2CPacket(channelName, buf)); + } + + // Helper methods + + /** + * Returns the Minecraft Server of a server play network handler. + * + * @param handler the server play network handler + */ + public static MinecraftServer getServer(ServerPlayNetworkHandler handler) { + Objects.requireNonNull(handler, "Network handler cannot be null"); + + return handler.player.server; + } + + private ServerPlayNetworking() { + } + + @FunctionalInterface + public interface PlayChannelHandler { + /** + * Handles an incoming packet. + * + *

This method is executed on {@linkplain io.netty.channel.EventLoop netty's event loops}. + * Modification to the game should be {@linkplain net.minecraft.util.thread.ThreadExecutor#submit(Runnable) scheduled} using the provided Minecraft server instance. + * + *

An example usage of this is to create an explosion where the player is looking: + *

{@code
+		 * ServerPlayNetworking.registerReceiver(new Identifier("mymod", "boom"), (server, player, handler, buf, responseSender) -&rt; {
+		 * 	boolean fire = buf.readBoolean();
+		 *
+		 * 	// All operations on the server or world must be executed on the server thread
+		 * 	server.execute(() -&rt; {
+		 * 		ModPacketHandler.createExplosion(player, fire);
+		 * 	});
+		 * });
+		 * }
+ * @param server the server + * @param player the player + * @param handler the network handler that received this packet, representing the player/client who sent the packet + * @param buf the payload of the packet + * @param responseSender the packet sender + */ + void receive(MinecraftServer server, ServerPlayerEntity player, ServerPlayNetworkHandler handler, PacketByteBuf buf, PacketSender responseSender); + } +} diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/networking/v1/package-info.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/networking/v1/package-info.java new file mode 100644 index 000000000..142811b1b --- /dev/null +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/networking/v1/package-info.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * The Networking API, version 1. + * + *

For login stage networking see {@link net.fabricmc.fabric.api.networking.v1.ServerLoginNetworking}. + * For play stage networking see {@link net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking}. + * + *

For events related to the connection to a client see {@link net.fabricmc.fabric.api.networking.v1.ServerLoginConnectionEvents} for login stage + * or {@link net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents} for play stage. + * + *

For events related to the ability of a client to receive packets on a channel of a specific name see {@link net.fabricmc.fabric.api.networking.v1.S2CPlayChannelEvents}. + */ + +package net.fabricmc.fabric.api.networking.v1; diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/AbstractChanneledNetworkAddon.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/AbstractChanneledNetworkAddon.java new file mode 100644 index 000000000..f05121d3a --- /dev/null +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/AbstractChanneledNetworkAddon.java @@ -0,0 +1,207 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.impl.networking; + +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +import io.netty.util.AsciiString; +import io.netty.util.concurrent.Future; +import io.netty.util.concurrent.GenericFutureListener; +import org.jetbrains.annotations.Nullable; + +import net.minecraft.network.ClientConnection; +import net.minecraft.network.Packet; +import net.minecraft.util.PacketByteBuf; +import net.minecraft.util.Identifier; +import net.minecraft.util.InvalidIdentifierException; + +import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; +import net.fabricmc.fabric.api.networking.v1.PacketSender; + +/** + * A network addon which is aware of the channels the other side may receive. + * + * @param the channel handler type + */ +public abstract class AbstractChanneledNetworkAddon extends AbstractNetworkAddon implements PacketSender { + protected final ClientConnection connection; + protected final GlobalReceiverRegistry receiver; + protected final Set sendableChannels; + protected final Set sendableChannelsView; + + protected AbstractChanneledNetworkAddon(GlobalReceiverRegistry receiver, ClientConnection connection, String description) { + this(receiver, connection, new HashSet<>(), description); + } + + protected AbstractChanneledNetworkAddon(GlobalReceiverRegistry receiver, ClientConnection connection, Set sendableChannels, String description) { + super(receiver, description); + this.connection = connection; + this.receiver = receiver; + this.sendableChannels = sendableChannels; + this.sendableChannelsView = Collections.unmodifiableSet(sendableChannels); + } + + public abstract void lateInit(); + + protected void registerPendingChannels(ChannelInfoHolder holder) { + final Collection pending = holder.getPendingChannelsNames(); + + if (!pending.isEmpty()) { + register(new ArrayList<>(pending)); + pending.clear(); + } + } + + // always supposed to handle async! + protected boolean handle(Identifier channelName, PacketByteBuf originalBuf) { + this.logger.debug("Handling inbound packet from channel with name \"{}\"", channelName); + + // Handle reserved packets + if (NetworkingImpl.REGISTER_CHANNEL.equals(channelName)) { + this.receiveRegistration(true, PacketByteBufs.slice(originalBuf)); + return true; + } + + if (NetworkingImpl.UNREGISTER_CHANNEL.equals(channelName)) { + this.receiveRegistration(false, PacketByteBufs.slice(originalBuf)); + return true; + } + + @Nullable H handler = this.getHandler(channelName); + + if (handler == null) { + return false; + } + + PacketByteBuf buf = PacketByteBufs.slice(originalBuf); + + try { + this.receive(handler, buf); + } catch (Throwable ex) { + this.logger.error("Encountered exception while handling in channel with name \"{}\"", channelName, ex); + throw ex; + } + + return true; + } + + protected abstract void receive(H handler, PacketByteBuf buf); + + protected void sendInitialChannelRegistrationPacket() { + final PacketByteBuf buf = this.createRegistrationPacket(this.getReceivableChannels()); + + if (buf != null) { + this.sendPacket(NetworkingImpl.REGISTER_CHANNEL, buf); + } + } + + @Nullable + protected PacketByteBuf createRegistrationPacket(Collection channels) { + if (channels.isEmpty()) { + return null; + } + + PacketByteBuf buf = PacketByteBufs.create(); + boolean first = true; + + for (Identifier channel : channels) { + if (first) { + first = false; + } else { + buf.writeByte(0); + } + + buf.writeBytes(channel.toString().getBytes(StandardCharsets.US_ASCII)); + } + + return buf; + } + + // wrap in try with res (buf) + protected void receiveRegistration(boolean register, PacketByteBuf buf) { + List ids = new ArrayList<>(); + StringBuilder active = new StringBuilder(); + + while (buf.isReadable()) { + byte b = buf.readByte(); + + if (b != 0) { + active.append(AsciiString.b2c(b)); + } else { + this.addId(ids, active); + active = new StringBuilder(); + } + } + + this.addId(ids, active); + this.schedule(register ? () -> register(ids) : () -> unregister(ids)); + } + + void register(List ids) { + this.sendableChannels.addAll(ids); + this.invokeRegisterEvent(ids); + } + + void unregister(List ids) { + this.sendableChannels.removeAll(ids); + this.invokeUnregisterEvent(ids); + } + + @Override + public void sendPacket(Packet packet) { + Objects.requireNonNull(packet, "Packet cannot be null"); + + this.connection.send(packet); + } + + @Override + public void sendPacket(Packet packet, GenericFutureListener> callback) { + Objects.requireNonNull(packet, "Packet cannot be null"); + + this.connection.send(packet, callback); + } + + /** + * Schedules a task to run on the main thread. + */ + protected abstract void schedule(Runnable task); + + protected abstract void invokeRegisterEvent(List ids); + + protected abstract void invokeUnregisterEvent(List ids); + + private void addId(List ids, StringBuilder sb) { + String literal = sb.toString(); + + try { + ids.add(new Identifier(literal)); + } catch (InvalidIdentifierException ex) { + this.logger.warn("Received invalid channel identifier \"{}\" from connection {}", literal, this.connection, ex); + } + } + + public Set getSendableChannels() { + return this.sendableChannelsView; + } +} diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/AbstractNetworkAddon.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/AbstractNetworkAddon.java new file mode 100644 index 000000000..956cf9c39 --- /dev/null +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/AbstractNetworkAddon.java @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.impl.networking; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.jetbrains.annotations.Nullable; + +import net.minecraft.util.Identifier; + +/** + * A network addon is a simple abstraction to hold information about a player's registered channels. + * + * @param the channel handler type + */ +public abstract class AbstractNetworkAddon { + protected final GlobalReceiverRegistry receiver; + protected final Logger logger; + // A lock is used due to possible access on netty's event loops and game thread at same times such as during dynamic registration + private final ReadWriteLock lock = new ReentrantReadWriteLock(); + // Sync map should be fine as there is little read write competition + // All access to this map is guarded by the lock + private final Map handlers = new HashMap<>(); + + protected AbstractNetworkAddon(GlobalReceiverRegistry receiver, String description) { + this.receiver = receiver; + this.logger = LogManager.getLogger(description); + } + + @Nullable + public H getHandler(Identifier channel) { + Lock lock = this.lock.readLock(); + lock.lock(); + + try { + return this.handlers.get(channel); + } finally { + lock.unlock(); + } + } + + public boolean registerChannel(Identifier channelName, H handler) { + Objects.requireNonNull(channelName, "Channel name cannot be null"); + Objects.requireNonNull(handler, "Packet handler cannot be null"); + + if (this.isReservedChannel(channelName)) { + throw new IllegalArgumentException(String.format("Cannot register handler for reserved channel with name \"%s\"", channelName)); + } + + Lock lock = this.lock.writeLock(); + lock.lock(); + + try { + final boolean replaced = this.handlers.putIfAbsent(channelName, handler) == null; + + if (replaced) { + this.handleRegistration(channelName); + } + + return replaced; + } finally { + lock.unlock(); + } + } + + public H unregisterChannel(Identifier channelName) { + Objects.requireNonNull(channelName, "Channel name cannot be null"); + + if (this.isReservedChannel(channelName)) { + throw new IllegalArgumentException(String.format("Cannot register handler for reserved channel with name \"%s\"", channelName)); + } + + Lock lock = this.lock.writeLock(); + lock.lock(); + + try { + final H removed = this.handlers.remove(channelName); + + if (removed != null) { + this.handleUnregistration(channelName); + } + + return removed; + } finally { + lock.unlock(); + } + } + + public Set getReceivableChannels() { + Lock lock = this.lock.readLock(); + lock.lock(); + + try { + return new HashSet<>(this.handlers.keySet()); + } finally { + lock.unlock(); + } + } + + protected abstract void handleRegistration(Identifier channelName); + + protected abstract void handleUnregistration(Identifier channelName); + + public abstract void invokeDisconnectEvent(); + + /** + * Checks if a channel is considered a "reserved" channel. + * A reserved channel such as "minecraft:(un)register" has special handling and should not have any channel handlers registered for it. + * + * @param channelName the channel name + * @return whether the channel is reserved + */ + protected abstract boolean isReservedChannel(Identifier channelName); +} diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/ChannelInfoHolder.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/ChannelInfoHolder.java new file mode 100644 index 000000000..bb4cad63e --- /dev/null +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/ChannelInfoHolder.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.impl.networking; + +import java.util.Collection; + +import net.minecraft.util.Identifier; + +public interface ChannelInfoHolder { + /** + * @return Channels which are declared as receivable by the other side but have not been declared yet. + */ + Collection getPendingChannelsNames(); +} diff --git a/fabric-networking-v0/src/main/java/net/fabricmc/fabric/impl/networking/PacketDebugOptions.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/DisconnectPacketSource.java similarity index 74% rename from fabric-networking-v0/src/main/java/net/fabricmc/fabric/impl/networking/PacketDebugOptions.java rename to fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/DisconnectPacketSource.java index b7040abb7..3d8b211e7 100644 --- a/fabric-networking-v0/src/main/java/net/fabricmc/fabric/impl/networking/PacketDebugOptions.java +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/DisconnectPacketSource.java @@ -16,8 +16,9 @@ package net.fabricmc.fabric.impl.networking; -public final class PacketDebugOptions { - public static final boolean DISABLE_BUFFER_RELEASES = System.getProperty("fabric.networking.broken.disableBufferReleases", "false").equalsIgnoreCase("true"); +import net.minecraft.network.Packet; +import net.minecraft.text.Text; - private PacketDebugOptions() { } +public interface DisconnectPacketSource { + Packet createDisconnectPacket(Text message); } diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/GlobalReceiverRegistry.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/GlobalReceiverRegistry.java new file mode 100644 index 000000000..a93fca984 --- /dev/null +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/GlobalReceiverRegistry.java @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.impl.networking; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +import org.jetbrains.annotations.Nullable; + +import net.minecraft.util.Identifier; + +public final class GlobalReceiverRegistry { + private final ReadWriteLock lock = new ReentrantReadWriteLock(); + private final Map handlers; + private final Set> trackedAddons = new HashSet<>(); + + public GlobalReceiverRegistry() { + this(new HashMap<>()); // sync map should be fine as there is little read write competitions + } + + public GlobalReceiverRegistry(Map map) { + this.handlers = map; + } + + @Nullable + public H getHandler(Identifier channelName) { + Lock lock = this.lock.readLock(); + lock.lock(); + + try { + return this.handlers.get(channelName); + } finally { + lock.unlock(); + } + } + + public boolean registerGlobalReceiver(Identifier channelName, H handler) { + Objects.requireNonNull(channelName, "Channel name cannot be null"); + Objects.requireNonNull(handler, "Channel handler cannot be null"); + + if (NetworkingImpl.isReservedPlayChannel(channelName)) { + throw new IllegalArgumentException(String.format("Cannot register handler for reserved channel with name \"%s\"", channelName)); + } + + Lock lock = this.lock.writeLock(); + lock.lock(); + + try { + final boolean replaced = this.handlers.putIfAbsent(channelName, handler) == null; + + if (!replaced) { + this.handleRegistration(channelName, handler); + } + + return replaced; + } finally { + lock.unlock(); + } + } + + public H unregisterGlobalReceiver(Identifier channelName) { + Objects.requireNonNull(channelName, "Channel name cannot be null"); + + if (NetworkingImpl.isReservedPlayChannel(channelName)) { + throw new IllegalArgumentException(String.format("Cannot unregister packet handler for reserved channel with name \"%s\"", channelName)); + } + + Lock lock = this.lock.writeLock(); + lock.lock(); + + try { + final H removed = this.handlers.remove(channelName); + + if (removed != null) { + this.handleUnregistration(channelName); + } + + return removed; + } finally { + lock.unlock(); + } + } + + public Map getHandlers() { + Lock lock = this.lock.writeLock(); + lock.lock(); + + try { + return new HashMap<>(this.handlers); + } finally { + lock.unlock(); + } + } + + public Set getChannels() { + Lock lock = this.lock.readLock(); + lock.lock(); + + try { + return new HashSet<>(this.handlers.keySet()); + } finally { + lock.unlock(); + } + } + + // State tracking methods + + public void startSession(AbstractNetworkAddon addon) { + Lock lock = this.lock.writeLock(); + lock.lock(); + + try { + this.trackedAddons.add(addon); + } finally { + lock.unlock(); + } + } + + public void endSession(AbstractNetworkAddon addon) { + Lock lock = this.lock.writeLock(); + lock.lock(); + + try { + this.trackedAddons.remove(addon); + } finally { + lock.unlock(); + } + } + + private void handleRegistration(Identifier channelName, H handler) { + Lock lock = this.lock.writeLock(); + lock.lock(); + + try { + for (AbstractNetworkAddon addon : this.trackedAddons) { + addon.registerChannel(channelName, handler); + } + } finally { + lock.unlock(); + } + } + + private void handleUnregistration(Identifier channelName) { + Lock lock = this.lock.writeLock(); + lock.lock(); + + try { + for (AbstractNetworkAddon addon : this.trackedAddons) { + addon.unregisterChannel(channelName); + } + } finally { + lock.unlock(); + } + } +} diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/NetworkingImpl.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/NetworkingImpl.java new file mode 100644 index 000000000..c996c69d1 --- /dev/null +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/NetworkingImpl.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.impl.networking; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import net.minecraft.util.PacketByteBuf; +import net.minecraft.util.Identifier; + +import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; +import net.fabricmc.fabric.api.networking.v1.ServerLoginConnectionEvents; +import net.fabricmc.fabric.api.networking.v1.ServerLoginNetworking; +import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; + +public final class NetworkingImpl { + public static final String MOD_ID = "fabric-networking-api-v1"; + public static final Logger LOGGER = LogManager.getLogger(MOD_ID); + /** + * Id of packet used to register supported channels. + */ + public static final Identifier REGISTER_CHANNEL = new Identifier("minecraft", "register"); + /** + * Id of packet used to unregister supported channels. + */ + public static final Identifier UNREGISTER_CHANNEL = new Identifier("minecraft", "unregister"); + /** + * Id of the packet used to declare all currently supported channels. + * Dynamic registration of supported channels is still allowed using {@link NetworkingImpl#REGISTER_CHANNEL} and {@link NetworkingImpl#UNREGISTER_CHANNEL}. + */ + public static final Identifier EARLY_REGISTRATION_CHANNEL = new Identifier(MOD_ID, "early_registration"); + + public static void init() { + // Login setup + ServerLoginConnectionEvents.QUERY_START.register((handler, server, sender, synchronizer) -> { + // Send early registration packet + PacketByteBuf buf = PacketByteBufs.create(); + Collection channelsNames = ServerPlayNetworking.getGlobalReceivers(); + buf.writeVarInt(channelsNames.size()); + + for (Identifier id : channelsNames) { + buf.writeIdentifier(id); + } + + sender.sendPacket(EARLY_REGISTRATION_CHANNEL, buf); + NetworkingImpl.LOGGER.debug("Sent accepted channels to the client for \"{}\"", handler.getConnectionInfo()); + }); + + ServerLoginNetworking.registerGlobalReceiver(EARLY_REGISTRATION_CHANNEL, (server, handler, understood, buf, synchronizer, sender) -> { + if (!understood) { + // The client is likely a vanilla client. + return; + } + + int n = buf.readVarInt(); + List ids = new ArrayList<>(n); + + for (int i = 0; i < n; i++) { + ids.add(buf.readIdentifier()); + } + + ((ChannelInfoHolder) handler.getConnection()).getPendingChannelsNames().addAll(ids); + NetworkingImpl.LOGGER.debug("Received accepted channels from the client for \"{}\"", handler.getConnectionInfo()); + }); + } + + public static boolean isReservedPlayChannel(Identifier channelName) { + return channelName.equals(REGISTER_CHANNEL) || channelName.equals(UNREGISTER_CHANNEL); + } +} diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/PacketCallbackListener.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/PacketCallbackListener.java new file mode 100644 index 000000000..421379838 --- /dev/null +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/PacketCallbackListener.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.impl.networking; + +import net.minecraft.network.Packet; + +public interface PacketCallbackListener { + /** + * Called after a packet has been sent. + * + * @param packet the packet + */ + void sent(Packet packet); +} diff --git a/fabric-networking-v0/src/main/java/net/fabricmc/fabric/impl/networking/server/EntityTrackerStorageAccessor.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/ThreadedAnvilChunkStorageTrackingExtensions.java similarity index 77% rename from fabric-networking-v0/src/main/java/net/fabricmc/fabric/impl/networking/server/EntityTrackerStorageAccessor.java rename to fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/ThreadedAnvilChunkStorageTrackingExtensions.java index 734ade823..216632bb2 100644 --- a/fabric-networking-v0/src/main/java/net/fabricmc/fabric/impl/networking/server/EntityTrackerStorageAccessor.java +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/ThreadedAnvilChunkStorageTrackingExtensions.java @@ -14,13 +14,13 @@ * limitations under the License. */ -package net.fabricmc.fabric.impl.networking.server; +package net.fabricmc.fabric.impl.networking; -import java.util.stream.Stream; +import java.util.Collection; import net.minecraft.entity.Entity; import net.minecraft.server.network.ServerPlayerEntity; -public interface EntityTrackerStorageAccessor { - Stream fabric_getTrackingPlayers(Entity entity); +public interface ThreadedAnvilChunkStorageTrackingExtensions { + Collection fabric_getTrackingPlayers(Entity entity); } diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/client/ClientLoginNetworkAddon.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/client/ClientLoginNetworkAddon.java new file mode 100644 index 000000000..bbf2aabc5 --- /dev/null +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/client/ClientLoginNetworkAddon.java @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.impl.networking.client; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +import io.netty.util.concurrent.Future; +import io.netty.util.concurrent.GenericFutureListener; +import org.jetbrains.annotations.Nullable; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.ClientLoginNetworkHandler; +import net.minecraft.util.PacketByteBuf; +import net.minecraft.client.network.packet.LoginQueryRequestS2CPacket; +import net.minecraft.server.network.packet.LoginQueryResponseC2SPacket; +import net.minecraft.util.Identifier; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.fabric.api.client.networking.v1.ClientLoginConnectionEvents; +import net.fabricmc.fabric.api.client.networking.v1.ClientLoginNetworking; +import net.fabricmc.fabric.api.networking.v1.FutureListeners; +import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; +import net.fabricmc.fabric.impl.networking.AbstractNetworkAddon; +import net.fabricmc.fabric.mixin.networking.accessor.LoginQueryRequestS2CPacketAccessor; + +@Environment(EnvType.CLIENT) +public final class ClientLoginNetworkAddon extends AbstractNetworkAddon { + private final ClientLoginNetworkHandler handler; + private final MinecraftClient client; + private boolean firstResponse = true; + + public ClientLoginNetworkAddon(ClientLoginNetworkHandler handler, MinecraftClient client) { + super(ClientNetworkingImpl.LOGIN, "ClientLoginNetworkAddon for Client"); + this.handler = handler; + this.client = client; + + ClientLoginConnectionEvents.INIT.invoker().onLoginStart(this.handler, this.client); + this.receiver.startSession(this); + } + + public boolean handlePacket(LoginQueryRequestS2CPacket packet) { + LoginQueryRequestS2CPacketAccessor access = (LoginQueryRequestS2CPacketAccessor) packet; + return handlePacket(packet.getQueryId(), access.getChannel(), access.getPayload()); + } + + private boolean handlePacket(int queryId, Identifier channelName, PacketByteBuf originalBuf) { + this.logger.debug("Handling inbound login response with id {} and channel with name {}", queryId, channelName); + + if (this.firstResponse) { + // Register global handlers + for (Map.Entry entry : ClientNetworkingImpl.LOGIN.getHandlers().entrySet()) { + ClientLoginNetworking.registerReceiver(entry.getKey(), entry.getValue()); + } + + ClientLoginConnectionEvents.QUERY_START.invoker().onLoginQueryStart(this.handler, this.client); + this.firstResponse = false; + } + + @Nullable ClientLoginNetworking.LoginQueryRequestHandler handler = this.getHandler(channelName); + + if (handler == null) { + return false; + } + + PacketByteBuf buf = PacketByteBufs.slice(originalBuf); + List>> futureListeners = new ArrayList<>(); + + try { + CompletableFuture<@Nullable PacketByteBuf> future = handler.receive(this.client, this.handler, buf, futureListeners::add); + future.thenAccept(result -> { + LoginQueryResponseC2SPacket packet = new LoginQueryResponseC2SPacket(queryId, result); + GenericFutureListener> listener = null; + + for (GenericFutureListener> each : futureListeners) { + listener = FutureListeners.union(listener, each); + } + + this.handler.getConnection().send(packet, listener); + }); + } catch (Throwable ex) { + this.logger.error("Encountered exception while handling in channel with name \"{}\"", channelName, ex); + throw ex; + } + + return true; + } + + @Override + protected void handleRegistration(Identifier channelName) { + } + + @Override + protected void handleUnregistration(Identifier channelName) { + } + + @Override + public void invokeDisconnectEvent() { + ClientLoginConnectionEvents.DISCONNECT.invoker().onLoginDisconnect(this.handler, this.client); + this.receiver.endSession(this); + } + + @Override + protected boolean isReservedChannel(Identifier channelName) { + return false; + } +} diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/client/ClientLoginNetworkHandlerExtensions.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/client/ClientLoginNetworkHandlerExtensions.java new file mode 100644 index 000000000..5530fc62f --- /dev/null +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/client/ClientLoginNetworkHandlerExtensions.java @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.impl.networking.client; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +@Environment(EnvType.CLIENT) +public interface ClientLoginNetworkHandlerExtensions { + ClientLoginNetworkAddon getAddon(); +} diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/client/ClientNetworkingImpl.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/client/ClientNetworkingImpl.java new file mode 100644 index 000000000..ab845eb05 --- /dev/null +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/client/ClientNetworkingImpl.java @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.impl.networking.client; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +import org.jetbrains.annotations.Nullable; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.screen.ConnectScreen; +import net.minecraft.client.network.ClientLoginNetworkHandler; +import net.minecraft.client.network.ClientPlayNetworkHandler; +import net.minecraft.network.ClientConnection; +import net.minecraft.network.Packet; +import net.minecraft.util.PacketByteBuf; +import net.minecraft.server.network.packet.CustomPayloadC2SPacket; +import net.minecraft.util.Identifier; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.fabric.api.client.networking.v1.ClientLoginNetworking; +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents; +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; +import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; +import net.fabricmc.fabric.impl.networking.ChannelInfoHolder; +import net.fabricmc.fabric.impl.networking.GlobalReceiverRegistry; +import net.fabricmc.fabric.impl.networking.NetworkingImpl; +import net.fabricmc.fabric.mixin.networking.accessor.ConnectScreenAccessor; +import net.fabricmc.fabric.mixin.networking.accessor.MinecraftClientAccessor; + +@Environment(EnvType.CLIENT) +public final class ClientNetworkingImpl { + public static final GlobalReceiverRegistry LOGIN = new GlobalReceiverRegistry<>(); + public static final GlobalReceiverRegistry PLAY = new GlobalReceiverRegistry<>(); + private static ClientPlayNetworkAddon currentPlayAddon; + + public static ClientPlayNetworkAddon getAddon(ClientPlayNetworkHandler handler) { + return ((ClientPlayNetworkHandlerExtensions) handler).getAddon(); + } + + public static ClientLoginNetworkAddon getAddon(ClientLoginNetworkHandler handler) { + return ((ClientLoginNetworkHandlerExtensions) handler).getAddon(); + } + + public static Packet createPlayC2SPacket(Identifier channelName, PacketByteBuf buf) { + return new CustomPayloadC2SPacket(channelName, buf); + } + + /** + * Due to the way logging into a integrated or remote dedicated server will differ, we need to obtain the login client connection differently. + */ + @Nullable + public static ClientConnection getLoginConnection() { + final ClientConnection connection = ((MinecraftClientAccessor) MinecraftClient.getInstance()).getConnection(); + + // Check if we are connecting to an integrated server. This will set the field on MinecraftClient + if (connection != null) { + return connection; + } else { + // We are probably connecting to a remote server. + // Check if the ConnectScreen is the currentScreen to determine that: + if (MinecraftClient.getInstance().currentScreen instanceof ConnectScreen) { + return ((ConnectScreenAccessor) MinecraftClient.getInstance().currentScreen).getConnection(); + } + } + + // We are not connected to a server at all. + return null; + } + + @Nullable + public static ClientPlayNetworkAddon getClientPlayAddon() { + // Since Minecraft can be a bit weird, we need to check for the play addon in a few ways: + // If the client's player is set this will work + if (MinecraftClient.getInstance().getNetworkHandler() != null) { + currentPlayAddon = null; // Shouldn't need this anymore + return ClientNetworkingImpl.getAddon(MinecraftClient.getInstance().getNetworkHandler()); + } + + // We haven't hit the end of onGameJoin yet, use our backing field here to access the network handler + if (currentPlayAddon != null) { + return currentPlayAddon; + } + + // We are not in play stage + return null; + } + + public static void setClientPlayAddon(ClientPlayNetworkAddon addon) { + currentPlayAddon = addon; + } + + public static void clientInit() { + // Reference cleanup for the locally stored addon if we are disconnected + ClientPlayConnectionEvents.DISCONNECT.register((handler, client) -> { + currentPlayAddon = null; + }); + + // Register a login query handler for early channel registration. + ClientLoginNetworking.registerGlobalReceiver(NetworkingImpl.EARLY_REGISTRATION_CHANNEL, (client, handler, buf, listenerAdder) -> { + int n = buf.readVarInt(); + List ids = new ArrayList<>(n); + + for (int i = 0; i < n; i++) { + ids.add(buf.readIdentifier()); + } + + ((ChannelInfoHolder) handler.getConnection()).getPendingChannelsNames().addAll(ids); + NetworkingImpl.LOGGER.debug("Received accepted channels from the server"); + + PacketByteBuf response = PacketByteBufs.create(); + Collection channels = ClientPlayNetworking.getGlobalReceivers(); + response.writeVarInt(channels.size()); + + for (Identifier id : channels) { + response.writeIdentifier(id); + } + + NetworkingImpl.LOGGER.debug("Sent accepted channels to the server"); + return CompletableFuture.completedFuture(response); + }); + } +} diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/client/ClientPlayNetworkAddon.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/client/ClientPlayNetworkAddon.java new file mode 100644 index 000000000..6930ecd83 --- /dev/null +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/client/ClientPlayNetworkAddon.java @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.impl.networking.client; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.ClientPlayNetworkHandler; +import net.minecraft.network.Packet; +import net.minecraft.util.PacketByteBuf; +import net.minecraft.client.network.packet.CustomPayloadS2CPacket; +import net.minecraft.util.Identifier; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.fabric.api.client.networking.v1.C2SPlayChannelEvents; +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents; +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; +import net.fabricmc.fabric.impl.networking.AbstractChanneledNetworkAddon; +import net.fabricmc.fabric.impl.networking.ChannelInfoHolder; +import net.fabricmc.fabric.impl.networking.NetworkingImpl; + +@Environment(EnvType.CLIENT) +public final class ClientPlayNetworkAddon extends AbstractChanneledNetworkAddon { + private final ClientPlayNetworkHandler handler; + private final MinecraftClient client; + private boolean sentInitialRegisterPacket; + + public ClientPlayNetworkAddon(ClientPlayNetworkHandler handler, MinecraftClient client) { + super(ClientNetworkingImpl.PLAY, handler.getConnection(), "ClientPlayNetworkAddon for " + handler.getProfile().getName()); + this.handler = handler; + this.client = client; + + // Must register pending channels via lateinit + this.registerPendingChannels((ChannelInfoHolder) this.connection); + + // Register global receivers and attach to session + this.receiver.startSession(this); + } + + @Override + public void lateInit() { + for (Map.Entry entry : this.receiver.getHandlers().entrySet()) { + this.registerChannel(entry.getKey(), entry.getValue()); + } + + ClientPlayConnectionEvents.INIT.invoker().onPlayInit(this.handler, this.client); + } + + public void onServerReady() { + ClientPlayConnectionEvents.JOIN.invoker().onPlayReady(this.handler, this, this.client); + + // The client cannot send any packets, including `minecraft:register` until after GameJoinS2CPacket is received. + this.sendInitialChannelRegistrationPacket(); + this.sentInitialRegisterPacket = true; + } + + /** + * Handles an incoming packet. + * + * @param packet the packet to handle + * @return true if the packet has been handled + */ + public boolean handle(CustomPayloadS2CPacket packet) { + // Do not handle the packet on game thread + if (this.client.isOnThread()) { + return false; + } + + PacketByteBuf buf = packet.getData(); + + try { + return this.handle(packet.getChannel(), buf); + } finally { + buf.release(); + } + } + + @Override + protected void receive(ClientPlayNetworking.PlayChannelHandler handler, PacketByteBuf buf) { + handler.receive(this.client, this.handler, buf, this); + } + + // impl details + + @Override + protected void schedule(Runnable task) { + MinecraftClient.getInstance().execute(task); + } + + @Override + public Packet createPacket(Identifier channelName, PacketByteBuf buf) { + return ClientPlayNetworking.createC2SPacket(channelName, buf); + } + + @Override + protected void invokeRegisterEvent(List ids) { + C2SPlayChannelEvents.REGISTER.invoker().onChannelRegister(this.handler, this, this.client, ids); + } + + @Override + protected void invokeUnregisterEvent(List ids) { + C2SPlayChannelEvents.UNREGISTER.invoker().onChannelUnregister(this.handler, this, this.client, ids); + } + + @Override + protected void handleRegistration(Identifier channelName) { + // If we can already send packets, immediately send the register packet for this channel + if (this.sentInitialRegisterPacket) { + final PacketByteBuf buf = this.createRegistrationPacket(Collections.singleton(channelName)); + + if (buf != null) { + this.sendPacket(NetworkingImpl.REGISTER_CHANNEL, buf); + } + } + } + + @Override + protected void handleUnregistration(Identifier channelName) { + // If we can already send packets, immediately send the unregister packet for this channel + if (this.sentInitialRegisterPacket) { + final PacketByteBuf buf = this.createRegistrationPacket(Collections.singleton(channelName)); + + if (buf != null) { + this.sendPacket(NetworkingImpl.UNREGISTER_CHANNEL, buf); + } + } + } + + @Override + public void invokeDisconnectEvent() { + ClientPlayConnectionEvents.DISCONNECT.invoker().onPlayDisconnect(this.handler, this.client); + this.receiver.endSession(this); + } + + @Override + protected boolean isReservedChannel(Identifier channelName) { + return NetworkingImpl.isReservedPlayChannel(channelName); + } +} diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/client/ClientPlayNetworkHandlerExtensions.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/client/ClientPlayNetworkHandlerExtensions.java new file mode 100644 index 000000000..75698d33c --- /dev/null +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/client/ClientPlayNetworkHandlerExtensions.java @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.impl.networking.client; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +@Environment(EnvType.CLIENT) +public interface ClientPlayNetworkHandlerExtensions { + ClientPlayNetworkAddon getAddon(); +} diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/server/QueryIdFactory.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/server/QueryIdFactory.java new file mode 100644 index 000000000..bf1e1bb2f --- /dev/null +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/server/QueryIdFactory.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.impl.networking.server; + +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Tracks the current query id used for login query responses. + */ +interface QueryIdFactory { + static QueryIdFactory create() { + return new QueryIdFactory() { + private final AtomicInteger currentId = new AtomicInteger(); + + @Override + public int nextId() { + return this.currentId.getAndIncrement(); + } + }; + } + + // called async prob. + int nextId(); +} diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/server/ServerLoginNetworkAddon.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/server/ServerLoginNetworkAddon.java new file mode 100644 index 000000000..8a7b48396 --- /dev/null +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/server/ServerLoginNetworkAddon.java @@ -0,0 +1,211 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.impl.networking.server; + +import java.util.Collection; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicReference; + +import io.netty.util.concurrent.GenericFutureListener; +import org.jetbrains.annotations.Nullable; + +import net.minecraft.network.ClientConnection; +import net.minecraft.network.Packet; +import net.minecraft.util.PacketByteBuf; +import net.minecraft.server.network.packet.LoginQueryResponseC2SPacket; +import net.minecraft.client.network.packet.LoginCompressionS2CPacket; +import net.minecraft.client.network.packet.LoginQueryRequestS2CPacket; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.network.ServerLoginNetworkHandler; +import net.minecraft.util.Identifier; + +import net.fabricmc.fabric.api.networking.v1.PacketSender; +import net.fabricmc.fabric.api.networking.v1.ServerLoginConnectionEvents; +import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; +import net.fabricmc.fabric.api.networking.v1.ServerLoginNetworking; +import net.fabricmc.fabric.impl.networking.AbstractNetworkAddon; +import net.fabricmc.fabric.mixin.networking.accessor.LoginQueryRequestS2CPacketAccessor; +import net.fabricmc.fabric.mixin.networking.accessor.LoginQueryResponseC2SPacketAccessor; +import net.fabricmc.fabric.mixin.networking.accessor.ServerLoginNetworkHandlerAccessor; + +public final class ServerLoginNetworkAddon extends AbstractNetworkAddon implements PacketSender { + private final ClientConnection connection; + private final ServerLoginNetworkHandler handler; + private final MinecraftServer server; + private final QueryIdFactory queryIdFactory; + private final Collection> waits = new ConcurrentLinkedQueue<>(); + private final Map channels = new ConcurrentHashMap<>(); + private boolean firstQueryTick = true; + + public ServerLoginNetworkAddon(ServerLoginNetworkHandler handler) { + super(ServerNetworkingImpl.LOGIN, "ServerLoginNetworkAddon for " + handler.getConnectionInfo()); + this.connection = handler.client; + this.handler = handler; + this.server = ((ServerLoginNetworkHandlerAccessor) handler).getServer(); + this.queryIdFactory = QueryIdFactory.create(); + + ServerLoginConnectionEvents.INIT.invoker().onLoginInit(handler, this.server); + this.receiver.startSession(this); + } + + // return true if no longer ticks query + public boolean queryTick() { + if (this.firstQueryTick) { + // Send the compression packet now so clients receive compressed login queries + this.sendCompressionPacket(); + + // Register global receivers. + for (Map.Entry entry : ServerNetworkingImpl.LOGIN.getHandlers().entrySet()) { + ServerLoginNetworking.registerReceiver(this.handler, entry.getKey(), entry.getValue()); + } + + ServerLoginConnectionEvents.QUERY_START.invoker().onLoginStart(this.handler, this.server, this, this.waits::add); + this.firstQueryTick = false; + } + + AtomicReference error = new AtomicReference<>(); + this.waits.removeIf(future -> { + if (!future.isDone()) { + return false; + } + + try { + future.get(); + } catch (ExecutionException ex) { + Throwable caught = ex.getCause(); + error.getAndUpdate(oldEx -> { + if (oldEx == null) { + return caught; + } + + oldEx.addSuppressed(caught); + return oldEx; + }); + } catch (InterruptedException | CancellationException ignored) { + // ignore + } + + return true; + }); + + return this.channels.isEmpty() && this.waits.isEmpty(); + } + + private void sendCompressionPacket() { + // Compression is not needed for local transport + if (this.server.getNetworkCompressionThreshold() >= 0 && !this.connection.isLocal()) { + this.connection.send(new LoginCompressionS2CPacket(this.server.getNetworkCompressionThreshold()), (channelFuture) -> + this.connection.setMinCompressedSize(this.server.getNetworkCompressionThreshold()) + ); + } + } + + /** + * Handles an incoming query response during login. + * + * @param packet the packet to handle + * @return true if the packet was handled + */ + public boolean handle(LoginQueryResponseC2SPacket packet) { + LoginQueryResponseC2SPacketAccessor access = (LoginQueryResponseC2SPacketAccessor) packet; + return handle(access.getQueryId(), access.getResponse()); + } + + private boolean handle(int queryId, @Nullable PacketByteBuf originalBuf) { + this.logger.debug("Handling inbound login query with id {}", queryId); + Identifier channel = this.channels.remove(queryId); + + if (channel == null) { + this.logger.warn("Query ID {} was received but no query has been associated in {}!", queryId, this.connection); + return false; + } + + boolean understood = originalBuf != null; + @Nullable ServerLoginNetworking.LoginQueryResponseHandler handler = ServerNetworkingImpl.LOGIN.getHandler(channel); + + if (handler == null) { + return false; + } + + PacketByteBuf buf = understood ? PacketByteBufs.slice(originalBuf) : PacketByteBufs.empty(); + + try { + handler.receive(this.server, this.handler, understood, buf, this.waits::add, this); + } catch (Throwable ex) { + this.logger.error("Encountered exception while handling in channel \"{}\"", channel, ex); + throw ex; + } + + return true; + } + + @Override + public Packet createPacket(Identifier channelName, PacketByteBuf buf) { + int queryId = this.queryIdFactory.nextId(); + LoginQueryRequestS2CPacket ret = new LoginQueryRequestS2CPacket(); + // The constructor for creating a non-empty response was removed by proguard + LoginQueryRequestS2CPacketAccessor access = (LoginQueryRequestS2CPacketAccessor) ret; + access.setQueryId(queryId); + access.setChannel(channelName); + access.setPayload(buf); + return ret; + } + + @Override + public void sendPacket(Packet packet) { + Objects.requireNonNull(packet, "Packet cannot be null"); + + this.connection.send(packet); + } + + @Override + public void sendPacket(Packet packet, GenericFutureListener> callback) { + Objects.requireNonNull(packet, "Packet cannot be null"); + + this.connection.send(packet, callback); + } + + public void registerOutgoingPacket(LoginQueryRequestS2CPacket packet) { + LoginQueryRequestS2CPacketAccessor access = (LoginQueryRequestS2CPacketAccessor) packet; + this.channels.put(access.getServerQueryId(), access.getChannel()); + } + + @Override + protected void handleRegistration(Identifier channelName) { + } + + @Override + protected void handleUnregistration(Identifier channelName) { + } + + @Override + public void invokeDisconnectEvent() { + ServerLoginConnectionEvents.DISCONNECT.invoker().onLoginDisconnect(this.handler, this.server); + this.receiver.endSession(this); + } + + @Override + protected boolean isReservedChannel(Identifier channelName) { + return false; + } +} diff --git a/fabric-networking-v0/src/main/java/net/fabricmc/fabric/impl/networking/server/EntityTrackerStreamAccessor.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/server/ServerLoginNetworkHandlerExtensions.java similarity index 77% rename from fabric-networking-v0/src/main/java/net/fabricmc/fabric/impl/networking/server/EntityTrackerStreamAccessor.java rename to fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/server/ServerLoginNetworkHandlerExtensions.java index 672d1096e..86cffcdbd 100644 --- a/fabric-networking-v0/src/main/java/net/fabricmc/fabric/impl/networking/server/EntityTrackerStreamAccessor.java +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/server/ServerLoginNetworkHandlerExtensions.java @@ -16,10 +16,6 @@ package net.fabricmc.fabric.impl.networking.server; -import java.util.stream.Stream; - -import net.minecraft.server.network.ServerPlayerEntity; - -public interface EntityTrackerStreamAccessor { - Stream fabric_getTrackingPlayers(); +public interface ServerLoginNetworkHandlerExtensions { + ServerLoginNetworkAddon getAddon(); } diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/server/ServerNetworkingImpl.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/server/ServerNetworkingImpl.java new file mode 100644 index 000000000..f46b936bd --- /dev/null +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/server/ServerNetworkingImpl.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.impl.networking.server; + +import net.minecraft.network.Packet; +import net.minecraft.util.PacketByteBuf; +import net.minecraft.client.network.packet.CustomPayloadS2CPacket; +import net.minecraft.server.network.ServerPlayNetworkHandler; +import net.minecraft.util.Identifier; + +import net.fabricmc.fabric.api.networking.v1.ServerLoginNetworking; +import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; +import net.fabricmc.fabric.impl.networking.GlobalReceiverRegistry; + +public final class ServerNetworkingImpl { + public static final GlobalReceiverRegistry LOGIN = new GlobalReceiverRegistry<>(); + public static final GlobalReceiverRegistry PLAY = new GlobalReceiverRegistry<>(); + + public static ServerPlayNetworkAddon getAddon(ServerPlayNetworkHandler handler) { + return ((ServerPlayNetworkHandlerExtensions) handler).getAddon(); + } + + public static Packet createPlayC2SPacket(Identifier channel, PacketByteBuf buf) { + return new CustomPayloadS2CPacket(channel, buf); + } +} diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/server/ServerPlayNetworkAddon.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/server/ServerPlayNetworkAddon.java new file mode 100644 index 000000000..9d3b98729 --- /dev/null +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/server/ServerPlayNetworkAddon.java @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.impl.networking.server; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import net.minecraft.network.Packet; +import net.minecraft.util.PacketByteBuf; +import net.minecraft.server.network.packet.CustomPayloadC2SPacket; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.network.ServerPlayNetworkHandler; +import net.minecraft.util.Identifier; + +import net.fabricmc.fabric.api.networking.v1.S2CPlayChannelEvents; +import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents; +import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; +import net.fabricmc.fabric.impl.networking.AbstractChanneledNetworkAddon; +import net.fabricmc.fabric.impl.networking.ChannelInfoHolder; +import net.fabricmc.fabric.impl.networking.NetworkingImpl; +import net.fabricmc.fabric.mixin.networking.accessor.CustomPayloadC2SPacketAccessor; + +public final class ServerPlayNetworkAddon extends AbstractChanneledNetworkAddon { + private final ServerPlayNetworkHandler handler; + private final MinecraftServer server; + private boolean sentInitialRegisterPacket; + + public ServerPlayNetworkAddon(ServerPlayNetworkHandler handler, MinecraftServer server) { + super(ServerNetworkingImpl.PLAY, handler.getConnection(), "ServerPlayNetworkAddon for " + handler.player.getEntityName()); + this.handler = handler; + this.server = server; + + // Must register pending channels via lateinit + this.registerPendingChannels((ChannelInfoHolder) this.connection); + + // Register global receivers and attach to session + this.receiver.startSession(this); + } + + @Override + public void lateInit() { + for (Map.Entry entry : this.receiver.getHandlers().entrySet()) { + this.registerChannel(entry.getKey(), entry.getValue()); + } + + ServerPlayConnectionEvents.INIT.invoker().onPlayInit(this.handler, this.server); + } + + public void onClientReady() { + ServerPlayConnectionEvents.JOIN.invoker().onPlayReady(this.handler, this, this.server); + + this.sendInitialChannelRegistrationPacket(); + this.sentInitialRegisterPacket = true; + } + + /** + * Handles an incoming packet. + * + * @param packet the packet to handle + * @return true if the packet has been handled + */ + public boolean handle(CustomPayloadC2SPacket packet) { + // Do not handle the packet on game thread + if (this.server.isOnThread()) { + return false; + } + + CustomPayloadC2SPacketAccessor access = (CustomPayloadC2SPacketAccessor) packet; + return this.handle(access.getChannel(), access.getData()); + } + + @Override + protected void receive(ServerPlayNetworking.PlayChannelHandler handler, PacketByteBuf buf) { + handler.receive(this.server, this.handler.player, this.handler, buf, this); + } + + // impl details + + @Override + protected void schedule(Runnable task) { + this.handler.player.server.execute(task); + } + + @Override + public Packet createPacket(Identifier channelName, PacketByteBuf buf) { + return ServerPlayNetworking.createS2CPacket(channelName, buf); + } + + @Override + protected void invokeRegisterEvent(List ids) { + S2CPlayChannelEvents.REGISTER.invoker().onChannelRegister(this.handler, this, this.server, ids); + } + + @Override + protected void invokeUnregisterEvent(List ids) { + S2CPlayChannelEvents.UNREGISTER.invoker().onChannelUnregister(this.handler, this, this.server, ids); + } + + @Override + protected void handleRegistration(Identifier channelName) { + // If we can already send packets, immediately send the register packet for this channel + if (this.sentInitialRegisterPacket) { + final PacketByteBuf buf = this.createRegistrationPacket(Collections.singleton(channelName)); + + if (buf != null) { + this.sendPacket(NetworkingImpl.REGISTER_CHANNEL, buf); + } + } + } + + @Override + protected void handleUnregistration(Identifier channelName) { + // If we can already send packets, immediately send the unregister packet for this channel + if (this.sentInitialRegisterPacket) { + final PacketByteBuf buf = this.createRegistrationPacket(Collections.singleton(channelName)); + + if (buf != null) { + this.sendPacket(NetworkingImpl.UNREGISTER_CHANNEL, buf); + } + } + } + + @Override + public void invokeDisconnectEvent() { + ServerPlayConnectionEvents.DISCONNECT.invoker().onPlayDisconnect(this.handler, this.server); + this.receiver.endSession(this); + } + + @Override + protected boolean isReservedChannel(Identifier channelName) { + return NetworkingImpl.isReservedPlayChannel(channelName); + } +} diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/server/ServerPlayNetworkHandlerExtensions.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/server/ServerPlayNetworkHandlerExtensions.java new file mode 100644 index 000000000..b06729e47 --- /dev/null +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/server/ServerPlayNetworkHandlerExtensions.java @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.impl.networking.server; + +public interface ServerPlayNetworkHandlerExtensions { + ServerPlayNetworkAddon getAddon(); +} diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/ClientConnectionMixin.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/ClientConnectionMixin.java new file mode 100644 index 000000000..049adb70d --- /dev/null +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/ClientConnectionMixin.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.mixin.networking; + +import java.util.Collection; +import java.util.Collections; +import java.util.concurrent.ConcurrentHashMap; + +import io.netty.util.concurrent.Future; +import io.netty.util.concurrent.GenericFutureListener; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.Redirect; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import net.minecraft.network.ClientConnection; +import net.minecraft.network.NetworkSide; +import net.minecraft.network.Packet; +import net.minecraft.network.listener.PacketListener; +import net.minecraft.text.Text; +import net.minecraft.text.TranslatableText; +import net.minecraft.util.Identifier; + +import net.fabricmc.fabric.impl.networking.ChannelInfoHolder; +import net.fabricmc.fabric.impl.networking.DisconnectPacketSource; +import net.fabricmc.fabric.impl.networking.PacketCallbackListener; + +@Mixin(ClientConnection.class) +abstract class ClientConnectionMixin implements ChannelInfoHolder { + @Shadow + private PacketListener packetListener; + + @Shadow + public abstract void send(Packet packet, GenericFutureListener> callback); + + @Shadow + public abstract void disconnect(Text disconnectReason); + + @Unique + private Collection playChannels; + + @Inject(method = "", at = @At("RETURN")) + private void initAddedFields(NetworkSide side, CallbackInfo ci) { + this.playChannels = Collections.newSetFromMap(new ConcurrentHashMap<>()); + } + + // Must be fully qualified due to mixin not working in production without it + @SuppressWarnings("UnnecessaryQualifiedMemberReference") + @Redirect(method = "Lnet/minecraft/network/ClientConnection;exceptionCaught(Lio/netty/channel/ChannelHandlerContext;Ljava/lang/Throwable;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/network/ClientConnection;send(Lnet/minecraft/network/Packet;Lio/netty/util/concurrent/GenericFutureListener;)V")) + private void resendOnExceptionCaught(ClientConnection self, Packet packet, GenericFutureListener> listener) { + PacketListener handler = this.packetListener; + + if (handler instanceof DisconnectPacketSource) { + this.send(((DisconnectPacketSource) handler).createDisconnectPacket(new TranslatableText("disconnect.genericReason")), listener); + } else { + this.disconnect(new TranslatableText("disconnect.genericReason")); // Don't send packet if we cannot send proper packets + } + } + + @Inject(method = "sendImmediately", at = @At(value = "FIELD", target = "Lnet/minecraft/network/ClientConnection;packetsSentCounter:I")) + private void checkPacket(Packet packet, GenericFutureListener> callback, CallbackInfo ci) { + if (this.packetListener instanceof PacketCallbackListener) { + ((PacketCallbackListener) this.packetListener).sent(packet); + } + } + + @Override + public Collection getPendingChannelsNames() { + return this.playChannels; + } +} diff --git a/fabric-networking-v0/src/main/java/net/fabricmc/fabric/mixin/networking/MixinEntityTracker.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/EntityTrackerEntryMixin.java similarity index 50% rename from fabric-networking-v0/src/main/java/net/fabricmc/fabric/mixin/networking/MixinEntityTracker.java rename to fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/EntityTrackerEntryMixin.java index bb8cba55d..7e908734e 100644 --- a/fabric-networking-v0/src/main/java/net/fabricmc/fabric/mixin/networking/MixinEntityTracker.java +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/EntityTrackerEntryMixin.java @@ -16,25 +16,32 @@ package net.fabricmc.fabric.mixin.networking; -import java.util.Set; -import java.util.stream.Stream; - import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import net.minecraft.entity.Entity; +import net.minecraft.server.network.EntityTrackerEntry; import net.minecraft.server.network.ServerPlayerEntity; -import net.fabricmc.fabric.impl.networking.server.EntityTrackerStreamAccessor; +import net.fabricmc.fabric.api.networking.v1.EntityTrackingEvents; -@Mixin(targets = "net.minecraft.server.world.ThreadedAnvilChunkStorage$EntityTracker") -public class MixinEntityTracker implements EntityTrackerStreamAccessor { +@Mixin(EntityTrackerEntry.class) +abstract class EntityTrackerEntryMixin { @Shadow @Final - private Set playersTracking; + private Entity entity; - @Override - public Stream fabric_getTrackingPlayers() { - return playersTracking.stream(); + @Inject(method = "startTracking", at = @At("HEAD")) + private void onStartTracking(ServerPlayerEntity player, CallbackInfo ci) { + EntityTrackingEvents.START_TRACKING.invoker().onStartTracking(this.entity, player); + } + + @Inject(method = "stopTracking", at = @At("TAIL")) + private void onStopTracking(ServerPlayerEntity player, CallbackInfo ci) { + EntityTrackingEvents.STOP_TRACKING.invoker().onStopTracking(this.entity, player); } } diff --git a/fabric-networking-v0/src/main/java/net/fabricmc/fabric/mixin/networking/MixinMinecraftClient.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/PlayerManagerMixin.java similarity index 56% rename from fabric-networking-v0/src/main/java/net/fabricmc/fabric/mixin/networking/MixinMinecraftClient.java rename to fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/PlayerManagerMixin.java index 071c0e0fe..b6f9d8282 100644 --- a/fabric-networking-v0/src/main/java/net/fabricmc/fabric/mixin/networking/MixinMinecraftClient.java +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/PlayerManagerMixin.java @@ -21,15 +21,16 @@ import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.gui.screen.Screen; +import net.minecraft.network.ClientConnection; +import net.minecraft.server.PlayerManager; +import net.minecraft.server.network.ServerPlayerEntity; -import net.fabricmc.fabric.impl.networking.ClientSidePacketRegistryImpl; +import net.fabricmc.fabric.impl.networking.server.ServerNetworkingImpl; -@Mixin(MinecraftClient.class) -public class MixinMinecraftClient { - @Inject(at = @At("RETURN"), method = "disconnect(Lnet/minecraft/client/gui/screen/Screen;)V") - public void disconnectAfter(Screen screen_1, CallbackInfo info) { - ClientSidePacketRegistryImpl.invalidateRegisteredIdList(); +@Mixin(PlayerManager.class) +abstract class PlayerManagerMixin { + @Inject(method = "onPlayerConnect", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/network/packet/CustomPayloadS2CPacket;(Lnet/minecraft/util/Identifier;Lnet/minecraft/util/PacketByteBuf;)V")) + private void handlePlayerConnection(ClientConnection connection, ServerPlayerEntity player, CallbackInfo ci) { + ServerNetworkingImpl.getAddon(player.networkHandler).onClientReady(); } } diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/ServerLoginNetworkHandlerMixin.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/ServerLoginNetworkHandlerMixin.java new file mode 100644 index 000000000..99a0cfdcd --- /dev/null +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/ServerLoginNetworkHandlerMixin.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.mixin.networking; + +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.Redirect; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import net.minecraft.network.Packet; +import net.minecraft.server.network.packet.LoginQueryResponseC2SPacket; +import net.minecraft.client.network.packet.LoginDisconnectS2CPacket; +import net.minecraft.client.network.packet.LoginQueryRequestS2CPacket; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.network.ServerLoginNetworkHandler; +import net.minecraft.text.Text; + +import net.fabricmc.fabric.impl.networking.DisconnectPacketSource; +import net.fabricmc.fabric.impl.networking.PacketCallbackListener; +import net.fabricmc.fabric.impl.networking.server.ServerLoginNetworkAddon; +import net.fabricmc.fabric.impl.networking.server.ServerLoginNetworkHandlerExtensions; + +@Mixin(ServerLoginNetworkHandler.class) +abstract class ServerLoginNetworkHandlerMixin implements ServerLoginNetworkHandlerExtensions, DisconnectPacketSource, PacketCallbackListener { + @Shadow + @Final + private MinecraftServer server; + + @Shadow + public abstract void acceptPlayer(); + + @Unique + private ServerLoginNetworkAddon addon; + + @Inject(method = "", at = @At("RETURN")) + private void initAddon(CallbackInfo ci) { + this.addon = new ServerLoginNetworkAddon((ServerLoginNetworkHandler) (Object) this); + } + + @Redirect(method = "tick", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/network/ServerLoginNetworkHandler;acceptPlayer()V")) + private void handlePlayerJoin(ServerLoginNetworkHandler handler) { + // Do not accept the player, thereby moving into play stage until all login futures being waited on are completed + if (this.addon.queryTick()) { + this.acceptPlayer(); + } + } + + @Inject(method = "onQueryResponse", at = @At("HEAD"), cancellable = true) + private void handleCustomPayloadReceivedAsync(LoginQueryResponseC2SPacket packet, CallbackInfo ci) { + // Handle queries + if (this.addon.handle(packet)) { + ci.cancel(); + } + } + + @Redirect(method = "acceptPlayer", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/MinecraftServer;getNetworkCompressionThreshold()I", ordinal = 0)) + private int removeLateCompressionPacketSending(MinecraftServer server) { + return -1; + } + + @Inject(method = "onDisconnected", at = @At("HEAD")) + private void handleDisconnection(Text reason, CallbackInfo ci) { + this.addon.invokeDisconnectEvent(); + } + + @Override + public void sent(Packet packet) { + if (packet instanceof LoginQueryRequestS2CPacket) { + this.addon.registerOutgoingPacket((LoginQueryRequestS2CPacket) packet); + } + } + + @Override + public ServerLoginNetworkAddon getAddon() { + return this.addon; + } + + @Override + public Packet createDisconnectPacket(Text message) { + return new LoginDisconnectS2CPacket(message); + } +} diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/ServerPlayNetworkHandlerMixin.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/ServerPlayNetworkHandlerMixin.java new file mode 100644 index 000000000..6f3bd1818 --- /dev/null +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/ServerPlayNetworkHandlerMixin.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.mixin.networking; + +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import net.minecraft.network.ClientConnection; +import net.minecraft.network.Packet; +import net.minecraft.server.network.packet.CustomPayloadC2SPacket; +import net.minecraft.client.network.packet.DisconnectS2CPacket; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.network.ServerPlayNetworkHandler; +import net.minecraft.text.Text; + +import net.fabricmc.fabric.impl.networking.DisconnectPacketSource; +import net.fabricmc.fabric.impl.networking.server.ServerPlayNetworkAddon; +import net.fabricmc.fabric.impl.networking.server.ServerPlayNetworkHandlerExtensions; + +// We want to apply a bit earlier than other mods which may not use us in order to prevent refCount issues +@Mixin(value = ServerPlayNetworkHandler.class, priority = 999) +abstract class ServerPlayNetworkHandlerMixin implements ServerPlayNetworkHandlerExtensions, DisconnectPacketSource { + @Shadow + @Final + private MinecraftServer server; + @Shadow + @Final + public ClientConnection client; + + @Unique + private ServerPlayNetworkAddon addon; + + @Inject(method = "", at = @At("RETURN")) + private void initAddon(CallbackInfo ci) { + this.addon = new ServerPlayNetworkAddon((ServerPlayNetworkHandler) (Object) this, this.server); + // A bit of a hack but it allows the field above to be set in case someone registers handlers during INIT event which refers to said field + this.addon.lateInit(); + } + + @Inject(method = "onCustomPayload", at = @At("HEAD"), cancellable = true) + private void handleCustomPayloadReceivedAsync(CustomPayloadC2SPacket packet, CallbackInfo ci) { + if (this.addon.handle(packet)) { + ci.cancel(); + } + } + + @Inject(method = "onDisconnected", at = @At("HEAD")) + private void handleDisconnection(Text reason, CallbackInfo ci) { + this.addon.invokeDisconnectEvent(); + } + + @Override + public ServerPlayNetworkAddon getAddon() { + return this.addon; + } + + @Override + public Packet createDisconnectPacket(Text message) { + return new DisconnectS2CPacket(message); + } +} diff --git a/fabric-networking-v0/src/main/java/net/fabricmc/fabric/mixin/networking/MixinThreadedAnvilChunkStorage.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/ThreadedAnvilChunkStorageMixin.java similarity index 56% rename from fabric-networking-v0/src/main/java/net/fabricmc/fabric/mixin/networking/MixinThreadedAnvilChunkStorage.java rename to fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/ThreadedAnvilChunkStorageMixin.java index 218c018f4..0c80a5de6 100644 --- a/fabric-networking-v0/src/main/java/net/fabricmc/fabric/mixin/networking/MixinThreadedAnvilChunkStorage.java +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/ThreadedAnvilChunkStorageMixin.java @@ -16,7 +16,8 @@ package net.fabricmc.fabric.mixin.networking; -import java.util.stream.Stream; +import java.util.Collection; +import java.util.Collections; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import org.spongepowered.asm.mixin.Final; @@ -27,18 +28,25 @@ import net.minecraft.entity.Entity; import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.server.world.ThreadedAnvilChunkStorage; -import net.fabricmc.fabric.impl.networking.server.EntityTrackerStorageAccessor; -import net.fabricmc.fabric.impl.networking.server.EntityTrackerStreamAccessor; +import net.fabricmc.fabric.impl.networking.ThreadedAnvilChunkStorageTrackingExtensions; +import net.fabricmc.fabric.mixin.networking.accessor.EntityTrackerAccessor; @Mixin(ThreadedAnvilChunkStorage.class) -public class MixinThreadedAnvilChunkStorage implements EntityTrackerStorageAccessor { +abstract class ThreadedAnvilChunkStorageMixin implements ThreadedAnvilChunkStorageTrackingExtensions { @Shadow @Final - private Int2ObjectMap entityTrackers; + // We can abuse type erasure here and just get the type in the map as the accessor. + // This allows us to avoid an access widener for the package-private `EntityTracker` subclass. + private Int2ObjectMap entityTrackers; @Override - public Stream fabric_getTrackingPlayers(Entity entity) { - EntityTrackerStreamAccessor accessor = entityTrackers.get(entity.getEntityId()); - return accessor != null ? accessor.fabric_getTrackingPlayers() : Stream.empty(); + public Collection fabric_getTrackingPlayers(Entity entity) { + EntityTrackerAccessor accessor = this.entityTrackers.get(entity.getEntityId()); + + if (accessor != null) { + return accessor.getPlayersTracking(); + } + + return Collections.emptySet(); } } diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/accessor/ConnectScreenAccessor.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/accessor/ConnectScreenAccessor.java new file mode 100644 index 000000000..89b08e0e5 --- /dev/null +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/accessor/ConnectScreenAccessor.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.mixin.networking.accessor; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +import net.minecraft.client.gui.screen.ConnectScreen; +import net.minecraft.network.ClientConnection; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +@Environment(EnvType.CLIENT) +@Mixin(ConnectScreen.class) +public interface ConnectScreenAccessor { + @Accessor + ClientConnection getConnection(); +} diff --git a/fabric-networking-v0/src/main/java/net/fabricmc/fabric/impl/networking/CustomPayloadC2SPacketAccessor.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/accessor/CustomPayloadC2SPacketAccessor.java similarity index 74% rename from fabric-networking-v0/src/main/java/net/fabricmc/fabric/impl/networking/CustomPayloadC2SPacketAccessor.java rename to fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/accessor/CustomPayloadC2SPacketAccessor.java index 33013a6bc..7285fd757 100644 --- a/fabric-networking-v0/src/main/java/net/fabricmc/fabric/impl/networking/CustomPayloadC2SPacketAccessor.java +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/accessor/CustomPayloadC2SPacketAccessor.java @@ -14,17 +14,20 @@ * limitations under the License. */ -package net.fabricmc.fabric.impl.networking; +package net.fabricmc.fabric.mixin.networking.accessor; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; -import net.minecraft.util.Identifier; import net.minecraft.util.PacketByteBuf; +import net.minecraft.server.network.packet.CustomPayloadC2SPacket; +import net.minecraft.util.Identifier; -/** - * Helper interface containing getters for CustomPayloadC2SPacket - * which were omitted from the compiled game. - */ +@Mixin(CustomPayloadC2SPacket.class) public interface CustomPayloadC2SPacketAccessor { + @Accessor Identifier getChannel(); + @Accessor PacketByteBuf getData(); } diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/accessor/EntityTrackerAccessor.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/accessor/EntityTrackerAccessor.java new file mode 100644 index 000000000..4a3b174e2 --- /dev/null +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/accessor/EntityTrackerAccessor.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.mixin.networking.accessor; + +import java.util.Set; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +import net.minecraft.server.network.ServerPlayerEntity; + +@Mixin(targets = "net/minecraft/server/world/ThreadedAnvilChunkStorage$EntityTracker") +public interface EntityTrackerAccessor { + @Accessor + Set getPlayersTracking(); +} diff --git a/fabric-networking-v0/src/main/java/net/fabricmc/fabric/mixin/networking/MixinCustomPayloadC2SPacket.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/accessor/LoginQueryRequestS2CPacketAccessor.java similarity index 55% rename from fabric-networking-v0/src/main/java/net/fabricmc/fabric/mixin/networking/MixinCustomPayloadC2SPacket.java rename to fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/accessor/LoginQueryRequestS2CPacketAccessor.java index aced22a15..37dbe8c68 100644 --- a/fabric-networking-v0/src/main/java/net/fabricmc/fabric/mixin/networking/MixinCustomPayloadC2SPacket.java +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/accessor/LoginQueryRequestS2CPacketAccessor.java @@ -14,31 +14,32 @@ * limitations under the License. */ -package net.fabricmc.fabric.mixin.networking; +package net.fabricmc.fabric.mixin.networking.accessor; import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.gen.Accessor; -import net.minecraft.server.network.packet.CustomPayloadC2SPacket; -import net.minecraft.util.Identifier; import net.minecraft.util.PacketByteBuf; +import net.minecraft.client.network.packet.LoginQueryRequestS2CPacket; +import net.minecraft.util.Identifier; -import net.fabricmc.fabric.impl.networking.CustomPayloadC2SPacketAccessor; +@Mixin(LoginQueryRequestS2CPacket.class) +public interface LoginQueryRequestS2CPacketAccessor { + @Accessor("queryId") + int getServerQueryId(); -@Mixin(CustomPayloadC2SPacket.class) -public class MixinCustomPayloadC2SPacket implements CustomPayloadC2SPacketAccessor { - @Shadow - private Identifier channel; - @Shadow - private PacketByteBuf data; + @Accessor + Identifier getChannel(); - @Override - public Identifier getChannel() { - return channel; - } + @Accessor + void setChannel(Identifier channel); - @Override - public PacketByteBuf getData() { - return new PacketByteBuf(this.data.copy()); - } + @Accessor + PacketByteBuf getPayload(); + + @Accessor + void setPayload(PacketByteBuf payload); + + @Accessor + void setQueryId(int queryId); } diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/accessor/LoginQueryResponseC2SPacketAccessor.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/accessor/LoginQueryResponseC2SPacketAccessor.java new file mode 100644 index 000000000..dcf460b83 --- /dev/null +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/accessor/LoginQueryResponseC2SPacketAccessor.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.mixin.networking.accessor; + +import org.jetbrains.annotations.Nullable; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +import net.minecraft.util.PacketByteBuf; +import net.minecraft.server.network.packet.LoginQueryResponseC2SPacket; + +@Mixin(LoginQueryResponseC2SPacket.class) +public interface LoginQueryResponseC2SPacketAccessor { + @Accessor + int getQueryId(); + + @Nullable + @Accessor + PacketByteBuf getResponse(); +} diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/accessor/MinecraftClientAccessor.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/accessor/MinecraftClientAccessor.java new file mode 100644 index 000000000..9f4808db7 --- /dev/null +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/accessor/MinecraftClientAccessor.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.mixin.networking.accessor; + +import org.jetbrains.annotations.Nullable; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.network.ClientConnection; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +@Environment(EnvType.CLIENT) +@Mixin(MinecraftClient.class) +public interface MinecraftClientAccessor { + @Nullable + @Accessor("clientConnection") + ClientConnection getConnection(); +} diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/accessor/ServerLoginNetworkHandlerAccessor.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/accessor/ServerLoginNetworkHandlerAccessor.java new file mode 100644 index 000000000..96d4ebad4 --- /dev/null +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/accessor/ServerLoginNetworkHandlerAccessor.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.mixin.networking.accessor; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.network.ServerLoginNetworkHandler; + +@Mixin(ServerLoginNetworkHandler.class) +public interface ServerLoginNetworkHandlerAccessor { + @Accessor + MinecraftServer getServer(); +} diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/client/ClientLoginNetworkHandlerMixin.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/client/ClientLoginNetworkHandlerMixin.java new file mode 100644 index 000000000..705b76cdb --- /dev/null +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/client/ClientLoginNetworkHandlerMixin.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.mixin.networking.client; + +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.ClientLoginNetworkHandler; +import net.minecraft.client.network.packet.LoginQueryRequestS2CPacket; +import net.minecraft.text.Text; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.fabric.impl.networking.client.ClientLoginNetworkAddon; +import net.fabricmc.fabric.impl.networking.client.ClientLoginNetworkHandlerExtensions; + +@Environment(EnvType.CLIENT) +@Mixin(ClientLoginNetworkHandler.class) +abstract class ClientLoginNetworkHandlerMixin implements ClientLoginNetworkHandlerExtensions { + @Shadow + @Final + private MinecraftClient client; + + @Unique + private ClientLoginNetworkAddon addon; + + @Inject(method = "", at = @At("RETURN")) + private void initAddon(CallbackInfo ci) { + this.addon = new ClientLoginNetworkAddon((ClientLoginNetworkHandler) (Object) this, this.client); + } + + @Inject(method = "onQueryRequest", at = @At(value = "INVOKE", target = "Ljava/util/function/Consumer;accept(Ljava/lang/Object;)V", remap = false, shift = At.Shift.AFTER), cancellable = true) + private void handleQueryRequest(LoginQueryRequestS2CPacket packet, CallbackInfo ci) { + if (this.addon.handlePacket(packet)) { + ci.cancel(); + } + } + + @Inject(method = "onDisconnected", at = @At("HEAD")) + private void invokeLoginDisconnectEvent(Text reason, CallbackInfo ci) { + this.addon.invokeDisconnectEvent(); + } + + @Override + public ClientLoginNetworkAddon getAddon() { + return this.addon; + } +} diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/client/ClientPlayNetworkHandlerMixin.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/client/ClientPlayNetworkHandlerMixin.java new file mode 100644 index 000000000..c6a02f4a9 --- /dev/null +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/client/ClientPlayNetworkHandlerMixin.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.mixin.networking.client; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.ClientPlayNetworkHandler; +import net.minecraft.client.network.packet.CustomPayloadS2CPacket; +import net.minecraft.client.network.packet.GameJoinS2CPacket; +import net.minecraft.text.Text; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.fabric.impl.networking.client.ClientNetworkingImpl; +import net.fabricmc.fabric.impl.networking.client.ClientPlayNetworkAddon; +import net.fabricmc.fabric.impl.networking.client.ClientPlayNetworkHandlerExtensions; + +// We want to apply a bit earlier than other mods which may not use us in order to prevent refCount issues +@Environment(EnvType.CLIENT) +@Mixin(value = ClientPlayNetworkHandler.class, priority = 999) +abstract class ClientPlayNetworkHandlerMixin implements ClientPlayNetworkHandlerExtensions { + @Shadow + private MinecraftClient client; + + @Unique + private ClientPlayNetworkAddon addon; + + @Inject(method = "", at = @At("RETURN")) + private void initAddon(CallbackInfo ci) { + this.addon = new ClientPlayNetworkAddon((ClientPlayNetworkHandler) (Object) this, this.client); + // A bit of a hack but it allows the field above to be set in case someone registers handlers during INIT event which refers to said field + ClientNetworkingImpl.setClientPlayAddon(this.addon); + this.addon.lateInit(); + } + + @Inject(method = "onGameJoin", at = @At("RETURN")) + private void handleServerPlayReady(GameJoinS2CPacket packet, CallbackInfo ci) { + this.addon.onServerReady(); + } + + @Inject(method = "onCustomPayload", at = @At("HEAD"), cancellable = true) + private void handleCustomPayload(CustomPayloadS2CPacket packet, CallbackInfo ci) { + if (this.addon.handle(packet)) { + ci.cancel(); + } + } + + @Inject(method = "onDisconnected", at = @At("HEAD")) + private void handleDisconnection(Text reason, CallbackInfo ci) { + this.addon.invokeDisconnectEvent(); + } + + @Override + public ClientPlayNetworkAddon getAddon() { + return this.addon; + } +} diff --git a/fabric-networking-api-v1/src/main/resources/assets/fabric-networking-api-v1/icon.png b/fabric-networking-api-v1/src/main/resources/assets/fabric-networking-api-v1/icon.png new file mode 100644 index 000000000..2931efbf6 Binary files /dev/null and b/fabric-networking-api-v1/src/main/resources/assets/fabric-networking-api-v1/icon.png differ diff --git a/fabric-networking-api-v1/src/main/resources/fabric-networking-api-v1.mixins.json b/fabric-networking-api-v1/src/main/resources/fabric-networking-api-v1.mixins.json new file mode 100644 index 000000000..f5504b3c0 --- /dev/null +++ b/fabric-networking-api-v1/src/main/resources/fabric-networking-api-v1.mixins.json @@ -0,0 +1,27 @@ +{ + "required": true, + "package": "net.fabricmc.fabric.mixin.networking", + "compatibilityLevel": "JAVA_8", + "mixins": [ + "ClientConnectionMixin", + "EntityTrackerEntryMixin", + "PlayerManagerMixin", + "ServerLoginNetworkHandlerMixin", + "ServerPlayNetworkHandlerMixin", + "ThreadedAnvilChunkStorageMixin", + "accessor.CustomPayloadC2SPacketAccessor", + "accessor.EntityTrackerAccessor", + "accessor.LoginQueryRequestS2CPacketAccessor", + "accessor.LoginQueryResponseC2SPacketAccessor", + "accessor.ServerLoginNetworkHandlerAccessor" + ], + "client": [ + "accessor.ConnectScreenAccessor", + "accessor.MinecraftClientAccessor", + "client.ClientLoginNetworkHandlerMixin", + "client.ClientPlayNetworkHandlerMixin" + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/fabric-networking-api-v1/src/main/resources/fabric.mod.json b/fabric-networking-api-v1/src/main/resources/fabric.mod.json new file mode 100644 index 000000000..0e2b09bc5 --- /dev/null +++ b/fabric-networking-api-v1/src/main/resources/fabric.mod.json @@ -0,0 +1,34 @@ +{ + "schemaVersion": 1, + "id": "fabric-networking-api-v1", + "name": "Fabric Networking API (v1)", + "version": "${version}", + "environment": "*", + "license": "Apache-2.0", + "icon": "assets/fabric-networking-api-v1/icon.png", + "contact": { + "homepage": "https://fabricmc.net", + "irc": "irc://irc.esper.net:6667/fabric", + "issues": "https://github.com/FabricMC/fabric/issues", + "sources": "https://github.com/FabricMC/fabric" + }, + "authors": [ + "FabricMC" + ], + "entrypoints": { + "main": [ + "net.fabricmc.fabric.impl.networking.NetworkingImpl::init" + ], + "client": [ + "net.fabricmc.fabric.impl.networking.client.ClientNetworkingImpl::clientInit" + ] + }, + "depends": { + "fabricloader": ">=0.4.0", + "fabric-api-base": "*" + }, + "description": "Low-level, vanilla protocol oriented networking hooks.", + "mixins": [ + "fabric-networking-api-v1.mixins.json" + ] +} diff --git a/fabric-networking-v0/src/main/java/net/fabricmc/fabric/impl/networking/PacketTypes.java b/fabric-networking-api-v1/src/testmod/java/net/fabricmc/fabric/test/networking/NetworkingTestmods.java similarity index 61% rename from fabric-networking-v0/src/main/java/net/fabricmc/fabric/impl/networking/PacketTypes.java rename to fabric-networking-api-v1/src/testmod/java/net/fabricmc/fabric/test/networking/NetworkingTestmods.java index cac2b8e15..d834b62e0 100644 --- a/fabric-networking-v0/src/main/java/net/fabricmc/fabric/impl/networking/PacketTypes.java +++ b/fabric-networking-api-v1/src/testmod/java/net/fabricmc/fabric/test/networking/NetworkingTestmods.java @@ -14,14 +14,21 @@ * limitations under the License. */ -package net.fabricmc.fabric.impl.networking; +package net.fabricmc.fabric.test.networking; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import net.minecraft.util.Identifier; -public final class PacketTypes { - public static final Identifier BRAND = new Identifier("minecraft:brand"); - public static final Identifier REGISTER = new Identifier("minecraft:register"); - public static final Identifier UNREGISTER = new Identifier("minecraft:unregister"); +public final class NetworkingTestmods { + public static final String ID = "fabric-networking-api-v1-testmod"; + public static final Logger LOGGER = LogManager.getLogger(ID); - public static final Identifier OPEN_CONTAINER = new Identifier("fabric", "container/open"); + public static Identifier id(String name) { + return new Identifier(ID, name); + } + + private NetworkingTestmods() { + } } diff --git a/fabric-networking-api-v1/src/testmod/java/net/fabricmc/fabric/test/networking/channeltest/ChannelList.java b/fabric-networking-api-v1/src/testmod/java/net/fabricmc/fabric/test/networking/channeltest/ChannelList.java new file mode 100644 index 000000000..3ea9ddac7 --- /dev/null +++ b/fabric-networking-api-v1/src/testmod/java/net/fabricmc/fabric/test/networking/channeltest/ChannelList.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.test.networking.channeltest; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.widget.EntryListWidget; +import net.minecraft.util.Formatting; +import net.minecraft.util.Identifier; + +final class ChannelList extends EntryListWidget { + ChannelList(MinecraftClient client, int width, int height, int top, int bottom, int itemHeight) { + super(client, width, height, top, bottom, itemHeight); + } + + @Override + public int addEntry(Entry entry) { + return super.addEntry(entry); + } + + void clear() { + this.clearEntries(); + } + + class Entry extends EntryListWidget.Entry { + private final Identifier channel; + + Entry(Identifier channel) { + this.channel = channel; + } + + @Override + public void render(int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float tickDelta) { + ChannelList.this.minecraft.textRenderer.draw(this.channel.toString(), x, y, Formatting.WHITE.getColorValue()); + } + } +} diff --git a/fabric-networking-api-v1/src/testmod/java/net/fabricmc/fabric/test/networking/channeltest/ChannelScreen.java b/fabric-networking-api-v1/src/testmod/java/net/fabricmc/fabric/test/networking/channeltest/ChannelScreen.java new file mode 100644 index 000000000..6e2ffaaad --- /dev/null +++ b/fabric-networking-api-v1/src/testmod/java/net/fabricmc/fabric/test/networking/channeltest/ChannelScreen.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.test.networking.channeltest; + +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.gui.widget.ButtonWidget; +import net.minecraft.text.LiteralText; +import net.minecraft.util.Formatting; +import net.minecraft.util.Identifier; + +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; + +final class ChannelScreen extends Screen { + private final NetworkingChannelClientTest mod; + private ButtonWidget s2cButton; + private ButtonWidget c2sButton; + private ButtonWidget closeButton; + private ChannelList channelList; + + ChannelScreen(NetworkingChannelClientTest mod) { + super(new LiteralText("TODO")); + this.mod = mod; + } + + @Override + protected void init() { + this.s2cButton = this.addButton(new ButtonWidget(this.width / 2 - 55, 5, 50, 20, "S2C", this::toS2C)); + this.c2sButton = this.addButton(new ButtonWidget(this.width / 2 + 5, 5, 50, 20, "C2S", this::toC2S)); + this.closeButton = this.addButton(new ButtonWidget(this.width / 2 - 60, this.height - 25, 120, 20, "Close", button -> this.onClose())); + this.channelList = new ChannelList(this.minecraft, this.width, this.height - 60, 30, this.height - 30, this.font.fontHeight + 2); + this.children.add(this.channelList); + } + + @Override + public void render(int mouseX, int mouseY, float delta) { + this.renderBackground(0); + this.channelList.render(mouseX, mouseY, delta); + super.render(mouseX, mouseY, delta); + + if (this.s2cButton.active && this.c2sButton.active) { + final String clickMe = "Click S2C or C2S to view supported channels"; + + final int textWidth = this.font.getStringWidth(clickMe); + //noinspection ConstantConditions + this.font.draw( + clickMe, + this.width / 2.0F - (textWidth / 2.0F), + 60, + Formatting.YELLOW.getColorValue() + ); + } + } + + void refresh() { + if (!this.c2sButton.active && this.s2cButton.active) { + this.toC2S(this.c2sButton); + } + } + + private void toC2S(ButtonWidget button) { + this.s2cButton.active = true; + button.active = false; + this.channelList.clear(); + + for (Identifier receiver : ClientPlayNetworking.getSendable()) { + this.channelList.addEntry(this.channelList.new Entry(receiver)); + } + } + + private void toS2C(ButtonWidget button) { + this.c2sButton.active = true; + button.active = false; + this.channelList.clear(); + + for (Identifier receiver : ClientPlayNetworking.getReceived()) { + this.channelList.addEntry(this.channelList.new Entry(receiver)); + } + } +} diff --git a/fabric-networking-api-v1/src/testmod/java/net/fabricmc/fabric/test/networking/channeltest/NetworkingChannelClientTest.java b/fabric-networking-api-v1/src/testmod/java/net/fabricmc/fabric/test/networking/channeltest/NetworkingChannelClientTest.java new file mode 100644 index 000000000..6256700b0 --- /dev/null +++ b/fabric-networking-api-v1/src/testmod/java/net/fabricmc/fabric/test/networking/channeltest/NetworkingChannelClientTest.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.test.networking.channeltest; + +import java.util.HashSet; +import java.util.Set; + +import org.lwjgl.glfw.GLFW; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.options.KeyBinding; +import net.minecraft.client.util.InputUtil; +import net.minecraft.util.Identifier; + +import net.fabricmc.api.ClientModInitializer; +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; +import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper; +import net.fabricmc.fabric.api.client.networking.v1.ClientLoginConnectionEvents; +import net.fabricmc.fabric.api.client.networking.v1.C2SPlayChannelEvents; +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents; + +public final class NetworkingChannelClientTest implements ClientModInitializer { + public static final KeyBinding OPEN = KeyBindingHelper.registerKeyBinding(new KeyBinding("networking-v1-test", InputUtil.Type.KEYSYM, GLFW.GLFW_KEY_MENU, "fabric-networking-api-v1-testmod\"")); + static final Set SUPPORTED_C2S_CHANNELS = new HashSet<>(); + + @Override + public void onInitializeClient() { + ClientTickEvents.END_CLIENT_TICK.register(client -> { + if (client.player != null) { + if (OPEN.wasPressed()) { + client.openScreen(new ChannelScreen(this)); + } + } + }); + + C2SPlayChannelEvents.REGISTER.register((handler, sender, client, channels) -> { + SUPPORTED_C2S_CHANNELS.addAll(channels); + + if (MinecraftClient.getInstance().currentScreen instanceof ChannelScreen) { + ((ChannelScreen) MinecraftClient.getInstance().currentScreen).refresh(); + } + }); + + C2SPlayChannelEvents.UNREGISTER.register((handler, sender, client, channels) -> { + SUPPORTED_C2S_CHANNELS.removeAll(channels); + + if (MinecraftClient.getInstance().currentScreen instanceof ChannelScreen) { + ((ChannelScreen) MinecraftClient.getInstance().currentScreen).refresh(); + } + }); + + // State destruction on disconnection: + ClientLoginConnectionEvents.DISCONNECT.register((handler, client) -> { + SUPPORTED_C2S_CHANNELS.clear(); + }); + + ClientPlayConnectionEvents.DISCONNECT.register((handler, client) -> { + SUPPORTED_C2S_CHANNELS.clear(); + }); + } +} diff --git a/fabric-networking-api-v1/src/testmod/java/net/fabricmc/fabric/test/networking/channeltest/NetworkingChannelTest.java b/fabric-networking-api-v1/src/testmod/java/net/fabricmc/fabric/test/networking/channeltest/NetworkingChannelTest.java new file mode 100644 index 000000000..4dc2b6755 --- /dev/null +++ b/fabric-networking-api-v1/src/testmod/java/net/fabricmc/fabric/test/networking/channeltest/NetworkingChannelTest.java @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.test.networking.channeltest; + +import static net.minecraft.command.arguments.EntityArgumentType.getPlayer; +import static net.minecraft.command.arguments.EntityArgumentType.player; +import static net.minecraft.command.arguments.IdentifierArgumentType.getIdentifier; +import static net.minecraft.command.arguments.IdentifierArgumentType.identifier; +import static net.minecraft.server.command.CommandManager.argument; +import static net.minecraft.server.command.CommandManager.literal; + +import java.util.concurrent.CompletableFuture; + +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; +import com.mojang.brigadier.suggestion.Suggestions; +import com.mojang.brigadier.suggestion.SuggestionsBuilder; +import com.mojang.brigadier.tree.ArgumentCommandNode; +import com.mojang.brigadier.tree.LiteralCommandNode; + +import net.minecraft.server.command.CommandSource; +import net.minecraft.command.EntitySelector; +import net.minecraft.server.command.ServerCommandSource; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.text.LiteralText; +import net.minecraft.util.Identifier; + +import net.fabricmc.api.ModInitializer; +import net.fabricmc.fabric.api.command.v1.CommandRegistrationCallback; +import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; + +public final class NetworkingChannelTest implements ModInitializer { + @Override + public void onInitialize() { + CommandRegistrationCallback.EVENT.register((dispatcher, dedicated) -> { + final LiteralCommandNode channelTestCommand = literal("network_channel_test").build(); + + // Info + { + final LiteralCommandNode info = literal("info") + .executes(context -> infoCommand(context, context.getSource().getPlayer())) + .build(); + + final ArgumentCommandNode player = argument("player", player()) + .executes(context -> infoCommand(context, getPlayer(context, "player"))) + .build(); + + info.addChild(player); + channelTestCommand.addChild(info); + } + + // Register + { + final LiteralCommandNode register = literal("register") + .then(argument("channel", identifier()) + .executes(context -> registerChannel(context, context.getSource().getPlayer()))) + .build(); + + channelTestCommand.addChild(register); + } + + // Unregister + { + final LiteralCommandNode unregister = literal("unregister") + .then(argument("channel", identifier()).suggests(NetworkingChannelTest::suggestReceivableChannels) + .executes(context -> unregisterChannel(context, context.getSource().getPlayer()))) + .build(); + + channelTestCommand.addChild(unregister); + } + + dispatcher.getRoot().addChild(channelTestCommand); + }); + } + + private static CompletableFuture suggestReceivableChannels(CommandContext context, SuggestionsBuilder builder) throws CommandSyntaxException { + final ServerPlayerEntity player = context.getSource().getPlayer(); + + return CommandSource.suggestIdentifiers(ServerPlayNetworking.getReceived(player), builder); + } + + private static int registerChannel(CommandContext context, ServerPlayerEntity executor) throws CommandSyntaxException { + final Identifier channel = getIdentifier(context, "channel"); + + if (ServerPlayNetworking.getReceived(executor).contains(channel)) { + throw new SimpleCommandExceptionType(new LiteralText(String.format("Cannot register channel %s twice for server player", channel))).create(); + } + + ServerPlayNetworking.registerReceiver(executor.networkHandler, channel, (server, player, handler, buf, sender) -> { + System.out.printf("Received packet on channel %s%n", channel); + }); + + context.getSource().sendFeedback(new LiteralText(String.format("Registered channel %s for %s", channel, executor.getEntityName())), false); + + return 1; + } + + private static int unregisterChannel(CommandContext context, ServerPlayerEntity player) throws CommandSyntaxException { + final Identifier channel = getIdentifier(context, "channel"); + + if (!ServerPlayNetworking.getReceived(player).contains(channel)) { + throw new SimpleCommandExceptionType(new LiteralText("Cannot unregister channel the server player entity cannot recieve packets on")).create(); + } + + ServerPlayNetworking.unregisterReceiver(player.networkHandler, channel); + context.getSource().sendFeedback(new LiteralText(String.format("Unregistered channel %s for %s", getIdentifier(context, "channel"), player.getEntityName())), false); + + return 1; + } + + private static int infoCommand(CommandContext context, ServerPlayerEntity player) { + // TODO: + + return 1; + } +} diff --git a/fabric-networking-api-v1/src/testmod/java/net/fabricmc/fabric/test/networking/keybindreciever/NetworkingKeybindClientPacketTest.java b/fabric-networking-api-v1/src/testmod/java/net/fabricmc/fabric/test/networking/keybindreciever/NetworkingKeybindClientPacketTest.java new file mode 100644 index 000000000..5ea8de478 --- /dev/null +++ b/fabric-networking-api-v1/src/testmod/java/net/fabricmc/fabric/test/networking/keybindreciever/NetworkingKeybindClientPacketTest.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.test.networking.keybindreciever; + +import org.lwjgl.glfw.GLFW; + +import net.minecraft.client.options.KeyBinding; +import net.minecraft.client.util.InputUtil; + +import net.fabricmc.api.ClientModInitializer; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; +import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper; +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; +import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; + +// Sends a packet to the server when a keybinding was pressed +// The server in response will send a chat message to the client. +@Environment(EnvType.CLIENT) +public class NetworkingKeybindClientPacketTest implements ClientModInitializer { + public static final KeyBinding TEST_BINDING = KeyBindingHelper.registerKeyBinding(new KeyBinding("fabric-networking-api-v1-testmod-keybind", InputUtil.Type.KEYSYM, GLFW.GLFW_KEY_RIGHT_BRACKET, "fabric-networking-api-v1-testmod")); + + @Override + public void onInitializeClient() { + ClientTickEvents.END_CLIENT_TICK.register(client -> { + // Player must be in game to send packets, i.e. client.player != null + if (client.getNetworkHandler() != null) { + if (TEST_BINDING.wasPressed()) { + // Send an empty payload, server just needs to be told when packet is sent + ClientPlayNetworking.send(NetworkingKeybindPacketTest.KEYBINDING_PACKET_ID, PacketByteBufs.empty()); + } + } + }); + } +} diff --git a/fabric-networking-api-v1/src/testmod/java/net/fabricmc/fabric/test/networking/keybindreciever/NetworkingKeybindPacketTest.java b/fabric-networking-api-v1/src/testmod/java/net/fabricmc/fabric/test/networking/keybindreciever/NetworkingKeybindPacketTest.java new file mode 100644 index 000000000..d04dda754 --- /dev/null +++ b/fabric-networking-api-v1/src/testmod/java/net/fabricmc/fabric/test/networking/keybindreciever/NetworkingKeybindPacketTest.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.test.networking.keybindreciever; + +import net.minecraft.util.PacketByteBuf; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.network.ServerPlayNetworkHandler; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.text.KeybindText; +import net.minecraft.text.LiteralText; +import net.minecraft.util.Formatting; +import net.minecraft.util.Identifier; + +import net.fabricmc.api.ModInitializer; +import net.fabricmc.fabric.api.networking.v1.PacketSender; +import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents; +import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; +import net.fabricmc.fabric.test.networking.NetworkingTestmods; + +// Listens for a packet from the client which is sent to the server when a keybinding is pressed. +// In response the server will send a message containing the keybind text letting the client know it pressed that key. +public final class NetworkingKeybindPacketTest implements ModInitializer { + public static final Identifier KEYBINDING_PACKET_ID = NetworkingTestmods.id("keybind_press_test"); + + private static void receive(MinecraftServer server, ServerPlayerEntity player, ServerPlayNetworkHandler handler, PacketByteBuf buf, PacketSender responseSender) { + // TODO: Can we send chat off the server thread? + server.execute(() -> { + player.sendMessage(new LiteralText("So you pressed ").append(new KeybindText("fabric-networking-api-v1-testmod-keybind").styled(style -> style.setColor(Formatting.BLUE)))); + }); + } + + @Override + public void onInitialize() { + ServerPlayConnectionEvents.INIT.register((handler, server) -> { + ServerPlayNetworking.registerReceiver(handler, KEYBINDING_PACKET_ID, NetworkingKeybindPacketTest::receive); + }); + } +} diff --git a/fabric-networking-api-v1/src/testmod/java/net/fabricmc/fabric/test/networking/login/NetworkingLoginQueryClientTest.java b/fabric-networking-api-v1/src/testmod/java/net/fabricmc/fabric/test/networking/login/NetworkingLoginQueryClientTest.java new file mode 100644 index 000000000..88e1d6598 --- /dev/null +++ b/fabric-networking-api-v1/src/testmod/java/net/fabricmc/fabric/test/networking/login/NetworkingLoginQueryClientTest.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.test.networking.login; + +import java.util.concurrent.CompletableFuture; + +import net.fabricmc.api.ClientModInitializer; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.fabric.api.client.networking.v1.ClientLoginNetworking; +import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; +import net.fabricmc.fabric.test.networking.play.NetworkingPlayPacketTest; + +@Environment(EnvType.CLIENT) +public final class NetworkingLoginQueryClientTest implements ClientModInitializer { + @Override + public void onInitializeClient() { + // Send a dummy response to the server in return, by registering here we essentially say we understood the server's query + ClientLoginNetworking.registerGlobalReceiver(NetworkingPlayPacketTest.TEST_CHANNEL, (client, handler, buf, listenerAdder) -> { + return CompletableFuture.completedFuture(PacketByteBufs.empty()); + }); + } +} diff --git a/fabric-networking-api-v1/src/testmod/java/net/fabricmc/fabric/test/networking/login/NetworkingLoginQueryTest.java b/fabric-networking-api-v1/src/testmod/java/net/fabricmc/fabric/test/networking/login/NetworkingLoginQueryTest.java new file mode 100644 index 000000000..d868e6b8e --- /dev/null +++ b/fabric-networking-api-v1/src/testmod/java/net/fabricmc/fabric/test/networking/login/NetworkingLoginQueryTest.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.test.networking.login; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.FutureTask; + +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.network.ServerLoginNetworkHandler; +import net.minecraft.util.Util; + +import net.fabricmc.api.ModInitializer; +import net.fabricmc.fabric.api.networking.v1.ServerLoginConnectionEvents; +import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; +import net.fabricmc.fabric.api.networking.v1.PacketSender; +import net.fabricmc.fabric.api.networking.v1.ServerLoginNetworking; +import net.fabricmc.fabric.test.networking.NetworkingTestmods; +import net.fabricmc.fabric.test.networking.play.NetworkingPlayPacketTest; + +public final class NetworkingLoginQueryTest implements ModInitializer { + private static final boolean useLoginDelayTest = System.getProperty("fabric-networking-api-v1.loginDelayTest") != null; + + @Override + public void onInitialize() { + ServerLoginConnectionEvents.QUERY_START.register(this::onLoginStart); + ServerLoginConnectionEvents.QUERY_START.register(this::delaySimply); + + // login delaying example + ServerLoginNetworking.registerGlobalReceiver(NetworkingPlayPacketTest.TEST_CHANNEL, (server, handler, understood, buf, synchronizer, sender) -> { + if (understood) { + NetworkingTestmods.LOGGER.info("Understood response from client in {}", NetworkingPlayPacketTest.TEST_CHANNEL); + + if (useLoginDelayTest) { + FutureTask future = new FutureTask<>(() -> { + for (int i = 0; i <= 10; i++) { + Thread.sleep(300); + NetworkingTestmods.LOGGER.info("Delayed login for number {} 300 milliseconds", i); + } + + return null; + }); + + // Execute the task on a worker thread as not to block the server thread + Util.getServerWorkerExecutor().execute(future); + synchronizer.waitFor(future); + } + } else { + NetworkingTestmods.LOGGER.info("Client did not understand response query message with channel name {}", NetworkingPlayPacketTest.TEST_CHANNEL); + } + }); + } + + private void delaySimply(ServerLoginNetworkHandler handler, MinecraftServer server, PacketSender sender, ServerLoginNetworking.LoginSynchronizer synchronizer) { + if (useLoginDelayTest) { + synchronizer.waitFor(CompletableFuture.runAsync(() -> { + NetworkingTestmods.LOGGER.info("Starting simple delay task for 3000 milliseconds"); + + try { + Thread.sleep(3000); + NetworkingTestmods.LOGGER.info("Simple delay task completed"); + } catch (InterruptedException e) { + NetworkingTestmods.LOGGER.error("Delay task caught exception", e); + } + })); + } + } + + private void onLoginStart(ServerLoginNetworkHandler networkHandler, MinecraftServer server, PacketSender sender, ServerLoginNetworking.LoginSynchronizer synchronizer) { + // Send a dummy query when the client starts accepting queries. + sender.sendPacket(NetworkingPlayPacketTest.TEST_CHANNEL, PacketByteBufs.empty()); // dummy packet + } +} diff --git a/fabric-networking-api-v1/src/testmod/java/net/fabricmc/fabric/test/networking/play/NetworkingPlayPacketClientTest.java b/fabric-networking-api-v1/src/testmod/java/net/fabricmc/fabric/test/networking/play/NetworkingPlayPacketClientTest.java new file mode 100644 index 000000000..0f28ac177 --- /dev/null +++ b/fabric-networking-api-v1/src/testmod/java/net/fabricmc/fabric/test/networking/play/NetworkingPlayPacketClientTest.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.test.networking.play; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.ClientPlayNetworkHandler; +import net.minecraft.util.PacketByteBuf; +import net.minecraft.text.Text; + +import net.fabricmc.api.ClientModInitializer; +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents; +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; +import net.fabricmc.fabric.api.networking.v1.PacketSender; + +public final class NetworkingPlayPacketClientTest implements ClientModInitializer { + @Override + public void onInitializeClient() { + //ClientPlayNetworking.registerGlobalReceiver(NetworkingPlayPacketTest.TEST_CHANNEL, this::receive); + + ClientPlayConnectionEvents.INIT.register((handler, client) -> { + ClientPlayNetworking.registerReceiver(NetworkingPlayPacketTest.TEST_CHANNEL, (client1, handler1, buf, sender1) -> receive(handler1, sender1, client1, buf)); + }); + } + + private void receive(ClientPlayNetworkHandler handler, PacketSender sender, MinecraftClient client, PacketByteBuf buf) { + Text text = buf.readText(); + client.execute(() -> client.inGameHud.setOverlayMessage(text, true)); + } +} diff --git a/fabric-networking-api-v1/src/testmod/java/net/fabricmc/fabric/test/networking/play/NetworkingPlayPacketTest.java b/fabric-networking-api-v1/src/testmod/java/net/fabricmc/fabric/test/networking/play/NetworkingPlayPacketTest.java new file mode 100644 index 000000000..0d56bbbdf --- /dev/null +++ b/fabric-networking-api-v1/src/testmod/java/net/fabricmc/fabric/test/networking/play/NetworkingPlayPacketTest.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.test.networking.play; + +import static com.mojang.brigadier.arguments.StringArgumentType.string; +import static net.minecraft.server.command.CommandManager.argument; +import static net.minecraft.server.command.CommandManager.literal; + +import com.mojang.brigadier.Command; +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.arguments.StringArgumentType; + +import net.minecraft.util.PacketByteBuf; +import net.minecraft.server.command.ServerCommandSource; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.text.LiteralText; +import net.minecraft.util.Identifier; + +import net.fabricmc.api.ModInitializer; +import net.fabricmc.fabric.api.command.v1.CommandRegistrationCallback; +import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; +import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; +import net.fabricmc.fabric.test.networking.NetworkingTestmods; + +public final class NetworkingPlayPacketTest implements ModInitializer { + public static final Identifier TEST_CHANNEL = NetworkingTestmods.id("test_channel"); + + public static void sendToTestChannel(ServerPlayerEntity player, String stuff) { + PacketByteBuf buf = PacketByteBufs.create(); + buf.writeText(new LiteralText(stuff)); + ServerPlayNetworking.send(player, TEST_CHANNEL, buf); + NetworkingTestmods.LOGGER.info("Sent custom payload packet in {}", TEST_CHANNEL); + } + + public static void registerCommand(CommandDispatcher dispatcher) { + NetworkingTestmods.LOGGER.info("Registering test command"); + + dispatcher.register(literal("networktestcommand").then(argument("stuff", string()).executes(ctx -> { + String stuff = StringArgumentType.getString(ctx, "stuff"); + sendToTestChannel(ctx.getSource().getPlayer(), stuff); + return Command.SINGLE_SUCCESS; + }))); + } + + @Override + public void onInitialize() { + NetworkingTestmods.LOGGER.info("Hello from networking user!"); + + CommandRegistrationCallback.EVENT.register((dispatcher, dedicated) -> { + NetworkingPlayPacketTest.registerCommand(dispatcher); + }); + } +} diff --git a/fabric-networking-api-v1/src/testmod/resources/fabric.mod.json b/fabric-networking-api-v1/src/testmod/resources/fabric.mod.json new file mode 100644 index 000000000..664aee76b --- /dev/null +++ b/fabric-networking-api-v1/src/testmod/resources/fabric.mod.json @@ -0,0 +1,25 @@ +{ + "schemaVersion": 1, + "id": "fabric-networking-api-v1-testmod", + "name": "Fabric Networking API (v1) Test Mod", + "version": "1.0.0", + "environment": "*", + "license": "Apache-2.0", + "depends": { + "fabric-networking-api-v1": "*" + }, + "entrypoints": { + "main": [ + "net.fabricmc.fabric.test.networking.channeltest.NetworkingChannelTest", + "net.fabricmc.fabric.test.networking.keybindreciever.NetworkingKeybindPacketTest", + "net.fabricmc.fabric.test.networking.login.NetworkingLoginQueryTest", + "net.fabricmc.fabric.test.networking.play.NetworkingPlayPacketTest" + ], + "client": [ + "net.fabricmc.fabric.test.networking.channeltest.NetworkingChannelClientTest", + "net.fabricmc.fabric.test.networking.keybindreciever.NetworkingKeybindClientPacketTest", + "net.fabricmc.fabric.test.networking.login.NetworkingLoginQueryClientTest", + "net.fabricmc.fabric.test.networking.play.NetworkingPlayPacketClientTest" + ] + } +} diff --git a/fabric-networking-v0/build.gradle b/fabric-networking-v0/build.gradle index bf5a9a942..13b854832 100644 --- a/fabric-networking-v0/build.gradle +++ b/fabric-networking-v0/build.gradle @@ -1,6 +1,7 @@ archivesBaseName = "fabric-networking-v0" -version = getSubprojectVersion(project, "0.1.10") +version = getSubprojectVersion(project, "0.2.0") dependencies { compile project(path: ':fabric-api-base', configuration: 'dev') + compile project(path: ':fabric-networking-api-v1', configuration: 'dev') } diff --git a/fabric-networking-v0/src/main/java/net/fabricmc/fabric/api/event/network/C2SPacketTypeCallback.java b/fabric-networking-v0/src/main/java/net/fabricmc/fabric/api/event/network/C2SPacketTypeCallback.java index 37491dff0..543773c76 100644 --- a/fabric-networking-v0/src/main/java/net/fabricmc/fabric/api/event/network/C2SPacketTypeCallback.java +++ b/fabric-networking-v0/src/main/java/net/fabricmc/fabric/api/event/network/C2SPacketTypeCallback.java @@ -21,6 +21,7 @@ import java.util.Collection; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.util.Identifier; +import net.fabricmc.fabric.api.client.networking.v1.C2SPlayChannelEvents; import net.fabricmc.fabric.api.event.Event; import net.fabricmc.fabric.api.event.EventFactory; @@ -30,8 +31,15 @@ import net.fabricmc.fabric.api.event.EventFactory; * *

Registrations received will be for server -> client packets * that the sending client can understand. + * + * @deprecated Please migrate to {@link C2SPlayChannelEvents}. */ +@Deprecated public interface C2SPacketTypeCallback { + /** + * @deprecated Please migrate to {@link C2SPlayChannelEvents#REGISTER}. + */ + @Deprecated Event REGISTERED = EventFactory.createArrayBacked( C2SPacketTypeCallback.class, (callbacks) -> (client, types) -> { @@ -41,6 +49,10 @@ public interface C2SPacketTypeCallback { } ); + /** + * @deprecated Please migrate to {@link C2SPlayChannelEvents#UNREGISTER}. + */ + @Deprecated Event UNREGISTERED = EventFactory.createArrayBacked( C2SPacketTypeCallback.class, (callbacks) -> (client, types) -> { diff --git a/fabric-networking-v0/src/main/java/net/fabricmc/fabric/api/event/network/S2CPacketTypeCallback.java b/fabric-networking-v0/src/main/java/net/fabricmc/fabric/api/event/network/S2CPacketTypeCallback.java index a854d5d1d..ae309152a 100644 --- a/fabric-networking-v0/src/main/java/net/fabricmc/fabric/api/event/network/S2CPacketTypeCallback.java +++ b/fabric-networking-v0/src/main/java/net/fabricmc/fabric/api/event/network/S2CPacketTypeCallback.java @@ -22,6 +22,7 @@ import net.minecraft.util.Identifier; import net.fabricmc.fabric.api.event.Event; import net.fabricmc.fabric.api.event.EventFactory; +import net.fabricmc.fabric.api.networking.v1.S2CPlayChannelEvents; /** * Event for listening to packet type registration and unregistration notifications @@ -29,8 +30,15 @@ import net.fabricmc.fabric.api.event.EventFactory; * *

Registrations received will be for client -> server packets * that the sending server can understand. + * + * @deprecated Please migrate to {@link S2CPlayChannelEvents}. */ +@Deprecated public interface S2CPacketTypeCallback { + /** + * @deprecated Please migrate to {@link S2CPlayChannelEvents#REGISTER}. + */ + @Deprecated Event REGISTERED = EventFactory.createArrayBacked( S2CPacketTypeCallback.class, (callbacks) -> (types) -> { @@ -40,6 +48,10 @@ public interface S2CPacketTypeCallback { } ); + /** + * @deprecated Please migrate to {@link S2CPlayChannelEvents#UNREGISTER}. + */ + @Deprecated Event UNREGISTERED = EventFactory.createArrayBacked( S2CPacketTypeCallback.class, (callbacks) -> (types) -> { diff --git a/fabric-networking-v0/src/main/java/net/fabricmc/fabric/api/network/ClientSidePacketRegistry.java b/fabric-networking-v0/src/main/java/net/fabricmc/fabric/api/network/ClientSidePacketRegistry.java index 84b796cb9..f662a45ad 100644 --- a/fabric-networking-v0/src/main/java/net/fabricmc/fabric/api/network/ClientSidePacketRegistry.java +++ b/fabric-networking-v0/src/main/java/net/fabricmc/fabric/api/network/ClientSidePacketRegistry.java @@ -23,6 +23,7 @@ import net.minecraft.network.Packet; import net.minecraft.util.Identifier; import net.minecraft.util.PacketByteBuf; +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; import net.fabricmc.fabric.impl.networking.ClientSidePacketRegistryImpl; /** @@ -32,7 +33,10 @@ import net.fabricmc.fabric.impl.networking.ClientSidePacketRegistryImpl; * *

  • registering client-side packet receivers (server -> client packets) *
  • sending packets to the server (client -> server packets).
+ * + * @deprecated Please migrate to {@link ClientPlayNetworking}. */ +@Deprecated public interface ClientSidePacketRegistry extends PacketRegistry { ClientSidePacketRegistry INSTANCE = new ClientSidePacketRegistryImpl(); diff --git a/fabric-networking-v0/src/main/java/net/fabricmc/fabric/api/network/PacketConsumer.java b/fabric-networking-v0/src/main/java/net/fabricmc/fabric/api/network/PacketConsumer.java index b6d42c55d..da02b8886 100644 --- a/fabric-networking-v0/src/main/java/net/fabricmc/fabric/api/network/PacketConsumer.java +++ b/fabric-networking-v0/src/main/java/net/fabricmc/fabric/api/network/PacketConsumer.java @@ -18,9 +18,15 @@ package net.fabricmc.fabric.api.network; import net.minecraft.util.PacketByteBuf; +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; +import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; + /** * Interface for receiving CustomPayload-based packets. + * + * @deprecated See the corresponding play packet handler in {@link ClientPlayNetworking} or {@link ServerPlayNetworking} */ +@Deprecated @FunctionalInterface public interface PacketConsumer { /** diff --git a/fabric-networking-v0/src/main/java/net/fabricmc/fabric/api/network/PacketContext.java b/fabric-networking-v0/src/main/java/net/fabricmc/fabric/api/network/PacketContext.java index 0c75fab37..962d98cc1 100644 --- a/fabric-networking-v0/src/main/java/net/fabricmc/fabric/api/network/PacketContext.java +++ b/fabric-networking-v0/src/main/java/net/fabricmc/fabric/api/network/PacketContext.java @@ -26,6 +26,7 @@ import net.fabricmc.api.EnvType; * to additional information, such as the source/target of the player, or * the correct task queue to enqueue synchronization-requiring code on. */ +@Deprecated public interface PacketContext { /** * Get the environment associated with the packet. diff --git a/fabric-networking-v0/src/main/java/net/fabricmc/fabric/api/network/PacketRegistry.java b/fabric-networking-v0/src/main/java/net/fabricmc/fabric/api/network/PacketRegistry.java index 335529461..c8b4d5099 100644 --- a/fabric-networking-v0/src/main/java/net/fabricmc/fabric/api/network/PacketRegistry.java +++ b/fabric-networking-v0/src/main/java/net/fabricmc/fabric/api/network/PacketRegistry.java @@ -20,6 +20,7 @@ import net.minecraft.network.Packet; import net.minecraft.util.Identifier; import net.minecraft.util.PacketByteBuf; +@Deprecated public interface PacketRegistry { /** * Turn a (identifier, byte buffer) pair into a "custom payload" packet diff --git a/fabric-networking-v0/src/main/java/net/fabricmc/fabric/api/network/ServerSidePacketRegistry.java b/fabric-networking-v0/src/main/java/net/fabricmc/fabric/api/network/ServerSidePacketRegistry.java index 23c1bb601..2ddbc3403 100644 --- a/fabric-networking-v0/src/main/java/net/fabricmc/fabric/api/network/ServerSidePacketRegistry.java +++ b/fabric-networking-v0/src/main/java/net/fabricmc/fabric/api/network/ServerSidePacketRegistry.java @@ -24,6 +24,7 @@ import net.minecraft.network.Packet; import net.minecraft.util.Identifier; import net.minecraft.util.PacketByteBuf; +import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; import net.fabricmc.fabric.api.server.PlayerStream; import net.fabricmc.fabric.impl.networking.ServerSidePacketRegistryImpl; @@ -36,7 +37,10 @@ import net.fabricmc.fabric.impl.networking.ServerSidePacketRegistryImpl; *
  • sending packets to clients (server -> client packets). * *

    For iterating over clients in a server, see {@link PlayerStream}. + * + * @deprecated Please migrate to {@link ServerPlayNetworking}. */ +@Deprecated public interface ServerSidePacketRegistry extends PacketRegistry { ServerSidePacketRegistry INSTANCE = new ServerSidePacketRegistryImpl(); diff --git a/fabric-networking-v0/src/main/java/net/fabricmc/fabric/api/server/PlayerStream.java b/fabric-networking-v0/src/main/java/net/fabricmc/fabric/api/server/PlayerStream.java index 56de100cf..15b0654ed 100644 --- a/fabric-networking-v0/src/main/java/net/fabricmc/fabric/api/server/PlayerStream.java +++ b/fabric-networking-v0/src/main/java/net/fabricmc/fabric/api/server/PlayerStream.java @@ -23,22 +23,22 @@ import net.minecraft.entity.Entity; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.server.MinecraftServer; import net.minecraft.server.network.ServerPlayerEntity; -import net.minecraft.server.world.ServerChunkManager; import net.minecraft.server.world.ServerWorld; -import net.minecraft.server.world.ThreadedAnvilChunkStorage; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.ChunkPos; import net.minecraft.util.math.Vec3d; import net.minecraft.world.World; -import net.minecraft.world.chunk.ChunkManager; -import net.fabricmc.fabric.impl.networking.server.EntityTrackerStorageAccessor; +import net.fabricmc.fabric.api.networking.v1.PlayerLookup; /** * Helper streams for looking up players on a server. * *

    In general, most of these methods will only function with a {@link ServerWorld} instance. + * + * @deprecated Please use {@link PlayerLookup} instead. */ +@Deprecated public final class PlayerStream { private PlayerStream() { } @@ -52,7 +52,7 @@ public final class PlayerStream { public static Stream world(World world) { if (world instanceof ServerWorld) { - // noinspection unchecked + // noinspection unchecked,rawtypes return ((Stream) ((ServerWorld) world).getPlayers().stream()); } else { throw new RuntimeException("Only supported on ServerWorld!"); @@ -60,14 +60,12 @@ public final class PlayerStream { } public static Stream watching(World world, ChunkPos pos) { - ChunkManager manager = world.getChunkManager(); - - if (!(manager instanceof ServerChunkManager)) { - throw new RuntimeException("Only supported on ServerWorld!"); - } else { - //noinspection unchecked - return ((Stream) ((ServerChunkManager) manager).threadedAnvilChunkStorage.getPlayersWatchingChunk(pos, false)); + if (world instanceof ServerWorld) { + //noinspection unchecked,rawtypes + return (Stream) PlayerLookup.tracking((ServerWorld) world, pos).stream(); } + + throw new RuntimeException("Only supported on ServerWorld!"); } /** @@ -77,19 +75,8 @@ public final class PlayerStream { */ @SuppressWarnings("JavaDoc") public static Stream watching(Entity entity) { - ChunkManager manager = entity.getEntityWorld().getChunkManager(); - - if (manager instanceof ServerChunkManager) { - ThreadedAnvilChunkStorage storage = ((ServerChunkManager) manager).threadedAnvilChunkStorage; - - if (storage instanceof EntityTrackerStorageAccessor) { - //noinspection unchecked - return ((Stream) ((EntityTrackerStorageAccessor) storage).fabric_getTrackingPlayers(entity)); - } - } - - // fallback - return watching(entity.getEntityWorld(), new ChunkPos((int) (entity.getX() / 16.0D), (int) (entity.getZ() / 16.0D))); + //noinspection unchecked,rawtypes + return (Stream) PlayerLookup.tracking(entity).stream(); } public static Stream watching(BlockEntity entity) { diff --git a/fabric-networking-v0/src/main/java/net/fabricmc/fabric/impl/networking/ClientSidePacketRegistryImpl.java b/fabric-networking-v0/src/main/java/net/fabricmc/fabric/impl/networking/ClientSidePacketRegistryImpl.java index fc98e5951..8c53519e4 100644 --- a/fabric-networking-v0/src/main/java/net/fabricmc/fabric/impl/networking/ClientSidePacketRegistryImpl.java +++ b/fabric-networking-v0/src/main/java/net/fabricmc/fabric/impl/networking/ClientSidePacketRegistryImpl.java @@ -16,87 +16,73 @@ package net.fabricmc.fabric.impl.networking; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; +import java.util.Objects; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.GenericFutureListener; import net.minecraft.client.MinecraftClient; -import net.minecraft.client.network.ClientPlayNetworkHandler; +import net.minecraft.entity.player.PlayerEntity; import net.minecraft.network.Packet; -import net.minecraft.server.network.packet.CustomPayloadC2SPacket; -import net.minecraft.util.Identifier; import net.minecraft.util.PacketByteBuf; +import net.minecraft.util.Identifier; +import net.minecraft.util.thread.ThreadExecutor; -import net.fabricmc.fabric.api.event.network.S2CPacketTypeCallback; +import net.fabricmc.api.EnvType; +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; import net.fabricmc.fabric.api.network.ClientSidePacketRegistry; +import net.fabricmc.fabric.api.network.PacketConsumer; import net.fabricmc.fabric.api.network.PacketContext; +import net.fabricmc.fabric.api.network.PacketRegistry; -public class ClientSidePacketRegistryImpl extends PacketRegistryImpl implements ClientSidePacketRegistry { - private final Collection serverPayloadIds = new HashSet<>(); - - public static void invalidateRegisteredIdList() { - ((ClientSidePacketRegistryImpl) ClientSidePacketRegistry.INSTANCE).serverPayloadIds.clear(); - } - +public class ClientSidePacketRegistryImpl implements ClientSidePacketRegistry, PacketRegistry { @Override public boolean canServerReceive(Identifier id) { - return serverPayloadIds.contains(id); + return ClientPlayNetworking.getReceived().contains(id); } @Override public void sendToServer(Packet packet, GenericFutureListener> completionListener) { - ClientPlayNetworkHandler handler = MinecraftClient.getInstance().getNetworkHandler(); - - if (handler != null) { - if (completionListener == null) { - // stay closer to the vanilla codepath - handler.sendPacket(packet); - } else { - handler.getConnection().send(packet, completionListener); - } - } else { - LOGGER.warn("Sending packet " + packet + " to server failed, not connected!"); + if (MinecraftClient.getInstance().getNetworkHandler() != null) { + MinecraftClient.getInstance().getNetworkHandler().getConnection().send(packet, completionListener); + return; } + + throw new IllegalStateException("Cannot send packet to server while not in game!"); // TODO: Error message } @Override public Packet toPacket(Identifier id, PacketByteBuf buf) { - return new CustomPayloadC2SPacket(id, buf); + return ClientPlayNetworking.createC2SPacket(id, buf); } @Override - protected void onRegister(Identifier id) { - ClientPlayNetworkHandler handler = MinecraftClient.getInstance().getNetworkHandler(); + public void register(Identifier id, PacketConsumer consumer) { + // id is checked in client networking + Objects.requireNonNull(consumer, "PacketConsumer cannot be null"); - if (handler != null) { - createRegisterTypePacket(PacketTypes.REGISTER, Collections.singleton(id)).ifPresent(handler::sendPacket); - } + ClientPlayNetworking.registerGlobalReceiver(id, (client, handler, buf, sender) -> { + consumer.accept(new PacketContext() { + @Override + public EnvType getPacketEnvironment() { + return EnvType.CLIENT; + } + + @Override + public PlayerEntity getPlayer() { + return client.player; + } + + @Override + public ThreadExecutor getTaskQueue() { + return client; + } + }, buf); + }); } @Override - protected void onUnregister(Identifier id) { - ClientPlayNetworkHandler handler = MinecraftClient.getInstance().getNetworkHandler(); - - if (handler != null) { - createRegisterTypePacket(PacketTypes.UNREGISTER, Collections.singleton(id)).ifPresent(handler::sendPacket); - } - } - - @Override - protected Collection getIdCollectionFor(PacketContext context) { - return serverPayloadIds; - } - - @Override - protected void onReceivedRegisterPacket(PacketContext context, Collection ids) { - S2CPacketTypeCallback.REGISTERED.invoker().accept(ids); - } - - @Override - protected void onReceivedUnregisterPacket(PacketContext context, Collection ids) { - S2CPacketTypeCallback.UNREGISTERED.invoker().accept(ids); + public void unregister(Identifier id) { + ClientPlayNetworking.unregisterGlobalReceiver(id); } } diff --git a/fabric-networking-v0/src/main/java/net/fabricmc/fabric/impl/networking/OldClientNetworkingHooks.java b/fabric-networking-v0/src/main/java/net/fabricmc/fabric/impl/networking/OldClientNetworkingHooks.java new file mode 100644 index 000000000..99ef6e531 --- /dev/null +++ b/fabric-networking-v0/src/main/java/net/fabricmc/fabric/impl/networking/OldClientNetworkingHooks.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.impl.networking; + +import net.fabricmc.api.ClientModInitializer; +import net.fabricmc.fabric.api.event.network.S2CPacketTypeCallback; +import net.fabricmc.fabric.api.client.networking.v1.C2SPlayChannelEvents; + +public final class OldClientNetworkingHooks implements ClientModInitializer { + @Override + public void onInitializeClient() { + // Must be lambdas below + C2SPlayChannelEvents.REGISTER.register((handler, client, sender, channels) -> S2CPacketTypeCallback.REGISTERED.invoker().accept(channels)); + C2SPlayChannelEvents.UNREGISTER.register((handler, client, sender, channels) -> S2CPacketTypeCallback.UNREGISTERED.invoker().accept(channels)); + } +} diff --git a/fabric-networking-v0/src/main/java/net/fabricmc/fabric/impl/networking/OldNetworkingHooks.java b/fabric-networking-v0/src/main/java/net/fabricmc/fabric/impl/networking/OldNetworkingHooks.java new file mode 100644 index 000000000..9ccc704ba --- /dev/null +++ b/fabric-networking-v0/src/main/java/net/fabricmc/fabric/impl/networking/OldNetworkingHooks.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.impl.networking; + +import net.fabricmc.api.ModInitializer; +import net.fabricmc.fabric.api.event.network.C2SPacketTypeCallback; +import net.fabricmc.fabric.api.networking.v1.S2CPlayChannelEvents; + +public final class OldNetworkingHooks implements ModInitializer { + @Override + public void onInitialize() { + // Must be lambdas below + S2CPlayChannelEvents.REGISTER.register((handler, server, sender, channels) -> { + C2SPacketTypeCallback.REGISTERED.invoker().accept(handler.player, channels); + }); + S2CPlayChannelEvents.UNREGISTER.register((handler, server, sender, channels) -> { + C2SPacketTypeCallback.UNREGISTERED.invoker().accept(handler.player, channels); + }); + } +} diff --git a/fabric-networking-v0/src/main/java/net/fabricmc/fabric/impl/networking/PacketRegistryImpl.java b/fabric-networking-v0/src/main/java/net/fabricmc/fabric/impl/networking/PacketRegistryImpl.java deleted file mode 100644 index ac54bfe24..000000000 --- a/fabric-networking-v0/src/main/java/net/fabricmc/fabric/impl/networking/PacketRegistryImpl.java +++ /dev/null @@ -1,206 +0,0 @@ -/* - * Copyright (c) 2016, 2017, 2018, 2019 FabricMC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.fabricmc.fabric.impl.networking; - -import java.nio.charset.StandardCharsets; -import java.util.Collection; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Optional; -import java.util.function.Supplier; - -import io.netty.buffer.Unpooled; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -import net.minecraft.network.Packet; -import net.minecraft.util.Identifier; -import net.minecraft.util.InvalidIdentifierException; -import net.minecraft.util.PacketByteBuf; - -import net.fabricmc.fabric.api.network.PacketConsumer; -import net.fabricmc.fabric.api.network.PacketContext; -import net.fabricmc.fabric.api.network.PacketRegistry; - -public abstract class PacketRegistryImpl implements PacketRegistry { - protected static final Logger LOGGER = LogManager.getLogger(); - protected final Map consumerMap; - - PacketRegistryImpl() { - consumerMap = new LinkedHashMap<>(); - } - - public static Optional> createInitialRegisterPacket(PacketRegistry registry) { - PacketRegistryImpl impl = (PacketRegistryImpl) registry; - return impl.createRegisterTypePacket(PacketTypes.REGISTER, impl.consumerMap.keySet()); - } - - @Override - public void register(Identifier id, PacketConsumer consumer) { - boolean isNew = true; - - if (consumerMap.containsKey(id)) { - LOGGER.warn("Registered duplicate packet " + id + "!"); - LOGGER.trace(new Throwable()); - isNew = false; - } - - consumerMap.put(id, consumer); - - if (isNew) { - onRegister(id); - } - } - - @Override - public void unregister(Identifier id) { - if (consumerMap.remove(id) != null) { - onUnregister(id); - } else { - LOGGER.warn("Tried to unregister non-registered packet " + id + "!"); - LOGGER.trace(new Throwable()); - } - } - - protected abstract void onRegister(Identifier id); - - protected abstract void onUnregister(Identifier id); - - protected abstract Collection getIdCollectionFor(PacketContext context); - - protected abstract void onReceivedRegisterPacket(PacketContext context, Collection ids); - - protected abstract void onReceivedUnregisterPacket(PacketContext context, Collection ids); - - protected Optional> createRegisterTypePacket(Identifier id, Collection ids) { - if (ids.isEmpty()) { - return Optional.empty(); - } - - PacketByteBuf buf = new PacketByteBuf(Unpooled.buffer()); - boolean first = true; - - for (Identifier a : ids) { - if (!first) { - buf.writeByte(0); - } else { - first = false; - } - - buf.writeBytes(a.toString().getBytes(StandardCharsets.US_ASCII)); - } - - return Optional.of(toPacket(id, buf)); - } - - private boolean acceptRegisterType(Identifier id, PacketContext context, Supplier bufSupplier) { - Collection ids = new HashSet<>(); - - { - StringBuilder sb = new StringBuilder(); - char c; - PacketByteBuf buf = bufSupplier.get(); - - try { - while (buf.readerIndex() < buf.writerIndex()) { - c = (char) buf.readByte(); - - if (c == 0) { - String s = sb.toString(); - - if (!s.isEmpty()) { - try { - ids.add(new Identifier(s)); - } catch (InvalidIdentifierException e) { - LOGGER.warn("Received invalid identifier in " + id + ": " + s + " (" + e.getLocalizedMessage() + ")"); - LOGGER.trace(e); - } - } - - sb = new StringBuilder(); - } else { - sb.append(c); - } - } - } finally { - buf.release(); - } - - String s = sb.toString(); - - if (!s.isEmpty()) { - try { - ids.add(new Identifier(s)); - } catch (InvalidIdentifierException e) { - LOGGER.warn("Received invalid identifier in " + id + ": " + s + " (" + e.getLocalizedMessage() + ")"); - LOGGER.trace(e); - } - } - } - - Collection target = getIdCollectionFor(context); - - if (id.equals(PacketTypes.UNREGISTER)) { - target.removeAll(ids); - onReceivedUnregisterPacket(context, ids); - } else { - target.addAll(ids); - onReceivedRegisterPacket(context, ids); - } - - return false; // continue execution for other mods - } - - /** - * Hook for accepting packets used in Fabric mixins. - * - *

    As PacketByteBuf getters in vanilla create a copy (to allow releasing the original packet buffer without - * breaking other, potentially delayed accesses), we use a Supplier to generate those copies and release them - * when needed. - * - * @param id The packet Identifier received. - * @param context The packet context provided. - * @param bufSupplier A supplier creating a new PacketByteBuf. - * @return Whether or not the packet was handled by this packet registry. - */ - public boolean accept(Identifier id, PacketContext context, Supplier bufSupplier) { - if (id.equals(PacketTypes.REGISTER) || id.equals(PacketTypes.UNREGISTER)) { - return acceptRegisterType(id, context, bufSupplier); - } - - PacketConsumer consumer = consumerMap.get(id); - - if (consumer != null) { - PacketByteBuf buf = bufSupplier.get(); - - try { - consumer.accept(context, buf); - } catch (Throwable t) { - LOGGER.warn("Failed to handle packet " + id + "!", t); - } finally { - if (buf.refCnt() > 0 && !PacketDebugOptions.DISABLE_BUFFER_RELEASES) { - buf.release(); - } - } - - return true; - } else { - return false; - } - } -} diff --git a/fabric-networking-v0/src/main/java/net/fabricmc/fabric/impl/networking/ServerSidePacketRegistryImpl.java b/fabric-networking-v0/src/main/java/net/fabricmc/fabric/impl/networking/ServerSidePacketRegistryImpl.java index 19176e950..b9238a504 100644 --- a/fabric-networking-v0/src/main/java/net/fabricmc/fabric/impl/networking/ServerSidePacketRegistryImpl.java +++ b/fabric-networking-v0/src/main/java/net/fabricmc/fabric/impl/networking/ServerSidePacketRegistryImpl.java @@ -16,74 +16,44 @@ package net.fabricmc.fabric.impl.networking; -import java.lang.ref.WeakReference; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Set; -import java.util.WeakHashMap; -import java.util.function.Consumer; +import java.util.Objects; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.GenericFutureListener; -import net.minecraft.client.network.packet.CustomPayloadS2CPacket; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.network.Packet; -import net.minecraft.server.network.ServerPlayNetworkHandler; -import net.minecraft.server.network.ServerPlayerEntity; -import net.minecraft.server.network.packet.LoginQueryResponseC2SPacket; -import net.minecraft.util.Identifier; import net.minecraft.util.PacketByteBuf; +import net.minecraft.client.network.packet.CustomPayloadS2CPacket; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.util.Identifier; +import net.minecraft.util.thread.ThreadExecutor; -import net.fabricmc.fabric.api.event.network.C2SPacketTypeCallback; +import net.fabricmc.api.EnvType; +import net.fabricmc.fabric.api.network.PacketConsumer; import net.fabricmc.fabric.api.network.PacketContext; +import net.fabricmc.fabric.api.network.PacketRegistry; import net.fabricmc.fabric.api.network.ServerSidePacketRegistry; +import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; -public class ServerSidePacketRegistryImpl extends PacketRegistryImpl implements ServerSidePacketRegistry { - private final WeakHashMap> playerPayloadIds = new WeakHashMap<>(); - private final Set> handlers = new HashSet<>(); - - public void onQueryResponse(LoginQueryResponseC2SPacket packet) { - } - - public void addNetworkHandler(ServerPlayNetworkHandler handler) { - handlers.add(new WeakReference<>(handler)); - } - - protected void forEachHandler(Consumer consumer) { - Iterator> it = handlers.iterator(); - - while (it.hasNext()) { - ServerPlayNetworkHandler server = it.next().get(); - - if (server != null) { - consumer.accept(server); - } else { - it.remove(); - } - } - } - +public class ServerSidePacketRegistryImpl implements ServerSidePacketRegistry, PacketRegistry { @Override public boolean canPlayerReceive(PlayerEntity player, Identifier id) { - Collection ids = playerPayloadIds.get(player); - - if (ids != null) { - return ids.contains(id); - } else { - return false; + if (player instanceof ServerPlayerEntity) { + return ServerPlayNetworking.canSend((ServerPlayerEntity) player, id); } + + return false; } @Override public void sendToPlayer(PlayerEntity player, Packet packet, GenericFutureListener> completionListener) { - if (!(player instanceof ServerPlayerEntity)) { - throw new RuntimeException("Can only send to ServerPlayerEntities!"); - } else { + if (player instanceof ServerPlayerEntity) { ((ServerPlayerEntity) player).networkHandler.sendPacket(packet, completionListener); + return; } + + throw new RuntimeException("Can only send to ServerPlayerEntities!"); } @Override @@ -92,29 +62,31 @@ public class ServerSidePacketRegistryImpl extends PacketRegistryImpl implements } @Override - protected void onRegister(Identifier id) { - createRegisterTypePacket(PacketTypes.REGISTER, Collections.singleton(id)) - .ifPresent((packet) -> forEachHandler((n) -> n.sendPacket(packet))); + public void register(Identifier id, PacketConsumer consumer) { + Objects.requireNonNull(consumer, "PacketConsumer cannot be null"); + + ServerPlayNetworking.registerGlobalReceiver(id, (server, player, handler, buf, sender) -> { + consumer.accept(new PacketContext() { + @Override + public EnvType getPacketEnvironment() { + return EnvType.SERVER; + } + + @Override + public PlayerEntity getPlayer() { + return player; + } + + @Override + public ThreadExecutor getTaskQueue() { + return server; + } + }, buf); + }); } @Override - protected void onUnregister(Identifier id) { - createRegisterTypePacket(PacketTypes.UNREGISTER, Collections.singleton(id)) - .ifPresent((packet) -> forEachHandler((n) -> n.sendPacket(packet))); - } - - @Override - protected Collection getIdCollectionFor(PacketContext context) { - return playerPayloadIds.computeIfAbsent(context.getPlayer(), (p) -> new HashSet<>()); - } - - @Override - protected void onReceivedRegisterPacket(PacketContext context, Collection ids) { - C2SPacketTypeCallback.REGISTERED.invoker().accept(context.getPlayer(), ids); - } - - @Override - protected void onReceivedUnregisterPacket(PacketContext context, Collection ids) { - C2SPacketTypeCallback.UNREGISTERED.invoker().accept(context.getPlayer(), ids); + public void unregister(Identifier id) { + ServerPlayNetworking.unregisterGlobalReceiver(id); } } diff --git a/fabric-networking-v0/src/main/java/net/fabricmc/fabric/mixin/networking/MixinClientPlayNetworkHandler.java b/fabric-networking-v0/src/main/java/net/fabricmc/fabric/mixin/networking/MixinClientPlayNetworkHandler.java deleted file mode 100644 index 42a702184..000000000 --- a/fabric-networking-v0/src/main/java/net/fabricmc/fabric/mixin/networking/MixinClientPlayNetworkHandler.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright (c) 2016, 2017, 2018, 2019 FabricMC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.fabricmc.fabric.mixin.networking; - -import java.util.Optional; - -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -import org.spongepowered.asm.mixin.injection.callback.LocalCapture; - -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.network.ClientPlayNetworkHandler; -import net.minecraft.client.network.packet.CustomPayloadS2CPacket; -import net.minecraft.client.network.packet.GameJoinS2CPacket; -import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.network.Packet; -import net.minecraft.util.Identifier; -import net.minecraft.util.PacketByteBuf; -import net.minecraft.util.thread.ThreadExecutor; - -import net.fabricmc.api.EnvType; -import net.fabricmc.fabric.api.network.ClientSidePacketRegistry; -import net.fabricmc.fabric.api.network.PacketContext; -import net.fabricmc.fabric.impl.networking.ClientSidePacketRegistryImpl; -import net.fabricmc.fabric.impl.networking.PacketRegistryImpl; -import net.fabricmc.fabric.impl.networking.PacketTypes; - -@Mixin(ClientPlayNetworkHandler.class) -public abstract class MixinClientPlayNetworkHandler implements PacketContext { - @Shadow - private MinecraftClient client; - - @Shadow - public abstract void sendPacket(Packet var1); - - @Inject(at = @At("RETURN"), method = "onGameJoin") - public void onGameJoin(GameJoinS2CPacket packet, CallbackInfo info) { - Optional> optionalPacket = PacketRegistryImpl.createInitialRegisterPacket(ClientSidePacketRegistry.INSTANCE); - - //noinspection OptionalIsPresent - if (optionalPacket.isPresent()) { - sendPacket(optionalPacket.get()); - } - } - - // Optional hook: it only removes a warning message. - @Inject(method = "onCustomPayload", at = @At(value = "CONSTANT", args = "stringValue=Unknown custom packed identifier: {}"), cancellable = true, locals = LocalCapture.CAPTURE_FAILSOFT, require = 0) - public void onCustomPayloadNotFound(CustomPayloadS2CPacket packet, CallbackInfo info, Identifier id, PacketByteBuf buf) { - if (packet.getChannel().equals(PacketTypes.REGISTER) || packet.getChannel().equals(PacketTypes.UNREGISTER)) { - if (buf.refCnt() > 0) { - buf.release(); - } - - info.cancel(); - } - } - - @Inject(method = "onCustomPayload", at = @At("HEAD"), cancellable = true) - public void onCustomPayload(CustomPayloadS2CPacket packet, CallbackInfo info) { - if (((ClientSidePacketRegistryImpl) ClientSidePacketRegistry.INSTANCE).accept(packet.getChannel(), this, packet::getData)) { - info.cancel(); - } - } - - @Override - public EnvType getPacketEnvironment() { - return EnvType.CLIENT; - } - - @Override - public PlayerEntity getPlayer() { - return client.player; - } - - @Override - public ThreadExecutor getTaskQueue() { - return client; - } -} diff --git a/fabric-networking-v0/src/main/java/net/fabricmc/fabric/mixin/networking/MixinPlayerManager.java b/fabric-networking-v0/src/main/java/net/fabricmc/fabric/mixin/networking/MixinPlayerManager.java deleted file mode 100644 index 4fddae1f2..000000000 --- a/fabric-networking-v0/src/main/java/net/fabricmc/fabric/mixin/networking/MixinPlayerManager.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2016, 2017, 2018, 2019 FabricMC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.fabricmc.fabric.mixin.networking; - -import java.util.Optional; - -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -import net.minecraft.network.ClientConnection; -import net.minecraft.network.Packet; -import net.minecraft.server.PlayerManager; -import net.minecraft.server.network.ServerPlayerEntity; - -import net.fabricmc.fabric.api.network.ServerSidePacketRegistry; -import net.fabricmc.fabric.impl.networking.PacketRegistryImpl; -import net.fabricmc.fabric.impl.networking.ServerSidePacketRegistryImpl; - -@Mixin(priority = 500, value = PlayerManager.class) -public abstract class MixinPlayerManager { - @Inject(method = "onPlayerConnect", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/network/packet/DifficultyS2CPacket;(Lnet/minecraft/world/Difficulty;Z)V")) - public void onPlayerConnect(ClientConnection connection, ServerPlayerEntity player, CallbackInfo info) { - Optional> optionalPacket = PacketRegistryImpl.createInitialRegisterPacket(ServerSidePacketRegistry.INSTANCE); - - //noinspection OptionalIsPresent - if (optionalPacket.isPresent()) { - player.networkHandler.sendPacket(optionalPacket.get()); - ((ServerSidePacketRegistryImpl) ServerSidePacketRegistry.INSTANCE).addNetworkHandler(player.networkHandler); - } - } -} diff --git a/fabric-networking-v0/src/main/java/net/fabricmc/fabric/mixin/networking/MixinServerPlayNetworkHandler.java b/fabric-networking-v0/src/main/java/net/fabricmc/fabric/mixin/networking/MixinServerPlayNetworkHandler.java deleted file mode 100644 index 1f7e52c13..000000000 --- a/fabric-networking-v0/src/main/java/net/fabricmc/fabric/mixin/networking/MixinServerPlayNetworkHandler.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (c) 2016, 2017, 2018, 2019 FabricMC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.fabricmc.fabric.mixin.networking; - -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.server.MinecraftServer; -import net.minecraft.server.network.ServerPlayNetworkHandler; -import net.minecraft.server.network.ServerPlayerEntity; -import net.minecraft.server.network.packet.CustomPayloadC2SPacket; -import net.minecraft.util.Identifier; -import net.minecraft.util.thread.ThreadExecutor; - -import net.fabricmc.api.EnvType; -import net.fabricmc.fabric.api.network.PacketContext; -import net.fabricmc.fabric.api.network.ServerSidePacketRegistry; -import net.fabricmc.fabric.impl.networking.CustomPayloadC2SPacketAccessor; -import net.fabricmc.fabric.impl.networking.ServerSidePacketRegistryImpl; - -@Mixin(ServerPlayNetworkHandler.class) -public class MixinServerPlayNetworkHandler implements PacketContext { - @Shadow - private MinecraftServer server; - @Shadow - private ServerPlayerEntity player; - - @Inject(method = "onCustomPayload", at = @At("HEAD"), cancellable = true) - public void onCustomPayload(CustomPayloadC2SPacket packet, CallbackInfo info) { - Identifier channel = ((CustomPayloadC2SPacketAccessor) packet).getChannel(); - - if (((ServerSidePacketRegistryImpl) ServerSidePacketRegistry.INSTANCE).accept(channel, this, ((CustomPayloadC2SPacketAccessor) packet)::getData)) { - info.cancel(); - } - } - - @Override - public EnvType getPacketEnvironment() { - return EnvType.SERVER; - } - - @Override - public PlayerEntity getPlayer() { - return player; - } - - @Override - public ThreadExecutor getTaskQueue() { - return server; - } -} diff --git a/fabric-networking-v0/src/main/resources/fabric-networking-v0.mixins.json b/fabric-networking-v0/src/main/resources/fabric-networking-v0.mixins.json deleted file mode 100644 index 2133ec325..000000000 --- a/fabric-networking-v0/src/main/resources/fabric-networking-v0.mixins.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "required": true, - "package": "net.fabricmc.fabric.mixin.networking", - "compatibilityLevel": "JAVA_8", - "mixins": [ - "MixinCustomPayloadC2SPacket", - "MixinEntityTracker", - "MixinPlayerManager", - "MixinServerPlayNetworkHandler", - "MixinThreadedAnvilChunkStorage" - ], - "client": [ - "MixinClientPlayNetworkHandler", - "MixinMinecraftClient" - ], - "injectors": { - "defaultRequire": 1 - } -} diff --git a/fabric-networking-v0/src/main/resources/fabric.mod.json b/fabric-networking-v0/src/main/resources/fabric.mod.json index 40d037b1a..b5c186463 100644 --- a/fabric-networking-v0/src/main/resources/fabric.mod.json +++ b/fabric-networking-v0/src/main/resources/fabric.mod.json @@ -15,12 +15,18 @@ "authors": [ "FabricMC" ], + "entrypoints": { + "main": [ + "net.fabricmc.fabric.impl.networking.OldNetworkingHooks" + ], + "client": [ + "net.fabricmc.fabric.impl.networking.OldClientNetworkingHooks" + ] + }, "depends": { "fabricloader": ">=0.4.0", - "fabric-api-base": "*" + "fabric-api-base": "*", + "fabric-networking-api-v1": "*" }, - "description": "Networking packet hooks and registries.", - "mixins": [ - "fabric-networking-v0.mixins.json" - ] + "description": "Legacy Networking packet hooks and registries, superseded by fabric-networking-api-v1." } diff --git a/fabric-registry-sync-v0/build.gradle b/fabric-registry-sync-v0/build.gradle index 2f7742aca..618a0e652 100644 --- a/fabric-registry-sync-v0/build.gradle +++ b/fabric-registry-sync-v0/build.gradle @@ -1,7 +1,7 @@ archivesBaseName = "fabric-registry-sync-v0" -version = getSubprojectVersion(project, "0.2.6") +version = getSubprojectVersion(project, "0.7.1") dependencies { compile project(path: ':fabric-api-base', configuration: 'dev') - compile project(path: ':fabric-networking-v0', configuration: 'dev') + compile project(path: ':fabric-networking-api-v1', configuration: 'dev') } diff --git a/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/impl/registry/sync/FabricRegistryClientInit.java b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/impl/registry/sync/FabricRegistryClientInit.java index e54964acc..b8469433d 100644 --- a/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/impl/registry/sync/FabricRegistryClientInit.java +++ b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/impl/registry/sync/FabricRegistryClientInit.java @@ -19,26 +19,23 @@ package net.fabricmc.fabric.impl.registry.sync; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.network.ClientPlayerEntity; import net.minecraft.text.LiteralText; import net.fabricmc.api.ClientModInitializer; -import net.fabricmc.fabric.api.network.ClientSidePacketRegistry; +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; public class FabricRegistryClientInit implements ClientModInitializer { private static final Logger LOGGER = LogManager.getLogger(); @Override public void onInitializeClient() { - ClientSidePacketRegistry.INSTANCE.register(RegistrySyncManager.ID, (ctx, buf) -> { + ClientPlayNetworking.registerGlobalReceiver(RegistrySyncManager.ID, (client, handler, buf, responseSender) -> { // if not hosting server, apply packet - RegistrySyncManager.receivePacket(ctx, buf, RegistrySyncManager.DEBUG || !MinecraftClient.getInstance().isInSingleplayer(), (e) -> { + RegistrySyncManager.receivePacket(client, buf, RegistrySyncManager.DEBUG || client.isInSingleplayer(), (e) -> { LOGGER.error("Registry remapping failed!", e); - MinecraftClient.getInstance().execute(() -> { - ((ClientPlayerEntity) ctx.getPlayer()).networkHandler.getConnection().disconnect( - new LiteralText("Registry remapping failed: " + e.getMessage()) - ); + + client.execute(() -> { + handler.getConnection().disconnect(new LiteralText("Registry remapping failed: " + e.getMessage())); }); }); }); diff --git a/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/impl/registry/sync/RegistrySyncManager.java b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/impl/registry/sync/RegistrySyncManager.java index 51118eef8..783833ece 100644 --- a/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/impl/registry/sync/RegistrySyncManager.java +++ b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/impl/registry/sync/RegistrySyncManager.java @@ -43,9 +43,9 @@ import net.minecraft.util.Identifier; import net.minecraft.util.PacketByteBuf; import net.minecraft.util.registry.MutableRegistry; import net.minecraft.util.registry.Registry; +import net.minecraft.util.thread.ThreadExecutor; -import net.fabricmc.fabric.api.network.PacketContext; -import net.fabricmc.fabric.api.network.ServerSidePacketRegistry; +import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; public final class RegistrySyncManager { static final boolean DEBUG = System.getProperty("fabric.registry.debug", "false").equalsIgnoreCase("true"); @@ -61,15 +61,15 @@ public final class RegistrySyncManager { PacketByteBuf buf = new PacketByteBuf(Unpooled.buffer()); buf.writeCompoundTag(toTag(true)); - return ServerSidePacketRegistry.INSTANCE.toPacket(ID, buf); + return ServerPlayNetworking.createS2CPacket(ID, buf); } - public static void receivePacket(PacketContext context, PacketByteBuf buf, boolean accept, Consumer errorHandler) { + public static void receivePacket(ThreadExecutor executor, PacketByteBuf buf, boolean accept, Consumer errorHandler) { CompoundTag compound = buf.readCompoundTag(); if (accept) { try { - context.getTaskQueue().submit(() -> { + executor.submit(() -> { if (compound == null) { errorHandler.accept(new RemapException("Received null compound tag in sync packet!")); return null; diff --git a/fabric-registry-sync-v0/src/main/resources/fabric.mod.json b/fabric-registry-sync-v0/src/main/resources/fabric.mod.json index 56a6f76ff..18b149852 100644 --- a/fabric-registry-sync-v0/src/main/resources/fabric.mod.json +++ b/fabric-registry-sync-v0/src/main/resources/fabric.mod.json @@ -18,7 +18,7 @@ "depends": { "fabricloader": ">=0.4.0", "fabric-api-base": "*", - "fabric-networking-v0": "*" + "fabric-networking-api-v1": "*" }, "description": "Syncs registry mappings.", "mixins": [ diff --git a/settings.gradle b/settings.gradle index d3d94dfe1..9f1c4e630 100644 --- a/settings.gradle +++ b/settings.gradle @@ -33,6 +33,7 @@ include 'fabric-loot-tables-v1' include 'fabric-mining-levels-v0' include 'fabric-models-v0' include 'fabric-networking-v0' +include 'fabric-networking-api-v1' include 'fabric-networking-blockentity-v0' include 'fabric-object-builder-api-v1' include 'fabric-object-builders-v0'