Add separate client-side payload handler registration (#2272)

This commit is contained in:
Dennis C 2025-06-30 16:55:20 +02:00 committed by GitHub
parent f4117d2bdb
commit fee486f017
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
35 changed files with 615 additions and 375 deletions

View File

@ -47,7 +47,7 @@
+
+ // Neo: Handle modded payloads. Vanilla payloads do not get sent to the modded handling pass. Additional payloads cannot be registered in the minecraft domain.
+ if (net.neoforged.neoforge.network.registration.NetworkRegistry.isModdedPayload(p_295727_.payload())) {
+ net.neoforged.neoforge.network.registration.NetworkRegistry.handleModdedPayload(this, p_295727_);
+ net.neoforged.neoforge.client.network.registration.ClientNetworkRegistry.handleModdedPayload(this, p_295727_);
+ return;
+ }
+

View File

@ -17,7 +17,7 @@
+ // Neo: Fallback detection layer for vanilla servers
+ if (this.connectionType.isOther()) {
+ this.initializedConnection = true;
+ net.neoforged.neoforge.network.registration.NetworkRegistry.initializeOtherConnection(this);
+ net.neoforged.neoforge.client.network.registration.ClientNetworkRegistry.initializeOtherConnection(this);
+ }
}
@ -45,7 +45,7 @@
+ // Packets can only be sent after the outbound protocol is set up again
+ if (!this.initializedConnection && this.connectionType.isOther()) {
+ // Neo: Fallback detection for servers with a delayed brand payload (BungeeCord)
+ net.neoforged.neoforge.network.registration.NetworkRegistry.initializeOtherConnection(this);
+ net.neoforged.neoforge.client.network.registration.ClientNetworkRegistry.initializeOtherConnection(this);
+ }
+ net.neoforged.neoforge.network.registration.NetworkRegistry.onConfigurationFinished(this);
this.connection.send(ServerboundFinishConfigurationPacket.INSTANCE);
@ -67,14 +67,14 @@
+ // Handle the query payload by responding with the client's network channels. Update the connection type accordingly.
+ if (packet.payload() instanceof net.neoforged.neoforge.network.payload.ModdedNetworkQueryPayload) {
+ this.connectionType = net.neoforged.neoforge.network.connection.ConnectionType.NEOFORGE;
+ net.neoforged.neoforge.network.registration.NetworkRegistry.onNetworkQuery(this);
+ net.neoforged.neoforge.client.network.registration.ClientNetworkRegistry.onNetworkQuery(this);
+ return;
+ }
+
+ // Receiving a modded network payload implies a successful negotiation by the server.
+ if (packet.payload() instanceof net.neoforged.neoforge.network.payload.ModdedNetworkPayload moddedNetworkPayload) {
+ this.initializedConnection = true;
+ net.neoforged.neoforge.network.registration.NetworkRegistry.initializeNeoForgeConnection(this, moddedNetworkPayload.setup());
+ net.neoforged.neoforge.client.network.registration.ClientNetworkRegistry.initializeNeoForgeConnection(this, moddedNetworkPayload.setup());
+ return;
+ }
+
@ -87,7 +87,7 @@
+ // Receiving a brand payload without having transitioned to a Neo connection implies a non-modded connection has begun.
+ if (this.connectionType.isOther() && packet.payload() instanceof net.minecraft.network.protocol.common.custom.BrandPayload) {
+ this.initializedConnection = true;
+ net.neoforged.neoforge.network.registration.NetworkRegistry.initializeOtherConnection(this);
+ net.neoforged.neoforge.client.network.registration.ClientNetworkRegistry.initializeOtherConnection(this);
+ // Continue processing the brand payload
+ }
+

View File

