[1.12] Создание своего мода. Перенесено на сайт!

Icosider

Kotliner
Администратор
3,603
99
664
Предисловие:
В связи с тем, что на форуме стали активно появляется темы данного рода: "[1.10.2] Как зарегать текстуру для предмета", "[1.11] Памагите!!11", "[1.10.2] Как создать предмет?!?", я был вынужден создать данную тему, где по полочкам разложу, как написать свой мод для mc версий 1.8+. Базовые статьи будут выходить часто, чтобы как можно быстрее наполнить тему, а вот статьи продвинутые будут выходить по 2-3 в неделю, так же они будут создаваться в случайном порядке. Так же скорость создания тех или иных статей зависит только от вас.
smile87.gif


Статьи:
Мечтали ли вы сделать себе новый эффект для зелья? Конечно же мечтали:) Сейчас я расскажу как создать свой эффект для зелья.

Для начала создадим класс PotionSnowman.

Код:
public class PotionSnowman extends Potion
{
   //Путь к нашей текстуре с иконками
   private static final ResourceLocation ICON = new ResourceLocation("textures/gui/container/inventory.png");

   /**
    * Конструктор который передаст наши значения родителю.
    * @param isBadEffectIn
    * @param liquidColorIn
    */
   public PotionSnowman(boolean isBadEffectIn, int liquidColorIn)
   {
       super(isBadEffectIn, liquidColorIn);
       setRegistryName("potionSnowman");
       //Это что-то типа UnlocalizedName для предмета
       setPotionName("effect.snowman");
       /*
        *   Полезный ли это эффект. Так же влияет на позицию иконки на экране.
        */
       setBeneficial();
   }

   //Данный метод отвечает за рендер иконки в GuiInventory.
   @Override
   @SideOnly(Side.CLIENT)
   public void renderInventoryEffect(int x, int y, PotionEffect effect, Minecraft mc)
   {
       //Вызываем нашу текстуру.
       mc.renderEngine.bindTexture(ICON);
       /**
        * Отрисовываем нашу иконку на текстуре
        * 1 - Позиция по оси X
        * 2 - Позиция по оси Y
        * 3 - Позиция иконки на самой текстуре по X
        * 4 - Позиция иконки на самой текстуре по Y
        * 5 - Ширина иконки
        * 6 - Высота иконки
        */
       mc.currentScreen.drawTexturedModalRect(x + 10, y + 11, 76, 238, 10, 10);
   }

   //Данный метод отвечает за рендер иконки на экране.
   @Override
   @SideOnly(Side.CLIENT)
   public void renderHUDEffect(int x, int y, PotionEffect effect, Minecraft mc, float alpha)
   {
       mc.renderEngine.bindTexture(ICON);
       mc.ingameGUI.drawTexturedModalRect(x + 7, y + 7, 76, 238, 10, 10);
   }
}


Теперь создадим класс PotionsRegister.
Код:
public class PotionsRegister
{
   public static PotionSnowman POTIONSNOWMAN = new PotionSnowman(false, 16777215);

   public static void register()
   {
       GameRegistry.register(POTIONSNOWMAN);
   }
}

И добавим в ServerProxy в метод preInit() такой код:
Код:
PotionsRegister.register();

Теперь добавим в EventsHandler вот такое событие:
Код:
@SubscribeEvent
   public void onGround(LivingEvent.LivingUpdateEvent e)
   {
       //Проверяем сущность на игрока
       if(e.getEntity() instanceof EntityPlayer)
       {
           //Получение нашего игрока
           EntityPlayer player = (EntityPlayer) e.getEntity();

           //Условие, если наше зелье активно и игрок на земле, то ставить снег на месте игрока.
           if (player.isPotionActive(PotionsRegister.potionSnowman) && player.onGround)
           {
               int i, j, k;

               for (int l = 0; l < 4; ++l)
               {
                   i = MathHelper.floor_double(player.posX + (double) ((float) (l % 2 * 2 - 1) * 0.25F));
                   j = MathHelper.floor_double(player.posY);
                   k = MathHelper.floor_double(player.posZ + (double) ((float) (l / 2 % 2 * 2 - 1) * 0.25F));
                   BlockPos blockpos = new BlockPos(i, j, k);//получение позиции блока, на котором стоит игрок.

                   //Если под ногами игрока ничего нет и снег можно поставить, то мы соответственно ставим слой снега.
                   if (player.worldObj.getBlockState(blockpos).getMaterial() == Material.AIR && Blocks.SNOW_LAYER.canPlaceBlockAt(player.worldObj, blockpos))
                   {
                       player.worldObj.setBlockState(blockpos, Blocks.SNOW_LAYER.getDefaultState());
                   }
               }
           }
       }
   }

Заходим в игру и прописываем команду
Код:
/effect @p modexample:potionSnowman 1000
и начинаем ходить по земле ставя под ногами снег. Чтобы использовать наш эффект в предмете или ещё где нибудь, пропишем такой код:
Код:
player.addPotionEffect(new PotionEffect(PotionSnowman.POTIONSNOWMAN, this.duration, 1));
Создание растения такое же простое, как и создание блока.

Создадим класс BlockDelphinium:
Код:
/**
*  Как вы могли заметить, что вместо Block мы прописали BlockBush.
*  Это некая заготовка/форма для простого создания своего растения.
*/
public class BlockDelphinium extends BlockBush
{
   public BlockDelphinium(String name)
   {
       setRegistryName(name);
       setUnlocalizedName(name);
       //Прочность блока
       setHardness(0);
       setCreativeTab(TabsHandler.TAB_BLOCKS);
   }

   /**
    *   Квадрат столкновения. В данном случаем это заполненая зона 16x16.
    */
   @Override
   public AxisAlignedBB getBoundingBox(IBlockState state, IBlockAccess source, BlockPos pos) {
       return FULL_BLOCK_AABB;
   }
}

зарегистрируем. Теперь добавим модель и текстуру.

Заходим в игру и видим:
6880eb66dff45551c113437cc52c6f38.png
С растениями в один блок всё понятно, а вот как сделать в два? Ведь в два блока круче чем какой то куст в один:)

Создадим класс BlockReed:
Код:
public class BlockReed extends BlockBush
{
   /**
    * Переменная которая задаёт для состояния блока переменную half.
    */
   public static final PropertyEnum<BlockDoublePlant.EnumBlockHalf> HALF = PropertyEnum.create("half", BlockDoublePlant.EnumBlockHalf.class);

   public BlockReed(String name)
   {
       setRegistryName(name);
       setUnlocalizedName(name);
       setCreativeTab(TabsHandler.TAB_BLOCKS);
       //Прочность блока
       setHardness(0);
       /**
        * Стандартные состояния, которые должны быть. В данном случае это half=lower и half=upper.
        * Про состояния блоков будет отдельная статья!
        */
       setDefaultState(this.blockState.getBaseState().withProperty(HALF, BlockDoublePlant.EnumBlockHalf.LOWER).withProperty(HALF, BlockDoublePlant.EnumBlockHalf.UPPER));
   }

   /**
    *  Квадрат столкновения. В данном случаем это заполненая зона 16x16.
    */
   @Override
   public AxisAlignedBB getBoundingBox(IBlockState state, IBlockAccess source, BlockPos pos)
   {
       return FULL_BLOCK_AABB;
   }

