Fabric Networking API V1 (#1081)

* Networking api v1




Some final docs?


Licenses and testmod


Fix a bunch o imports and make things work for v1 (v0 is bork)


Make the testmod pass checkstyle and work


Docs for v1

* Deprecate v0 and implement using v1

* Drop files down one package due to package check error

* Fix issue with channel registration, add another testmod

* jaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaavadoc

* Make javadoc use `code`, move impl interface to package access

* this things

* Rename a few internal methods

* Mark all client side stuff client only, move client mixins

* Add null checks around the place, clarify some javadoc and method names

* Make FutureListeners uninstantiable

* Some internal nullable annotations

* An impl class I forgot to rename

* Some comments and clarify some client login handler javadoc

* Add a missing FunctionalInterface annotation

* Split play and login, move client stuff to right package

* No interface left behind

* Inline channel registries in api

* Login and play subpackages not needed

* Add helper method to create play custom packets

* hasGlobalChannel -> hasGlobalReceiver

* Just rename the collection method for now

* Inline PlayPacketSender into static methods

* Start on testmod idea for verifying dynamic registration

* Add client login events

* You don't say hello when talking to yourself.

Also more testmod stuff

* Make event names present tense

* Some javadoc and impl interface rename

* Change the test keybinding

* Begin working on dynamic reg

* Dynamic reg works, just need a lot of cleanup and reimpling global

* A few renames, readd global methods

* Try to reduce the amount of duplicate registration logic

* Reimplement dynamic accessors

* More impl

* Start reimplementing global receivers. Still very hacky solution.

* Reimplement some server global reciever stuff

* Add login init event for server login.

* Implement client login query start event

* Move event invocations into addon, don't dual register global recievers

* Finally reimplement global recievers for all networking phases

* A revelation: Send packets properly

This also finds the issue with screen getting the proper S2C channels, current on TODO list.

* Disconnect event does not need a packet sender

* Clarify, add methods to get channels net handler can recieve on client

* Unregister actually works now

* Bunch of null checks, add simpler login delay test for vanilla clients

* Add some debug logging entries, fix unregister on client's session reg

* Play channel event javadoc and rename login query handlers

* More channel -> channelName

* thisening

* Introduce the basics infrastructure for tracking global receivers

* Add more substantial javadoc to login connection events

* Javadoc, reimplement unreg methods on v0, 1 impl fix

* Implement tracking for global recievers

* Dont forget to start tracked sessions in 3/4 cases

* Global receiver docs and move methods in classes

* Complete null checks

* big boi javadoc part 1

* Finish the main javadoc, usage javadoc is left

* Set so has method is not needed

* Rename receiveable and sendable methods

* Add the two missing private ctors

* buildscript update to upstream

* Split out player finding stuff to networking player tracking API v1

Signed-off-by: liach <liach@users.noreply.github.com>

Forward v0 PlayerStream to new module, add entity track events

Rename module to player tracking

Well javadoc can make sense

Decide on tracking for the name

Update fabric-player-tracking-api-v1/src/main/java/net/fabricmc/fabric/api/networking/player/tracking/v1/package-info.java

Co-authored-by: Erlend Åmdal <erlend@aamdal.com>
Remove exceptions from javadoc that are not thrown

javadoc fix again

Handle a case where the player manager happens to be null

rename player tracking to player lookup

Yeet

* Cherrypick #1092

* Remove some redundant client networking methods, rename `(un)register` to `(un)registerReceiver`

* Simplify access to dynamic reg on client

* Param shifting, let users get sender.

* Warning about time and distance units

* Make sure these are client only

* Fix control flow in ClientPlayNetworking#send

* Correct example code javadoc

* javadoc correction in server login

* Put login delay tests behind system property

Also remove unnecessary junk added by old module that was merged together.

* Fix ordering so channel registrations during `PHASE`_INIT work

* Fix prod bug and an oversight

* Fix login when connecting to dedicated server

* Update registry sync to v0 to prevent issue with reg sync hanging client

* this is done
This commit is contained in:
i509VCB 2020-12-05 13:06:42 -06:00 committed by GitHub
parent ffb68a877b
commit 3775d21f76
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
93 changed files with 5307 additions and 678 deletions

View file

@ -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();

View file

@ -33,10 +33,10 @@ import net.minecraft.network.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();

View file

@ -0,0 +1,12 @@
archivesBaseName = "fabric-networking-api-v1"
version = getSubprojectVersion(project, "1.0.0")
moduleDependencies(project, [
'fabric-api-base'
])
dependencies {
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')
}

View file

@ -0,0 +1,105 @@
/*
* 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.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 {
/**
* An event for when the client's login process has begun.
* 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.
*
* <p>No packets should be sent when this event is invoked.
*/
public static final Event<LoginInit> LOGIN_INIT = EventFactory.createArrayBacked(LoginInit.class, callbacks -> (handler, client) -> {
for (LoginInit 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.
*
* <p>This event may be used to {@link ClientLoginNetworking.LoginQueryRequestHandler register login query handlers}
* which may be used to send a response to a server.
*
* <p>No packets should be sent when this event is invoked.
*/
public static final Event<LoginQueryStart> LOGIN_QUERY_START = EventFactory.createArrayBacked(LoginQueryStart.class, callbacks -> (handler, client) -> {
for (LoginQueryStart callback : callbacks) {
callback.onLoginQueryStart(handler, client);
}
});
/**
* An event for when the client's login process has ended due to disconnection.
*
* <p>No packets should be sent when this event is invoked.
*/
public static final Event<LoginDisconnect> LOGIN_DISCONNECT = EventFactory.createArrayBacked(LoginDisconnect.class, callbacks -> (handler, client) -> {
for (LoginDisconnect callback : callbacks) {
callback.onLoginDisconnect(handler, client);
}
});
private ClientLoginConnectionEvents() {
}
/**
* @see ClientLoginConnectionEvents#LOGIN_INIT
*/
@Environment(EnvType.CLIENT)
@FunctionalInterface
public interface LoginInit {
void onLoginStart(ClientLoginNetworkHandler handler, MinecraftClient client);
}
/**
* @see ClientLoginConnectionEvents#LOGIN_QUERY_START
*/
@Environment(EnvType.CLIENT)
@FunctionalInterface
public interface LoginQueryStart {
void onLoginQueryStart(ClientLoginNetworkHandler handler, MinecraftClient client);
}
/**
* @see ClientLoginConnectionEvents#LOGIN_DISCONNECT
*/
@Environment(EnvType.CLIENT)
@FunctionalInterface
public interface LoginDisconnect {
void onLoginDisconnect(ClientLoginNetworkHandler handler, MinecraftClient client);
}
}

View file

@ -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.network.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.
*
* <p>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.
*
* <p>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.</p>
*
* @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.
*
* <p>The {@code channel} is guaranteed not to have a handler after this call.</p>
*
* @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<Identifier> getGlobalReceivers() {
return ClientNetworkingImpl.LOGIN.getChannels();
}
/**
* Registers a handler to a query request channel.
*
* <p>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.</p>
*
* @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.
*
* <p>The {@code channelName} is guaranteed not to have a handler after this call.</p>
*
* @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.
*
* <p>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.
*
* <p>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<GenericFutureListener<? extends Future<? super Void>>> listenerAdder);
}
}

View file

@ -0,0 +1,69 @@
/*
* 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.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 {
/**
* An event for the initialization of the client play network handler.
*
* <p>At this stage, the network handler is ready to send packets to the server.
*/
public static final Event<PlayInit> PLAY_INIT = EventFactory.createArrayBacked(PlayInit.class, callbacks -> (handler, sender, client) -> {
for (PlayInit callback : callbacks) {
callback.onPlayInit(handler, sender, client);
}
});
/**
* An event for the disconnection of the client play network handler.
*
* <p>No packets should be sent when this event is invoked.
*/
public static final Event<PlayDisconnect> PLAY_DISCONNECT = EventFactory.createArrayBacked(PlayDisconnect.class, callbacks -> (handler, client) -> {
for (PlayDisconnect callback : callbacks) {
callback.onPlayDisconnect(handler, client);
}
});
private ClientPlayConnectionEvents() {
}
@Environment(EnvType.CLIENT)
@FunctionalInterface
public interface PlayInit {
void onPlayInit(ClientPlayNetworkHandler handler, PacketSender sender, MinecraftClient client);
}
@Environment(EnvType.CLIENT)
@FunctionalInterface
public interface PlayDisconnect {
void onPlayDisconnect(ClientPlayNetworkHandler handler, MinecraftClient client);
}
}

View file

@ -0,0 +1,245 @@
/*
* 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.network.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;
/**
* Offers access to play stage client-side networking functionalities.
*
* <p>Client-side networking functionalities include receiving clientbound packets,
* sending serverbound packets, and events related to client-side network handlers.
*
* <p>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.
*
* <p>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.</p>
*
* @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.
*
* <p>The {@code channel} is guaranteed not to have a handler after this call.</p>
*
* @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<Identifier> getGlobalReceivers() {
return ClientNetworkingImpl.PLAY.getChannels();
}
/**
* Registers a handler to a channel.
*
* <p>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.</p>
*
* @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
*/
public static boolean registerReceiver(Identifier channelName, PlayChannelHandler channelHandler) {
if (MinecraftClient.getInstance().getNetworkHandler() != null) {
return ClientNetworkingImpl.getAddon(MinecraftClient.getInstance().getNetworkHandler()).registerChannel(channelName, channelHandler);
}
throw new IllegalStateException("Cannot register receiver while not in game!");
}
/**
* Removes the handler of a channel.
*
* <p>The {@code channel} is guaranteed not to have a handler after this call.</p>
*
* @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 {
if (MinecraftClient.getInstance().getNetworkHandler() != null) {
return ClientNetworkingImpl.getAddon(MinecraftClient.getInstance().getNetworkHandler()).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<Identifier> getReceived() throws IllegalStateException {
if (MinecraftClient.getInstance().getNetworkHandler() != null) {
return ClientNetworkingImpl.getAddon(MinecraftClient.getInstance().getNetworkHandler()).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<Identifier> getSendable() throws IllegalStateException {
if (MinecraftClient.getInstance().getNetworkHandler() != null) {
return ClientNetworkingImpl.getAddon(MinecraftClient.getInstance().getNetworkHandler()).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
* @throws IllegalStateException if the client is not connected to a server
*/
public static boolean canSend(Identifier channelName) throws IllegalArgumentException {
if (MinecraftClient.getInstance().getNetworkHandler() != null) {
return ClientNetworkingImpl.getAddon(MinecraftClient.getInstance().getNetworkHandler()).getSendableChannels().contains(channelName);
}
throw new IllegalStateException("Cannot check whether the server can receive a packet while not in game!");
}
/**
* 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 {
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 {
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.
*
* <p>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.
*
* <p>An example usage of this is to display an overlay message:
* <pre>{@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);
* });
* });
* }</pre>
* @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);
}
}

View file

@ -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 S2CPlayChannelEvents {
/**
* 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> 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> UNREGISTER = EventFactory.createArrayBacked(Unregister.class, callbacks -> (handler, sender, client, channels) -> {
for (Unregister callback : callbacks) {
callback.onChannelUnregister(handler, sender, client, channels);
}
});
private S2CPlayChannelEvents() {
}
/**
* @see S2CPlayChannelEvents#REGISTER
*/
@Environment(EnvType.CLIENT)
@FunctionalInterface
public interface Register {
void onChannelRegister(ClientPlayNetworkHandler handler, PacketSender sender, MinecraftClient client, List<Identifier> channels);
}
/**
* @see S2CPlayChannelEvents#UNREGISTER
*/
@Environment(EnvType.CLIENT)
@FunctionalInterface
public interface Unregister {
void onChannelUnregister(ClientPlayNetworkHandler handler, PacketSender sender, MinecraftClient client, List<Identifier> channels);
}
}

View file

@ -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.
*
* <p>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}.
*
* <p>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.
*
* <p>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.S2CPlayChannelEvents}.
*/
package net.fabricmc.fabric.api.client.networking.v1;

