Client-Side Features
Overview
Section titled “Overview”Matthiesen Lib provides client-side abstractions for common Minecraft rendering and UI tasks. All client-side features work identically on Fabric and NeoForge, abstracting away platform-specific registration events and API differences.
Menu Screen Registration
Section titled “Menu Screen Registration”Register custom menu screens that open when players interact with your custom menus.
Quick Example
Section titled “Quick Example”// In your common client classpublic class MyModCommonClient { public static void registerMenuScreens() { MatthiesenLibClient.registerMenuScreens(registry -> { registry.register(MenuTypesRegistry.MY_MENU, MyMenuScreen::new); registry.register(MenuTypesRegistry.ANOTHER_MENU, AnotherScreen::new); }); }}Single Screen Registration
Section titled “Single Screen Registration”Register a single screen using a Supplier<MenuType>:
MatthiesenLibClient.registerMenuScreen( MenuTypesRegistry.CONFIRMATION_SCREEN, ConfirmationScreen::new);Or using a direct MenuType reference:
MatthiesenLibClient.registerMenuScreen( menuType, // Direct MenuType reference MyScreen::new);Multiple Screens Registration
Section titled “Multiple Screens Registration”Register multiple screens at once:
MatthiesenLibClient.registerMenuScreens(registry -> { registry.register(MenuTypesRegistry.CONFIRMATION_SCREEN, ConfirmationScreen::new); registry.register(MenuTypesRegistry.SELECT_MOVE_SCREEN, SelectMoveScreen::new); registry.register(MenuTypesRegistry.POKEMON_SELECTION_SCREEN, PokemonSelectionScreen::new);});Creating Custom Screens
Section titled “Creating Custom Screens”Matthiesen Lib provides AbstractSimpleScreen for screens that only need a background texture:
public class MyCustomScreen extends AbstractSimpleScreen<MyCustomMenu> { private static final ResourceLocation BACKGROUND = ResourceLocation.fromNamespaceAndPath("mymod", "textures/gui/my_screen.png");
public MyCustomScreen(MyCustomMenu menu, Inventory inventory, Component title) { super(menu, inventory, title); }
@Override protected int getBgWidth() { return 176; // Width of your texture }
@Override protected int getBgHeight() { return 166; // Height of your texture }
@Override protected ResourceLocation getBackgroundTexture() { return BACKGROUND; }
@Override public void render(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTick) { super.render(guiGraphics, mouseX, mouseY, partialTick); // Add custom rendering here }}Benefits of AbstractSimpleScreen:
- Automatically handles background rendering
- Suppresses default container and inventory labels
- Provides standard render flow
Entity Renderer Registration
Section titled “Entity Renderer Registration”Register custom renderers for entities and block entities.
Entity Renderer
Section titled “Entity Renderer”MatthiesenLibClient.registerEntityRenderers(registry -> { registry.registerEntityRenderer( EntityTypeRegistry.MY_CUSTOM_ENTITY, MyEntityRenderer::new );});Or register a single entity renderer:
MatthiesenLibClient.registerEntityRenderer( EntityTypeRegistry.MY_CUSTOM_ENTITY, MyEntityRenderer::new);Block Entity Renderer
Section titled “Block Entity Renderer”MatthiesenLibClient.registerEntityRenderers(registry -> { registry.registerBlockEntityRenderer( BlockEntityTypeRegistry.MY_BLOCK_ENTITY, MyBlockEntityRenderer::new );});Or register a single block entity renderer:
MatthiesenLibClient.registerBlockEntityRenderer( BlockEntityTypeRegistry.MY_BLOCK_ENTITY, MyBlockEntityRenderer::new);Multiple Renderers
Section titled “Multiple Renderers”Register both entity and block entity renderers together:
MatthiesenLibClient.registerEntityRenderers(registry -> { // Entity renderers registry.registerEntityRenderer( EntityTypeRegistry.CUSTOM_PROJECTILE, CustomProjectileRenderer::new );
// Block entity renderers registry.registerBlockEntityRenderer( BlockEntityTypeRegistry.CUSTOM_CHEST, CustomChestRenderer::new );
registry.registerBlockEntityRenderer( BlockEntityTypeRegistry.CUSTOM_SIGN, CustomSignRenderer::new );});HUD Layer Registration
Section titled “HUD Layer Registration”Add custom HUD layers that render on top of (or below) vanilla HUD elements.
Register Above All Other Layers
Section titled “Register Above All Other Layers”MatthiesenLibClient.registerHudLayer( ResourceLocation.fromNamespaceAndPath("mymod", "status_display"), (guiGraphics, deltaTracker) -> { // Your HUD rendering logic int screenWidth = guiGraphics.guiWidth(); int screenHeight = guiGraphics.guiHeight();
guiGraphics.drawString( Minecraft.getInstance().font, "Custom HUD", 10, 10, 0xFFFFFF ); });Register with Specific Ordering
Section titled “Register with Specific Ordering”MatthiesenLibClient.registerHudLayers(registrar -> { // Render below the chat registrar.registerBelow( NeoForgeVanillaGuiLayers.CHAT, ResourceLocation.fromNamespaceAndPath("mymod", "chat_background"), (guiGraphics, deltaTracker) -> { // Render a custom background behind chat } );
// Render above the hotbar registrar.registerAbove( NeoForgeVanillaGuiLayers.HOTBAR, ResourceLocation.fromNamespaceAndPath("mymod", "hotbar_overlay"), (guiGraphics, deltaTracker) -> { // Render custom hotbar overlay } );
// Render at the very top registrar.registerAboveAll( ResourceLocation.fromNamespaceAndPath("mymod", "top_overlay"), (guiGraphics, deltaTracker) -> { // Always renders on top of everything } );
// Render at the very bottom registrar.registerBelowAll( ResourceLocation.fromNamespaceAndPath("mymod", "bottom_overlay"), (guiGraphics, deltaTracker) -> { // Always renders below everything } );});Explicit Ordering
Section titled “Explicit Ordering”MatthiesenLibClient.registerHudLayer( MatthiesenLibHudOrdering.BEFORE, // Ordering mode NeoForgeVanillaGuiLayers.CHAT, // Reference layer ResourceLocation.fromNamespaceAndPath("mymod", "my_layer"), (guiGraphics, deltaTracker) -> { // Render before chat });
// Or render after a specific layerMatthiesenLibClient.registerHudLayer( MatthiesenLibHudOrdering.AFTER, NeoForgeVanillaGuiLayers.HOTBAR, ResourceLocation.fromNamespaceAndPath("mymod", "my_layer"), (guiGraphics, deltaTracker) -> { // Render after hotbar });Available Vanilla Layers
Section titled “Available Vanilla Layers”Use NeoForgeVanillaGuiLayers constants for ordering:
CAMERA_OVERLAYS- Fire, water, pumpkin overlaysCROSSHAIR- CrosshairHOTBAR- Hotbar and selected itemJUMP_METER- Horse jump meterEXPERIENCE_BAR- XP barPLAYER_HEALTH- Health heartsARMOR_LEVEL- Armor iconsFOOD_LEVEL- Food/hunger barVEHICLE_HEALTH- Mount healthAIR_LEVEL- Air bubblesSELECTED_ITEM_NAME- Item name tooltipSPECTATOR_TOOLTIP- Spectator mode tooltipsEXPERIENCE_LEVEL- XP level numberEFFECTS- Status effectsBOSS_OVERLAY- Boss health barsSLEEP_OVERLAY- Sleep overlayDEMO_OVERLAY- Demo mode overlayDEBUG_OVERLAY- F3 debug screenSCOREBOARD_SIDEBAR- ScoreboardOVERLAY_MESSAGE- Action bar messagesTITLE- Title/subtitle textCHAT- Chat windowTAB_LIST- Player list (Tab)SUBTITLE_OVERLAY- SubtitlesSAVING_INDICATOR- Saving world indicator
Complete HUD Example
Section titled “Complete HUD Example”public class MyModCommonClient { public static void registerHudLayers() { MatthiesenLibClient.registerHudLayers(registrar -> { // Status display in top-left corner registrar.registerAbove( NeoForgeVanillaGuiLayers.HOTBAR, ResourceLocation.fromNamespaceAndPath("mymod", "status_hud"), (guiGraphics, deltaTracker) -> { Minecraft mc = Minecraft.getInstance(); if (mc.player == null) return;
int x = 10; int y = 10;
guiGraphics.drawString( mc.font, "Mana: " + getPlayerMana(mc.player), x, y, 0x00FFFF ); } ); }); }}Block Outline Listeners
Section titled “Block Outline Listeners”Customize the block selection outline (the box that appears when you look at a block).
Basic Example
Section titled “Basic Example”MatthiesenLibClient.registerBlockOutlineListener(context -> { BlockPos pos = context.blockHitResult().getBlockPos(); BlockState state = context.level().getBlockState(pos);
// Only customize for specific blocks if (!(state.getBlock() instanceof MyCustomBlock)) { return true; // Keep vanilla outline }
// Render custom outline VoxelShape shape = state.getShape(context.level(), pos);
double x = pos.getX() - context.camera().getPosition().x(); double y = pos.getY() - context.camera().getPosition().y(); double z = pos.getZ() - context.camera().getPosition().z();
LevelRenderer.renderVoxelShape( context.poseStack(), context.multiBufferSource().getBuffer(RenderType.lines()), shape, x, y, z, 1.0F, 0.0F, 0.0F, 0.8F, // Red outline false );
return false; // Cancel vanilla outline});Multi-Block Outline
Section titled “Multi-Block Outline”Highlight multiple blocks as a group:
MatthiesenLibClient.registerBlockOutlineListener(context -> { BlockPos basePos = MyLogic.findMultiBlockBase( context.level(), context.blockHitResult().getBlockPos() );
if (basePos == null) { return true; // Not a multi-block, use vanilla outline }
// Get all positions in the multi-block structure List<BlockPos> positions = MyLogic.getMultiBlockPositions(basePos);
for (BlockPos pos : positions) { BlockState state = context.level().getBlockState(pos); VoxelShape shape = state.getShape(context.level(), pos);
double x = pos.getX() - context.camera().getPosition().x(); double y = pos.getY() - context.camera().getPosition().y(); double z = pos.getZ() - context.camera().getPosition().z();
LevelRenderer.renderVoxelShape( context.poseStack(), context.multiBufferSource().getBuffer(RenderType.lines()), shape, x, y, z, 0.0F, 1.0F, 0.0F, 0.6F, // Green outline false ); }
return false; // Cancel vanilla outline});Context Information
Section titled “Context Information”The MatthiesenLibBlockOutlineContext provides:
level()- The client levelblockHitResult()- The block being looked atposeStack()- The pose stack for renderingcamera()- The camera for position calculationsmultiBufferSource()- The buffer source for rendering
Return true to keep vanilla outline, false to cancel it.
Client Initialization Pattern
Section titled “Client Initialization Pattern”Here’s a complete example showing how to structure client-side initialization:
package dev.example.mymod;
import dev.matthiesen.common.matthiesen_lib.MatthiesenLibClient;import dev.example.mymod.registry.MenuTypesRegistry;import dev.example.mymod.ui.client.*;
public class MyModCommonClient { public static void initialize() { MyModConstants.createInfoLog("Initializing client logic"); }
public static void registerMenuScreens() { MatthiesenLibClient.registerMenuScreens(registry -> { registry.register(MenuTypesRegistry.MY_MENU, MyMenuScreen::new); registry.register(MenuTypesRegistry.SETTINGS_MENU, SettingsScreen::new); }); }
public static void registerEntityRenderers() { MatthiesenLibClient.registerEntityRenderers(registry -> { registry.registerEntityRenderer( EntityTypeRegistry.MY_ENTITY, MyEntityRenderer::new );
registry.registerBlockEntityRenderer( BlockEntityTypeRegistry.MY_BLOCK_ENTITY, MyBlockEntityRenderer::new ); }); }
public static void registerHudLayers() { MatthiesenLibClient.registerHudLayers(registrar -> { registrar.registerAbove( NeoForgeVanillaGuiLayers.HOTBAR, ResourceLocation.fromNamespaceAndPath("mymod", "status_hud"), (guiGraphics, deltaTracker) -> { // Custom HUD rendering } ); }); }
public static void registerBlockOutlines() { MatthiesenLibClient.registerBlockOutlineListener(context -> { // Custom block outline logic return true; }); }}Then call these methods from your platform-specific client initializers:
Fabric:
public class MyModFabricClient implements ClientModInitializer { @Override public void onInitializeClient() { MyModCommonClient.initialize(); MyModCommonClient.registerMenuScreens(); MyModCommonClient.registerEntityRenderers(); MyModCommonClient.registerHudLayers(); MyModCommonClient.registerBlockOutlines(); }}NeoForge:
public class MyModNeoForgeClient { public MyModNeoForgeClient(IEventBus modEventBus, ModContainer modContainer) { MyModCommonClient.initialize(); MyModCommonClient.registerMenuScreens(); MyModCommonClient.registerEntityRenderers(); MyModCommonClient.registerHudLayers(); MyModCommonClient.registerBlockOutlines(); }}Best Practices
Section titled “Best Practices”Separate Client and Common Code
Section titled “Separate Client and Common Code”Always keep client-only code separate from common code. Use separate packages:
common/- Server and client codecommon/.../ui/client/- Client-only UI codefabric/orneoforge/- Platform-specific initialization
Use Suppliers for Lazy Registration
Section titled “Use Suppliers for Lazy Registration”When registering content, use Supplier references to avoid initialization order issues:
// Good - uses SupplierMatthiesenLibClient.registerMenuScreen( MenuTypesRegistry.MY_MENU, // Supplier<MenuType> MyScreen::new);
// Also good - direct reference when you know it's initializedMatthiesenLibClient.registerMenuScreen( myMenuType, // Direct MenuType MyScreen::new);Check for Client-Side Environment
Section titled “Check for Client-Side Environment”Some methods should only run on the client:
if (level.isClientSide()) { // Safe to use client-only code here}Test on Both Platforms
Section titled “Test on Both Platforms”Always test your client-side features on both Fabric and NeoForge to ensure they work identically.