сохранение NBT игрока

Версия Minecraft
1.12.2
API
Forge
112
5
16
Доброго времени уток.
Недавно познакомился с таким интересным явлением как NBT.
Использую NBT игрока, давая ему X boolean для отметки того, что он уже сделал действие N.
Но при смерти и выходе с сервера NBT теряется.
Пытался пофиксить потерю при смерти таким не хитрым делом
Java:
 @SubscribeEvent
    public void playerClone(PlayerEvent.Clone e){
        NBTTagCompound orig = e.getOriginal().getEntityData();
        NBTTagCompound clone = e.getEntityPlayer().getEntityData();
        clone.setBoolean("isUsedApple", orig.getBoolean("isUsedApple"));
    }
но клону это дело не присваивается.
Так же я заметил, что всегда есть один оригинал)
Т.Е. если для теста, вместо булиня сделать integer и при использовании X предмета повышать этот
коунтер, то у оригинала будут ВСЕ использования.
Т.Е. Если по очереди убивать
Оригинал - 3 нажатия
1 клон - 3 нажатия
2 клон - 3 нажатия
И при смерти клона 2, если в ивенте PlayerEvent.Clone
вызвать getOriginal и получить с него дату, то там будут все 9 нажатий.

Хотя в PlayerEvent.Tick я пытаюсь получить тот самый Boolean, но он выдаёт false, хотя до смерти выдавал True :|.
Подскажите пожалуйста, как правильно сохранять и присваивать NBT игрока при смерти или выходе с сервера.
Спасибо
 
112
5
16
Используй капабилити.
Так, я вроде как намутил капабилити для статов(Пример)
Тырфейс

Java:
public interface IStat {
    int getStat();
    void setStat(int stat);
    void increaseStat(int statAdd);
}
Сам класс статов
Java:
public class Stat implements IStat{

    private int stat;
    @Override
    public int getStat() {
        return this.stat;
    }

    @Override
    public void setStat(int stat) {
        this.stat = stat;
    }

    @Override
    public void increaseStat(int statAdd) {
        this.stat += statAdd;
    }
}

Капабилити

Java:
public class StatCapability implements ICapabilitySerializable<NBTTagCompound> {

    @CapabilityInject(IStat.class)
    public static Capability<IStat> CAPABILITY_STAT = null;
    private final IStat instance = CAPABILITY_STAT.getDefaultInstance();

    @Override
    public boolean hasCapability(@Nonnull Capability<?> capability, @Nullable EnumFacing facing) {
        return capability == CAPABILITY_STAT;
    }

    @Nullable
    @Override
    public <T> T getCapability(@Nonnull Capability<T> capability, @Nullable EnumFacing facing) {
        if (capability == CAPABILITY_STAT) return  (T) this.instance;
        return null;
    }

    @Override
    public NBTTagCompound serializeNBT() {
        return (NBTTagCompound)CAPABILITY_STAT.getStorage().writeNBT(CAPABILITY_STAT, this.instance, null);
    }

    @Override
    public void deserializeNBT(NBTTagCompound nbt) {
        CAPABILITY_STAT.getStorage().readNBT(CAPABILITY_STAT, this.instance, null, nbt);
    }
}

И хранилище для этого дела.

Java:
public class StatStorage implements Capability.IStorage<IStat> {

    private String statName;

    public StatStorage(String statName) {
        this.statName = statName;
    }

    @Nullable
    @Override
    public NBTBase writeNBT(Capability<IStat> capability, IStat instance, EnumFacing side) {
        NBTTagCompound nbt = new NBTTagCompound();
        nbt.setInteger(statName, instance.getStat());
        return nbt;
    }

    @Override
    public void readNBT(Capability<IStat> capability, IStat instance, EnumFacing side, NBTBase nbt) {
        NBTTagCompound tag = (NBTTagCompound) nbt;
        instance.setStat(tag.getInteger(statName));
    }
}

Всё по заветам гайдов.

Но как это дело прикрутить к игроку? + Если я хочу иметь разные статы, банально силу, агилу, вынос, инту и т.п. шлак, будет ж очень тупо создавать каждый раз новые классы для разных статов
 

Eifel

Модератор
1,623
78
608
Не нужно создавать отдельные классы, ты все можешь хранить в одной капе. Если ты нашел гайд по капе, то там пишет, как это дело прикрутить к игроку(для начала ее зарегать и потом добавить в аттач ивенте). Далее, аналогично копируем капу в клон ивенте
 
112
5
16
ты все можешь хранить в одной капе
Это круто.
Если ты нашел гайд по капе, то там пишет, как это дело прикрутить к игроку
При приклеплении к игроку ловлю нул поинтер.


Java:
 public static final ResourceLocation STATS = new ResourceLocation(Reference.MODID, "stats");
    @SubscribeEvent
    public void attachCapability(AttachCapabilitiesEvent event) {
        event.addCapability(STATS, new StatCapability());
    }
Вот на этом моменте.

UPD
Сам нулпоинтер вот тут


Java:
public class StatCapability implements ICapabilitySerializable<NBTTagCompound> {

    @CapabilityInject(IStat.class)
    public static Capability<IStat> CAPABILITY_STAT;
    private IStat instance = CAPABILITY_STAT.getDefaultInstance();
...
    }

instance нулевой, да и оно понятно почему...
 
112
5
16
И при смерти не сохраняет статы :|
Java:
    @SubscribeEvent
    public void playerClone(PlayerEvent.Clone e) {
       // StatStorage statStorage = new StatStorage("str");
        if(e.isWasDeath()) {
            EntityPlayer player = e.getEntityPlayer();
            IStat newStats = player.getCapability(StatCapability.CAPABILITY_STAT, null);
            IStat oldStats = e.getOriginal().getCapability(StatCapability.CAPABILITY_STAT, null);
            player.sendMessage(new TextComponentString("STR -> " + oldStats.getStr() + " | AGL -> " + oldStats.getAgl() + " | VIT -> " + oldStats.getVit()) );
            player.sendMessage(new TextComponentString("STR -> " + newStats.getStr() + " | AGL -> " + newStats.getAgl() + " | VIT -> " + newStats.getVit()) );

            assert oldStats != null;
            assert newStats != null;
            newStats.setStr(oldStats.getStr());
            newStats.setAgl(oldStats.getAgl());
            newStats.setVit(oldStats.getVit());
        }
    }
 

Eifel

Модератор
1,623
78
608
Проверка на смерть там вовсе не нужна, иначе твой игрок потеряет свои статы в другом случае. Лучше клонировать всегда. Если ты все правильно сделал, то должно быть ок. И лучше в самой капе сделать метод, который будет отвечать за клонирование инфы из другой капы, нежели выносить эту всю логику в ивент, мало ли еще где пригодиться
 
112
5
16
Лучше в самой капе сделать метод, который будет отвечать за клонирование инфы из другой капы

Звучит суперлогично.

Если ты все правильно сделал, то должно быть ок
Вот тут я не уверен)
При смерти/переходе в энд не сохраняется ничего, и если в PlayerEvent.Tick выводить эту капу, то я получаю 2 значения - старое и нулевое. Скорее всего со стороны сервера не сохраняется она, поэтому такая беда
 
112
5
16
После долгого разбирательства, я получаю (ничего), а именно костылище такое, что можно было бы опереть 30 кодеров-инвалидов (типо меня).
В моменте где мне нужно применить эти статы, а именно PlayerEvent.Tick, я в тупую делаю проверку !world.isRemote...
Если кто знает как можно сделать по человечески, подскажите пожалуйста.
 
112
5
16
Во всяком случае примеров что у тебя происходит нет
Сейчас подробно опишу всё.

Тырфейс
Java:
public interface IStat extends Callable{

    int getStr();
    void setStr(int stat);
    void setStatsByAnotherStats(IStat stats);
    void increaseStr(int statAdd);
    void clearAll();
}

