- 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 . Выглядеть это будет вот так:
Для предмета :
Для тайла :
Теперь напишем класс нашего алхимического меча. В классе мы определим, что при нажатии ЛКМ на блоке свинца происходит передача всей накопленной энергии из 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);
}
}
}
}
Что в итоге?
В итоге у нас есть меч, убивая которым мы получаем очки энергии. Нажимая ЛКМ на блок свинца мы потихоньку увеличиваем содержание золота в нем. Когда энергия блока достигает 100 - он превращается в полноценный блок золота.