   /**
    *  Если под нашим растением нету блока, то уничтожать нижний и верхний блок растения, и дропать блок.
    */
   @Override
   protected void checkAndDropBlock(World worldIn, BlockPos pos, IBlockState state)
   {
       if (!this.canBlockStay(worldIn, pos, state))
       {
           boolean flag = state.getValue(HALF) == BlockDoublePlant.EnumBlockHalf.UPPER;
           BlockPos blockpos = flag ? pos : pos.up();
           BlockPos blockpos1 = flag ? pos.down() : pos;
           Block block = flag ? this : worldIn.getBlockState(blockpos).getBlock();
           Block block1 = flag ? worldIn.getBlockState(blockpos1).getBlock() : this;

           if (!flag) this.dropBlockAsItem(worldIn, pos, state, 0);

           if (block == this)
           {
               worldIn.setBlockState(blockpos, Blocks.AIR.getDefaultState(), 2);
           }

           if (block1 == this)
           {
               worldIn.setBlockState(blockpos1, Blocks.AIR.getDefaultState(), 3);
           }
       }
   }

   /**
    *  Делаем проверку на то, что есть ли под верхним блоком нижний.
    */
   @Override
   public boolean canBlockStay(World worldIn, BlockPos pos, IBlockState state)
   {
       if (state.getBlock() != this) return super.canBlockStay(worldIn, pos, state);
       if (state.getValue(HALF) == BlockDoublePlant.EnumBlockHalf.UPPER)
       {
           return worldIn.getBlockState(pos.down()).getBlock() == this;
       }
       else
       {
           IBlockState iblockstate = worldIn.getBlockState(pos.up());
           return iblockstate.getBlock() == this && super.canBlockStay(worldIn, pos, iblockstate);
       }
   }

   /**
    *  Если блок был установлен, то ставим над ним верхний блок.
    */
   @Override
   public void onBlockPlacedBy(World worldIn, BlockPos pos, IBlockState state, EntityLivingBase placer, ItemStack stack)
   {
       worldIn.setBlockState(pos.up(), this.getDefaultState().withProperty(HALF, BlockDoublePlant.EnumBlockHalf.UPPER), 2);
   }

   /**
    *  Проверка на сломаный блок
    */
   @Override
   public void onBlockHarvested(World worldIn, BlockPos pos, IBlockState state, EntityPlayer player)
   {
       //Если это верхний блок
       if (state.getValue(HALF) == BlockDoublePlant.EnumBlockHalf.UPPER)
       {
           if (worldIn.getBlockState(pos.down()).getBlock() == this)
           {
               if (player.capabilities.isCreativeMode)
               {
                   worldIn.setBlockToAir(pos.down());
               }
               else
               {
                   if (worldIn.isRemote)
                   {
                       worldIn.setBlockToAir(pos.down());
                   }
                   else if (player.getHeldItemMainhand() != null && player.getHeldItemMainhand().getItem() == Items.SHEARS)
                   {
                       worldIn.setBlockToAir(pos.down());
                   }
                   else
                   {
                       worldIn.destroyBlock(pos.down(), true);
                   }
               }
           }
       }
       else if (worldIn.getBlockState(pos.up()).getBlock() == this)
       {
           worldIn.setBlockState(pos.up(), Blocks.AIR.getDefaultState(), 2);
       }

       super.onBlockHarvested(worldIn, pos, state, player);
   }

   /**
    *  Получение метатега.
    */
   @Override
   public IBlockState getStateFromMeta(int meta)
   {
       return (meta & 8) > 0 ? this.getDefaultState().withProperty(HALF, BlockDoublePlant.EnumBlockHalf.UPPER) : this.getDefaultState().withProperty(HALF, BlockDoublePlant.EnumBlockHalf.LOWER);
   }

   /**
    *  Получение метатега.
    */
   @Override
   public int getMetaFromState(IBlockState state)
   {
       return state.getValue(HALF) == BlockDoublePlant.EnumBlockHalf.UPPER ? 8 : 0;
   }

   /**
    *  Можно ли разместить блок сверху
    */
   @Override
   public boolean canPlaceBlockAt(World worldIn, BlockPos pos)
   {
       return super.canPlaceBlockAt(worldIn, pos) && worldIn.isAirBlock(pos.up());
   }

   /**
    *  Регистрация состояния блока.
    */
   @Override
   protected BlockStateContainer createBlockState()
   {
       return new BlockStateContainer(this, new IProperty[] {HALF});
   }
}
На будущие рекомендую сделать отдельный класс, допустим DoubleBlock, и засунуть туда весь код, кроме конструктора, чтобы потом не создавать одно и тоже для каждого растения в два блока.

Теперь зарегистрируем наш блок. Добавим модели, которые я сделал для вас:)
Запускаем игру и видим, что у нас получилось.
c39f7db54f915f68caf15e4550d589ef.png
Метадата это некий "тип" для предмета, она нужна для создания большого количество предметов одним классом, прочности(как в ic2, заряженный предмет 133:1 разряженный 133:27) и т.п. вещам. В данном уроке мы рассмотрим прочность и добавление цвета к каждому предмету с различной метадатой.

Пропишем данный метод в конструктор нашего предмета:
Код:
//Данный конструктор объявляет о том, что наш предмет имеет подтипы
setHasSubtypes(true);

Создадим в классе с предметом getSubItems:
Код:
   /**
    * Данный метод возвращает предметы с метадатой
    * @param itemIn - это наш предмет
    * @param tab - получение вкладки (?)
    * @param subItems - лист в котором хранятся наши предметы с метадатой.
    */
   @Override
   @SideOnly(Side.CLIENT)
   public void getSubItems(Item itemIn, CreativeTabs tab, NonNullList<ItemStack>subItems)
   {
       /**
        * 1 - наш предмет
        * 2 - количество
        * 3 - метадата, начинается с 0
        */
       subItems.add(new ItemStack(itemIn, 1, 0));
       subItems.add(new ItemStack(itemIn, 1, 1));
       subItems.add(new ItemStack(itemIn, 1, 2));
       subItems.add(new ItemStack(itemIn, 1, 3));
   }

И такой метод:
Код:
    /**
     * Данный метод возвращает отображаемое название предмета
     * @param stack - наш предмет
     * @return
     */
    @SideOnly(Side.CLIENT)
    public String getItemStackDisplayName(final ItemStack stack)
    {
        final TextFormatting s;

        //Получаем метадату и проверяем
        switch(stack.getMetadata())
        {
            case 0: s = TextFormatting.DARK_GRAY; break;//Для 0 метадаты цвет тёмно-серый
            case 1: s = TextFormatting.BLUE; break;//Для 1 метадаты цвет синий
            case 2: s = TextFormatting.YELLOW; break;//Для 2 метадаты цвет жёлтый
            case 3: s = TextFormatting.DARK_RED; break;//Для 3 метадаты цвет тёмно-красный
            default: s = TextFormatting.GRAY; break;//Для всех остальный метадат серый цвет
        }

        //Возвращаем название предмета
        return s + I18n.translateToLocal(getUnlocalizedName() + ".name");
    }

Теперь перейдём в класс ItemsRegister и добавим в метод registerRender такой код:
Код:
Minecraft.getMinecraft().getRenderItem().getItemModelMesher().register(SPHERE, 1, new ModelResourceLocation(SPHERE.getRegistryName(), "inventory"));
        Minecraft.getMinecraft().getRenderItem().getItemModelMesher().register(SPHERE, 2, new ModelResourceLocation(SPHERE.getRegistryName(), "inventory"));
        Minecraft.getMinecraft().getRenderItem().getItemModelMesher().register(SPHERE, 3, new ModelResourceLocation(SPHERE.getRegistryName(), "inventory"));