View file

@ -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 C2SPlayChannelEvents {
/**
* 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> 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> UNREGISTER = EventFactory.createArrayBacked(Unregister.class, callbacks -> (handler, sender, server, channels) -> {
for (Unregister callback : callbacks) {
callback.onChannelUnregister(handler, sender, server, channels);
}
});
private C2SPlayChannelEvents() {
}
/**
* @see C2SPlayChannelEvents#REGISTER
*/
@FunctionalInterface
public interface Register {
void onChannelRegister(ServerPlayNetworkHandler handler, PacketSender sender, MinecraftServer server, List<Identifier> channels);
}
/**
* @see C2SPlayChannelEvents#UNREGISTER
*/
@FunctionalInterface
public interface Unregister {
void onChannelUnregister(ServerPlayNetworkHandler handler, PacketSender sender, MinecraftServer server, List<Identifier> channels);
}
}

View file

@ -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<StartTracking> 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<StopTracking> 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() {
}
}

View file

@ -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.network.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 <A> the future type of the first listener, used for casting
* @param <B> 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 <A extends Future<? super Void>, B extends Future<? super Void>> GenericFutureListener<? extends Future<? super Void>> union(GenericFutureListener<A> first, GenericFutureListener<B> 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() {
}
}

View file

@ -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.network.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() {
}
}

View file

