9
0
mirror of https://github.com/Winds-Studio/Leaf.git synced 2025-12-25 09:59:15 +00:00

Do A Barrel Roll Protocol (#315)

* Do A Barrel Roll Protocol

* cleanup

* [ci skip] cleanup

* [ci skip] cleanup

* [ci skip] rename patch
This commit is contained in:
hayanesuru
2025-05-07 00:37:31 +09:00
committed by GitHub
parent 41664455a4
commit 974ede5f87
7 changed files with 686 additions and 0 deletions

View File

@@ -2,6 +2,8 @@ package org.dreeam.leaf.config.modules.network;
import org.dreeam.leaf.config.ConfigModules;
import org.dreeam.leaf.config.EnumConfigCategory;
import org.dreeam.leaf.protocol.DoABarrelRollPackets;
import org.dreeam.leaf.protocol.DoABarrelRollProtocol;
import java.util.concurrent.ThreadLocalRandom;
@@ -22,6 +24,13 @@ public class ProtocolSupport extends ConfigModules {
public static boolean syncmaticaQuota = false;
public static int syncmaticaQuotaLimit = 40000000;
public static boolean doABarrelRollProtocol = false;
public static boolean doABarrelRollAllowThrusting = false;
public static boolean doABarrelRollForceEnabled = false;
public static boolean doABarrelRollForceInstalled = false;
public static int doABarrelRollInstalledTimeout = 40;
public static DoABarrelRollPackets.KineticDamage doABarrelRollKineticDamage = DoABarrelRollPackets.KineticDamage.VANILLA;
@Override
public void onLoaded() {
jadeProtocol = config.getBoolean(getBasePath() + ".jade-protocol", jadeProtocol);
@@ -38,5 +47,26 @@ public class ProtocolSupport extends ConfigModules {
if (syncmaticaProtocol) {
org.leavesmc.leaves.protocol.syncmatica.SyncmaticaProtocol.init();
}
doABarrelRollProtocol = config.getBoolean(getBasePath() + ".do-a-barrel-roll-protocol", doABarrelRollProtocol);
doABarrelRollAllowThrusting = config.getBoolean(getBasePath() + ".do-a-barrel-roll-allow-thrusting", doABarrelRollAllowThrusting);
doABarrelRollForceEnabled = config.getBoolean(getBasePath() + ".do-a-barrel-roll-force-enabled", doABarrelRollForceEnabled);
doABarrelRollForceInstalled = config.getBoolean(getBasePath() + ".do-a-barrel-roll-force-installed", doABarrelRollForceInstalled);
doABarrelRollInstalledTimeout = config.getInt(getBasePath() + ".do-a-barrel-roll-installed-timeout", 0);
doABarrelRollKineticDamage = DoABarrelRollPackets.KineticDamage.valueOf(config.getString(getBasePath() + ".do-a-barrel-roll-kinetic-damage", doABarrelRollKineticDamage.name()));
if (doABarrelRollInstalledTimeout <= 0) {
doABarrelRollInstalledTimeout = 40;
}
if (doABarrelRollProtocol) {
DoABarrelRollProtocol.init(
doABarrelRollAllowThrusting,
doABarrelRollForceEnabled,
doABarrelRollForceInstalled,
doABarrelRollInstalledTimeout,
doABarrelRollKineticDamage
);
} else {
DoABarrelRollProtocol.deinit();
}
}
}

View File

@@ -0,0 +1,148 @@
package org.dreeam.leaf.protocol;
import io.netty.buffer.ByteBuf;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.resources.ResourceLocation;
import org.jetbrains.annotations.NotNull;
public class DoABarrelRollPackets {
private static <T extends LeafCustomPayload> LeafCustomPayload.@NotNull Type<T> createType(String path) {
return new LeafCustomPayload.Type<>(ResourceLocation.fromNamespaceAndPath(DoABarrelRollProtocol.NAMESPACE, path));
}
public record ConfigResponseC2SPacket(int protocolVersion, boolean success) implements LeafCustomPayload {
public static final Type<ConfigResponseC2SPacket> TYPE = createType("config_response");
public static final StreamCodec<FriendlyByteBuf, ConfigResponseC2SPacket> STREAM_CODEC = StreamCodec.composite(
ByteBufCodecs.INT, ConfigResponseC2SPacket::protocolVersion,
ByteBufCodecs.BOOL, ConfigResponseC2SPacket::success,
ConfigResponseC2SPacket::new
);
@Override
public @NotNull Type<ConfigResponseC2SPacket> type() {
return TYPE;
}
}
public record ConfigSyncS2CPacket(int protocolVersion,
LimitedModConfigServer applicableConfig,
boolean isLimited,
ModConfigServer fullConfig
) implements LeafCustomPayload {
public static final Type<ConfigSyncS2CPacket> TYPE = createType("config_sync");
public static final StreamCodec<FriendlyByteBuf, ConfigSyncS2CPacket> STREAM_CODEC = StreamCodec.composite(
ByteBufCodecs.INT, ConfigSyncS2CPacket::protocolVersion,
LimitedModConfigServer.getCodec(), ConfigSyncS2CPacket::applicableConfig,
ByteBufCodecs.BOOL, ConfigSyncS2CPacket::isLimited,
ModConfigServer.PACKET_CODEC, ConfigSyncS2CPacket::fullConfig,
ConfigSyncS2CPacket::new
);
@Override
public @NotNull Type<ConfigSyncS2CPacket> type() {
return TYPE;
}
}
public record ConfigUpdateAckS2CPacket(int protocolVersion, boolean success) implements LeafCustomPayload {
public static final Type<ConfigUpdateAckS2CPacket> TYPE = createType("config_update_ack");
public static final StreamCodec<FriendlyByteBuf, ConfigUpdateAckS2CPacket> STREAM_CODEC = StreamCodec.composite(
ByteBufCodecs.INT, ConfigUpdateAckS2CPacket::protocolVersion,
ByteBufCodecs.BOOL, ConfigUpdateAckS2CPacket::success,
ConfigUpdateAckS2CPacket::new
);
@Override
public @NotNull Type<ConfigUpdateAckS2CPacket> type() {
return TYPE;
}
}
public record ConfigUpdateC2SPacket(int protocolVersion, ModConfigServer config) implements LeafCustomPayload {
public static final Type<ConfigUpdateC2SPacket> TYPE = createType("config_update");
public static final StreamCodec<FriendlyByteBuf, ConfigUpdateC2SPacket> STREAM_CODEC = StreamCodec.composite(
ByteBufCodecs.INT, ConfigUpdateC2SPacket::protocolVersion,
ModConfigServer.PACKET_CODEC, ConfigUpdateC2SPacket::config,
ConfigUpdateC2SPacket::new
);
@Override
public @NotNull Type<ConfigUpdateC2SPacket> type() {
return TYPE;
}
}
public record RollSyncC2SPacket(boolean rolling, float roll) implements LeafCustomPayload {
public static final Type<RollSyncC2SPacket> TYPE = createType("roll_sync");
public static final StreamCodec<FriendlyByteBuf, RollSyncC2SPacket> STREAM_CODEC = StreamCodec.composite(
ByteBufCodecs.BOOL, RollSyncC2SPacket::rolling,
ByteBufCodecs.FLOAT, RollSyncC2SPacket::roll,
RollSyncC2SPacket::new
);
@Override
public @NotNull Type<RollSyncC2SPacket> type() {
return TYPE;
}
}
public record RollSyncS2CPacket(int entityId, boolean rolling, float roll) implements LeafCustomPayload {
public static final Type<RollSyncS2CPacket> TYPE = createType("roll_sync");
public static final StreamCodec<FriendlyByteBuf, RollSyncS2CPacket> STREAM_CODEC = StreamCodec.composite(
ByteBufCodecs.INT, RollSyncS2CPacket::entityId,
ByteBufCodecs.BOOL, RollSyncS2CPacket::rolling,
ByteBufCodecs.FLOAT, RollSyncS2CPacket::roll,
RollSyncS2CPacket::new
);
@Override
public @NotNull Type<RollSyncS2CPacket> type() {
return TYPE;
}
}
public interface LimitedModConfigServer {
boolean allowThrusting();
boolean forceEnabled();
static StreamCodec<ByteBuf, LimitedModConfigServer> getCodec() {
return StreamCodec.composite(
ByteBufCodecs.BOOL, LimitedModConfigServer::allowThrusting,
ByteBufCodecs.BOOL, LimitedModConfigServer::forceEnabled,
Impl::new
);
}
record Impl(boolean allowThrusting, boolean forceEnabled) implements LimitedModConfigServer {
}
}
public record ModConfigServer(boolean allowThrusting,
boolean forceEnabled,
boolean forceInstalled,
int installedTimeout,
KineticDamage kineticDamage
) implements LimitedModConfigServer {
public static final StreamCodec<ByteBuf, ModConfigServer> PACKET_CODEC = StreamCodec.composite(
ByteBufCodecs.BOOL, ModConfigServer::allowThrusting,
ByteBufCodecs.BOOL, ModConfigServer::forceEnabled,
ByteBufCodecs.BOOL, ModConfigServer::forceInstalled,
ByteBufCodecs.INT, ModConfigServer::installedTimeout,
KineticDamage.CODEC, ModConfigServer::kineticDamage,
ModConfigServer::new
);
}
public enum KineticDamage {
VANILLA,
HIGH_SPEED,
NONE,
INSTANT_KILL;
public static final StreamCodec<ByteBuf, KineticDamage> CODEC =
ByteBufCodecs.STRING_UTF8.map(KineticDamage::valueOf, KineticDamage::name);
}
}

View File

@@ -0,0 +1,298 @@
package org.dreeam.leaf.protocol;
import com.google.common.collect.ImmutableList;
import it.unimi.dsi.fastutil.objects.*;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.chat.Component;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.network.ServerGamePacketListenerImpl;
import net.minecraft.server.network.ServerPlayerConnection;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.bukkit.event.player.PlayerKickEvent;
import org.dreeam.leaf.protocol.DoABarrelRollPackets.*;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import java.util.OptionalInt;
public class DoABarrelRollProtocol implements Protocol {
protected static final String NAMESPACE = "do_a_barrel_roll";
private static final Logger LOGGER = LogManager.getLogger(NAMESPACE);
private static final int PROTOCOL_VERSION = 4;
private static final ModConfigServer DEFAULT = new ModConfigServer(false, false, false, 40, KineticDamage.VANILLA);
private static final Component SYNC_TIMEOUT_MESSAGE = Component.literal("Please install Do a Barrel Roll 2.4.0 or later to play on this server.");
private static DoABarrelRollProtocol INSTANCE = null;
private final List<Protocols.TypeAndCodec<FriendlyByteBuf, ? extends LeafCustomPayload>> c2s = ImmutableList.of(
new Protocols.TypeAndCodec<>(ConfigUpdateC2SPacket.TYPE, ConfigUpdateC2SPacket.STREAM_CODEC),
new Protocols.TypeAndCodec<>(ConfigResponseC2SPacket.TYPE, ConfigResponseC2SPacket.STREAM_CODEC),
new Protocols.TypeAndCodec<>(RollSyncC2SPacket.TYPE, RollSyncC2SPacket.STREAM_CODEC));
private final List<Protocols.TypeAndCodec<FriendlyByteBuf, ? extends LeafCustomPayload>> s2c = ImmutableList.of(
new Protocols.TypeAndCodec<>(ConfigUpdateAckS2CPacket.TYPE, ConfigUpdateAckS2CPacket.STREAM_CODEC),
new Protocols.TypeAndCodec<>(ConfigSyncS2CPacket.TYPE, ConfigSyncS2CPacket.STREAM_CODEC),
new Protocols.TypeAndCodec<>(RollSyncS2CPacket.TYPE, RollSyncS2CPacket.STREAM_CODEC)
);
private ModConfigServer config = DEFAULT;
private boolean configUpdated = false;
private final Reference2ReferenceMap<ServerGamePacketListenerImpl, ClientInfo> syncStates = new Reference2ReferenceOpenHashMap<>();
private final Reference2ReferenceMap<ServerGamePacketListenerImpl, DelayedRunnable> scheduledKicks = new Reference2ReferenceOpenHashMap<>();
public final Reference2BooleanMap<ServerGamePacketListenerImpl> isRollingMap = Reference2BooleanMaps.synchronize(new Reference2BooleanOpenHashMap<>());
public final Reference2FloatMap<ServerGamePacketListenerImpl> rollMap = Reference2FloatMaps.synchronize(new Reference2FloatOpenHashMap<>());
public final Reference2BooleanMap<ServerGamePacketListenerImpl> lastIsRollingMap = Reference2BooleanMaps.synchronize(new Reference2BooleanOpenHashMap<>());
public final Reference2FloatMap<ServerGamePacketListenerImpl> lastRollMap = Reference2FloatMaps.synchronize(new Reference2FloatOpenHashMap<>());
public static void deinit() {
if (INSTANCE != null) {
INSTANCE = null;
Protocols.unregister(INSTANCE);
}
}
public static void init(
boolean allowThrusting,
boolean forceEnabled,
boolean forceInstalled,
int installedTimeout,
KineticDamage kineticDamage
) {
if (INSTANCE == null) {
INSTANCE = new DoABarrelRollProtocol();
Protocols.register(INSTANCE);
}
INSTANCE.config = new ModConfigServer(allowThrusting, forceEnabled, forceInstalled, installedTimeout, kineticDamage);
INSTANCE.configUpdated = true;
}
@Override
public String namespace() {
return NAMESPACE;
}
@Override
public List<Protocols.TypeAndCodec<FriendlyByteBuf, ? extends LeafCustomPayload>> c2s() {
return c2s;
}
@Override
public List<Protocols.TypeAndCodec<FriendlyByteBuf, ? extends LeafCustomPayload>> s2c() {
return s2c;
}
@Override
public void handle(ServerPlayer player, @NotNull LeafCustomPayload payload) {
switch (payload) {
case ConfigUpdateC2SPacket ignored ->
player.connection.send(Protocols.createPacket(new ConfigUpdateAckS2CPacket(PROTOCOL_VERSION, false)));
case ConfigResponseC2SPacket configResponseC2SPacket -> {
var reply = clientReplied(player.connection, configResponseC2SPacket);
if (reply == HandshakeState.RESEND) {
sendHandshake(player);
}
}
case RollSyncC2SPacket rollSyncC2SPacket -> {
var state = getHandshakeState(player.connection);
if (state.state != HandshakeState.ACCEPTED) {
return;
}
var rolling = rollSyncC2SPacket.rolling();
var roll = rollSyncC2SPacket.roll();
isRollingMap.put(player.connection, rolling);
if (Float.isInfinite(roll)) {
roll = 0.0F;
}
rollMap.put(player.connection, roll);
}
default -> {
}
}
}
@Override
public void disconnected(ServerPlayer player) {
final var handler = player.connection;
syncStates.remove(handler);
isRollingMap.removeBoolean(handler);
rollMap.removeFloat(handler);
lastIsRollingMap.removeBoolean(handler);
lastRollMap.removeFloat(handler);
}
@Override
public void tickTracker(ServerPlayer player) {
if (!isRollingMap.containsKey(player.connection)) {
return;
}
var isRolling = isRollingMap.getBoolean(player.connection);
var roll = rollMap.getFloat(player.connection);
var lastIsRolling = lastIsRollingMap.getBoolean(player.connection);
var lastRoll = lastRollMap.getFloat(player.connection);
if (isRolling == lastIsRolling && roll == lastRoll) {
return;
}
var payload = new RollSyncS2CPacket(player.getId(), isRolling, roll);
var packet = Protocols.createPacket(payload);
for (ServerPlayerConnection seenBy : player.moonrise$getTrackedEntity().seenBy.toArray(new ServerPlayerConnection[0])) {
if (seenBy instanceof ServerGamePacketListenerImpl conn
&& getHandshakeState(conn).state == HandshakeState.ACCEPTED) {
seenBy.send(packet);
}
}
lastIsRollingMap.put(player.connection, isRolling);
lastRollMap.put(player.connection, roll);
}
@Override
public void tickPlayer(ServerPlayer player) {
if (getHandshakeState(player.connection).state == HandshakeState.NOT_SENT) {
sendHandshake(player);
}
if (!isRollingMap.containsKey(player.connection)) {
return;
}
if (!isRollingMap.getBoolean(player.connection)) {
rollMap.put(player.connection, 0.0F);
}
}
@Override
public void tickServer(MinecraftServer server) {
var it = scheduledKicks.entrySet().iterator();
while (it.hasNext()) {
var entry = it.next();
if (entry.getValue().isDone()) {
it.remove();
} else {
entry.getValue().tick();
}
}
if (configUpdated) {
configUpdated = false;
for (ServerPlayer player : server.getPlayerList().players) {
sendHandshake(player);
}
}
}
private OptionalInt getSyncTimeout(ModConfigServer config) {
return config.forceInstalled() ? OptionalInt.of(config.installedTimeout()) : OptionalInt.empty();
}
private void sendHandshake(ServerPlayer player) {
player.connection.send(Protocols.createPacket(initiateConfigSync(player.connection)));
configSentToClient(player.connection);
}
private void configSentToClient(ServerGamePacketListenerImpl handler) {
getHandshakeState(handler).state = HandshakeState.SENT;
OptionalInt timeout = getSyncTimeout(config);
if (timeout.isEmpty()) {
return;
}
scheduledKicks.put(handler, new DelayedRunnable(timeout.getAsInt(), () -> {
if (getHandshakeState(handler).state != HandshakeState.ACCEPTED) {
LOGGER.warn(
"{} did not accept config syncing, config indicates we kick them.",
handler.getPlayer().getName().getString()
);
handler.disconnect(SYNC_TIMEOUT_MESSAGE, PlayerKickEvent.Cause.PLUGIN);
}
}));
}
private HandshakeState clientReplied(ServerGamePacketListenerImpl handler, ConfigResponseC2SPacket packet) {
var info = getHandshakeState(handler);
var player = handler.getPlayer();
if (info.state == HandshakeState.SENT) {
var protocolVersion = packet.protocolVersion();
if (protocolVersion < 1 || protocolVersion > PROTOCOL_VERSION) {
LOGGER.warn(
"{} sent unknown protocol version, expected range 1-{}, got {}. Will attempt to proceed anyway.",
player.getName().getString(),
PROTOCOL_VERSION,
protocolVersion
);
}
if (protocolVersion == 2 && info.protocolVersion != 2) {
LOGGER.info("{} is using an older protocol version, resending.", player.getName().getString());
info.state = HandshakeState.RESEND;
} else if (packet.success()) {
LOGGER.info("{} accepted server config.", player.getName().getString());
info.state = HandshakeState.ACCEPTED;
} else {
LOGGER.warn(
"{} failed to process server config, check client logs find what went wrong.",
player.getName().getString());
info.state = HandshakeState.FAILED;
}
info.protocolVersion = protocolVersion;
}
return info.state;
}
private boolean isLimited(ServerGamePacketListenerImpl net) {
return true;
// return net.getPlayer().getBukkitEntity().hasPermission(DoABarrelRoll.MODID + ".configure");
}
private ClientInfo getHandshakeState(ServerGamePacketListenerImpl handler) {
return syncStates.computeIfAbsent(handler, key -> new ClientInfo(HandshakeState.NOT_SENT, PROTOCOL_VERSION, true));
}
private ConfigSyncS2CPacket initiateConfigSync(ServerGamePacketListenerImpl handler) {
var isLimited = isLimited(handler);
getHandshakeState(handler).isLimited = isLimited;
return new ConfigSyncS2CPacket(PROTOCOL_VERSION, config, isLimited, isLimited ? DEFAULT : config);
}
private static class ClientInfo {
private HandshakeState state;
private int protocolVersion;
private boolean isLimited;
private ClientInfo(HandshakeState state, int protocolVersion, boolean isLimited) {
this.state = state;
this.protocolVersion = protocolVersion;
this.isLimited = isLimited;
}
}
private static class DelayedRunnable {
private final Runnable runnable;
private final int delay;
private int ticks = 0;
private DelayedRunnable(int delay, Runnable runnable) {
this.runnable = runnable;
this.delay = delay;
}
private void tick() {
if (++ticks >= delay) {
runnable.run();
}
}
private boolean isDone() {
return ticks >= delay;
}
}
private enum HandshakeState {
NOT_SENT,
SENT,
ACCEPTED,
FAILED,
RESEND
}
}

View File

@@ -0,0 +1,10 @@
package org.dreeam.leaf.protocol;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import org.jetbrains.annotations.NotNull;
public interface LeafCustomPayload extends CustomPacketPayload {
@NotNull
@Override
Type<? extends LeafCustomPayload> type();
}

View File

@@ -0,0 +1,19 @@
package org.dreeam.leaf.protocol;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerPlayer;
import org.jetbrains.annotations.NotNull;
import java.util.List;
interface Protocol {
String namespace();
List<Protocols.TypeAndCodec<FriendlyByteBuf, ? extends LeafCustomPayload>> c2s();
List<Protocols.TypeAndCodec<FriendlyByteBuf, ? extends LeafCustomPayload>> s2c();
void tickServer(MinecraftServer server);
void tickPlayer(ServerPlayer player);
void tickTracker(ServerPlayer player);
void disconnected(ServerPlayer conn);
void handle(ServerPlayer player, @NotNull LeafCustomPayload payload);
}

View File

@@ -0,0 +1,97 @@
package org.dreeam.leaf.protocol;
import io.netty.buffer.Unpooled;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket;
import net.minecraft.network.protocol.common.custom.DiscardedPayload;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerPlayer;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class Protocols {
private static final ObjectArrayList<Protocol> PROTOCOLS = new ObjectArrayList<>();
static void register(Protocol protocol) {
PROTOCOLS.add(protocol);
}
static void unregister(Protocol protocol) {
PROTOCOLS.remove(protocol);
}
public record TypeAndCodec<B extends FriendlyByteBuf, T extends LeafCustomPayload>(LeafCustomPayload.Type<T> type, StreamCodec<B, T> codec) {}
public static <B extends FriendlyByteBuf> void write(B byteBuf, LeafCustomPayload payload) {
for (Protocol protocol : PROTOCOLS) {
if (protocol.namespace().equals(payload.type().id().getNamespace())) {
encode(byteBuf, payload, protocol);
return;
}
}
}
public static void handle(ServerPlayer player, @NotNull DiscardedPayload payload) {
for (Protocol protocol : PROTOCOLS) {
if (payload.type().id().getNamespace().equals(protocol.namespace())) {
var leafCustomPayload = decode(protocol, payload);
if (leafCustomPayload != null) {
protocol.handle(player, leafCustomPayload);
}
return;
}
}
}
public static void tickServer(MinecraftServer server) {
for (Protocol protocol : PROTOCOLS) {
protocol.tickServer(server);
}
}
public static void tickPlayer(ServerPlayer player) {
for (Protocol protocol : PROTOCOLS) {
protocol.tickPlayer(player);
}
}
public static void tickTracker(ServerPlayer player) {
for (Protocol protocol : PROTOCOLS) {
protocol.tickTracker(player);
}
}
public static void disconnected(ServerPlayer conn) {
for (Protocol protocol : PROTOCOLS) {
protocol.disconnected(conn);
}
}
@Contract("_ -> new")
public static @NotNull ClientboundCustomPayloadPacket createPacket(LeafCustomPayload payload) {
return new ClientboundCustomPayloadPacket(payload);
}
private static <B extends FriendlyByteBuf> void encode(B byteBuf, LeafCustomPayload payload, Protocol protocol) {
for (var codec : protocol.s2c()) {
if (codec.type().id().equals(payload.type().id())) {
byteBuf.writeResourceLocation(payload.type().id());
//noinspection unchecked,rawtypes
((StreamCodec) codec.codec()).encode(byteBuf, payload);
return;
}
}
}
private static @Nullable LeafCustomPayload decode(Protocol protocol, DiscardedPayload payload) {
for (var packet : protocol.c2s()) {
if (packet.type().id().equals(payload.type().id())) {
return packet.codec().decode(new FriendlyByteBuf(Unpooled.wrappedBuffer(payload.data())));
}
}
return null;
}
}