Как создавать мод для Minecraft: выбор версии, Forge/Fabric, структура проекта, примеры и публикация

Опубликовано: 7 Июня, 2023

1. Введение

Мод Minecraft — это дополнение, которое расширяет игру на уровне кода: добавляет новые блоки, предметы, сущности, механики, интерфейсы, сетевое взаимодействие и интеграции с другими модами.

Важно различать три подхода, потому что они дают разный “потолок” возможностей:

  • Resourcepack: текстуры, модели, звуки, интерфейсные ресурсы. Код не меняет.

  • Datapack: рецепты, лут-таблицы, теги, функции, простая логика (в рамках возможностей датапака). Код не требует.

  • Полноценный мод: Java-код + ресурсы. Максимальная гибкость, но выше требования к подготовке и поддержке.


2. Что выбрать: datapack/resourcepack или мод

2.1. Когда достаточно datapack

Datapack подходит, если нужно:

  • добавить/изменить рецепты крафта;

  • настроить дроп (loot tables);

  • добавить/изменить теги (tags);

  • добавить достижения (advancements);

  • настроить генерацию в мире в рамках поддерживаемых механизмов.

Плюсы:

  • не нужен загрузчик модов;

  • проще обновлять между версиями (в среднем);

  • ниже порог входа.

Минусы:

  • ограничения по логике и взаимодействию;

  • сложнее сделать “новую механику” (GUI, сеть, сложное поведение).

2.2. Когда нужен полноценный мод

Мод нужен, если требуется:

  • новые блоки/предметы с кастомным поведением;

  • новые мобы/сущности;

  • новые интерфейсы, контейнеры, меню;

  • новая механика мира, машины, энергия, сложные системы;

  • сетевые пакеты и мультиплеерная логика.

Плюсы:

  • максимум возможностей;

  • можно делать масштабные системы и интеграции.

Минусы:

  • потребуется Java и понимание архитектуры;

  • обновления Minecraft часто ломают API и требуют сопровождения;

  • выше риск конфликтов и багов без тестирования.


3. Выбор версии Minecraft и модлоадера

3.1. Почему версия важна

Мод “привязан” к версии игры и к конкретному лоадеру. Если выбрать версию без экосистемы или слишком новую/редкую, мод будет сложнее тестировать, а пользователям — собирать модпак.

Практическая рекомендация:

  • выбирайте версию, под которую уже существуют нужные вам зависимости (библиотеки, API, базовые моды);

  • фиксируйте версию на старте проекта и обновляйтесь только по плану.

3.2. Forge / NeoForge

Forge-ветка (и NeoForge как ответвление экосистемы) обычно выбирается, когда:

  • нужны “тяжелые” моды и большая экосистема;

  • нужны готовые решения и привычные паттерны (регистры, события);

  • планируется совместимость с большим набором Forge-модов.

Плюсы:

  • широкая экосистема;

  • зрелые практики для крупных модов;

  • много примеров архитектурных решений.

Минусы:

  • сложнее старт для новичка;

  • некоторые версии требуют больше внимания к конфигурации и зависимости.

3.3. Fabric (и Quilt)

Fabric часто выбирают, когда:

  • нужен относительно легкий стек;

  • важна скорость обновлений и минимализм;

  • вы готовы работать с более “низкоуровневым” подходом (включая mixin-практики, если требуется).

Плюсы:

  • проще “тонко” вмешиваться в игру;

  • часто легче стартовый проект;

  • хорошие варианты для небольших модов и оптимизаций.

Минусы:

  • для некоторых задач придется больше собирать “вручную”;

  • совместимость и “готовые модули” зависят от конкретной экосистемы.


4. Подготовка окружения разработки

4.1. Java (JDK)

Minecraft использует Java. Для моддинга нужны:

  • JDK (не JRE);

  • корректная версия Java под целевую версию Minecraft.

Ориентиры по версиям Java (в типовых ветках Minecraft):

  • старые ветки (например, 1.16.x) часто работают на Java 8;

  • современные ветки (1.18+ и далее) обычно требуют Java 17;

  • отдельные будущие версии могут поднять требование выше — это нужно проверять на момент выбора версии.

4.2. IDE

На практике используют:

  • IntelliJ IDEA (часто удобнее для Gradle, анализа кода и рефакторинга),

  • Eclipse (тоже возможно, но обычно требует больше ручной настройки).

4.3. Gradle и структура проекта

Почти все современные шаблоны модов собираются Gradle’ом:

  • зависимости лоадера;

  • запуск dev-клиента/сервера;

  • сборка итогового JAR.

Типовые проблемы:

  • неправильная версия JDK в Gradle;

  • сломанные кэши Gradle;

  • конфликт версий библиотек.


5. Создание проекта мода: общая структура

Независимо от лоадера, структура обычно похожа:

  • src/main/java — исходники Java

  • src/main/resources — ресурсы мода:

    • assets// — клиентские ресурсы (текстуры, модели, lang и т. д.)

    • data// — дата-ресурсы (рецепты, лут, теги, worldgen и т. д.)

5.1. Базовые идентификаторы

  • modid: короткий идентификатор (строчные латинские буквы, цифры, подчёркивание). Не меняется после релиза.

  • name: отображаемое имя мода.

  • version: версия мода (желательно семантически: 1.0.0, 1.0.1 и т. п.).


 


6. Архитектура мода и регистрация (углубление)

Примечание по версиям: API Forge/Fabric меняется между ветками Minecraft, поэтому примеры ориентированы на “современные” версии (логика и структура верны, но названия отдельных классов/ивентов могут отличаться на конкретной связке).

6.1. Минимальная структура проекта

Общая структура (подходит и для Forge, и для Fabric)

examplemod/
  build.gradle
  settings.gradle
  gradle.properties
  src/main/java/com/example/examplemod/
    ExampleMod.java
    ModItems.java
    ModBlocks.java
    ModCreativeTabs.java         (опционально)
    network/
      ModNetworking.java
      ExampleC2SPacket.java
    block/
      ExampleBlock.java          (опционально)
  src/main/resources/
    assets/examplemod/
      lang/ru_ru.json
      models/item/example_ingot.json
      models/block/example_block.json
      models/item/example_block.json
      blockstates/example_block.json
      textures/item/example_ingot.png
      textures/block/example_block.png
    data/examplemod/
      recipes/example_block.json
      loot_tables/blocks/example_block.json
      tags/items/example_ingots.json

6.2. Forge: регистрация через DeferredRegister (правильная “база”)

6.2.1. Main-класс мода (Forge)

package com.example.examplemod;

import net.minecraftforge.eventbus.api.IEventBus;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;

@Mod(ExampleMod.MODID)
public class ExampleMod {
    public static final String MODID = "examplemod";

    public ExampleMod() {
        IEventBus modBus = FMLJavaModLoadingContext.get().getModEventBus();

        ModItems.register(modBus);
        ModBlocks.register(modBus);
        ModCreativeTabs.register(modBus);   // опционально

        // Сеть обычно инициализируют в common setup или статически (зависит от версии)
        ModNetworking.init();
    }
}

6.2.2. Предметы (Forge)

package com.example.examplemod;

import net.minecraft.world.item.Item;
import net.minecraft.world.item.Rarity;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.TooltipFlag;
import net.minecraft.network.chat.Component;
import net.minecraft.world.level.Level;
import net.minecraftforge.eventbus.api.IEventBus;
import net.minecraftforge.registries.DeferredRegister;
import net.minecraftforge.registries.ForgeRegistries;
import net.minecraftforge.registries.RegistryObject;

import java.util.List;

public final class ModItems {
    private ModItems() {}

    public static final DeferredRegister ITEMS =
            DeferredRegister.create(ForgeRegistries.ITEMS, ExampleMod.MODID);

    // Простой предмет
    public static final RegistryObject EXAMPLE_INGOT =
            ITEMS.register("example_ingot", () -> new Item(new Item.Properties()));

    // Предмет с поведением и tooltip
    public static final RegistryObject DEBUG_WAND =
            ITEMS.register("debug_wand", () -> new Item(new Item.Properties().stacksTo(1).rarity(Rarity.UNCOMMON)) {
                @Override
                public void appendHoverText(ItemStack stack, Level level, List tooltip, TooltipFlag flag) {
                    tooltip.add(Component.literal("Тестовый предмет для отладки действий."));
                }
            });

    public static void register(IEventBus bus) {
        ITEMS.register(bus);
    }
}

6.2.3. Блоки + BlockItem (Forge — практичный паттерн)

Критически важно: блок и его “предмет-представление” (BlockItem) — разные реестры.

package com.example.examplemod;

import net.minecraft.world.item.BlockItem;
import net.minecraft.world.item.Item;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.SoundType;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.material.MapColor;
import net.minecraftforge.eventbus.api.IEventBus;
import net.minecraftforge.registries.DeferredRegister;
import net.minecraftforge.registries.ForgeRegistries;
import net.minecraftforge.registries.RegistryObject;

import java.util.function.Supplier;

public final class ModBlocks {
    private ModBlocks() {}

    public static final DeferredRegister BLOCKS =
            DeferredRegister.create(ForgeRegistries.BLOCKS, ExampleMod.MODID);

    public static final RegistryObject EXAMPLE_BLOCK =
            BLOCKS.register("example_block", () -> new Block(
                    BlockBehaviour.Properties.of()
                            .mapColor(MapColor.STONE)
                            .strength(2.0f, 6.0f)
                            .sound(SoundType.STONE)
            ));

    // Утилита: регистрируем блок и BlockItem “в одном месте”
    private static  RegistryObject registerBlock(
            String name,
            Supplier blockSupplier
    ) {
        RegistryObject block = BLOCKS.register(name, blockSupplier);
        ModItems.ITEMS.register(name, () -> new BlockItem(block.get(), new Item.Properties()));
        return block;
    }

    // Альтернатива: используйте registerBlock(...) вместо ручной пары
    public static final RegistryObject EXAMPLE_BLOCK_2 =
            registerBlock("example_block_2", () -> new Block(BlockBehaviour.Properties.of().strength(1.5f)));

    public static void register(IEventBus bus) {
        BLOCKS.register(bus);
    }
}

6.3. Fabric: регистрация через Registry (база)

6.3.1. Main-класс (Fabric)

package com.example.examplemod;

import net.fabricmc.api.ModInitializer;

public class ExampleMod implements ModInitializer {
    public static final String MODID = "examplemod";

    @Override
    public void onInitialize() {
        ModItems.register();
        ModBlocks.register();
        ModNetworking.registerC2S(); // серверные пакеты
    }
}

6.3.2. Предметы (Fabric)

package com.example.examplemod;

import net.minecraft.item.Item;
import net.minecraft.registry.Registries;
import net.minecraft.registry.Registry;
import net.minecraft.util.Identifier;

public final class ModItems {
    private ModItems() {}

    public static final Item EXAMPLE_INGOT = new Item(new Item.Settings());

    public static void register() {
        Registry.register(Registries.ITEM, new Identifier(ExampleMod.MODID, "example_ingot"), EXAMPLE_INGOT);
    }
}

6.3.3. Блоки + BlockItem (Fabric)

package com.example.examplemod;

import net.minecraft.block.Block;
import net.minecraft.block.AbstractBlock;
import net.minecraft.block.Blocks;
import net.minecraft.item.BlockItem;
import net.minecraft.item.Item;
import net.minecraft.registry.Registries;
import net.minecraft.registry.Registry;
import net.minecraft.util.Identifier;

public final class ModBlocks {
    private ModBlocks() {}

    public static final Block EXAMPLE_BLOCK = new Block(
            AbstractBlock.Settings.copy(Blocks.STONE).strength(2.0f, 6.0f)
    );

    public static void register() {
        Registry.register(Registries.BLOCK, new Identifier(ExampleMod.MODID, "example_block"), EXAMPLE_BLOCK);
        Registry.register(Registries.ITEM, new Identifier(ExampleMod.MODID, "example_block"),
                new BlockItem(EXAMPLE_BLOCK, new Item.Settings()));
    }
}

7. Подход Fabric: скелет проекта и регистрация

7.1. Точка входа (Fabric)

package com.example.examplemod;

import net.fabricmc.api.ModInitializer;

public class ExampleMod implements ModInitializer {
    public static final String MODID = "examplemod";

    @Override
    public void onInitialize() {
        ModItems.register();
        ModBlocks.register();
    }
}

7.2. Регистрация предметов (Fabric)

package com.example.examplemod;

import net.minecraft.item.Item;
import net.minecraft.registry.Registries;
import net.minecraft.registry.Registry;
import net.minecraft.util.Identifier;

public final class ModItems {
    private ModItems() {}

    public static final Item EXAMPLE_INGOT = new Item(new Item.Settings());

    public static void register() {
        Registry.register(Registries.ITEM, new Identifier(ExampleMod.MODID, "example_ingot"), EXAMPLE_INGOT);
    }
}

7.3. Регистрация блока и BlockItem (Fabric)

package com.example.examplemod;

import net.minecraft.block.Block;
import net.minecraft.block.AbstractBlock;
import net.minecraft.block.Blocks;
import net.minecraft.item.BlockItem;
import net.minecraft.item.Item;
import net.minecraft.registry.Registries;
import net.minecraft.registry.Registry;
import net.minecraft.util.Identifier;

public final class ModBlocks {
    private ModBlocks() {}

    public static final Block EXAMPLE_BLOCK = new Block(
            AbstractBlock.Settings.copy(Blocks.STONE).strength(2.0f, 6.0f)
    );

    public static void register() {
        Registry.register(Registries.BLOCK, new Identifier(ExampleMod.MODID, "example_block"), EXAMPLE_BLOCK);
        Registry.register(Registries.ITEM, new Identifier(ExampleMod.MODID, "example_block"),
                new BlockItem(EXAMPLE_BLOCK, new Item.Settings()));
    }
}

8. Практика 1: предмет глубже (поведение, взаимодействие, кулдаун, NBT)

8.1. Forge: предмет, который при ПКМ пишет в чат и ставит кулдаун

package com.example.examplemod.item;

import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResultHolder;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.network.chat.Component;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;

public class PingItem extends Item {
    public PingItem(Properties props) {
        super(props);
    }

    @Override
    public InteractionResultHolder use(Level level, Player player, InteractionHand hand) {
        ItemStack stack = player.getItemInHand(hand);

        if (!level.isClientSide) {
            player.sendSystemMessage(Component.literal("PingItem: сервер получил действие."));
            player.getCooldowns().addCooldown(this, 40); // 2 секунды при 20 tps
        }

        return InteractionResultHolder.sidedSuccess(stack, level.isClientSide);
    }
}

Регистрация (Forge):

// внутри ModItems
public static final RegistryObject PING_ITEM =
    ITEMS.register("ping_item", () -> new PingItem(new Item.Properties().stacksTo(1)));

8.2. Fabric: предмет с NBT-счётчиком кликов

package com.example.examplemod.item;

import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.text.Text;
import net.minecraft.util.Hand;
import net.minecraft.util.TypedActionResult;
import net.minecraft.world.World;
import net.minecraft.entity.player.PlayerEntity;

public class CounterItem extends Item {
    private static final String KEY = "clicks";