@ -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.network.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<? extends Future<? super Void>> 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<? extends Future<? super Void>> callback) {
Objects.requireNonNull(channel, "Channel cannot be null");
Objects.requireNonNull(buf, "Payload cannot be null");
this.sendPacket(this.createPacket(channel, buf), callback);
}
}

View file

@ -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.
*
* <p>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.
*
* <p>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.
*
* <p>The returned collection is immutable.
*
* @param server the server
* @return all players on the server
*/
public static Collection<ServerPlayerEntity> 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.
*
* <p>The returned collection is immutable.
*
* @param world the server world
* @return the players in the server world
*/
public static Collection<ServerPlayerEntity> 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<ServerPlayerEntity> 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.
*
* <p>The returned collection is immutable.
*
* <p><b>Warning</b>: 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<ServerPlayerEntity> 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<ServerPlayerEntity> 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<ServerPlayerEntity> 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.
*
* <p>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<ServerPlayerEntity> 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.
*
* <p>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<ServerPlayerEntity> 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() {
}
}

View file

@ -0,0 +1,94 @@
/*
* 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 {
/**
* An event for the initialization 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)}.
*
* <p>No packets should be sent when this event is invoked.
*/
public static final Event<LoginInit> LOGIN_INIT = EventFactory.createArrayBacked(LoginInit.class, callbacks -> (handler, server) -> {
for (LoginInit 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.
*
* <p>You may send login queries to the connected client using the provided {@link PacketSender}.
*/
public static final Event<LoginQueryStart> LOGIN_QUERY_START = EventFactory.createArrayBacked(LoginQueryStart.class, callbacks -> (handler, server, sender, synchronizer) -> {
for (LoginQueryStart callback : callbacks) {
callback.onLoginStart(handler, server, sender, synchronizer);
}
});
/**
* An event for the disconnection of the server login network handler.
*
* <p>No packets should be sent when this event is invoked.
*/
public static final Event<LoginDisconnect> LOGIN_DISCONNECT = EventFactory.createArrayBacked(LoginDisconnect.class, callbacks -> (handler, server) -> {
for (LoginDisconnect callback : callbacks) {
callback.onLoginDisconnect(handler, server);
}
});
private ServerLoginConnectionEvents() {
}
/**
* @see ServerLoginConnectionEvents#LOGIN_INIT
*/
@FunctionalInterface
public interface LoginInit {
void onLoginInit(ServerLoginNetworkHandler handler, MinecraftServer server);
}
/**
* @see ServerLoginConnectionEvents#LOGIN_QUERY_START
*/
@FunctionalInterface
public interface LoginQueryStart {
void onLoginStart(ServerLoginNetworkHandler handler, MinecraftServer server, PacketSender sender, ServerLoginNetworking.LoginSynchronizer synchronizer);
}
/**
* @see ServerLoginConnectionEvents#LOGIN_DISCONNECT
*/
@FunctionalInterface
public interface LoginDisconnect {
void onLoginDisconnect(ServerLoginNetworkHandler handler, MinecraftServer server);
}
}

View file

@ -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.network.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.
*
* <p>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.
*
* <p>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.
*
* <p>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<Identifier> getGlobalReceivers() {
return ServerNetworkingImpl.LOGIN.getChannels();
}
/**
* Registers a handler to a query response channel.
*
* <p>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.</p>
*
* @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.
*
* <p>The {@code channelName} is guaranteed not to have a handler after this call.</p>
*
* @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 <i>Minecraft</i> 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.
*
* <p>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.
*
* <p><b>Whether the client understood the query should be checked before reading from the payload of the packet.</b>
* @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}.
*
* <p>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.</p>
*
* <p>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:
* <pre>{@code
* ServerLoginNetworking.registerGlobalReceiver(CHECK_CHANNEL, (server, handler, understood, buf, synchronizer, responseSender) -&gt; {
* 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(() -&gt; {
* 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));
* }));
* });
* }</pre>
* Usually it is enough to pass the return value for {@link net.minecraft.util.thread.ThreadExecutor#submit(Runnable)} for {@code future}.</p>
*
* @param future the future that must be done before the player can log in
*/
void waitFor(Future<?> future);
}
}

View file

@ -0,0 +1,63 @@
/*
* 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.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 {
/**
* An event for the initialization of the server play network handler.
*
* <p>At this stage, the network handler is ready to send packets to the client.
*/
public static final Event<PlayInit> PLAY_INIT = EventFactory.createArrayBacked(PlayInit.class, callbacks -> (handler, sender, server) -> {
for (PlayInit callback : callbacks) {
callback.onPlayInit(handler, sender, server);
}
});
/**
* An event for the disconnection of the server play network handler.
*
* <p>No packets should be sent when this event is invoked.</p>
*/
public static final Event<PlayDisconnect> PLAY_DISCONNECT = EventFactory.createArrayBacked(PlayDisconnect.class, callbacks -> (handler, server) -> {
for (PlayDisconnect callback : callbacks) {
callback.onPlayDisconnect(handler, server);
}
});
private ServerPlayConnectionEvents() {
}
@FunctionalInterface
public interface PlayInit {
void onPlayInit(ServerPlayNetworkHandler handler, PacketSender sender, MinecraftServer server);
}
@FunctionalInterface
public interface PlayDisconnect {
void onPlayDisconnect(ServerPlayNetworkHandler handler, MinecraftServer server);
}
}

View file

@ -0,0 +1,292 @@
/*
* 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.network.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.
*
* <p>Server-side networking functionalities include receiving serverbound packets, sending clientbound packets, and events related to server-side network handlers.
*
* <p>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.
*
* <p>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.</p>
*
* @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.
*
* <p>The {@code channel} is guaranteed not to have a handler after this call.</p>
*
* @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<Identifier> getGlobalReceivers() {
return ServerNetworkingImpl.PLAY.getChannels();
}
/**
* Registers a handler to a channel.
*
* <p>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.</p>
*
* @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
*/
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.
*
* <p>The {@code channelName} is guaranteed not to have a handler after this call.</p>
*
* @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<Identifier> 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<Identifier> 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<Identifier> 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<Identifier> 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 <i>Minecraft</i> 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.
*
* <p>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.
*
* <p>An example usage of this is to create an explosion where the player is looking:
* <pre>{@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);
* });
* });
* }</pre>
* @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);
}
}

View file

@ -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.
*
* <p>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}.
*
* <p>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.
*
* <p>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.C2SPlayChannelEvents}.
*/
package net.fabricmc.fabric.api.networking.v1;

View file

