mirror of
https://github.com/MinecraftForge/MinecraftForge.git
synced 2026-11-12 08:17:35 +00:00
Remove ICustomPacket and add PayloadChannel. (#9972)
New PayloadChannel that uses the vanilla payload Type for packet distinction Implement the minecraft:register/unregister channels using the new PayloadChannel New generic channel builder function allowing people to implement channels however they want.
This commit is contained in:
1 parent
640c206117
commit
2b3a4137e1
34 files changed
+1035
-338
No files matched your search
@@ -52,6 +52,7 @@ import net.minecraft.core.NonNullList;
|
||||
import net.minecraft.core.component.DataComponents;
|
||||
import net.minecraft.core.particles.ParticleTypes;
|
||||
import net.minecraft.network.Connection;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf;
|
||||
import net.minecraft.network.chat.ClickEvent;
|
||||
import net.minecraft.network.chat.Component;
|
||||
@@ -101,9 +102,13 @@ import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.ListTag;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.network.protocol.PacketFlow;
|
||||
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
|
||||
import net.minecraft.network.protocol.common.custom.DiscardedPayload;
|
||||
import net.minecraft.network.syncher.EntityDataSerializer;
|
||||
import net.minecraft.network.protocol.game.ClientGamePacketListener;
|
||||
import net.minecraft.network.protocol.game.ClientboundBlockUpdatePacket;
|
||||
import net.minecraft.network.protocol.login.ClientboundCustomQueryPacket;
|
||||
import net.minecraft.network.protocol.login.ServerboundCustomQueryAnswerPacket;
|
||||
import net.minecraft.stats.Stats;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import net.minecraft.world.level.block.entity.FurnaceBlockEntity;
|
||||
@@ -161,7 +166,7 @@ import net.minecraftforge.fml.ModLoader;
|
||||
import net.minecraftforge.fml.config.ConfigTracker;
|
||||
import net.minecraftforge.fml.util.thread.EffectiveSide;
|
||||
import net.minecraftforge.network.ConnectionType;
|
||||
import net.minecraftforge.network.ICustomPacket;
|
||||
import net.minecraftforge.network.ForgePayload;
|
||||
import net.minecraftforge.network.NetworkContext;
|
||||
import net.minecraftforge.network.NetworkDirection;
|
||||
import net.minecraftforge.network.NetworkInitialization;
|
||||
@@ -1111,19 +1116,51 @@ public final class ForgeHooks {
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
public static boolean onCustomPayload(ICustomPacket<?> packet, Connection connection) {
|
||||
public static <B extends FriendlyByteBuf> StreamCodec<B, ? extends CustomPacketPayload> getCustomPayloadCodec(ResourceLocation id, int max) {
|
||||
var channel = NetworkRegistry.findTarget(id);
|
||||
if (channel == null)
|
||||
return DiscardedPayload.codec(id, max);
|
||||
|
||||
return StreamCodec.<B, ForgePayload>ofMember(
|
||||
(value, buf) -> {
|
||||
value.encoder().accept(buf);
|
||||
},
|
||||
(buf) -> {
|
||||
int len = buf.readableBytes();
|
||||
if (len < 0 || len > max)
|
||||
throw new IllegalArgumentException("Payload may not be larger then " + max + " bytes");
|
||||
return ForgePayload.create(id, buf.wrap(buf.readBytes(len)));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
public static boolean onCustomPayload(CustomPacketPayload payload, Connection connection) {
|
||||
var context = new CustomPayloadEvent.Context(connection);
|
||||
return onCustomPayload(new CustomPayloadEvent(payload.type().id(), payload, context, 0));
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
public static boolean onCustomPayload(ClientboundCustomQueryPacket packet, Connection connection) {
|
||||
var context = new CustomPayloadEvent.Context(connection);
|
||||
return onCustomPayload(new CustomPayloadEvent(packet.payload().id(), packet.payload(), context, packet.transactionId()));
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
public static boolean onCustomPayload(ServerboundCustomQueryAnswerPacket packet, Connection connection) {
|
||||
var context = new CustomPayloadEvent.Context(connection);
|
||||
return onCustomPayload(new CustomPayloadEvent(NetworkInitialization.LOGIN_NAME, packet.payload(), context, packet.transactionId()));
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
public static boolean onCustomPayload(CustomPayloadEvent event) {
|
||||
var connection = event.getSource().getConnection();
|
||||
var expectedSide = connection.getReceiving() == PacketFlow.CLIENTBOUND ? LogicalSide.CLIENT : LogicalSide.SERVER;
|
||||
if (expectedSide != EffectiveSide.get()) {
|
||||
connection.disconnect(Component.literal("Illegal packet received, terminating connection"));
|
||||
return false;
|
||||
}
|
||||
|
||||
var context = new CustomPayloadEvent.Context(connection);
|
||||
return onCustomPayload(new CustomPayloadEvent(packet, context));
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
public static boolean onCustomPayload(CustomPayloadEvent event) {
|
||||
var channel = NetworkRegistry.findTarget(event.getChannel());
|
||||
if (channel != null && channel.dispatch(event))
|
||||
return true;
|
||||
@@ -1146,13 +1183,12 @@ public final class ForgeHooks {
|
||||
LOGGER.info("Connected to a modded server.");
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@ApiStatus.Internal
|
||||
public static Packet<ClientGamePacketListener> getEntitySpawnPacket(Entity entity) {
|
||||
if (!(entity instanceof IEntityAdditionalSpawnData add))
|
||||
throw new IllegalArgumentException(entity.getClass() + " is not an instance of " + IEntityAdditionalSpawnData.class);
|
||||
|
||||
return (Packet<ClientGamePacketListener>)NetworkDirection.PLAY_TO_CLIENT.buildPacket(NetworkInitialization.PLAY, new SpawnEntity(entity));
|
||||
return NetworkDirection.PLAY_TO_CLIENT.buildPacket(NetworkInitialization.PLAY, new SpawnEntity(entity));
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
|
||||
@@ -10,11 +10,17 @@ import org.apache.logging.log4j.Logger;
|
||||
import org.apache.logging.log4j.Marker;
|
||||
import org.apache.logging.log4j.MarkerManager;
|
||||
|
||||
import io.netty.buffer.Unpooled;
|
||||
import net.minecraft.network.Connection;
|
||||
import net.minecraft.network.PacketListener;
|
||||
import net.minecraft.network.ProtocolInfo;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.network.protocol.PacketFlow;
|
||||
import net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket;
|
||||
import net.minecraft.network.protocol.common.ServerboundCustomPayloadPacket;
|
||||
import net.minecraft.network.protocol.configuration.ClientboundFinishConfigurationPacket;
|
||||
import net.minecraftforge.network.ICustomPacket;
|
||||
import net.minecraft.network.protocol.login.ClientboundCustomQueryPacket;
|
||||
import net.minecraft.network.protocol.login.ServerboundCustomQueryAnswerPacket;
|
||||
|
||||
/*
|
||||
* This is a utility class that just dumps packets to the logger.
|
||||
@@ -33,20 +39,44 @@ public class PacketLogger {
|
||||
}
|
||||
|
||||
public void send(Packet<?> packet) {
|
||||
common(connection.getReceiving(), connection.getSending(), packet);
|
||||
common(connection.getReceiving(), connection.getSending(), connection.getOutputboundProtocolInfo(), packet);
|
||||
}
|
||||
|
||||
public void recv(Packet<?> packet) {
|
||||
common(connection.getReceiving(), connection.getReceiving(), packet);
|
||||
common(connection.getReceiving(), connection.getReceiving(), connection.getInboundProtocolInfo(), packet);
|
||||
}
|
||||
|
||||
private void common(PacketFlow side, PacketFlow flow, Packet<?> packet) {
|
||||
private void common(PacketFlow side, PacketFlow flow, ProtocolInfo<?> protocol, Packet<?> packet) {
|
||||
if (!enabled) return;
|
||||
if (packet instanceof ClientboundFinishConfigurationPacket)
|
||||
enabled = false;
|
||||
|
||||
if (packet instanceof ICustomPacket custom && custom.getInternalData() != null) {
|
||||
LOGGER.info(MARKER, "{} {} {} {}\n{}", side(side), dir(flow), packet.getClass().getName(), custom.getName(), HexDumper.dump(custom.getInternalData()));
|
||||
String channel = null;
|
||||
String hex = null;
|
||||
switch (packet) {
|
||||
case ClientboundCustomPayloadPacket custom -> {
|
||||
channel = custom.payload().type().id().toString();
|
||||
hex = hex(protocol, packet);
|
||||
}
|
||||
case ServerboundCustomPayloadPacket custom -> {
|
||||
channel = custom.payload().type().id().toString();
|
||||
hex = hex(protocol, packet);
|
||||
}
|
||||
case ClientboundCustomQueryPacket custom -> {
|
||||
channel = custom.payload().id().toString() + " id " + custom.transactionId();
|
||||
hex = hex(protocol, packet);
|
||||
}
|
||||
case ServerboundCustomQueryAnswerPacket custom -> {
|
||||
channel = packet.getClass().getName() + " id " + custom.transactionId();
|
||||
hex = hex(protocol, packet);
|
||||
}
|
||||
default -> {}
|
||||
}
|
||||
|
||||
if (channel != null && hex != null) {
|
||||
LOGGER.info(MARKER, "{} {} {} {}\n{}", side(side), dir(flow), packet.getClass().getName(), channel, hex);
|
||||
} else if (channel != null) {
|
||||
LOGGER.info(MARKER, "{} {} {} {}", side(side), dir(flow), packet.getClass().getName(), channel);
|
||||
} else {
|
||||
LOGGER.info(MARKER, "{} {} {}", side(side), dir(flow), packet.getClass().getName());
|
||||
}
|
||||
@@ -58,4 +88,11 @@ public class PacketLogger {
|
||||
private static String dir(PacketFlow flow) {
|
||||
return flow == PacketFlow.CLIENTBOUND ? "S->C" : "C->S";
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
private static <T extends PacketListener> String hex(ProtocolInfo protocol, Packet packet) {
|
||||
var buf = Unpooled.buffer();
|
||||
protocol.codec().encode(buf, packet);
|
||||
return HexDumper.dump(buf);
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@ import net.minecraft.server.network.ServerGamePacketListenerImpl;
|
||||
import net.minecraft.network.Connection;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraftforge.eventbus.api.Event;
|
||||
import net.minecraftforge.network.ICustomPacket;
|
||||
import net.minecraftforge.network.ForgePayload;
|
||||
import net.minecraftforge.common.util.LogicalSidedProvider;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -26,27 +26,30 @@ import java.util.concurrent.CompletableFuture;
|
||||
// But also expose it as a generic listener event for anyone who cares about it but is outside out channel control system.
|
||||
public class CustomPayloadEvent extends Event {
|
||||
private final ResourceLocation channel;
|
||||
private final FriendlyByteBuf payload;
|
||||
private final Object payload;
|
||||
private final FriendlyByteBuf data;
|
||||
private final Context source;
|
||||
private final int loginIndex;
|
||||
|
||||
public CustomPayloadEvent(ResourceLocation channel, FriendlyByteBuf payload, Context source, int loginIndex) {
|
||||
public CustomPayloadEvent(ResourceLocation channel, Object payload, Context source, int loginIndex) {
|
||||
this.channel = channel;
|
||||
this.payload = payload;
|
||||
this.data = payload instanceof ForgePayload forge ? forge.data() : null;
|
||||
this.source = source;
|
||||
this.loginIndex = loginIndex;
|
||||
}
|
||||
|
||||
public CustomPayloadEvent(ICustomPacket<?> payload, Context source) {
|
||||
this(payload.getName(), payload.getInternalData(), source, payload.getIndex());
|
||||
}
|
||||
|
||||
public ResourceLocation getChannel() {
|
||||
return this.channel;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public FriendlyByteBuf getPayload() {
|
||||
return payload;
|
||||
return data;
|
||||
}
|
||||
|
||||
public Object getPayloadObject() {
|
||||
return this.payload;
|
||||
}
|
||||
|
||||
public int getLoginIndex() {
|
||||
|
||||
@@ -7,9 +7,15 @@ package net.minecraftforge.network;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.apache.logging.log4j.Marker;
|
||||
import org.apache.logging.log4j.MarkerManager;
|
||||
|
||||
import net.minecraft.network.Connection;
|
||||
import net.minecraft.network.ConnectionProtocol;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.network.protocol.PacketFlow;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
@@ -17,6 +23,8 @@ import net.minecraftforge.event.network.CustomPayloadEvent;
|
||||
import net.minecraftforge.network.packets.LoginWrapper;
|
||||
|
||||
public abstract class Channel<MSG> {
|
||||
private static final Logger LOGGER = LogManager.getLogger();
|
||||
private static final Marker MARKER = MarkerManager.getMarker("CHANNEL");
|
||||
protected final NetworkInstance instance;
|
||||
|
||||
protected Channel(NetworkInstance instance) {
|
||||
@@ -27,6 +35,15 @@ public abstract class Channel<MSG> {
|
||||
return instance.getChannelName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the channel name to use for the specified packet
|
||||
* This typically is the main channel's name, but in some cases
|
||||
* you want to specify a custom one. If your channel has {@link NetworkInstance#addChild(ResourceLocation) children}
|
||||
*/
|
||||
ResourceLocation getName(MSG packet) {
|
||||
return getName();
|
||||
}
|
||||
|
||||
public int getProtocolVersion() {
|
||||
return instance.getNetworkProtocolVersion();
|
||||
}
|
||||
@@ -35,12 +52,11 @@ public abstract class Channel<MSG> {
|
||||
* Returns true if the channel is present in the given connection.
|
||||
*/
|
||||
public boolean isRemotePresent(Connection connection) {
|
||||
return NetworkContext.get(connection).getRemoteChannels().contains(getName());
|
||||
return instance.isRemotePresent(connection);
|
||||
}
|
||||
|
||||
public abstract void encode(FriendlyByteBuf out, MSG message);
|
||||
|
||||
// Package private so we can call from ourselves.
|
||||
protected Packet<?> toVanillaPacket(Connection connection, MSG message) {
|
||||
var protocol = connection.getProtocol();
|
||||
var handler = switch (protocol) {
|
||||
@@ -55,7 +71,7 @@ public abstract class Channel<MSG> {
|
||||
if (protocol == ConnectionProtocol.LOGIN && this != NetworkInitialization.LOGIN && connection.getSending() == PacketFlow.SERVERBOUND)
|
||||
return NetworkInitialization.LOGIN.toVanillaPacket(connection, new LoginWrapper(this, message));
|
||||
|
||||
return handler.buildPacket(connection.getSending(), this, message).getThis();
|
||||
return handler.buildPacket(connection.getSending(), this, message);
|
||||
}
|
||||
|
||||
public void send(MSG msg, Connection connection) {
|
||||
@@ -74,13 +90,30 @@ public abstract class Channel<MSG> {
|
||||
* @param <MSG> The type of the message
|
||||
*/
|
||||
public void send(MSG msg, PacketDistributor.PacketTarget target) {
|
||||
target.send(target.direction().buildPacket(this, msg).getThis());
|
||||
target.send(target.direction().buildPacket(this, msg));
|
||||
}
|
||||
|
||||
public void reply(MSG msg, CustomPayloadEvent.Context context) {
|
||||
send(msg, context.getConnection());
|
||||
}
|
||||
|
||||
protected void validate(Object id, Connection con, NetworkProtocol<?> protocol, PacketFlow direction, boolean sending) {
|
||||
var actualD = sending ? con.getSending() : con.getReceiving();
|
||||
var actualP = con.getProtocol();
|
||||
var expectedP = protocol == null ? actualP : protocol.toVanilla();
|
||||
var expectedD = direction == null ? actualD : direction;
|
||||
|
||||
if (expectedP != actualP || expectedD != actualD) {
|
||||
var error = "Illegal packet " + (sending ? "sent" : "received") + ", terminating connection. " + id + " expected " +
|
||||
expectedD + " " + expectedP + " but was " +
|
||||
actualD + " " + actualP;
|
||||
|
||||
LOGGER.error(MARKER, error);
|
||||
con.disconnect(Component.literal(error));
|
||||
throw new IllegalStateException(error);
|
||||
}
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public static interface VersionTest {
|
||||
public static final VersionTest ACCEPT_MISSING = (status, version) -> status == Status.MISSING;
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.network;
|
||||
|
||||
public interface ChannelBuildable<T> {
|
||||
/*
|
||||
* This will build the entire channel, locking any future modifications from happening.
|
||||
* This should be the LAST call in your builder chain and can only be called once.
|
||||
*/
|
||||
Channel<T> build();
|
||||
}
|
||||
@@ -14,8 +14,10 @@ import java.util.function.Supplier;
|
||||
|
||||
import io.netty.util.AttributeKey;
|
||||
import net.minecraft.network.Connection;
|
||||
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraftforge.network.Channel.VersionTest;
|
||||
import net.minecraftforge.network.payload.PayloadConnection;
|
||||
|
||||
/**
|
||||
* Builder for constructing impl channels using a builder style API.
|
||||
@@ -27,6 +29,7 @@ public class ChannelBuilder {
|
||||
private Channel.VersionTest serverAcceptedVersions;
|
||||
private Map<AttributeKey<?>, Function<Connection, ?>> attributes = new HashMap<>();
|
||||
private Consumer<Connection> connectionHandler;
|
||||
private boolean registerSelf = true;
|
||||
|
||||
/**
|
||||
* Creates a new channel builder, The name of the channel must be unique.
|
||||
@@ -171,21 +174,17 @@ public class ChannelBuilder {
|
||||
* @return the {@link NetworkInstance}
|
||||
*/
|
||||
private NetworkInstance createNetworkInstance() {
|
||||
if (NetworkRegistry.lock) {
|
||||
NetworkRegistry.LOGGER.error(NetworkRegistry.NETREGISTRY, "Attempted to register channel {} even though registry phase is over", name);
|
||||
throw new IllegalArgumentException("Registration of impl channels is locked");
|
||||
}
|
||||
if (NetworkRegistry.instances.containsKey(name)) {
|
||||
NetworkRegistry.LOGGER.error(NetworkRegistry.NETREGISTRY, "NetworkDirection channel {} already registered.", name);
|
||||
throw new IllegalArgumentException("NetworkDirection Channel {"+ name +"} already registered");
|
||||
}
|
||||
|
||||
var networkInstance = new NetworkInstance(name, networkProtocolVersion,
|
||||
var instance = new NetworkInstance(name, networkProtocolVersion,
|
||||
getClientAcceptedVersions(), getServerAcceptedVersions(),
|
||||
attributes, connectionHandler
|
||||
);
|
||||
NetworkRegistry.instances.put(name, networkInstance);
|
||||
return networkInstance;
|
||||
|
||||
NetworkRegistry.register(instance);
|
||||
|
||||
if (registerSelf)
|
||||
instance.addChild(name);
|
||||
|
||||
return instance;
|
||||
|
||||
}
|
||||
|
||||
@@ -203,7 +202,7 @@ public class ChannelBuilder {
|
||||
* @return A new {@link SimpleChannel}
|
||||
*/
|
||||
public SimpleChannel simpleChannel() {
|
||||
return new SimpleChannel(createNetworkInstance());
|
||||
return channel(SimpleChannel::new);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -211,6 +210,23 @@ public class ChannelBuilder {
|
||||
* @return A new {@link EventNetworkChannel}
|
||||
*/
|
||||
public EventNetworkChannel eventNetworkChannel() {
|
||||
return new EventNetworkChannel(createNetworkInstance());
|
||||
return channel(EventNetworkChannel::new);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a new {@link PayloadChannel} with this builder's configuration.
|
||||
* @return A new {@link PayloadConnection PayloadConnection<CustomPacketPayload>}
|
||||
*/
|
||||
public PayloadConnection<CustomPacketPayload> payloadChannel() {
|
||||
this.registerSelf = false;
|
||||
return channel(PayloadChannel::builder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers this channel with the {@link NetworkManager} and calls the supplied Function.
|
||||
* This is meant to allow modders to build their own Channel implementations.
|
||||
*/
|
||||
public <C> C channel(Function<NetworkInstance, C> factory) {
|
||||
return factory.apply(createNetworkInstance());
|
||||
}
|
||||
}
|
||||
@@ -6,10 +6,11 @@
|
||||
package net.minecraftforge.network;
|
||||
|
||||
import com.mojang.logging.LogUtils;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import net.minecraft.ResourceLocationException;
|
||||
import net.minecraft.network.Connection;
|
||||
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.minecraftforge.event.ForgeEventFactory;
|
||||
import net.minecraftforge.event.network.ChannelRegistrationChangeEvent;
|
||||
@@ -19,44 +20,70 @@ import org.jetbrains.annotations.ApiStatus;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
||||
@ApiStatus.Internal // TODO: Decide what I want to have public, Right now it should just be add/remove channels.
|
||||
public class ChannelListManager {
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
static final EventNetworkChannel REGISTER = ChannelBuilder
|
||||
.named("minecraft:register")
|
||||
.optional()
|
||||
.eventNetworkChannel()
|
||||
.addListener(ChannelListManager::registerListener);
|
||||
static final EventNetworkChannel UNREGISTER = ChannelBuilder
|
||||
.named("minecraft:unregister")
|
||||
.optional()
|
||||
.eventNetworkChannel()
|
||||
.addListener(ChannelListManager::unregisterListener);
|
||||
|
||||
private static void registerListener(CustomPayloadEvent evt) {
|
||||
var ctx = evt.getSource();
|
||||
updateFrom(ctx, evt.getPayload(), ChannelRegistrationChangeEvent.Type.REGISTER);
|
||||
public static final ResourceLocation NAME = new ResourceLocation("forge", "channel_registration");
|
||||
|
||||
static final Channel<CustomPacketPayload> CHANNEL = ChannelBuilder
|
||||
.named(NAME)
|
||||
.optional()
|
||||
.payloadChannel()
|
||||
.any()
|
||||
.bidirectional()
|
||||
.add(Register.TYPE, Register.CODEC, ChannelListManager::register)
|
||||
.add(Unregister.TYPE, Unregister.CODEC, ChannelListManager::unregister)
|
||||
.build();
|
||||
|
||||
private record Register(List<String> channels) implements CustomPacketPayload {
|
||||
private static Type<Register> TYPE = CustomPacketPayload.createType("minecraft:register");
|
||||
private static StreamCodec<FriendlyByteBuf, Register> CODEC = StreamCodec.of(
|
||||
(buf, v) -> encode(buf, v.channels),
|
||||
buf -> new Register(decode(buf))
|
||||
);
|
||||
|
||||
@Override
|
||||
public Type<Register> type() {
|
||||
return TYPE;
|
||||
}
|
||||
}
|
||||
|
||||
private record Unregister(List<String> channels) implements CustomPacketPayload {
|
||||
private static Type<Unregister> TYPE = CustomPacketPayload.createType("minecraft:unregister");
|
||||
private static StreamCodec<FriendlyByteBuf, Unregister> CODEC = StreamCodec.of(
|
||||
(buf, v) -> encode(buf, v.channels),
|
||||
buf -> new Unregister(decode(buf))
|
||||
);
|
||||
|
||||
@Override
|
||||
public Type<Unregister> type() {
|
||||
return TYPE;
|
||||
}
|
||||
}
|
||||
|
||||
private static void register(Register payload, CustomPayloadEvent.Context ctx) {
|
||||
updateFrom(ctx, payload.channels(), ChannelRegistrationChangeEvent.Type.REGISTER);
|
||||
ctx.setPacketHandled(true);
|
||||
// Send the client's channels to the server whenever asked. If we're in the login state then our login wrapper will unwrap for us.
|
||||
if (ctx.isClientSide())
|
||||
addChannels(ctx.getConnection());
|
||||
}
|
||||
|
||||
private static void unregisterListener(CustomPayloadEvent evt) {
|
||||
var ctx = evt.getSource();
|
||||
updateFrom(ctx, evt.getPayload(), ChannelRegistrationChangeEvent.Type.UNREGISTER);
|
||||
private static void unregister(Unregister payload, CustomPayloadEvent.Context ctx) {
|
||||
updateFrom(ctx, payload.channels(), ChannelRegistrationChangeEvent.Type.UNREGISTER);
|
||||
ctx.setPacketHandled(true);
|
||||
}
|
||||
|
||||
public static void addChannels(Connection connection) {
|
||||
addChannels(connection, NetworkRegistry.buildChannelVersions().keySet().stream()
|
||||
.filter(rl -> !"minecraft".equals(rl.getNamespace()))
|
||||
.toList()
|
||||
);
|
||||
addChannels(connection, NetworkRegistry.buildRegisterList());
|
||||
}
|
||||
|
||||
public static void addChannels(Connection connection, ResourceLocation... channels) {
|
||||
@@ -65,12 +92,14 @@ public class ChannelListManager {
|
||||
|
||||
public static void addChannels(Connection connection, Collection<ResourceLocation> channels) {
|
||||
var list = NetworkContext.get(connection);
|
||||
var toSend = new HashSet<ResourceLocation>();
|
||||
var toSend = new HashSet<String>();
|
||||
for (var channel : channels) {
|
||||
if (list.sentChannels.add(channel))
|
||||
toSend.add(channel);
|
||||
toSend.add(channel.toString());
|
||||
}
|
||||
sendChannels(REGISTER, connection, toSend);
|
||||
|
||||
if (!toSend.isEmpty())
|
||||
CHANNEL.send(new Register(toSend.stream().sorted().toList()), connection);
|
||||
}
|
||||
|
||||
public static void removeChannels(Connection connection, ResourceLocation... channels) {
|
||||
@@ -79,31 +108,27 @@ public class ChannelListManager {
|
||||
|
||||
public static void removeChannels(Connection connection, Collection<ResourceLocation> channels) {
|
||||
var list = NetworkContext.get(connection);
|
||||
var toSend = new HashSet<ResourceLocation>();
|
||||
var toSend = new HashSet<String>();
|
||||
for (var channel : channels) {
|
||||
if (list.sentChannels.remove(channel))
|
||||
toSend.add(channel);
|
||||
toSend.add(channel.toString());
|
||||
}
|
||||
sendChannels(UNREGISTER, connection, toSend);
|
||||
|
||||
if (!toSend.isEmpty())
|
||||
CHANNEL.send(new Unregister(toSend.stream().toList()), connection);
|
||||
}
|
||||
|
||||
private static void sendChannels(EventNetworkChannel channel, Connection connection, Collection<ResourceLocation> channels) {
|
||||
if (channels.isEmpty())
|
||||
return;
|
||||
|
||||
var buf = new FriendlyByteBuf(Unpooled.buffer());
|
||||
private static void encode(FriendlyByteBuf buf, List<String> channels) {
|
||||
for (var c : channels) {
|
||||
buf.writeBytes(c.toString().getBytes(StandardCharsets.UTF_8));
|
||||
buf.writeByte(0);
|
||||
}
|
||||
|
||||
channel.send(buf, connection);
|
||||
}
|
||||
|
||||
private static void updateFrom(CustomPayloadEvent.Context source, FriendlyByteBuf buffer, final ChannelRegistrationChangeEvent.Type changeType) {
|
||||
private static List<String> decode(FriendlyByteBuf buffer) {
|
||||
byte[] data = new byte[Math.max(buffer.readableBytes(), 0)];
|
||||
buffer.readBytes(data);
|
||||
var channels = new HashSet<String>();
|
||||
var channels = new ArrayList<String>();
|
||||
|
||||
int last = 0;
|
||||
for (int cur = 0; cur < data.length; cur++) {
|
||||
@@ -117,6 +142,10 @@ public class ChannelListManager {
|
||||
if (last < data.length)
|
||||
channels.add(new String(data, last, data.length - last, StandardCharsets.UTF_8));
|
||||
|
||||
return Collections.unmodifiableList(channels);
|
||||
}
|
||||
|
||||
private static void updateFrom(CustomPayloadEvent.Context source, List<String> channels, final ChannelRegistrationChangeEvent.Type changeType) {
|
||||
var changed = new HashSet<ResourceLocation>();
|
||||
for (var channel : channels) {
|
||||
// It also says nothing about the format of channels so ignore bad channels.
|
||||
@@ -142,7 +171,7 @@ public class ChannelListManager {
|
||||
if (fire) {
|
||||
var target = NetworkRegistry.findTarget(channel);
|
||||
if (target != null)
|
||||
target.registrationChange(changeType == ChannelRegistrationChangeEvent.Type.REGISTER);
|
||||
target.registrationChange(channel, changeType == ChannelRegistrationChangeEvent.Type.REGISTER);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ public class ForgePacketHandler {
|
||||
}
|
||||
|
||||
void handleLoginWrapper(LoginWrapper msg, CustomPayloadEvent.Context ctx) {
|
||||
ForgeHooks.onCustomPayload(new CustomPayloadEvent(msg.name(), msg.data(), ctx, -1));
|
||||
ForgeHooks.onCustomPayload(new CustomPayloadEvent(msg.name(), ForgePayload.create(msg.name(), msg.data()), ctx, -1));
|
||||
}
|
||||
|
||||
void handleClientAck(Acknowledge msg, CustomPayloadEvent.Context ctx) {
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.network;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
|
||||
import net.minecraft.network.protocol.login.custom.CustomQueryAnswerPayload;
|
||||
import net.minecraft.network.protocol.login.custom.CustomQueryPayload;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
@ApiStatus.Internal
|
||||
public record ForgePayload (
|
||||
ResourceLocation id,
|
||||
@Nullable
|
||||
FriendlyByteBuf data,
|
||||
Consumer<FriendlyByteBuf> encoder
|
||||
) implements CustomPacketPayload, CustomQueryPayload, CustomQueryAnswerPayload {
|
||||
public static ForgePayload create(ResourceLocation id, FriendlyByteBuf data) {
|
||||
return new ForgePayload(id, data, buf -> buf.writeBytes(data.slice()));
|
||||
}
|
||||
|
||||
public static ForgePayload create(ResourceLocation id, Consumer<FriendlyByteBuf> encoder) {
|
||||
return new ForgePayload(id, null, encoder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(FriendlyByteBuf buf) {
|
||||
encoder.accept(buf);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type<? extends CustomPacketPayload> type() {
|
||||
return new Type<>(this.id());
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.network;
|
||||
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
public interface ICustomPacket<T extends Packet<?>> {
|
||||
/**
|
||||
* Returns a unsafe reference to this packet's internal data.
|
||||
* Any modifications to this buffer will be reflected in the main buffer.
|
||||
*/
|
||||
@Nullable
|
||||
FriendlyByteBuf getInternalData();
|
||||
|
||||
ResourceLocation getName();
|
||||
|
||||
int getIndex();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
default T getThis() {
|
||||
return (T)this;
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ package net.minecraftforge.network;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.network.protocol.PacketFlow;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.network.PacketListener;
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf;
|
||||
|
||||
public record NetworkDirection<B extends FriendlyByteBuf>(NetworkProtocol<B> protocol, PacketFlow direction) {
|
||||
@@ -18,7 +19,7 @@ public record NetworkDirection<B extends FriendlyByteBuf>(NetworkProtocol<B> pro
|
||||
public static final NetworkDirection<FriendlyByteBuf> CONFIGURATION_TO_CLIENT = new NetworkDirection<>(NetworkProtocol.CONFIGURATION, PacketFlow.CLIENTBOUND);
|
||||
public static final NetworkDirection<FriendlyByteBuf> CONFIGURATION_TO_SERVER = new NetworkDirection<>(NetworkProtocol.CONFIGURATION, PacketFlow.SERVERBOUND);
|
||||
|
||||
public <T extends Packet<?>, MSG> ICustomPacket<T> buildPacket(Channel<MSG> channel, MSG packet) {
|
||||
public <T extends PacketListener, MSG> Packet<T> buildPacket(Channel<MSG> channel, MSG packet) {
|
||||
return this.protocol().buildPacket(this.direction(), channel, packet);
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,7 @@ public final class NetworkInitialization {
|
||||
private static final Marker MARKER = MarkerManager.getMarker("FORGE_NETWORK");
|
||||
public static final ResourceLocation LOGIN_NAME = new ResourceLocation("forge", "login");
|
||||
private static final ResourceLocation HANDSHAKE_NAME = new ResourceLocation("forge", "handshake");
|
||||
private static final ResourceLocation PLAY_NAME = new ResourceLocation("forge", "network");
|
||||
//private static final ResourceLocation PLAY_NAME = new ResourceLocation("forge", "network");
|
||||
public static final AttributeKey<ForgePacketHandler> CONTEXT = AttributeKey.newInstance(HANDSHAKE_NAME.toString());
|
||||
|
||||
public static final SimpleChannel LOGIN = ChannelBuilder
|
||||
@@ -62,7 +62,7 @@ public final class NetworkInitialization {
|
||||
public static final SimpleChannel PLAY = CONFIG;;
|
||||
|
||||
public static void init() {
|
||||
for (var channel : new Channel[]{ LOGIN, CONFIG, PLAY, ChannelListManager.REGISTER, ChannelListManager.UNREGISTER})
|
||||
for (var channel : new Channel[]{ LOGIN, CONFIG, PLAY, ChannelListManager.CHANNEL})
|
||||
LOGGER.debug(MARKER, "Registering Network {} v{}", channel.getName(), channel.getProtocolVersion());
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,9 @@ import net.minecraftforge.eventbus.api.IEventBus;
|
||||
import net.minecraftforge.eventbus.api.IEventListener;
|
||||
import net.minecraftforge.network.Channel.VersionTest;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
@@ -38,6 +40,7 @@ public final class NetworkInstance {
|
||||
final Map<AttributeKey<?>, Function<Connection, ?>> attributes;
|
||||
final Consumer<Connection> channelHandler;
|
||||
final ServerStatusPing.ChannelData pingData;
|
||||
private final Set<ResourceLocation> ids = new HashSet<>();
|
||||
|
||||
NetworkInstance(ResourceLocation channelName, int networkProtocolVersion,
|
||||
VersionTest clientAcceptedVersions, VersionTest serverAcceptedVersions,
|
||||
@@ -73,6 +76,16 @@ public final class NetworkInstance {
|
||||
return event.getSource().getPacketHandled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers another name that will have its CustomPayloadEvents redirected to this channel.
|
||||
* Like the main name, this must be unique across all channels.
|
||||
*/
|
||||
public NetworkInstance addChild(ResourceLocation name) {
|
||||
NetworkRegistry.register(this, name);
|
||||
this.ids.add(name);
|
||||
return this;
|
||||
}
|
||||
|
||||
ResourceLocation getChannelName() {
|
||||
return channelName;
|
||||
}
|
||||
@@ -81,7 +94,12 @@ public final class NetworkInstance {
|
||||
return networkProtocolVersion;
|
||||
}
|
||||
|
||||
void registrationChange(boolean registered) {
|
||||
void registrationChange(ResourceLocation name, boolean registered) {
|
||||
// TODO: Expose to listeners?
|
||||
}
|
||||
|
||||
boolean isRemotePresent(Connection con) {
|
||||
var channels = NetworkContext.get(con).getRemoteChannels();
|
||||
return channels.containsAll(ids);
|
||||
}
|
||||
}
|
||||
@@ -9,17 +9,16 @@ import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.network.protocol.PacketFlow;
|
||||
import net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket;
|
||||
import net.minecraft.network.protocol.common.ServerboundCustomPayloadPacket;
|
||||
import net.minecraft.network.protocol.common.custom.DiscardedPayload;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import net.minecraft.network.ConnectionProtocol;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.network.PacketListener;
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf;
|
||||
import net.minecraft.network.protocol.login.ClientboundCustomQueryPacket;
|
||||
import net.minecraft.network.protocol.login.ServerboundCustomQueryAnswerPacket;
|
||||
import net.minecraft.network.protocol.login.custom.DiscardedQueryAnswerPayload;
|
||||
import net.minecraft.network.protocol.login.custom.DiscardedQueryPayload;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
public class NetworkProtocol<B extends FriendlyByteBuf> {
|
||||
public static final NetworkProtocol<RegistryFriendlyByteBuf> PLAY = new NetworkProtocol<>(ConnectionProtocol.PLAY);
|
||||
@@ -41,23 +40,26 @@ public class NetworkProtocol<B extends FriendlyByteBuf> {
|
||||
return this.protocol;
|
||||
}
|
||||
|
||||
public <T extends PacketListener, MSG> Packet<T> buildPacket(PacketFlow direction, Channel<MSG> channel, MSG packet) {
|
||||
return buildPacket(direction, channel.getName(packet), buf -> channel.encode(buf, packet));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends Packet<?>, MSG> ICustomPacket<T> buildPacket(PacketFlow direction, Channel<MSG> channel, MSG packet) {
|
||||
var name = channel.getName();
|
||||
var encoder = (Consumer<FriendlyByteBuf>)(buf -> channel.encode(buf, packet));
|
||||
public <T extends PacketListener> Packet<T> buildPacket(PacketFlow direction, ResourceLocation name, Consumer<B> encoder) {
|
||||
var payload = ForgePayload.create(name, (Consumer<FriendlyByteBuf>)encoder);
|
||||
|
||||
switch (this.protocol) {
|
||||
case PLAY, CONFIGURATION:
|
||||
if (direction == PacketFlow.CLIENTBOUND)
|
||||
return (ICustomPacket<T>)new ClientboundCustomPayloadPacket(new DiscardedPayload(name, null, encoder));
|
||||
return (Packet<T>)new ClientboundCustomPayloadPacket(payload);
|
||||
else
|
||||
return (ICustomPacket<T>)new ServerboundCustomPayloadPacket(new DiscardedPayload(name, null, encoder));
|
||||
return (Packet<T>)new ServerboundCustomPayloadPacket(payload);
|
||||
|
||||
case LOGIN:
|
||||
if (direction == PacketFlow.CLIENTBOUND)
|
||||
return (ICustomPacket<T>)new ClientboundCustomQueryPacket(0, new DiscardedQueryPayload(name, null, encoder));
|
||||
return (Packet<T>)new ClientboundCustomQueryPacket(0, payload);
|
||||
else
|
||||
return (ICustomPacket<T>)new ServerboundCustomQueryAnswerPacket(0, new DiscardedQueryAnswerPayload(null, encoder));
|
||||
return (Packet<T>)new ServerboundCustomQueryAnswerPacket(0, payload);
|
||||
|
||||
default:
|
||||
throw new IllegalArgumentException("Invalid protocol, shouldn't be possible as this is a private class..");
|
||||
|
||||
@@ -41,25 +41,28 @@ public class NetworkRegistry {
|
||||
static final Logger LOGGER = LogManager.getLogger();
|
||||
static final Marker NETREGISTRY = MarkerManager.getMarker("NETREGISTRY");
|
||||
|
||||
static Map<ResourceLocation, NetworkInstance> instances = Collections.synchronizedMap(new HashMap<>());
|
||||
private static Map<ResourceLocation, NetworkInstance> instances = Collections.synchronizedMap(new HashMap<>());
|
||||
private static Map<ResourceLocation, NetworkInstance> byName = Collections.synchronizedMap(new HashMap<>());
|
||||
|
||||
public static boolean acceptsVanillaClientConnections() {
|
||||
return (instances.isEmpty() || listRejectedVanillaMods(n -> n.clientAcceptedVersions).isEmpty()) && DataPackRegistriesHooks.getSyncedCustomRegistries().isEmpty();
|
||||
return listRejectedVanillaMods(n -> n.clientAcceptedVersions).isEmpty() && DataPackRegistriesHooks.getSyncedCustomRegistries().isEmpty();
|
||||
}
|
||||
|
||||
public static boolean canConnectToVanillaServer() {
|
||||
return instances.isEmpty() || listRejectedVanillaMods(n -> n.serverAcceptedVersions).isEmpty();
|
||||
return listRejectedVanillaMods(n -> n.serverAcceptedVersions).isEmpty();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static NetworkInstance findTarget(ResourceLocation resourceLocation) {
|
||||
return instances.get(resourceLocation);
|
||||
return byName.get(resourceLocation);
|
||||
}
|
||||
|
||||
static Map<ResourceLocation, ServerStatusPing.ChannelData> buildChannelVersionsForListPing() {
|
||||
return instances.entrySet().stream()
|
||||
.filter(p -> !p.getKey().getNamespace().equals("forge"))
|
||||
.collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue().pingData));
|
||||
var ret = new HashMap<ResourceLocation, ServerStatusPing.ChannelData>();
|
||||
for (var channel : instances.values()) {
|
||||
ret.put(channel.getChannelName(), channel.pingData);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static List<String> listRejectedVanillaMods(Function<NetworkInstance, VersionTest> testFunction) {
|
||||
@@ -86,7 +89,8 @@ public class NetworkRegistry {
|
||||
|
||||
Set<ResourceLocation> missing = new HashSet<>();
|
||||
Map<ResourceLocation, NetworkMismatchData.Version> results = new HashMap<>();
|
||||
instances.forEach((name, net) -> {
|
||||
for (var net : instances.values()) {
|
||||
var name = net.getChannelName();
|
||||
VersionTest test = fromClient ? net.clientAcceptedVersions : net.serverAcceptedVersions;
|
||||
|
||||
var status = VersionTest.Status.MISSING;
|
||||
@@ -105,7 +109,7 @@ public class NetworkRegistry {
|
||||
else
|
||||
results.put(name, new NetworkMismatchData.Version(Integer.toString(version), Integer.toString(net.getNetworkProtocolVersion())));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!results.isEmpty() || !missing.isEmpty()) {
|
||||
LOGGER.error(NETREGISTRY, "Channels [{}] rejected their {} side version number",
|
||||
@@ -176,7 +180,45 @@ public class NetworkRegistry {
|
||||
|
||||
public static Map<ResourceLocation, Integer> buildChannelVersions() {
|
||||
var ret = new Object2IntOpenHashMap<ResourceLocation>(instances.size());
|
||||
instances.forEach((k, v) -> ret.put(k, v.getNetworkProtocolVersion()));
|
||||
for (var net : instances.values()) {
|
||||
ret.put(net.getChannelName(), net.getNetworkProtocolVersion());
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static List<ResourceLocation> buildRegisterList() {
|
||||
var ret = new ArrayList<ResourceLocation>(byName.keySet().size());
|
||||
for (var name : byName.keySet())
|
||||
if (!"minecraft".equals(name.getNamespace()))
|
||||
ret.add(name);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void register(NetworkInstance instance, ResourceLocation name) {
|
||||
checkLock(instance);
|
||||
if (NetworkRegistry.byName.containsKey(name))
|
||||
error("Payload name " + name + " already registered.");
|
||||
|
||||
byName.put(name, instance);
|
||||
}
|
||||
|
||||
static void register(NetworkInstance instance) {
|
||||
checkLock(instance);
|
||||
|
||||
var name = instance.getChannelName();
|
||||
if (NetworkRegistry.instances.containsKey(name))
|
||||
error("Channel " + name + " already registered.");
|
||||
|
||||
instances.put(name, instance);
|
||||
}
|
||||
|
||||
private static void checkLock(NetworkInstance instance) {
|
||||
if (NetworkRegistry.lock)
|
||||
error("Attempted to register channel " + instance.getChannelName() + " even though registry phase is over");
|
||||
}
|
||||
|
||||
private static void error(String message) {
|
||||
NetworkRegistry.LOGGER.error(NetworkRegistry.NETREGISTRY, message);
|
||||
throw new IllegalArgumentException(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,241 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.network;
|
||||
|
||||
import io.netty.util.AttributeKey;
|
||||
import net.minecraft.network.Connection;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.network.codec.StreamCodec;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.network.protocol.PacketFlow;
|
||||
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
|
||||
import net.minecraft.network.protocol.common.custom.CustomPacketPayload.Type;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraftforge.event.network.CustomPayloadEvent;
|
||||
import net.minecraftforge.event.network.CustomPayloadEvent.Context;
|
||||
import net.minecraftforge.network.simple.handler.SimplePacket;
|
||||
import net.minecraftforge.network.payload.PayloadConnection;
|
||||
import net.minecraftforge.network.payload.PayloadFlow;
|
||||
import net.minecraftforge.network.payload.PayloadProtocol;
|
||||
import net.minecraftforge.network.payload.handler.PayloadHandlerFlow;
|
||||
import net.minecraftforge.network.payload.handler.PayloadHandlerProtocol;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.apache.logging.log4j.Marker;
|
||||
import org.apache.logging.log4j.MarkerManager;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
final class PayloadChannel extends Channel<CustomPacketPayload> {
|
||||
private static final Logger LOGGER = LogManager.getLogger();
|
||||
private static final Marker MARKER = MarkerManager.getMarker("PAYLOAD_CHANNEL");
|
||||
private final Map<ResourceLocation, Message<?, ?>> payloads;
|
||||
|
||||
@ApiStatus.Internal
|
||||
private PayloadChannel(Builder builder) {
|
||||
super(builder.instance);
|
||||
builder.instance.addListener(this::onPacketReceived);
|
||||
this.payloads = Collections.unmodifiableMap(builder.payloads);
|
||||
}
|
||||
|
||||
static PayloadConnection<CustomPacketPayload> builder(NetworkInstance instance) {
|
||||
return new Builder(instance);
|
||||
}
|
||||
|
||||
private final static class Builder implements PayloadConnection<CustomPacketPayload> {
|
||||
private final NetworkInstance instance;
|
||||
private boolean built = false;
|
||||
private final Map<ResourceLocation, Message<?, ?>> payloads = new HashMap<>();
|
||||
|
||||
private Builder(NetworkInstance instance) {
|
||||
this.instance = instance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <NEWBUF extends FriendlyByteBuf, NEWBASE extends CustomPacketPayload> PayloadProtocol<NEWBUF, NEWBASE> protocol(NetworkProtocol<NEWBUF> protocol) {
|
||||
return new Protocol<>(new BuilderContext<NEWBUF, NEWBASE>(this).protocol(protocol));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <NEWBUF extends FriendlyByteBuf, CTX, NEWBASE extends CustomPacketPayload & SimplePacket<CTX>> PayloadHandlerProtocol<NEWBUF, NEWBASE> protocol(AttributeKey<CTX> context, NetworkProtocol<NEWBUF> protocol) {
|
||||
return new HandlerProtocol<>(context, new BuilderContext<NEWBUF, NEWBASE>(this).protocol(protocol));
|
||||
}
|
||||
|
||||
private void add(Message<?, ?> msg) {
|
||||
if (this.payloads.put(msg.type().id(), msg) != null)
|
||||
throw new IllegalArgumentException("Packet with type " + msg.type().id() + " already registered");
|
||||
|
||||
this.instance.addChild(msg.type().id());
|
||||
}
|
||||
|
||||
private void checkBuilt() {
|
||||
if (built)
|
||||
throw new IllegalStateException("PayloadChannel builder is fully built, can not modify it any more");
|
||||
}
|
||||
|
||||
public PayloadChannel build() {
|
||||
checkBuilt();
|
||||
return new PayloadChannel(this);
|
||||
}
|
||||
|
||||
private record BuilderContext<BUF extends FriendlyByteBuf, BASE>(Builder builder, @Nullable NetworkProtocol<BUF> protocol, @Nullable PacketFlow flow) {
|
||||
public BuilderContext(Builder builder) {
|
||||
this(builder, null, null);
|
||||
}
|
||||
|
||||
public <NEWBUF extends FriendlyByteBuf> BuilderContext<NEWBUF, BASE> protocol(NetworkProtocol<NEWBUF> protocol) {
|
||||
return new BuilderContext<>(builder, protocol, flow);
|
||||
}
|
||||
|
||||
private BuilderContext<BUF, BASE> flow(@Nullable PacketFlow flow) {
|
||||
return new BuilderContext<>(builder, protocol, flow);
|
||||
}
|
||||
}
|
||||
private record Protocol<BUF extends FriendlyByteBuf, BASE extends CustomPacketPayload>(BuilderContext<BUF, BASE> ctx) implements ProtocolFactory<BUF, BASE>, PayloadProtocol<BUF, BASE> {
|
||||
@Override
|
||||
public PayloadFlow<BUF, BASE> flow(@Nullable PacketFlow flow) {
|
||||
return new Flow<>(ctx.flow(flow));
|
||||
}
|
||||
}
|
||||
|
||||
private record HandlerProtocol<BUF extends FriendlyByteBuf, CTX, BASE extends CustomPacketPayload & SimplePacket<CTX>>(
|
||||
AttributeKey<CTX> key,
|
||||
BuilderContext<BUF, BASE> ctx
|
||||
) implements ProtocolFactory<BUF, BASE>, PayloadHandlerProtocol<BUF, BASE> {
|
||||
@Override
|
||||
public PayloadHandlerFlow<BUF, BASE> flow(@Nullable PacketFlow flow) {
|
||||
return new HandlerFlow<>(key, ctx.flow(flow));
|
||||
}
|
||||
}
|
||||
|
||||
private record Flow<BUF extends FriendlyByteBuf, BASE extends CustomPacketPayload>(BuilderContext<BUF, BASE> ctx) implements ProtocolFactory<BUF, BASE>, PayloadFlow<BUF, BASE> {
|
||||
@Override
|
||||
public <MSG extends BASE> Flow<BUF, BASE> add(Type<MSG> type, StreamCodec<BUF, MSG> codec, BiConsumer<MSG, Context> consumer) {
|
||||
ctx.builder().add(new Message<>(type, ctx.protocol(), ctx.flow(), codec, consumer));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PayloadFlow<BUF, BASE> flow(@Nullable PacketFlow flow) {
|
||||
return new Flow<>(ctx.flow(flow));
|
||||
}
|
||||
}
|
||||
|
||||
private record HandlerFlow<BUF extends FriendlyByteBuf, CTX, BASE extends CustomPacketPayload & SimplePacket<CTX>>(
|
||||
AttributeKey<CTX> key,
|
||||
BuilderContext<BUF, BASE> ctx
|
||||
) implements ProtocolFactory<BUF, BASE>, PayloadHandlerFlow<BUF, BASE> {
|
||||
@Override
|
||||
public <MSG extends BASE> PayloadHandlerFlow<BUF, BASE> add(Type<MSG> type, StreamCodec<BUF, MSG> codec) {
|
||||
return this.add(type, codec, (msg, ctx) -> {
|
||||
var inst = ctx.getConnection().channel().attr(key).get();
|
||||
ctx.setPacketHandled(msg.handle(inst, ctx));
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public <MSG extends BASE> PayloadHandlerFlow<BUF, BASE> addMain(Type<MSG> type, StreamCodec<BUF, MSG> codec) {
|
||||
return this.add(type, codec, (msg, ctx) -> {
|
||||
ctx.enqueueWork(() -> {
|
||||
var inst = ctx.getConnection().channel().attr(key).get();
|
||||
msg.handle(inst, ctx);
|
||||
});
|
||||
ctx.setPacketHandled(true);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public <MSG extends BASE> PayloadHandlerFlow<BUF, BASE> add(Type<MSG> type, StreamCodec<BUF, MSG> codec, BiConsumer<MSG, Context> consumer) {
|
||||
ctx.builder().add(new Message<>(type, ctx.protocol(), ctx.flow(), codec, consumer));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PayloadHandlerFlow<BUF, BASE> flow(@Nullable PacketFlow flow) {
|
||||
return new HandlerFlow<>(key, ctx.flow(flow));
|
||||
}
|
||||
}
|
||||
|
||||
private interface ProtocolFactory<BUF extends FriendlyByteBuf, BASE extends CustomPacketPayload> extends PayloadConnection<BASE>, ChannelBuildable<CustomPacketPayload> {
|
||||
BuilderContext<BUF, BASE> ctx();
|
||||
|
||||
@Override
|
||||
default <NEWBUF extends FriendlyByteBuf, NEWBASE extends CustomPacketPayload> PayloadProtocol<NEWBUF, NEWBASE> protocol(NetworkProtocol<NEWBUF> protocol) {
|
||||
return ctx().builder.protocol(protocol);
|
||||
}
|
||||
|
||||
@Override
|
||||
default <NEWBUF extends FriendlyByteBuf, CTX, NEWBASE extends SimplePacket<CTX> & CustomPacketPayload> PayloadHandlerProtocol<NEWBUF, NEWBASE> protocol(AttributeKey<CTX> context, NetworkProtocol<NEWBUF> protocol) {
|
||||
return ctx().builder.protocol(context, protocol);
|
||||
}
|
||||
|
||||
@Override
|
||||
default Channel<CustomPacketPayload> build() {
|
||||
return ctx().builder.build();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private record Message<MSG extends CustomPacketPayload, BUF extends FriendlyByteBuf>(
|
||||
Type<MSG> type,
|
||||
@Nullable
|
||||
NetworkProtocol<BUF> protocol,
|
||||
@Nullable
|
||||
PacketFlow direction,
|
||||
StreamCodec<BUF, MSG> codec,
|
||||
BiConsumer<MSG, CustomPayloadEvent.Context> consumer
|
||||
){ };
|
||||
|
||||
private Message<CustomPacketPayload, FriendlyByteBuf> get(ResourceLocation type, boolean send) {
|
||||
@SuppressWarnings("unchecked")
|
||||
var msg = (Message<CustomPacketPayload, FriendlyByteBuf>)payloads.get(type);
|
||||
|
||||
if (msg == null)
|
||||
error((send ? "Attemped to send" : "Received") + " invalid message " + type + " on channel " + getName());
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
||||
private void error(String message) {
|
||||
LOGGER.error(MARKER, message);
|
||||
throw new IllegalArgumentException(message);
|
||||
}
|
||||
|
||||
private void onPacketReceived(CustomPayloadEvent event) {
|
||||
var data = event.getPayload();
|
||||
if (data == null)
|
||||
error("Received empty payload on channel " + getName() + " login index " + event.getLoginIndex() + " for payload type " + event.getChannel());
|
||||
|
||||
var msg = get(event.getChannel(), false);
|
||||
this.validate(event.getChannel(), event.getSource().getConnection(), msg.protocol(), msg.direction(), false);
|
||||
|
||||
var pkt = msg.codec().decode(data);
|
||||
msg.consumer().accept(pkt, event.getSource());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Packet<?> toVanillaPacket(Connection con, CustomPacketPayload message) {
|
||||
var msg = get(message.type().id(), true);
|
||||
this.validate(message.type().id(), con, msg.protocol(), msg.direction(), true);
|
||||
return super.toVanillaPacket(con, message);
|
||||
}
|
||||
|
||||
@Override
|
||||
ResourceLocation getName(CustomPacketPayload packet) {
|
||||
return packet.type().id();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(FriendlyByteBuf out, CustomPacketPayload message) {
|
||||
var msg = get(message.type().id(), true);
|
||||
msg.codec().encode(out, message);
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,6 @@ import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
|
||||
import net.minecraft.network.Connection;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.network.codec.StreamCodec;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.network.protocol.PacketFlow;
|
||||
@@ -505,6 +504,22 @@ public class SimpleChannel extends Channel<Object> implements SimpleConnection<O
|
||||
BiConsumer<MSG, CustomPayloadEvent.Context> consumer
|
||||
){ };
|
||||
|
||||
private Message<Object, FriendlyByteBuf> get(int id) {
|
||||
@SuppressWarnings("unchecked")
|
||||
var msg = (Message<Object, FriendlyByteBuf>)byId.get(id);
|
||||
if (msg == null)
|
||||
error("Received invalid discriminator " + id + " on channel " + getName());
|
||||
return msg;
|
||||
}
|
||||
|
||||
private Message<Object, FriendlyByteBuf> get(Object type) {
|
||||
@SuppressWarnings("unchecked")
|
||||
var msg = (Message<Object, FriendlyByteBuf>)byType.get(type.getClass());
|
||||
if (msg == null)
|
||||
error("Attemped to send invalid message " + type + " on channel " + getName());
|
||||
return msg;
|
||||
}
|
||||
|
||||
protected int nextIndex() {
|
||||
while (byId.containsKey(lastIndex)) {
|
||||
lastIndex++;
|
||||
@@ -521,74 +536,29 @@ public class SimpleChannel extends Channel<Object> implements SimpleConnection<O
|
||||
return;
|
||||
}
|
||||
|
||||
int id = data.readVarInt();
|
||||
@SuppressWarnings("unchecked")
|
||||
var msg = (Message<?, FriendlyByteBuf>)byId.get(id);
|
||||
if (msg == null) {
|
||||
LOGGER.error(MARKER, "Received invalid discriminator {} on channel {}", id, getName());
|
||||
return;
|
||||
}
|
||||
|
||||
var con = event.getSource().getConnection();
|
||||
var protocol = msg.protocol() == null ? con.getProtocol() : msg.protocol().toVanilla();
|
||||
var direction = msg.direction() == null ? con.getReceiving() : msg.direction();
|
||||
|
||||
if (protocol != con.getProtocol() || direction != con.getReceiving()) {
|
||||
var error = "Illegal packet received, terminating connection. " + msg.type().getName() + " expected " +
|
||||
direction.name() + " " + protocol.name() + " but was " +
|
||||
con.getReceiving().name() + " " + con.getProtocol().name();
|
||||
|
||||
LOGGER.error(MARKER, error);
|
||||
con.disconnect(Component.literal(error));
|
||||
throw new IllegalStateException(error);
|
||||
}
|
||||
|
||||
decodeAndDispatch(data, event.getSource(), msg);
|
||||
}
|
||||
|
||||
// Yay generics
|
||||
private static <MSG> void decodeAndDispatch(FriendlyByteBuf data, CustomPayloadEvent.Context ctx, Message<MSG, FriendlyByteBuf> msg) {
|
||||
var message = msg.decoder().apply(data);
|
||||
msg.consumer.accept(message, ctx);
|
||||
var msg = get(data.readVarInt());
|
||||
super.validate(msg.type.getName(), event.getSource().getConnection(), msg.protocol(), msg.direction(), false);
|
||||
var pkt = msg.decoder().apply(data);
|
||||
msg.consumer().accept(pkt, event.getSource());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Packet<?> toVanillaPacket(Connection con, Object message) {
|
||||
var msg = byType.get(message.getClass());
|
||||
|
||||
if (msg == null) {
|
||||
LOGGER.error(MARKER, "Attempted to send invalid message {} on channel {}", message.getClass().getName(), getName());
|
||||
throw new IllegalArgumentException("Invalid message " + message.getClass().getName());
|
||||
}
|
||||
|
||||
var protocol = msg.protocol() == null ? con.getProtocol() : msg.protocol().toVanilla();
|
||||
var direction = msg.direction() == null ? con.getSending() : msg.direction();
|
||||
|
||||
if (protocol != con.getProtocol() || direction != con.getSending()) {
|
||||
var error = "Illegal packet sent, terminating connection. " + msg.type().getName() + " expected " +
|
||||
direction.name() + " " + protocol.name() + " but was " +
|
||||
con.getSending().name() + " " + con.getProtocol().name();
|
||||
|
||||
LOGGER.error(MARKER, error);
|
||||
con.disconnect(Component.literal(error));
|
||||
throw new IllegalStateException(error);
|
||||
}
|
||||
|
||||
var msg = get(message);
|
||||
super.validate(message.getClass().getName(), con, msg.protocol(), msg.direction(), true);
|
||||
return super.toVanillaPacket(con, message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(FriendlyByteBuf out, Object message) {
|
||||
@SuppressWarnings("unchecked")
|
||||
var msg = (Message<Object, FriendlyByteBuf>)byType.get(message.getClass());
|
||||
|
||||
if (msg == null) {
|
||||
LOGGER.error(MARKER, "Attempted to send invalid message {} on channel {}", message.getClass().getName(), getName());
|
||||
throw new IllegalArgumentException("Invalid message " + message.getClass().getName());
|
||||
}
|
||||
|
||||
var msg = get(message);
|
||||
out.writeVarInt(msg.index());
|
||||
if (msg.encoder() != null)
|
||||
msg.encoder().accept(message, out);
|
||||
}
|
||||
|
||||
private void error(String message) {
|
||||
LOGGER.error(MARKER, message);
|
||||
throw new IllegalArgumentException(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.network.payload;
|
||||
|
||||
import io.netty.util.AttributeKey;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf;
|
||||
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
|
||||
import net.minecraftforge.network.NetworkProtocol;
|
||||
import net.minecraftforge.network.payload.handler.PayloadHandlerProtocol;
|
||||
import net.minecraftforge.network.simple.handler.SimplePacket;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public interface PayloadConnection<BASE extends CustomPacketPayload> {
|
||||
/**
|
||||
* Creates a builder grouping together all packets under the same protocol.
|
||||
* This will validate that the protocol matches before the packet is sent or received.
|
||||
*/
|
||||
<NEWBUF extends FriendlyByteBuf, NEWBASE extends CustomPacketPayload> PayloadProtocol<NEWBUF, NEWBASE> protocol(@Nullable NetworkProtocol<NEWBUF> protocol);
|
||||
|
||||
/**
|
||||
* Creates a builder grouping together all packets under the same protocol.
|
||||
* This will validate that the protocol matches before the packet is sent or received.
|
||||
*/
|
||||
<NEWBUF extends FriendlyByteBuf, CTX, NEWBASE extends SimplePacket<CTX> & CustomPacketPayload> PayloadHandlerProtocol<NEWBUF, NEWBASE> protocol(AttributeKey<CTX> context, @Nullable NetworkProtocol<NEWBUF> protocol);
|
||||
|
||||
/**
|
||||
* Consumer version of {@link #protocol(NetworkProtocol)}. The Consumer will immediately be called with the created protocol.
|
||||
*/
|
||||
default <BUF extends FriendlyByteBuf> PayloadConnection<BASE> protocol(@Nullable NetworkProtocol<BUF> protocol, Consumer<PayloadProtocol<BUF, BASE>> consumer) {
|
||||
var tmp = this.<BUF, BASE>protocol(protocol);
|
||||
consumer.accept(tmp);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Consumer version of {@link #protocol(AttributeKey,NetworkProtocol)}. The Consumer will immediately be called with the created protocol.
|
||||
*/
|
||||
default <BUF extends FriendlyByteBuf, CTX, NEWBASE extends SimplePacket<CTX> & CustomPacketPayload> PayloadConnection<BASE> protocol(AttributeKey<CTX> context, @Nullable NetworkProtocol<BUF> protocol, Consumer<PayloadHandlerProtocol<BUF, NEWBASE>> consumer) {
|
||||
var tmp = this.<BUF, CTX, NEWBASE>protocol(context, protocol);
|
||||
consumer.accept(tmp);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a builder grouping together packets that are only valid when under the Configuration protocol.
|
||||
* This will validate that the protocol matches before the packet is sent or received.
|
||||
*/
|
||||
default PayloadProtocol<FriendlyByteBuf, BASE> configuration() {
|
||||
return protocol(NetworkProtocol.CONFIGURATION);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a builder grouping together packets that are only valid when under the Configuration protocol.
|
||||
* This will validate that the protocol matches before the packet is sent or received.
|
||||
*/
|
||||
default <CTX, NEWBASE extends SimplePacket<CTX> & CustomPacketPayload> PayloadHandlerProtocol<FriendlyByteBuf, NEWBASE> configuration(AttributeKey<CTX> context) {
|
||||
return protocol(context, NetworkProtocol.CONFIGURATION);
|
||||
}
|
||||
|
||||
/**
|
||||
* Consumer version of {@link #configuration()}. The Consumer will immediately be called with the created protocol.
|
||||
*/
|
||||
default PayloadConnection<BASE> configuration(Consumer<PayloadProtocol<FriendlyByteBuf, BASE>> consumer) {
|
||||
return protocol(NetworkProtocol.CONFIGURATION, consumer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Consumer version of {@link #configuration(AttributeKey)}. The Consumer will immediately be called with the created protocol.
|
||||
*/
|
||||
default <CTX, NEWBASE extends SimplePacket<CTX> & CustomPacketPayload> PayloadConnection<BASE> configuration(AttributeKey<CTX> context, Consumer<PayloadHandlerProtocol<FriendlyByteBuf, NEWBASE>> consumer) {
|
||||
return protocol(context, NetworkProtocol.CONFIGURATION, consumer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a builder grouping together packets that are only valid when under the Login protocol.
|
||||
* This will validate that the protocol matches before the packet is sent or received.
|
||||
*/
|
||||
default PayloadProtocol<FriendlyByteBuf, BASE> login() {
|
||||
return protocol(NetworkProtocol.LOGIN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a builder grouping together packets that are only valid when under the Login protocol.
|
||||
* This will validate that the protocol matches before the packet is sent or received.
|
||||
*/
|
||||
default <CTX, NEWBASE extends SimplePacket<CTX> & CustomPacketPayload> PayloadHandlerProtocol<FriendlyByteBuf, NEWBASE> login(AttributeKey<CTX> context) {
|
||||
return protocol(context, NetworkProtocol.LOGIN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Consumer version of {@link #login()}. The Consumer will immediately be called with the created protocol.
|
||||
*/
|
||||
default PayloadConnection<BASE> login(Consumer<PayloadProtocol<FriendlyByteBuf, BASE>> consumer) {
|
||||
return protocol(NetworkProtocol.LOGIN, consumer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Consumer version of {@link #login(AttributeKey)}. The Consumer will immediately be called with the created protocol.
|
||||
*/
|
||||
default <CTX, NEWBASE extends SimplePacket<CTX> & CustomPacketPayload> PayloadConnection<BASE> login(AttributeKey<CTX> context, Consumer<PayloadHandlerProtocol<FriendlyByteBuf, NEWBASE>> consumer) {
|
||||
return protocol(context, NetworkProtocol.LOGIN, consumer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a builder grouping together packets that are only valid when under the Play protocol.
|
||||
* This will validate that the protocol matches before the packet is sent or received.
|
||||
*/
|
||||
default PayloadProtocol<RegistryFriendlyByteBuf, BASE> play() {
|
||||
return protocol(NetworkProtocol.PLAY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a builder grouping together packets that are only valid when under the Login protocol.
|
||||
* This will validate that the protocol matches before the packet is sent or received.
|
||||
*/
|
||||
default <CTX, NEWBASE extends SimplePacket<CTX> & CustomPacketPayload> PayloadHandlerProtocol<RegistryFriendlyByteBuf, NEWBASE> play(AttributeKey<CTX> context) {
|
||||
return protocol(context, NetworkProtocol.PLAY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Consumer version of {@link #play()}. The Consumer will immediately be called with the created protocol.
|
||||
*/
|
||||
default PayloadConnection<BASE> play(Consumer<PayloadProtocol<RegistryFriendlyByteBuf, BASE>> consumer) {
|
||||
return protocol(NetworkProtocol.PLAY, consumer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Consumer version of {@link #play(AttributeKey)}. The Consumer will immediately be called with the created protocol.
|
||||
*/
|
||||
default <CTX, NEWBASE extends SimplePacket<CTX> & CustomPacketPayload> PayloadConnection<BASE> play(AttributeKey<CTX> context, Consumer<PayloadHandlerProtocol<RegistryFriendlyByteBuf, NEWBASE>> consumer) {
|
||||
return protocol(context, NetworkProtocol.PLAY, consumer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a builder grouping together packets that are valid under any protocol.
|
||||
* It is not recommended to do this, instead you should use one of the other methods in this class to make sure your packets have basic validation.
|
||||
*/
|
||||
default PayloadProtocol<FriendlyByteBuf, BASE> any() {
|
||||
return protocol(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a builder grouping together packets that are valid under any protocol.
|
||||
* It is not recommended to do this, instead you should use one of the other methods in this class to make sure your packets have basic validation.
|
||||
*/
|
||||
default <CTX, NEWBASE extends SimplePacket<CTX> & CustomPacketPayload> PayloadHandlerProtocol<FriendlyByteBuf, NEWBASE> any(AttributeKey<CTX> context) {
|
||||
return protocol(context, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Consumer version of {@link #any()}. The Consumer will immediately be called with the created protocol.
|
||||
* It is not recommended to do this, instead you should use one of the other methods in this class to make sure your packets have basic validation.
|
||||
*/
|
||||
default PayloadConnection<BASE> any(Consumer<PayloadProtocol<FriendlyByteBuf, BASE>> consumer) {
|
||||
return protocol(null, consumer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Consumer version of {@link #any(AttributeKey)}. The Consumer will immediately be called with the created protocol.
|
||||
* It is not recommended to do this, instead you should use one of the other methods in this class to make sure your packets have basic validation.
|
||||
*/
|
||||
default <CTX, NEWBASE extends SimplePacket<CTX> & CustomPacketPayload> PayloadConnection<BASE> any(AttributeKey<CTX> context, Consumer<PayloadHandlerProtocol<FriendlyByteBuf, NEWBASE>> consumer) {
|
||||
return protocol(context, null, consumer);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.network.payload;
|
||||
|
||||
import java.util.function.BiConsumer;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.network.codec.StreamCodec;
|
||||
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
|
||||
import net.minecraft.network.protocol.common.custom.CustomPacketPayload.Type;
|
||||
import net.minecraftforge.event.network.CustomPayloadEvent;
|
||||
import net.minecraftforge.event.network.CustomPayloadEvent.Context;
|
||||
import net.minecraftforge.network.ChannelBuildable;
|
||||
|
||||
public interface PayloadFlow<BUF extends FriendlyByteBuf, BASE extends CustomPacketPayload> extends PayloadProtocol<BUF, BASE>, ChannelBuildable<CustomPacketPayload> {
|
||||
/**
|
||||
* Adds a packet to this channel that has it's protocol validated whenever sent or received.
|
||||
* <p>
|
||||
* The handler is called on the network thread, and so should not interact with most game state by default.
|
||||
* {@link CustomPayloadEvent.Context#enqueueWork(Runnable)} can be used to handle the message on the main server or
|
||||
* client thread.
|
||||
*/
|
||||
<MSG extends BASE> PayloadFlow<BUF, BASE> add(Type<MSG> type, StreamCodec<BUF, MSG> codec, BiConsumer<MSG, Context> handler);
|
||||
|
||||
/**
|
||||
* Adds a packet to this channel that has it's protocol validated whenever sent or received.
|
||||
* <p>
|
||||
* Unlike {@link #add(Class,StreamCodec,BiConsumer)}, the consumer is called on the main thread, and so can
|
||||
* interact with most game state by default.
|
||||
*/
|
||||
default <MSG extends BASE> PayloadFlow<BUF, BASE> addMain(Type<MSG> type, StreamCodec<BUF, MSG> codec, BiConsumer<MSG, Context> handler) {
|
||||
return add(type, codec, (msg, ctx) -> {
|
||||
ctx.enqueueWork(() -> handler.accept(msg, ctx));
|
||||
ctx.setPacketHandled(true);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.network.payload;
|
||||
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
|
||||
import net.minecraftforge.network.simple.BaseProtocol;
|
||||
|
||||
/**
|
||||
* Entry point for a {@link net.minecraftforge.network.PayloadChannel PayloadChannel} protocol without any extra context handler marshaling.
|
||||
*/
|
||||
public interface PayloadProtocol<BUF extends FriendlyByteBuf, BASE extends CustomPacketPayload> extends BaseProtocol<PayloadFlow<BUF, BASE>, PayloadProtocol<BUF, BASE>>, PayloadConnection<BASE> {
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.network.payload.handler;
|
||||
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.network.codec.StreamCodec;
|
||||
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
|
||||
import net.minecraft.network.protocol.common.custom.CustomPacketPayload.Type;
|
||||
import net.minecraftforge.event.network.CustomPayloadEvent;
|
||||
import net.minecraftforge.network.ChannelBuildable;
|
||||
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
public interface PayloadHandlerFlow<BUF extends FriendlyByteBuf, BASE extends CustomPacketPayload> extends PayloadHandlerProtocol<BUF, BASE>, ChannelBuildable<CustomPacketPayload> {
|
||||
/**
|
||||
* Adds a packet to this channel that has it's protocol validated whenever sent or received.
|
||||
* <p>
|
||||
* The handler is called on the network thread, and so should not interact with most game state by default.
|
||||
* {@link CustomPayloadEvent.Context#enqueueWork(Runnable)} can be used to handle the message on the main server or
|
||||
* client thread. Alternatively one can use {@link #addMain(Class, StreamCodec)} to run the handler on the
|
||||
* main thread.
|
||||
*/
|
||||
<MSG extends BASE> PayloadHandlerFlow<BUF, BASE> add(Type<MSG> type, StreamCodec<BUF, MSG> codec);
|
||||
|
||||
/**
|
||||
* Adds a packet to this channel that has it's protocol validated whenever sent or received.
|
||||
* <p>
|
||||
* Unlike {@link #add(Type,StreamCodec)}, the consumer is called on the main thread, and so can
|
||||
* interact with most game state by default.
|
||||
*/
|
||||
<MSG extends BASE> PayloadHandlerFlow<BUF, BASE> addMain(Type<MSG> type, StreamCodec<BUF, MSG> codec);
|
||||
|
||||
/**
|
||||
* Adds a packet to this channel that has it's protocol validated whenever sent or received.
|
||||
* <p>
|
||||
* The handler is called on the network thread, and so should not interact with most game state by default.
|
||||
* {@link CustomPayloadEvent.Context#enqueueWork(Runnable)} can be used to handle the message on the main server or
|
||||
* client thread.
|
||||
*/
|
||||
<MSG extends BASE> PayloadHandlerFlow<BUF, BASE> add(Type<MSG> type, StreamCodec<BUF, MSG> codec, BiConsumer<MSG, CustomPayloadEvent.Context> handler);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.network.payload.handler;
|
||||
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
|
||||
import net.minecraftforge.network.payload.PayloadConnection;
|
||||
import net.minecraftforge.network.simple.BaseProtocol;
|
||||
|
||||
/**
|
||||
* Entry point for a {@link net.minecraftforge.network.PayloadChannel PayloadChannel} protocol that mimics vanilla's
|
||||
* {@link net.minecraft.network.protocol.Packet Packet} system where every packet must implement
|
||||
* {@link SimplePacket} and will have their {@link SimplePacket#handle(Object, net.minecraftforge.event.network.CustomPayloadEvent.Context) handle(CTX, Context)}
|
||||
* method called.
|
||||
*/
|
||||
public interface PayloadHandlerProtocol<BUF extends FriendlyByteBuf, BASE extends CustomPacketPayload> extends BaseProtocol<PayloadHandlerFlow<BUF, BASE>, PayloadHandlerProtocol<BUF, BASE>>, PayloadConnection<BASE> {
|
||||
}
|
||||
@@ -5,12 +5,14 @@
|
||||
|
||||
package net.minecraftforge.network.simple;
|
||||
|
||||
import net.minecraftforge.network.ChannelBuildable;
|
||||
import net.minecraftforge.network.SimpleChannel;
|
||||
|
||||
public interface SimpleBuildable {
|
||||
public interface SimpleBuildable extends ChannelBuildable<Object> {
|
||||
/*
|
||||
* This will build the entire channel, locking any future modifications from happening.
|
||||
* This should be the LAST call in your builder chain and can only be called once.
|
||||
*/
|
||||
@Override
|
||||
SimpleChannel build();
|
||||
}
|
||||
@@ -135,4 +135,36 @@ public interface SimpleConnection<BASE> {
|
||||
default <CTX, NEWBASE extends SimplePacket<CTX>> SimpleConnection<BASE> play(AttributeKey<CTX> context, Consumer<SimpleHandlerProtocol<RegistryFriendlyByteBuf, NEWBASE>> consumer) {
|
||||
return protocol(context, NetworkProtocol.PLAY, consumer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a builder grouping together packets that are valid under any protocol.
|
||||
* It is not recommended to do this, instead you should use one of the other methods in this class to make sure your packets have basic validation.
|
||||
*/
|
||||
default SimpleProtocol<FriendlyByteBuf, BASE> any() {
|
||||
return protocol(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a builder grouping together packets that are valid under any protocol.
|
||||
* It is not recommended to do this, instead you should use one of the other methods in this class to make sure your packets have basic validation.
|
||||
*/
|
||||
default <CTX, NEWBASE extends SimplePacket<CTX>> SimpleHandlerProtocol<FriendlyByteBuf, NEWBASE> any(AttributeKey<CTX> context) {
|
||||
return protocol(context, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Consumer version of {@link #any()}. The Consumer will immediately be called with the created protocol.
|
||||
* It is not recommended to do this, instead you should use one of the other methods in this class to make sure your packets have basic validation.
|
||||
*/
|
||||
default SimpleConnection<BASE> any(Consumer<SimpleProtocol<FriendlyByteBuf, BASE>> consumer) {
|
||||
return protocol(null, consumer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Consumer version of {@link #any(AttributeKey)}. The Consumer will immediately be called with the created protocol.
|
||||
* It is not recommended to do this, instead you should use one of the other methods in this class to make sure your packets have basic validation.
|
||||
*/
|
||||
default <CTX, NEWBASE extends SimplePacket<CTX>> SimpleConnection<BASE> any(AttributeKey<CTX> context, Consumer<SimpleHandlerProtocol<FriendlyByteBuf, NEWBASE>> consumer) {
|
||||
return protocol(context, null, consumer);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user