    public CounterItem(Settings settings) {
        super(settings);
    }

    @Override
    public TypedActionResult use(World world, PlayerEntity user, Hand hand) {
        ItemStack stack = user.getStackInHand(hand);

        if (!world.isClient) {
            NbtCompound nbt = stack.getOrCreateNbt();
            int clicks = nbt.getInt(KEY) + 1;
            nbt.putInt(KEY, clicks);

            user.sendMessage(Text.literal("Кликов: " + clicks), false);
        }

        return TypedActionResult.success(stack, world.isClient);
    }
}

Регистрация (Fabric):

public static final Item COUNTER_ITEM = new CounterItem(new Item.Settings().maxCount(1));
// Registry.register(..., "counter_item", COUNTER_ITEM);

9. Практика: блок глубже (взаимодействие и серверная логика)

9.1. Forge: блок, который при ПКМ меняет состояние (BooleanProperty)

package com.example.examplemod.block;

import net.minecraft.core.BlockPos;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.BooleanProperty;
import net.minecraft.network.chat.Component;

public class ToggleBlock extends Block {
    public static final BooleanProperty ENABLED = BooleanProperty.create("enabled");

    public ToggleBlock(Properties props) {
        super(props);
        this.registerDefaultState(this.stateDefinition.any().setValue(ENABLED, false));
    }

    @Override
    protected void createBlockStateDefinition(StateDefinition.Builder builder) {
        builder.add(ENABLED);
    }

    @Override
    public InteractionResult use(BlockState state, Level level, BlockPos pos,
                                 Player player, InteractionHand hand, BlockHitResult hit) {
        if (!level.isClientSide) {
            boolean current = state.getValue(ENABLED);
            level.setBlock(pos, state.setValue(ENABLED, !current), 3);
            player.sendSystemMessage(Component.literal("ToggleBlock: enabled=" + (!current)));
        }
        return InteractionResult.sidedSuccess(level.isClientSide);
    }
}

Регистрация:

public static final RegistryObject TOGGLE_BLOCK =
    ModBlocks.BLOCKS.register("toggle_block", () -> new ToggleBlock(
        BlockBehaviour.Properties.of().strength(2.0f)
    ));

// BlockItem регистрируйте в Items, как в паттерне registerBlock(...)

Ресурсы для blockstate с variants по enabled:

// assets/examplemod/blockstates/toggle_block.json
{
  "variants": {
    "enabled=false": { "model": "examplemod:block/toggle_block_off" },
    "enabled=true":  { "model": "examplemod:block/toggle_block_on" }
  }
}

9.2. Fabric: блок с onUse и проверкой server-side

package com.example.examplemod.block;

import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.text.Text;
import net.minecraft.util.ActionResult;
import net.minecraft.util.Hand;
import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.world.World;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.util.math.BlockPos;

public class PingBlock extends Block {
    public PingBlock(Settings settings) {
        super(settings);
    }

    @Override
    public ActionResult onUse(BlockState state, World world, BlockPos pos,
                              PlayerEntity player, Hand hand, BlockHitResult hit) {
        if (!world.isClient) {
            player.sendMessage(Text.literal("PingBlock: сервер обработал onUse."), false);
        }
        return ActionResult.SUCCESS;
    }
}

10. Практика: рецепты, лут, теги (плюс DataGen)

10.1. “Ручные” JSON (универсально)

Рецепт shaped

{
  "type": "minecraft:crafting_shaped",
  "pattern": [
    "II",
    "II"
  ],
  "key": {
    "I": { "item": "examplemod:example_ingot" }
  },
  "result": {
    "item": "examplemod:example_block",
    "count": 1
  }
}

Лут блока (дроп самого себя)

{
  "type": "minecraft:block",
  "pools": [
    {
      "rolls": 1,
      "entries": [
        { "type": "minecraft:item", "name": "examplemod:example_block" }
      ],
      "conditions": [
        { "condition": "minecraft:survives_explosion" }
      ]
    }
  ]
}