@ -0,0 +1,205 @@
/*
* 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.network.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 <H> the channel handler type
*/
public abstract class AbstractChanneledNetworkAddon<H> extends AbstractNetworkAddon<H> implements PacketSender {
protected final ClientConnection connection;
protected final GlobalReceiverRegistry<H> receiver;
protected final Set<Identifier> sendableChannels;
protected final Set<Identifier> sendableChannelsView;
protected AbstractChanneledNetworkAddon(GlobalReceiverRegistry<H> receiver, ClientConnection connection, String description) {
this(receiver, connection, new HashSet<>(), description);
}
protected AbstractChanneledNetworkAddon(GlobalReceiverRegistry<H> receiver, ClientConnection connection, Set<Identifier> sendableChannels, String description) {
super(receiver, description);
this.connection = connection;
this.receiver = receiver;
this.sendableChannels = sendableChannels;
this.sendableChannelsView = Collections.unmodifiableSet(sendableChannels);
}
protected void registerPendingChannels(ChannelInfoHolder holder) {
final Collection<Identifier> 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);
public void sendChannelRegistrationPacket() {
final PacketByteBuf buf = this.createRegistrationPacket(this.receiver.getChannels());
if (buf != null) {
this.sendPacket(NetworkingImpl.REGISTER_CHANNEL, buf);
}
}
@Nullable
protected PacketByteBuf createRegistrationPacket(Collection<Identifier> 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<Identifier> 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));
}
public void register(List<Identifier> ids) {
this.sendableChannels.addAll(ids);
this.invokeRegisterEvent(ids);
}
public void unregister(List<Identifier> 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<? extends Future<? super Void>> 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<Identifier> ids);
protected abstract void invokeUnregisterEvent(List<Identifier> ids);
private void addId(List<Identifier> 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<Identifier> getSendableChannels() {
return this.sendableChannelsView;
}
}

View file

@ -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 <H> the channel handler type
*/
public abstract class AbstractNetworkAddon<H> {
protected final GlobalReceiverRegistry<H> 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<Identifier, H> handlers = new HashMap<>();
protected AbstractNetworkAddon(GlobalReceiverRegistry<H> 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<Identifier> 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);
}

View file

@ -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<Identifier> getPendingChannelsNames();
}

View file

@ -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);
}

View file

@ -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<H> {
private final ReadWriteLock lock = new ReentrantReadWriteLock();
private final Map<Identifier, H> handlers;
private final Set<AbstractNetworkAddon<H>> trackedAddons = new HashSet<>();
public GlobalReceiverRegistry() {
this(new HashMap<>()); // sync map should be fine as there is little read write competitions
}
public GlobalReceiverRegistry(Map<Identifier, H> 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<Identifier, H> getHandlers() {
Lock lock = this.lock.writeLock();
lock.lock();
try {
return new HashMap<>(this.handlers);
} finally {
lock.unlock();
}
}
public Set<Identifier> 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<H> addon) {
Lock lock = this.lock.writeLock();
lock.lock();
try {
this.trackedAddons.add(addon);
} finally {
lock.unlock();
}
}
public void endSession(AbstractNetworkAddon<H> 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<H> 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<H> addon : this.trackedAddons) {
addon.unregisterChannel(channelName);
}
} finally {
lock.unlock();
}
}
}

View file

@ -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.network.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.LOGIN_QUERY_START.register((handler, server, sender, synchronizer) -> {
// Send early registration packet
PacketByteBuf buf = PacketByteBufs.create();
Collection<Identifier> 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<Identifier> 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);
}
}

View file

@ -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);
}

View file

@ -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<ServerPlayerEntity> fabric_getTrackingPlayers(Entity entity);
public interface ThreadedAnvilChunkStorageTrackingExtensions {
Collection<ServerPlayerEntity> fabric_getTrackingPlayers(Entity entity);
}

View file

@ -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.network.PacketByteBuf;
import net.minecraft.network.packet.c2s.login.LoginQueryResponseC2SPacket;
import net.minecraft.network.packet.s2c.login.LoginQueryRequestS2CPacket;
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<ClientLoginNetworking.LoginQueryRequestHandler> {
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.LOGIN_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<Identifier, ClientLoginNetworking.LoginQueryRequestHandler> entry : ClientNetworkingImpl.LOGIN.getHandlers().entrySet()) {
ClientLoginNetworking.registerReceiver(entry.getKey(), entry.getValue());
}
ClientLoginConnectionEvents.LOGIN_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<GenericFutureListener<? extends Future<? super Void>>> 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<? extends Future<? super Void>> listener = null;
for (GenericFutureListener<? extends Future<? super Void>> 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.LOGIN_DISCONNECT.invoker().onLoginDisconnect(this.handler, this.client);
this.receiver.endSession(this);
}
@Override
protected boolean isReservedChannel(Identifier channelName) {
return false;
}
}

View file

@ -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();
}

View file

@ -0,0 +1,111 @@
/*
* 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.network.PacketByteBuf;
import net.minecraft.network.packet.c2s.play.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.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<ClientLoginNetworking.LoginQueryRequestHandler> LOGIN = new GlobalReceiverRegistry<>();
public static final GlobalReceiverRegistry<ClientPlayNetworking.PlayChannelHandler> PLAY = new GlobalReceiverRegistry<>();
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;
}
public static void clientInit() {
// Register a login query handler for early channel registration.
ClientLoginNetworking.registerGlobalReceiver(NetworkingImpl.EARLY_REGISTRATION_CHANNEL, (client, handler, buf, listenerAdder) -> {
int n = buf.readVarInt();
List<Identifier> 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<Identifier> 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);
});
}
}

View file

@ -0,0 +1,146 @@
/*
* 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.network.PacketByteBuf;
import net.minecraft.network.packet.s2c.play.CustomPayloadS2CPacket;
import net.minecraft.util.Identifier;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.fabricmc.fabric.api.client.networking.v1.S2CPlayChannelEvents;
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<ClientPlayNetworking.PlayChannelHandler> {
private final ClientPlayNetworkHandler handler;
private final MinecraftClient client;
private boolean canSendPackets;
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);
}
// also expose sendRegistration
public void onServerReady() {
// Register global receivers
for (Map.Entry<Identifier, ClientPlayNetworking.PlayChannelHandler> entry : ClientNetworkingImpl.PLAY.getHandlers().entrySet()) {
this.registerChannel(entry.getKey(), entry.getValue());
}
this.sendChannelRegistrationPacket();
this.canSendPackets = true;
ClientPlayConnectionEvents.PLAY_INIT.invoker().onPlayInit(this.handler, this, this.client);
this.receiver.startSession(this);
}
/**
* Handles an incoming packet.
*
* @param packet the packet to handle
* @return true if the packet has been handled
*/
public boolean handle(CustomPayloadS2CPacket packet) {
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<Identifier> ids) {
S2CPlayChannelEvents.REGISTER.invoker().onChannelRegister(this.handler, this, this.client, ids);
}
@Override
protected void invokeUnregisterEvent(List<Identifier> ids) {
S2CPlayChannelEvents.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.canSendPackets) {
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.canSendPackets) {
final PacketByteBuf buf = this.createRegistrationPacket(Collections.singleton(channelName));
if (buf != null) {
this.sendPacket(NetworkingImpl.UNREGISTER_CHANNEL, buf);
}
}
}
@Override
public void invokeDisconnectEvent() {
ClientPlayConnectionEvents.PLAY_DISCONNECT.invoker().onPlayDisconnect(this.handler, this.client);
this.receiver.endSession(this);
}
@Override
protected boolean isReservedChannel(Identifier channelName) {
return NetworkingImpl.isReservedPlayChannel(channelName);
}
}

