Всем привет! Клиентские моды во многом отличаются от глобальных. Но чем?

Основное различие заключается в том, что клиентские моды работают только на стороне игрока и не требуют установки на сервер. Они изменяют визуальную часть, добавляют удобства в управлении или предоставляют вспомогательные функции (например, мини-карты, моды на интерфейс или оптимизацию).

В этом гайде мы разберём, как создать Forge-мод, который будет работать исключительно на клиенте.

(на примере буду работать на MCreator)

Откройте главный класс мода (у меня это "LemonadeMod"), после найдите инициализирующий класс, после напишите следующие -

// в public (название класса без class)
IEventBus modEventBus = FMLJavaModLoadingContext.get().getModEventBus(); // может быть другое название за место modEventBus
modEventBus.addListener(this::setupClient);
// создаем класс setupClient
private void setupClient(final FMLClientSetupEvent event) {
  event.enqueueWork(() -> {
    if (FMLEnvironment.dist == Dist.CLIENT) {
      new LemonadeModClient(); // наш класс
    }
  });
}
  • FMLJavaModLoadingContext.get() - получает контекст загрузки мода.

  • .getModEventBus() - возвращает шину событий (EventBus), специфичную для вашего мода.

  • modEventBus - это просто название переменной, вы можете назвать её как угодно (например, eventBus).

  • addListener - регистрирует метод (в данном случае setupClient), который будет вызываться при наступлении определённого события.

  • this::setupClient - ссылка на метод setupClient в текущем классе.

  • FMLClientSetupEvent - событие, которое вызывается, когда Forge готов к настройке клиентской части мода.

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

  • enqueueWork - добавляет задачу в очередь выполнения на основном потоке Minecraft.

  • Используется, потому что некоторые операции (например, регистрация рендеров) должны выполняться в основном потоке игры.

  • FMLEnvironment.dist - проверяет, в каком окружении работает код (CLIENT или DEDICATED_SERVER).

  • Dist.CLIENT - гарантирует, что код внутри блока выполнится только на клиенте (избыточная проверка, так как FMLClientSetupEvent и так вызывается только на клиенте).

  • LemonadeModClient() - инициализирует клиентскую часть мода (например, регистрирует рендеры для сущностей, блоков и т. д.).

Создаем класс LemonadeModClient -

package net.genamistik.lemonade;

import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent;
import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.event.server.ServerStartingEvent;
import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.api.distmarker.Dist;

@Mod.EventBusSubscriber(bus = Mod.EventBusSubscriber.Bus.MOD)
public class LemonadeModClient {
	public LemonadeModClient() {
	}

	@SubscribeEvent
	public static void init(FMLCommonSetupEvent event) {
		new LemonadeModClient();
	}

	@Mod.EventBusSubscriber
	private static class ForgeBusEvents {
		@SubscribeEvent
		public static void serverLoad(ServerStartingEvent event) {
		}

		@OnlyIn(Dist.CLIENT)
		@SubscribeEvent
		public static void clientLoad(FMLClientSetupEvent event) {
		}
	}
}
  • @Mod.EventBusSubscriber – указывает, что этот класс подписывается на события Forge.

    • bus = Mod.EventBusSubscriber.Bus.MOD – означает, что он слушает события Mod-шины (а не Forge-шины).

  • public LemonadeModClient() – пустой конструктор. Обычно здесь инициализируются клиентские вещи (рендеры, текстуры и т. д.), но в данном случае он пуст.

  • @SubscribeEvent – метод будет вызван при наступлении события FMLCommonSetupEvent.

  • FMLCommonSetupEvent – событие, которое вызывается на обеих сторонах (клиент и сервер) во время загрузки мода.

  • new LemonadeModClient() – создаёт экземпляр клиентского класса.

@Mod.EventBusSubscriber
private static class ForgeBusEvents {
    @SubscribeEvent
    public static void serverLoad(ServerStartingEvent event) {
    }

    @OnlyIn(Dist.CLIENT)
    @SubscribeEvent
    public static void clientLoad(FMLClientSetupEvent event) {
    }
}

Данный кусок кода ForgeBusEvents это подписка на Forge-шину (а не Mod-шину), поэтому он может слушать другие события.

@SubscribeEvent
public static void serverLoad(ServerStartingEvent event) {
}
  • ServerStartingEvent – вызывается при старте сервера.
    (Можно использовать для регистрации команд, инициализации данных и т. д.)

@OnlyIn(Dist.CLIENT)
@SubscribeEvent
public static void clientLoad(FMLClientSetupEvent event) {
}
  • @OnlyIn(Dist.CLIENT) – гарантирует, что метод не вызовется на сервере (иначе будет краш).

  • FMLClientSetupEvent – вызывается только на клиенте, когда можно безопасно регистрировать рендеры, текстуры и т. д.

И все! Он стал клиентским модом! 

Для проверки зайдем с MCreator на сервер 217.106.107.100:27481.

И вы увидите -

image.png.ca832ef51f6a1a8d466eefd5698fbd44.png
image.png.ca832ef51f6a1a8d466eefd5698fbd44.png

Давайте проверим добавив кнопку (на главное меню/стоп экран) и при нажатии майнкрафт будет закрыт (класс TestKnopkaOverlay) -

package net.genamistik.lemonade.client.screens;

import net.minecraftforge.client.event.ScreenEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.api.distmarker.Dist;

import net.minecraft.client.gui.components.Button;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.client.Minecraft;
import net.minecraft.network.chat.Component;
import net.minecraft.client.gui.screens.TitleScreen;
import net.minecraft.client.gui.screens.PauseScreen;

@Mod.EventBusSubscriber(Dist.CLIENT)
public class TestKnopkaOverlay {

    @SubscribeEvent
    public static void onScreenInit(ScreenEvent.Init event) {
        if (event.getScreen() instanceof TitleScreen || event.getScreen() instanceof PauseScreen) {
            int w = Minecraft.getInstance().getWindow().getGuiScaledWidth();
            int h = Minecraft.getInstance().getWindow().getGuiScaledHeight();

            // Координаты кнопки: w / 2 + -90, h / 2 + -4
            int buttonX = w / 2 - 130;  // X = центр экрана - 90
            int buttonY = h / 2 - 8;   // Y = центр экрана - 4

            // Создаем кнопку с желтой "L" (или другим текстом)
            Button button = Button.builder(
                    Component.literal("§eL"),  // Желтая "L"
                    btn -> Minecraft.getInstance().stop()
                )
                .bounds(
                    buttonX,  // Позиция X
                    buttonY,  // Позиция Y
                    20, 20    // Размер кнопки (можно настроить)
                )
                .build();

            event.addListener(button);
        }
    }
}

(его не надо регистрировать)

И на главном меню со стоп меню вы увидите кнопку "L" (желтый)\

Удачной разработки!