Как вы могли заметить, после SPHERE значение с 0 поменялось на 1, 2, 3 - это метадата нашего предмета, мы можем подгружать для каждой меты свою модель, но в данном случае мы подгружаем стандартную модель сферы.
e77074551e8bc4ff4f4cad607e07eca8.gif
В этой статье вы научитесь делать обновления к своему моду. Перейдём на GitHubGist и создадим файл update.json.

8d7695dd3709d4c539d6dc27a437a8f1.png


Нажимаем "Create public gist", нас перебросит к нашему файлу.

693879d8af584140888d91e32bd39ba1.png

1 - С помощью этой кнопки мы будем получать полный путь до нашего файла, а затем копировать его в наш мод.
2 - С помощью этой кнопки мы будем вносить изменения в наш файл(Добавлять новые версии, описание фиксов и т.п.)

Нажимаем на кнопку "Raw" и копируем ссылку из адресной строки! Теперь переходим в среду разработки с нашим модом. Зайдём в главный класс и в аннотации @mod пропишем такую переменную:
Код:
updateJSON = ""
затем вставим туда нашу ссылку:
Код:
updateJSON = "https://gist.githubusercontent.com/WildsHearts/c71a54eb8491e43eae1547fc91bf7364/raw/cb295aa31051b3ad42706b2616ac0bd128bb8a7a/update.json"
изменим для проверки нашу переменную version дав ей значение "1.0.0.7" и зайдём в игру. И вот, что у нас получилось.
[video=youtube]
В данной статье Вы научитесь создавать блок-контейнер или по простому, хранилище.

Создадим блок-хранилище:
Код:
public class BlockWatcher extends BlockContainer
{
   public BlockWatcher(String name)
   {
       super(Material.WOOD);
       setRegistryName(name);
       setUnlocalizedName(name);
       setSoundType(SoundType.WOOD);
       setHardness(2.0F);
       setCreativeTab(TabsHandler.TAB_BLOCKS);
   }

   /**
    *  Данный метод срабатывает когда с блоком произошло взаимодействие. В нашем случаи если игрок кликнул, то открывает
    *  окно с ивентарём.
    */
   @Override
   public boolean onBlockActivated(World worldIn, BlockPos pos, IBlockState state, EntityPlayer playerIn, EnumHand hand, EnumFacing side, float hitX, float hitY, float hitZ)
   {
      if(!worldIn.isRemote)
      {
         TileEntity entity = worldIn.getTileEntity(pos);
         if(entity instanceof TileWatcher)
         {
             playerIn.openGui(Core.INSTANCE, GuiHandler.GUI_WATCHER, worldIn, pos.getX(), pos.getY(), pos.getZ());
         }
      }
     return true;
   }

   /**
    *  Данный метод создаёт для нашего блок сущность.
    */
   @Override
   public TileEntity createNewTileEntity(World worldIn, int meta)
   {
       return new TileWatcher();
   }

   @Override
   public void breakBlock(World worldIn, BlockPos pos, IBlockState state)
   {
       TileEntity tileEntity = worldIn.getTileEntity(pos);

       if(tileEntity instanceof TileWatcher)
       {
           TileWatcher watcher = (TileWatcher) tileEntity;
           InventoryHelper.dropInventoryItems(worldIn, pos, watcher.basic);
           worldIn.updateComparatorOutputLevel(pos, this);
       }

       super.breakBlock(worldIn, pos, state);
   }
}
И зарегистрируем его.

Создадим TileEntity:

Код:
public class TileWatcher extends TileEntity
{
   public InventoryBasic basic;

   public TileWatcher()
   {
       /**
        *   Создадим новый инвентарь для TileEntity.
        *   @param tile - это название нашего инвентаря.
        *   @param customName - задаёт кастомное имя.
        *   @param slotCount - количество слотов
        */
       basic = new InventoryBasic("invWatcher", false, 15);
   }

   /**
    *   Дабы наши вещи сохранялись, мы будем записывать их в NBT самого TileEntity.
    */
   @Override
   public NBTTagCompound writeToNBT(NBTTagCompound compound)
   {
       super.writeToNBT(compound);
       NBTTagList list = new NBTTagList();

       for(int i = 0; i < this.basic.getSizeInventory(); ++i)
       {
           if(this.basic.getStackInSlot(i) != null)
           {
               NBTTagCompound tag = new NBTTagCompound();
               tag.setByte("Slot", (byte) i);
               this.basic.getStackInSlot(i).writeToNBT(tag);
               list.appendTag(tag);
           }
       }

       compound.setTag("Items", list);
       return compound;
   }

   /**
    *   Данный метод будет читать NBT и выводить значения из него.
    */
   @Override
   public void readFromNBT(NBTTagCompound compound)
   {
       super.readFromNBT(compound);
       NBTTagList list = compound.getTagList("Items", 10);

       for(int i = 0; i < list.tagCount(); ++i)
       {
           NBTTagCompound tag = list.getCompoundTagAt(i);
           int j = tag.getByte("Slot") & 255;

           if(j >= 0 && j < this.basic.getSizeInventory())
           {
               this.basic.setInventorySlotContents(j, new ItemStack(tag));
           }
       }
   }
}

Создадим GuiHandler, чтобы регистрировать наши контейнеры, gui и прочее:
Код:
public class GuiHandler implements IGuiHandler
{
   /**
    *   Это параметр хранящий в себе id нашего gui.
    */
   public static final int GUI_WATCHER = 0;

   /**
    * Серверный метод открытия gui. Здесь мы будем открывать контейнер, так как серверу графика не нужна.
    * @param ID - уникальный идентификатор нашего gui
    * @param player - игрок, который открывает
    * @param world - мир в котором открывается
    * @param x - позиция по оси X (В основном позиции нужны для tileEntity, но о нём позже)
    * @param y - позиция по оси Y
    * @param z - позиция по оси Z
    * @return
    */
   @Override
   public Object getServerGuiElement(int ID, EntityPlayer player, World world, int x, int y, int z)
   {
       switch (ID)
       {
           case GUI_WATCHER: return new ContainerWatcher(player.inventory, (TileWatcher) world.getTileEntity(new BlockPos(x, y, z)));
           default: return null;
       }
   }

   /**
    * Серверный метод открытия gui. Здесь мы будем открывать gui и контейнер.
    * @param ID - уникальный идентификатор нашего gui
    * @param player - игрок, который открывает
    * @param world - мир в котором открывается
    * @param x - позиция по оси X (В основном позиции нужны для tileEntity, но о нём позже)
    * @param y - позиция по оси Y
    * @param z - позиция по оси Z
    * @return
    */
   @Override
   @SideOnly(Side.CLIENT)
   public Object getClientGuiElement(int ID, EntityPlayer player, World world, int x, int y, int z)
   {
       switch (ID)
       {
           case GUI_WATCHER: return new GuiWatcher(new ContainerWatcher(player.inventory, (TileWatcher) world.getTileEntity(new BlockPos(x, y, z))));
           default: return null;
       }
   }
}

И в ServerProxy в метод init() добавим такую строку:
Код:
NetworkRegistry.INSTANCE.registerGuiHandler(Core.INSTANCE, new GuiHandler());
Она нужна будет только для регистрации GuiHandler, прописывать её повторно не нужно. Теперь все манипуляции с gui, контейнерами будут происходить именно в GuiHandler.