@ -5,47 +5,19 @@
package net.neoforged.neoforge.client.internal;
import java.util.Objects;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.multiplayer.ClientPacketListener;
import net.minecraft.core.HolderLookup;
import net.minecraft.core.Registry;
import net.minecraft.network.protocol.common.ClientCommonPacketListener;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.thread.BlockableEventLoop;
import net.minecraft.world.item.TooltipFlag;
import net.neoforged.neoforge.client.network.handlers.ClientPayloadHandler;
import net.neoforged.neoforge.client.network.handling.ClientPayloadContext;
import net.neoforged.neoforge.internal.NeoForgeProxy;
import net.neoforged.neoforge.network.handling.IPayloadContext;
import net.neoforged.neoforge.network.payload.ClientDispatchPayload;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
@ApiStatus.Internal
public class NeoForgeClientProxy extends NeoForgeProxy {
@Override
public void sendToServer(CustomPacketPayload payload, CustomPacketPayload... payloads) {
ClientPacketListener listener = Objects.requireNonNull(Minecraft.getInstance().getConnection());
listener.send(payload);
for (CustomPacketPayload otherPayload : payloads) {
listener.send(otherPayload);
}
}
@Override
public IPayloadContext newClientPayloadContext(ClientCommonPacketListener listener, ResourceLocation payloadId) {
return new ClientPayloadContext(listener, payloadId);
}
@Override
public void handleClientPayload(ClientDispatchPayload payload, IPayloadContext context) {
ClientPayloadHandler.dispatch(payload, context);
}
@Override
public BlockableEventLoop<Runnable> getClientExecutor() {
return Minecraft.getInstance();

View File

@ -26,6 +26,7 @@ import net.neoforged.fml.VersionChecker;
import net.neoforged.fml.loading.EarlyLoadingScreenController;
import net.neoforged.neoforge.client.config.NeoForgeClientConfig;
import net.neoforged.neoforge.client.gui.LoadingErrorScreen;
import net.neoforged.neoforge.client.network.registration.ClientNetworkRegistry;
import net.neoforged.neoforge.internal.CommonModLoader;
import net.neoforged.neoforge.logging.CrashReportExtender;
import net.neoforged.neoforge.resource.ResourcePackLoader;
@ -97,7 +98,10 @@ public class ClientModLoader extends CommonModLoader {
}
private static void finishModLoading(Executor syncExecutor, Executor parallelExecutor) {
catchLoadingException(() -> finish(syncExecutor, parallelExecutor));
catchLoadingException(() -> {
finish(syncExecutor, parallelExecutor);
ModLoader.runInitTask("Client network registry lock", syncExecutor, () -> {}, ClientNetworkRegistry::setup);
});
loading = false;
loadingComplete = true;
}

View File

@ -0,0 +1,31 @@
/*
* Copyright (c) NeoForged and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.neoforged.neoforge.client.network;
import java.util.Objects;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ClientPacketListener;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
/**
* Means to distribute serverbound packets
*/
public final class ClientPacketDistributor {
private ClientPacketDistributor() {}
/**
* Send the given payload(s) to the server
*/
public static void sendToServer(CustomPacketPayload payload, CustomPacketPayload... payloads) {
ClientPacketListener listener = Objects.requireNonNull(Minecraft.getInstance().getConnection());
Objects.requireNonNull(payload, "Cannot send null payload");
listener.send(payload);
for (CustomPacketPayload otherPayload : payloads) {
Objects.requireNonNull(otherPayload, "Cannot send null payload");
listener.send(otherPayload);
}
}
}

View File

@ -3,7 +3,7 @@
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.neoforged.neoforge.client.network.handlers;
package net.neoforged.neoforge.client.network;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
@ -25,19 +25,23 @@ import net.minecraft.world.entity.Entity;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.inventory.MenuType;
import net.minecraft.world.item.crafting.RecipeMap;
import net.neoforged.api.distmarker.Dist;
import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.fml.common.EventBusSubscriber;
import net.neoforged.neoforge.client.event.RecipesReceivedEvent;
import net.neoforged.neoforge.client.network.event.RegisterClientPayloadHandlersEvent;
import net.neoforged.neoforge.client.registries.ClientRegistryManager;
import net.neoforged.neoforge.common.NeoForge;
import net.neoforged.neoforge.common.world.AuxiliaryLightManager;
import net.neoforged.neoforge.common.world.LevelChunkAuxiliaryLightManager;
import net.neoforged.neoforge.entity.IEntityWithComplexSpawn;
import net.neoforged.neoforge.internal.versions.neoforge.NeoForgeVersion;
import net.neoforged.neoforge.network.ConfigSync;
import net.neoforged.neoforge.network.handling.IPayloadContext;
import net.neoforged.neoforge.network.payload.AdvancedAddEntityPayload;
import net.neoforged.neoforge.network.payload.AdvancedContainerSetDataPayload;
import net.neoforged.neoforge.network.payload.AdvancedOpenScreenPayload;
import net.neoforged.neoforge.network.payload.AuxiliaryLightDataPayload;
import net.neoforged.neoforge.network.payload.ClientDispatchPayload;
import net.neoforged.neoforge.network.payload.ClientboundCustomSetTimePayload;
import net.neoforged.neoforge.network.payload.ConfigFilePayload;
import net.neoforged.neoforge.network.payload.FrozenRegistryPayload;
@ -53,28 +57,28 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ApiStatus.Internal
public final class ClientPayloadHandler {
@EventBusSubscriber(modid = NeoForgeVersion.MOD_ID, value = Dist.CLIENT)
final class ClientPayloadHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(ClientPayloadHandler.class);
private static final Set<ResourceLocation> toSynchronize = Sets.newConcurrentHashSet();
private static final Map<ResourceLocation, RegistrySnapshot> synchronizedRegistries = Maps.newConcurrentMap();
private ClientPayloadHandler() {}
public static void dispatch(ClientDispatchPayload payload, IPayloadContext context) {
switch (payload) {
case AdvancedAddEntityPayload advancedAddEntityPayload -> handle(advancedAddEntityPayload, context);
case AdvancedContainerSetDataPayload advancedContainerSetDataPayload -> handle(advancedContainerSetDataPayload, context);
case AdvancedOpenScreenPayload advancedOpenScreenPayload -> handle(advancedOpenScreenPayload, context);
case AuxiliaryLightDataPayload auxiliaryLightDataPayload -> handle(auxiliaryLightDataPayload, context);
case ClientboundCustomSetTimePayload clientboundCustomSetTimePayload -> handle(clientboundCustomSetTimePayload, context);
case ConfigFilePayload configFilePayload -> handle(configFilePayload, context);
case FrozenRegistryPayload frozenRegistryPayload -> handle(frozenRegistryPayload, context);
case FrozenRegistrySyncCompletedPayload frozenRegistrySyncCompletedPayload -> handle(frozenRegistrySyncCompletedPayload, context);
case FrozenRegistrySyncStartPayload frozenRegistrySyncStartPayload -> handle(frozenRegistrySyncStartPayload, context);
case KnownRegistryDataMapsPayload knownRegistryDataMapsPayload -> ClientRegistryManager.handleKnownDataMaps(knownRegistryDataMapsPayload, context);
case RecipeContentPayload recipeContentPayload -> handle(recipeContentPayload, context);
case RegistryDataMapSyncPayload<?> registryDataMapSyncPayload -> ClientRegistryManager.handleDataMapSync(registryDataMapSyncPayload, context);
}
@SubscribeEvent
private static void register(RegisterClientPayloadHandlersEvent event) {
event.register(ConfigFilePayload.TYPE, ClientPayloadHandler::handle);
event.register(FrozenRegistrySyncStartPayload.TYPE, ClientPayloadHandler::handle);
event.register(FrozenRegistryPayload.TYPE, ClientPayloadHandler::handle);
event.register(FrozenRegistrySyncCompletedPayload.TYPE, ClientPayloadHandler::handle);
event.register(KnownRegistryDataMapsPayload.TYPE, ClientRegistryManager::handleKnownDataMaps);
event.register(AdvancedAddEntityPayload.TYPE, ClientPayloadHandler::handle);
event.register(AdvancedOpenScreenPayload.TYPE, ClientPayloadHandler::handle);
event.register(AuxiliaryLightDataPayload.TYPE, ClientPayloadHandler::handle);
event.register(RegistryDataMapSyncPayload.TYPE, ClientRegistryManager::handleDataMapSync);
event.register(AdvancedContainerSetDataPayload.TYPE, ClientPayloadHandler::handle);
event.register(ClientboundCustomSetTimePayload.TYPE, ClientPayloadHandler::handle);
event.register(RecipeContentPayload.TYPE, ClientPayloadHandler::handle);
}
private static void handle(FrozenRegistryPayload payload, IPayloadContext context) {

View File

@ -0,0 +1,47 @@
/*
* Copyright (c) NeoForged and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.neoforged.neoforge.client.network.event;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.neoforged.bus.api.Event;
import net.neoforged.fml.event.IModBusEvent;
import net.neoforged.neoforge.client.network.registration.ClientNetworkRegistry;
import net.neoforged.neoforge.network.handling.IPayloadHandler;
import net.neoforged.neoforge.network.registration.HandlerThread;
import org.jetbrains.annotations.ApiStatus;
/**
* Event fired on the mod event bus when the {@link ClientNetworkRegistry} is being set up.
* <p>
* This event is used to assign payload handlers to clientbound payload types.
*/
public class RegisterClientPayloadHandlersEvent extends Event implements IModBusEvent {
@ApiStatus.Internal
public RegisterClientPayloadHandlersEvent() {}
/**
* Registers the provided {@link IPayloadHandler} as the client handler to be invoked on the main thread
* for the provided {@link CustomPacketPayload.Type}
*
* @param type The payload type to register the handler for
* @param handler The client-side payload handler to register
*/
public <T extends CustomPacketPayload> void register(CustomPacketPayload.Type<T> type, IPayloadHandler<T> handler) {
this.register(type, HandlerThread.MAIN, handler);
}
/**
* Registers the provided {@link IPayloadHandler} as the client handler to be invoked on the specified thread
* for the provided {@link CustomPacketPayload.Type}
*
* @param type The payload type to register the handler for
* @param thread The thread the handler should be invoked on
* @param handler The client-side payload handler to register
*/
public <T extends CustomPacketPayload> void register(CustomPacketPayload.Type<T> type, HandlerThread thread, IPayloadHandler<T> handler) {
ClientNetworkRegistry.register(type, thread, handler);
}
}

View File

@ -6,7 +6,7 @@
@FieldsAreNonnullByDefault
@MethodsReturnNonnullByDefault
@ParametersAreNonnullByDefault
package net.neoforged.neoforge.client.network.handlers;
package net.neoforged.neoforge.client.network.event;
import javax.annotation.ParametersAreNonnullByDefault;
import net.minecraft.FieldsAreNonnullByDefault;

View File

@ -0,0 +1,13 @@
/*
* Copyright (c) NeoForged and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
@FieldsAreNonnullByDefault
@MethodsReturnNonnullByDefault
@ParametersAreNonnullByDefault
package net.neoforged.neoforge.client.network;
import javax.annotation.ParametersAreNonnullByDefault;
import net.minecraft.FieldsAreNonnullByDefault;
import net.minecraft.MethodsReturnNonnullByDefault;

View File

@ -0,0 +1,272 @@
/*
* Copyright (c) NeoForged and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.neoforged.neoforge.client.network.registration;
import com.google.common.collect.ImmutableSet;
import com.mojang.logging.LogUtils;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import net.minecraft.network.ConnectionProtocol;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.PacketFlow;
import net.minecraft.network.protocol.common.ClientCommonPacketListener;
import net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket;
import net.minecraft.network.protocol.common.custom.BrandPayload;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.network.protocol.configuration.ClientConfigurationPacketListener;
import net.minecraft.resources.ResourceLocation;
import net.neoforged.fml.ModLoader;
import net.neoforged.fml.config.ConfigTracker;
import net.neoforged.neoforge.client.network.event.RegisterClientPayloadHandlersEvent;
import net.neoforged.neoforge.client.network.handling.ClientPayloadContext;
import net.neoforged.neoforge.internal.versions.neoforge.NeoForgeVersion;
import net.neoforged.neoforge.network.configuration.CheckExtensibleEnums;
import net.neoforged.neoforge.network.configuration.CheckFeatureFlags;
import net.neoforged.neoforge.network.connection.ConnectionType;
import net.neoforged.neoforge.network.filters.NetworkFilters;
import net.neoforged.neoforge.network.handling.IPayloadHandler;
import net.neoforged.neoforge.network.handling.MainThreadPayloadHandler;
import net.neoforged.neoforge.network.negotiation.NegotiableNetworkComponent;
import net.neoforged.neoforge.network.negotiation.NegotiationResult;
import net.neoforged.neoforge.network.negotiation.NetworkComponentNegotiator;
import net.neoforged.neoforge.network.payload.MinecraftRegisterPayload;
import net.neoforged.neoforge.network.payload.ModdedNetworkPayload;
import net.neoforged.neoforge.network.payload.ModdedNetworkQueryPayload;
import net.neoforged.neoforge.network.registration.ChannelAttributes;
import net.neoforged.neoforge.network.registration.HandlerThread;
import net.neoforged.neoforge.network.registration.NetworkChannel;
import net.neoforged.neoforge.network.registration.NetworkPayloadSetup;
import net.neoforged.neoforge.network.registration.NetworkRegistry;
import net.neoforged.neoforge.network.registration.PayloadRegistration;
import org.jetbrains.annotations.ApiStatus;
import org.slf4j.Logger;
@ApiStatus.Internal
public final class ClientNetworkRegistry extends NetworkRegistry {
private static final Logger LOGGER = LogUtils.getLogger();
private static boolean setupClient = false;
private ClientNetworkRegistry() {}
/**
* Sets up the client network registry by firing {@link RegisterClientPayloadHandlersEvent}, updating the payload
* registrations for clientbound payloads in {@link #PAYLOAD_REGISTRATIONS}.
*/
public static void setup() {
if (!NetworkRegistry.setup) {
throw new IllegalStateException("ClientNetworkRegistry cannot be set up before main NetworkRegistry");
}
if (setupClient) {
throw new IllegalStateException("The client network registry can only be set up once.");
}
ModLoader.postEvent(new RegisterClientPayloadHandlersEvent());
List<ResourceLocation> missingHandlers = PAYLOAD_REGISTRATIONS.values()
.stream()
.map(Map::values)
.flatMap(Collection::stream)
.filter(reg -> {
if (!reg.matchesFlow(PacketFlow.CLIENTBOUND)) {
return false;
}
for (ConnectionProtocol protocol : reg.protocols()) {
if (!CLIENTBOUND_HANDLERS.get(protocol).containsKey(reg.id())) {
return true;
}
}
return false;
})
.map(PayloadRegistration::id)
.distinct()
.toList();
if (!missingHandlers.isEmpty()) {
throw new IllegalStateException("Some clientbound payloads are missing client-side handlers: " + missingHandlers);
}
setupClient = true;
}
@SuppressWarnings("unchecked")
public static <T extends CustomPacketPayload> void register(CustomPacketPayload.Type<T> type, HandlerThread thread, IPayloadHandler<T> handler) {
if (setupClient) {
throw new UnsupportedOperationException("Cannot register client-side handler for payload " + type.id() + " after registration phase.");
}
if (thread == HandlerThread.MAIN) {
handler = new MainThreadPayloadHandler<>(handler);
}
boolean found = false;
for (var entry : PAYLOAD_REGISTRATIONS.entrySet()) {
ConnectionProtocol protocol = entry.getKey();
Map<ResourceLocation, PayloadRegistration<?>> registrations = entry.getValue();
PayloadRegistration<T> registration = (PayloadRegistration<T>) registrations.get(type.id());
if (registration == null) {
continue;
}
if (!registration.matchesFlow(PacketFlow.CLIENTBOUND)) {
throw new IllegalArgumentException("Cannot register client handler for serverbound payload " + type);
}
found = true;
registerHandler(CLIENTBOUND_HANDLERS, protocol, PacketFlow.CLIENTBOUND, type, handler);
}
if (!found) {
throw new IllegalArgumentException("Cannot register client handler for unknown payload type " + type);
}
}
/**
* Handles modded payloads on the client. Invoked after built-in handling.
* <p>
* Called on the network thread.
*
* @param listener The listener which received the packet.
* @param packet The packet that was received.
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public static void handleModdedPayload(ClientCommonPacketListener listener, ClientboundCustomPayloadPacket packet) {
NetworkPayloadSetup payloadSetup = ChannelAttributes.getPayloadSetup(listener.getConnection());
// Check if channels were negotiated.
if (payloadSetup == null) {
LOGGER.warn("Received a modded payload before channel negotiation; disconnecting.");
listener.getConnection().disconnect(Component.translatable("multiplayer.disconnect.incompatible", "NeoForge %s (No Payload Setup)".formatted(NeoForgeVersion.getVersion())));
return;
}
ResourceLocation payloadId = packet.payload().type().id();
ClientPayloadContext context = new ClientPayloadContext(listener, payloadId);
if (CLIENTBOUND_HANDLERS.containsKey(listener.protocol())) {
// Get the configuration channel for the packet.
NetworkChannel channel = payloadSetup.getChannel(listener.protocol(), payloadId);
// Check if the channel should even be processed.
if (channel == null && !hasAdhocChannel(listener.protocol(), packet.payload().type().id(), PacketFlow.CLIENTBOUND)) {
LOGGER.warn("Received a modded payload with an unknown or unaccepted channel; disconnecting.");
listener.getConnection().disconnect(Component.translatable("multiplayer.disconnect.incompatible", "NeoForge %s (No Channel for %s)".formatted(NeoForgeVersion.getVersion(), payloadId.toString())));
return;
}
IPayloadHandler handler = CLIENTBOUND_HANDLERS.get(listener.protocol()).get(payloadId);
if (handler == null) {
LOGGER.error("Received a modded payload with no registration; disconnecting.");
listener.getConnection().disconnect(Component.translatable("multiplayer.disconnect.incompatible", "NeoForge %s (No Handler for %s)".formatted(NeoForgeVersion.getVersion(), payloadId.toString())));
dumpStackToLog(); // This case is only likely when handling packets without serialization, i.e. from a compound listener, so this can help debug why.
return;
}
handler.handle(packet.payload(), context);
} else {
LOGGER.error("Received a modded payload while not in the configuration or play phase. Disconnecting.");
listener.getConnection().disconnect(Component.translatable("multiplayer.disconnect.incompatible", "NeoForge %s (Invalid Protocol %s)".formatted(NeoForgeVersion.getVersion(), listener.protocol().name())));
}
}
/**
* Invoked by the client when a modded server queries it for its available channels. The negotiation happens solely on the server side, and the result is later transmitted to the client.
* <p>
* Invoked on the network thread.
*
* @param listener The listener which received the query.
*/
public static void onNetworkQuery(ClientConfigurationPacketListener listener) {
listener.send(ModdedNetworkQueryPayload.fromRegistry(PAYLOAD_REGISTRATIONS));
}
/**
* Invoked by the client to indicate that it detect a connection to a modded server, by receiving a {@link ModdedNetworkPayload}.
* This will configure the active connection to the server to use the channels that were negotiated.
* <p>
* Once this method completes a {@link NetworkPayloadSetup} will be present on the connection.
* <p>
* Invoked on the network thread.
*
* @param listener The listener which received the payload.
* @param setup The network channels that were negotiated.
*/
public static void initializeNeoForgeConnection(ClientConfigurationPacketListener listener, NetworkPayloadSetup setup) {
ChannelAttributes.setPayloadSetup(listener.getConnection(), setup);
ChannelAttributes.setConnectionType(listener.getConnection(), listener.getConnectionType());
// Only inject filters once the payload setup is stored, as the filters might check for available channels.
NetworkFilters.injectIfNecessary(listener.getConnection());
final ImmutableSet.Builder<ResourceLocation> nowListeningOn = ImmutableSet.builder();
nowListeningOn.addAll(getInitialListeningChannels(listener.flow()));
nowListeningOn.addAll(setup.getChannels(ConnectionProtocol.CONFIGURATION).keySet());
listener.send(new MinecraftRegisterPayload(nowListeningOn.build()));
}
/**
* Invoked by the client when no {@link ModdedNetworkQueryPayload} has been received, but instead a {@link BrandPayload} has been received as the first packet during negotiation in the configuration phase.
* <p>
* If this happens then the client will do a negotiation of its own internal channel configuration, to check if any mods are installed that require a modded connection to the server.
* If those are found then the connection is aborted and the client disconnects from the server.
* <p>
* This method should never be invoked on a connection where the server is {@link ConnectionType#NEOFORGE}.
* <p>
* Invoked on the network thread.
*
* @param listener The listener which received the brand payload.
*/
public static void initializeOtherConnection(ClientConfigurationPacketListener listener) {
// Because we are in vanilla land, no matter what we are not able to support any custom channels.
ChannelAttributes.setPayloadSetup(listener.getConnection(), NetworkPayloadSetup.empty());
ChannelAttributes.setConnectionType(listener.getConnection(), listener.getConnectionType());
for (ConnectionProtocol protocol : PAYLOAD_REGISTRATIONS.keySet()) {
NegotiationResult negotiationResult = NetworkComponentNegotiator.negotiate(
List.of(),
PAYLOAD_REGISTRATIONS.get(protocol).entrySet().stream()
.map(entry -> new NegotiableNetworkComponent(entry.getKey(), entry.getValue().version(), entry.getValue().flow(), entry.getValue().optional()))
.toList());
// Negotiation failed. Disconnect the client.
if (!negotiationResult.success()) {
listener.getConnection().disconnect(Component.translatableWithFallback("neoforge.network.negotiation.failure.vanilla.server.not_supported",
"You are trying to connect to a server that is not running NeoForge, but you have mods that require it. A connection could not be established.", NeoForgeVersion.getVersion()));
return;
}
}
// We are on the client, connected to a vanilla server, make sure we don't have any extended enums that may be sent to the server
if (!CheckExtensibleEnums.handleVanillaServerConnection(listener)) {
return;
}
// We are on the client, connected to a vanilla server, make sure we don't have any modded feature flags
if (!CheckFeatureFlags.handleVanillaServerConnection(listener)) {
return;
}
// We are on the client, connected to a vanilla server, We have to load the default configs.
ConfigTracker.INSTANCE.loadDefaultServerConfigs();
NetworkFilters.injectIfNecessary(listener.getConnection());
ImmutableSet.Builder<ResourceLocation> nowListeningOn = ImmutableSet.builder();
nowListeningOn.addAll(getInitialListeningChannels(listener.flow()));
PAYLOAD_REGISTRATIONS.get(ConnectionProtocol.CONFIGURATION).entrySet().stream()
.filter(registration -> registration.getValue().matchesFlow(listener.flow()))
.filter(registration -> registration.getValue().optional())
.forEach(registration -> nowListeningOn.add(registration.getKey()));
listener.send(new MinecraftRegisterPayload(nowListeningOn.build()));
}
/**
* Used in place of {@link Thread#dumpStack()} as that logs to {@link System#err}.
*/
private static void dumpStackToLog() {
LOGGER.error("", new Exception("Stack Trace"));
}
}

View File

@ -0,0 +1,13 @@
/*
* Copyright (c) NeoForged and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
@FieldsAreNonnullByDefault
@MethodsReturnNonnullByDefault
@ParametersAreNonnullByDefault
package net.neoforged.neoforge.client.network.registration;
import javax.annotation.ParametersAreNonnullByDefault;
import net.minecraft.FieldsAreNonnullByDefault;
import net.minecraft.MethodsReturnNonnullByDefault;

View File

@ -7,16 +7,11 @@ package net.neoforged.neoforge.internal;
import net.minecraft.core.HolderLookup;
import net.minecraft.core.Registry;
import net.minecraft.network.protocol.common.ClientCommonPacketListener;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.MinecraftServer;
import net.minecraft.util.thread.BlockableEventLoop;
import net.minecraft.world.item.TooltipFlag;
import net.neoforged.fml.loading.FMLLoader;
import net.neoforged.neoforge.network.handling.IPayloadContext;
import net.neoforged.neoforge.network.payload.ClientDispatchPayload;
import net.neoforged.neoforge.server.ServerLifecycleHooks;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
@ -44,18 +39,6 @@ public class NeoForgeProxy {
};
}
public void sendToServer(CustomPacketPayload payload, CustomPacketPayload... payloads) {
throw new UnsupportedOperationException("Cannot send serverbound payloads on the server");
}
public IPayloadContext newClientPayloadContext(ClientCommonPacketListener listener, ResourceLocation payloadId) {
throw new UnsupportedOperationException("Cannot create ClientPayloadContext on the server");
}
public void handleClientPayload(ClientDispatchPayload payload, IPayloadContext context) {
throw new UnsupportedOperationException("Cannot handle client payload on the server");
}
public BlockableEventLoop<Runnable> getClientExecutor() {
throw new UnsupportedOperationException("Cannot access client on the server");
}

View File

@ -7,19 +7,15 @@ package net.neoforged.neoforge.network;
import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.fml.common.EventBusSubscriber;
import net.neoforged.neoforge.internal.NeoForgeProxy;
import net.neoforged.neoforge.internal.versions.neoforge.NeoForgeVersion;
import net.neoforged.neoforge.network.configuration.CheckExtensibleEnums;
import net.neoforged.neoforge.network.configuration.CheckFeatureFlags;
import net.neoforged.neoforge.network.event.RegisterPayloadHandlersEvent;
import net.neoforged.neoforge.network.handlers.ServerPayloadHandler;
import net.neoforged.neoforge.network.handling.DirectionalPayloadHandler;
import net.neoforged.neoforge.network.handling.IPayloadHandler;
import net.neoforged.neoforge.network.payload.AdvancedAddEntityPayload;
import net.neoforged.neoforge.network.payload.AdvancedContainerSetDataPayload;
import net.neoforged.neoforge.network.payload.AdvancedOpenScreenPayload;
import net.neoforged.neoforge.network.payload.AuxiliaryLightDataPayload;
import net.neoforged.neoforge.network.payload.ClientDispatchPayload;
import net.neoforged.neoforge.network.payload.ClientboundCustomSetTimePayload;
import net.neoforged.neoforge.network.payload.ConfigFilePayload;
import net.neoforged.neoforge.network.payload.ExtensibleEnumAcknowledgePayload;
@ -39,10 +35,8 @@ import org.jetbrains.annotations.ApiStatus;
@ApiStatus.Internal
@EventBusSubscriber(modid = NeoForgeVersion.MOD_ID)
public class NetworkInitialization {
private static <T extends ClientDispatchPayload> IPayloadHandler<T> clientHandler() {
return NeoForgeProxy.INSTANCE::handleClientPayload;
}
final class NetworkInitialization {
private NetworkInitialization() {}
@SubscribeEvent
private static void register(final RegisterPayloadHandlersEvent event) {
@ -51,24 +45,20 @@ public class NetworkInitialization {
registrar
.commonToClient(
ConfigFilePayload.TYPE,
ConfigFilePayload.STREAM_CODEC,
clientHandler())
ConfigFilePayload.STREAM_CODEC)
.configurationToClient(
FrozenRegistrySyncStartPayload.TYPE,
FrozenRegistrySyncStartPayload.STREAM_CODEC,
clientHandler())
FrozenRegistrySyncStartPayload.STREAM_CODEC)
.configurationToClient(
FrozenRegistryPayload.TYPE,
FrozenRegistryPayload.STREAM_CODEC,
clientHandler())
FrozenRegistryPayload.STREAM_CODEC)
.configurationBidirectional(
FrozenRegistrySyncCompletedPayload.TYPE,
FrozenRegistrySyncCompletedPayload.STREAM_CODEC,
new DirectionalPayloadHandler<>(clientHandler(), ServerPayloadHandler::handle))
ServerPayloadHandler::handle)
.configurationToClient(
KnownRegistryDataMapsPayload.TYPE,
KnownRegistryDataMapsPayload.STREAM_CODEC,
clientHandler())
KnownRegistryDataMapsPayload.STREAM_CODEC)
.configurationToClient(
ExtensibleEnumDataPayload.TYPE,
ExtensibleEnumDataPayload.STREAM_CODEC,
@ -91,30 +81,23 @@ public class NetworkInitialization {
CheckFeatureFlags::handleServerboundPayload)
.playToClient(
AdvancedAddEntityPayload.TYPE,
AdvancedAddEntityPayload.STREAM_CODEC,
clientHandler())
AdvancedAddEntityPayload.STREAM_CODEC)
.playToClient(
AdvancedOpenScreenPayload.TYPE,
AdvancedOpenScreenPayload.STREAM_CODEC,
clientHandler())
AdvancedOpenScreenPayload.STREAM_CODEC)
.playToClient(
AuxiliaryLightDataPayload.TYPE,
AuxiliaryLightDataPayload.STREAM_CODEC,
clientHandler())
AuxiliaryLightDataPayload.STREAM_CODEC)
.playToClient(
RegistryDataMapSyncPayload.TYPE,
RegistryDataMapSyncPayload.STREAM_CODEC,
clientHandler())
RegistryDataMapSyncPayload.STREAM_CODEC)
.playToClient(AdvancedContainerSetDataPayload.TYPE,
AdvancedContainerSetDataPayload.STREAM_CODEC,
clientHandler())
AdvancedContainerSetDataPayload.STREAM_CODEC)
.playToClient(
ClientboundCustomSetTimePayload.TYPE,
ClientboundCustomSetTimePayload.STREAM_CODEC,
clientHandler())
ClientboundCustomSetTimePayload.STREAM_CODEC)
.playToClient(
RecipeContentPayload.TYPE,
RecipeContentPayload.STREAM_CODEC,
clientHandler());
RecipeContentPayload.STREAM_CODEC);
}
}

View File

@ -19,23 +19,17 @@ import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.ChunkPos;
import net.neoforged.neoforge.internal.NeoForgeProxy;
import net.neoforged.neoforge.server.ServerLifecycleHooks;
import org.jetbrains.annotations.Nullable;
/**
* Means to distribute packets in various ways
* Means to distribute packets in various ways.
* <p>
* Serverbound payloads can be sent via {@code ClientPacketDistributor#sendToServer()}.
*/
public final class PacketDistributor {
private PacketDistributor() {}
/**
* Send the given payload(s) to the server
*/
public static void sendToServer(CustomPacketPayload payload, CustomPacketPayload... payloads) {
NeoForgeProxy.INSTANCE.sendToServer(payload, payloads);
}
/**
* Send the given payload(s) to the given player
*/
@ -110,10 +104,12 @@ public final class PacketDistributor {
}
private static Packet<?> makeClientboundPacket(CustomPacketPayload payload, CustomPacketPayload... payloads) {
Objects.requireNonNull(payload, "Cannot send null payload");
if (payloads.length > 0) {
final List<Packet<? super ClientGamePacketListener>> packets = new ArrayList<>();
packets.add(new ClientboundCustomPayloadPacket(payload));
for (CustomPacketPayload otherPayload : payloads) {
Objects.requireNonNull(otherPayload, "Cannot send null payload");
packets.add(new ClientboundCustomPayloadPacket(otherPayload));
}
return new ClientboundBundlePacket(packets);

View File

@ -61,7 +61,7 @@ public class GenericPacketSplitter extends MessageToMessageEncoder<Packet<?>> im
private static void register(final RegisterPayloadHandlersEvent event) {
event.registrar("1")
.optional()
.commonBidirectional(SplitPacketPayload.TYPE, SplitPacketPayload.STREAM_CODEC, GenericPacketSplitter::handle);
.commonBidirectional(SplitPacketPayload.TYPE, SplitPacketPayload.STREAM_CODEC, GenericPacketSplitter::handle, GenericPacketSplitter::handle);
}
private static void handle(SplitPacketPayload payload, IPayloadContext context) {

View File

@ -1,22 +0,0 @@
/*
* Copyright (c) NeoForged and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.neoforged.neoforge.network.handling;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
/**
* Helper class that merges two unidirectional handlers into a single bidirectional handler.
*/
public record DirectionalPayloadHandler<T extends CustomPacketPayload>(IPayloadHandler<T> clientSide, IPayloadHandler<T> serverSide) implements IPayloadHandler<T> {
@Override
public void handle(T payload, IPayloadContext context) {
if (context.flow().isClientbound()) {
clientSide.handle(payload, context);
} else if (context.flow().isServerbound()) {
serverSide.handle(payload, context);
}
}
}

View File

@ -8,6 +8,7 @@ package net.neoforged.neoforge.network.payload;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.Entity;
import net.neoforged.neoforge.common.util.FriendlyByteBufUtil;
@ -23,7 +24,7 @@ import org.jetbrains.annotations.ApiStatus;
* @param customPayload The custom data of the entity to add.
*/
@ApiStatus.Internal
public record AdvancedAddEntityPayload(int entityId, byte[] customPayload) implements ClientDispatchPayload {
public record AdvancedAddEntityPayload(int entityId, byte[] customPayload) implements CustomPacketPayload {
public static final Type<AdvancedAddEntityPayload> TYPE = new Type<>(ResourceLocation.fromNamespaceAndPath(NeoForgeVersion.MOD_ID, "advanced_add_entity"));
public static final StreamCodec<FriendlyByteBuf, AdvancedAddEntityPayload> STREAM_CODEC = StreamCodec.composite(
ByteBufCodecs.VAR_INT,

View File

@ -8,6 +8,7 @@ package net.neoforged.neoforge.network.payload;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.network.protocol.game.ClientboundContainerSetDataPacket;
import net.minecraft.resources.ResourceLocation;
import net.neoforged.neoforge.internal.versions.neoforge.NeoForgeVersion;
@ -21,7 +22,7 @@ import org.jetbrains.annotations.ApiStatus;
* @param value The value of the dataslot.
*/
@ApiStatus.Internal
public record AdvancedContainerSetDataPayload(byte containerId, short dataId, int value) implements ClientDispatchPayload {
public record AdvancedContainerSetDataPayload(byte containerId, short dataId, int value) implements CustomPacketPayload {
public static final Type<AdvancedContainerSetDataPayload> TYPE = new Type<>(ResourceLocation.fromNamespaceAndPath(NeoForgeVersion.MOD_ID, "advanced_container_set_data"));
public static final StreamCodec<RegistryFriendlyByteBuf, AdvancedContainerSetDataPayload> STREAM_CODEC = StreamCodec.composite(

View File

@ -11,6 +11,7 @@ import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.ComponentSerialization;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.inventory.MenuType;
import net.neoforged.neoforge.internal.versions.neoforge.NeoForgeVersion;
@ -26,7 +27,7 @@ import org.jetbrains.annotations.ApiStatus;
* @param additionalData The additional data to pass to the screen.
*/
@ApiStatus.Internal
public record AdvancedOpenScreenPayload(int windowId, MenuType<?> menuType, Component name, byte[] additionalData) implements ClientDispatchPayload {
public record AdvancedOpenScreenPayload(int windowId, MenuType<?> menuType, Component name, byte[] additionalData) implements CustomPacketPayload {
public static final Type<AdvancedOpenScreenPayload> TYPE = new Type<>(ResourceLocation.fromNamespaceAndPath(NeoForgeVersion.MOD_ID, "advanced_open_screen"));
public static final StreamCodec<RegistryFriendlyByteBuf, AdvancedOpenScreenPayload> STREAM_CODEC = StreamCodec.composite(

View File

@ -11,6 +11,7 @@ import net.minecraft.core.BlockPos;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.ChunkPos;
import net.neoforged.neoforge.internal.versions.neoforge.NeoForgeVersion;
@ -18,7 +19,7 @@ import net.neoforged.neoforge.network.codec.NeoForgeStreamCodecs;
import org.jetbrains.annotations.ApiStatus;
@ApiStatus.Internal
public record AuxiliaryLightDataPayload(ChunkPos pos, Map<BlockPos, Byte> entries) implements ClientDispatchPayload {
public record AuxiliaryLightDataPayload(ChunkPos pos, Map<BlockPos, Byte> entries) implements CustomPacketPayload {
public static final Type<AuxiliaryLightDataPayload> TYPE = new Type<>(ResourceLocation.fromNamespaceAndPath(NeoForgeVersion.MOD_ID, "auxiliary_light_data"));
public static final StreamCodec<RegistryFriendlyByteBuf, AuxiliaryLightDataPayload> STREAM_CODEC = StreamCodec.composite(
NeoForgeStreamCodecs.CHUNK_POS,

View File

@ -1,26 +0,0 @@
/*
* Copyright (c) NeoForged and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.neoforged.neoforge.network.payload;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
/**
* Internal marker classes for packets for which the handler dispatch happens in {@code ClientPayloadHandler}.
* This is meant to be a temporary workaround until we rework the networking API to allow for separate handler registration.
*/
public sealed interface ClientDispatchPayload extends CustomPacketPayload
permits AdvancedAddEntityPayload,
AdvancedContainerSetDataPayload,
AdvancedOpenScreenPayload,
AuxiliaryLightDataPayload,
ClientboundCustomSetTimePayload,
ConfigFilePayload,
FrozenRegistryPayload,
FrozenRegistrySyncCompletedPayload,
FrozenRegistrySyncStartPayload,
KnownRegistryDataMapsPayload,
RecipeContentPayload,
RegistryDataMapSyncPayload {}

View File

@ -8,12 +8,13 @@ package net.neoforged.neoforge.network.payload;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.resources.ResourceLocation;
import net.neoforged.neoforge.internal.versions.neoforge.NeoForgeVersion;
import org.jetbrains.annotations.ApiStatus;
@ApiStatus.Internal
public record ClientboundCustomSetTimePayload(long gameTime, long dayTime, boolean gameRule, float dayTimeFraction, float dayTimePerTick) implements ClientDispatchPayload {
public record ClientboundCustomSetTimePayload(long gameTime, long dayTime, boolean gameRule, float dayTimeFraction, float dayTimePerTick) implements CustomPacketPayload {
public static final Type<ClientboundCustomSetTimePayload> TYPE = new Type<>(ResourceLocation.fromNamespaceAndPath(NeoForgeVersion.MOD_ID, "custom_time_packet"));

View File

@ -8,6 +8,7 @@ package net.neoforged.neoforge.network.payload;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.resources.ResourceLocation;
import net.neoforged.neoforge.internal.versions.neoforge.NeoForgeVersion;
import net.neoforged.neoforge.network.codec.NeoForgeStreamCodecs;
@ -23,7 +24,7 @@ import org.jetbrains.annotations.ApiStatus;
* @param contents The contents of the config file.
*/
@ApiStatus.Internal
public record ConfigFilePayload(String fileName, byte[] contents) implements ClientDispatchPayload {
public record ConfigFilePayload(String fileName, byte[] contents) implements CustomPacketPayload {
public static final Type<ConfigFilePayload> TYPE = new Type<>(ResourceLocation.fromNamespaceAndPath(NeoForgeVersion.MOD_ID, "config_file"));
public static final StreamCodec<FriendlyByteBuf, ConfigFilePayload> STREAM_CODEC = StreamCodec.composite(
ByteBufCodecs.STRING_UTF8,

View File

@ -7,6 +7,7 @@ package net.neoforged.neoforge.network.payload;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.resources.ResourceLocation;
import net.neoforged.neoforge.internal.versions.neoforge.NeoForgeVersion;
import net.neoforged.neoforge.registries.RegistrySnapshot;
@ -19,7 +20,7 @@ import org.jetbrains.annotations.ApiStatus;
* @param snapshot The snapshot of the registry
*/
@ApiStatus.Internal
public record FrozenRegistryPayload(ResourceLocation registryName, RegistrySnapshot snapshot) implements ClientDispatchPayload {
public record FrozenRegistryPayload(ResourceLocation registryName, RegistrySnapshot snapshot) implements CustomPacketPayload {
public static final Type<FrozenRegistryPayload> TYPE = new Type<>(ResourceLocation.fromNamespaceAndPath(NeoForgeVersion.MOD_ID, "frozen_registry"));
public static final StreamCodec<FriendlyByteBuf, FrozenRegistryPayload> STREAM_CODEC = StreamCodec.composite(
ResourceLocation.STREAM_CODEC,

View File

@ -7,6 +7,7 @@ package net.neoforged.neoforge.network.payload;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.resources.ResourceLocation;
import net.neoforged.neoforge.internal.versions.neoforge.NeoForgeVersion;
import org.jetbrains.annotations.ApiStatus;
@ -15,7 +16,7 @@ import org.jetbrains.annotations.ApiStatus;
* This payload is sent to the client when the server has finished sending all the frozen registries.
*/
@ApiStatus.Internal
public final class FrozenRegistrySyncCompletedPayload implements ClientDispatchPayload {
public final class FrozenRegistrySyncCompletedPayload implements CustomPacketPayload {
public static final Type<FrozenRegistrySyncCompletedPayload> TYPE = new Type<>(ResourceLocation.fromNamespaceAndPath(NeoForgeVersion.MOD_ID, "frozen_registry_sync_completed"));
public static final FrozenRegistrySyncCompletedPayload INSTANCE = new FrozenRegistrySyncCompletedPayload();
public static final StreamCodec<FriendlyByteBuf, FrozenRegistrySyncCompletedPayload> STREAM_CODEC = StreamCodec.unit(INSTANCE);

View File

@ -9,6 +9,7 @@ import java.util.List;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.resources.ResourceLocation;
import net.neoforged.neoforge.internal.versions.neoforge.NeoForgeVersion;
import org.jetbrains.annotations.ApiStatus;
@ -22,7 +23,7 @@ import org.jetbrains.annotations.ApiStatus;
* @param toAccess The registries to access.
*/
@ApiStatus.Internal
public record FrozenRegistrySyncStartPayload(List<ResourceLocation> toAccess) implements ClientDispatchPayload {
public record FrozenRegistrySyncStartPayload(List<ResourceLocation> toAccess) implements CustomPacketPayload {
public static final Type<FrozenRegistrySyncStartPayload> TYPE = new Type<>(ResourceLocation.fromNamespaceAndPath(NeoForgeVersion.MOD_ID, "frozen_registry_sync_start"));
public static final StreamCodec<FriendlyByteBuf, FrozenRegistrySyncStartPayload> STREAM_CODEC = StreamCodec.composite(
ResourceLocation.STREAM_CODEC.apply(ByteBufCodecs.list()),

View File

@ -12,13 +12,14 @@ import net.minecraft.core.Registry;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.neoforged.neoforge.network.codec.NeoForgeStreamCodecs;
import org.jetbrains.annotations.ApiStatus;
@ApiStatus.Internal
public record KnownRegistryDataMapsPayload(Map<ResourceKey<? extends Registry<?>>, List<KnownDataMap>> dataMaps) implements ClientDispatchPayload {
public record KnownRegistryDataMapsPayload(Map<ResourceKey<? extends Registry<?>>, List<KnownDataMap>> dataMaps) implements CustomPacketPayload {
public static final Type<KnownRegistryDataMapsPayload> TYPE = new Type<>(ResourceLocation.fromNamespaceAndPath("neoforge", "known_registry_data_maps"));
public static final StreamCodec<FriendlyByteBuf, KnownRegistryDataMapsPayload> STREAM_CODEC = StreamCodec.composite(
ByteBufCodecs.map(

View File

@ -13,6 +13,7 @@ import net.minecraft.core.registries.Registries;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.crafting.RecipeHolder;
import net.minecraft.world.item.crafting.RecipeMap;
@ -26,7 +27,7 @@ import org.jetbrains.annotations.ApiStatus;
@ApiStatus.Internal
public record RecipeContentPayload(
Set<RecipeType<?>> recipeTypes,
List<RecipeHolder<?>> recipes) implements ClientDispatchPayload {
List<RecipeHolder<?>> recipes) implements CustomPacketPayload {
public static final Type<RecipeContentPayload> TYPE = new Type<>(ResourceLocation.fromNamespaceAndPath(NeoForgeVersion.MOD_ID, "recipe_content"));
public static final StreamCodec<RegistryFriendlyByteBuf, RecipeContentPayload> STREAM_CODEC = StreamCodec.composite(

View File

@ -28,7 +28,7 @@ import org.jetbrains.annotations.ApiStatus;
@ApiStatus.Internal
@SuppressWarnings({ "unchecked", "rawtypes" })
public record RegistryDataMapSyncPayload<T>(ResourceKey<? extends Registry<T>> registryKey,
Map<ResourceLocation, Map<ResourceKey<T>, ?>> dataMaps) implements ClientDispatchPayload {
Map<ResourceLocation, Map<ResourceKey<T>, ?>> dataMaps) implements CustomPacketPayload {
public static final CustomPacketPayload.Type<RegistryDataMapSyncPayload<?>> TYPE = new Type<>(ResourceLocation.fromNamespaceAndPath("neoforge", "registry_data_map_sync"));
public static final StreamCodec<RegistryFriendlyByteBuf, RegistryDataMapSyncPayload<?>> STREAM_CODEC = StreamCodec.ofMember(
RegistryDataMapSyncPayload::write, RegistryDataMapSyncPayload::decode);

View File

@ -36,20 +36,14 @@ import net.minecraft.network.protocol.common.ClientCommonPacketListener;
import net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket;
import net.minecraft.network.protocol.common.ServerCommonPacketListener;
import net.minecraft.network.protocol.common.ServerboundCustomPayloadPacket;
import net.minecraft.network.protocol.common.custom.BrandPayload;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.network.protocol.common.custom.DiscardedPayload;
import net.minecraft.network.protocol.configuration.ClientConfigurationPacketListener;
import net.minecraft.network.protocol.configuration.ServerConfigurationPacketListener;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.network.ServerConfigurationPacketListenerImpl;
import net.neoforged.fml.ModLoader;
import net.neoforged.fml.config.ConfigTracker;
import net.neoforged.neoforge.common.extensions.ICommonPacketListener;
import net.neoforged.neoforge.internal.NeoForgeProxy;
import net.neoforged.neoforge.internal.versions.neoforge.NeoForgeVersion;
import net.neoforged.neoforge.network.configuration.CheckExtensibleEnums;
import net.neoforged.neoforge.network.configuration.CheckFeatureFlags;
import net.neoforged.neoforge.network.configuration.CommonRegisterTask;
import net.neoforged.neoforge.network.configuration.CommonVersionTask;
import net.neoforged.neoforge.network.connection.ConnectionType;
@ -98,7 +92,7 @@ public class NetworkRegistry {
* Map of NeoForge payloads that may be sent before channel negotiation.
* TODO: Separate by protocol + flow.
*/
private static final Map<ResourceLocation, StreamCodec<FriendlyByteBuf, ? extends CustomPacketPayload>> BUILTIN_PAYLOADS = ImmutableMap.of(
protected static final Map<ResourceLocation, StreamCodec<FriendlyByteBuf, ? extends CustomPacketPayload>> BUILTIN_PAYLOADS = ImmutableMap.of(
MinecraftRegisterPayload.ID, MinecraftRegisterPayload.STREAM_CODEC,
MinecraftUnregisterPayload.ID, MinecraftUnregisterPayload.STREAM_CODEC,
ModdedNetworkQueryPayload.ID, ModdedNetworkQueryPayload.STREAM_CODEC,
@ -111,13 +105,19 @@ public class NetworkRegistry {
* Registry of all custom payload handlers. The initial state of this map should reflect the protocols which support custom payloads.
* TODO: Change key type to a combination of protocol + flow.
*/
private static final Map<ConnectionProtocol, Map<ResourceLocation, PayloadRegistration<?>>> PAYLOAD_REGISTRATIONS = ImmutableMap.of(
protected static final Map<ConnectionProtocol, Map<ResourceLocation, PayloadRegistration<?>>> PAYLOAD_REGISTRATIONS = ImmutableMap.of(
ConnectionProtocol.CONFIGURATION, new HashMap<>(),
ConnectionProtocol.PLAY, new HashMap<>());
protected static final Map<ConnectionProtocol, Map<ResourceLocation, IPayloadHandler<?>>> SERVERBOUND_HANDLERS = ImmutableMap.of(
ConnectionProtocol.CONFIGURATION, new HashMap<>(),
ConnectionProtocol.PLAY, new HashMap<>());
protected static final Map<ConnectionProtocol, Map<ResourceLocation, IPayloadHandler<?>>> CLIENTBOUND_HANDLERS = ImmutableMap.of(
ConnectionProtocol.CONFIGURATION, new HashMap<>(),
ConnectionProtocol.PLAY, new HashMap<>());
private static boolean setup = false;
protected static boolean setup = false;
private NetworkRegistry() {}
protected NetworkRegistry() {}
/**
* Sets up the network registry by firing {@link RegisterPayloadHandlersEvent}, storing the resulting payload registrations in {@link #PAYLOAD_REGISTRATIONS}.
@ -135,19 +135,27 @@ public class NetworkRegistry {
/**
* Registers a new payload.
*
* @param <T> The class of the payload.
* @param <B> The class of the ByteBuf. Only {@link ConnectionProtocol#PLAY play} payloads may use {@link RegistryFriendlyByteBuf}.
* @param type The type of the payload.
* @param codec The codec for the payload.
* @param handler The handler for the payload. This handler should expect to receive the payload on all declared protocols and flows. It will be executed on the network thread.
* @param protocols The protocols this payload supports being sent over. Only {@link ConnectionProtocol#CONFIGURATION configuration} and {@link ConnectionProtocol#PLAY play} are supported.
* @param flow The flow of this payload. Specify {@link Optional#empty()} to support sending in both directions.
* @param version The version of the payload. Increase the payload version if the codec logic or handler logic changes. Neo-Neo connections with mismatched versions are denied.
* @param optional If the payload is optional. Any connection with missing non-optional payloads is denied.
* @param <T> The class of the payload.
* @param <B> The class of the ByteBuf. Only {@link ConnectionProtocol#PLAY play} payloads may use {@link RegistryFriendlyByteBuf}.
* @param type The type of the payload.
* @param codec The codec for the payload.
* @param serverHandler The server-side handler for the payload. This handler will be executed on the network thread.
* @param clientHandler The client-side handler for the payload. This handler will be executed on the network thread.
* @param protocols The protocols this payload supports being sent over. Only {@link ConnectionProtocol#CONFIGURATION configuration} and {@link ConnectionProtocol#PLAY play} are supported.
* @param flow The flow of this payload. Specify {@link Optional#empty()} to support sending in both directions.
* @param version The version of the payload. Increase the payload version if the codec logic or handler logic changes. Neo-Neo connections with mismatched versions are denied.
* @param optional If the payload is optional. Any connection with missing non-optional payloads is denied.
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public static <T extends CustomPacketPayload, B extends FriendlyByteBuf> void register(CustomPacketPayload.Type<T> type, StreamCodec<? super B, T> codec, IPayloadHandler<T> handler,
List<ConnectionProtocol> protocols, Optional<PacketFlow> flow, String version, boolean optional) {
public static <T extends CustomPacketPayload, B extends FriendlyByteBuf> void register(
CustomPacketPayload.Type<T> type,
StreamCodec<? super B, T> codec,
@Nullable IPayloadHandler<T> serverHandler,
@Nullable IPayloadHandler<T> clientHandler,
List<ConnectionProtocol> protocols,
Optional<PacketFlow> flow,
String version,
boolean optional) {
if (setup) {
throw new UnsupportedOperationException("Cannot register payload " + type.id() + " after registration phase.");
}
@ -164,7 +172,11 @@ public class NetworkRegistry {
throw new UnsupportedOperationException("Cannot register payload " + type.id() + " using the domain \"minecraft\".");
}
PayloadRegistration<T> reg = new PayloadRegistration(type, codec, handler, protocols, flow, version.strip(), optional);
PayloadRegistration<T> reg = new PayloadRegistration(type, codec, protocols, flow, version.strip(), optional);
if (reg.matchesFlow(PacketFlow.SERVERBOUND) && serverHandler == null) {
throw new IllegalArgumentException("Cannot register serverbound payload " + type.id() + " without a server-side handler");
}
for (ConnectionProtocol protocol : protocols) {
Map<ResourceLocation, PayloadRegistration<?>> byProtocol = PAYLOAD_REGISTRATIONS.get(protocol);
@ -178,9 +190,34 @@ public class NetworkRegistry {
}
byProtocol.put(type.id(), reg);
if (serverHandler != null && reg.matchesFlow(PacketFlow.SERVERBOUND)) {
registerHandler(SERVERBOUND_HANDLERS, protocol, PacketFlow.SERVERBOUND, type, serverHandler);
}
if (clientHandler != null && reg.matchesFlow(PacketFlow.CLIENTBOUND)) {
registerHandler(CLIENTBOUND_HANDLERS, protocol, PacketFlow.CLIENTBOUND, type, clientHandler);
}
}
}
protected static <T extends CustomPacketPayload> void registerHandler(
Map<ConnectionProtocol, Map<ResourceLocation, IPayloadHandler<?>>> handlers,
ConnectionProtocol protocol,
PacketFlow flow,
CustomPacketPayload.Type<T> type,
IPayloadHandler<T> handler) {
Map<ResourceLocation, IPayloadHandler<?>> byProtocol = handlers.get(protocol);
if (byProtocol == null) {
throw new UnsupportedOperationException("Cannot register handler for payload " + type.id() + " for unsupported protocol: " + protocol.name());
}
if (byProtocol.containsKey(type.id())) {
throw new UnsupportedOperationException("Duplicate " + flow.id() + " handler registration for payload " + type.id());
}
byProtocol.put(type.id(), handler);
}
/**
* Attempts to retrieve the {@link StreamCodec} for a non-vanilla payload.
* <p>
@ -261,7 +298,7 @@ public class NetworkRegistry {
ServerPayloadContext context = new ServerPayloadContext(listener, packet.payload().type().id());
if (PAYLOAD_REGISTRATIONS.containsKey(listener.protocol())) {
if (SERVERBOUND_HANDLERS.containsKey(listener.protocol())) {
// Get the configuration channel for the packet.
NetworkChannel channel = payloadSetup.getChannel(listener.protocol(), context.payloadId());
@ -272,68 +309,21 @@ public class NetworkRegistry {
return;
}
PayloadRegistration registration = PAYLOAD_REGISTRATIONS.get(listener.protocol()).get(context.payloadId());
if (registration == null) {
IPayloadHandler handler = SERVERBOUND_HANDLERS.get(listener.protocol()).get(context.payloadId());
if (handler == null) {
LOGGER.error("Received a modded payload {} with no registration; disconnecting.", context.payloadId());
listener.disconnect(Component.translatable("multiplayer.disconnect.incompatible", "NeoForge %s (No Handler for %s)".formatted(NeoForgeVersion.getVersion(), context.payloadId().toString())));
dumpStackToLog(); // This case is only likely when handling packets without serialization, i.e. from a compound listener, so this can help debug why.
return;
}
registration.handler().handle(packet.payload(), context);
handler.handle(packet.payload(), context);
} else {
LOGGER.error("Received a modded payload {} while not in the configuration or play phase; disconnecting.", context.payloadId());
listener.disconnect(Component.translatable("multiplayer.disconnect.incompatible", "NeoForge %s (Invalid Protocol %s)".formatted(NeoForgeVersion.getVersion(), listener.protocol().name())));
}
}
/**
* Handles modded payloads on the client. Invoked after built-in handling.
* <p>
* Called on the network thread.
*
* @param listener The listener which received the packet.
* @param packet The packet that was received.
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public static void handleModdedPayload(ClientCommonPacketListener listener, ClientboundCustomPayloadPacket packet) {
NetworkPayloadSetup payloadSetup = ChannelAttributes.getPayloadSetup(listener.getConnection());
// Check if channels were negotiated.
if (payloadSetup == null) {
LOGGER.warn("Received a modded payload before channel negotiation; disconnecting.");
listener.getConnection().disconnect(Component.translatable("multiplayer.disconnect.incompatible", "NeoForge %s (No Payload Setup)".formatted(NeoForgeVersion.getVersion())));
return;
}
var payloadId = packet.payload().type().id();
IPayloadContext context = NeoForgeProxy.INSTANCE.newClientPayloadContext(listener, payloadId);
if (PAYLOAD_REGISTRATIONS.containsKey(listener.protocol())) {
// Get the configuration channel for the packet.
NetworkChannel channel = payloadSetup.getChannel(listener.protocol(), payloadId);
// Check if the channel should even be processed.
if (channel == null && !hasAdhocChannel(listener.protocol(), packet.payload().type().id(), PacketFlow.CLIENTBOUND)) {
LOGGER.warn("Received a modded payload with an unknown or unaccepted channel; disconnecting.");
listener.getConnection().disconnect(Component.translatable("multiplayer.disconnect.incompatible", "NeoForge %s (No Channel for %s)".formatted(NeoForgeVersion.getVersion(), payloadId.toString())));
return;
}
PayloadRegistration registration = PAYLOAD_REGISTRATIONS.get(listener.protocol()).get(payloadId);
if (registration == null) {
LOGGER.error("Received a modded payload with no registration; disconnecting.");
listener.getConnection().disconnect(Component.translatable("multiplayer.disconnect.incompatible", "NeoForge %s (No Handler for %s)".formatted(NeoForgeVersion.getVersion(), payloadId.toString())));
dumpStackToLog(); // This case is only likely when handling packets without serialization, i.e. from a compound listener, so this can help debug why.
return;
}
registration.handler().handle(packet.payload(), context);
} else {
LOGGER.error("Received a modded payload while not in the configuration or play phase. Disconnecting.");
listener.getConnection().disconnect(Component.translatable("multiplayer.disconnect.incompatible", "NeoForge %s (Invalid Protocol %s)".formatted(NeoForgeVersion.getVersion(), listener.protocol().name())));
}
}
/**
* Invoked by the server when it completes the negotiation with the client during the configuration phase.
* <p>
@ -346,9 +336,8 @@ public class NetworkRegistry {
* <p>
* Invoked on the network thread.
*
* @param listener The listener which completed the negotiation.
* @param configuration The configuration channels that the client has available.
* @param play The play channels that the client has available.
* @param listener The listener which completed the negotiation.
* @param clientChannels The network channels that the client has available.
*/
public static void initializeNeoForgeConnection(ServerConfigurationPacketListener listener, Map<ConnectionProtocol, Set<ModdedNetworkQueryComponent>> clientChannels) {
ChannelAttributes.setPayloadSetup(listener.getConnection(), NetworkPayloadSetup.empty());
@ -479,103 +468,11 @@ public class NetworkRegistry {
* @param flow The flow of the packet.
* @return True if the packet is ad-hoc readable, false otherwise.
*/
private static boolean hasAdhocChannel(ConnectionProtocol protocol, ResourceLocation id, PacketFlow flow) {
protected static boolean hasAdhocChannel(ConnectionProtocol protocol, ResourceLocation id, PacketFlow flow) {
PayloadRegistration<?> reg = PAYLOAD_REGISTRATIONS.getOrDefault(protocol, Collections.emptyMap()).get(id);
return reg != null && reg.optional() && reg.matchesFlow(flow);
}
/**
* Invoked by the client when a modded server queries it for its available channels. The negotiation happens solely on the server side, and the result is later transmitted to the client.
* <p>
* Invoked on the network thread.
*
* @param listener The listener which received the query.
*/
public static void onNetworkQuery(ClientConfigurationPacketListener listener) {
listener.send(ModdedNetworkQueryPayload.fromRegistry(PAYLOAD_REGISTRATIONS));
}
/**
* Invoked by the client to indicate that it detect a connection to a modded server, by receiving a {@link ModdedNetworkPayload}.
* This will configure the active connection to the server to use the channels that were negotiated.
* <p>
* Once this method completes a {@link NetworkPayloadSetup} will be present on the connection.
* <p>
* Invoked on the network thread.
*
* @param listener The listener which received the payload.
* @param configuration The configuration channels that were negotiated.
* @param play The play channels that were negotiated.
*/
public static void initializeNeoForgeConnection(ClientConfigurationPacketListener listener, NetworkPayloadSetup setup) {
ChannelAttributes.setPayloadSetup(listener.getConnection(), setup);
ChannelAttributes.setConnectionType(listener.getConnection(), listener.getConnectionType());
// Only inject filters once the payload setup is stored, as the filters might check for available channels.
NetworkFilters.injectIfNecessary(listener.getConnection());
final ImmutableSet.Builder<ResourceLocation> nowListeningOn = ImmutableSet.builder();
nowListeningOn.addAll(getInitialListeningChannels(listener.flow()));
nowListeningOn.addAll(setup.getChannels(ConnectionProtocol.CONFIGURATION).keySet());
listener.send(new MinecraftRegisterPayload(nowListeningOn.build()));
}
/**
* Invoked by the client when no {@link ModdedNetworkQueryPayload} has been received, but instead a {@link BrandPayload} has been received as the first packet during negotiation in the configuration phase.
* <p>
* If this happens then the client will do a negotiation of its own internal channel configuration, to check if any mods are installed that require a modded connection to the server.
* If those are found then the connection is aborted and the client disconnects from the server.
* <p>
* This method should never be invoked on a connection where the server is {@link ConnectionType#NEOFORGE}.
* <p>
* Invoked on the network thread.
*
* @param listener The listener which received the brand payload.
* @return True if the vanilla connection should be handled by the client, false otherwise.
*/
public static void initializeOtherConnection(ClientConfigurationPacketListener listener) {
// Because we are in vanilla land, no matter what we are not able to support any custom channels.
ChannelAttributes.setPayloadSetup(listener.getConnection(), NetworkPayloadSetup.empty());
ChannelAttributes.setConnectionType(listener.getConnection(), listener.getConnectionType());
for (ConnectionProtocol protocol : PAYLOAD_REGISTRATIONS.keySet()) {
NegotiationResult negotiationResult = NetworkComponentNegotiator.negotiate(
List.of(),
PAYLOAD_REGISTRATIONS.get(protocol).entrySet().stream()
.map(entry -> new NegotiableNetworkComponent(entry.getKey(), entry.getValue().version(), entry.getValue().flow(), entry.getValue().optional()))
.toList());
// Negotiation failed. Disconnect the client.
if (!negotiationResult.success()) {
listener.getConnection().disconnect(Component.translatableWithFallback("neoforge.network.negotiation.failure.vanilla.server.not_supported",
"You are trying to connect to a server that is not running NeoForge, but you have mods that require it. A connection could not be established.", NeoForgeVersion.getVersion()));
return;
}
}
// We are on the client, connected to a vanilla server, make sure we don't have any extended enums that may be sent to the server
if (!CheckExtensibleEnums.handleVanillaServerConnection(listener)) {
return;
}
// We are on the client, connected to a vanilla server, make sure we don't have any modded feature flags
if (!CheckFeatureFlags.handleVanillaServerConnection(listener)) {
return;
}
// We are on the client, connected to a vanilla server, We have to load the default configs.
ConfigTracker.INSTANCE.loadDefaultServerConfigs();
NetworkFilters.injectIfNecessary(listener.getConnection());
ImmutableSet.Builder<ResourceLocation> nowListeningOn = ImmutableSet.builder();
nowListeningOn.addAll(getInitialListeningChannels(listener.flow()));
PAYLOAD_REGISTRATIONS.get(ConnectionProtocol.CONFIGURATION).entrySet().stream()
.filter(registration -> registration.getValue().matchesFlow(listener.flow()))
.filter(registration -> registration.getValue().optional())
.forEach(registration -> nowListeningOn.add(registration.getKey()));
listener.send(new MinecraftRegisterPayload(nowListeningOn.build()));
}
/**
* Checks if the packet listener's connection can send/receive the given payload.
*
@ -783,7 +680,7 @@ public class NetworkRegistry {
* <p>
* Updates the ad-hoc channels to prepare for the game phase by removing the initial channels and building a new list based on the connection type.
*
* @param listener
* @param listener The packet listener finishing the configuration phase
*/
public static void onConfigurationFinished(ICommonPacketListener listener) {
NetworkPayloadSetup setup = ChannelAttributes.getPayloadSetup(listener.getConnection());

View File

@ -13,9 +13,9 @@ import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.PacketFlow;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.neoforged.neoforge.network.handling.DirectionalPayloadHandler;
import net.neoforged.neoforge.network.handling.IPayloadHandler;
import net.neoforged.neoforge.network.handling.MainThreadPayloadHandler;
import org.jetbrains.annotations.Nullable;
/**
* Builder-style helper for registering {@link CustomPacketPayload}s, used for modded networking.
@ -41,81 +41,132 @@ public class PayloadRegistrar {
/**
* Registers a client-bound payload for the play phase.
*/
public <T extends CustomPacketPayload> PayloadRegistrar playToClient(CustomPacketPayload.Type<T> type, StreamCodec<? super RegistryFriendlyByteBuf, T> reader, IPayloadHandler<T> handler) {
register(type, reader, handler, List.of(ConnectionProtocol.PLAY), Optional.of(PacketFlow.CLIENTBOUND), version, optional);
public <T extends CustomPacketPayload> PayloadRegistrar playToClient(CustomPacketPayload.Type<T> type, StreamCodec<? super RegistryFriendlyByteBuf, T> codec, IPayloadHandler<T> handler) {
register(type, codec, null, handler, List.of(ConnectionProtocol.PLAY), Optional.of(PacketFlow.CLIENTBOUND), version, optional);
return this;
}
/**
* Registers a client-bound payload for the play phase without a handler. The missing handler must be registered via {@code RegisterClientPayloadHandlersEvent}.
*/
public <T extends CustomPacketPayload> PayloadRegistrar playToClient(CustomPacketPayload.Type<T> type, StreamCodec<? super RegistryFriendlyByteBuf, T> codec) {
register(type, codec, null, null, List.of(ConnectionProtocol.PLAY), Optional.of(PacketFlow.CLIENTBOUND), version, optional);
return this;
}
/**
* Registers a server-bound payload for the play phase.
*/
public <T extends CustomPacketPayload> PayloadRegistrar playToServer(CustomPacketPayload.Type<T> type, StreamCodec<? super RegistryFriendlyByteBuf, T> reader, IPayloadHandler<T> handler) {
register(type, reader, handler, List.of(ConnectionProtocol.PLAY), Optional.of(PacketFlow.SERVERBOUND), version, optional);
public <T extends CustomPacketPayload> PayloadRegistrar playToServer(CustomPacketPayload.Type<T> type, StreamCodec<? super RegistryFriendlyByteBuf, T> codec, IPayloadHandler<T> handler) {
register(type, codec, handler, null, List.of(ConnectionProtocol.PLAY), Optional.of(PacketFlow.SERVERBOUND), version, optional);
return this;
}
/**
* Registers a bidirectional payload for the play phase.
* <p>
* Consider using {@link DirectionalPayloadHandler} to wrap client and server handlers.
* If the provided {@linkplain IPayloadHandler client handler} is null, it must be registered via {@code RegisterClientPayloadHandlersEvent}
*/
public <T extends CustomPacketPayload> PayloadRegistrar playBidirectional(CustomPacketPayload.Type<T> type, StreamCodec<? super RegistryFriendlyByteBuf, T> reader, IPayloadHandler<T> handler) {
register(type, reader, handler, List.of(ConnectionProtocol.PLAY), Optional.empty(), version, optional);
public <T extends CustomPacketPayload> PayloadRegistrar playBidirectional(CustomPacketPayload.Type<T> type, StreamCodec<? super RegistryFriendlyByteBuf, T> codec, IPayloadHandler<T> serverHandler, @Nullable IPayloadHandler<T> clientHandler) {
register(type, codec, serverHandler, clientHandler, List.of(ConnectionProtocol.PLAY), Optional.empty(), version, optional);
return this;
}
/**
* Registers a bidirectional payload for the play phase.
* <p>
* The provided handler is registered for serverbound payloads and the client-side handler must be registered via {@code RegisterClientPayloadHandlersEvent}
*/
public <T extends CustomPacketPayload> PayloadRegistrar playBidirectional(CustomPacketPayload.Type<T> type, StreamCodec<? super RegistryFriendlyByteBuf, T> codec, IPayloadHandler<T> serverHandler) {
return this.playBidirectional(type, codec, serverHandler, null);
}
/**
* Registers a client-bound payload for the configuration phase.
*/
public <T extends CustomPacketPayload> PayloadRegistrar configurationToClient(CustomPacketPayload.Type<T> type, StreamCodec<? super FriendlyByteBuf, T> reader, IPayloadHandler<T> handler) {
register(type, reader, handler, List.of(ConnectionProtocol.CONFIGURATION), Optional.of(PacketFlow.CLIENTBOUND), version, optional);
public <T extends CustomPacketPayload> PayloadRegistrar configurationToClient(CustomPacketPayload.Type<T> type, StreamCodec<? super FriendlyByteBuf, T> codec, IPayloadHandler<T> handler) {
register(type, codec, null, handler, List.of(ConnectionProtocol.CONFIGURATION), Optional.of(PacketFlow.CLIENTBOUND), version, optional);
return this;
}
/**
* Registers a client-bound payload for the configuration phase without a handler. The missing handler must be registered via {@code RegisterClientPayloadHandlersEvent}.
*/
public <T extends CustomPacketPayload> PayloadRegistrar configurationToClient(CustomPacketPayload.Type<T> type, StreamCodec<? super FriendlyByteBuf, T> codec) {
register(type, codec, null, null, List.of(ConnectionProtocol.CONFIGURATION), Optional.of(PacketFlow.CLIENTBOUND), version, optional);
return this;
}
/**
* Registers a server-bound payload for the configuration phase.
*/
public <T extends CustomPacketPayload> PayloadRegistrar configurationToServer(CustomPacketPayload.Type<T> type, StreamCodec<? super FriendlyByteBuf, T> reader, IPayloadHandler<T> handler) {
register(type, reader, handler, List.of(ConnectionProtocol.CONFIGURATION), Optional.of(PacketFlow.SERVERBOUND), version, optional);
public <T extends CustomPacketPayload> PayloadRegistrar configurationToServer(CustomPacketPayload.Type<T> type, StreamCodec<? super FriendlyByteBuf, T> codec, IPayloadHandler<T> handler) {
register(type, codec, handler, null, List.of(ConnectionProtocol.CONFIGURATION), Optional.of(PacketFlow.SERVERBOUND), version, optional);
return this;
}
/**
* Registers a bidirectional payload for the configuration phase.
* <p>
* Consider using {@link DirectionalPayloadHandler} to wrap client and server handlers.
* If the provided {@linkplain IPayloadHandler client handler} is null, it must be registered via {@code RegisterClientPayloadHandlersEvent}
*/
public <T extends CustomPacketPayload> PayloadRegistrar configurationBidirectional(CustomPacketPayload.Type<T> type, StreamCodec<? super FriendlyByteBuf, T> reader, IPayloadHandler<T> handler) {
register(type, reader, handler, List.of(ConnectionProtocol.CONFIGURATION), Optional.empty(), version, optional);
public <T extends CustomPacketPayload> PayloadRegistrar configurationBidirectional(CustomPacketPayload.Type<T> type, StreamCodec<? super FriendlyByteBuf, T> codec, IPayloadHandler<T> serverHandler, @Nullable IPayloadHandler<T> clientHandler) {
register(type, codec, serverHandler, clientHandler, List.of(ConnectionProtocol.CONFIGURATION), Optional.empty(), version, optional);
return this;
}
/**
* Registers a bidirectional payload for the configuration phase.
* <p>
* The provided handler is registered for serverbound payloads and the client-side handler must be registered via {@code RegisterClientPayloadHandlersEvent}
*/
public <T extends CustomPacketPayload> PayloadRegistrar configurationBidirectional(CustomPacketPayload.Type<T> type, StreamCodec<? super FriendlyByteBuf, T> codec, IPayloadHandler<T> serverHandler) {
return this.configurationBidirectional(type, codec, serverHandler, null);
}
/**
* Registers a client-bound payload for all phases.
*/
public <T extends CustomPacketPayload> PayloadRegistrar commonToClient(CustomPacketPayload.Type<T> type, StreamCodec<? super FriendlyByteBuf, T> reader, IPayloadHandler<T> handler) {
register(type, reader, handler, List.of(ConnectionProtocol.PLAY, ConnectionProtocol.CONFIGURATION), Optional.of(PacketFlow.CLIENTBOUND), version, optional);
public <T extends CustomPacketPayload> PayloadRegistrar commonToClient(CustomPacketPayload.Type<T> type, StreamCodec<? super FriendlyByteBuf, T> codec, IPayloadHandler<T> handler) {
register(type, codec, null, handler, List.of(ConnectionProtocol.PLAY, ConnectionProtocol.CONFIGURATION), Optional.of(PacketFlow.CLIENTBOUND), version, optional);
return this;
}
/**
* Registers a client-bound payload for all phases without a handler. The missing handler must be registered via {@code RegisterClientPayloadHandlersEvent}.
*/
public <T extends CustomPacketPayload> PayloadRegistrar commonToClient(CustomPacketPayload.Type<T> type, StreamCodec<? super FriendlyByteBuf, T> codec) {
register(type, codec, null, null, List.of(ConnectionProtocol.PLAY, ConnectionProtocol.CONFIGURATION), Optional.of(PacketFlow.CLIENTBOUND), version, optional);
return this;
}
/**
* Registers a server-bound payload for all phases.
*/
public <T extends CustomPacketPayload> PayloadRegistrar commonToServer(CustomPacketPayload.Type<T> type, StreamCodec<? super FriendlyByteBuf, T> reader, IPayloadHandler<T> handler) {
register(type, reader, handler, List.of(ConnectionProtocol.PLAY, ConnectionProtocol.CONFIGURATION), Optional.of(PacketFlow.SERVERBOUND), version, optional);
public <T extends CustomPacketPayload> PayloadRegistrar commonToServer(CustomPacketPayload.Type<T> type, StreamCodec<? super FriendlyByteBuf, T> codec, IPayloadHandler<T> handler) {
register(type, codec, handler, null, List.of(ConnectionProtocol.PLAY, ConnectionProtocol.CONFIGURATION), Optional.of(PacketFlow.SERVERBOUND), version, optional);
return this;
}
/**
* Registers a bidirectional payload for all phases.
* <p>
* Consider using {@link DirectionalPayloadHandler} to wrap client and server handlers.
* If the provided {@linkplain IPayloadHandler client handler} is null, it must be registered via {@code RegisterClientPayloadHandlersEvent}
*/
public <T extends CustomPacketPayload> PayloadRegistrar commonBidirectional(CustomPacketPayload.Type<T> type, StreamCodec<? super FriendlyByteBuf, T> reader, IPayloadHandler<T> handler) {
register(type, reader, handler, List.of(ConnectionProtocol.PLAY, ConnectionProtocol.CONFIGURATION), Optional.empty(), version, optional);
public <T extends CustomPacketPayload> PayloadRegistrar commonBidirectional(CustomPacketPayload.Type<T> type, StreamCodec<? super FriendlyByteBuf, T> codec, IPayloadHandler<T> serverHandler, @Nullable IPayloadHandler<T> clientHandler) {
register(type, codec, serverHandler, clientHandler, List.of(ConnectionProtocol.PLAY, ConnectionProtocol.CONFIGURATION), Optional.empty(), version, optional);
return this;
}
/**
* Registers a bidirectional payload for all phases.
* <p>
* The provided handler is registered for serverbound payloads and the client-side handler must be registered via {@code RegisterClientPayloadHandlersEvent}
*/
public <T extends CustomPacketPayload> PayloadRegistrar commonBidirectional(CustomPacketPayload.Type<T> type, StreamCodec<? super FriendlyByteBuf, T> codec, IPayloadHandler<T> serverHandler) {
return this.commonBidirectional(type, codec, serverHandler, null);
}
/**
* Creates a copy of this registrar with a different default handling thread.
* <p>
@ -161,11 +212,23 @@ public class PayloadRegistrar {
return clone;
}
private <T extends CustomPacketPayload, B extends FriendlyByteBuf> void register(CustomPacketPayload.Type<T> type, StreamCodec<? super B, T> codec, IPayloadHandler<T> handler,
List<ConnectionProtocol> protocols, Optional<PacketFlow> flow, String version, boolean optional) {
private <T extends CustomPacketPayload, B extends FriendlyByteBuf> void register(
CustomPacketPayload.Type<T> type,
StreamCodec<? super B, T> codec,
@Nullable IPayloadHandler<T> serverHandler,
@Nullable IPayloadHandler<T> clientHandler,
List<ConnectionProtocol> protocols,
Optional<PacketFlow> flow,
String version,
boolean optional) {
if (this.thread == HandlerThread.MAIN) {
handler = new MainThreadPayloadHandler<>(handler);
if (serverHandler != null) {
serverHandler = new MainThreadPayloadHandler<>(serverHandler);
}
if (clientHandler != null) {
clientHandler = new MainThreadPayloadHandler<>(clientHandler);
}
}
NetworkRegistry.register(type, codec, handler, protocols, flow, version, optional);
NetworkRegistry.register(type, codec, serverHandler, clientHandler, protocols, flow, version, optional);
}
}

View File

@ -13,7 +13,6 @@ import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.PacketFlow;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.resources.ResourceLocation;
import net.neoforged.neoforge.network.handling.IPayloadHandler;
import org.jetbrains.annotations.ApiStatus;
/**
@ -21,7 +20,6 @@ import org.jetbrains.annotations.ApiStatus;
*
* @param type The type of the payload
* @param codec The codec for the payload
* @param handler The handler for the payload
* @param protocols The protocols this payload supports
* @param flow The flow this payload supports (empty if both)
* @param version The version of the payload
@ -32,7 +30,6 @@ import org.jetbrains.annotations.ApiStatus;
public record PayloadRegistration<T extends CustomPacketPayload>(
CustomPacketPayload.Type<T> type,
StreamCodec<? super RegistryFriendlyByteBuf, T> codec,
IPayloadHandler<T> handler,
List<ConnectionProtocol> protocols,
Optional<PacketFlow> flow,
String version,

View File

@ -42,6 +42,7 @@ import net.neoforged.bus.api.IEventBus;
import net.neoforged.fml.ModContainer;
import net.neoforged.fml.loading.FMLLoader;
import net.neoforged.neoforge.client.event.ClientPlayerNetworkEvent;
import net.neoforged.neoforge.client.network.ClientPacketDistributor;
import net.neoforged.neoforge.common.NeoForge;
import net.neoforged.neoforge.event.entity.player.PlayerEvent;
import net.neoforged.neoforge.event.server.ServerStartedEvent;
@ -322,7 +323,7 @@ public class TestFrameworkImpl implements MutableTestFramework {
final ChangeStatusPayload packet = new ChangeStatusPayload(this, test.id(), newStatus);
sendPacketIfOn(
() -> PacketDistributor.sendToAllPlayers(packet),
() -> PacketDistributor.sendToServer(packet),
() -> ClientPacketDistributor.sendToServer(packet),
null);
}
@ -349,7 +350,7 @@ public class TestFrameworkImpl implements MutableTestFramework {
final ChangeEnabledPayload packet = new ChangeEnabledPayload(TestFrameworkImpl.this, test.id(), enabled);
sendPacketIfOn(
() -> PacketDistributor.sendToAllPlayers(packet),
() -> PacketDistributor.sendToServer(packet),
() -> ClientPacketDistributor.sendToServer(packet),
null);
}

View File

@ -21,10 +21,10 @@ public record TestFrameworkPayloadInitialization(MutableTestFramework framework)
registrar.playBidirectional(ChangeStatusPayload.ID,
StreamCodec.of((RegistryFriendlyByteBuf buf, ChangeStatusPayload packet) -> packet.write(buf), buf -> ChangeStatusPayload.decode(framework, buf)),
(payload, context) -> payload.handle(context));
ChangeStatusPayload::handle, ChangeStatusPayload::handle);
registrar.playBidirectional(ChangeEnabledPayload.ID,
StreamCodec.of((buf, packet) -> packet.write(buf), buf -> ChangeEnabledPayload.decode(framework, buf)),
(payload, context) -> payload.handle(context));
ChangeEnabledPayload::handle, ChangeEnabledPayload::handle);
}
}

View File

@ -57,6 +57,7 @@ public class ModMismatchTest implements IPayloadHandler<ModMismatchTest.ModMisma
.configurationBidirectional(
ModMismatchPayload.TYPE,
ModMismatchPayload.STREAM_CODEC,
this,
this);
}
}