[Гайд][1.8] NBT : Создание собственных переменных

329
13
Named Binary Tag : Создание собственных переменных

В этом гайде речь пойдет о NBT - формате хранения данных Minecraft. Подробное описание можно найти в Minecraft-Wiki, нас же в основном интересует как эту вещь использовать для написания модов. NBT есть в вещах, блоках, мобах, самом игроке и мире, в котором он находится. Можно сказать, практически везде.

Предположим, нам необходим предмет, который должен хранить в себе полезную информацию, чтобы эта информация могла меняться и не терялась при перезапуске сервера. Например, мне необходим меч, который "хранит души убитых им существ". Причем эти души не просто хранятся, а могут использоваться для "алхимического преобразования блока свинца в золото". Звучит немного странно, но приступим...

Для начала - основной класс NBTTutorialMod:
Код:
@Mod(modid = NBTTutorialMod.MODID, name = NBTTutorialMod.NAME, version = NBTTutorialMod.VERSION)
public class NBTTutorialMod
{
    public static final String MODID = "nbttutorial";
    public static final String NAME = "NBT Tutorial Mod";
    public static final String VERSION = "1.0";
    
    @Instance(MODID)
    public static NBTTutorialMod instance;
    
    @SidedProxy(clientSide="ru.mcmodding.nbttutorial.client.ClientProxy", serverSide="ru.fallout.nbttutorial.common.CommonProxy")
    public static CommonProxy proxy;

    @EventHandler
    public void init(FMLInitializationEvent event)
    {
        // Здесь мы лишь вызываем CommonProxy и ClientProxy
        proxy.init();
    }
    
    public static void info(String format, Object... data)
    {
        FMLLog.log(NBTTutorialMod.NAME, Level.INFO, format, data);
    }
}

Теперь создадим CommonProxy и ClientProxy, в которых зарегистрируем 1 блок (свинец), 1 предмет (алхимический меч), 1 тайл (для блока свинца) и 2 вкладки для креатива. Тайл - это некий хранитель данных, он будет привязан к блоку и будет хранить то количество "энергии душ", которую мы передали блоку через наш алхимический меч:

CommonProxy:
Код:
public class CommonProxy
{
    // Вкладка для предметов. В нашем случае - для алхимического меча
    public static final CreativeTabs tabNBTItem = new CreativeTabs("nbtItems")
    {
        @Override
        @SideOnly(Side.CLIENT)
        public Item getTabIconItem() { return Items.iron_sword; }
    };
    
    // Вкладка для блоков. В нашем случае - для блока свинца
    public static final CreativeTabs tabNBTBlock = new CreativeTabs("nbtBlocks")
    {
        @Override
        @SideOnly(Side.CLIENT)
        public Item getTabIconItem() { return Item.getItemFromBlock(Blocks.gold_block); }
    };

    public static Item item_magic_sword = new ItemMagicSword("magic_sword", tabNBTItem);
    public static Block block_plumbum = new BlockPlumbum("plumbum", tabNBTBlock);
    
    /**
     * Конструктор CommonProxy. В нем мы регистрируем класс,
     * который будет обрабатывать наши будущие события (Event'ы)
     */
    public CommonProxy()
    {
        MinecraftForge.EVENT_BUS.register(new CommonEventHandler());
    }

    public void init()
    {
        this.registerItems();
        this.registerBlocks();
        this.registerTiles();
        this.registerEntities();
    }

    public void registerItems()
    {
        GameRegistry.registerItem(item_magic_sword, "magic_sword");
    }
    
    public void registerBlocks()
    {
        GameRegistry.registerBlock(block_plumbum, "plumbum");
    }
    
    public void registerEntities() {}
    
    public void registerTiles()
    {
        GameRegistry.registerTileEntity(TileEntityAlchemyCube.class, "alchemycube");
    }
}

ClientProxy:
Код:
public class ClientProxy extends CommonProxy
{
    @Override
    public void init()
    {
        super.init();
        this.registerItemModel();
    }

