Записать и получить ArrayList в NBT

Версия Minecraft
1.12.2
Здравствуйте, мне требуется записать ArrayList, который содержит в себе строки(String) в NBT и передать соответственно через пакеты его на клиент.
Долго искал в интернете, нужной информации так и не нашел. Прошу помочь мне. С обычным NBT у меня нет проблем, одиночную строку передаю спокойно, а вот как целый лист - не знаю. Ясно дело, что тут нужен NBTTagList но я так и не понял как с ним работать.

Обычную строку, если что, передаю вот так:


ModTicker.java:
NBTTagCompound pTag = new NBTTagCompound();
pTag.setString("Title", titleInfo);
NetworkHandler.network.sendTo(new PacketTitles(pTag), (EntityPlayerMP)player);

PacketTitles.java:
import io.netty.buffer.ByteBuf;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraftforge.fml.common.network.ByteBufUtils;
import net.minecraftforge.fml.common.network.simpleimpl.IMessage;
import net.minecraftforge.fml.common.network.simpleimpl.IMessageHandler;
import net.minecraftforge.fml.common.network.simpleimpl.MessageContext;

public class PacketTitles implements IMessage
{
    private NBTTagCompound nbtTag;

    public PacketTitles() {}
    public PacketTitles(NBTTagCompound nbt)
    {
        this.nbtTag = nbt;
    }

    @Override
    public void fromBytes(ByteBuf buf)
    {
        nbtTag = ByteBufUtils.readTag(buf);
    }

    @Override
    public void toBytes(ByteBuf buf)
    {
        ByteBufUtils.writeTag(buf, nbtTag);
    }

    public static class Handler implements IMessageHandler<PacketTitles, IMessage>
    {
        @Override
        public IMessage onMessage(PacketTitles message, MessageContext ctx)
        {
            if(message != null)
            {
                NBTTagCompound nbt = message.nbtTag;
                whoami.Handler.Title = nbt.getString("Title");
            }

            return null;
        }
    }
}
Получаю её в классе Handler: String Title;
Сюда же и нужно передать ArrayList
 

necauqua

когда-то был anti344
Администратор
1,216
27
172
NBTTagList можно засетить в NBTTagCompound при помощи метода setTag (ну или какого-то похожего, не помню, с аргументом NBTBase).
Ты его создаёшь и в нём должны быть какие-то методы типа add или вроде того, вот не помню и сейчас пойти посмотреть не могу, но базовая идея такая - создаёшь, заполняешь из твоего листа циклом при помощи таких-то методов, и сеттишь в свой compound
 
7,099
324
1,509
Можно просто записать список строк в пакет. Сначала записать длину списка, потом последовательно записать все строки(см. ByteBufUtils), этой информации хватит, чтобы прочитать список строк при получении пакета
 
167
10
69
Обязательно ArrayList в NBT писать? или можно ArrayList отправить кленту?

Пример для 1.7.10 на основе FlansMod Network, возможно поможет.

PacketBase:
package com.flansmod.common.network;

import cpw.mods.fml.common.network.ByteBufUtils;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;

public abstract class PacketBase {
    public abstract void encodeInto(ChannelHandlerContext var1, ByteBuf var2);

    public abstract void decodeInto(ChannelHandlerContext var1, ByteBuf var2);

    public abstract void handleServerSide(EntityPlayerMP var1);

    @SideOnly(Side.CLIENT)
    public abstract void handleClientSide(EntityPlayer var1);

    public void writeUTF(ByteBuf data, String s) {
        ByteBufUtils.writeUTF8String(data, s);
    }

    public String readUTF(ByteBuf data) {
        return ByteBufUtils.readUTF8String(data);
    }
}

Отправляемый пакет:
package ru.fr0le.arena.network;

import java.util.ArrayList;

import com.flansmod.common.network.PacketBase;

import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;

public class PacketTEST extends PacketBase {

    public static ArrayList<String> list;

    public PacketTEST() {

    }

    public PacketTEST(ArrayList<String> list) {
        this.list = list;
    }

    @Override
    public void encodeInto(ChannelHandlerContext ctx, ByteBuf data) {
        data.writeInt(this.list.size());
        for(String list : this.list) {
            this.writeUTF(data, (String)list);
        }
    }

    @Override
    public void decodeInto(ChannelHandlerContext ctx, ByteBuf data) {
        int size = data.readInt();
        this.list = new ArrayList<String>(size);
        for(int i = 0; i < size; ++i) {
            this.list.add(this.readUTF(data));
        }
    }

    @Override
    public void handleServerSide(EntityPlayerMP player) {
        //Код выполняемый на стороне сервера
    }

    @Override
    @SideOnly(Side.CLIENT)
    public void handleClientSide(EntityPlayer clientPlayer) {
        //Код выполняемый на стороне клиента
        System.out.println("sendToPlayer " + this.list);
    }

}

Сам код для отправки пакета:
EntityPlayerMP player = (EntityPlayerMP)event.entity;
ArrayList<String> list = new ArrayList<String>();
list.add("one");
list.add("two");
FlansMod.getPacketHandler().sendTo((PacketBase)(new PacketTEST(list)), player);

Результат на стороне клиента:
[Client thread/INFO] [STDOUT]: [ru.fr0le.arena.network.PacketTEST:handleClientSide:52]: sendToPlayer [one, two]
 
Последнее редактирование:
7,099
324
1,509
Ох, как много кода. А между тем с использованием CodeChickenLib все просто и лаконично:
Отправка:
Java:
List<String> some = ...

PacketCustom packet = new PacketCustom("modid",1).write(some.size());
for(String v:some) //Сначала я хотел заюзать Stream#reduce, но вспомнил, что это требует ассоциативность операции, а writeString не ассоциативна. Хотя стримы все равно можно было бы юзать, если бы нужно было отправлять неупорядоченное множество, а не список
    packet.writeString(v);
packet.sendToPlayer(...);
Прием:
Java:
public class ClientPacketHandler implements ICustomPacketHandler.IClientPacketHandler {

    @Override
    public void handlePacket(PacketCustom packetCustom, Minecraft minecraft, INetHandlerPlayClient iNetHandlerPlayClient) {
        if (packetCustom.getType() == 1) {
            int listSize=packetCustom.readInt();
            List<String> r = new ArrayList<>(listSize);
            for(int i=0;i<listSize; i++)
                r.add(packetCustom.readString());
            System.out.println("Recieved string list "+r);
        }
    }
}
 
Последнее редактирование:

necauqua

когда-то был anti344
Администратор
1,216
27
172
ну как минимум:
String list = null;
list = new String(this.readUTF(data));
if(list != null) {
this.list.add(list);
}
полностью эквивалентно записи list.add(readUTF(data))

а так получается примерно то же, что с либой, только без либы
 
167
10
69
Последнее редактирование:
Обязательно ArrayList в NBT писать? или можно ArrayList отправить кленту?

Пример для 1.7.10 на основе FlansMod Network, возможно поможет.

PacketBase:
package com.flansmod.common.network;

import cpw.mods.fml.common.network.ByteBufUtils;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;

public abstract class PacketBase {
    public abstract void encodeInto(ChannelHandlerContext var1, ByteBuf var2);

    public abstract void decodeInto(ChannelHandlerContext var1, ByteBuf var2);

    public abstract void handleServerSide(EntityPlayerMP var1);

    @SideOnly(Side.CLIENT)
    public abstract void handleClientSide(EntityPlayer var1);

    public void writeUTF(ByteBuf data, String s) {
        ByteBufUtils.writeUTF8String(data, s);
    }

    public String readUTF(ByteBuf data) {
        return ByteBufUtils.readUTF8String(data);
    }
}

Отправляемый пакет:
package ru.fr0le.arena.network;

import java.util.ArrayList;

import com.flansmod.common.network.PacketBase;

import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;

public class PacketTEST extends PacketBase {

    public static ArrayList<String> list;

    public PacketTEST() {

    }

    public PacketTEST(ArrayList<String> list) {
        this.list = list;
    }

    @Override
    public void encodeInto(ChannelHandlerContext ctx, ByteBuf data) {
        data.writeInt(this.list.size());
        for(String list : this.list) {
            this.writeUTF(data, (String)list);
        }
    }

    @Override
    public void decodeInto(ChannelHandlerContext ctx, ByteBuf data) {
        int size = data.readInt();
        this.list = new ArrayList<String>(size);
        for(int i = 0; i < size; ++i) {
            this.list.add(this.readUTF(data));
        }
    }

    @Override
    public void handleServerSide(EntityPlayerMP player) {
        //Код выполняемый на стороне сервера
    }

    @Override
    @SideOnly(Side.CLIENT)
    public void handleClientSide(EntityPlayer clientPlayer) {
        //Код выполняемый на стороне клиента
        System.out.println("sendToPlayer " + this.list);
    }

}

Сам код для отправки пакета:
EntityPlayerMP player = (EntityPlayerMP)event.entity;
ArrayList<String> list = new ArrayList<String>();
list.add("one");
list.add("two");
FlansMod.getPacketHandler().sendTo((PacketBase)(new PacketTEST(list)), player);

Результат на стороне клиента:
[Client thread/INFO] [STDOUT]: [ru.fr0le.arena.network.PacketTEST:handleClientSide:52]: sendToPlayer [one, two]
А что за файл cpw который импортируется?
 
В общем ваши методы не очень мне помогли, первый из какого-то мода вообще не понял. Но проблему решил. Страшным и ужасным костылем, но решил.

Я на сервере создал переменную со строкой, циклом прогнал ArrayList и добавил все данные из листа в строку, разделяя их символом %, затем эту длинную строку отправил пакетом на клиент, где обратно расшифровал с помощью разделительного символа % и добавил каждое нужно мне слово в ArrayList на стороне клиента. Все работает, да метод страшный получился, но рабочий и мне этого хватает. Все равно спасибо всем за советы, лайки проставлю за то, что хотя бы какую-то помощь предложили, спасибо!
 

necauqua

когда-то был anti344
Администратор
1,216
27
172
чот горит, короче, вот:

Конкретно у тебя есть вот такой код
Java:
@Override
public void fromBytes(ByteBuf buf) {
    nbtTag = ByteBufUtils.readTag(buf);
}

@Override
public void toBytes(ByteBuf buf) {
    ByteBufUtils.writeTag(buf, nbtTag);
}

Но тебе нужно откуда-то куда-то передать список строк. Итого вопрос - а зачем ты вообще приплетаешь сюда NBT?
Любую структуру передаваемых данных можно описать просто полями твоего пакета и потом в одинаковом порядке в этих двух методах их писать/считывать, при помощи всяких методов в самом ByteBuf и в ByteBufUtils.

В любом случае, если тебе нужен список строк, то эти методы можно сделать так, общая структура такая-же, как тебе десять раз говорили:
Java:
@Override
public void fromBytes(ByteBuf buf) {
    int size = buf.readInt();
    List<String> list = new ArrayList<>(size);
    for (int i = 0; i < size; ++i) {
        list.add(ByteBufUtils.readUTF8String(buf));
    }
    this.list = list;
}

@Override
public void toBytes(ByteBuf buf) {
    buf.writeInt(list.size());
    for (String item : list) {
        ByteBufUtils.writeUTF8String(buf, item);
    }
}

И, естественно, если у тебя есть хоть какое-нибудь понимание вообще того что ты пишешь текстом, то надо твоё поле с тэгом поменять на лист

Java:
private List<String> list;

public PacketTitles() {}
public PacketTitles(List<String> list) {
    this.list = list;
}

И ещё там где ты шлёшь тоже NBT долой и шлём напрямую лист, в соответствии с вышепереписанным кодом
Java:
NetworkHandler.network.sendTo(new PacketTitles(TVOY_LIST_PRYAMO_TUT_TRANSLITOM), (EntityPlayerMP)player);

Если тебе кроме листа нужна ещё и строка отдельная, или вообще любая информация, не важно, то впиливаем всё по той-же схеме, поле и считываем-пишем в тех двух методах:
1. Добавляем поле private String titleOrWhat; и параметр в конструктор;
2. в начало метода fromBytes добавляем строчку titleOrWhat = ByteBufUtils.readUTF8String(buf);;
3. в начало метода toBytes добавляем строчку ByteBufUtils.writeUTF8String(buf, titleOrWhat);;
4. ну и в месте создания пакета добавляем параметр.

усё, разжевал и положил, ну надеюсь)
 
477
7
51
Только разбиваешь ещё свой объект на компоненты и отправляешь их.
Java:
                for(ItemShop s : ShopServer.items_shop) {
                    GameShop.network.sendTo(GameShop.network.createPacket(SHOP_SYNC,ShopServer.items_shop.size(),s.getId(),s.getName(),s.getPrice(),s.getDescription(),s.getItem(),s.getQuantity()), player);
                                  
                }
я всё ещё туплю, у меня 4 обьекта добавлено, он 4 раза одно и тоже отправляет,надо 1 раз но со всеми данными

Java:
int size = buf.readInt();
        int getid = buf.readInt();
        String getname = ByteBufUtils.readUTF8String(buf);
        int getprice = buf.readInt();
        String getdesc = ByteBufUtils.readUTF8String(buf);
        ItemStack stack = ByteBufUtils.readItemStack(buf);
        int getcolvo = buf.readInt();
        
        TLinkedHashSet<ItemShop> list = new TLinkedHashSet<>(size);
        for (int i = 0; i < size; ++i) {
            list.add(new ItemShop(getid, getname, getprice, getdesc, stack, getcolvo));
        }
        list.forEach(itemShop -> {
            System.out.println(itemShop.getName() + itemShop.getPrice() + itemShop.getQuantity());
       });;
 
Сверху