View file

@ -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();
}

View file

@ -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();
}

View file

@ -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.network.PacketByteBuf;
import net.minecraft.network.packet.c2s.login.LoginQueryResponseC2SPacket;
import net.minecraft.network.packet.s2c.login.LoginCompressionS2CPacket;
import net.minecraft.network.packet.s2c.login.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<ServerLoginNetworking.LoginQueryResponseHandler> implements PacketSender {
private final ClientConnection connection;
private final ServerLoginNetworkHandler handler;
private final MinecraftServer server;
private final QueryIdFactory queryIdFactory;
private final Collection<Future<?>> waits = new ConcurrentLinkedQueue<>();
private final Map<Integer, Identifier> channels = new ConcurrentHashMap<>();
private boolean firstQueryTick = true;
public ServerLoginNetworkAddon(ServerLoginNetworkHandler handler) {
super(ServerNetworkingImpl.LOGIN, "ServerLoginNetworkAddon for " + handler.getConnectionInfo());
this.connection = handler.connection;
this.handler = handler;
this.server = ((ServerLoginNetworkHandlerAccessor) handler).getServer();
this.queryIdFactory = QueryIdFactory.create();
ServerLoginConnectionEvents.LOGIN_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<Identifier, ServerLoginNetworking.LoginQueryResponseHandler> entry : ServerNetworkingImpl.LOGIN.getHandlers().entrySet()) {
ServerLoginNetworking.registerReceiver(this.handler, entry.getKey(), entry.getValue());
}
ServerLoginConnectionEvents.LOGIN_QUERY_START.invoker().onLoginStart(this.handler, this.server, this, this.waits::add);
this.firstQueryTick = false;
}
AtomicReference<Throwable> 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.setCompressionThreshold(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<? extends io.netty.util.concurrent.Future<? super Void>> 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.LOGIN_DISCONNECT.invoker().onLoginDisconnect(this.handler, this.server);
this.receiver.endSession(this);
}
@Override
protected boolean isReservedChannel(Identifier channelName) {
return false;
}
}

View file

@ -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<ServerPlayerEntity> fabric_getTrackingPlayers();
public interface ServerLoginNetworkHandlerExtensions {
ServerLoginNetworkAddon getAddon();
}

View file

@ -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.network.PacketByteBuf;
import net.minecraft.network.packet.s2c.play.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<ServerLoginNetworking.LoginQueryResponseHandler> LOGIN = new GlobalReceiverRegistry<>();
public static final GlobalReceiverRegistry<ServerPlayNetworking.PlayChannelHandler> 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);
}
}

View file

@ -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.server;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import net.minecraft.network.Packet;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.network.packet.c2s.play.CustomPayloadC2SPacket;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.network.ServerPlayNetworkHandler;
import net.minecraft.util.Identifier;
import net.fabricmc.fabric.api.networking.v1.C2SPlayChannelEvents;
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<ServerPlayNetworking.PlayChannelHandler> {
private final ServerPlayNetworkHandler handler;
private final MinecraftServer server;
private boolean canSendPackets = false;
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);
}
public void onClientReady() {
// Register global receivers
for (Map.Entry<Identifier, ServerPlayNetworking.PlayChannelHandler> entry : ServerNetworkingImpl.PLAY.getHandlers().entrySet()) {
this.registerChannel(entry.getKey(), entry.getValue());
}
this.sendChannelRegistrationPacket();
this.canSendPackets = true;
ServerPlayConnectionEvents.PLAY_INIT.invoker().onPlayInit(this.handler, this, this.server);
this.receiver.startSession(this);
}
/**
* Handles an incoming packet.
*
* @param packet the packet to handle
* @return true if the packet has been handled
*/
public boolean handle(CustomPayloadC2SPacket packet) {
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<Identifier> ids) {
C2SPlayChannelEvents.REGISTER.invoker().onChannelRegister(this.handler, this, this.server, ids);
}
@Override
protected void invokeUnregisterEvent(List<Identifier> ids) {
C2SPlayChannelEvents.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.canSendPackets) {
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.canSendPackets) {
final PacketByteBuf buf = this.createRegistrationPacket(Collections.singleton(channelName));
if (buf != null) {
this.sendPacket(NetworkingImpl.UNREGISTER_CHANNEL, buf);
}
}
}
@Override
public void invokeDisconnectEvent() {
ServerPlayConnectionEvents.PLAY_DISCONNECT.invoker().onPlayDisconnect(this.handler, this.server);
this.receiver.endSession(this);
}
@Override
protected boolean isReservedChannel(Identifier channelName) {
return NetworkingImpl.isReservedPlayChannel(channelName);
}
}

View file

@ -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();
}

View file

@ -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<? extends Future<? super Void>> callback);
@Shadow
public abstract void disconnect(Text disconnectReason);
@Unique
private Collection<Identifier> playChannels;
@Inject(method = "<init>", 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<? extends Future<? super Void>> 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<? extends Future<? super Void>> callback, CallbackInfo ci) {
if (this.packetListener instanceof PacketCallbackListener) {
((PacketCallbackListener) this.packetListener).sent(packet);
}
}
@Override
public Collection<Identifier> getPendingChannelsNames() {
return this.playChannels;
}
}

View file

@ -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<ServerPlayerEntity> playersTracking;
private Entity entity;
@Override
public Stream<ServerPlayerEntity> 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);
}
}

View file

@ -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/network/packet/s2c/play/CustomPayloadS2CPacket;<init>(Lnet/minecraft/util/Identifier;Lnet/minecraft/network/PacketByteBuf;)V"))
private void handlePlayerConnection(ClientConnection connection, ServerPlayerEntity player, CallbackInfo ci) {
ServerNetworkingImpl.getAddon(player.networkHandler).onClientReady();
}
}

View file

@ -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.network.packet.c2s.login.LoginQueryResponseC2SPacket;
import net.minecraft.network.packet.s2c.login.LoginDisconnectS2CPacket;
import net.minecraft.network.packet.s2c.login.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 = "<init>", 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);
}
}

View file