    public void registerItemModel()
    {
        Minecraft.getMinecraft().getRenderItem().getItemModelMesher().register(item_magic_sword, 0, new ModelResourceLocation(NBTTutorialMod.MODID + ":" + "magic_sword", "inventory"));
        Minecraft.getMinecraft().getRenderItem().getItemModelMesher().register(Item.getItemFromBlock(block_plumbum), 0, new ModelResourceLocation(NBTTutorialMod.MODID + ":" + "plumbum", "inventory"));
    }
}

Теперь нам необходимо Создать классы меча и блока. Но для начала отвлечемся на теорию, а именно определимся с форматом хранения данных в NBT.

Меч (предмет) изначально не имеет NBT совсем. Поэтому мы создадим пустой NBT, а затем добавим в него свою собственную ветвь, имя которой дадим согласно MODID . Выглядеть это будет вот так:

Для предмета :
image.png


Для тайла :
image.png


Теперь напишем класс нашего алхимического меча. В классе мы определим, что при нажатии ЛКМ на блоке свинца происходит передача всей накопленной энергии из NBT меча в NBT тайла, привязанного в блоку.

ItemMagicSword:
Код:
public class ItemMagicSword extends Item
{
    /**
     * Конструктор класса. Тут стоит обратить особое внимание
     * на размер стака (он равен 1). Это необходимо, чтобы NBT
     * корректно работал.
     */
    public ItemMagicSword(String par1ItemName, CreativeTabs par2CreativeTab)
    {
        super();
        this.setUnlocalizedName(NBTTutorialMod.MODID + "." + par1ItemName);
        this.setCreativeTab(par2CreativeTab);
        this.setMaxStackSize(1);
    }
    
    @Override
    public boolean onItemUse(ItemStack stack, EntityPlayer playerIn, World worldIn, BlockPos pos, EnumFacing side, float hitX, float hitY, float hitZ)
    {
        if (!worldIn.isRemote && worldIn.getBlockState(pos).getBlock() instanceof BlockPlumbum)
        {
            TileEntityAlchemyCube tile = (TileEntityAlchemyCube) worldIn.getTileEntity(pos);
            if (getGold(stack) > 0)
            {
                tile.gold += getGold(stack);
                playerIn.addChatMessage(new ChatComponentText("Блоку передано " + this.getGold(stack) + " очков алхимической энергии"));
                resetGold(stack);
            }
            if (tile.gold >= 100)
            {
                worldIn.destroyBlock(pos, false);
                worldIn.setBlockState(pos, Blocks.gold_block.getDefaultState());
                playerIn.addChatMessage(new ChatComponentText("Блок накопил достаточно энергии и превратился в золото"));
            }
            else
            {
                playerIn.addChatMessage(new ChatComponentText("Этот блок накопил " + tile.gold + " очков алхимической энергии"));
                ((BlockPlumbum) worldIn.getBlockState(pos).getBlock()).updateAlchemyLevel(worldIn, pos, playerIn);
            }
        }
        return false;
    }
    
    /**
     * Вот здесь как раз и происходит все волшебство создания NBT
     */
    @Override
    public void onUpdate(ItemStack stack, World worldIn, Entity entityIn, int itemSlot, boolean isSelected)
    {
        /*
         * Проверяем, есть ли у предмета NBT. Если нет - то создаем его,
         * а в нем создаем тег nbttutorial, в котором хранятся 2 параметра:
         * owner - тот, кто владел этим мечом
         * gold - количество энергии для преобразования свинца в золото
         */
        if (!stack.hasTagCompound())
        {
            stack.setTagCompound(stack.getSubCompound("tag", true));
            NBTTagCompound itemCompound = new NBTTagCompound();
            itemCompound.setString("owner", ((EntityPlayer)entityIn).getDisplayNameString());
            itemCompound.setInteger("gold", 0);
            stack.getTagCompound().setTag(NBTTutorialMod.MODID, itemCompound);
        }
        else
        {
            /*
             * Если меч подобрал кто-то другой, то он получает нового хозяина,
             * а счетчик энергии сбросится на ноль.
             */
            String nameOwner = stack.getTagCompound().getCompoundTag(NBTTutorialMod.MODID).getString("owner");
            String namePlayer = ((EntityPlayer)entityIn).getDisplayNameString();
            if (!nameOwner.equals(namePlayer))
            {
                stack.getTagCompound().getCompoundTag(NBTTutorialMod.MODID).setString("owner", namePlayer);
                this.resetGold(stack);
            }
        }
    }
    
