Skip to content

Forge 1.18

Icyllis Milica edited this page Jun 23, 2022 · 13 revisions

The Minecraft Forge extension is released simultaneously with Modern UI core. The extension adds the compatibility layer and the extension layer to coordinate Modern UI with Minecraft. This chapter introduces ways to use the extended API for Minecraft mod development.

Threading

You need to know three important threads, which are different threads in Minecraft extension.

Main Thread

Main thread, also the JVM main thread, is the thread calls main function. The thread is used for handling events from the operating system. For example, mouse events, window resizing events, clipboard accessing. This thread contains an event loop that can poll the operating system events and is used as the main event loop of the application. It's provided by GLFW and cannot be shared with AWT, etc.

Render Thread

Because Minecraft uses OpenGL, usually only one thread can make the final API calls. Most of the functions of the underlying 3D graphics API will be called on this thread in a specific order. Render thread, which has the 3D graphics context shared between windows. In Minecraft, render thread is the same thread as main thread. Modern UI also uses this thread for final rendering.

UI Thread

The UI thread is used for handling UI events and recording drawing contents for the UI on application side, there will no graphics context on this thread. All input events from the window for UI should be posted to this thread. UI thread can be main thread or render thread. In Minecraft, UI thread is a separated thread managed by UIManager. Your UI operations, such as click events, will be called on this thread. You cannot directly operate Minecraft game objects, such as entities, blocks, containers, and items.

Thread Scheduling

In the UI component of Modern UI, you cannot use any rendering methods provided by Minecraft, such as Screen.fill, Screen.blit, Font.draw or ItemRenderer.renderAndDecorateItem. You can only use Modern UI's own rendering engine, Canvas. To post a task to main thread, use Minecraft.getInstance().tell or execute, to post a task to UI thread, use MuiForgeApi.postToUiThread.

Interaction Models

Interaction models can be broadly divided into two categories, local and client/server. In most cases, you should use the latter.

Local

The local model means that the UI is initiated on the client without notifying the server. Such a model can be used for game settings, main menu.

Client/server

The client/server model means that the UI is initiated on the server, and then notifies the client. Such a model involves the communication between client and server, a container menu is used as the communication medium. It can be used for server management, container items interaction, crafting items.

Create a Menu Screen

Container menu is required for the client/server model. However, the registration of menu types is slightly different from Forge.

Register your MenuType

Subscribe a RegisterEvent on your mod event bus. Example:

@Mod.EventBusSubscriber(modid = "my_modid", bus = Mod.EventBusSubscriber.Bus.MOD)
public class Registration {
    @SubscribeEvent
    public static void registerMenus(@Nonnull RegistryEvent.Register<MenuType<?>> event) {
        IContainerFactory factory = (containerId, inventory, extraData) -> {
            return new MyMenu(containerId, ...);
        };
        event.getRegistry().register(IForgeMenuType.create(factory).setRegistryName("my_menu"));
    }
}

Reference to the registry entry:

public class MyRegistry {
    public static final RegistryObject<MenuType<?>> MY_MENU =
        RegistryObject.of(new ResourceLocation("my_modid", "my_menu"), ForgeRegistries.CONTAINERS);
}

Open your Menu

Server side (initiator):
Replace your NetworkHooks.openGui with MuiForgeApi.openMenu. See javadoc for details. This opens a Menu on server side.

Example:

public class MyBlock extends Block {
    @Override
    public InteractionResult use(BlockState state, Level level, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) {
        if (!level.isClientSide && level.getBlockEntity(pos) instanceof MyBlockEntity e) {
            // we assume MyBlockEntity implements MenuProvider
            MuiForgeApi.openMenu(e, pos);
        }
        return InteractionResult.sidedSuccess(level.isClientSide);
    }
}

Client side:
Do not use MenuScreens.register, instead, subscribe OpenMenuEvent on your mod event bus. See javadoc for details. This opens a Menu on client side, and a Fragment as your UI lifecycle owner.

Example:

@Mod.EventBusSubscriber(value = Dist.CLIENT, modid = "my_modid", bus = Mod.EventBusSubscriber.Bus.MOD)
public class Registration {
    @OnlyIn(Dist.CLIENT)
    @SubscribeEvent
    static void onMenuOpen(@Nonnull OpenMenuEvent event) {
        if (event.getMenu().getType() == MyRegistry.MY_MENU.get()) {
            // create main fragment
            var fragment = new MyFragment();
            // pass arguments (optional)
            var args = new DataSet();
            args.putInt("token", event.getMenu().containerId);
            fragment.setArguments(args);
            // finish the event with a result
            event.set(fragment);
        }
    }
}

Main Fragment

See Android developer guide and javadoc for details.

Example:

public class MyFragment extends Fragment {
    @Nullable
    @Override
    public View onCreateView(@Nullable ViewGroup container, @Nullable DataSet savedInstanceState) {
        var base = new ScrollView();
        {
            var content = new TestLinearLayout();
            var params = new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
            base.addView(content, params);
        }
        base.setBackground(new Drawable() {
            @Override
            public void draw(@Nonnull Canvas canvas) {
                Paint paint = Paint.get();
                Rect b = getBounds();
                paint.setRGBA(8, 8, 8, 80);
                canvas.drawRoundRect(b.left, b.top, b.right, b.bottom, 8, paint);
            }
        });
        {
            var params = new FrameLayout.LayoutParams(dp(480), dp(360));
            params.gravity = Gravity.CENTER;
            base.setLayoutParams(params);
        }
        return base;
    }
}

Example: https://github.com/SonarSonic/Flux-Networks/blob/1.18/src/main/java/sonar/fluxnetworks/client/mui/FluxDeviceUI.java

Networking

Modern UI provides a faster network handler than Forge, used for communication between client and server. An example can be found in icyllis.modernui.forge.Registration.setupCommon. Read javadoc for details.