Теперь создадим GuiWatcher:
Код:
public class GuiWatcher extends GuiContainer
{
   /**
    *   Это текстура нашего gui. В данном случаи используется текстура выбрасывателя.
    */
   private static final ResourceLocation TEXTURE = new ResourceLocation("textures/gui/container/dispenser.png");

   public GuiWatcher(Container inventorySlotsIn)
   {
       super(inventorySlotsIn);
   }

   @Override
   protected void drawGuiContainerBackgroundLayer(float partialTicks, int mouseX, int mouseY)
   {
       /**
        *   Располагаем gui по центру.
        */
       mc.renderEngine.bindTexture(TEXTURE);
       int x = (this.width - this.xSize) / 2;
       int y = (this.height - this.xSize) / 2;
       drawTexturedModalRect(x, y, 0, 0, xSize, ySize);
   }
}

И теперь создадим для него ContainerWatcher:
Код:
public class ContainerWatcher extends Container
{
   public ContainerWatcher(IInventory playerInv, TileWatcher watcher)
   {
       int i = -18;
       int j;
       int k;

       int index = 0;
       /**
        *   Этот цикл отвечает за вывод всех 15 слотов, которые мы прописали.
        */
       for (j = 0; j < 3; ++j)
       {
           for (k = 0; k < 5; ++k)
           {
               addSlotToContainer(new Slot(watcher.basic, index++, 44 + k * 18, 17 + j * 18));
           }
       }

       /**
        *   Этот цикл отвечает за вывод инвентаря игрока.
        */
       for (j = 0; j < 3; ++j)
       {
           for (k = 0; k < 9; ++k)
           {
               this.addSlotToContainer(new Slot(playerInv, k + j * 9 + 9, 8 + k * 18, 102 + j * 18 + i));
           }
       }

       /**
        *   Этот цикл отвечает за вывод hud бара игрока.
        */
       for (j = 0; j < 9; ++j)
       {
           this.addSlotToContainer(new Slot(playerInv, j, 8 + j * 18, 160 + i));
       }
   }

   /**
    *   Можно ли взаимодействовать с контейнером.
    */
   @Override
   public boolean canInteractWith(EntityPlayer playerIn)
   {
       return true;
   }

    @Override
    public ItemStack transferStackInSlot(EntityPlayer playerIn, int index)
    {
       ItemStack itemstack = ItemStack.EMPTY;
       Slot slot = this.inventorySlots.get(index);

       if (slot != null && slot.getHasStack())
       {
           ItemStack itemstack1 = slot.getStack();
           itemstack = itemstack1.copy();

           if (index < 15)
           {
               if (!this.mergeItemStack(itemstack1, 15, this.inventorySlots.size(), true))
               {
                   return ItemStack.EMPTY;
               }
           }
           else if(!this.mergeItemStack(itemstack1, 0, 15, false))
           {
               return ItemStack.EMPTY;
           }

           if (itemstack1.isEmpty())
           {
               slot.putStack(ItemStack.EMPTY);
           }
           else
           {
               slot.onSlotChanged();
           }
       }

       return itemstack;
   }
}

Теперь создадим класс EntityRegister:
Код:
public class EntityRegister
{
   public static void register()
   {
       /**
        *  @param 1 - это класс нашего TileEntity
        *  @param 2 - это id нашего TileEntity
        */
       GameRegistry.registerTileEntity(TileWatcher.class, "TileWatcher");
   }
}

Далее добавим в ServerProxy в метод preInit такой код:
Код:
EntityRegister.register();
Теперь пора научится делать растения, которые будут расти.

Создадим класс BlockDogyCrop:

Код:
public class BlockDogyCrop extends BlockCrops
{
   private Item crop, seed;
   private static final AxisAlignedBB[] CROPS_AABB = new AxisAlignedBB[] {new AxisAlignedBB(0.0D, 0.0D, 0.0D, 1.0D, 0.125D, 1.0D), new AxisAlignedBB(0.0D, 0.0D, 0.0D, 1.0D, 0.25D, 1.0D), new AxisAlignedBB(0.0D, 0.0D, 0.0D, 1.0D, 0.375D, 1.0D), new AxisAlignedBB(0.0D, 0.0D, 0.0D, 1.0D, 0.5D, 1.0D), new AxisAlignedBB(0.0D, 0.0D, 0.0D, 1.0D, 0.625D, 1.0D), new AxisAlignedBB(0.0D, 0.0D, 0.0D, 1.0D, 0.75D, 1.0D), new AxisAlignedBB(0.0D, 0.0D, 0.0D, 1.0D, 0.875D, 1.0D), new AxisAlignedBB(0.0D, 0.0D, 0.0D, 1.0D, 1.0D, 1.0D)};

   public BlockDogyCrop(String name, Item crop, Item seed)
   {
       this.setRegistryName(name);
       this.setUnlocalizedName(name);
       this.crop = crop;
       this.seed = seed;
   }

   /**
    *  Данный метод позволит нам активировать блок, чтобы мы смогли не ломая блока взять с него урожай.
    */
   @Override
   public boolean onBlockActivated(World worldIn, BlockPos pos, IBlockState state, EntityPlayer playerIn, EnumHand hand, EnumFacing facing, float hitX, float hitY, float hitZ)
   {
       if(!worldIn.isRemote)
       {
           //Проверка на зрелость
           if(this.isMaxAge(state))
           {
              //Выбрасываем предмет с блока.
              EntityItem item = new EntityItem(worldIn, pos.getX(), pos.getY(), pos.getZ(), new                 ItemStack(getCrop()));
              worldIn.spawnEntity(item);

              //Устанавливаем для растения возраст 5
              worldIn.setBlockState(pos, this.withAge(5));
              return true;
           }
       }
       return false;
   }

   /**
    *  Задаёт сетка столкновения. В нашем случае сетка будет увеличиваться в зависимости от роста.
    */
   @Override
   public AxisAlignedBB getBoundingBox(final IBlockState state, final IBlockAccess source, final BlockPos pos)
   {
       return CROPS_AABB[(state.getValue(this.getAgeProperty())).intValue()];
   }

   /**
    *  Задаёт блок на который можно поставить наше растение.
    */
   @Override
   protected boolean canSustainBush(final IBlockState state)
   {
       return state.getBlock() == Blocks.GRASS;
   }

   /**
    *  Задаёт зерна для нашего растения.
    */
   @Override
   protected Item getSeed()
   {
       return this.seed;
   }

   /**
    *  Задаёт урожай для нашего растения.
    */
   @Override
   protected Item getCrop()
   {
       return this.crop;
   }

   /**
    *  Задаёт возможность использовать костную муку на нашем растении.
    */
   @Override
   public boolean canUseBonemeal(final World worldIn, final Random rand, final BlockPos pos, final IBlockState state)
   {
       return true;
   }
}

Теперь создадим зёрна:
Код:
public class ItemDogySeed extends Item implements IPlantable
{
   public ItemDogySeed(String name)
   {
       this.setRegistryName(name);
       this.setUnlocalizedName(name);
       this.setCreativeTab(TabsHandler.TAB_BLOCKS);
   }