    @SideOnly(Side.CLIENT)
    public void addInformation(ItemStack stack, EntityPlayer playerIn, List tooltip, boolean advanced)
    {
        if (stack.getSubCompound(NBTTutorialMod.MODID, false) != null)
        {
            tooltip.add("Золота накоплено : " + stack.getTagCompound().getCompoundTag(NBTTutorialMod.MODID).getInteger("gold"));
        }
    }
    
    // Метод, вынимающий их NBT значение золота
    public int getGold(ItemStack stack)
    {
        return stack.getTagCompound().getCompoundTag(NBTTutorialMod.MODID).getInteger("gold");
    }
    
    // Метод, добавляющий в NBT определенное количество золота
    public void addGold(EntityPlayer playerIn, ItemStack stack, int count)
    {
        int gold = stack.getTagCompound().getCompoundTag(NBTTutorialMod.MODID).getInteger("gold");
        stack.getTagCompound().getCompoundTag(NBTTutorialMod.MODID).setInteger("gold", gold + count);
        playerIn.addChatMessage(new ChatComponentText("Получено " + count + " очков алхимической энергии"));
    }
    
    // Метод, сбрасывающий счетчик золота
    public void resetGold(ItemStack stack)
    {
        stack.getTagCompound().getCompoundTag(NBTTutorialMod.MODID).setInteger("gold", 0);
    }
}

Теперь код блока BlockPlumbum. Следует заметить, что он расширяет не класс Block, а класс BlockContainer, который умеет создавать тайл.
Код:
public class BlockPlumbum extends BlockContainer
{
    public static final PropertyInteger MAGIC = PropertyInteger.create("magic", 0, 4);
    
    public BlockPlumbum(String par1BlockName, CreativeTabs par2CreativeTab)
    {
        super(Material.iron);
        this.setUnlocalizedName(NBTTutorialMod.MODID + "." + par1BlockName);
        this.setCreativeTab(par2CreativeTab);
        this.setDefaultState(this.blockState.getBaseState().withProperty(MAGIC, Integer.valueOf(0)));
    }
    
    /*
     * Проверяем уровень накопленной алхимической энергии
     * и меняем состояние блока в зависимости от того,
     * сколько энергии хранит тайл.
     */
    public void updateAlchemyLevel(World worldIn, BlockPos pos, EntityPlayer player)
    {
        TileEntityAlchemyCube tile = (TileEntityAlchemyCube) worldIn.getTileEntity(pos);
        worldIn.setBlockState(pos, this.getBlockState().getBaseState().withProperty(MAGIC, Integer.valueOf(tile.gold / 20)));
    }
    
    /*
     * Указываем какой тайл создавать при установке блока
     */
    @Override
    public TileEntity createNewTileEntity(World worldIn, int meta)
    {
        return new TileEntityAlchemyCube();
    }
    
    @Override
    protected BlockState createBlockState()
    {
        return new BlockState(this, new IProperty[] {MAGIC});
    }
    
    @Override
    public IBlockState getStateFromMeta(int meta)
    {
        return this.getDefaultState().withProperty(MAGIC, Integer.valueOf(meta));
    }
    
    @Override
    public int getMetaFromState(IBlockState state)
    {
        return (int) state.getValue(MAGIC);
    }
    
    /*
     * Этот метод необходим потому, что BlockContainer по умолчанию
     * возвращает -1 , а нам нужна 3.
     */
    @Override
    public int getRenderType()
    {
        return 3;
    }
}

И, наконец, код тайла (TileEntityAlchemyCube) для блока свинца. Он будет содержать NBT и хранить в нем накопленную алхимическую энергию.

Код:
public class TileEntityAlchemyCube extends TileEntity
{
    public int gold;
    
    public TileEntityAlchemyCube()
    {
        this.gold = 0;
    }
    
    @Override
    public void readFromNBT(NBTTagCompound compound)
    {
        super.readFromNBT(compound);
        NBTTagCompound tagCompound = (NBTTagCompound) compound.getTag(NBTTutorialMod.MODID);
        gold = tagCompound.getInteger("gold");
    }
    