Тег предметов (собственный)

{
  "replace": false,
  "values": [
    "examplemod:example_ingot"
  ]
}

10.2. Forge DataGen: рецепты (пример провайдера)

Преимущество DataGen: меньше ручной рутины и меньше ошибок путей.

package com.example.examplemod.datagen;

import com.example.examplemod.ModItems;
import com.example.examplemod.ModBlocks;
import net.minecraft.data.PackOutput;
import net.minecraft.data.recipes.RecipeOutput;
import net.minecraft.data.recipes.ShapedRecipeBuilder;
import net.minecraft.data.recipes.RecipeProvider;
import net.minecraft.world.item.Items;
import net.minecraft.world.level.ItemLike;

public class ModRecipeProvider extends RecipeProvider {
    public ModRecipeProvider(PackOutput output) {
        super(output);
    }

    @Override
    protected void buildRecipes(RecipeOutput output) {
        shaped2x2(output, ModBlocks.EXAMPLE_BLOCK.get(), ModItems.EXAMPLE_INGOT.get());
    }

    private void shaped2x2(RecipeOutput out, ItemLike result, ItemLike ingredient) {
        ShapedRecipeBuilder.shaped(result, 1)
                .pattern("II")
                .pattern("II")
                .define('I', ingredient)
                .unlockedBy("has_ingredient", has(ingredient))
                .save(out);
    }
}

10.3. Fabric: генерация data обычно решается отдельным модулем datagen

В Fabric распространён подход “отдельный entrypoint для datagen”. Концептуально это похоже: провайдеры рецептов/лутов/тегов генерируют JSON в src/main/generated, который затем подключается как ресурс. Реализация зависит от конкретного шаблона проекта.


11. Практика 4: генерация руды или структуры (концептуально)

Worldgen сильно менялся между ветками Minecraft. Поэтому корректный путь такой:

  1. определить целевую версию;

  2. использовать её модель worldgen (placed features / configured features и т. п.);

  3. вынести параметры (частота, высоты, биомы) в data-ресурсы, где это возможно;

  4. тестировать на отдельном мире с фиксированным seed.

Плюсы worldgen через data-ресурсы:

  • часть параметров можно менять без перекомпиляции;

  • проще балансировать.

Минусы:

  • легко “сломать” генерацию неправильными диапазонами или конфликтующими правилами;

  • для сложных структур часто потребуется код и аккуратная интеграция с биомами/шумами.


12. Практика 5: простая сущность (общий подход)

Создание моба включает:

  • регистрацию типа сущности;

  • атрибуты (здоровье, скорость и т. п.);

  • поведение (AI goals);

  • клиентский рендер и модель (для визуала).

Плюсы:

  • расширение геймплея и контента;

  • можно строить новые механики (дроп, взаимодействия, квесты).

Минусы:

  • повышается риск проблем с производительностью и сетевой синхронизацией;

  • требует чёткого разделения client/server, иначе мод будет падать на выделенном сервере.


13. GUI и взаимодействие с игроком

GUI в модах — это почти всегда:

  • серверная логика контейнера/меню (проверки, инвентарь, синхронизация),

  • клиентский экран (отрисовка, ввод),

  • сетевые сообщения, если требуется интерактивность сверх стандартных операций.

Плюсы:

  • можно делать “станки”, интерфейсы, терминалы, конфиги в игре.

Минусы:

  • типовая ошибка — выполнять серверную логику на клиенте;

  • типовая ошибка — забыть синхронизацию данных, из-за чего возникают дюпы/рассинхрон.


14. Сеть и мультиплеер: пакеты C2S и правило “сервер решает”

Ниже минимальный практический пример: клиент нажимает “действие”, отправляет пакет на сервер, сервер выполняет логику и отвечает (или просто подтверждает).

14.1. Forge: SimpleChannel + C2S пакет

14.1.1. Инициализация канала

package com.example.examplemod.network;

import com.example.examplemod.ExampleMod;
import net.minecraft.resources.ResourceLocation;
import net.minecraftforge.network.NetworkRegistry;
import net.minecraftforge.network.simple.SimpleChannel;

public final class ModNetworking {
    private ModNetworking() {}

    private static final String PROTOCOL = "1";
    public static final SimpleChannel CHANNEL = NetworkRegistry.newSimpleChannel(
            new ResourceLocation(ExampleMod.MODID, "main"),
            () -> PROTOCOL,
            PROTOCOL::equals,
            PROTOCOL::equals
    );

    private static int id = 0;

    public static void init() {
        CHANNEL.registerMessage(id++, ExampleC2SPacket.class,
                ExampleC2SPacket::encode,
                ExampleC2SPacket::decode,
                ExampleC2SPacket::handle
        );
    }
}

14.1.2. Пакет C2S

package com.example.examplemod.network;

import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.network.chat.Component;
import net.minecraftforge.network.NetworkEvent;

import java.util.function.Supplier;

public class ExampleC2SPacket {
    private final int value;

    public ExampleC2SPacket(int value) {
        this.value = value;
    }

    public static void encode(ExampleC2SPacket msg, FriendlyByteBuf buf) {
        buf.writeInt(msg.value);
    }

    public static ExampleC2SPacket decode(FriendlyByteBuf buf) {
        return new ExampleC2SPacket(buf.readInt());
    }

    public static void handle(ExampleC2SPacket msg, Supplier ctxSupplier) {
        NetworkEvent.Context ctx = ctxSupplier.get();
        ctx.enqueueWork(() -> {
            ServerPlayer player = ctx.getSender();
            if (player == null) return;

            // Серверная логика
            player.sendSystemMessage(Component.literal("C2S пакет получен. value=" + msg.value));
        });
        ctx.setPacketHandled(true);
    }
}

14.1.3. Отправка пакета с клиента

Например, из предмета при ПКМ:

import com.example.examplemod.network.ModNetworking;
import com.example.examplemod.network.ExampleC2SPacket;
// ...
if (level.isClientSide) {
    ModNetworking.CHANNEL.sendToServer(new ExampleC2SPacket(123));
}

Плюсы:

  • корректная мультиплеерная модель;

  • легко расширять на дополнительные действия.

Минусы:

  • требуется дисциплина версий протокола;

  • нельзя выполнять “мировую” логику на клиенте.


14.2. Fabric: ServerPlayNetworking (C2S)

14.2.1. Идентификатор и регистрация при инициализации

package com.example.examplemod.network;

import com.example.examplemod.ExampleMod;
import net.minecraft.util.Identifier;
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking;
import net.minecraft.text.Text;

public final class ModNetworking {
    private ModNetworking() {}

    public static final Identifier EXAMPLE_C2S = new Identifier(ExampleMod.MODID, "example_c2s");

    public static void registerC2S() {
        ServerPlayNetworking.registerGlobalReceiver(EXAMPLE_C2S, (server, player, handler, buf, responseSender) -> {
            int value = buf.readInt();

            server.execute(() -> {
                player.sendMessage(Text.literal("C2S пакет получен. value=" + value), false);
                // Серверная логика здесь
            });
        });
    }
}

14.2.2. Отправка пакета с клиента

import com.example.examplemod.network.ModNetworking;
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.util.Identifier;
import io.netty.buffer.Unpooled;

PacketByteBuf buf = new PacketByteBuf(Unpooled.buffer());
buf.writeInt(123);
ClientPlayNetworking.send(ModNetworking.EXAMPLE_C2S, buf);

15. Отладка и типовые ошибки (углубление): что ломается чаще всего и как чинить

15.1. “Фиолетово-чёрная” текстура / отсутствует модель