@ -0,0 +1,78 @@
/*
* 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.network.packet.c2s.play.CustomPayloadC2SPacket;
import net.minecraft.network.packet.s2c.play.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 connection;
@Unique
private ServerPlayNetworkAddon addon;
@Inject(method = "<init>", at = @At("RETURN"))
private void initAddon(CallbackInfo ci) {
this.addon = new ServerPlayNetworkAddon((ServerPlayNetworkHandler) (Object) this, this.server);
}
@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);
}
}

View file

@ -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<EntityTrackerStreamAccessor> 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<EntityTrackerAccessor> entityTrackers;
@Override
public Stream<ServerPlayerEntity> fabric_getTrackingPlayers(Entity entity) {
EntityTrackerStreamAccessor accessor = entityTrackers.get(entity.getEntityId());
return accessor != null ? accessor.fabric_getTrackingPlayers() : Stream.empty();
public Collection<ServerPlayerEntity> fabric_getTrackingPlayers(Entity entity) {
EntityTrackerAccessor accessor = this.entityTrackers.get(entity.getEntityId());
if (accessor != null) {
return accessor.getPlayersTracking();
}
return Collections.emptySet();
}
}

View file

@ -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();
}

View file

@ -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.network.PacketByteBuf;
import net.minecraft.network.packet.c2s.play.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();
}

View file

@ -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<ServerPlayerEntity> getPlayersTracking();
}

View file

@ -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.network.packet.c2s.play.CustomPayloadC2SPacket;
import net.minecraft.util.Identifier;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.network.packet.s2c.login.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;
}
@Override
public PacketByteBuf getData() {
return new PacketByteBuf(this.data.copy());
}
@Accessor
void setChannel(Identifier channel);
@Accessor
PacketByteBuf getPayload();
@Accessor
void setPayload(PacketByteBuf payload);
@Accessor
void setQueryId(int queryId);
}

View file

@ -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.network.PacketByteBuf;
import net.minecraft.network.packet.c2s.login.LoginQueryResponseC2SPacket;
@Mixin(LoginQueryResponseC2SPacket.class)
public interface LoginQueryResponseC2SPacketAccessor {
@Accessor
int getQueryId();
@Nullable
@Accessor
PacketByteBuf getResponse();
}

View file

@ -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 getConnection();
}

View file

@ -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();
}

View file

@ -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.network.packet.s2c.login.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 = "<init>", 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;
}
}

View file

@ -0,0 +1,73 @@
/*
* 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.network.packet.s2c.play.CustomPayloadS2CPacket;
import net.minecraft.network.packet.s2c.play.GameJoinS2CPacket;
import net.minecraft.text.Text;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
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 = "<init>", at = @At("RETURN"))
private void initAddon(CallbackInfo ci) {
this.addon = new ClientPlayNetworkAddon((ClientPlayNetworkHandler) (Object) this, this.client);
}
@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;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -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
}
}

View file

@ -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"
]
}

View file

@ -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() {
}
}

View file

@ -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.channeltest;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.widget.EntryListWidget;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.text.LiteralText;
import net.minecraft.util.Formatting;
import net.minecraft.util.Identifier;
final class ChannelList extends EntryListWidget<ChannelList.Entry> {
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<Entry> {
private final Identifier channel;
Entry(Identifier channel) {
this.channel = channel;
}
@Override
public void render(MatrixStack matrices, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float tickDelta) {
ChannelList.this.client.textRenderer.draw(matrices, new LiteralText(this.channel.toString()), x, y, Formatting.WHITE.getColorValue());
}
}
}

View file

@ -0,0 +1,99 @@
/*
* 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.client.util.math.MatrixStack;
import net.minecraft.text.LiteralText;
import net.minecraft.text.Text;
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, new LiteralText("S2C"), this::toS2C, (button, matrices, mouseX, mouseY) -> {
this.renderTooltip(matrices, new LiteralText("Packets this client can receive"), mouseX, mouseY);
}));
this.c2sButton = this.addButton(new ButtonWidget(this.width / 2 + 5, 5, 50, 20, new LiteralText("C2S"), this::toC2S, (button, matrices, mouseX, mouseY) -> {
this.renderTooltip(matrices, new LiteralText("Packets the server can receive"), mouseX, mouseY);
}));
this.closeButton = this.addButton(new ButtonWidget(this.width / 2 - 60, this.height - 25, 120, 20, new LiteralText("Close"), button -> this.onClose()));
this.channelList = this.addChild(new ChannelList(this.client, this.width, this.height - 60, 30, this.height - 30, this.textRenderer.fontHeight + 2));
}
@Override
public void render(MatrixStack matrices, int mouseX, int mouseY, float delta) {
this.renderBackgroundTexture(0);
this.channelList.render(matrices, mouseX, mouseY, delta);
super.render(matrices, mouseX, mouseY, delta);
if (this.s2cButton.active && this.c2sButton.active) {
final Text clickMe = new LiteralText("Click S2C or C2S to view supported channels");
final int textWidth = this.textRenderer.getWidth(clickMe);
//noinspection ConstantConditions
this.textRenderer.draw(
matrices,
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));
}
}
}

View file

@ -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.S2CPlayChannelEvents;
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<Identifier> 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));
}
}
});
S2CPlayChannelEvents.REGISTER.register((handler, sender, client, channels) -> {
SUPPORTED_C2S_CHANNELS.addAll(channels);
if (MinecraftClient.getInstance().currentScreen instanceof ChannelScreen) {
((ChannelScreen) MinecraftClient.getInstance().currentScreen).refresh();
}
});
S2CPlayChannelEvents.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.LOGIN_DISCONNECT.register((handler, client) -> {
SUPPORTED_C2S_CHANNELS.clear();
});
ClientPlayConnectionEvents.PLAY_DISCONNECT.register((handler, client) -> {
SUPPORTED_C2S_CHANNELS.clear();
});
}
}

View file

@ -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.argument.EntityArgumentType.getPlayer;
import static net.minecraft.command.argument.EntityArgumentType.player;
import static net.minecraft.command.argument.IdentifierArgumentType.getIdentifier;
import static net.minecraft.command.argument.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.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<ServerCommandSource> channelTestCommand = literal("network_channel_test").build();
// Info
{
final LiteralCommandNode<ServerCommandSource> info = literal("info")
.executes(context -> infoCommand(context, context.getSource().getPlayer()))
.build();
final ArgumentCommandNode<ServerCommandSource, EntitySelector> player = argument("player", player())
.executes(context -> infoCommand(context, getPlayer(context, "player")))
.build();
info.addChild(player);
channelTestCommand.addChild(info);
}
// Register
{
final LiteralCommandNode<ServerCommandSource> register = literal("register")
.then(argument("channel", identifier())
.executes(context -> registerChannel(context, context.getSource().getPlayer())))
.build();
channelTestCommand.addChild(register);
}
// Unregister
{
final LiteralCommandNode<ServerCommandSource> 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<Suggestions> suggestReceivableChannels(CommandContext<ServerCommandSource> context, SuggestionsBuilder builder) throws CommandSyntaxException {
final ServerPlayerEntity player = context.getSource().getPlayer();
return CommandSource.suggestIdentifiers(ServerPlayNetworking.getReceived(player), builder);
}
private static int registerChannel(CommandContext<ServerCommandSource> 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<ServerCommandSource> 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<ServerCommandSource> context, ServerPlayerEntity player) {
// TODO:
return 1;
}
}

View file

@ -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());
}
}
});
}
}

View file

@ -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.network.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.withFormatting(Formatting.BLUE))), false);
});
}
@Override
public void onInitialize() {
ServerPlayConnectionEvents.PLAY_INIT.register((handler, sender, server) -> {
ServerPlayNetworking.registerReceiver(handler, KEYBINDING_PACKET_ID, NetworkingKeybindPacketTest::receive);
});
}
}

View file

@ -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());
});
}
}

View file

@ -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.LOGIN_QUERY_START.register(this::onLoginStart);
ServerLoginConnectionEvents.LOGIN_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.getMainWorkerExecutor().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
}
}

View file

@ -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.network.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.PLAY_INIT.register((handler, sender, 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));
}
}

View file

@ -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.network.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<ServerCommandSource> 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);
});
}
}

View file

@ -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"
]
}
}

View file

@ -1,6 +1,7 @@
archivesBaseName = "fabric-networking-v0"
version = getSubprojectVersion(project, "0.1.10")
version = getSubprojectVersion(project, "0.2.0")
moduleDependencies(project, [
'fabric-api-base'
'fabric-api-base',
'fabric-networking-api-v1'
])

View file

@ -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.S2CPlayChannelEvents;
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;
*
* <p>Registrations received will be for <em>server -&gt; client</em> packets
* that the sending client can understand.
*
* @deprecated Please migrate to {@link S2CPlayChannelEvents}.
*/
@Deprecated
public interface C2SPacketTypeCallback {
/**
* @deprecated Please migrate to {@link S2CPlayChannelEvents#REGISTER}.
*/
@Deprecated
Event<C2SPacketTypeCallback> REGISTERED = EventFactory.createArrayBacked(
C2SPacketTypeCallback.class,
(callbacks) -> (client, types) -> {
@ -41,6 +49,10 @@ public interface C2SPacketTypeCallback {
}
);
/**
* @deprecated Please migrate to {@link S2CPlayChannelEvents#UNREGISTER}.
*/
@Deprecated
Event<C2SPacketTypeCallback> UNREGISTERED = EventFactory.createArrayBacked(
C2SPacketTypeCallback.class,
(callbacks) -> (client, types) -> {

View file

@ -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.C2SPlayChannelEvents;
/**
* Event for listening to packet type registration and unregistration notifications
@ -29,8 +30,15 @@ import net.fabricmc.fabric.api.event.EventFactory;
*
* <p>Registrations received will be for <em>client -&gt; server</em> packets
* that the sending server can understand.
*
* @deprecated Please migrate to {@link C2SPlayChannelEvents}.
*/
@Deprecated
public interface S2CPacketTypeCallback {
/**
* @deprecated Please migrate to {@link C2SPlayChannelEvents#REGISTER}.
*/
@Deprecated
Event<S2CPacketTypeCallback> REGISTERED = EventFactory.createArrayBacked(
S2CPacketTypeCallback.class,
(callbacks) -> (types) -> {
@ -40,6 +48,10 @@ public interface S2CPacketTypeCallback {
}
);
/**
* @deprecated Please migrate to {@link C2SPlayChannelEvents#UNREGISTER}.
*/
@Deprecated
Event<S2CPacketTypeCallback> UNREGISTERED = EventFactory.createArrayBacked(
S2CPacketTypeCallback.class,
(callbacks) -> (types) -> {

View file

@ -23,6 +23,7 @@ import net.minecraft.network.Packet;
import net.minecraft.util.Identifier;
import net.minecraft.network.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;
*
* <ul><li>registering client-side packet receivers (server -&gt; client packets)
* <li>sending packets to the server (client -&gt; server packets).</ul>
*
* @deprecated Please migrate to {@link ClientPlayNetworking}.
*/
@Deprecated
public interface ClientSidePacketRegistry extends PacketRegistry {
ClientSidePacketRegistry INSTANCE = new ClientSidePacketRegistryImpl();

View file

@ -18,9 +18,15 @@ package net.fabricmc.fabric.api.network;
import net.minecraft.network.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 {
/**

View file

@ -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.

View file

@ -20,6 +20,7 @@ import net.minecraft.network.Packet;
import net.minecraft.util.Identifier;
import net.minecraft.network.PacketByteBuf;
@Deprecated
public interface PacketRegistry {
/**
* Turn a (identifier, byte buffer) pair into a "custom payload" packet

View file

@ -24,6 +24,7 @@ import net.minecraft.network.Packet;
import net.minecraft.util.Identifier;
import net.minecraft.network.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;
* <li>sending packets to clients (server -&gt; client packets).</ul>
*
* <p>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();

View file

@ -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.
*
* <p>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<PlayerEntity> 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<PlayerEntity> 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<PlayerEntity> 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<PlayerEntity> watching(BlockEntity entity) {

View file

@ -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.network.packet.c2s.play.CustomPayloadC2SPacket;
import net.minecraft.util.Identifier;
import net.minecraft.network.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<Identifier> 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<? extends Future<? super Void>> completionListener) {
ClientPlayNetworkHandler handler = MinecraftClient.getInstance().getNetworkHandler();
if (MinecraftClient.getInstance().getNetworkHandler() != null) {
MinecraftClient.getInstance().getNetworkHandler().getConnection().send(packet, completionListener);
return;
}
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!");
}
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
protected void onUnregister(Identifier id) {
ClientPlayNetworkHandler handler = MinecraftClient.getInstance().getNetworkHandler();
if (handler != null) {
createRegisterTypePacket(PacketTypes.UNREGISTER, Collections.singleton(id)).ifPresent(handler::sendPacket);
}
public PlayerEntity getPlayer() {
return client.player;
}
@Override
protected Collection<Identifier> getIdCollectionFor(PacketContext context) {
return serverPayloadIds;
public ThreadExecutor<?> getTaskQueue() {
return client;
}
}, buf);
});
}
@Override
protected void onReceivedRegisterPacket(PacketContext context, Collection<Identifier> ids) {
S2CPacketTypeCallback.REGISTERED.invoker().accept(ids);
}
@Override
protected void onReceivedUnregisterPacket(PacketContext context, Collection<Identifier> ids) {
S2CPacketTypeCallback.UNREGISTERED.invoker().accept(ids);
public void unregister(Identifier id) {
ClientPlayNetworking.unregisterGlobalReceiver(id);
}
}

View file

@ -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.S2CPlayChannelEvents;
public final class OldClientNetworkingHooks implements ClientModInitializer {
@Override
public void onInitializeClient() {
// Must be lambdas below
S2CPlayChannelEvents.REGISTER.register((handler, client, sender, channels) -> S2CPacketTypeCallback.REGISTERED.invoker().accept(channels));
S2CPlayChannelEvents.UNREGISTER.register((handler, client, sender, channels) -> S2CPacketTypeCallback.UNREGISTERED.invoker().accept(channels));
}
}

View file

@ -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.C2SPlayChannelEvents;
public final class OldNetworkingHooks implements ModInitializer {
@Override
public void onInitialize() {
// Must be lambdas below
C2SPlayChannelEvents.REGISTER.register((handler, server, sender, channels) -> {
C2SPacketTypeCallback.REGISTERED.invoker().accept(handler.player, channels);
});
C2SPlayChannelEvents.UNREGISTER.register((handler, server, sender, channels) -> {
C2SPacketTypeCallback.UNREGISTERED.invoker().accept(handler.player, channels);
});
}
}

View file

@ -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.network.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<Identifier, PacketConsumer> consumerMap;
PacketRegistryImpl() {
consumerMap = new LinkedHashMap<>();
}
public static Optional<Packet<?>> 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<Identifier> getIdCollectionFor(PacketContext context);
protected abstract void onReceivedRegisterPacket(PacketContext context, Collection<Identifier> ids);
protected abstract void onReceivedUnregisterPacket(PacketContext context, Collection<Identifier> ids);
protected Optional<Packet<?>> createRegisterTypePacket(Identifier id, Collection<Identifier> 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<PacketByteBuf> bufSupplier) {
Collection<Identifier> 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<Identifier> 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.
*
* <p>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<PacketByteBuf> 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;
}
}
}

View file

@ -16,14 +16,7 @@
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;
@ -31,59 +24,36 @@ import io.netty.util.concurrent.GenericFutureListener;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.network.Packet;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.network.packet.c2s.login.LoginQueryResponseC2SPacket;
import net.minecraft.network.packet.s2c.play.CustomPayloadS2CPacket;
import net.minecraft.server.network.ServerPlayNetworkHandler;
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<PlayerEntity, Collection<Identifier>> playerPayloadIds = new WeakHashMap<>();
private final Set<WeakReference<ServerPlayNetworkHandler>> handlers = new HashSet<>();
public void onQueryResponse(LoginQueryResponseC2SPacket packet) {
}
public void addNetworkHandler(ServerPlayNetworkHandler handler) {
handlers.add(new WeakReference<>(handler));
}
protected void forEachHandler(Consumer<ServerPlayNetworkHandler> consumer) {
Iterator<WeakReference<ServerPlayNetworkHandler>> 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<Identifier> 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<? extends Future<? super Void>> 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
protected void onUnregister(Identifier id) {
createRegisterTypePacket(PacketTypes.UNREGISTER, Collections.singleton(id))
.ifPresent((packet) -> forEachHandler((n) -> n.sendPacket(packet)));
public PlayerEntity getPlayer() {
return player;
}
@Override
protected Collection<Identifier> getIdCollectionFor(PacketContext context) {
return playerPayloadIds.computeIfAbsent(context.getPlayer(), (p) -> new HashSet<>());
public ThreadExecutor<?> getTaskQueue() {
return server;
}
}, buf);
});
}
@Override
protected void onReceivedRegisterPacket(PacketContext context, Collection<Identifier> ids) {
C2SPacketTypeCallback.REGISTERED.invoker().accept(context.getPlayer(), ids);
}
@Override
protected void onReceivedUnregisterPacket(PacketContext context, Collection<Identifier> ids) {
C2SPacketTypeCallback.UNREGISTERED.invoker().accept(context.getPlayer(), ids);
public void unregister(Identifier id) {
ServerPlayNetworking.unregisterGlobalReceiver(id);
}
}

View file

@ -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.network.packet.s2c.play.CustomPayloadS2CPacket;
import net.minecraft.network.packet.s2c.play.GameJoinS2CPacket;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.network.Packet;
import net.minecraft.util.Identifier;
import net.minecraft.network.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<Packet<?>> 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;
}
}

View file

@ -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/network/packet/s2c/play/DifficultyS2CPacket;<init>(Lnet/minecraft/world/Difficulty;Z)V"))
public void onPlayerConnect(ClientConnection connection, ServerPlayerEntity player, CallbackInfo info) {
Optional<Packet<?>> optionalPacket = PacketRegistryImpl.createInitialRegisterPacket(ServerSidePacketRegistry.INSTANCE);
//noinspection OptionalIsPresent
if (optionalPacket.isPresent()) {
player.networkHandler.sendPacket(optionalPacket.get());
((ServerSidePacketRegistryImpl) ServerSidePacketRegistry.INSTANCE).addNetworkHandler(player.networkHandler);
}
}
}

View file

@ -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.network.packet.c2s.play.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;
}
}

View file

@ -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
}
}

View file

@ -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."
}

View file

@ -3,5 +3,5 @@ version = getSubprojectVersion(project, "0.7.1")
moduleDependencies(project, [
'fabric-api-base',
'fabric-networking-v0'
'fabric-networking-api-v1'
])

View file

@ -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()));
});
});
});

View file

@ -42,11 +42,11 @@ import net.minecraft.network.Packet;
import net.minecraft.util.Identifier;
import net.minecraft.network.PacketByteBuf;
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.event.registry.RegistryAttribute;
import net.fabricmc.fabric.api.event.registry.RegistryAttributeHolder;
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking;
public final class RegistrySyncManager {
static final boolean DEBUG = System.getProperty("fabric.registry.debug", "false").equalsIgnoreCase("true");
@ -71,15 +71,15 @@ public final class RegistrySyncManager {
PacketByteBuf buf = new PacketByteBuf(Unpooled.buffer());
buf.writeCompoundTag(tag);
return ServerSidePacketRegistry.INSTANCE.toPacket(ID, buf);
return ServerPlayNetworking.createS2CPacket(ID, buf);
}
public static void receivePacket(PacketContext context, PacketByteBuf buf, boolean accept, Consumer<Exception> errorHandler) {
public static void receivePacket(ThreadExecutor<?> executor, PacketByteBuf buf, boolean accept, Consumer<Exception> 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;

View file

@ -18,7 +18,7 @@
"depends": {
"fabricloader": ">=0.9.2",
"fabric-api-base": "*",
"fabric-networking-v0": "*"
"fabric-networking-api-v1": "*"
},
"description": "Syncs registry mappings.",
"mixins": [

View file

@ -34,6 +34,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'