mirror of
https://github.com/FabricMC/fabric.git
synced 2025-07-28 15:09:35 -04:00
[1.20.2] Support common registration packets. Add configuration task API (#3244)
* Config networking refactor :) * Add some unit tests for common packets. * write FabricPacket on network thread. Split ServerConfigurationConnectionEvents into two. * Fixes * Rename event * Add a testmod + ssome docs * Improve registry sync fixing deadlock in a number of cases. * Cleanup channel events. * Review feedback and fixes.
This commit is contained in:
parent
86b12645b9
commit
0b2eb405dc
41 changed files with 1642 additions and 303 deletions
build.gradle
fabric-networking-api-v1/src
client/java/net/fabricmc/fabric
api/client/networking/v1
C2SConfigurationChannelEvents.javaC2SPlayChannelEvents.javaClientConfigurationNetworking.javaClientPlayNetworking.java
impl/networking/client
main
java/net/fabricmc/fabric
api/networking/v1
FabricPacket.javaFabricServerConfigurationNetworkHandler.javaPacketSender.javaS2CConfigurationChannelEvents.javaServerConfigurationConnectionEvents.javaServerConfigurationNetworking.javaServerPlayNetworking.java
impl/networking
AbstractChanneledNetworkAddon.javaCommonPacketHandler.javaCommonPacketsImpl.javaCommonRegisterPayload.javaCommonVersionPayload.javaGlobalReceiverRegistry.javaNetworkingImpl.java
payload
server
mixin/networking
resources
test/java/net/fabricmc/fabric/test/networking/unit
testmod
java/net/fabricmc/fabric/test/networking/configuration
resources
testmodClient/java/net/fabricmc/fabric/test/networking/client/configuration
fabric-recipe-api-v1/src/main/java/net/fabricmc/fabric/impl/recipe/ingredient
fabric-registry-sync-v0/src
client/java/net/fabricmc/fabric/impl/client/registry/sync
main/java/net/fabricmc/fabric/impl/registry/sync
|
@ -223,6 +223,7 @@ allprojects {
|
|||
|
||||
testImplementation "net.fabricmc:fabric-loader-junit:${project.loader_version}"
|
||||
testImplementation sourceSets.testmodClient.output
|
||||
testImplementation 'org.mockito:mockito-core:5.4.0'
|
||||
}
|
||||
|
||||
test {
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* 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.ClientConfigurationNetworkHandler;
|
||||
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.PacketSender;
|
||||
|
||||
/**
|
||||
* Offers access to events related to the indication of a connected server's ability to receive packets in certain channels.
|
||||
*/
|
||||
public final class C2SConfigurationChannelEvents {
|
||||
/**
|
||||
* An event for the client configuration 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 configuration 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 C2SConfigurationChannelEvents() {
|
||||
}
|
||||
|
||||
/**
|
||||
* @see C2SConfigurationChannelEvents#REGISTER
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface Register {
|
||||
void onChannelRegister(ClientConfigurationNetworkHandler handler, PacketSender sender, MinecraftClient client, List<Identifier> channels);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see C2SConfigurationChannelEvents#UNREGISTER
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface Unregister {
|
||||
void onChannelUnregister(ClientConfigurationNetworkHandler handler, PacketSender sender, MinecraftClient client, List<Identifier> channels);
|
||||
}
|
||||
}
|
|
@ -18,10 +18,7 @@ package net.fabricmc.fabric.api.client.networking.v1;
|
|||
|
||||
import java.util.List;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.network.ClientConfigurationNetworkHandler;
|
||||
import net.minecraft.client.network.ClientPlayNetworkHandler;
|
||||
import net.minecraft.util.Identifier;
|
||||
|
||||
|
@ -53,28 +50,6 @@ public final class C2SPlayChannelEvents {
|
|||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* An event for the client configuration 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.
|
||||
*/
|
||||
@ApiStatus.Experimental
|
||||
public static final Event<RegisterConfiguration> REGISTER_CONFIGURATION = EventFactory.createArrayBacked(RegisterConfiguration.class, callbacks -> (handler, sender, client, channels) -> {
|
||||
for (RegisterConfiguration callback : callbacks) {
|
||||
callback.onChannelRegister(handler, sender, client, channels);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* An event for the client configuration 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.
|
||||
*/
|
||||
@ApiStatus.Experimental
|
||||
public static final Event<UnregisterConfiguration> UNREGISTER_CONFIGURATION = EventFactory.createArrayBacked(UnregisterConfiguration.class, callbacks -> (handler, sender, client, channels) -> {
|
||||
for (UnregisterConfiguration callback : callbacks) {
|
||||
callback.onChannelUnregister(handler, sender, client, channels);
|
||||
}
|
||||
});
|
||||
|
||||
private C2SPlayChannelEvents() {
|
||||
}
|
||||
|
||||
|
@ -93,22 +68,4 @@ public final class C2SPlayChannelEvents {
|
|||
public interface Unregister {
|
||||
void onChannelUnregister(ClientPlayNetworkHandler handler, PacketSender sender, MinecraftClient client, List<Identifier> channels);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see C2SPlayChannelEvents#REGISTER_CONFIGURATION
|
||||
*/
|
||||
@FunctionalInterface
|
||||
@ApiStatus.Experimental
|
||||
public interface RegisterConfiguration {
|
||||
void onChannelRegister(ClientConfigurationNetworkHandler handler, PacketSender sender, MinecraftClient client, List<Identifier> channels);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see C2SPlayChannelEvents#UNREGISTER_CONFIGURATION
|
||||
*/
|
||||
@FunctionalInterface
|
||||
@ApiStatus.Experimental
|
||||
public interface UnregisterConfiguration {
|
||||
void onChannelUnregister(ClientConfigurationNetworkHandler handler, PacketSender sender, MinecraftClient client, List<Identifier> channels);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,7 +31,6 @@ import net.minecraft.util.Identifier;
|
|||
import net.minecraft.util.thread.ThreadExecutor;
|
||||
|
||||
import net.fabricmc.fabric.api.networking.v1.FabricPacket;
|
||||
import net.fabricmc.fabric.api.networking.v1.PacketByteBufs;
|
||||
import net.fabricmc.fabric.api.networking.v1.PacketSender;
|
||||
import net.fabricmc.fabric.api.networking.v1.PacketType;
|
||||
import net.fabricmc.fabric.api.networking.v1.ServerConfigurationNetworking;
|
||||
|
@ -384,9 +383,14 @@ public final class ClientConfigurationNetworking {
|
|||
Objects.requireNonNull(packet, "Packet cannot be null");
|
||||
Objects.requireNonNull(packet.getType(), "Packet#getType cannot return null");
|
||||
|
||||
PacketByteBuf buf = PacketByteBufs.create();
|
||||
packet.write(buf);
|
||||
send(packet.getType().getId(), buf);
|
||||
final ClientConfigurationNetworkAddon addon = ClientNetworkingImpl.getClientConfigurationAddon();
|
||||
|
||||
if (addon != null) {
|
||||
addon.sendPacket(packet);
|
||||
return;
|
||||
}
|
||||
|
||||
throw new IllegalStateException("Cannot send packet while not configuring!");
|
||||
}
|
||||
|
||||
private ClientConfigurationNetworking() {
|
||||
|
|
|
@ -31,7 +31,6 @@ import net.minecraft.util.Identifier;
|
|||
import net.minecraft.util.thread.ThreadExecutor;
|
||||
|
||||
import net.fabricmc.fabric.api.networking.v1.FabricPacket;
|
||||
import net.fabricmc.fabric.api.networking.v1.PacketByteBufs;
|
||||
import net.fabricmc.fabric.api.networking.v1.PacketSender;
|
||||
import net.fabricmc.fabric.api.networking.v1.PacketType;
|
||||
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking;
|
||||
|
@ -339,6 +338,16 @@ public final class ClientPlayNetworking {
|
|||
return ClientNetworkingImpl.createC2SPacket(channelName, buf);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a packet which may be sent to the connected server.
|
||||
*
|
||||
* @param packet the fabric packet
|
||||
* @return a new packet
|
||||
*/
|
||||
public static <T extends FabricPacket> Packet<ServerCommonPacketListener> createC2SPacket(T packet) {
|
||||
return ClientNetworkingImpl.createC2SPacket(packet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the packet sender which sends packets to the connected server.
|
||||
*
|
||||
|
@ -381,9 +390,13 @@ public final class ClientPlayNetworking {
|
|||
Objects.requireNonNull(packet, "Packet cannot be null");
|
||||
Objects.requireNonNull(packet.getType(), "Packet#getType cannot return null");
|
||||
|
||||
PacketByteBuf buf = PacketByteBufs.create();
|
||||
packet.write(buf);
|
||||
send(packet.getType().getId(), buf);
|
||||
// You cant send without a client player, so this is fine
|
||||
if (MinecraftClient.getInstance().getNetworkHandler() != null) {
|
||||
MinecraftClient.getInstance().getNetworkHandler().sendPacket(createC2SPacket(packet));
|
||||
return;
|
||||
}
|
||||
|
||||
throw new IllegalStateException("Cannot send packets when not in game!");
|
||||
}
|
||||
|
||||
private ClientPlayNetworking() {
|
||||
|
|
|
@ -27,16 +27,18 @@ import net.minecraft.network.PacketByteBuf;
|
|||
import net.minecraft.network.packet.Packet;
|
||||
import net.minecraft.util.Identifier;
|
||||
|
||||
import net.fabricmc.fabric.api.client.networking.v1.C2SPlayChannelEvents;
|
||||
import net.fabricmc.fabric.api.client.networking.v1.C2SConfigurationChannelEvents;
|
||||
import net.fabricmc.fabric.api.client.networking.v1.ClientConfigurationConnectionEvents;
|
||||
import net.fabricmc.fabric.api.client.networking.v1.ClientConfigurationNetworking;
|
||||
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
|
||||
import net.fabricmc.fabric.api.networking.v1.FabricPacket;
|
||||
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.impl.networking.payload.PacketByteBufPayload;
|
||||
import net.fabricmc.fabric.mixin.networking.client.accessor.ClientCommonNetworkHandlerAccessor;
|
||||
import net.fabricmc.fabric.mixin.networking.client.accessor.ClientConfigurationNetworkHandlerAccessor;
|
||||
import net.fabricmc.fabric.mixin.networking.client.accessor.ClientLoginNetworkHandlerAccessor;
|
||||
|
||||
public final class ClientConfigurationNetworkAddon extends AbstractChanneledNetworkAddon<ClientConfigurationNetworking.ConfigurationChannelHandler> {
|
||||
private final ClientConfigurationNetworkHandler handler;
|
||||
|
@ -65,8 +67,17 @@ public final class ClientConfigurationNetworkAddon extends AbstractChanneledNetw
|
|||
}
|
||||
|
||||
public void onServerReady() {
|
||||
this.sendInitialChannelRegistrationPacket();
|
||||
this.sentInitialRegisterPacket = true;
|
||||
// Do nothing for now
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void receiveRegistration(boolean register, PacketByteBuf buf) {
|
||||
super.receiveRegistration(register, buf);
|
||||
|
||||
if (register && !this.sentInitialRegisterPacket) {
|
||||
this.sendInitialChannelRegistrationPacket();
|
||||
this.sentInitialRegisterPacket = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -100,14 +111,19 @@ public final class ClientConfigurationNetworkAddon extends AbstractChanneledNetw
|
|||
return ClientPlayNetworking.createC2SPacket(channelName, buf);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Packet<?> createPacket(FabricPacket packet) {
|
||||
return ClientPlayNetworking.createC2SPacket(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void invokeRegisterEvent(List<Identifier> ids) {
|
||||
C2SPlayChannelEvents.REGISTER_CONFIGURATION.invoker().onChannelRegister(this.handler, this, this.client, ids);
|
||||
C2SConfigurationChannelEvents.REGISTER.invoker().onChannelRegister(this.handler, this, this.client, ids);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void invokeUnregisterEvent(List<Identifier> ids) {
|
||||
C2SPlayChannelEvents.UNREGISTER_CONFIGURATION.invoker().onChannelUnregister(this.handler, this, this.client, ids);
|
||||
C2SConfigurationChannelEvents.UNREGISTER.invoker().onChannelUnregister(this.handler, this, this.client, ids);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -147,6 +163,10 @@ public final class ClientConfigurationNetworkAddon extends AbstractChanneledNetw
|
|||
|
||||
@Override
|
||||
protected boolean isReservedChannel(Identifier channelName) {
|
||||
return NetworkingImpl.isReservedPlayChannel(channelName);
|
||||
return NetworkingImpl.isReservedCommonChannel(channelName);
|
||||
}
|
||||
|
||||
public ChannelInfoHolder getChannelInfoHolder() {
|
||||
return (ChannelInfoHolder) ((ClientLoginNetworkHandlerAccessor) handler).getConnection();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,15 +16,13 @@
|
|||
|
||||
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 java.util.Objects;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.gui.screen.ConnectScreen;
|
||||
import net.minecraft.client.network.ClientConfigurationNetworkHandler;
|
||||
import net.minecraft.client.network.ClientLoginNetworkHandler;
|
||||
import net.minecraft.client.network.ClientPlayNetworkHandler;
|
||||
import net.minecraft.network.ClientConnection;
|
||||
|
@ -40,20 +38,24 @@ import net.fabricmc.fabric.api.client.networking.v1.ClientConfigurationNetworkin
|
|||
import net.fabricmc.fabric.api.client.networking.v1.ClientLoginNetworking;
|
||||
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents;
|
||||
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
|
||||
import net.fabricmc.fabric.api.networking.v1.FabricPacket;
|
||||
import net.fabricmc.fabric.api.networking.v1.PacketByteBufs;
|
||||
import net.fabricmc.fabric.impl.networking.ChannelInfoHolder;
|
||||
import net.fabricmc.fabric.api.networking.v1.PacketSender;
|
||||
import net.fabricmc.fabric.impl.networking.CommonPacketsImpl;
|
||||
import net.fabricmc.fabric.impl.networking.CommonRegisterPayload;
|
||||
import net.fabricmc.fabric.impl.networking.CommonVersionPayload;
|
||||
import net.fabricmc.fabric.impl.networking.GlobalReceiverRegistry;
|
||||
import net.fabricmc.fabric.impl.networking.NetworkHandlerExtensions;
|
||||
import net.fabricmc.fabric.impl.networking.NetworkingImpl;
|
||||
import net.fabricmc.fabric.impl.networking.payload.FabricPacketPayload;
|
||||
import net.fabricmc.fabric.impl.networking.payload.PacketByteBufPayload;
|
||||
import net.fabricmc.fabric.mixin.networking.client.accessor.ClientLoginNetworkHandlerAccessor;
|
||||
import net.fabricmc.fabric.mixin.networking.client.accessor.ConnectScreenAccessor;
|
||||
import net.fabricmc.fabric.mixin.networking.client.accessor.MinecraftClientAccessor;
|
||||
|
||||
public final class ClientNetworkingImpl {
|
||||
public static final GlobalReceiverRegistry<ClientLoginNetworking.LoginQueryRequestHandler> LOGIN = new GlobalReceiverRegistry<>();
|
||||
public static final GlobalReceiverRegistry<ClientConfigurationNetworking.ConfigurationChannelHandler> CONFIGURATION = new GlobalReceiverRegistry<>();
|
||||
public static final GlobalReceiverRegistry<ClientPlayNetworking.PlayChannelHandler> PLAY = new GlobalReceiverRegistry<>();
|
||||
public static final GlobalReceiverRegistry<ClientLoginNetworking.LoginQueryRequestHandler> LOGIN = new GlobalReceiverRegistry<>(NetworkState.LOGIN);
|
||||
public static final GlobalReceiverRegistry<ClientConfigurationNetworking.ConfigurationChannelHandler> CONFIGURATION = new GlobalReceiverRegistry<>(NetworkState.CONFIGURATION);
|
||||
public static final GlobalReceiverRegistry<ClientPlayNetworking.PlayChannelHandler> PLAY = new GlobalReceiverRegistry<>(NetworkState.PLAY);
|
||||
private static ClientPlayNetworkAddon currentPlayAddon;
|
||||
private static ClientConfigurationNetworkAddon currentConfigurationAddon;
|
||||
|
||||
|
@ -61,6 +63,10 @@ public final class ClientNetworkingImpl {
|
|||
return (ClientPlayNetworkAddon) ((NetworkHandlerExtensions) handler).getAddon();
|
||||
}
|
||||
|
||||
public static ClientConfigurationNetworkAddon getAddon(ClientConfigurationNetworkHandler handler) {
|
||||
return (ClientConfigurationNetworkAddon) ((NetworkHandlerExtensions) handler).getAddon();
|
||||
}
|
||||
|
||||
public static ClientLoginNetworkAddon getAddon(ClientLoginNetworkHandler handler) {
|
||||
return (ClientLoginNetworkAddon) ((NetworkHandlerExtensions) handler).getAddon();
|
||||
}
|
||||
|
@ -69,6 +75,19 @@ public final class ClientNetworkingImpl {
|
|||
return new CustomPayloadC2SPacket(new PacketByteBufPayload(channelName, buf));
|
||||
}
|
||||
|
||||
public static Packet<ServerCommonPacketListener> createC2SPacket(FabricPacket packet) {
|
||||
Objects.requireNonNull(packet, "Packet cannot be null");
|
||||
Objects.requireNonNull(packet.getType(), "Packet#getType cannot return null");
|
||||
|
||||
if (NetworkingImpl.WRITE_FABRIC_PACKET_CALLING_THREAD) {
|
||||
PacketByteBuf buf = PacketByteBufs.create();
|
||||
packet.write(buf);
|
||||
return createC2SPacket(packet.getType().getId(), buf);
|
||||
}
|
||||
|
||||
return new CustomPayloadC2SPacket(new FabricPacketPayload(packet));
|
||||
}
|
||||
|
||||
/**
|
||||
* Due to the way logging into a integrated or remote dedicated server will differ, we need to obtain the login client connection differently.
|
||||
*/
|
||||
|
@ -134,29 +153,44 @@ public final class ClientNetworkingImpl {
|
|||
currentConfigurationAddon = null;
|
||||
});
|
||||
|
||||
// Register a login query handler for early channel registration.
|
||||
ClientLoginNetworking.registerGlobalReceiver(NetworkingImpl.EARLY_REGISTRATION_CHANNEL, (client, handler, buf, listenerAdder) -> {
|
||||
int n = buf.readVarInt();
|
||||
List<Identifier> ids = new ArrayList<>(n);
|
||||
// Version packet
|
||||
ClientConfigurationNetworking.registerGlobalReceiver(CommonVersionPayload.PACKET_ID, (client, handler, buf, responseSender) -> {
|
||||
var payload = new CommonVersionPayload(buf);
|
||||
int negotiatedVersion = handleVersionPacket(payload, responseSender);
|
||||
ClientNetworkingImpl.getAddon(handler).onCommonVersionPacket(negotiatedVersion);
|
||||
});
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
ids.add(buf.readIdentifier());
|
||||
// Register packet
|
||||
ClientConfigurationNetworking.registerGlobalReceiver(CommonRegisterPayload.PACKET_ID, (client, handler, buf, responseSender) -> {
|
||||
var payload = new CommonRegisterPayload(buf);
|
||||
ClientConfigurationNetworkAddon addon = ClientNetworkingImpl.getAddon(handler);
|
||||
|
||||
if (CommonRegisterPayload.PLAY_PHASE.equals(payload.phase())) {
|
||||
if (payload.version() != addon.getNegotiatedVersion()) {
|
||||
throw new IllegalStateException("Negotiated common packet version: %d but received packet with version: %d".formatted(addon.getNegotiatedVersion(), payload.version()));
|
||||
}
|
||||
|
||||
addon.getChannelInfoHolder().getPendingChannelsNames(NetworkState.PLAY).addAll(payload.channels());
|
||||
NetworkingImpl.LOGGER.debug("Received accepted channels from the server");
|
||||
responseSender.sendPacket(new CommonRegisterPayload(addon.getNegotiatedVersion(), CommonRegisterPayload.PLAY_PHASE, ClientPlayNetworking.getGlobalReceivers()));
|
||||
} else {
|
||||
addon.onCommonRegisterPacket(payload);
|
||||
responseSender.sendPacket(addon.createRegisterPayload());
|
||||
}
|
||||
|
||||
ClientConnection connection = ((ClientLoginNetworkHandlerAccessor) handler).getConnection();
|
||||
((ChannelInfoHolder) connection).getPendingChannelsNames(NetworkState.PLAY).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);
|
||||
});
|
||||
}
|
||||
|
||||
// Disconnect if there are no commonly supported versions.
|
||||
// Client responds with the intersection of supported versions.
|
||||
// Return the highest supported version
|
||||
private static int handleVersionPacket(CommonVersionPayload payload, PacketSender packetSender) {
|
||||
int version = CommonPacketsImpl.getHighestCommonVersion(payload.versions(), CommonPacketsImpl.SUPPORTED_COMMON_PACKET_VERSIONS);
|
||||
|
||||
if (version <= 0) {
|
||||
throw new UnsupportedOperationException("Client does not support any requested versions from server");
|
||||
}
|
||||
|
||||
packetSender.sendPacket(new CommonVersionPayload(new int[]{ version }));
|
||||
return version;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,6 +33,7 @@ import net.minecraft.util.Identifier;
|
|||
import net.fabricmc.fabric.api.client.networking.v1.C2SPlayChannelEvents;
|
||||
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents;
|
||||
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
|
||||
import net.fabricmc.fabric.api.networking.v1.FabricPacket;
|
||||
import net.fabricmc.fabric.impl.networking.AbstractChanneledNetworkAddon;
|
||||
import net.fabricmc.fabric.impl.networking.ChannelInfoHolder;
|
||||
import net.fabricmc.fabric.impl.networking.NetworkingImpl;
|
||||
|
@ -109,6 +110,11 @@ public final class ClientPlayNetworkAddon extends AbstractChanneledNetworkAddon<
|
|||
return ClientPlayNetworking.createC2SPacket(channelName, buf);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Packet<?> createPacket(FabricPacket packet) {
|
||||
return ClientPlayNetworking.createC2SPacket(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void invokeRegisterEvent(List<Identifier> ids) {
|
||||
C2SPlayChannelEvents.REGISTER.invoker().onChannelRegister(this.handler, this, this.client, ids);
|
||||
|
@ -151,6 +157,6 @@ public final class ClientPlayNetworkAddon extends AbstractChanneledNetworkAddon<
|
|||
|
||||
@Override
|
||||
protected boolean isReservedChannel(Identifier channelName) {
|
||||
return NetworkingImpl.isReservedPlayChannel(channelName);
|
||||
return NetworkingImpl.isReservedCommonChannel(channelName);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -57,7 +57,7 @@ import net.minecraft.server.network.ServerPlayerEntity;
|
|||
*
|
||||
* @see ServerPlayNetworking#registerGlobalReceiver(PacketType, ServerPlayNetworking.PlayPacketHandler)
|
||||
* @see ServerPlayNetworking#send(ServerPlayerEntity, PacketType, FabricPacket)
|
||||
* @see PacketSender#sendPacket(PacketType, FabricPacket)
|
||||
* @see PacketSender#sendPacket(FabricPacket)
|
||||
*/
|
||||
public interface FabricPacket {
|
||||
/**
|
||||
|
|
|
@ -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.api.networking.v1;
|
||||
|
||||
import net.minecraft.server.network.ServerConfigurationNetworkHandler;
|
||||
import net.minecraft.server.network.ServerPlayerConfigurationTask;
|
||||
import net.minecraft.util.Identifier;
|
||||
|
||||
/**
|
||||
* Fabric-provided extensions for {@link ServerConfigurationNetworkHandler}.
|
||||
* This interface is automatically implemented via Mixin and interface injection.
|
||||
*/
|
||||
public interface FabricServerConfigurationNetworkHandler {
|
||||
/**
|
||||
* Enqueue a {@link ServerPlayerConfigurationTask} task to be processed.
|
||||
*
|
||||
* <p>Before adding a task use {@link ServerConfigurationNetworking#canSend(ServerConfigurationNetworkHandler, Identifier)}
|
||||
* to ensure that the client can process this task.
|
||||
*
|
||||
* <p>Once the client has handled the task a packet should be sent to the server.
|
||||
* Upon receiving this packet the server should call {@link FabricServerConfigurationNetworkHandler#completeTask(ServerPlayerConfigurationTask.Key)}
|
||||
*
|
||||
* @param task the task
|
||||
*/
|
||||
default void addTask(ServerPlayerConfigurationTask task) {
|
||||
throw new UnsupportedOperationException("Implemented via mixin");
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param key the key
|
||||
*/
|
||||
default void completeTask(ServerPlayerConfigurationTask.Key key) {
|
||||
throw new UnsupportedOperationException("Implemented via mixin");
|
||||
}
|
||||
}
|
|
@ -23,9 +23,10 @@ import io.netty.util.concurrent.Future;
|
|||
import io.netty.util.concurrent.GenericFutureListener;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import net.minecraft.network.packet.Packet;
|
||||
import net.minecraft.network.PacketByteBuf;
|
||||
import net.minecraft.network.PacketCallbacks;
|
||||
import net.minecraft.network.packet.CustomPayload;
|
||||
import net.minecraft.network.packet.Packet;
|
||||
import net.minecraft.util.Identifier;
|
||||
|
||||
import net.fabricmc.fabric.impl.networking.GenericFutureListenerHolder;
|
||||
|
@ -43,6 +44,13 @@ public interface PacketSender {
|
|||
*/
|
||||
Packet<?> createPacket(Identifier channelName, PacketByteBuf buf);
|
||||
|
||||
/**
|
||||
* Makes a packet for a fabric packet.
|
||||
*
|
||||
* @param packet the fabric packet
|
||||
*/
|
||||
Packet<?> createPacket(FabricPacket packet);
|
||||
|
||||
/**
|
||||
* Sends a packet.
|
||||
*
|
||||
|
@ -57,9 +65,17 @@ public interface PacketSender {
|
|||
* @param packet the packet
|
||||
*/
|
||||
default <T extends FabricPacket> void sendPacket(T packet) {
|
||||
sendPacket(createPacket(packet));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a packet.
|
||||
* @param payload the payload
|
||||
*/
|
||||
default void sendPacket(CustomPayload payload) {
|
||||
PacketByteBuf buf = PacketByteBufs.create();
|
||||
packet.write(buf);
|
||||
sendPacket(packet.getType().getId(), buf);
|
||||
payload.write(buf);
|
||||
sendPacket(payload.id(), buf);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -77,9 +93,19 @@ public interface PacketSender {
|
|||
* @param callback an optional callback to execute after the packet is sent, may be {@code null}. The callback may also accept a {@link ChannelFutureListener}.
|
||||
*/
|
||||
default <T extends FabricPacket> void sendPacket(T packet, @Nullable GenericFutureListener<? extends Future<? super Void>> callback) {
|
||||
sendPacket(createPacket(packet), callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a packet.
|
||||
*
|
||||
* @param payload the payload
|
||||
* @param callback an optional callback to execute after the packet is sent, may be {@code null}. The callback may also accept a {@link ChannelFutureListener}.
|
||||
*/
|
||||
default void sendPacket(CustomPayload payload, @Nullable GenericFutureListener<? extends Future<? super Void>> callback) {
|
||||
PacketByteBuf buf = PacketByteBufs.create();
|
||||
packet.write(buf);
|
||||
sendPacket(packet.getType().getId(), buf, callback);
|
||||
payload.write(buf);
|
||||
sendPacket(payload.id(), buf, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -97,9 +123,19 @@ public interface PacketSender {
|
|||
* @param callback an optional callback to execute after the packet is sent, may be {@code null}. The callback may also accept a {@link ChannelFutureListener}.
|
||||
*/
|
||||
default <T extends FabricPacket> void sendPacket(T packet, @Nullable PacketCallbacks callback) {
|
||||
sendPacket(createPacket(packet), callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a packet.
|
||||
*
|
||||
* @param payload the payload
|
||||
* @param callback an optional callback to execute after the packet is sent, may be {@code null}. The callback may also accept a {@link ChannelFutureListener}.
|
||||
*/
|
||||
default void sendPacket(CustomPayload payload, @Nullable PacketCallbacks callback) {
|
||||
PacketByteBuf buf = PacketByteBufs.create();
|
||||
packet.write(buf);
|
||||
sendPacket(packet.getType().getId(), buf, callback);
|
||||
payload.write(buf);
|
||||
sendPacket(payload.id(), buf, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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.ServerConfigurationNetworkHandler;
|
||||
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 S2CConfigurationChannelEvents {
|
||||
/**
|
||||
* An event for the server configuration 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 configuration 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 S2CConfigurationChannelEvents() {
|
||||
}
|
||||
|
||||
/**
|
||||
* @see S2CConfigurationChannelEvents#REGISTER
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface Register {
|
||||
void onChannelRegister(ServerConfigurationNetworkHandler handler, PacketSender sender, MinecraftServer server, List<Identifier> channels);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see S2CConfigurationChannelEvents#UNREGISTER
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface Unregister {
|
||||
void onChannelUnregister(ServerConfigurationNetworkHandler handler, PacketSender sender, MinecraftServer server, List<Identifier> channels);
|
||||
}
|
||||
}
|
|
@ -30,10 +30,37 @@ import net.fabricmc.fabric.api.event.EventFactory;
|
|||
@ApiStatus.Experimental
|
||||
public class ServerConfigurationConnectionEvents {
|
||||
/**
|
||||
* Event indicating a connection began sending configuration packets.
|
||||
* Event fired before any vanilla configuration has taken place.
|
||||
*
|
||||
* <p>This event is executed on {@linkplain io.netty.channel.EventLoop netty's event loops}
|
||||
*
|
||||
* <p>Task queued during this event will complete before vanilla configuration starts.
|
||||
*/
|
||||
public static final Event<Send> SEND = EventFactory.createArrayBacked(Send.class, callbacks -> (handler, server) -> {
|
||||
for (Send callback : callbacks) {
|
||||
public static final Event<Configure> BEFORE_CONFIGURE = EventFactory.createArrayBacked(Configure.class, callbacks -> (handler, server) -> {
|
||||
for (Configure callback : callbacks) {
|
||||
callback.onSendConfiguration(handler, server);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Event fired during vanilla configuration.
|
||||
*
|
||||
* <p>This event is executed on {@linkplain io.netty.channel.EventLoop netty's event loops}
|
||||
*
|
||||
* <p>An example usage of this:
|
||||
* <pre>{@code
|
||||
* ServerConfigurationConnectionEvents.CONFIGURE.register((handler, server) -> {
|
||||
* if (ServerConfigurationNetworking.canSend(handler, ConfigurationPacket.PACKET_TYPE)) {
|
||||
* handler.addTask(new TestConfigurationTask("Example data"));
|
||||
* } else {
|
||||
* // You can opt to disconnect the client if it cannot handle the configuration task
|
||||
* handler.disconnect(Text.literal("Network test configuration not supported by client"));
|
||||
* }
|
||||
* });
|
||||
* }</pre>
|
||||
*/
|
||||
public static final Event<Configure> CONFIGURE = EventFactory.createArrayBacked(Configure.class, callbacks -> (handler, server) -> {
|
||||
for (Configure callback : callbacks) {
|
||||
callback.onSendConfiguration(handler, server);
|
||||
}
|
||||
});
|
||||
|
@ -50,7 +77,7 @@ public class ServerConfigurationConnectionEvents {
|
|||
});
|
||||
|
||||
@FunctionalInterface
|
||||
public interface Send {
|
||||
public interface Configure {
|
||||
void onSendConfiguration(ServerConfigurationNetworkHandler handler, MinecraftServer server);
|
||||
}
|
||||
|
||||
|
|
|
@ -96,18 +96,7 @@ public final class ServerConfigurationNetworking {
|
|||
@Override
|
||||
public void receive(MinecraftServer server, ServerConfigurationNetworkHandler networkHandler, PacketByteBuf buf, PacketSender sender) {
|
||||
T packet = type.read(buf);
|
||||
|
||||
if (server.isOnThread()) {
|
||||
// Do not submit to the server thread if we're already running there.
|
||||
// Normally, packets are handled on the network IO thread - though it is
|
||||
// not guaranteed (for example, with 1.19.4 S2C packet bundling)
|
||||
// Since we're handling it right now, connection check is redundant.
|
||||
handler.receive(packet, sender);
|
||||
} else {
|
||||
server.execute(() -> {
|
||||
if (networkHandler.isConnectionOpen()) handler.receive(packet, sender);
|
||||
});
|
||||
}
|
||||
handler.receive(packet, networkHandler, sender);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -160,7 +149,7 @@ public final class ServerConfigurationNetworking {
|
|||
/**
|
||||
* Registers a handler to a channel.
|
||||
* This method differs from {@link ServerConfigurationNetworking#registerGlobalReceiver(Identifier, ConfigurationChannelHandler)} since
|
||||
* the channel handler will only be applied to the player represented by the {@link ServerConfigurationNetworkHandler}.
|
||||
* the channel handler will only be applied to the client represented by the {@link ServerConfigurationNetworkHandler}.
|
||||
*
|
||||
* <p>The handler runs on the network thread. After reading the buffer there, the world
|
||||
* must be modified in the server thread by calling {@link ThreadExecutor#execute(Runnable)}.
|
||||
|
@ -189,7 +178,7 @@ public final class ServerConfigurationNetworking {
|
|||
/**
|
||||
* Registers a handler for a packet type.
|
||||
* This method differs from {@link ServerConfigurationNetworking#registerGlobalReceiver(PacketType, ConfigurationPacketHandler)} since
|
||||
* the channel handler will only be applied to the player represented by the {@link ServerConfigurationNetworkHandler}.
|
||||
* the channel handler will only be applied to the client represented by the {@link ServerConfigurationNetworkHandler}.
|
||||
*
|
||||
* <p>For example, if you only register a receiver using this method when a {@linkplain ServerLoginNetworking#registerGlobalReceiver(Identifier, ServerLoginNetworking.LoginQueryResponseHandler)}
|
||||
* login response has been received, you should use {@link ServerPlayConnectionEvents#INIT} to register the channel handler.
|
||||
|
@ -213,18 +202,7 @@ public final class ServerConfigurationNetworking {
|
|||
@Override
|
||||
public void receive(MinecraftServer server, ServerConfigurationNetworkHandler networkHandler2, PacketByteBuf buf, PacketSender sender) {
|
||||
T packet = type.read(buf);
|
||||
|
||||
if (server.isOnThread()) {
|
||||
// Do not submit to the server thread if we're already running there.
|
||||
// Normally, packets are handled on the network IO thread - though it is
|
||||
// not guaranteed (for example, with 1.19.4 S2C packet bundling)
|
||||
// Since we're handling it right now, connection check is redundant.
|
||||
handler.receive(packet, sender);
|
||||
} else {
|
||||
server.execute(() -> {
|
||||
if (networkHandler2.isConnectionOpen()) handler.receive(packet, sender);
|
||||
});
|
||||
}
|
||||
handler.receive(packet, networkHandler2, sender);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -326,6 +304,19 @@ public final class ServerConfigurationNetworking {
|
|||
return ServerNetworkingImpl.createC2SPacket(channelName, buf);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a packet which may be sent to a connected client.
|
||||
*
|
||||
* @param packet the fabric packet
|
||||
* @return a new packet
|
||||
*/
|
||||
public static <T extends FabricPacket> Packet<ClientCommonPacketListener> createS2CPacket(T packet) {
|
||||
Objects.requireNonNull(packet, "Packet cannot be null");
|
||||
Objects.requireNonNull(packet.getType(), "Packet#getType cannot return null");
|
||||
|
||||
return ServerNetworkingImpl.createC2SPacket(packet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the packet sender which sends packets to the connected client.
|
||||
*
|
||||
|
@ -364,9 +355,7 @@ public final class ServerConfigurationNetworking {
|
|||
Objects.requireNonNull(packet, "Packet cannot be null");
|
||||
Objects.requireNonNull(packet.getType(), "Packet#getType cannot return null");
|
||||
|
||||
PacketByteBuf buf = PacketByteBufs.create();
|
||||
packet.write(buf);
|
||||
handler.sendPacket(createS2CPacket(packet.getType().getId(), buf));
|
||||
handler.sendPacket(createS2CPacket(packet));
|
||||
}
|
||||
|
||||
// Helper methods
|
||||
|
@ -391,7 +380,7 @@ public final class ServerConfigurationNetworking {
|
|||
* 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 ThreadExecutor#submit(Runnable) scheduled} using the provided Minecraft server instance.
|
||||
* Modification to the game should be {@linkplain ThreadExecutor#submit(Runnable) scheduled} using the server instance from {@link ServerConfigurationNetworking#getServer(ServerConfigurationNetworkHandler)}.
|
||||
*
|
||||
* <p>An example usage of this is:
|
||||
* <pre>{@code
|
||||
|
@ -405,7 +394,7 @@ public final class ServerConfigurationNetworking {
|
|||
* });
|
||||
* }</pre>
|
||||
* @param server the server
|
||||
* @param handler the network handler that received this packet, representing the player/client who sent the packet
|
||||
* @param handler the network handler that received this packet, representing the client who sent the packet
|
||||
* @param buf the payload of the packet
|
||||
* @param responseSender the packet sender
|
||||
*/
|
||||
|
@ -427,7 +416,10 @@ public final class ServerConfigurationNetworking {
|
|||
@FunctionalInterface
|
||||
public interface ConfigurationPacketHandler<T extends FabricPacket> {
|
||||
/**
|
||||
* Handles the incoming packet. This is called on the server thread.
|
||||
* Handles an incoming packet.
|
||||
*
|
||||
* <p>Unlike {@link ServerPlayNetworking.PlayPacketHandler} this method is executed on {@linkplain io.netty.channel.EventLoop netty's event loops}.
|
||||
* Modification to the game should be {@linkplain ThreadExecutor#submit(Runnable) scheduled} using the Minecraft server instance from {@link ServerConfigurationNetworking#getServer(ServerConfigurationNetworkHandler)}.
|
||||
*
|
||||
* <p>An example usage of this:
|
||||
* <pre>{@code
|
||||
|
@ -439,9 +431,10 @@ public final class ServerConfigurationNetworking {
|
|||
*
|
||||
*
|
||||
* @param packet the packet
|
||||
* @param networkHandler the network handler
|
||||
* @param responseSender the packet sender
|
||||
* @see FabricPacket
|
||||
*/
|
||||
void receive(T packet, PacketSender responseSender);
|
||||
void receive(T packet, ServerConfigurationNetworkHandler networkHandler, PacketSender responseSender);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -389,6 +389,16 @@ public final class ServerPlayNetworking {
|
|||
return ServerNetworkingImpl.createC2SPacket(channelName, buf);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a packet which may be sent to a connected client.
|
||||
*
|
||||
* @param packet the fabric packet
|
||||
* @return a new packet
|
||||
*/
|
||||
public static <T extends FabricPacket> Packet<ClientCommonPacketListener> createS2CPacket(T packet) {
|
||||
return ServerNetworkingImpl.createC2SPacket(packet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the packet sender which sends packets to the connected client.
|
||||
*
|
||||
|
@ -439,9 +449,7 @@ public final class ServerPlayNetworking {
|
|||
Objects.requireNonNull(packet, "Packet cannot be null");
|
||||
Objects.requireNonNull(packet.getType(), "Packet#getType cannot return null");
|
||||
|
||||
PacketByteBuf buf = PacketByteBufs.create();
|
||||
packet.write(buf);
|
||||
player.networkHandler.sendPacket(createS2CPacket(packet.getType().getId(), buf));
|
||||
player.networkHandler.sendPacket(createS2CPacket(packet));
|
||||
}
|
||||
|
||||
// Helper methods
|
||||
|
|
|
@ -46,22 +46,18 @@ import net.fabricmc.fabric.api.networking.v1.PacketSender;
|
|||
*
|
||||
* @param <H> the channel handler type
|
||||
*/
|
||||
public abstract class AbstractChanneledNetworkAddon<H> extends AbstractNetworkAddon<H> implements PacketSender {
|
||||
public abstract class AbstractChanneledNetworkAddon<H> extends AbstractNetworkAddon<H> implements PacketSender, CommonPacketHandler {
|
||||
protected final ClientConnection connection;
|
||||
protected final GlobalReceiverRegistry<H> receiver;
|
||||
protected final Set<Identifier> sendableChannels;
|
||||
protected final Set<Identifier> sendableChannelsView;
|
||||
|
||||
protected int commonVersion = -1;
|
||||
|
||||
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);
|
||||
this.sendableChannels = Collections.synchronizedSet(new HashSet<>());
|
||||
}
|
||||
|
||||
public abstract void lateInit();
|
||||
|
@ -157,17 +153,22 @@ public abstract class AbstractChanneledNetworkAddon<H> extends AbstractNetworkAd
|
|||
}
|
||||
|
||||
this.addId(ids, active);
|
||||
this.schedule(register ? () -> register(ids) : () -> unregister(ids));
|
||||
|
||||
if (register) {
|
||||
register(ids);
|
||||
} else {
|
||||
unregister(ids);
|
||||
}
|
||||
}
|
||||
|
||||
void register(List<Identifier> ids) {
|
||||
this.sendableChannels.addAll(ids);
|
||||
this.invokeRegisterEvent(ids);
|
||||
schedule(() -> this.invokeRegisterEvent(ids));
|
||||
}
|
||||
|
||||
void unregister(List<Identifier> ids) {
|
||||
this.sendableChannels.removeAll(ids);
|
||||
this.invokeUnregisterEvent(ids);
|
||||
schedule(() -> this.invokeUnregisterEvent(ids));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -202,6 +203,62 @@ public abstract class AbstractChanneledNetworkAddon<H> extends AbstractNetworkAd
|
|||
}
|
||||
|
||||
public Set<Identifier> getSendableChannels() {
|
||||
return this.sendableChannelsView;
|
||||
return Collections.unmodifiableSet(this.sendableChannels);
|
||||
}
|
||||
|
||||
// Common packet handlers
|
||||
|
||||
@Override
|
||||
public void onCommonVersionPacket(int negotiatedVersion) {
|
||||
assert negotiatedVersion == 1; // We only support version 1 for now
|
||||
|
||||
commonVersion = negotiatedVersion;
|
||||
this.logger.info("Negotiated common packet version {}", commonVersion);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCommonRegisterPacket(CommonRegisterPayload payload) {
|
||||
if (payload.version() != getNegotiatedVersion()) {
|
||||
throw new IllegalStateException("Negotiated common packet version: %d but received packet with version: %d".formatted(commonVersion, payload.version()));
|
||||
}
|
||||
|
||||
final String currentPhase = getPhase();
|
||||
|
||||
if (currentPhase == null) {
|
||||
// We don't support receiving the register packet during this phase. See getPhase() for supported phases.
|
||||
// The normal case where the play channels are sent during configuration is handled in the client/common configuration packet handlers.
|
||||
logger.warn("Received common register packet for phase {} in network state: {}", payload.phase(), receiver.getState());
|
||||
return;
|
||||
}
|
||||
|
||||
if (!payload.phase().equals(currentPhase)) {
|
||||
// We need to handle receiving the play phase during configuration!
|
||||
throw new IllegalStateException("Register packet received for phase (%s) on handler for phase(%s)".formatted(payload.phase(), currentPhase));
|
||||
}
|
||||
|
||||
register(new ArrayList<>(payload.channels()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommonRegisterPayload createRegisterPayload() {
|
||||
return new CommonRegisterPayload(getNegotiatedVersion(), getPhase(), this.getReceivableChannels());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNegotiatedVersion() {
|
||||
if (commonVersion == -1) {
|
||||
throw new IllegalStateException("Not yet negotiated common packet version");
|
||||
}
|
||||
|
||||
return commonVersion;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private String getPhase() {
|
||||
return switch (receiver.getState()) {
|
||||
case PLAY -> CommonRegisterPayload.PLAY_PHASE;
|
||||
case CONFIGURATION -> CommonRegisterPayload.CONFIGURATION_PHASE;
|
||||
default -> null; // We don't support receiving this packet on any other phase
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
public interface CommonPacketHandler {
|
||||
void onCommonVersionPacket(int negotiatedVersion);
|
||||
|
||||
void onCommonRegisterPacket(CommonRegisterPayload payload);
|
||||
|
||||
CommonRegisterPayload createRegisterPayload();
|
||||
|
||||
int getNegotiatedVersion();
|
||||
}
|
|
@ -0,0 +1,141 @@
|
|||
/*
|
||||
* 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.Arrays;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import net.minecraft.network.NetworkState;
|
||||
import net.minecraft.network.packet.Packet;
|
||||
import net.minecraft.server.network.ServerPlayerConfigurationTask;
|
||||
|
||||
import net.fabricmc.fabric.api.networking.v1.ServerConfigurationConnectionEvents;
|
||||
import net.fabricmc.fabric.api.networking.v1.ServerConfigurationNetworking;
|
||||
import net.fabricmc.fabric.impl.networking.server.ServerConfigurationNetworkAddon;
|
||||
import net.fabricmc.fabric.impl.networking.server.ServerNetworkingImpl;
|
||||
|
||||
public class CommonPacketsImpl {
|
||||
public static final int PACKET_VERSION_1 = 1;
|
||||
public static final int[] SUPPORTED_COMMON_PACKET_VERSIONS = new int[]{ PACKET_VERSION_1 };
|
||||
|
||||
public static void init() {
|
||||
ServerConfigurationNetworking.registerGlobalReceiver(CommonVersionPayload.PACKET_ID, (server, handler, buf, responseSender) -> {
|
||||
var payload = new CommonVersionPayload(buf);
|
||||
ServerConfigurationNetworkAddon addon = ServerNetworkingImpl.getAddon(handler);
|
||||
addon.onCommonVersionPacket(getNegotiatedVersion(payload));
|
||||
handler.completeTask(CommonVersionConfigurationTask.KEY);
|
||||
});
|
||||
|
||||
ServerConfigurationNetworking.registerGlobalReceiver(CommonRegisterPayload.PACKET_ID, (server, handler, buf, responseSender) -> {
|
||||
var payload = new CommonRegisterPayload(buf);
|
||||
ServerConfigurationNetworkAddon addon = ServerNetworkingImpl.getAddon(handler);
|
||||
|
||||
if (CommonRegisterPayload.PLAY_PHASE.equals(payload.phase())) {
|
||||
if (payload.version() != addon.getNegotiatedVersion()) {
|
||||
throw new IllegalStateException("Negotiated common packet version: %d but received packet with version: %d".formatted(addon.getNegotiatedVersion(), payload.version()));
|
||||
}
|
||||
|
||||
// Play phase hasnt started yet, add them to the pending names.
|
||||
addon.getChannelInfoHolder().getPendingChannelsNames(NetworkState.PLAY).addAll(payload.channels());
|
||||
NetworkingImpl.LOGGER.debug("Received accepted channels from the client for play phase");
|
||||
} else {
|
||||
addon.onCommonRegisterPacket(payload);
|
||||
}
|
||||
|
||||
handler.completeTask(CommonRegisterConfigurationTask.KEY);
|
||||
});
|
||||
|
||||
// Create a configuration task to send and receive the common packets
|
||||
ServerConfigurationConnectionEvents.CONFIGURE.register((handler, server) -> {
|
||||
final ServerConfigurationNetworkAddon addon = ServerNetworkingImpl.getAddon(handler);
|
||||
|
||||
if (ServerConfigurationNetworking.canSend(handler, CommonVersionPayload.PACKET_ID)) {
|
||||
// Tasks are processed in order.
|
||||
handler.addTask(new CommonVersionConfigurationTask(addon));
|
||||
|
||||
if (ServerConfigurationNetworking.canSend(handler, CommonRegisterPayload.PACKET_ID)) {
|
||||
handler.addTask(new CommonRegisterConfigurationTask(addon));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// A configuration phase task to send and receive the version packets.
|
||||
private record CommonVersionConfigurationTask(ServerConfigurationNetworkAddon addon) implements ServerPlayerConfigurationTask {
|
||||
public static final Key KEY = new Key(CommonVersionPayload.PACKET_ID.toString());
|
||||
|
||||
@Override
|
||||
public void sendPacket(Consumer<Packet<?>> sender) {
|
||||
addon.sendPacket(new CommonVersionPayload(SUPPORTED_COMMON_PACKET_VERSIONS));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Key getKey() {
|
||||
return KEY;
|
||||
}
|
||||
}
|
||||
|
||||
// A configuration phase task to send and receive the registration packets.
|
||||
private record CommonRegisterConfigurationTask(ServerConfigurationNetworkAddon addon) implements ServerPlayerConfigurationTask {
|
||||
public static final Key KEY = new Key(CommonRegisterPayload.PACKET_ID.toString());
|
||||
|
||||
@Override
|
||||
public void sendPacket(Consumer<Packet<?>> sender) {
|
||||
addon.sendPacket(addon.createRegisterPayload());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Key getKey() {
|
||||
return KEY;
|
||||
}
|
||||
}
|
||||
|
||||
private static int getNegotiatedVersion(CommonVersionPayload payload) {
|
||||
int version = getHighestCommonVersion(payload.versions(), SUPPORTED_COMMON_PACKET_VERSIONS);
|
||||
|
||||
if (version <= 0) {
|
||||
throw new UnsupportedOperationException("server does not support any requested versions from client");
|
||||
}
|
||||
|
||||
return version;
|
||||
}
|
||||
|
||||
public static int getHighestCommonVersion(int[] a, int[] b) {
|
||||
int[] as = a.clone();
|
||||
int[] bs = b.clone();
|
||||
|
||||
Arrays.sort(as);
|
||||
Arrays.sort(bs);
|
||||
|
||||
int ap = as.length - 1;
|
||||
int bp = bs.length - 1;
|
||||
|
||||
while (ap >= 0 && bp >= 0) {
|
||||
if (as[ap] == bs[bp]) {
|
||||
return as[ap];
|
||||
}
|
||||
|
||||
if (as[ap] > bs[bp]) {
|
||||
ap--;
|
||||
} else {
|
||||
bp--;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* 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.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import net.minecraft.network.PacketByteBuf;
|
||||
import net.minecraft.network.packet.CustomPayload;
|
||||
import net.minecraft.util.Identifier;
|
||||
|
||||
public record CommonRegisterPayload(int version, String phase, Set<Identifier> channels) implements CustomPayload {
|
||||
public static final Identifier PACKET_ID = new Identifier("c", "register");
|
||||
|
||||
public static final String PLAY_PHASE = "play";
|
||||
public static final String CONFIGURATION_PHASE = "configuration";
|
||||
|
||||
public CommonRegisterPayload(PacketByteBuf buf) {
|
||||
this(
|
||||
buf.readVarInt(),
|
||||
buf.readString(),
|
||||
buf.readCollection(HashSet::new, PacketByteBuf::readIdentifier)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(PacketByteBuf buf) {
|
||||
buf.writeVarInt(version);
|
||||
buf.writeString(phase);
|
||||
buf.writeCollection(channels, PacketByteBuf::writeIdentifier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Identifier id() {
|
||||
return PACKET_ID;
|
||||
}
|
||||
}
|
|
@ -14,20 +14,26 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.fabricmc.fabric.impl.registry.sync;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
package net.fabricmc.fabric.impl.networking;
|
||||
|
||||
import net.minecraft.network.PacketByteBuf;
|
||||
import net.minecraft.network.packet.Packet;
|
||||
import net.minecraft.network.packet.CustomPayload;
|
||||
import net.minecraft.util.Identifier;
|
||||
|
||||
import net.fabricmc.fabric.api.networking.v1.ServerConfigurationNetworking;
|
||||
public record CommonVersionPayload(int[] versions) implements CustomPayload {
|
||||
public static final Identifier PACKET_ID = new Identifier("c", "version");
|
||||
|
||||
public record ConfiguringServerPlayer(GameProfile gameProfile, Consumer<Packet<?>> sender) {
|
||||
public void sendPacket(Identifier identifier, PacketByteBuf buf) {
|
||||
sender.accept(ServerConfigurationNetworking.createS2CPacket(identifier, buf));
|
||||
public CommonVersionPayload(PacketByteBuf buf) {
|
||||
this(buf.readIntArray());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(PacketByteBuf buf) {
|
||||
buf.writeIntArray(versions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Identifier id() {
|
||||
return PACKET_ID;
|
||||
}
|
||||
}
|
|
@ -27,18 +27,22 @@ import java.util.concurrent.locks.ReentrantReadWriteLock;
|
|||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import net.minecraft.network.NetworkState;
|
||||
import net.minecraft.util.Identifier;
|
||||
|
||||
public final class GlobalReceiverRegistry<H> {
|
||||
private final NetworkState state;
|
||||
|
||||
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(NetworkState state) {
|
||||
this(state, new HashMap<>()); // sync map should be fine as there is little read write competitions
|
||||
}
|
||||
|
||||
public GlobalReceiverRegistry(Map<Identifier, H> map) {
|
||||
public GlobalReceiverRegistry(NetworkState state, Map<Identifier, H> map) {
|
||||
this.state = state;
|
||||
this.handlers = map;
|
||||
}
|
||||
|
||||
|
@ -58,7 +62,7 @@ public final class GlobalReceiverRegistry<H> {
|
|||
Objects.requireNonNull(channelName, "Channel name cannot be null");
|
||||
Objects.requireNonNull(handler, "Channel handler cannot be null");
|
||||
|
||||
if (NetworkingImpl.isReservedPlayChannel(channelName)) {
|
||||
if (NetworkingImpl.isReservedCommonChannel(channelName)) {
|
||||
throw new IllegalArgumentException(String.format("Cannot register handler for reserved channel with name \"%s\"", channelName));
|
||||
}
|
||||
|
||||
|
@ -81,7 +85,7 @@ public final class GlobalReceiverRegistry<H> {
|
|||
public H unregisterGlobalReceiver(Identifier channelName) {
|
||||
Objects.requireNonNull(channelName, "Channel name cannot be null");
|
||||
|
||||
if (NetworkingImpl.isReservedPlayChannel(channelName)) {
|
||||
if (NetworkingImpl.isReservedCommonChannel(channelName)) {
|
||||
throw new IllegalArgumentException(String.format("Cannot unregister packet handler for reserved channel with name \"%s\"", channelName));
|
||||
}
|
||||
|
||||
|
@ -172,4 +176,8 @@ public final class GlobalReceiverRegistry<H> {
|
|||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public NetworkState getState() {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,77 +16,32 @@
|
|||
|
||||
package net.fabricmc.fabric.impl.networking;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import net.minecraft.network.ClientConnection;
|
||||
import net.minecraft.network.NetworkState;
|
||||
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;
|
||||
import net.fabricmc.fabric.mixin.networking.accessor.ServerLoginNetworkHandlerAccessor;
|
||||
|
||||
public final class NetworkingImpl {
|
||||
public static final String MOD_ID = "fabric-networking-api-v1";
|
||||
public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID);
|
||||
|
||||
/**
|
||||
* When enabled the fabric packet is written to the {@link net.minecraft.network.PacketByteBuf} on the calling thread.
|
||||
* This is not enabled by default as it currently causes issues in single player.
|
||||
*/
|
||||
public static final boolean WRITE_FABRIC_PACKET_CALLING_THREAD = Boolean.parseBoolean(System.getProperty("fabric-api.networking.write-fabric-packet-calling-thread", "true"));
|
||||
|
||||
/**
|
||||
* Id of packet used to register supported channels.
|
||||
*/
|
||||
public static final Identifier REGISTER_CHANNEL = new Identifier("minecraft", "register");
|
||||
|
||||
/**
|
||||
* Id of packet used to unregister supported channels.
|
||||
*/
|
||||
public static final Identifier UNREGISTER_CHANNEL = new Identifier("minecraft", "unregister");
|
||||
/**
|
||||
* Id of the packet used to declare all currently supported channels.
|
||||
* Dynamic registration of supported channels is still allowed using {@link NetworkingImpl#REGISTER_CHANNEL} and {@link NetworkingImpl#UNREGISTER_CHANNEL}.
|
||||
*/
|
||||
public static final Identifier EARLY_REGISTRATION_CHANNEL = new Identifier(MOD_ID, "early_registration");
|
||||
|
||||
public static void init() {
|
||||
// Login setup
|
||||
ServerLoginConnectionEvents.QUERY_START.register((handler, server, sender, synchronizer) -> {
|
||||
// Send early registration packet
|
||||
PacketByteBuf buf = PacketByteBufs.create();
|
||||
Collection<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());
|
||||
}
|
||||
|
||||
ClientConnection connection = ((ServerLoginNetworkHandlerAccessor) handler).getConnection();
|
||||
((ChannelInfoHolder) connection).getPendingChannelsNames(NetworkState.PLAY).addAll(ids);
|
||||
NetworkingImpl.LOGGER.debug("Received accepted channels from the client for \"{}\"", handler.getConnectionInfo());
|
||||
});
|
||||
}
|
||||
|
||||
public static boolean isReservedPlayChannel(Identifier channelName) {
|
||||
public static boolean isReservedCommonChannel(Identifier channelName) {
|
||||
return channelName.equals(REGISTER_CHANNEL) || channelName.equals(UNREGISTER_CHANNEL);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.impl.networking.payload;
|
||||
|
||||
import net.minecraft.network.PacketByteBuf;
|
||||
import net.minecraft.network.packet.s2c.login.LoginQueryRequestPayload;
|
||||
import net.minecraft.util.Identifier;
|
||||
|
||||
import net.fabricmc.fabric.api.networking.v1.FabricPacket;
|
||||
|
||||
public record FabricPacketLoginQueryRequestPayload(FabricPacket fabricPacket) implements LoginQueryRequestPayload {
|
||||
@Override
|
||||
public void write(PacketByteBuf buf) {
|
||||
fabricPacket.write(buf);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Identifier id() {
|
||||
return fabricPacket.getType().getId();
|
||||
}
|
||||
}
|
|
@ -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.impl.networking.payload;
|
||||
|
||||
import net.minecraft.network.PacketByteBuf;
|
||||
import net.minecraft.network.packet.CustomPayload;
|
||||
import net.minecraft.util.Identifier;
|
||||
|
||||
import net.fabricmc.fabric.api.networking.v1.FabricPacket;
|
||||
|
||||
public record FabricPacketPayload(FabricPacket fabricPacket) implements CustomPayload {
|
||||
@Override
|
||||
public void write(PacketByteBuf buf) {
|
||||
fabricPacket.write(buf);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Identifier id() {
|
||||
return fabricPacket.getType().getId();
|
||||
}
|
||||
}
|
|
@ -24,10 +24,13 @@ import net.minecraft.network.NetworkState;
|
|||
import net.minecraft.network.PacketByteBuf;
|
||||
import net.minecraft.network.PacketCallbacks;
|
||||
import net.minecraft.network.packet.Packet;
|
||||
import net.minecraft.network.packet.s2c.common.PlayPingS2CPacket;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.network.ServerConfigurationNetworkHandler;
|
||||
import net.minecraft.util.Identifier;
|
||||
|
||||
import net.fabricmc.fabric.api.networking.v1.FabricPacket;
|
||||
import net.fabricmc.fabric.api.networking.v1.S2CConfigurationChannelEvents;
|
||||
import net.fabricmc.fabric.api.networking.v1.ServerConfigurationConnectionEvents;
|
||||
import net.fabricmc.fabric.api.networking.v1.ServerConfigurationNetworking;
|
||||
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking;
|
||||
|
@ -36,11 +39,12 @@ import net.fabricmc.fabric.impl.networking.ChannelInfoHolder;
|
|||
import net.fabricmc.fabric.impl.networking.NetworkingImpl;
|
||||
import net.fabricmc.fabric.impl.networking.payload.PacketByteBufPayload;
|
||||
import net.fabricmc.fabric.mixin.networking.accessor.ServerCommonNetworkHandlerAccessor;
|
||||
import net.fabricmc.fabric.mixin.networking.accessor.ServerLoginNetworkHandlerAccessor;
|
||||
|
||||
public final class ServerConfigurationNetworkAddon extends AbstractChanneledNetworkAddon<ServerConfigurationNetworking.ConfigurationChannelHandler> {
|
||||
private final ServerConfigurationNetworkHandler handler;
|
||||
private final MinecraftServer server;
|
||||
private boolean sentInitialRegisterPacket;
|
||||
private RegisterState registerState = RegisterState.NOT_SENT;
|
||||
|
||||
public ServerConfigurationNetworkAddon(ServerConfigurationNetworkHandler handler, MinecraftServer server) {
|
||||
super(ServerNetworkingImpl.CONFIGURATION, ((ServerCommonNetworkHandlerAccessor) handler).getConnection(), "ServerConfigurationNetworkAddon for " + handler.getDebugProfile().getName());
|
||||
|
@ -61,13 +65,48 @@ public final class ServerConfigurationNetworkAddon extends AbstractChanneledNetw
|
|||
}
|
||||
}
|
||||
|
||||
public void sendConfiguration() {
|
||||
ServerConfigurationConnectionEvents.SEND.invoker().onSendConfiguration(handler, server);
|
||||
public void preConfiguration() {
|
||||
ServerConfigurationConnectionEvents.BEFORE_CONFIGURE.invoker().onSendConfiguration(handler, server);
|
||||
}
|
||||
|
||||
public void onClientReady() {
|
||||
this.sendInitialChannelRegistrationPacket();
|
||||
this.sentInitialRegisterPacket = true;
|
||||
public void configuration() {
|
||||
ServerConfigurationConnectionEvents.CONFIGURE.invoker().onSendConfiguration(handler, server);
|
||||
}
|
||||
|
||||
public boolean startConfiguration() {
|
||||
if (this.registerState == RegisterState.NOT_SENT) {
|
||||
// Send the registration packet, followed by a ping
|
||||
this.sendInitialChannelRegistrationPacket();
|
||||
this.sendPacket(new PlayPingS2CPacket(0xFAB71C));
|
||||
|
||||
this.registerState = RegisterState.SENT;
|
||||
|
||||
// Cancel the configuration for now, the response from the ping or registration packet will continue.
|
||||
return true;
|
||||
}
|
||||
|
||||
// We should have received a response
|
||||
assert registerState == RegisterState.RECEIVED || registerState == RegisterState.NOT_RECEIVED;
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void receiveRegistration(boolean register, PacketByteBuf buf) {
|
||||
super.receiveRegistration(register, buf);
|
||||
|
||||
if (register && registerState == RegisterState.SENT) {
|
||||
// We received the registration packet, thus we know this is a modded client, continue with configuration.
|
||||
registerState = RegisterState.RECEIVED;
|
||||
handler.sendConfigurations();
|
||||
}
|
||||
}
|
||||
|
||||
public void onPong(int parameter) {
|
||||
if (registerState == RegisterState.SENT) {
|
||||
// We did not receive the registration packet, thus we think this is a vanilla client, continue with configuration.
|
||||
registerState = RegisterState.NOT_RECEIVED;
|
||||
handler.sendConfigurations();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -89,6 +128,7 @@ public final class ServerConfigurationNetworkAddon extends AbstractChanneledNetw
|
|||
|
||||
@Override
|
||||
protected void schedule(Runnable task) {
|
||||
this.server.execute(task);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -96,18 +136,25 @@ public final class ServerConfigurationNetworkAddon extends AbstractChanneledNetw
|
|||
return ServerPlayNetworking.createS2CPacket(channelName, buf);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Packet<?> createPacket(FabricPacket packet) {
|
||||
return ServerPlayNetworking.createS2CPacket(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void invokeRegisterEvent(List<Identifier> ids) {
|
||||
S2CConfigurationChannelEvents.REGISTER.invoker().onChannelRegister(this.handler, this, this.server, ids);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void invokeUnregisterEvent(List<Identifier> ids) {
|
||||
S2CConfigurationChannelEvents.UNREGISTER.invoker().onChannelUnregister(this.handler, this, this.server, ids);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleRegistration(Identifier channelName) {
|
||||
// If we can already send packets, immediately send the register packet for this channel
|
||||
if (this.sentInitialRegisterPacket) {
|
||||
if (this.registerState != RegisterState.NOT_SENT) {
|
||||
final PacketByteBuf buf = this.createRegistrationPacket(Collections.singleton(channelName));
|
||||
|
||||
if (buf != null) {
|
||||
|
@ -119,7 +166,7 @@ public final class ServerConfigurationNetworkAddon extends AbstractChanneledNetw
|
|||
@Override
|
||||
protected void handleUnregistration(Identifier channelName) {
|
||||
// If we can already send packets, immediately send the unregister packet for this channel
|
||||
if (this.sentInitialRegisterPacket) {
|
||||
if (this.registerState != RegisterState.NOT_SENT) {
|
||||
final PacketByteBuf buf = this.createRegistrationPacket(Collections.singleton(channelName));
|
||||
|
||||
if (buf != null) {
|
||||
|
@ -136,7 +183,7 @@ public final class ServerConfigurationNetworkAddon extends AbstractChanneledNetw
|
|||
|
||||
@Override
|
||||
protected boolean isReservedChannel(Identifier channelName) {
|
||||
return NetworkingImpl.isReservedPlayChannel(channelName);
|
||||
return NetworkingImpl.isReservedCommonChannel(channelName);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -144,4 +191,15 @@ public final class ServerConfigurationNetworkAddon extends AbstractChanneledNetw
|
|||
// Ensure we flush the packet.
|
||||
handler.send(packet, callback, true);
|
||||
}
|
||||
|
||||
private enum RegisterState {
|
||||
NOT_SENT,
|
||||
SENT,
|
||||
RECEIVED,
|
||||
NOT_RECEIVED
|
||||
}
|
||||
|
||||
public ChannelInfoHolder getChannelInfoHolder() {
|
||||
return (ChannelInfoHolder) ((ServerLoginNetworkHandlerAccessor) handler).getConnection();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,12 +40,14 @@ import net.minecraft.server.MinecraftServer;
|
|||
import net.minecraft.server.network.ServerLoginNetworkHandler;
|
||||
import net.minecraft.util.Identifier;
|
||||
|
||||
import net.fabricmc.fabric.api.networking.v1.FabricPacket;
|
||||
import net.fabricmc.fabric.api.networking.v1.PacketByteBufs;
|
||||
import net.fabricmc.fabric.api.networking.v1.PacketSender;
|
||||
import net.fabricmc.fabric.api.networking.v1.ServerLoginConnectionEvents;
|
||||
import net.fabricmc.fabric.api.networking.v1.ServerLoginNetworking;
|
||||
import net.fabricmc.fabric.impl.networking.AbstractNetworkAddon;
|
||||
import net.fabricmc.fabric.impl.networking.GenericFutureListenerHolder;
|
||||
import net.fabricmc.fabric.impl.networking.payload.FabricPacketLoginQueryRequestPayload;
|
||||
import net.fabricmc.fabric.impl.networking.payload.PacketByteBufLoginQueryRequestPayload;
|
||||
import net.fabricmc.fabric.impl.networking.payload.PacketByteBufLoginQueryResponse;
|
||||
import net.fabricmc.fabric.mixin.networking.accessor.ServerLoginNetworkHandlerAccessor;
|
||||
|
@ -164,9 +166,13 @@ public final class ServerLoginNetworkAddon extends AbstractNetworkAddon<ServerLo
|
|||
@Override
|
||||
public Packet<?> createPacket(Identifier channelName, PacketByteBuf buf) {
|
||||
int queryId = this.queryIdFactory.nextId();
|
||||
return new LoginQueryRequestS2CPacket(queryId, new PacketByteBufLoginQueryRequestPayload(channelName, buf));
|
||||
}
|
||||
|
||||
LoginQueryRequestS2CPacket ret = new LoginQueryRequestS2CPacket(queryId, new PacketByteBufLoginQueryRequestPayload(channelName, buf));
|
||||
return ret;
|
||||
@Override
|
||||
public Packet<?> createPacket(FabricPacket packet) {
|
||||
int queryId = this.queryIdFactory.nextId();
|
||||
return new LoginQueryRequestS2CPacket(queryId, new FabricPacketLoginQueryRequestPayload(packet));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -16,6 +16,9 @@
|
|||
|
||||
package net.fabricmc.fabric.impl.networking.server;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import net.minecraft.network.NetworkState;
|
||||
import net.minecraft.network.PacketByteBuf;
|
||||
import net.minecraft.network.listener.ClientCommonPacketListener;
|
||||
import net.minecraft.network.packet.Packet;
|
||||
|
@ -25,17 +28,21 @@ import net.minecraft.server.network.ServerLoginNetworkHandler;
|
|||
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.FabricPacket;
|
||||
import net.fabricmc.fabric.api.networking.v1.PacketByteBufs;
|
||||
import net.fabricmc.fabric.api.networking.v1.ServerConfigurationNetworking;
|
||||
import net.fabricmc.fabric.api.networking.v1.ServerLoginNetworking;
|
||||
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking;
|
||||
import net.fabricmc.fabric.impl.networking.GlobalReceiverRegistry;
|
||||
import net.fabricmc.fabric.impl.networking.NetworkHandlerExtensions;
|
||||
import net.fabricmc.fabric.impl.networking.NetworkingImpl;
|
||||
import net.fabricmc.fabric.impl.networking.payload.FabricPacketPayload;
|
||||
import net.fabricmc.fabric.impl.networking.payload.PacketByteBufPayload;
|
||||
|
||||
public final class ServerNetworkingImpl {
|
||||
public static final GlobalReceiverRegistry<ServerLoginNetworking.LoginQueryResponseHandler> LOGIN = new GlobalReceiverRegistry<>();
|
||||
public static final GlobalReceiverRegistry<ServerConfigurationNetworking.ConfigurationChannelHandler> CONFIGURATION = new GlobalReceiverRegistry<>();
|
||||
public static final GlobalReceiverRegistry<ServerPlayNetworking.PlayChannelHandler> PLAY = new GlobalReceiverRegistry<>();
|
||||
public static final GlobalReceiverRegistry<ServerLoginNetworking.LoginQueryResponseHandler> LOGIN = new GlobalReceiverRegistry<>(NetworkState.LOGIN);
|
||||
public static final GlobalReceiverRegistry<ServerConfigurationNetworking.ConfigurationChannelHandler> CONFIGURATION = new GlobalReceiverRegistry<>(NetworkState.CONFIGURATION);
|
||||
public static final GlobalReceiverRegistry<ServerPlayNetworking.PlayChannelHandler> PLAY = new GlobalReceiverRegistry<>(NetworkState.PLAY);
|
||||
|
||||
public static ServerPlayNetworkAddon getAddon(ServerPlayNetworkHandler handler) {
|
||||
return (ServerPlayNetworkAddon) ((NetworkHandlerExtensions) handler).getAddon();
|
||||
|
@ -52,4 +59,17 @@ public final class ServerNetworkingImpl {
|
|||
public static Packet<ClientCommonPacketListener> createC2SPacket(Identifier channel, PacketByteBuf buf) {
|
||||
return new CustomPayloadS2CPacket(new PacketByteBufPayload(channel, buf));
|
||||
}
|
||||
|
||||
public static Packet<ClientCommonPacketListener> createC2SPacket(FabricPacket packet) {
|
||||
Objects.requireNonNull(packet, "Packet cannot be null");
|
||||
Objects.requireNonNull(packet.getType(), "Packet#getType cannot return null");
|
||||
|
||||
if (NetworkingImpl.WRITE_FABRIC_PACKET_CALLING_THREAD) {
|
||||
PacketByteBuf buf = PacketByteBufs.create();
|
||||
packet.write(buf);
|
||||
return createC2SPacket(packet.getType().getId(), buf);
|
||||
}
|
||||
|
||||
return new CustomPayloadS2CPacket(new FabricPacketPayload(packet));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ import net.minecraft.server.MinecraftServer;
|
|||
import net.minecraft.server.network.ServerPlayNetworkHandler;
|
||||
import net.minecraft.util.Identifier;
|
||||
|
||||
import net.fabricmc.fabric.api.networking.v1.FabricPacket;
|
||||
import net.fabricmc.fabric.api.networking.v1.S2CPlayChannelEvents;
|
||||
import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents;
|
||||
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking;
|
||||
|
@ -96,6 +97,11 @@ public final class ServerPlayNetworkAddon extends AbstractChanneledNetworkAddon<
|
|||
return ServerPlayNetworking.createS2CPacket(channelName, buf);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Packet<?> createPacket(FabricPacket packet) {
|
||||
return ServerPlayNetworking.createS2CPacket(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void invokeRegisterEvent(List<Identifier> ids) {
|
||||
S2CPlayChannelEvents.REGISTER.invoker().onChannelRegister(this.handler, this, this.server, ids);
|
||||
|
@ -138,6 +144,6 @@ public final class ServerPlayNetworkAddon extends AbstractChanneledNetworkAddon<
|
|||
|
||||
@Override
|
||||
protected boolean isReservedChannel(Identifier channelName) {
|
||||
return NetworkingImpl.isReservedPlayChannel(channelName);
|
||||
return NetworkingImpl.isReservedCommonChannel(channelName);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ import org.spongepowered.asm.mixin.injection.Inject;
|
|||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
import net.minecraft.network.packet.c2s.common.CustomPayloadC2SPacket;
|
||||
import net.minecraft.network.packet.c2s.common.PlayPongC2SPacket;
|
||||
import net.minecraft.server.network.ServerCommonNetworkHandler;
|
||||
|
||||
import net.fabricmc.fabric.impl.networking.NetworkHandlerExtensions;
|
||||
|
@ -51,4 +52,11 @@ public abstract class ServerCommonNetworkHandlerMixin implements NetworkHandlerE
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "onPlayPong", at = @At("HEAD"))
|
||||
private void onPlayPong(PlayPongC2SPacket packet, CallbackInfo ci) {
|
||||
if (getAddon() instanceof ServerConfigurationNetworkAddon addon) {
|
||||
addon.onPong(packet.getParameter());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,12 @@
|
|||
|
||||
package net.fabricmc.fabric.mixin.networking;
|
||||
|
||||
import java.util.Queue;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
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;
|
||||
|
@ -28,17 +33,42 @@ import net.minecraft.network.packet.s2c.common.DisconnectS2CPacket;
|
|||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.network.ServerCommonNetworkHandler;
|
||||
import net.minecraft.server.network.ServerConfigurationNetworkHandler;
|
||||
import net.minecraft.server.network.ServerPlayerConfigurationTask;
|
||||
import net.minecraft.text.Text;
|
||||
|
||||
import net.fabricmc.fabric.api.networking.v1.FabricServerConfigurationNetworkHandler;
|
||||
import net.fabricmc.fabric.impl.networking.DisconnectPacketSource;
|
||||
import net.fabricmc.fabric.impl.networking.NetworkHandlerExtensions;
|
||||
import net.fabricmc.fabric.impl.networking.server.ServerConfigurationNetworkAddon;
|
||||
|
||||
// We want to apply a bit earlier than other mods which may not use us in order to prevent refCount issues
|
||||
@Mixin(value = ServerConfigurationNetworkHandler.class, priority = 999)
|
||||
public abstract class ServerConfigurationNetworkHandlerMixin extends ServerCommonNetworkHandler implements NetworkHandlerExtensions, DisconnectPacketSource {
|
||||
@Mixin(value = ServerConfigurationNetworkHandler.class, priority = 900)
|
||||
public abstract class ServerConfigurationNetworkHandlerMixin extends ServerCommonNetworkHandler implements NetworkHandlerExtensions, DisconnectPacketSource, FabricServerConfigurationNetworkHandler {
|
||||
@Shadow
|
||||
@Nullable
|
||||
private ServerPlayerConfigurationTask currentTask;
|
||||
|
||||
@Shadow
|
||||
protected abstract void onTaskFinished(ServerPlayerConfigurationTask.Key key);
|
||||
|
||||
@Shadow
|
||||
@Final
|
||||
private Queue<ServerPlayerConfigurationTask> tasks;
|
||||
|
||||
@Shadow
|
||||
public abstract boolean isConnectionOpen();
|
||||
|
||||
@Shadow
|
||||
public abstract void sendConfigurations();
|
||||
|
||||
@Unique
|
||||
ServerConfigurationNetworkAddon addon;
|
||||
private ServerConfigurationNetworkAddon addon;
|
||||
|
||||
@Unique
|
||||
private boolean sentConfiguration;
|
||||
|
||||
@Unique
|
||||
private boolean earlyTaskExecution;
|
||||
|
||||
public ServerConfigurationNetworkHandlerMixin(MinecraftServer server, ClientConnection connection, int keepAliveId) {
|
||||
super(server, connection, keepAliveId);
|
||||
|
@ -51,14 +81,63 @@ public abstract class ServerConfigurationNetworkHandlerMixin extends ServerCommo
|
|||
this.addon.lateInit();
|
||||
}
|
||||
|
||||
@Inject(method = "sendConfigurations", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/MinecraftServer;getCombinedDynamicRegistries()Lnet/minecraft/registry/CombinedDynamicRegistries;"))
|
||||
@Inject(method = "sendConfigurations", at = @At("HEAD"), cancellable = true)
|
||||
private void onClientReady(CallbackInfo ci) {
|
||||
this.addon.onClientReady();
|
||||
// Send the initial channel registration packet
|
||||
if (this.addon.startConfiguration()) {
|
||||
assert currentTask == null;
|
||||
ci.cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
// Ready to start sending packets
|
||||
if (!sentConfiguration) {
|
||||
this.addon.preConfiguration();
|
||||
sentConfiguration = true;
|
||||
earlyTaskExecution = true;
|
||||
}
|
||||
|
||||
// Run the early tasks
|
||||
if (earlyTaskExecution) {
|
||||
if (pollEarlyTasks()) {
|
||||
ci.cancel();
|
||||
return;
|
||||
} else {
|
||||
earlyTaskExecution = false;
|
||||
}
|
||||
}
|
||||
|
||||
// All early tasks should have been completed
|
||||
assert currentTask == null;
|
||||
assert tasks.isEmpty();
|
||||
|
||||
// Run the vanilla tasks.
|
||||
this.addon.configuration();
|
||||
}
|
||||
|
||||
@Inject(method = "sendConfigurations", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/network/ServerConfigurationNetworkHandler;queueSendResourcePackTask()V"))
|
||||
private void sendConfigurations(CallbackInfo ci) {
|
||||
this.addon.sendConfiguration();
|
||||
@Unique
|
||||
private boolean pollEarlyTasks() {
|
||||
if (!earlyTaskExecution) {
|
||||
throw new IllegalStateException("Early task execution has finished");
|
||||
}
|
||||
|
||||
if (this.currentTask != null) {
|
||||
throw new IllegalStateException("Task " + this.currentTask.getKey().id() + " has not finished yet");
|
||||
}
|
||||
|
||||
if (!this.isConnectionOpen()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final ServerPlayerConfigurationTask task = this.tasks.poll();
|
||||
|
||||
if (task != null) {
|
||||
this.currentTask = task;
|
||||
task.sendPacket(this::sendPacket);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Inject(method = "onDisconnected", at = @At("HEAD"))
|
||||
|
@ -75,4 +154,26 @@ public abstract class ServerConfigurationNetworkHandlerMixin extends ServerCommo
|
|||
public Packet<?> createDisconnectPacket(Text message) {
|
||||
return new DisconnectS2CPacket(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addTask(ServerPlayerConfigurationTask task) {
|
||||
tasks.add(task);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void completeTask(ServerPlayerConfigurationTask.Key key) {
|
||||
if (!earlyTaskExecution) {
|
||||
onTaskFinished(key);
|
||||
return;
|
||||
}
|
||||
|
||||
final ServerPlayerConfigurationTask.Key currentKey = this.currentTask != null ? this.currentTask.getKey() : null;
|
||||
|
||||
if (!key.equals(currentKey)) {
|
||||
throw new IllegalStateException("Unexpected request for task finish, current task: " + currentKey + ", requested: " + key);
|
||||
}
|
||||
|
||||
this.currentTask = null;
|
||||
sendConfigurations();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
],
|
||||
"entrypoints": {
|
||||
"main": [
|
||||
"net.fabricmc.fabric.impl.networking.NetworkingImpl::init"
|
||||
"net.fabricmc.fabric.impl.networking.CommonPacketsImpl::init"
|
||||
],
|
||||
"client": [
|
||||
"net.fabricmc.fabric.impl.networking.client.ClientNetworkingImpl::clientInit"
|
||||
|
@ -37,6 +37,9 @@
|
|||
}
|
||||
],
|
||||
"custom": {
|
||||
"fabric-api:module-lifecycle": "stable"
|
||||
"fabric-api:module-lifecycle": "stable",
|
||||
"loom:injected_interfaces": {
|
||||
"net/minecraft/class_8610": [ "net/fabricmc/fabric/api/networking/v1/FabricServerConfigurationNetworkHandler" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,335 @@
|
|||
/*
|
||||
* 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.unit;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertIterableEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.Mockito.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
import net.minecraft.client.network.ClientConfigurationNetworkHandler;
|
||||
import net.minecraft.network.NetworkState;
|
||||
import net.minecraft.network.PacketByteBuf;
|
||||
import net.minecraft.network.packet.CustomPayload;
|
||||
import net.minecraft.server.network.ServerConfigurationNetworkHandler;
|
||||
import net.minecraft.util.Identifier;
|
||||
|
||||
import net.fabricmc.fabric.api.client.networking.v1.ClientConfigurationNetworking;
|
||||
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
|
||||
import net.fabricmc.fabric.api.networking.v1.PacketByteBufs;
|
||||
import net.fabricmc.fabric.api.networking.v1.PacketSender;
|
||||
import net.fabricmc.fabric.api.networking.v1.ServerConfigurationNetworking;
|
||||
import net.fabricmc.fabric.impl.networking.ChannelInfoHolder;
|
||||
import net.fabricmc.fabric.impl.networking.CommonPacketHandler;
|
||||
import net.fabricmc.fabric.impl.networking.CommonPacketsImpl;
|
||||
import net.fabricmc.fabric.impl.networking.CommonRegisterPayload;
|
||||
import net.fabricmc.fabric.impl.networking.CommonVersionPayload;
|
||||
import net.fabricmc.fabric.impl.networking.client.ClientConfigurationNetworkAddon;
|
||||
import net.fabricmc.fabric.impl.networking.client.ClientNetworkingImpl;
|
||||
import net.fabricmc.fabric.impl.networking.server.ServerConfigurationNetworkAddon;
|
||||
import net.fabricmc.fabric.impl.networking.server.ServerNetworkingImpl;
|
||||
|
||||
public class CommonPacketTests {
|
||||
private PacketSender packetSender;
|
||||
private ChannelInfoHolder channelInfoHolder;
|
||||
|
||||
private ClientConfigurationNetworkHandler clientNetworkHandler;
|
||||
private ClientConfigurationNetworkAddon clientAddon;
|
||||
|
||||
private ServerConfigurationNetworkHandler serverNetworkHandler;
|
||||
private ServerConfigurationNetworkAddon serverAddon;
|
||||
|
||||
@BeforeAll
|
||||
static void beforeAll() {
|
||||
CommonPacketsImpl.init();
|
||||
ClientNetworkingImpl.clientInit();
|
||||
|
||||
// Register a receiver to send in the play registry response
|
||||
ClientPlayNetworking.registerGlobalReceiver(new Identifier("fabric", "global_client"), (client, handler, buf, responseSender) -> {
|
||||
});
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
packetSender = mock(PacketSender.class);
|
||||
channelInfoHolder = new MockChannelInfoHolder();
|
||||
|
||||
clientNetworkHandler = mock(ClientConfigurationNetworkHandler.class);
|
||||
clientAddon = mock(ClientConfigurationNetworkAddon.class);
|
||||
when(ClientNetworkingImpl.getAddon(clientNetworkHandler)).thenReturn(clientAddon);
|
||||
when(clientAddon.getChannelInfoHolder()).thenReturn(channelInfoHolder);
|
||||
|
||||
serverNetworkHandler = mock(ServerConfigurationNetworkHandler.class);
|
||||
serverAddon = mock(ServerConfigurationNetworkAddon.class);
|
||||
when(ServerNetworkingImpl.getAddon(serverNetworkHandler)).thenReturn(serverAddon);
|
||||
when(serverAddon.getChannelInfoHolder()).thenReturn(channelInfoHolder);
|
||||
}
|
||||
|
||||
// Test handling the version packet on the client
|
||||
@Test
|
||||
void handleVersionPacketClient() {
|
||||
ClientConfigurationNetworking.ConfigurationChannelHandler packetHandler = ClientNetworkingImpl.CONFIGURATION.getHandler(CommonVersionPayload.PACKET_ID);
|
||||
assertNotNull(packetHandler);
|
||||
|
||||
// Receive a packet from the server
|
||||
PacketByteBuf buf = PacketByteBufs.create();
|
||||
buf.writeIntArray(new int[]{1, 2, 3});
|
||||
|
||||
packetHandler.receive(null, clientNetworkHandler, buf, packetSender);
|
||||
|
||||
// Assert the entire packet was read
|
||||
assertEquals(0, buf.readableBytes());
|
||||
|
||||
// Check the response we are sending back to the server
|
||||
PacketByteBuf response = readResponse(packetSender);
|
||||
assertArrayEquals(new int[]{1}, response.readIntArray());
|
||||
assertEquals(0, response.readableBytes());
|
||||
|
||||
assertEquals(1, getNegotiatedVersion(clientAddon));
|
||||
}
|
||||
|
||||
// Test handling the version packet on the client, when the server sends unsupported versions
|
||||
@Test
|
||||
void handleVersionPacketClientUnsupported() {
|
||||
ClientConfigurationNetworking.ConfigurationChannelHandler packetHandler = ClientNetworkingImpl.CONFIGURATION.getHandler(CommonVersionPayload.PACKET_ID);
|
||||
assertNotNull(packetHandler);
|
||||
|
||||
// Receive a packet from the server
|
||||
PacketByteBuf buf = PacketByteBufs.create();
|
||||
buf.writeIntArray(new int[]{2, 3}); // We only support version 1
|
||||
|
||||
assertThrows(UnsupportedOperationException.class, () -> {
|
||||
packetHandler.receive(null, clientNetworkHandler, buf, packetSender);
|
||||
});
|
||||
|
||||
// Assert the entire packet was read
|
||||
assertEquals(0, buf.readableBytes());
|
||||
}
|
||||
|
||||
// Test handling the version packet on the server
|
||||
@Test
|
||||
void handleVersionPacketServer() {
|
||||
ServerConfigurationNetworking.ConfigurationChannelHandler packetHandler = ServerNetworkingImpl.CONFIGURATION.getHandler(CommonVersionPayload.PACKET_ID);
|
||||
assertNotNull(packetHandler);
|
||||
|
||||
// Receive a packet from the client
|
||||
PacketByteBuf buf = PacketByteBufs.create();
|
||||
buf.writeIntArray(new int[]{1, 2, 3});
|
||||
|
||||
packetHandler.receive(null, serverNetworkHandler, buf, null);
|
||||
|
||||
// Assert the entire packet was read
|
||||
assertEquals(0, buf.readableBytes());
|
||||
assertEquals(1, getNegotiatedVersion(serverAddon));
|
||||
}
|
||||
|
||||
// Test handling the version packet on the server unsupported version
|
||||
@Test
|
||||
void handleVersionPacketServerUnsupported() {
|
||||
ServerConfigurationNetworking.ConfigurationChannelHandler packetHandler = ServerNetworkingImpl.CONFIGURATION.getHandler(CommonVersionPayload.PACKET_ID);
|
||||
assertNotNull(packetHandler);
|
||||
|
||||
// Receive a packet from the client
|
||||
PacketByteBuf buf = PacketByteBufs.create();
|
||||
buf.writeIntArray(new int[]{3}); // Server only supports version 1
|
||||
|
||||
assertThrows(UnsupportedOperationException.class, () -> {
|
||||
packetHandler.receive(null, serverNetworkHandler, buf, packetSender);
|
||||
});
|
||||
|
||||
// Assert the entire packet was read
|
||||
assertEquals(0, buf.readableBytes());
|
||||
}
|
||||
|
||||
// Test handing the play registry packet on the client configuration handler
|
||||
@Test
|
||||
void handlePlayRegistryClient() {
|
||||
ClientConfigurationNetworking.ConfigurationChannelHandler packetHandler = ClientNetworkingImpl.CONFIGURATION.getHandler(CommonRegisterPayload.PACKET_ID);
|
||||
assertNotNull(packetHandler);
|
||||
|
||||
when(clientAddon.getNegotiatedVersion()).thenReturn(1);
|
||||
|
||||
// Receive a packet from the server
|
||||
PacketByteBuf buf = PacketByteBufs.create();
|
||||
buf.writeVarInt(1); // Version
|
||||
buf.writeString("play"); // Target phase
|
||||
buf.writeCollection(List.of(new Identifier("fabric", "test")), PacketByteBuf::writeIdentifier);
|
||||
|
||||
packetHandler.receive(null, clientNetworkHandler, buf, packetSender);
|
||||
|
||||
// Assert the entire packet was read
|
||||
assertEquals(0, buf.readableBytes());
|
||||
assertIterableEquals(List.of(new Identifier("fabric", "test")), channelInfoHolder.getPendingChannelsNames(NetworkState.PLAY));
|
||||
|
||||
// Check the response we are sending back to the server
|
||||
PacketByteBuf response = readResponse(packetSender);
|
||||
assertEquals(1, response.readVarInt());
|
||||
assertEquals("play", response.readString());
|
||||
assertIterableEquals(List.of(new Identifier("fabric", "global_client")), response.readCollection(HashSet::new, PacketByteBuf::readIdentifier));
|
||||
assertEquals(0, response.readableBytes());
|
||||
}
|
||||
|
||||
// Test handling the configuration registry packet on the client configuration handler
|
||||
@Test
|
||||
void handleConfigurationRegistryClient() {
|
||||
ClientConfigurationNetworking.ConfigurationChannelHandler packetHandler = ClientNetworkingImpl.CONFIGURATION.getHandler(CommonRegisterPayload.PACKET_ID);
|
||||
assertNotNull(packetHandler);
|
||||
|
||||
when(clientAddon.getNegotiatedVersion()).thenReturn(1);
|
||||
when(clientAddon.createRegisterPayload()).thenAnswer(i -> new CommonRegisterPayload(1, "configuration", Set.of(new Identifier("fabric", "global_configuration_client"))));
|
||||
|
||||
// Receive a packet from the server
|
||||
PacketByteBuf buf = PacketByteBufs.create();
|
||||
buf.writeVarInt(1); // Version
|
||||
buf.writeString("configuration"); // Target phase
|
||||
buf.writeCollection(List.of(new Identifier("fabric", "test")), PacketByteBuf::writeIdentifier);
|
||||
|
||||
packetHandler.receive(null, clientNetworkHandler, buf, packetSender);
|
||||
|
||||
// Assert the entire packet was read
|
||||
assertEquals(0, buf.readableBytes());
|
||||
verify(clientAddon, times(1)).onCommonRegisterPacket(any());
|
||||
|
||||
// Check the response we are sending back to the server
|
||||
PacketByteBuf response = readResponse(packetSender);
|
||||
assertEquals(1, response.readVarInt());
|
||||
assertEquals("configuration", response.readString());
|
||||
assertIterableEquals(List.of(new Identifier("fabric", "global_configuration_client")), response.readCollection(HashSet::new, PacketByteBuf::readIdentifier));
|
||||
assertEquals(0, response.readableBytes());
|
||||
}
|
||||
|
||||
// Test handing the play registry packet on the server configuration handler
|
||||
@Test
|
||||
void handlePlayRegistryServer() {
|
||||
ServerConfigurationNetworking.ConfigurationChannelHandler packetHandler = ServerNetworkingImpl.CONFIGURATION.getHandler(CommonRegisterPayload.PACKET_ID);
|
||||
assertNotNull(packetHandler);
|
||||
|
||||
when(serverAddon.getNegotiatedVersion()).thenReturn(1);
|
||||
|
||||
// Receive a packet from the client
|
||||
PacketByteBuf buf = PacketByteBufs.create();
|
||||
buf.writeVarInt(1); // Version
|
||||
buf.writeString("play"); // Target phase
|
||||
buf.writeCollection(List.of(new Identifier("fabric", "test")), PacketByteBuf::writeIdentifier);
|
||||
|
||||
packetHandler.receive(null, serverNetworkHandler, buf, packetSender);
|
||||
|
||||
// Assert the entire packet was read
|
||||
assertEquals(0, buf.readableBytes());
|
||||
assertIterableEquals(List.of(new Identifier("fabric", "test")), channelInfoHolder.getPendingChannelsNames(NetworkState.PLAY));
|
||||
}
|
||||
|
||||
// Test handing the configuration registry packet on the server configuration handler
|
||||
@Test
|
||||
void handleConfigurationRegistryServer() {
|
||||
ServerConfigurationNetworking.ConfigurationChannelHandler packetHandler = ServerNetworkingImpl.CONFIGURATION.getHandler(CommonRegisterPayload.PACKET_ID);
|
||||
assertNotNull(packetHandler);
|
||||
|
||||
when(serverAddon.getNegotiatedVersion()).thenReturn(1);
|
||||
|
||||
// Receive a packet from the client
|
||||
PacketByteBuf buf = PacketByteBufs.create();
|
||||
buf.writeVarInt(1); // Version
|
||||
buf.writeString("configuration"); // Target phase
|
||||
buf.writeCollection(List.of(new Identifier("fabric", "test")), PacketByteBuf::writeIdentifier);
|
||||
|
||||
packetHandler.receive(null, serverNetworkHandler, buf, packetSender);
|
||||
|
||||
// Assert the entire packet was read
|
||||
assertEquals(0, buf.readableBytes());
|
||||
verify(serverAddon, times(1)).onCommonRegisterPacket(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHighestCommonVersionWithCommonElement() {
|
||||
int[] a = {1, 2, 3};
|
||||
int[] b = {1, 2};
|
||||
assertEquals(2, CommonPacketsImpl.getHighestCommonVersion(a, b));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHighestCommonVersionWithoutCommonElement() {
|
||||
int[] a = {1, 3, 5};
|
||||
int[] b = {2, 4, 6};
|
||||
assertEquals(-1, CommonPacketsImpl.getHighestCommonVersion(a, b));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHighestCommonVersionWithOneEmptyArray() {
|
||||
int[] a = {1, 3, 5};
|
||||
int[] b = {};
|
||||
assertEquals(-1, CommonPacketsImpl.getHighestCommonVersion(a, b));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHighestCommonVersionWithBothEmptyArrays() {
|
||||
int[] a = {};
|
||||
int[] b = {};
|
||||
assertEquals(-1, CommonPacketsImpl.getHighestCommonVersion(a, b));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHighestCommonVersionWithIdenticalArrays() {
|
||||
int[] a = {1, 2, 3};
|
||||
int[] b = {1, 2, 3};
|
||||
assertEquals(3, CommonPacketsImpl.getHighestCommonVersion(a, b));
|
||||
}
|
||||
|
||||
private static PacketByteBuf readResponse(PacketSender packetSender) {
|
||||
ArgumentCaptor<CustomPayload> responseCaptor = ArgumentCaptor.forClass(CustomPayload.class);
|
||||
verify(packetSender, times(1)).sendPacket(responseCaptor.capture());
|
||||
|
||||
PacketByteBuf buf = PacketByteBufs.create();
|
||||
responseCaptor.getValue().write(buf);
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
private static int getNegotiatedVersion(CommonPacketHandler packetHandler) {
|
||||
ArgumentCaptor<Integer> responseCaptor = ArgumentCaptor.forClass(Integer.class);
|
||||
verify(packetHandler, times(1)).onCommonVersionPacket(responseCaptor.capture());
|
||||
return responseCaptor.getValue();
|
||||
}
|
||||
|
||||
private static class MockChannelInfoHolder implements ChannelInfoHolder {
|
||||
private final Map<NetworkState, Collection<Identifier>> playChannels = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public Collection<Identifier> getPendingChannelsNames(NetworkState state) {
|
||||
return this.playChannels.computeIfAbsent(state, (key) -> Collections.newSetFromMap(new ConcurrentHashMap<>()));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,104 @@
|
|||
/*
|
||||
* 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.configuration;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import net.minecraft.network.PacketByteBuf;
|
||||
import net.minecraft.network.packet.Packet;
|
||||
import net.minecraft.server.network.ServerPlayerConfigurationTask;
|
||||
import net.minecraft.text.Text;
|
||||
import net.minecraft.util.Identifier;
|
||||
|
||||
import net.fabricmc.api.ModInitializer;
|
||||
import net.fabricmc.fabric.api.networking.v1.FabricPacket;
|
||||
import net.fabricmc.fabric.api.networking.v1.PacketType;
|
||||
import net.fabricmc.fabric.api.networking.v1.ServerConfigurationConnectionEvents;
|
||||
import net.fabricmc.fabric.api.networking.v1.ServerConfigurationNetworking;
|
||||
import net.fabricmc.fabric.test.networking.NetworkingTestmods;
|
||||
|
||||
/**
|
||||
* Also see NetworkingConfigurationClientTest.
|
||||
*/
|
||||
public class NetworkingConfigurationTest implements ModInitializer {
|
||||
@Override
|
||||
public void onInitialize() {
|
||||
ServerConfigurationConnectionEvents.CONFIGURE.register((handler, server) -> {
|
||||
// You must check to see if the client can handle your config task
|
||||
if (ServerConfigurationNetworking.canSend(handler, ConfigurationPacket.PACKET_TYPE)) {
|
||||
handler.addTask(new TestConfigurationTask("Example data"));
|
||||
} else {
|
||||
// You can opt to disconnect the client if it cannot handle the configuration task
|
||||
handler.disconnect(Text.literal("Network test configuration not supported by client"));
|
||||
}
|
||||
});
|
||||
|
||||
ServerConfigurationNetworking.registerGlobalReceiver(ConfigurationCompletePacket.PACKET_TYPE, (packet, networkHandler, responseSender) -> {
|
||||
networkHandler.completeTask(TestConfigurationTask.KEY);
|
||||
});
|
||||
}
|
||||
|
||||
public record TestConfigurationTask(String data) implements ServerPlayerConfigurationTask {
|
||||
public static final Key KEY = new Key(new Identifier(NetworkingTestmods.ID, "configure").toString());
|
||||
|
||||
@Override
|
||||
public void sendPacket(Consumer<Packet<?>> sender) {
|
||||
var packet = new ConfigurationPacket(data);
|
||||
sender.accept(ServerConfigurationNetworking.createS2CPacket(packet));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Key getKey() {
|
||||
return KEY;
|
||||
}
|
||||
}
|
||||
|
||||
public record ConfigurationPacket(String data) implements FabricPacket {
|
||||
public static final PacketType<ConfigurationPacket> PACKET_TYPE = PacketType.create(new Identifier(NetworkingTestmods.ID, "configure"), ConfigurationPacket::new);
|
||||
|
||||
public ConfigurationPacket(PacketByteBuf buf) {
|
||||
this(buf.readString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(PacketByteBuf buf) {
|
||||
buf.writeString(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketType<?> getType() {
|
||||
return PACKET_TYPE;
|
||||
}
|
||||
}
|
||||
|
||||
public record ConfigurationCompletePacket() implements FabricPacket {
|
||||
public static final PacketType<ConfigurationCompletePacket> PACKET_TYPE = PacketType.create(new Identifier(NetworkingTestmods.ID, "configure_complete"), ConfigurationCompletePacket::new);
|
||||
|
||||
public ConfigurationCompletePacket(PacketByteBuf buf) {
|
||||
this();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(PacketByteBuf buf) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketType<?> getType() {
|
||||
return PACKET_TYPE;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -11,12 +11,14 @@
|
|||
"entrypoints": {
|
||||
"main": [
|
||||
"net.fabricmc.fabric.test.networking.channeltest.NetworkingChannelTest",
|
||||
"net.fabricmc.fabric.test.networking.configuration.NetworkingConfigurationTest",
|
||||
"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.client.channeltest.NetworkingChannelClientTest",
|
||||
"net.fabricmc.fabric.test.networking.client.configuration.NetworkingConfigurationClientTest",
|
||||
"net.fabricmc.fabric.test.networking.client.DisconnectScreenTest",
|
||||
"net.fabricmc.fabric.test.networking.client.keybindreciever.NetworkingKeybindClientPacketTest",
|
||||
"net.fabricmc.fabric.test.networking.client.login.NetworkingLoginQueryClientTest",
|
||||
|
|
|
@ -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.test.networking.client.configuration;
|
||||
|
||||
import net.fabricmc.api.ClientModInitializer;
|
||||
import net.fabricmc.fabric.api.client.networking.v1.ClientConfigurationNetworking;
|
||||
import net.fabricmc.fabric.test.networking.configuration.NetworkingConfigurationTest;
|
||||
|
||||
public class NetworkingConfigurationClientTest implements ClientModInitializer {
|
||||
@Override
|
||||
public void onInitializeClient() {
|
||||
ClientConfigurationNetworking.registerGlobalReceiver(NetworkingConfigurationTest.ConfigurationPacket.PACKET_TYPE, (packet, responseSender) -> {
|
||||
// Handle stuff here
|
||||
|
||||
// Respond back to the server that the task is complete
|
||||
responseSender.sendPacket(new NetworkingConfigurationTest.ConfigurationCompletePacket());
|
||||
});
|
||||
}
|
||||
}
|
|
@ -18,12 +18,15 @@ package net.fabricmc.fabric.impl.recipe.ingredient;
|
|||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import net.minecraft.network.PacketByteBuf;
|
||||
import net.minecraft.network.handler.PacketEncoder;
|
||||
import net.minecraft.network.packet.Packet;
|
||||
import net.minecraft.server.network.ServerPlayerConfigurationTask;
|
||||
import net.minecraft.util.Identifier;
|
||||
|
||||
import net.fabricmc.api.ModInitializer;
|
||||
|
@ -82,18 +85,12 @@ public class CustomIngredientSync implements ModInitializer {
|
|||
|
||||
@Override
|
||||
public void onInitialize() {
|
||||
ServerConfigurationConnectionEvents.SEND.register((handler, server) -> {
|
||||
// TODO 1.20.2 canSend isnt working reliably during configuration
|
||||
//if (!ServerConfigurationNetworking.canSend(handler, PACKET_ID)) {
|
||||
// return;
|
||||
//}
|
||||
|
||||
// Send packet with 1 so the client can send us back the list of supported tags.
|
||||
// 1 is sent in case we need a different protocol later for some reason.
|
||||
PacketByteBuf buf = PacketByteBufs.create();
|
||||
buf.writeVarInt(PROTOCOL_VERSION_1); // max supported server protocol version
|
||||
handler.sendPacket(ServerConfigurationNetworking.createS2CPacket(PACKET_ID, buf));
|
||||
ServerConfigurationConnectionEvents.CONFIGURE.register((handler, server) -> {
|
||||
if (ServerConfigurationNetworking.canSend(handler, PACKET_ID)) {
|
||||
handler.addTask(new IngredientSyncTask());
|
||||
}
|
||||
});
|
||||
|
||||
ServerConfigurationNetworking.registerGlobalReceiver(PACKET_ID, (server, handler, buf, responseSender) -> {
|
||||
Set<Identifier> supportedCustomIngredients = decodeResponsePacket(buf);
|
||||
ChannelHandler packetEncoder = ((ServerCommonNetworkHandlerAccessor) handler).getConnection().channel.pipeline().get("encoder");
|
||||
|
@ -101,6 +98,26 @@ public class CustomIngredientSync implements ModInitializer {
|
|||
if (packetEncoder != null) { // Null in singleplayer
|
||||
((SupportedIngredientsPacketEncoder) packetEncoder).fabric_setSupportedCustomIngredients(supportedCustomIngredients);
|
||||
}
|
||||
|
||||
handler.completeTask(IngredientSyncTask.KEY);
|
||||
});
|
||||
}
|
||||
|
||||
private record IngredientSyncTask() implements ServerPlayerConfigurationTask {
|
||||
public static final Key KEY = new Key(PACKET_ID.toString());
|
||||
|
||||
@Override
|
||||
public void sendPacket(Consumer<Packet<?>> sender) {
|
||||
// Send packet with 1 so the client can send us back the list of supported tags.
|
||||
// 1 is sent in case we need a different protocol later for some reason.
|
||||
PacketByteBuf buf = PacketByteBufs.create();
|
||||
buf.writeVarInt(PROTOCOL_VERSION_1); // max supported server protocol version
|
||||
sender.accept(ServerConfigurationNetworking.createS2CPacket(PACKET_ID, buf));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Key getKey() {
|
||||
return KEY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
|
||||
package net.fabricmc.fabric.impl.client.registry.sync;
|
||||
|
||||
import java.util.concurrent.CompletionException;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
@ -23,6 +25,8 @@ import net.minecraft.text.Text;
|
|||
|
||||
import net.fabricmc.api.ClientModInitializer;
|
||||
import net.fabricmc.fabric.api.client.networking.v1.ClientConfigurationNetworking;
|
||||
import net.fabricmc.fabric.api.networking.v1.PacketByteBufs;
|
||||
import net.fabricmc.fabric.impl.registry.sync.FabricRegistryInit;
|
||||
import net.fabricmc.fabric.impl.registry.sync.RegistrySyncManager;
|
||||
import net.fabricmc.fabric.impl.registry.sync.RemapException;
|
||||
import net.fabricmc.fabric.impl.registry.sync.packet.RegistryPacketHandler;
|
||||
|
@ -37,20 +41,31 @@ public class FabricRegistryClientInit implements ClientModInitializer {
|
|||
}
|
||||
|
||||
private void registerSyncPacketReceiver(RegistryPacketHandler packetHandler) {
|
||||
ClientConfigurationNetworking.registerGlobalReceiver(packetHandler.getPacketId(), (client, handler, buf, responseSender) ->
|
||||
RegistrySyncManager.receivePacket(client, packetHandler, buf, RegistrySyncManager.DEBUG || !client.isInSingleplayer(), (e) -> {
|
||||
LOGGER.error("Registry remapping failed!", e);
|
||||
client.execute(() -> ((ClientCommonNetworkHandlerAccessor) handler).getConnection().disconnect(getText(e)));
|
||||
}));
|
||||
ClientConfigurationNetworking.registerGlobalReceiver(packetHandler.getPacketId(), (client, handler, buf, responseSender) -> {
|
||||
RegistrySyncManager.receivePacket(client, packetHandler, buf, RegistrySyncManager.DEBUG || !client.isInSingleplayer())
|
||||
.whenComplete((complete, throwable) -> {
|
||||
if (throwable != null) {
|
||||
LOGGER.error("Registry remapping failed!", throwable);
|
||||
client.execute(() -> ((ClientCommonNetworkHandlerAccessor) handler).getConnection().disconnect(getText(throwable)));
|
||||
return;
|
||||
}
|
||||
|
||||
if (complete) {
|
||||
handler.sendPacket(ClientConfigurationNetworking.createC2SPacket(FabricRegistryInit.SYNC_COMPLETE_ID, PacketByteBufs.create()));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private Text getText(Exception e) {
|
||||
private Text getText(Throwable e) {
|
||||
if (e instanceof RemapException remapException) {
|
||||
final Text text = remapException.getText();
|
||||
|
||||
if (text != null) {
|
||||
return text;
|
||||
}
|
||||
} else if (e instanceof CompletionException completionException) {
|
||||
return getText(completionException.getCause());
|
||||
}
|
||||
|
||||
return Text.literal("Registry remapping failed: " + e.getMessage());
|
||||
|
|
|
@ -17,16 +17,23 @@
|
|||
package net.fabricmc.fabric.impl.registry.sync;
|
||||
|
||||
import net.minecraft.registry.Registries;
|
||||
import net.minecraft.util.Identifier;
|
||||
|
||||
import net.fabricmc.api.ModInitializer;
|
||||
import net.fabricmc.fabric.api.event.registry.RegistryAttribute;
|
||||
import net.fabricmc.fabric.api.event.registry.RegistryAttributeHolder;
|
||||
import net.fabricmc.fabric.api.networking.v1.ServerConfigurationConnectionEvents;
|
||||
import net.fabricmc.fabric.api.networking.v1.ServerConfigurationNetworking;
|
||||
|
||||
public class FabricRegistryInit implements ModInitializer {
|
||||
public static final Identifier SYNC_COMPLETE_ID = new Identifier("fabric", "registry/sync/complete");
|
||||
|
||||
@Override
|
||||
public void onInitialize() {
|
||||
ServerConfigurationConnectionEvents.SEND.register(RegistrySyncManager::configureClient);
|
||||
ServerConfigurationConnectionEvents.BEFORE_CONFIGURE.register(RegistrySyncManager::configureClient);
|
||||
ServerConfigurationNetworking.registerGlobalReceiver(SYNC_COMPLETE_ID, (server, handler, buf, responseSender) -> {
|
||||
handler.completeTask(RegistrySyncManager.SyncConfigurationTask.KEY);
|
||||
});
|
||||
|
||||
// Synced in PlaySoundS2CPacket.
|
||||
RegistryAttributeHolder.get(Registries.SOUND_EVENT)
|
||||
|
|
|
@ -26,9 +26,8 @@ import java.util.LinkedHashMap;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionException;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
|
@ -45,11 +44,13 @@ import org.slf4j.LoggerFactory;
|
|||
|
||||
import net.minecraft.nbt.NbtCompound;
|
||||
import net.minecraft.network.PacketByteBuf;
|
||||
import net.minecraft.network.packet.Packet;
|
||||
import net.minecraft.registry.Registries;
|
||||
import net.minecraft.registry.Registry;
|
||||
import net.minecraft.registry.RegistryKey;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.network.ServerConfigurationNetworkHandler;
|
||||
import net.minecraft.server.network.ServerPlayerConfigurationTask;
|
||||
import net.minecraft.text.MutableText;
|
||||
import net.minecraft.text.Text;
|
||||
import net.minecraft.util.Formatting;
|
||||
|
@ -58,6 +59,7 @@ import net.minecraft.util.thread.ThreadExecutor;
|
|||
|
||||
import net.fabricmc.fabric.api.event.registry.RegistryAttribute;
|
||||
import net.fabricmc.fabric.api.event.registry.RegistryAttributeHolder;
|
||||
import net.fabricmc.fabric.api.networking.v1.ServerConfigurationNetworking;
|
||||
import net.fabricmc.fabric.impl.registry.sync.packet.DirectRegistryPacketHandler;
|
||||
import net.fabricmc.fabric.impl.registry.sync.packet.RegistryPacketHandler;
|
||||
|
||||
|
@ -75,30 +77,48 @@ public final class RegistrySyncManager {
|
|||
private RegistrySyncManager() { }
|
||||
|
||||
public static void configureClient(ServerConfigurationNetworkHandler handler, MinecraftServer server) {
|
||||
sendPacket(server, new ConfiguringServerPlayer(handler.getDebugProfile(), handler::sendPacket));
|
||||
}
|
||||
|
||||
static void sendPacket(MinecraftServer server, ConfiguringServerPlayer player) {
|
||||
if (!DEBUG && server.isHost(player.gameProfile())) {
|
||||
if (!DEBUG && server.isHost(handler.getDebugProfile())) {
|
||||
// Dont send in singleplayer
|
||||
return;
|
||||
}
|
||||
|
||||
sendPacket(player, DIRECT_PACKET_HANDLER);
|
||||
if (!ServerConfigurationNetworking.canSend(handler, DIRECT_PACKET_HANDLER.getPacketId())) {
|
||||
// Don't send if the client cannot receive
|
||||
return;
|
||||
}
|
||||
|
||||
final Map<Identifier, Object2IntMap<Identifier>> map = RegistrySyncManager.createAndPopulateRegistryMap(true, null);
|
||||
|
||||
if (map == null) {
|
||||
// Don't send when there is nothing to map
|
||||
return;
|
||||
}
|
||||
|
||||
handler.addTask(new SyncConfigurationTask(handler, map));
|
||||
}
|
||||
|
||||
private static void sendPacket(ConfiguringServerPlayer player, RegistryPacketHandler handler) {
|
||||
Map<Identifier, Object2IntMap<Identifier>> map = RegistrySyncManager.createAndPopulateRegistryMap(true, null);
|
||||
public record SyncConfigurationTask(
|
||||
ServerConfigurationNetworkHandler handler,
|
||||
Map<Identifier, Object2IntMap<Identifier>> map
|
||||
) implements ServerPlayerConfigurationTask {
|
||||
public static final Key KEY = new Key("fabric:registry/sync");
|
||||
|
||||
if (map != null) {
|
||||
handler.sendPacket(player, map);
|
||||
@Override
|
||||
public void sendPacket(Consumer<Packet<?>> sender) {
|
||||
DIRECT_PACKET_HANDLER.sendPacket(handler::sendPacket, map);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Key getKey() {
|
||||
return KEY;
|
||||
}
|
||||
}
|
||||
|
||||
public static void receivePacket(ThreadExecutor<?> executor, RegistryPacketHandler handler, PacketByteBuf buf, boolean accept, Consumer<Exception> errorHandler) {
|
||||
public static CompletableFuture<Boolean> receivePacket(ThreadExecutor<?> executor, RegistryPacketHandler handler, PacketByteBuf buf, boolean accept) {
|
||||
handler.receivePacket(buf);
|
||||
|
||||
if (!handler.isPacketFinished()) {
|
||||
return;
|
||||
return CompletableFuture.completedFuture(false);
|
||||
}
|
||||
|
||||
if (DEBUG) {
|
||||
|
@ -110,26 +130,22 @@ public final class RegistrySyncManager {
|
|||
|
||||
Map<Identifier, Object2IntMap<Identifier>> map = handler.getSyncedRegistryMap();
|
||||
|
||||
if (accept) {
|
||||
try {
|
||||
executor.submit(() -> {
|
||||
if (map == null) {
|
||||
errorHandler.accept(new RemapException("Received null map in sync packet!"));
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
apply(map, RemappableRegistry.RemapMode.REMOTE);
|
||||
} catch (RemapException e) {
|
||||
errorHandler.accept(e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}).get(30, TimeUnit.SECONDS);
|
||||
} catch (ExecutionException | InterruptedException | TimeoutException e) {
|
||||
errorHandler.accept(e);
|
||||
}
|
||||
if (!accept) {
|
||||
return CompletableFuture.completedFuture(true);
|
||||
}
|
||||
|
||||
return executor.submit(() -> {
|
||||
if (map == null) {
|
||||
throw new CompletionException(new RemapException("Received null map in sync packet!"));
|
||||
}
|
||||
|
||||
try {
|
||||
apply(map, RemappableRegistry.RemapMode.REMOTE);
|
||||
return true;
|
||||
} catch (RemapException e) {
|
||||
throw new CompletionException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -22,6 +22,7 @@ import java.util.Iterator;
|
|||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
|
@ -30,10 +31,10 @@ import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
|||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import net.minecraft.network.PacketByteBuf;
|
||||
import net.minecraft.network.packet.Packet;
|
||||
import net.minecraft.util.Identifier;
|
||||
|
||||
import net.fabricmc.fabric.api.networking.v1.PacketByteBufs;
|
||||
import net.fabricmc.fabric.impl.registry.sync.ConfiguringServerPlayer;
|
||||
|
||||
/**
|
||||
* A more optimized method to sync registry ids to client.
|
||||
|
@ -73,7 +74,7 @@ public class DirectRegistryPacketHandler extends RegistryPacketHandler {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void sendPacket(ConfiguringServerPlayer player, Map<Identifier, Object2IntMap<Identifier>> registryMap) {
|
||||
public void sendPacket(Consumer<Packet<?>> sender, Map<Identifier, Object2IntMap<Identifier>> registryMap) {
|
||||
PacketByteBuf buf = PacketByteBufs.create();
|
||||
|
||||
// Group registry ids with same namespace.
|
||||
|
@ -152,12 +153,12 @@ public class DirectRegistryPacketHandler extends RegistryPacketHandler {
|
|||
while (sliceIndex < readableBytes) {
|
||||
int sliceSize = Math.min(readableBytes - sliceIndex, MAX_PAYLOAD_SIZE);
|
||||
PacketByteBuf slicedBuf = PacketByteBufs.slice(buf, sliceIndex, sliceSize);
|
||||
sendPacket(player, slicedBuf);
|
||||
sendPacket(sender, slicedBuf);
|
||||
sliceIndex += sliceSize;
|
||||
}
|
||||
|
||||
// Send an empty buffer to mark the end of the split.
|
||||
sendPacket(player, PacketByteBufs.empty());
|
||||
sendPacket(sender, PacketByteBufs.empty());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
package net.fabricmc.fabric.impl.registry.sync.packet;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.zip.Deflater;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
@ -24,10 +25,11 @@ import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
|||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import net.minecraft.network.PacketByteBuf;
|
||||
import net.minecraft.network.packet.Packet;
|
||||
import net.minecraft.util.Identifier;
|
||||
|
||||
import net.fabricmc.fabric.api.networking.v1.PacketByteBufs;
|
||||
import net.fabricmc.fabric.impl.registry.sync.ConfiguringServerPlayer;
|
||||
import net.fabricmc.fabric.api.networking.v1.ServerConfigurationNetworking;
|
||||
import net.fabricmc.fabric.impl.registry.sync.RegistrySyncManager;
|
||||
|
||||
public abstract class RegistryPacketHandler {
|
||||
|
@ -36,7 +38,7 @@ public abstract class RegistryPacketHandler {
|
|||
|
||||
public abstract Identifier getPacketId();
|
||||
|
||||
public abstract void sendPacket(ConfiguringServerPlayer player, Map<Identifier, Object2IntMap<Identifier>> registryMap);
|
||||
public abstract void sendPacket(Consumer<Packet<?>> sender, Map<Identifier, Object2IntMap<Identifier>> registryMap);
|
||||
|
||||
public abstract void receivePacket(PacketByteBuf buf);
|
||||
|
||||
|
@ -47,8 +49,8 @@ public abstract class RegistryPacketHandler {
|
|||
@Nullable
|
||||
public abstract Map<Identifier, Object2IntMap<Identifier>> getSyncedRegistryMap();
|
||||
|
||||
protected final void sendPacket(ConfiguringServerPlayer player, PacketByteBuf buf) {
|
||||
player.sendPacket(getPacketId(), buf);
|
||||
protected final void sendPacket(Consumer<Packet<?>> sender, PacketByteBuf buf) {
|
||||
sender.accept(ServerConfigurationNetworking.createS2CPacket(getPacketId(), buf));
|
||||
}
|
||||
|
||||
protected final void computeBufSize(PacketByteBuf buf) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue