9
0
mirror of https://github.com/Xiao-MoMi/craft-engine.git synced 2026-01-04 15:41:38 +00:00

Merge pull request #426 from jhqwqmc/client-mod

适配新版本客户端模组
This commit is contained in:
XiaoMoMi
2025-10-29 22:04:31 +08:00
committed by GitHub
8 changed files with 187 additions and 26 deletions

View File

@@ -6,6 +6,9 @@ import net.momirealms.craftengine.bukkit.block.behavior.UnsafeCompositeBlockBeha
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine;
import net.momirealms.craftengine.bukkit.plugin.injector.BlockGenerator;
import net.momirealms.craftengine.bukkit.plugin.network.BukkitNetworkManager;
import net.momirealms.craftengine.bukkit.plugin.network.payload.PayloadHelper;
import net.momirealms.craftengine.bukkit.plugin.network.payload.protocol.VisualBlockStatePacket;
import net.momirealms.craftengine.bukkit.plugin.reflection.bukkit.CraftBukkitReflections;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.*;
import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer;
@@ -63,6 +66,8 @@ public final class BukkitBlockManager extends AbstractBlockManager {
private Set<Object> missingHitSounds = Set.of();
private Set<Object> missingStepSounds = Set.of();
private Set<Key> missingInteractSoundBlocks = Set.of();
// 缓存的VisualBlockStatePacket
private VisualBlockStatePacket cachedVisualBlockStatePacket;
public BukkitBlockManager(BukkitCraftEngine plugin) {
super(plugin, RegistryUtils.currentBlockRegistrySize(), Config.serverSideBlocks());
@@ -122,6 +127,11 @@ public final class BukkitBlockManager extends AbstractBlockManager {
public void delayedLoad() {
this.plugin.networkManager().registerBlockStatePacketListeners(this.blockStateMappings); // 重置方块映射表
super.delayedLoad();
this.cachedVisualBlockStatePacket = VisualBlockStatePacket.create();
for (BukkitServerPlayer player : BukkitNetworkManager.instance().onlineUsers()) {
if (!player.clientModEnabled()) continue;
PayloadHelper.sendData(player, this.cachedVisualBlockStatePacket);
}
}
@Override
@@ -358,6 +368,10 @@ public final class BukkitBlockManager extends AbstractBlockManager {
return this.cachedUpdateTagsPacket;
}
public VisualBlockStatePacket cachedVisualBlockStatePacket() {
return this.cachedVisualBlockStatePacket;
}
private void markVanillaNoteBlocks() {
try {
Object block = FastNMS.INSTANCE.method$Registry$getValue(MBuiltInRegistries.BLOCK, KeyUtils.toResourceLocation(BlockKeys.NOTE_BLOCK));

View File

@@ -75,11 +75,11 @@ public class DirectionalAttachedBlockBehavior extends BukkitBlockBehavior {
if (behavior == null) return false;
Direction direction;
if (isSixDirection) {
direction = ((Direction) state.get(behavior.facingProperty)).opposite();
direction = (Direction) state.get(behavior.facingProperty);
} else {
direction = ((HorizontalDirection) state.get(behavior.facingProperty)).opposite().toDirection();
direction = ((HorizontalDirection) state.get(behavior.facingProperty)).toDirection();
}
BlockPos blockPos = LocationUtils.fromBlockPos(args[2]).relative(direction);
BlockPos blockPos = LocationUtils.fromBlockPos(args[2]).relative(direction.opposite());
Object nmsPos = LocationUtils.toBlockPos(blockPos);
Object nmsState = FastNMS.INSTANCE.method$BlockGetter$getBlockState(args[1], nmsPos);
return FastNMS.INSTANCE.method$BlockStateBase$isFaceSturdy(nmsState, args[1], nmsPos, DirectionUtils.toNMSDirection(direction), CoreReflections.instance$SupportType$FULL)

View File

@@ -187,9 +187,6 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
Object bundle = FastNMS.INSTANCE.constructor$ClientboundBundlePacket(packets);
this.immediatePacketConsumer.accept(channel, bundle, sendListener);
};
// set up mod channel
this.plugin.javaPlugin().getServer().getMessenger().registerIncomingPluginChannel(this.plugin.javaPlugin(), MOD_CHANNEL, this);
this.plugin.javaPlugin().getServer().getMessenger().registerOutgoingPluginChannel(this.plugin.javaPlugin(), MOD_CHANNEL);
// Inject server channel
try {
Object server = FastNMS.INSTANCE.method$MinecraftServer$getServer();
@@ -1612,7 +1609,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
} else {
return;
}
if (clientPayload == null || !clientPayload.channel().equals(NetworkManager.MOD_CHANNEL_KEY)) return;
if (clientPayload == null) return;
PayloadHelper.handleReceiver(clientPayload, user);
}
}
@@ -1943,6 +1940,10 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
this.needsDowngrade = MiscUtils.ceilLog2(BlockStateUtils.vanillaBlockStateCount()) != MiscUtils.ceilLog2(blockRegistrySize);
}
public int remapBlockState(int stateId, boolean enableMod) {
return enableMod ? this.modBlockStateMapper[stateId] : this.blockStateMapper[stateId];
}
@Override
public void onPacketSend(NetWorkUser user, ByteBufPacketEvent event) {
BukkitServerPlayer player = (BukkitServerPlayer) user;
@@ -1989,14 +1990,14 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
}
Palette<Integer> palette = container.data().palette();
if (palette.canRemap()) {
if (palette.remapAndCheck(s -> this.blockStateMapper[s])) {
if (palette.remapAndCheck(s -> remapBlockState(s, user.clientModEnabled()))) {
hasChangedAnyBlock = true;
}
} else {
hasGlobalPalette = true;
for (int j = 0; j < 4096; j++) {
int state = container.get(j);
int newState = this.blockStateMapper[state];
int newState = remapBlockState(state, user.clientModEnabled());
if (newState != state) {
container.set(j, newState);
hasChangedAnyBlock = true;
@@ -3752,7 +3753,6 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
if (VersionHelper.isOrAbove1_20_2()) return;
FriendlyByteBuf byteBuf = event.getBuffer();
Key key = byteBuf.readKey();
if (!key.equals(NetworkManager.MOD_CHANNEL_KEY)) return;
PayloadHelper.handleReceiver(new UnknownPayload(key, byteBuf.readBytes(byteBuf.readableBytes())), user);
}
}

View File

@@ -1,13 +1,16 @@
package net.momirealms.craftengine.bukkit.plugin.network.payload;
import io.netty.buffer.Unpooled;
import net.kyori.adventure.text.Component;
import net.momirealms.craftengine.bukkit.plugin.network.payload.protocol.CancelBlockUpdatePacket;
import net.momirealms.craftengine.bukkit.plugin.network.payload.protocol.ClientBlockStateSizePacket;
import net.momirealms.craftengine.bukkit.plugin.network.payload.protocol.ClientCustomBlockPacket;
import net.momirealms.craftengine.bukkit.plugin.network.payload.protocol.VisualBlockStatePacket;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.plugin.logger.Debugger;
import net.momirealms.craftengine.core.plugin.network.PayloadChannelKeys;
import net.momirealms.craftengine.core.plugin.network.ModPacket;
import net.momirealms.craftengine.core.plugin.network.NetWorkUser;
import net.momirealms.craftengine.core.plugin.network.NetworkManager;
import net.momirealms.craftengine.core.plugin.network.codec.NetworkCodec;
import net.momirealms.craftengine.core.registry.BuiltInRegistries;
import net.momirealms.craftengine.core.registry.WritableRegistry;
@@ -15,11 +18,13 @@ import net.momirealms.craftengine.core.util.FriendlyByteBuf;
import net.momirealms.craftengine.core.util.ResourceKey;
public class PayloadHelper {
public static final byte[] JADE_RESPONSE = new byte[]{0, 0, 0, 0};
public static void registerDataTypes() {
registerDataType(ClientCustomBlockPacket.TYPE, ClientCustomBlockPacket.CODEC);
registerDataType(CancelBlockUpdatePacket.TYPE, CancelBlockUpdatePacket.CODEC);
registerDataType(ClientBlockStateSizePacket.TYPE, ClientBlockStateSizePacket.CODEC);
registerDataType(VisualBlockStatePacket.TYPE, VisualBlockStatePacket.CODEC);
}
public static <T extends ModPacket> void registerDataType(ResourceKey<NetworkCodec<FriendlyByteBuf, ? extends ModPacket>> key, NetworkCodec<FriendlyByteBuf, T> codec) {
@@ -36,16 +41,31 @@ public class PayloadHelper {
FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer());
buf.writeByte(BuiltInRegistries.MOD_PACKET.getId(codec));
codec.encode(buf, data);
user.sendCustomPayload(NetworkManager.MOD_CHANNEL_KEY, buf.array());
user.sendCustomPayload(PayloadChannelKeys.CRAFTENGINE_CHANNEL, buf.array());
}
public static void handleReceiver(Payload payload, NetWorkUser user) {
try {
if (payload.channel().equals(PayloadChannelKeys.CRAFTENGINE_CHANNEL)) {
handleCraftEngineModReceiver(payload, user);
}
} catch (Throwable e) {
// 乱发包我给你踹了
user.kick(Component.translatable(
"disconnect.craftengine.invalid_payload",
"Connection terminated due to transmission of invalid payload. \n Please ensure that the client mod and server plugin are the latest version."
));
Debugger.COMMON.warn(() -> "Failed to handle payload", e);
}
}
private static void handleCraftEngineModReceiver(Payload payload, NetWorkUser user) {
FriendlyByteBuf buf = payload.toBuffer();
byte type = buf.readByte();
@SuppressWarnings("unchecked")
NetworkCodec<FriendlyByteBuf, ModPacket> codec = (NetworkCodec<FriendlyByteBuf, ModPacket>) BuiltInRegistries.MOD_PACKET.getValue(type);
if (codec == null) {
CraftEngine.instance().logger().warn("Unknown data type received: " + type);
Debugger.COMMON.debug(() -> "Unknown data type received: " + type);
return;
}

View File

@@ -4,10 +4,13 @@ package net.momirealms.craftengine.bukkit.plugin.network.payload.protocol;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TranslationArgument;
import net.momirealms.craftengine.bukkit.block.BukkitBlockManager;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.network.payload.PayloadHelper;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.NetworkReflections;
import net.momirealms.craftengine.bukkit.plugin.reflection.paper.PaperReflections;
import net.momirealms.craftengine.bukkit.util.BlockStateUtils;
import net.momirealms.craftengine.bukkit.util.RegistryUtils;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.plugin.network.ModPacket;
@@ -17,7 +20,7 @@ import net.momirealms.craftengine.core.registry.BuiltInRegistries;
import net.momirealms.craftengine.core.util.*;
import org.bukkit.entity.Player;
public record ClientCustomBlockPacket(int size) implements ModPacket {
public record ClientCustomBlockPacket(int vanillaSize, int currentSize) implements ModPacket {
public static final ResourceKey<NetworkCodec<FriendlyByteBuf, ? extends ModPacket>> TYPE = ResourceKey.create(
BuiltInRegistries.MOD_PACKET.key().location(), Key.of("craftengine", "client_custom_block")
);
@@ -27,11 +30,12 @@ public record ClientCustomBlockPacket(int size) implements ModPacket {
);
private ClientCustomBlockPacket(FriendlyByteBuf buf) {
this(buf.readInt());
this(buf.readInt(), buf.readInt());
}
private void encode(FriendlyByteBuf buf) {
buf.writeInt(this.size);
buf.writeInt(this.vanillaSize);
buf.writeInt(this.currentSize);
}
@Override
@@ -42,17 +46,27 @@ public record ClientCustomBlockPacket(int size) implements ModPacket {
@Override
public void handle(NetWorkUser user) {
if (user.clientModEnabled()) return; // 防止滥用
int serverBlockRegistrySize = RegistryUtils.currentBlockRegistrySize();
if (this.size != serverBlockRegistrySize) {
int vanillaBlockRegistrySize = BlockStateUtils.vanillaBlockStateCount();
if (this.vanillaSize != vanillaBlockRegistrySize) {
user.kick(Component.translatable(
"disconnect.craftengine.block_registry_mismatch",
TranslationArgument.numeric(this.size),
"disconnect.craftengine.vanilla_block_registry_mismatch",
TranslationArgument.numeric(this.vanillaSize),
TranslationArgument.numeric(vanillaBlockRegistrySize)
));
return;
}
int serverBlockRegistrySize = RegistryUtils.currentBlockRegistrySize();
if (this.currentSize != serverBlockRegistrySize) {
user.kick(Component.translatable(
"disconnect.craftengine.current_block_registry_mismatch",
TranslationArgument.numeric(this.currentSize),
TranslationArgument.numeric(serverBlockRegistrySize)
));
return;
}
user.setClientModState(true);
user.setClientBlockList(new IntIdentityList(this.size));
user.setClientBlockList(new IntIdentityList(this.currentSize));
PayloadHelper.sendData(user, BukkitBlockManager.instance().cachedVisualBlockStatePacket());
if (!VersionHelper.isOrAbove1_20_2()) {
// 因为旧版本没有配置阶段需要重新发送区块
try {

View File

@@ -0,0 +1,111 @@
package net.momirealms.craftengine.bukkit.plugin.network.payload.protocol;
import io.netty.handler.codec.DecoderException;
import net.momirealms.craftengine.bukkit.block.BukkitBlockManager;
import net.momirealms.craftengine.bukkit.util.BlockStateUtils;
import net.momirealms.craftengine.core.block.ImmutableBlockState;
import net.momirealms.craftengine.core.plugin.config.Config;
import net.momirealms.craftengine.core.plugin.network.ModPacket;
import net.momirealms.craftengine.core.plugin.network.codec.NetworkCodec;
import net.momirealms.craftengine.core.registry.BuiltInRegistries;
import net.momirealms.craftengine.core.util.FriendlyByteBuf;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.ResourceKey;
public record VisualBlockStatePacket(int[] data) implements ModPacket {
public static final ResourceKey<NetworkCodec<FriendlyByteBuf, ? extends ModPacket>> TYPE = ResourceKey.create(
BuiltInRegistries.MOD_PACKET.key().location(), Key.of("craftengine", "visual_block_state")
);
public static final NetworkCodec<FriendlyByteBuf, VisualBlockStatePacket> CODEC = ModPacket.codec(
VisualBlockStatePacket::encode,
VisualBlockStatePacket::new
);
private static final int RLE_THRESHOLD = 3;
private static final int RLE_TAG = 0;
private static final int DELTA_TAG = 1;
private VisualBlockStatePacket(FriendlyByteBuf buf) {
this(decode(buf));
}
private void encode(FriendlyByteBuf buf) {
encode(buf, this.data);
}
private static void encode(FriendlyByteBuf buf, int[] data) {
if (data.length == 0) {
buf.writeVarInt(0);
return;
}
buf.writeVarInt(data.length);
int i = 0;
int previousValue = 0;
while (i < data.length) {
int currentValue = data[i];
int repeatCount = 1;
int j = i + 1;
while (j < data.length && data[j] == currentValue) {
repeatCount++;
j++;
}
if (repeatCount >= RLE_THRESHOLD) {
buf.writeVarInt(RLE_TAG);
buf.writeVarInt(currentValue);
buf.writeVarInt(repeatCount);
i += repeatCount;
previousValue = currentValue;
} else {
buf.writeVarInt(DELTA_TAG);
int delta = currentValue - previousValue;
buf.writeVarInt(delta);
previousValue = currentValue;
i++;
}
}
}
private static int[] decode(FriendlyByteBuf buf) {
int length = buf.readVarInt();
if (length == 0) return new int[0];
int[] data = new int[length];
int previousValue = 0;
int i = 0;
while (i < length) {
int tag = buf.readVarInt();
if (tag == RLE_TAG) {
int value = buf.readVarInt();
int count = buf.readVarInt();
if (i + count > length) throw new DecoderException("RLE count exceeds array bounds");
for (int j = 0; j < count; j++) data[i++] = value;
previousValue = value;
} else if (tag == DELTA_TAG) {
int delta = buf.readVarInt();
int currentValue = previousValue + delta;
data[i++] = currentValue;
previousValue = currentValue;
} else {
throw new DecoderException("Unknown encoding tag: " + tag);
}
}
if (i != length) throw new DecoderException("Decoded length mismatch");
return data;
}
@Override
public ResourceKey<NetworkCodec<FriendlyByteBuf, ? extends ModPacket>> type() {
return TYPE;
}
public static VisualBlockStatePacket create() {
int vanillaBlockStateCount = BlockStateUtils.vanillaBlockStateCount();
int serverSideBlockCount = Config.serverSideBlocks();
int[] mappings = new int[serverSideBlockCount];
for (int i = 0; i < serverSideBlockCount; i++) {
ImmutableBlockState state = BukkitBlockManager.instance().getImmutableBlockStateUnsafe(i + vanillaBlockStateCount);
if (state.isEmpty()) continue;
mappings[state.customBlockState().registryId() - vanillaBlockStateCount] = state.vanillaBlockState().registryId();
}
return new VisualBlockStatePacket(mappings);
}
}

View File

@@ -3,16 +3,11 @@ package net.momirealms.craftengine.core.plugin.network;
import io.netty.channel.Channel;
import net.momirealms.craftengine.core.entity.player.Player;
import net.momirealms.craftengine.core.plugin.Manageable;
import net.momirealms.craftengine.core.util.Key;
import org.jetbrains.annotations.NotNull;
import java.util.List;
public interface NetworkManager extends Manageable {
String MOD_CHANNEL = "craftengine:payload";
String VIA_CHANNEL = "vv:proxy_details";
Key MOD_CHANNEL_KEY = Key.of(MOD_CHANNEL);
Key VIA_CHANNEL_KEY = Key.of(VIA_CHANNEL);
void setUser(Channel channel, NetWorkUser user);

View File

@@ -0,0 +1,7 @@
package net.momirealms.craftengine.core.plugin.network;
import net.momirealms.craftengine.core.util.Key;
public final class PayloadChannelKeys {
public static final Key CRAFTENGINE_CHANNEL = Key.of("craftengine:payload"); // 进出
}