Flandre923
1676 words
8 minutes
27 一个乱动的稿子 和网络发包

27 一个乱动的稿子 和网络发包#

由于我的世界是一个可以联机的游戏,所以为了游戏数据在多个玩家之间的数据是一致的,所以游戏数据的处理一般是在一个服务器上的。各个玩家之间的数据通过数网络通信保证数据的一致性。

而我的世界游戏为了保证一个jar包的代码可以在服务器和客户端都运行,所以你需要在数据同步的时间考虑到服务器和客户端的数据同步的问题,这部分的内容需要你去自己解决了, 而其他的逻辑代码是可以同时存在于客户端和服务端,这样做的原因也是保证一个jar包可以在客户端和服务端都运行,你不需要去单独写两份代码。

更多的数据包的相关内容见我往期的教程。

先来看数据的包的定义,我们顶一个了一个数据包,这个数据包是让客户端给服务器发送数据,并让服务器根据这些数据做一些事情。

public class FuC2SPacket implements CustomPacketPayload {
    static Vec3 direction; // 静态变量,用于存储从客户端发送过来的方向向量
    static UUID uuid; // 静态变量,用于存储从客户端发送过来的UUID

    public Vec3 directionMessage; // 实例变量,用于接收客户端发送的方向向量
    public UUID uuidMessage; // 实例变量,用于接收客户端发送的UUID
    public int flag; // 一个标志位,用于区分数据类型

    public static void receive(FuC2SPacket data) {
        // 这个函数只在服务器端执行
        // 根据标志位处理数据,然后可以发送一个包到客户端通知他们游戏模式的变化
        int flag = data.flag;
        if(flag==1) {
            direction = data.directionMessage; // 如果标志位为1,更新方向向量
        } else if(flag==2) {
            uuid = data.uuidMessage; // 如果标志位为2,更新UUID
        }
        // 示例代码被注释掉了,实际使用时可能需要取消注释
    }
    public static Vec3 getDirection() {
        return direction; // 返回方向向量
    }
    public static UUID getUuid() {
        if(uuid == null){
            return null; // 如果UUID为null,则返回null
        } else {
            return uuid; // 否则返回UUID
        }
    }
    public static final Type<FuC2SPacket> TYPE = new Type<FuC2SPacket>(ResourceLocation.fromNamespaceAndPath(NeoMafishMod.MODID,"fu_c2s"));
    // 定义这个包的类型,用于注册和处理网络消息
    public static final StreamCodec<FriendlyByteBuf,FuC2SPacket> STREAM_CODEC =
            CustomPacketPayload.codec(FuC2SPacket::write,FuC2SPacket::new);

    private FuC2SPacket(FriendlyByteBuf buf) {
        // 私有构造函数,用于从缓冲区读取数据并初始化对象
        this.flag = buf.readInt();
        if (flag == 1){
            this.directionMessage = buf.readVec3();
        } else if (flag == 2){
            this.uuidMessage = buf.readUUID();
        }
    }

    public void write(FriendlyByteBuf buf) {
        // 将对象的数据写入缓冲区,用于网络传输
        buf.writeInt(flag);
        if (flag==1){
            buf.writeVec3(directionMessage);
        } else if (flag==2){
            buf.writeUUID(this.uuidMessage);
        }
    }

    public FuC2SPacket(int flag,UUID uuid,Vec3 directionMessage) {
        // 公共构造函数,用于创建对象并初始化标志位、UUID和方向向量
        this.flag =flag;
        this.uuidMessage = uuid;
        this.directionMessage = directionMessage;
    }

    public static void handle(final FuC2SPacket data, final IPayloadContext context){
        // 处理网络包的方法,它将接收到的数据在服务器端的逻辑线程上处理
        context.enqueueWork(()->{
            receive(data);
        });
    }

    @Override
    public Type<? extends CustomPacketPayload> type() {
        // 返回这个包的类型
        return TYPE;
    }
}

然后我们看一个乱动的稿子,如何用客户端发来的数据包的信息中的方向在处理数据。这是一个mixin,mixin进入了掉落物的实体,如果对应的对应的掉落物是稿子,并且附魔对应的附魔,那么就设置他的拾取延迟,然后通过发包随机一个移动反向,当碰到了方块时候破坏发方块,这个破坏方块有一个时间的延迟。随后稿子向这个方向移动。


// 使用Mixin注解标记这个类,表明它将被用来注入代码到ItemEntity类中
@Mixin(ItemEntity.class)
public abstract class ItemEntityMixin extends Entity implements TraceableEntity {
    // ItemEntityMixin的构造函数,调用父类的构造函数
    public ItemEntityMixin(EntityType<?> entityType, Level level) {
        super(entityType, level);
    }   
	// 使用@Shadow注解来声明ItemEntity类中的方法,以便在Mixin中使用
    @Shadow
    public abstract ItemStack getItem();

    // @Unique注解的字段,用于存储冷却时间,初始化为0
    @Unique
    private int cd=0;