    @Override
    public void writeToNBT(NBTTagCompound compound)
    {
        super.writeToNBT(compound);
        NBTTagCompound tagCompound = new NBTTagCompound();
        tagCompound.setInteger("gold", gold);
        compound.setTag(NBTTutorialMod.MODID, tagCompound);
    }
    
    @Override
    public boolean shouldRefresh(World world, BlockPos pos, IBlockState oldState, IBlockState newSate)
    {
        return false;
    }
}

Осталось определиться с кодом, который будет заряжать наш алхимический меч. Умирая от меча, мобы должны передавать мечу игрока некоторое количество энергии. В Forge есть замечательный Event - LivingDeathEvent, который мы используем в нашел классе-обработчике событий CommonEventHandler:

Код:
public class CommonEventHandler
{
    @SubscribeEvent
    public void onLivingDeathEvent(LivingDeathEvent event)
    {
        // Если кто-то умер от руки (меча) игрока
        if (!event.entity.worldObj.isRemote && event.source.getSourceOfDamage() instanceof EntityPlayerMP)
        {
            // И этот игрок был вооружен мечом ItemMagicSword
            EntityPlayerMP player = (EntityPlayerMP) event.source.getSourceOfDamage();
            if (player.getCurrentEquippedItem().getItem() instanceof ItemMagicSword)
            {
                // То добавим этому мечу 5 единиц энергии
                ((ItemMagicSword) player.getCurrentEquippedItem().getItem()).addGold(player, player.getCurrentEquippedItem(), 5);
            }
        }
    }
}

Что в итоге?


owgYQKV.png


DM6DKH3.png


В итоге у нас есть меч, убивая которым мы получаем очки энергии. Нажимая ЛКМ на блок свинца мы потихоньку увеличиваем содержание золота в нем. Когда энергия блока достигает 100 - он превращается в полноценный блок золота.
 
329
13
Alex090909 написал(а):
супер ваще то что надо!
А я тебя просил рассказать о твоем моде. Просто решил на его примере создать гайд по NBT.
Но предупреждаю: это все - для 1.8 . Для этой версии здесь пишут единицы.
 

timaxa007

Модератор
5,831
409
672
nickita45, судя по-написанному - работа с NBT на 1.7.х и 1.8.х не отличается. Может и есть пару каких=то фишек, но их не знаю.
 
329
13
timaxa007 написал(а):
nickita45, судя по-написанному - работа с NBT на 1.7.х и 1.8.х не отличается. Может и есть пару каких=то фишек, но их не знаю.
Полностью согласен, в плане NBT обе версии идентичны. А вот в плане создания предметов и блоков 1.8 своими JSON-моделями крови пьёт немерено.
 
Как и где (после проверки чего) можно создать NBT переменную для игрока?
 

timaxa007

Модератор
5,831
409
672
4el0vek, используй эвент "EntityConstructing" и создать класс с:
Код:
implements IExtendedEntityProperties

Код:
@SubscribeEvent
public void addNBTforPlayer(EntityEvent.EntityConstructing event) {
    if (event.entity instanceof EntityPlayer) {
        EntityPlayer player = (EntityPlayer)event.entity;
        if (PlayerMana.get(player) == null) PlayerMana.reg(player);
    }
}
Код:
public class PlayerMana implements IExtendedEntityProperties {

    public final static String MANA_TAG = "ManaPlayer";
    //private EntityPlayer player;
    //private World world;
    private int mana, mana_max;

    public PlayerMana(EntityPlayer player) {
        //this.player = player;
        mana = mana_max = 0;
    }

    public static final void reg(EntityPlayer player) {
        player.registerExtendedProperties(PlayerMana.MANA_TAG, new PlayerMana(player));
    }

    public static final PlayerMana get(EntityPlayer player) {
        return (PlayerMana)player.getExtendedProperties(MANA_TAG);
    }
    //------------------------------------
    public void addMana(int mana) {
        this.mana += mana;
    }

    public void setMana(int mana) {
        this.mana = (mana >= 0 ? mana : 0);
    }

    public int getMana() {return mana;}
    //------------------------------------
    public void addManaMax(int mana_max) {
        this.mana_max += mana_max;
    }

    public void setManaMax(int mana_max) {
        this.mana_max = (mana_max >= 0 ? mana_max : 0);
    }

    public int getManaMax() {return mana_max;}
    //------------------------------------
    public void quicklyManaMax() {mana = mana_max;}
    //------------------------------------
    @SideOnly(Side.CLIENT)
    public static void setClientPlayer(int mana, int mana_max) {
        net.minecraft.client.entity.EntityClientPlayerMP player = UtilSMT.getPlayerClient();
        if (player != null && PlayerMana.get(player) != null) {
            PlayerMana.get(player).setMana(mana);
            PlayerMana.get(player).setManaMax(mana_max);
        }
    }
    //------------------------------------
    @Override
    public void init(Entity entity, World world) {
        //this.world = world;
    }

    @Override
    public void saveNBTData(NBTTagCompound nbt) {
        NBTTagCompound nbt_tag = new NBTTagCompound();
        nbt_tag.setInteger("Mana", mana);
        nbt_tag.setInteger("ManaMax", mana_max);
        nbt.setTag(MANA_TAG, nbt_tag);
    }

    @Override
    public void loadNBTData(NBTTagCompound nbt) {
        NBTTagCompound nbt_tag = (NBTTagCompound)nbt.getTag(MANA_TAG);
        if (nbt_tag != null) {
            mana = nbt_tag.getInteger("Mana");
            mana_max = nbt_tag.getInteger("ManaMax");
        }
    }

}

Код:
public ItemStack onItemRightClick(ItemStack is, World world, EntityPlayer player) {
    PlayerMana player_mana = PlayerMana.get(player);
    if (player_mana != null) {
        int mana = player_mana.getMana;
        --mana;
        player_mana.setMana(mana);
    }
    return super.onItemRightClick(is, world, player);
}
 
timaxa007, огромное спасибо, всё понятно, кроме одного. Что делает этот метод?
Код:
UtilSMT.getPlayerClien();
 

timaxa007

Модератор
5,831
409
672
4el0vek, если ты про "UtilSMT.getPlayerClient()", то я забыл убрать, хотя может и пригодиться у кого как.
Он возвращает "FMLClientHandler.instance().getClientPlayerEntity()", а он при обычных случаях, возвращает "Minecraft.getMinecraft().thePlayer".
Используя эвент "EntityJoinWorldEvent" на серверной стороне, должен отправить пакеты на клиент игрока его значения на сервере, используя у меня метод "PlayerMana.setClientPlayer(int mana, int mana_max)" срабатывая на клиентской стороне. По крайней мере у меня было такое, что на сервере были нужные изменённые значения, а на клиенте новые (не изменённые) значение, не те что на сервере.
[merge_posts_bbcode]Добавлено: 01.10.2015 19:30:50[/merge_posts_bbcode]

И кстати, написанный код для примера, был набросан для примера и теста. По этому некоторые методы не проработаны до конца.
 
timaxa007, ещё раз спасибо, последний вопрос. Подобный метод мне, скорее всего, потребуется. Где-нибудь на форуме есть гайд по работе с пакетами?
 

timaxa007

Модератор
5,831
409
672
1,087
2
Не знаю, будет ли полезно или нет (А тем кто собирается делать моды и для англ. аудитории и для рус. аудитории это будет полезно), но вместо этого текста из русских буковок лучше сделать так:
Код:
StatCollector.translateToLocal("modid.chat.*")
Не обязательно прям "modid.chat".
 
Понимаю, что некропостинг, но возникла острая необходимость разобраться в NBT. Нашел только сей гайд, но тут отсутствует просто-напросто нужный как раз фрагмент. Может, может кто посоветовать другой гайд по работе с NBT, как создается база. Или может у кого сохранился этот целиковый? Буду рад любой помощи.
 

Вложения

  • 1518700392145.png
    1518700392145.png
    42.4 KB · Просмотры: 39

CMTV

Основатель
Администратор
1,304
4
601
Ну, создать этого гайда (@TaoGunner) очень давно не появлялся на форуме, так что надо искать другой. Может, кто-то возьмется.
 

Eifel

Модератор
1,623
78
608
Вот же еще один гайд. Правда для 1.7.10 но там все что касается НБТ должно быть неизменно
 
Сверху