Почти всегда причина в одном из пунктов:

  • неправильный путь в assets//textures/...;

  • в JSON модель ссылается на examplemod:item/..., а modid другой;

  • имя файла не совпадает (регистр, подчёркивания).

Минимальная проверка:

  • сверить modid в коде и в ресурсах;

  • открыть JSON и убедиться, что строка текстуры соответствует пути.

15.2. Краш при старте из-за регистрации

Типовые причины:

  • один и тот же id зарегистрирован дважды;

  • обращение к RegistryObject.get() слишком рано (Forge);

  • клиентские классы загружаются на сервере (Screen/Renderer в common).

Практика (Forge):

  • держать регистрации строго через DeferredRegister;

  • не дергать .get() в статических инициализациях, где порядок не гарантирован.

15.3. “Работает в одиночке, падает на dedicated server”

Причина: на сервере нет клиентских классов. Если common-код импортирует их — сервер падает при загрузке.

Как избегать:

  • рендеры, экраны, клиентские обработчики — только в client-модуле/клиентской инициализации;

  • серверные действия — строго на сервере;

  • сеть — мост между клиентом и сервером.

15.4. Быстрый диагностический минимум

  • читать stacktrace сверху вниз до первого класса вашего мода;

  • проверять Caused by: — там обычно причина;

  • отделять “первичный” NPE/IllegalState от последствий.


16. Сборка, упаковка и публикация

16.1. Сборка

После разработки:

  • собираете JAR через Gradle;

  • проверяете на “чистой” сборке (без dev-среды);

  • тестируете минимум в двух режимах: singleplayer и dedicated server (если мод не чисто клиентский).

16.2. Версионирование и совместимость

Рекомендуемая дисциплина:

  • mod version: major.minor.patch;

  • фиксация совместимости: версия Minecraft и лоадера;

  • список изменений (changelog) на каждую версию.

16.3. Публикация

До публикации стоит подготовить:

  • краткое описание и список функций;

  • требования (версия Minecraft, лоадер, зависимости);

  • инструкции по установке;

  • список известных проблем и ограничений;

  • лицензию (особенно если планируется open-source).

Плюсы такой подготовки:

  • меньше вопросов от пользователей;

  • быстрее воспроизводятся баги.

Минусы:

  • требует времени и дисциплины релизов.


17. Чек-лист перед релизом

  1. Сборка проходит без ошибок в чистой среде.

  2. Нет отсутствующих ресурсов (модели/текстуры/локализация).

  3. Тест одиночной игры: новые предметы/блоки работают, рецепты и дроп корректны.

  4. Тест сервера (если мод не клиентский): старт без краша, взаимодействия корректны.

  5. Нет client-only импорта в common-коде.

  6. Нет конфликтов id и повторной регистрации.

  7. Настроены теги/рецепты/лут так, чтобы мод был устойчив к отсутствию “соседних” модов.

  8. Документация: требования, установка, версии.


18. FAQ

Можно ли сделать мод без Java

Полноценный мод — нет, требуется код (обычно Java). Но часть задач закрывается datapack/resourcepack без программирования.

Что проще: Forge или Fabric

Для “первого мода” часто проще тот стек, для которого у вас лучше шаблон и меньше зависимостей. В среднем:

  • Forge проще “по шаблонам” и типовым решениям в крупных модах,

  • Fabric проще “по минимализму”, но иногда требует больше понимания низкого уровня.

Почему мод ломается после обновления Minecraft

Потому что меняются внутренние классы, регистрационные механики, структуры data-ресурсов и API лоадеров. Нужно закладывать время на сопровождение.

Какой минимальный мод стоит сделать первым

Практичный “первый мод”:

  • 1 предмет,

  • 1 блок,

  • 1 рецепт,

  • корректные модели/текстуры/lang,

  • проверка на клиенте и сервере (если мод не client-only).