   /**
    *   Данный метод позволит нам установить наш блок урожая.
    */
   @Override
   public EnumActionResult onItemUse(EntityPlayer playerIn, World worldIn, BlockPos pos, EnumHand hand, EnumFacing facing, float hitX, float hitY, float hitZ)
   {
       final IBlockState state = worldIn.getBlockState(pos);
       final ItemStack stack = playerIn.inventory.getItemStack();

       if(facing == EnumFacing.UP && playerIn.canPlayerEdit(pos.offset(facing), facing, stack) && state.getBlock().canSustainPlant(state, worldIn, pos, EnumFacing.UP, this) && worldIn.isAirBlock(pos.up()))
       {
          worldIn.setBlockState(pos.up(), BlocksRegister.DOGY_CROP.getDefaultState());
          stack.shrink(1);
          return EnumActionResult.SUCCESS;
       }
       return EnumActionResult.FAIL;
   }

   /**
    *  Задаёт тип урожая.
    */
   @Override
   public EnumPlantType getPlantType(IBlockAccess world, BlockPos pos)
   {
       return EnumPlantType.Plains;
   }

   /**
    *  Задаёт состояние урожая.
    */
   @Override
   public IBlockState getPlant(IBlockAccess world, BlockPos pos)
   {
       return BlocksRegister.DOGY_CROP.getDefaultState();
   }
}

И зарегистрируем их. Теперь добавим состояние блока для этого перейдём в assets/modexample/blockstates и добавим файл dogy_crop с таким содержимым:
Код:
{
"variants": {
   "age=0": { "model": "wheat_stage0" },
   "age=1": { "model": "wheat_stage1" },
   "age=2": { "model": "wheat_stage2" },
   "age=3": { "model": "wheat_stage3" },
   "age=4": { "model": "wheat_stage4" },
   "age=5": { "model": "wheat_stage5" },
   "age=6": { "model": "wheat_stage6" },
   "age=7": { "model": "wheat_stage7" }
}
}
В нашем случае используются модели пшеницы, но вы можете настроить свои модели.

Доска в Trello, там вы можете выбрать(проголосовать) за статью, что ускорит её выход в учебник.
 
Последнее редактирование:

Icosider

Kotliner
Администратор
3,603
99
664
RE: [1.8+] Создание своего мода.

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

Создадим класс ConfigRegister:
Код:
public class ConfigRegister
{
   public static Configuration config;
   public static Property randomList;
   public static int potionDuration;
   public static boolean fluidColor, layersRegister, usePlayerAPI;
   public static float lightLevel;

   public static void register(FMLPreInitializationEvent e)
   {
       config = new Configuration(e.getSuggestedConfigurationFile());
       //Загрузка нашего конфига.
       config.load();
       /**
        * Это boolean переменная конфига, т.е. true/false.
        * 1 - Название нашей переменной в конфиге.
        * 2 - Название категории для нашей переменной. Для каждой переменной можно не создавать свою категорию,
        * а использовать существующую.
        * 3 - Стандартное значение, которое будет автоматически прописано при создании конфига.
        * 4 - Описание для нашей переменной. Я использую I18n дабы описание можно было перевести под разные языки.
        * 5 - Это некий ключ(как в I18n), нужный для того, чтобы дать название переменной в GUI.
        */
       fluidColor = config.getBoolean("fluidColor", "fluids", false, I18n.format("config.fluidColor"), "config.fluidColor.name");
       layersRegister = config.getBoolean("layersRegister", "layers", false, I18n.format("config.layersRegister"), "config.layersRegister.name");
       usePlayerAPI = config.getBoolean("usePlayerAPI", "api", true, I18n.format("config.usePlayerAPI"), "config.usePlayerAPI.name");
       /**
        * Это int переменная конфига, т.е. 1, 2, 3 и т.д.
        * 1 - Название нашей переменной в конфиге.
        * 2 - Название категории для нашей переменной. Для каждой переменной можно не создавать свою категорию,
        * а использовать существующую.
        * 3 - Стандартное значение, которое будет автоматически прописано при создании конфига.
        * 4 - Минимальное значение, если будет установлено меньше чем минимум, переменная вернётся в исходное значение.
        * 5 - Максимальное значение, если будет установлено больше чем максимум, переменная вернётся в исходное значение.
        * 6 - Описание для нашей переменной. Я использую I18n дабы описание можно было перевести под разные языки.
        * 7 - Это некий ключ(как в I18n), нужный для того, чтобы дать название переменной в GUI
        */
       potionDuration = config.getInt("potionDuration", "potions", 60, 5, 3600, I18n.format("config.potionDuration"), "config.potionDuration.name");
       /**
        * Это float переменная конфига, т.е. 1.0, 2.21, 3.3242 и т.д. Всё тоже самое, что и у int переменной,
        * только добавляется плавающая точка., что позволяет вводить более точные значения.
        */
       lightLevel = config.getFloat("lightLevel", "blocks", 1.0F, 0.2F, 2.0F, I18n.format("config.lightLevel"), "config.lightLevel.name");
       /**
        * Это int переменная содержащая массив наших значений, int[] { 1, 2, 5, 12, 521, 15, 46 }.
        * 1 - Название категории для нашей переменной.
        * 2 - Название нашей переменной в конфиге.
        * 3 - Массив значений
        * 4 - Описание для нашей переменной
        * 5 - Минимальное значений в массиве(минимум 1)
        * 6 - Максимальное значений в массиве(максимум 16)
        */
       randomList = config.get("blocks", "randomList", new int[]{ 1, 2, 3, 4, 5, 6 }, I18n.format("config.randomList"), 1, 16).setLanguageKey("config.randomList.name");
       //Сохранение нашего конфига.
       config.save();
   }
}

И добавим его в ServerProxy, в метод preInit().
Код:
   public void preInit(FMLPreInitializationEvent e)
   {
       //Конфиг должен регистрироваться раньше блоков, предметов и т.д.!
       ConfigRegister.register(e);
   }

Теперь добавим в наш ru_RU.lang такие строки:
Код:
config.fluidColor=Включает цвет для жидкости.
config.layersRegister=Включает регистрацию слоёв.
config.usePlayerAPI=Включает использование библиотеки PlayerAPI
config.potionDuration=Время действия эффекта для PoisonApple. Указывать в секундах!
config.lightLevel=Устанавливает уровень светимости для блока.
config.randomList=Эти значения можно использовать где и как угодно.

config.fluidColor.name=Цвет жидкости
config.layersRegister.name=Регистрация слоёв
config.usePlayerAPI.name=Использование PlayerAPI
config.potionDuration.name=Время действия эффекта для PoisonApple
config.lightLevel.name=Уровень светимости блока
config.randomList.name=Эти значения можно использовать где и как угодно.

А сейчас мы используем наши значения из конфига! Перейдём в наш блок пирамиды и в setLightLevel установим такое значение: ConfigRegister.lightLevel
Перейдём в класс ItemPoisonApple и введём новую переменную:
Код:
public static int getDuration = ConfigRegister.potionDuration;
в конструктор добавим:
Код:
getDuration *= 20;
и теперь в методе addPotionEffect изменим значение 1200 на getDuration:
Код:
//Было
player.addPotionEffect(new PotionEffect(Potion.getPotionById(4), 1200, 2, false, false));

//Стало
player.addPotionEffect(new PotionEffect(Potion.getPotionById(4), getDuration, 2, false, false));

Перейдём в ClientProxy и в методе postInit добавим условие:
Код:
   public void postInit()
   {
       if(ConfigRegister.usePlayerAPI)
           ModelPlayerAPI.register("cmp", ModelPlayerOverride.class);
   }

Если вы хотите использовать randomList, то в нужном вам месте вставьте такую переменную \/ и используйте как массив.
Код:
public static int[] randomList = ConfigRegister.randomList.getIntList();

Запускаем игру! После окончательной загрузки переходим в папку config в корневом разделе .minecraft(или в run в папке с рабочей средой). Открываем наш файл и видим:
Код:
# Configuration file

api {
   # Включает использование библиотеки PlayerAPI [default: true]
   B:usePlayerAPI=true
}


blocks {
   # Эти значения можно использовать где и как угодно.
   I:randomList <
       1
       2
       3
       4
       5
       6
    >

   # Устанавливает уровень светимости для блока. [range: 0.2 ~ 2.0, default: 1.0]
   S:lightLevel=1.0
}


fluids {
   # Включает цвет для жидкости. [default: false]
   B:fluidColor=false
}


layers {
   # Включает регистрацию слоёв. [default: false]
   B:layersRegister=false
}


potions {
   # Время действия эффекта для PoisonApple. Указывать в секундах! [range: 5 ~ 3600, default: 60]
   I:potionDuration=60
}


Так же если вы добавите новую переменную в конфиг, старый конфиг будет перезаписан и все значения которые были изменены вернутся к стандартному состоянию!
В прошлой статье я рассказывал, как добавить конфиг для своего мода, но каждый раз лазить в папку с модов, открывать файл настраивать значения, не слишком то и удобно, то ли дело открыть GUI изменить настройки и просто перезайти. Сейчас я расскажу про такую классную вещь как GuiFactory!

Переходим в наш главный класс и в аннотации прописываем такое значение:
Код:
//Это наш путь от папки src/main/java до файла ConfigGuiFactory(это класс)
guiFactory = "ru.wildheart.examplemod.gui.ConfigGuiFactory"

//Должно получится так:
@Mod(modid = "modexample", version = "1.0", guiFactory = "ru.wildheart.examplemod.gui.ConfigGuiFactory")

Создадим класс ConfigGuiFactory.
Код:
public class ConfigGuiFactory implements IModGuiFactory
{
   @Override
   public void initialize(Minecraft minecraftInstance)
   {

   }

   /**
    *  Здесь мы укажим класс GuiConfig'a для нашего мода.
    */
   @Override
   public Class<? extends GuiScreen> mainConfigGuiClass()
   {
       return GuiModConfig.class;
   }

   @Override
   public Set<RuntimeOptionCategoryElement> runtimeGuiCategories()
   {
       return null;
   }

   @Override
   public RuntimeOptionGuiHandler getHandlerFor(RuntimeOptionCategoryElement element)
   {
       return null;
   }

   /**
    *  Это наш класс унаследованный от GuiConfig
    */
   public static class GuiModConfig extends GuiConfig
   {
       public GuiModConfig(GuiScreen parentScreen)
       {
           /**
            *  1 - родительское GUI
            *  2 - получение значений конфига
            *  3 - уникальное значение нашего мода, т.е. MODID
            *  4 - требует перезагрузки мира(Изменилась генерация мира, положение рук и т.п.)
            *  5 - требует перезагрузки игры(Отключили бибилиотеку, отключили ненужные предметы)
            *  6 - заголовок нашего GUI
            */
           super(parentScreen, getConfigElements(), "modexample", false, true, I18n.format("gui.config.configTitle"));
       }

       /**
        * Здесь находятся наши категории
        */
       private static List<IConfigElement> getConfigElements()
       {
           List<IConfigElement> list = new ArrayList<IConfigElement>();
           /**
            * 1 - Название категории(некое уникальное значение)
            * 2 - Это некий ключ(как в I18n), нужный для того, чтобы дать название кнопки
            * 3 - Класс категории
            */
           list.add(new DummyConfigElement.DummyCategoryElement("api", "gui.config.category.api", CategoryAPI.class));
           list.add(new DummyConfigElement.DummyCategoryElement("blocks", "gui.config.category.blocks", CategoryBlocks.class));
           list.add(new DummyConfigElement.DummyCategoryElement("fluids", "gui.config.category.fluids", CategoryFluids.class));
           list.add(new DummyConfigElement.DummyCategoryElement("layers", "gui.config.category.layers", CategoryLayers.class));
           list.add(new DummyConfigElement.DummyCategoryElement("potions", "gui.config.category.potions", CategoryPotions.class));
           return list;
       }

       public static class CategoryAPI extends GuiConfigEntries.CategoryEntry
       {
           public CategoryAPI(GuiConfig owningScreen, GuiConfigEntries owningEntryList, IConfigElement prop)
           {
               super(owningScreen, owningEntryList, prop);
           }

           /**
            * Здесь мы создаём GUI содержащее все значения из категории API
            */
           @Override
           protected GuiScreen buildChildScreen()
           {
               return new GuiConfig(this.owningScreen,
                       (new ConfigElement(ConfigRegister.config.getCategory("api"))).getChildElements(),
                       this.owningScreen.modID, "api", this.configElement.requiresWorldRestart() || this.owningScreen.allRequireWorldRestart,
                       this.configElement.requiresMcRestart() || this.owningScreen.allRequireMcRestart,
                       GuiConfig.getAbridgedConfigPath(ConfigRegister.config.toString()));
           }
       }

       public static class CategoryBlocks extends GuiConfigEntries.CategoryEntry
       {
           public CategoryBlocks(GuiConfig owningScreen, GuiConfigEntries owningEntryList, IConfigElement prop)
           {
               super(owningScreen, owningEntryList, prop);
           }

           @Override
           protected GuiScreen buildChildScreen()
           {
               return new GuiConfig(this.owningScreen,
                       (new ConfigElement(ConfigRegister.config.getCategory("blocks"))).getChildElements(),
                       this.owningScreen.modID, "blocks", this.configElement.requiresWorldRestart() || this.owningScreen.allRequireWorldRestart,
                       this.configElement.requiresMcRestart() || this.owningScreen.allRequireMcRestart,
                       GuiConfig.getAbridgedConfigPath(ConfigRegister.config.toString()));
           }
       }

       public static class CategoryFluids extends GuiConfigEntries.CategoryEntry
       {
           public CategoryFluids(GuiConfig owningScreen, GuiConfigEntries owningEntryList, IConfigElement prop)
           {
               super(owningScreen, owningEntryList, prop);
           }

           @Override
           protected GuiScreen buildChildScreen()
           {
               return new GuiConfig(this.owningScreen,
                       (new ConfigElement(ConfigRegister.config.getCategory("fluids"))).getChildElements(),
                       this.owningScreen.modID, "fluids", this.configElement.requiresWorldRestart() || this.owningScreen.allRequireWorldRestart,
                       this.configElement.requiresMcRestart() || this.owningScreen.allRequireMcRestart,
                       GuiConfig.getAbridgedConfigPath(ConfigRegister.config.toString()));
           }
       }

       public static class CategoryLayers extends GuiConfigEntries.CategoryEntry
       {
           public CategoryLayers(GuiConfig owningScreen, GuiConfigEntries owningEntryList, IConfigElement prop)
           {
               super(owningScreen, owningEntryList, prop);
           }

           @Override
           protected GuiScreen buildChildScreen()
           {
               return new GuiConfig(this.owningScreen,
                       (new ConfigElement(ConfigRegister.config.getCategory("layers"))).getChildElements(),
                       this.owningScreen.modID, "layers", this.configElement.requiresWorldRestart() || this.owningScreen.allRequireWorldRestart,
                       this.configElement.requiresMcRestart() || this.owningScreen.allRequireMcRestart,
                       GuiConfig.getAbridgedConfigPath(ConfigRegister.config.toString()));
           }
       }

       public static class CategoryPotions extends GuiConfigEntries.CategoryEntry
       {
           public CategoryPotions(GuiConfig owningScreen, GuiConfigEntries owningEntryList, IConfigElement prop)
           {
               super(owningScreen, owningEntryList, prop);
           }

           @Override
           protected GuiScreen buildChildScreen()
           {
               return new GuiConfig(this.owningScreen,
                       (new ConfigElement(ConfigRegister.config.getCategory("potions"))).getChildElements(),
                       this.owningScreen.modID, "potions", this.configElement.requiresWorldRestart() || this.owningScreen.allRequireWorldRestart,
                       this.configElement.requiresMcRestart() || this.owningScreen.allRequireMcRestart,
                       GuiConfig.getAbridgedConfigPath(ConfigRegister.config.toString()));
           }
       }
   }
}

И добавим в наш ru_RU.lang такие значения:
Код:
gui.config.configTitle=Конфигурация мода Modexample.

gui.config.category.api=Настройки API
gui.config.category.blocks=Настройки блоков
gui.config.category.fluids=Настройки жидкостей
gui.config.category.layers=Настройки слоёв
gui.config.category.potions=Настройки зелий

gui.config.category.api.tooltip=Здесь хранятся настройки API.
gui.config.category.blocks.tooltip=Здесь хранятся настройки блоков.
gui.config.category.fluids.tooltip=Здесь хранятся настройки жидкостей.
gui.config.category.layers.tooltip=Здесь хранятся настройки слоёв.
gui.config.category.potions.tooltip=Здесь хранятся настройки зелий.

Запускаем игру, нажимаем на кнопку mods, ищем наш мод и нажимаем config!
[video=vimeo]
Все знаю про такой файл как mcmod.info, но не все знаю, про то что его не обязательно заполнять. Заполнить можно и через сам мод.

Создадим класс MetadataRegister:
Код:
public class MetadataRegister
{
   public static void register(FMLPreInitializationEvent e)
   {
       ModMetadata metadata = e.getModMetadata();
       /**
        * Это список авторов
        */
       metadata.authorList = Arrays.asList(new String[] { "WildHeart" });
       /**
        * Автоматическая генерация. Пока точно не известно для чего он нужен,
        * ибо Forge: "this field is not for use in the json"
        */
       metadata.autogenerated = false;
       /**
        * Заслуги/благодарности и т.п.
        */
       metadata.credits = "Особая благодарность @Dahaka за помощь";
       /**
        * Описание мода
        */
       metadata.description = "Данный мод предназначен для ознакомления!";
       /**
        * Версия мода
        */
       metadata.version = "1.0 Alpha";
       /**
        * Ссылка
        */
       metadata.url = "http://forum.mcmodding.ru/Тема-1-8-1-11-2-Создание-своего-мода";
       /**
        * Ссылка на логотип
        */
       metadata.logoFile = "assets/modexample/textures/logo.png";
   }
}

И теперь зарегистрируем его в ServerProxy, в методе preInit():
Код:
MetadataRegister.register(e);
Не хотели ли вы добавить жидкость в мод? Например новое топливо или сок. Сейчас я расскажу как сделать свою жидкость.

Создадим класс BlockOil.
Код:
public class BlockFluid extends BlockFluidClassic
{
   /**
    * Это конструктор который передаст наши значения родителю.
    * @param fluid - это наша жидкость
    * @param name - это материал нашего блока. Существует два метериала, Water - ведёт себя как вода, быстро течёт
    * Lava - ведёт себя как лава, медленно течёт.
    */
   public BlockFluid(Fluid fluid, String name)
   {
       super(fluid, Material.WATER);
       setCreativeTab(TabsHandler.TAB_BLOCKS);
       setUnlocalizedName(name);
       setRegistryName(name);
   }

   /**
    * Проверка на то, что блок был поставлен
    */
   @Override
   public void onBlockAdded(World world, BlockPos pos, IBlockState state)
   {
       super.onBlockAdded(world, pos, state);
       mergerFluids(pos, world);
   }

   /**
    * Это проверка на соседние блоки.
    */
   @Override
   public void neighborChanged(IBlockState state, World world, BlockPos pos, Block neighborBlock)
   {
       super.neighborChanged(state, world, pos, neighborBlock);
       mergerFluids(pos, world);
   }

   private void mergerFluids(BlockPos pos, World world)
   {
       for(EnumFacing facing : EnumFacing.values())
       {
           Block block = world.getBlockState(pos.offset(facing)).getBlock();
           //Если вода, то ставим камень
           if(block == Blocks.WATER || block == Blocks.FLOWING_WATER)
           {
               world.setBlockState(pos.offset(facing), Blocks.STONE.getDefaultState());
           }
           //Если лава, ставим кирпичный блок
           else if(block == Blocks.LAVA || block == Blocks.FLOWING_LAVA)
           {
               world.setBlockState(pos.offset(facing), Blocks.BRICK_BLOCK.getDefaultState());
           }
       }
   }
}

и зарегистрируем в BlocksRegister так:
Код:
   public static Block
           OIL = new BlockFluid(FluidsRegister.OIL, "fluid_oil"),
           MILK = new BlockFluid(FluidsRegister.MILK, "fluid_milk");

   public static void register()
   {
       registerBlock(OIL);
       registerBlock(MILK);
   }
Как вы могли заметить, мы не регистрируем модель для блока, так как у нас подгружается модель воды.

Создадим класс FluidOil.
Код:
public class CustomFluid extends Fluid
{
   /**
    * Конструктор нашей жидкости
    * @param fluidName - Название жидкости
    * @param still - Название файла "стоячий" жидкости
    * @param flowing - Название файла "текучей" жидкости
    */
   public CustomFluid(String fluidName, String still, String flowing)
   {
       super(fluidName, new ResourceLocation("modexample", "blocks/" + still), new ResourceLocation("modexample", "blocks/" + flowing));
   }

   /**
    * Так же мы можем задать цвет жидкости.
    * Последние шесть цифр после 0xFF, это наш цвет.
    * Если цвет не нужен, то просто удалите этот метод.
    */
   @Override
   public int getColor() {
       return 0xFF00FF00;
   }
}

и создадим класс FluidsRegister.
Код:
public class FluidsRegister
{
   /**
    *  Переменные с нашими жидкостями.
    */
   public static Fluid OIL = new CustomFluid("oil", "oil_still", "oil_flow");
   public static Fluid MILK = new CustomFluid("milk", "milk_still", "milk_flow");

   /**
    * Регистрация нашей жидкости
    */
   public static void register()
   {
       FluidRegistry.registerFluid(OIL);
       FluidRegistry.registerFluid(MILK);
   }

   /**
    * Регистрация модели нашей жидкости
    */
   public static void registerRender()
   {
       /**
        * Я упростил регистрацию, и теперь, чтобы зарегистрировать жидкость нужно
        * прописать метод modelLoader с такими значениями:
        * 1 - Блок
        * 2 - Вариант жидкости, в основном это fluid или gas, но про газ познее.
        */
       modelLoader(BlocksRegister.OIL, "oil");
       modelLoader(BlocksRegister.MILK, "milk");
   }

   @SideOnly(Side.CLIENT)
   public static void modelLoader(Block block, String variant)
   {
       ModelResourceLocation milkLocation = new ModelResourceLocation("modexample:fluid", variant);
       Item milk = Item.getItemFromBlock(block);

       ModelBakery.registerItemVariants(milk);
       ModelLoader.setCustomMeshDefinition(milk, stack -> milkLocation);
       ModelLoader.setCustomStateMapper(block, new StateMapperBase()
       {
           protected ModelResourceLocation getModelResourceLocation(IBlockState state)
           {
               return milkLocation;
           }
       });
   }
}

Теперь добавим в ClientProxy, в метод preInit() после super.preInit() такой код:
Код:
FluidsRegister.registerRender();
и в ServerProxy, в метод preInit() до регистрации блоков такой код:
Код:
FluidsRegister.register();

Теперь нам нужно создать файл fluid.json в папке blockstates, в папке с ресурсами мода.
Код:
{
   "forge_marker": 1,
   "defaults": {
       "model": "forge:fluid",
       "transform": "forge:default-item"
   },
   "variants": {
       "oil": [{
           "custom": { "fluid": "oil" }
       }],
       "milk": [{
           "custom": { "fluid": "milk" }
       }]
   }
}

Разберём данный файл:
variants - это наши варианты жидкости, как я говорил может быть gas и fluid(можно задать другие, но я буду рассматривать только эти варианты).
fluid - это вариант состояния нашей жидкости
custom->fluid - это параметр который устанавливает для oil(это название нашей жидкости) модель жидкости.


Теперь зайдём в игру и видим:
Так выглядит наше молоко без getColor:
2bdb70ab3ddfec289e0adfbafc517189.png

А вот так с getColor:
542d7ba176841db96cdd474d0fcdd40d.png
 
Последнее редактирование:
1,470
19
189
RE: [1.8+] Создание своего мода.

Еще про нбт расскажи, будет полезно
 

Icosider

Kotliner
Администратор
3,603
99
664
RE: [1.8+] Создание своего мода.

Добавил статью с добавлением в мод предметов.


Обновил скриншоты, обновил файл с моделью предмета, теперь есть текстура.


Добавил 2D модель, так же ссылка на скачивание прикреплена.
 
2,505
81
397
RE: [1.8-1.11.2] Создание своего мода.

Иногда в proxy.init() бывает полезен сам объект ивента. Оттуда достать можно что-нибудь полезное.
И надеюсь, ты не забыл про preInit и postInit :)


А еще, postInit нужен не для регистрации рецептов, а для взаимодействия с другими модами (например, со списком всех рецептов).
 

Icosider

Kotliner
Администратор
3,603
99
664
RE: [1.8-1.11.2] Создание своего мода.

Dahaka написал(а):
Иногда в proxy.init() бывает полезен сам объект ивента. Оттуда достать можно что-нибудь полезное.
И надеюсь, ты не забыл про preInit и postInit :)

Конечно не забыл, двигаюсь постепенно.


А я в нём всегда рецепты регал:D Исправил
 
2,505
81
397
RE: [1.8-1.11.2] Создание своего мода.

"eItem" Серьезно? А я думал, ты придерживаешься стандартного код стайла
 

Icosider

Kotliner
Администратор
3,603
99
664
RE: [1.8-1.11.2] Создание своего мода.

Dahaka написал(а):
"eItem" Серьезно? А я думал, ты придерживаешься стандартного код стайла
Ну ладно, исправлю:)
 
2,505
81
397
RE: [1.8-1.11.2] Создание своего мода.

Код:
@SideOnly(Side.CLIENT)
public static void registerRender()
Серверу не нужны модели. И соответственно, этот метод нужно вызывать только в ClientProxy.
 
RE: [1.8-1.11.2] Создание своего мода.

Вы делаете очень полезное дело! Спасибо.
 

Icosider

Kotliner
Администратор
3,603
99
664
RE: [1.8-1.11.2] Создание своего мода.

Обновил разделы: главный класс, прокси и предмет. Добавил статьи: блок, вкладка.
 
5,018
47
783
RE: [1.8-1.11.2] Создание своего мода.

WildHeart написал(а):
Dahaka написал(а):
Выглядит полезно. Решил создать гигантский урок?
Я им дам основу, а дальше буду делать адванцед уроки, типа как использовать капу, как сделать пакеты(хоть и есть тема), как юзать asm ну и т.п.
Про асм я так ниче и не понял. Надеюсь,это не нужно запускать DosBox  править код на уровне ассемблера?  :blush:


Вот спасибо тебе огромное! Значит, грибы на 1.8 + появятся намного быстрее благодаря тебе! (щас пока подзабил на кодинг - надоело пока, чуть попозже начну(это для тех, кто неистово ждет новую версию грибов))
 

Icosider

Kotliner
Администратор
3,603
99
664
RE: [1.8-1.11.2] Создание своего мода.

Liahim написал(а):
Я смотрю, тут решили заново учебник написать...
Кстати, где он вообще? Не могу найти на него ссылку?

А это наши одмены убрали сайт и сделали редирект на форум... Учебник был там...


Ах да, и учебник там был откровенно говоря овно :)


Maxik001 написал(а):
WildHeart написал(а):
Dahaka написал(а):
Выглядит полезно. Решил создать гигантский урок?
Я им дам основу, а дальше буду делать адванцед уроки, типа как использовать капу, как сделать пакеты(хоть и есть тема), как юзать asm ну и т.п.
Про асм я так ниче и не понял. Надеюсь,это не нужно запускать DosBox  править код на уровне ассемблера?  :blush:


Вот спасибо тебе огромное! Значит, грибы на 1.8 + появятся намного быстрее благодаря тебе! (щас пока подзабил на кодинг - надоело пока, чуть попозже начну(это для тех, кто неистово ждет новую версию грибов))


Фу, зачем извращения с DosBox? Тем на форуме было много, дахака или дракон объясняли как работает. Так же на гитхабе если порыться, то можно найти.
 
1,470
19
189
RE: [1.8-1.11.2] Создание своего мода.

Сделай тутор про бронь
 
2,505
81
397
RE: [1.8-1.11.2] Создание своего мода.

WildHeart написал(а):
Фу, зачем извращения с DosBox? Тем на форуме было много, дахака или дракон объясняли как работает. Так же на гитхабе если порыться, то можно найти.
Я не шарю в ASM'е. Я всегда юзаю хуклибу GloomyFolken'а (не хочу свои велосипеды пилить). Не было желания разбираться и особой нужды.
Причем тут вообще DosBox? Это же эмулятор. В любом случае, с ассемблером в чистом виде работать не придется. ASM это библиотека для взаимодействия с байт кодом.
И работает, в моем представлении, по принципу хуков. Т.е. код загружен в память, и допустим хотим вставить свой код в определенное место в рантайме. В этом месте ассемблерная команда подменяется на джамп туда, где будет наш код (включая ту команду, которую подменяем), а оставшаяся длинна команды забивается нопами. Таким образом код остается целостный. Ну дак вот этот ASM позволяет юзверям(нам) ничего этого не знать и модифицировать код.


Ты там eItem не везде поправил


ItemsRegister.register(); нужно вставлять в ServerProxy в твоем случае.
 

Icosider

Kotliner
Администратор
3,603
99
664
RE: [1.8-1.11.2] Создание своего мода.

Dahaka написал(а):
WildHeart написал(а):
Фу, зачем извращения с DosBox? Тем на форуме было много, дахака или дракон объясняли как работает. Так же на гитхабе если порыться, то можно найти.
Я не шарю в ASM'е. Я всегда юзаю хуклибу GloomyFolken'а (не хочу свои велосипеды пилить). Не было желания разбираться и особой нужды.

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


А ещё почему нельзя кастомный блок использовать:\
 
Сверху