Класс статов

Java:
public class Stat implements IStat{

    private int str = 0;
    
    @Override
    public int getStr() {
        return str;
    }

    @Override
    public void setStr(int stat) {
        this.str = stat;
    }

    @Override
    public void setStatsByAnotherStats(IStat stats) {
        Stat stat = (Stat) stats;
        this.str = stat.str;
    }

    @Override
    public void increaseStr(int statAdd) {
        this.str += statAdd;
    }

    @Override
    public void clearAll() {
        this.str = 0;
    }

    @Override
    public String toString(){
        return "STR ->" + str;
    }

    @Override
    public Object call() throws Exception {
        return this;
    }
}

Капабилити
Java:
public class StatCapability implements ICapabilitySerializable<NBTTagCompound> {

    @CapabilityInject(IStat.class)
    public static Capability<IStat> CAPABILITY_STAT;
    private IStat instance = CAPABILITY_STAT.getDefaultInstance();

    @Override
    public boolean hasCapability(@Nonnull Capability<?> capability, @Nullable EnumFacing facing) {
        return capability == CAPABILITY_STAT;
    }

    @Nullable
    @Override
    @SuppressWarnings("unchecked")
    public <T> T getCapability(@Nonnull Capability<T> capability, @Nullable EnumFacing facing) {
        if (capability == CAPABILITY_STAT) return  (T) this.instance;
        return null;
    }

    @Override
    public NBTTagCompound serializeNBT() {
        return (NBTTagCompound) CAPABILITY_STAT.getStorage().writeNBT(CAPABILITY_STAT, this.instance, null);
    }

    @Override
    public void deserializeNBT(NBTTagCompound nbt) {
        CAPABILITY_STAT.getStorage().readNBT(CAPABILITY_STAT, this.instance, null, nbt);
    }

Стораге
Java:
public class StatStorage implements Capability.IStorage<IStat> {

    @Nullable
    @Override
    public NBTBase writeNBT(Capability<IStat> capability, IStat instance, EnumFacing side) {
        NBTTagCompound nbt = new NBTTagCompound();
        nbt.setInteger("strength", instance.getStr());
        return nbt;
    }

    @Override
    public void readNBT(Capability<IStat> capability, IStat instance, EnumFacing side, NBTBase nbt) {
        NBTTagCompound tag = (NBTTagCompound) nbt;
        instance.setStr(tag.getInteger("strength"));
    }
}

Регистрация в эвент хенлдере и клонирование чара
Java:
    //--------STATS-------------
    public static final ResourceLocation STATS = new ResourceLocation(Reference.MODID, "stats");


    @SubscribeEvent
    public void attachCapability(AttachCapabilitiesEvent event) {
        event.addCapability(STATS, new StatCapability());
    }

    @SubscribeEvent
    public void onPlayerLogsIn(net.minecraftforge.fml.common.gameevent.PlayerEvent.PlayerLoggedInEvent event) {
        EntityPlayer player = event.player;
        IStat stats = player.getCapability(StatCapability.CAPABILITY_STAT, null);
        player.sendMessage(new TextComponentString(stats.toString()));
    }

    @SubscribeEvent
    public void playerClone(PlayerEvent.Clone e) {
            EntityPlayer player = e.getEntityPlayer();
            IStat newStats = player.getCapability(StatCapability.CAPABILITY_STAT, null);
            IStat oldStats = e.getOriginal().getCapability(StatCapability.CAPABILITY_STAT, null);
            newStats.setStatsByAnotherStats(oldStats);

    }


Что собственно происходит.
В Player event tick Я вызываю капу и беру из нее значения, то при условии !world.isRemote ( т.е. со стороны сервера) показывает правильные статы. В другом случае, статы нулевые, которые не сохранились.
 
112
5
16
ты зачем-то статы лепишь сюда
Так это основная задумка собственно. Сохранять boolean после X действий, чтобы можно было делать цепочку последствий после X действий. А статы чисто для теста.
 
Сверху