[1.7-1.13.2] Пакетная система

[1.7-1.13.2] Пакетная система

Нет прав для скачивания
Версия(и) Minecraft
1.7-1.13.2
Предисловие:
Данный ресурс научит вас, как надо правильно создавать, отправлять и регистрировать пакеты. Тема со временем будет наполнятся различными статьями, которые помогут вам решить ту или иную проблему. Так же убедительная просьба обратить внимание на то, что код для статей пишется на версии 1.10 Minecraft.

Оглавление:
  • Начало
  • Статьи
  • - Частицы от игрока
  • Заключение
Начало:
Для 1.12.2 и ниже версий minecraft

Данный класс нам понадобится для упрощённого создания пакетов. Вы так же можете по прежнему использовать IMessage и IMessageHandler, это никак серьёзно не отразится на отправке пакетов.

Код:
public class SimplePacket implements IMessage, IMessageHandler<SimplePacket, SimplePacket> {
    private ByteBuf buf;

    @Override
    public SimplePacket onMessage(SimplePacket sp, MessageContext ctx) {
        if (ctx.side.isServer())
            sp.server(ctx.getServerHandler().playerEntity);
        else
            sp.client(clientPlayer());
        return null;
    }

    protected ByteBuf buf() {
        return buf != null ? buf : (buf = Unpooled.buffer());
    }

    public void client(EntityPlayer player) {}
    public void server(EntityPlayerMP player) {}

    @Override
    public final void fromBytes(ByteBuf buf) {
        this.buf = buf;
    }

    @Override
    public final void toBytes(ByteBuf buf) {
        if (buf != null)
            buf.writeBytes(this.buf);
    }

    @SideOnly(Side.CLIENT)
    private EntityPlayer clientPlayer() {
        return Minecraft.getMinecraft().thePlayer;
    }
}
Данный класс позволит нам регистрировать пакеты.

Java:
public final class NetworkHandler {
    private short id;

    /**
     *  ChannelName - название канала. Канал должен быть один на весь мод, создавать дополнительные каналы не нужно!
     */
    public static final SimpleNetworkWrapper NETWORK = NetworkRegistry.INSTANCE.newSimpleChannel("ChannelName");

    public NetworkHandler() {
        //Здесь будет происходить регистрация
    }

    /**
     * Получение дистанции от определённой точки в мире.
     * @param world - мир
     * @param updateDistance - радиус в котором будет действовать пакет
     * @return позиция
     */
    public static void sendToAllAround(SimplePacket packet, World world, double x, double y, double z, double distance) {
        NETWORK.sendToAllAround(packet, new NetworkRegistry.TargetPoint(world.provider.dimensionId, x, y, z, distance));
    }