    // 使用@Shadow注解声明的方法,用于设置物品的拾取延迟
    @Shadow public abstract void setPickUpDelay(int pickupDelay);

    // 以下是被注释掉的onBlockCollision方法,它可能是用于处理方块碰撞的旧代码或草稿

    // 在ItemEntity类的setThrower方法执行后注入代码
    @Inject(at = @At("TAIL"), method = "setThrower")
    private void init(Entity thrower, CallbackInfo ci) {
        // 获取物品的"释放"附魔等级
        int i = InjectHelper.getEnchantmentLevel(this.getItem(), ModEnchantments.FANGSHENG);
        // 如果物品是镐,并且有释放附魔
        if(this.getItem().getItem() instanceof PickaxeItem && i>0) {
            // 设置拾取延迟为200游戏刻
            this.setPickUpDelay(200);
        }
        // 重置冷却时间为0
        cd = 0;
    }

    // 在ItemEntity类的tick方法执行后注入代码
    @Inject(at = @At("TAIL"), method = "tick")
    private void init1(CallbackInfo info) {
        // 获取物品的"释放"附魔等级
        int i = InjectHelper.getEnchantmentLevel(this.getItem(), ModEnchantments.FANGSHENG);
        // 如果物品是镐,并且有释放附魔
        if(this.getItem().getItem() instanceof PickaxeItem && i>0) {
            // 如果物品在地上
            if(this.onGround()) {
                // 随机生成一个方向
                double angle = random.nextDouble() * 2 * Math.PI;
                double x = Math.cos(angle);
                double z = Math.sin(angle);
                Vec3 direction = new Vec3(x, 0.0, z);
                // 设置水平速度
                double horizontalSpeed = 0.3f;
                if(level().isClientSide) {
                    // 客户端特有逻辑
                    mafishmod$ItemEntityC2S(direction);
                }
                // 根据服务器发送的方向更新物品的运动方向
                direction = FuC2SPacket.getDirection();
                if(direction!=null) {
                    this.setDeltaMovement(new Vec3(direction.x * horizontalSpeed, 0.4, direction.z * horizontalSpeed));
                }
            }

            // 获取与物品碰撞的方块的VoxelShape列表
            Iterable<VoxelShape> blockCollisions = this.level().getBlockCollisions(this, this.getBoundingBox().inflate(0.05));
            List<BlockPos> hitBlockPos = new ArrayList<>();
            for (VoxelShape voxelShape : blockCollisions) {
                voxelShape.forAllBoxes((minX, minY, minZ, maxX, maxY, maxZ) -> {
                    BlockPos pos = new BlockPos((int) Math.floor((minX + maxX) / 2.0),
                            (int) Math.floor((minY + maxY) / 2.0),
                            (int) Math.floor((minZ + maxZ) / 2.0));
                    hitBlockPos.add(pos);
                });
            }
            // 遍历碰撞的方块位置列表
            for (BlockPos pos : hitBlockPos) {
                BlockState blockState = this.level().getBlockState(pos);
                // 如果方块可以用镐挖掘,并且冷却时间已过
                if(blockState.is(BlockTags.MINEABLE_WITH_PICKAXE) && cd == 0){
                    this.level().destroyBlock(pos,true);
                    cd = 10; // 设置冷却时间为10
                }
            }
            // 如果冷却时间大于0,则减少冷却时间
            if(cd > 0) {
                cd = cd - 1;
            }
        }
    }

    // 客户端特有方法,用于发送网络包到服务器
    @Unique
    @OnlyIn(Dist.CLIENT)
    private void mafishmod$ItemEntityC2S(Vec3 direction){
        // 构建并发送一个自定义的网络包
        PacketDistributor.sendToServer(new FuC2SPacket(1,null,direction));
    }
}

数据包的注册和定义

@EventBusSubscriber(modid = NeoMafishMod.MODID,bus = EventBusSubscriber.Bus.MOD)
public class ModMessage {
    @SubscribeEvent
    public static void register(final RegisterPayloadHandlersEvent event) {
        final PayloadRegistrar registrar = event.registrar(NeoMafishMod.MODID);
		// 一个服务端处理的例子
        // 注册FuC2SPacket类型的网络消息,并指定其处理方式
        registrar.playBidirectional(
                FuC2SPacket.TYPE,
                FuC2SPacket.STREAM_CODEC,
                new DirectionalPayloadHandler<>(
                        null,
                        FuC2SPacket::handle
                )
        );
        // 一个客户端处理的例子
        // 注册NeverGonnaS2CPacket类型的网络消息(服务器到客户端),并指定其处理方式
        registrar.playBidirectional(
                NeverGonnaS2CPacket.TYPE,
                NeverGonnaS2CPacket.STREAM_CODEC,
                new DirectionalPayloadHandler<NeverGonnaS2CPacket>(
                        NeverGonnaS2CPacket::handle,
                        null
                )
        );
    }
}

27 一个乱动的稿子 和网络发包
https://fuwari.vercel.app/posts/minecraft1_21_0/27_一个乱动的稿子_和网络发包/
Author
Flandre923
Published at
2024-08-24