    /**
     * Регистрация пакета через один метод.
     * @param packet - класс пакета
     * @param side - сторона (клиент/сервер)
     */
    private void register(Class<? extends SimplePacket> packet, Side side) {
        try {
            NETWORK.registerMessage(packet.newInstance(), packet, id++, side);
        } catch (InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}

Для 1.13 и выше версий minecraft.
Данный класс позволит нам регистрировать пакеты.

Java:
public class NetworkHandler {
    /**
     * Первый параметр отвечает за название нашего канала. Соответственно: modid - уникальный идентификатор мода, channelName - название канала
     * Версия сетевого протокола, под которой будет работать наш канал. Если версия не будет совпадать с сервером/клиентом, пакеты не будут приниматься.
     * Проверка версии протокола на клиенте, если версия совпадает, то можем обработать пакет. Вместо `true` лучше выставить проверку `s.equals(...)` и т.п.
     * Проверка версии протокола на сервере, если версия совпадает, то можем обработать пакет. Вместо `true` лучше выставить проверку `s.equals(...)` и т.п.
     */
    private static final SimpleChannel CHANNEL = NetworkRegistry.newSimpleChannel(
            new ResourceLocation("modid", "channelName"), () -> "2.0", client -> true, server -> true);

    /**
     * Уникальный идентификатор пакета.
     */
    private short id = 0;

    /**
     * В конструкторе мы будем регистрировать пакеты.
     */
    public NetworkHandler() {}

    /**
     * Данный метод позволяет отправить наш пакет игроку. Пакет отсылается на КЛИЕНТ!
     *
     * @param packet - наш пакет
     * @param player - игрок на сервере
     */
    public void sendTo(Packet packet, EntityPlayerMP player) {
        CHANNEL.send(PacketDistributor.PLAYER.with(() -> player), packet);
    }

    /**
     * Данный метод позволяет отправить наш пакет всем. Пакет отсылается на КЛИЕНТ!
     *
     * @param packet - наш пакет
     */
    public void sendToAll(Packet packet) {
        CHANNEL.send(PacketDistributor.ALL.noArg(), packet);
    }

    /**
     * Данный метод позволяет отправить наш пакет ближайшим игрокам. Пакет отсылается на КЛИЕНТ!
     *
     * @param packet - наш пакет
     * @param point  - от которой начнётся отсылка пакетов до N радиуса
     */
    public void sendToNear(Packet packet, PacketDistributor.TargetPoint point) {
        CHANNEL.send(PacketDistributor.NEAR.with(() -> point), packet);
    }

    /**
     * Данный метод позволяет отправить наш пакет на сервер.
     *
     * @param packet - наш пакет
     */
    public void sendToServer(Packet packet) {
        CHANNEL.send(PacketDistributor.SERVER.noArg(), packet);
    }

    /**
     * Данный метод позволяет отправить наш пакет всем в измерении(dimension). Пакет отсылается на КЛИЕНТ!
     *
     * @param packet - наш пакет
     * @param type   - тип измерения. Доступные в mc: {OVERWORLD, NETHER, THE_END}
     */
    public void sendToDim(Packet packet, DimensionType type) {
        CHANNEL.send(PacketDistributor.DIMENSION.with(() -> type), packet);
    }

    /**
     * Данный метод позволяет отправить наш пакет всем отслеживающим сущность. Пакет отсылается на КЛИЕНТ!
     *
     * @param packet - наш пакет
     * @param entity - сущность, которую нужно отправить отслеживающим
     */
    public void sendToTracking(Packet packet, Entity entity) {
        CHANNEL.send(PacketDistributor.TRACKING_ENTITY.with(() -> entity), packet);
    }

    /**
     * Данный метод позволяет отправить наш пакет всем отслеживающим сущность и игрока. Пакет отсылается на КЛИЕНТ!
     *
     * @param packet - наш пакет
     * @param entity - сущность, которую нужно отправить отслеживающим
     */
    public void sendToTrackingAndSelf(Packet packet, Entity entity) {
        CHANNEL.send(PacketDistributor.TRACKING_ENTITY_AND_SELF.with(() -> entity), packet);
    }

    /**
     * Данный метод позволяет отправить наш пакет всем отслеживающим чанк. Пакет отсылается на КЛИЕНТ!
     *
     * @param packet - наш пакет
     * @param chunk  - чанк, который нужно отправить отслеживающим
     */
    public void sendToTrackingChunk(Packet packet, Chunk chunk) {
        CHANNEL.send(PacketDistributor.TRACKING_CHUNK.with(() -> chunk), packet);
    }

    /**
     * Данный метод позволяет отправить наш пакет всем `менеджерам`. Пакет отсылается на КЛИЕНТ!
     * Вы можете использовать данный метод для отправки пакетов ТОЛЬКО некоторым игрокам, а не всем или тем кто находится по близости.
     * Пример использования: Создайте список NetworkManager, а затем добавьте в него
     * `p.connection.netManager` (там где p это EntityPlayerMP!)
     *
     * @param packet   - наш пакет
     * @param managers - список менеджеров, которым нужно отправить пакет.
     */
    public void sendToSeveralPlayers(Packet packet, List<NetworkManager> managers) {
        CHANNEL.send(PacketDistributor.NMLIST.with(() -> managers), packet);
    }

    /**
     * Данный метод поможет нам с лёгкостью зарегистрировать пакет имплементированный от `SimplePacket`.
     * Так же хочу отметить то, что это не идеальная реализация, но вы всегда можете самостоятельно переписать её под свои нужды.
     * А ещё вы можете использовать вместо `registerMessage`, метод `messageBuilder` который позволит вам использовать к примеру
     * только `encode` или только `handle` своего пакета.
     *
     * @param clazz - класс пакета
     */
    private void registerPacket(Class<Packet> clazz) {
        try {
            final Packet packet = clazz.newInstance();
            CHANNEL.registerMessage(id++, clazz, packet::encode, packet::decode, packet::handlePacket);
        } catch (InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    public abstract class Packet {
        abstract void encode(Packet packet, PacketBuffer buf);
        abstract Packet decode(PacketBuffer buf);

        void handlePacket(Packet packet, Supplier<NetworkEvent.Context> context) {
            final NetworkEvent.Context ctx = context.get();

            /*
             * Начиная с 1.8/1.9 всю обработку пакета надо выносить над основным потоком сервера,
             * добавив его в качестве запланированной задачи.
             */
            if (ctx.getDirection().getReceptionSide() == LogicalSide.SERVER) {
                ctx.enqueueWork(() -> packet.server(ctx.getSender()));
                ctx.setPacketHandled(true);
            } else
                Minecraft.getInstance().addScheduledTask(() -> packet.client(clientPlayer()));
        }

        void client(EntityPlayerSP player) {}
        void server(EntityPlayerMP player) {}

        @OnlyIn(Dist.CLIENT)
        private EntityPlayerSP clientPlayer() {
            return Minecraft.getInstance().player;
        }
    }
}

Статьи:
Для 1.12.2 и ниже версий minecraft.

Создадим пакет SPacketParticles, данный пакет будет отсылаться на сервер, а затем сервер будет отсылать всем ближайшим игрокам данные о позиции частиц, которые испускает игрок. Отправлять данный пакет мы будем через метод sendToServer, вот так: NetworkHandler.NETWORK.sendToServer(new SPacketParticles());

Код:
public final class SPacketParticles extends SimplePacket {
    @Override
    public void server(EntityPlayerMP player) {
        /*
         * Здесь идёт отправка пакета с серверной стороны на клиентскую.
         * server -> client
         * Для начала, мы задаем метод отправки пакета на клиент. В нашем случае используется
         * наш своего sendToAllAround. Далее идёт наш пакет,
         * в котором мы ОБЯЗАТЕЛЬНО(Для своего sendToALLAround) прописываем позицию игрока.
         * Так же хочу отметить то, что позиция игрока берётся с сервера, а не клиента!
         */
        NetworkHandler.sendToAllAround(new CPacketParticles(player.posX, player.posY, player.posZ), player.worldObj);
    }
}
Код:
public final class CPacketParticles extends SimplePacket {
    public CPacketParticles(){}
    public CPacketParticles(double x, double y, double z) {
        buf().writeDouble(x);
        buf().writeDouble(y);
        buf().writeDouble(z);
    }

    @Override
    public void client(EntityPlayer player) {
        double
                x = buf().readDouble(),
                y = buf().readDouble(),
                z = buf().readDouble(),
                newPosX, newPosZ, speedX, speedZ,
                doublePi = 2 * Math.PI;

        final double RADIUS = 0.4;//Радиус от игрока.

        for (double i = 0; i < doublePi; i += doublePi / 50) {
            /**
             * Здесь я бы хотел, чтобы вы обратили внимание на то, что позиция игрока
             * берётся не от EntityPlayer, а от нашего пакета, this.x/y/z
             * Если мы вместо this.x/y/z укажем player.PosX/Y/Z то частицы будут
             * отображаться у другого игрока на нём, а не на вас. Т.е. будет создаваться
             * видимость того, что это он спавнит частицы, а не Вы.
             */
            newPosX = x + RADIUS * Math.cos(i);
            newPosZ = z + RADIUS * Math.sin(i);

            speedX = (newPosX - x) * 0.2D;
            speedZ = (newPosZ - z) * 0.2D;

            /**
             * Данный метод(spawnParticles) может отличаться в зависимости от версии mc!
             * @param EnumParticleTypes.FLAME - Это частицы, которые мы спавним.
             * @param newPosX - Позиция игрока по X.
             * @param y - Позиция игрока по Y.
             * @param newPosZ - Позиция игрока по Z.
             * @param speedX - Скорость движения частиц по X.
             * @param 0.0D - Скорость движения частиц по Y.
             * @param speedZ - Скорость движения частиц по Z.
             */
            player.worldObj.spawnParticle(EnumParticleTypes.FLAME, newPosX, y, newPosZ, speedX, 0.0D, speedZ);
        }
    }
}
Для 1.13 и выше версий minecraft.
Создадим пакет SPacketParticles, данный пакет будет отсылаться на сервер, а затем сервер будет отсылать всем ближайшим игрокам данные о позиции частиц, которые испускает игрок. Отправлять данный пакет мы будем через метод sendToServer, вот так: NETWORK.sendToServer(new SPacketParticles());

Код:
public class SPacketParticles extends Packet {
    @Override
    public void encode(Packet simplePacket, PacketBuffer buf) {}

    @Override
    public SPacketParticles decode(PacketBuffer buf) {
        return null;
    }

    @Override
    public void server(EntityPlayerMP player) {
        NETWORK.sendToNear(new CPacketParticles(),
                new PacketDistributor.TargetPoint(player.posX, player.posY, player.posZ, 64.0, player.dimension));
    }
}
Код:
public class CPacketParticles extends Packet {
    public double x, y, z;

    public CPacketParticles() {}
    public CPacketParticles(double x, double y, double z) {
        this.x = x;
        this.y = y;
        this.z = z;
    }

    @Override
    public void encode(Packet simplePacket, PacketBuffer buf) {
        buf.writeDouble(x);
        buf.writeDouble(y);
        buf.writeDouble(z);
    }

    @Override
    public CPacketParticles decode(PacketBuffer buf) {
        this.x = buf.readDouble();
        this.y = buf.readDouble();
        this.z = buf.readDouble();
        return null;
    }

    @Override
    public void client(EntityPlayerSP player) {
        final double twicePi = Math.PI * 2;
        double nX, nZ, speedX, speedZ;

        for (double i = 0; i < twicePi; i += twicePi / 50) {
            /*
             * Здесь я бы хотел, чтобы вы обратили внимание на то, что позиция игрока
             * берётся не от EntityPlayer, а от нашего пакета, this.x/y/z
             * Если мы вместо this.x/y/z укажем player.PosX/Y/Z то частицы будут
             * отображаться у другого игрока на нём, а не на вас. Т.е. будет создаваться
             * видимость того, что это он спавнит частицы, а не Вы.
             */
            nX = this.x + .4F * Math.cos(i);
            nZ = this.z + .4F * Math.sin(i);

            speedX = (nX - this.x) * .2D;
            speedZ = (nZ - this.z) * .2D;

            /*
             * Particles.FLAME - Это частицы, которые мы спавним.
             * nX              - Позиция игрока по X.
             * this.y          - Позиция игрока по Y.
             * nZ              - Позиция игрока по Z.
             * speedX          - Скорость движения частиц по X.
             * .0D             - Скорость движения частиц по Y.
             * speedZ          - Скорость движения частиц по Z.
             */
            player.world.spawnParticle(Particles.FLAME, nX, this.y, nZ, speedX, .0, speedZ);
        }
    }
}

Заключение:
На этом всё, если же возникнут проблемы и вопросы, обращайтесь. Помогу чем смогу.

Особые благодарности:
@Dahaka - за улучшение кода и всяческие советы.
Автор
Ivasik
Скачивания
18
Просмотры
172
Первый выпуск
Обновление
Оценка
4.80 звёзд 5 оценок

Последние обновления

  1. Обновление от 9.03.2019

    * Обновил NetworkHandler(1.13.2). Теперь использовать пакетную систему стало удобней. * Обновил...
  2. Обновление статей.

    * Портировал статью "Части от игрока" на версию 1.13.2
  3. Обновление для 1.13.2.

    * Добавил дополнительные методы, которые упростят отправку пакетов.

Последние рецензии

полезно
Отлично!
Ой, а чего код такой странный? И зачем делать надстройку над надстройкой? В курсе, что можно вместо нулла ответ отправить?
Ivasik
Ivasik
onMessage? Можно вообще от него отказаться и делать все в fromBytes.
Скачал систему, сначала работала хорошо но потом начались перебои - думал система пакетов полетела - отнес в сервис, сказали да, у вас все сломано нужно скачивать новую. В общем, вроде работает, но я ее скачивать больше не буду :)
Я не понял преимущества AbstractPacket над обычным IMessage.
Ivasik
Ivasik
Обновил, теперь думаю преимущество будет.
Сверху