MinecraftForge Documentation

repository·1.21.x·Indexed 19 days ago

https://github.com/minecraftforge/documentation

Official documentation for MinecraftForge, providing definitive explanations of Forge development concepts for mod developers. Covers advanced topics such as Access Transformers for modifying class visibility and final flags, as well as comprehensive guides on BlockEntities, including BlockEntityRenderer (BER) implementation, data persistence, ticking, and server-to-client synchronization.

Tokens
92.3K
Snippets
202
Records
350
Agent score
60%

What's inside MinecraftForge Documentation

  1. What is BlockEntityWithoutLevelRenderer (BEWLR)?

    1.21.x
    A BlockEntityWithoutLevelRenderer (BEWLR) is a mechanism used to handle dynamic rendering for items. Unlike the legacy ItemStack system, BEWLR is simpler and provides direct access to the ItemStack being rendered. It is primarily used when an item needs to display complex, animated, or dynamic visuals that a standard static model cannot provide.
  2. Important: Where to edit code in the Forge project

    1.21.x

    When making changes to the codebase, you must adhere to the following rule to avoid breaking your environment:

    • Only edit code in the "Forge" sub-project.

    Do not make changes in the "Clean" project. Modifying the "Clean" project will interfere with ForgeGradle and the patch generation process, which can render your development environment unusable.

  3. How Loot Table components work together

    1.21.x

    Loot tables are constructed using a hierarchical builder pattern. The hierarchy is as follows:

    • LootTable: The root object. Use LootTable#lootTable to get a builder. You add pools via #withPool and apply modifiers via #apply.
    • LootPool: A group of operations. Use LootPool#lootPool to get a builder. Pools use #add for entries, #when for conditions, and #apply for functions. Execution frequency is controlled via #setRolls and #setBonusRolls (which factors in luck).
    • LootPoolEntryContainer: Defines the actual operations (like generating an item). Use LootPoolEntryContainer$Builder. Entries can be executed simultaneously via #append, sequentially via #then, or as a fallback via #otherwise.
    • LootItemCondition: Requirements for an operation to execute. Use LootItemCondition$Builder. Conditions can be combined with #or or inverted with #invert.
    • LootItemFunction: Modifies the result of an execution. Use LootItemFunction$Builder.
    • NumberProvider: Determines how many times a pool executes. Use LootNumberProviderType implementations. A special type is ScoreboardValue (a ScoreboardNameProvider) which pulls the roll count from a scoreboard name.
  4. Use `ItemOverrides` for dynamic model rendering

    1.21.x
    The ItemOverrides class allows a BakedModel to transform itself based on the state of an ItemStack. It functions as a mapping of (BakedModel, ItemStack, ClientLevel, LivingEntity, int) -> BakedModel. This is the mechanism used to implement dynamic item models, such as those responding to item properties or state changes. When a model is resolved via ItemOverrides, the returned model replaces the original for rendering purposes.
  5. Understand the BakedModel interface

    1.21.x

    A BakedModel represents optimized geometry that is nearly ready for GPU rendering. It is the result of calling UnbakedModel#bake (for vanilla loaders) or IUnbakedGeometry#bake (for custom loaders). Unlike abstract shapes, a BakedModel can process the state of an item or block to modify its appearance.

    In most development scenarios, you should use existing implementations rather than implementing this interface manually.

  6. Use IntrinsicHolderTagsProvider to add objects directly

    1.21.x

    An IntrinsicHolderTagsProvider allows you to use the object itself in the #add method within a #tag block. This is achieved by providing a function in the constructor that converts the object into its ResourceKey.

    // Subtype of `IntrinsicHolderTagsProvider`
    public AttributeTagsProvider(PackOutput output, CompletableFuture<HolderLookup.Provider> registries, ExistingFileHelper fileHelper) {
      super(
        output,
        ForgeRegistries.Keys.ATTRIBUTES,
        registries,
        attribute -> ForgeRegistries.ATTRIBUTES.getResourceKey(attribute).get(),
        MOD_ID,
        fileHelper
      );
    }
  7. Understand the Block State system

    1.21.x

    The Block State system replaces the legacy metadata system. Instead of using a single integer (metadata) to represent various block configurations, blocks now use a set of named properties.

    A BlockState is a unique, immutable combination of a Block and a map of Property<?> objects to their specific values.

    Key Concepts:

    • Properties: Each property (e.g., DirectionProperty, BooleanProperty) describes a specific aspect of the block.
    • Immutability: BlockState objects are immutable. Modifying a state via setValue returns a new state.
    • Startup Generation: All possible combinations of a block's properties are generated at game startup. This means you can use reference equality (==) to compare two BlockState objects.

    Best Practices:

    • Avoid Overuse: Do not put every variation into a BlockState. Excessive properties increase startup time and complexity.
    • Rule of Thumb: If a variation has a different name, it should be a separate Block. For example, use properties for the direction of a chair, but use different blocks for different wood types (e.g., oak_chair vs spruce_chair).
    • Complex Data: If a block needs to store complex data that doesn't fit into simple properties, use a BlockEntity instead.
  8. How custom recipes are structured

    1.21.x

    Every custom recipe definition in Forge consists of three core components that work together:

    1. Recipe: The implementation that holds the recipe data and contains the execution logic (e.g., matching inputs and providing results). It typically implements Recipe<Container> for item-based transformations.
    2. RecipeType: Defines the category or context of the recipe (e.g., RecipeType#SMELTING or RecipeType#BLASTING). If your context is unique, you must register a new RecipeType.
    3. RecipeSerializer: Handles the decoding of JSON data and manages network communication between the server and client. A serializer must be registered.

    To integrate these, your Recipe implementation must return the correct RecipeType via #getType() and the correct RecipeSerializer via #getSerializer().

    public record ExampleRecipe(Ingredient input, int data, ItemStack output) implements Recipe<Container> {
      @Override
      public RecipeType<?> getType() {
        return EXAMPLE_TYPE.get();
      }
    
      @Override
      public RecipeSerializer<?> getSerializer() {
        return EXAMPLE_SERIALIZER.get();
      }
    }
  9. Organize mod code using sub-packages

    1.21.x

    Once your top-level package is established, organize your classes into sub-packages using one of two primary methods:

    1. Group By Function: Group classes by their technical purpose.
      • Examples: com.example.mymod.block, com.example.mymod.entity, com.example.mymod.item.
    2. Group By Logic: Group classes by the feature they belong to.
      • Example: If creating a crafting table feature, put its block, menu, and item under com.example.mymod.feature.crafting_table.

    Side Isolation (Client vs. Server)

    It is highly recommended to isolate code based on the runtime/side to prevent crashes on dedicated servers:

    • client package: Use this for all client-only code. Since dedicated servers do not have access to client-only Minecraft classes, keeping this isolated helps ensure you do not accidentally reference client code on a server.
    • server package: Use this for code that only runs on the dedicated server.
    • data package: Use this for code related to data generation.
  10. Use TranslatableContents for server-to-client messaging

    1.21.x

    TranslatableContents is a ComponentContents implementation that performs localization and formatting lazily.

    This is the recommended way to send messages from a server to players. Because the localization happens lazily, the text is translated using the client's locale settings rather than the server's locale (which is always en_us on dedicated servers).

    • Constructor: TranslatableContents(String, Object...) where the first argument is the translation key.
    • Formatting: Supports %s, %1$s, %2$s, etc. Formatting arguments can be Component objects, which will preserve their attributes when inserted.
    • Creation:
      • Use Component#translatable(String, Object...) to create a MutableComponent directly.
      • Use MutableComponent#create(ComponentContents) to create a component from an existing TranslatableContents instance.
    // Creating a translatable component for a player message
    MutableComponent message = Component.translatable("chat.example.welcome", playerName);
    // The server sends this key; the client localizes it to their language.
  11. How BlockEntityRenderer (BER) works

    1.21.x

    A BlockEntityRenderer (BER) is used to render blocks that require dynamic visuals that cannot be represented by static baked models (like JSON or OBJ).

    Key Constraints:

    • A BER requires the associated block to have a BlockEntity.
    • Singleton Pattern: Only one BER instance exists for a given BlockEntityType.
    • State Management: Because the BER is shared across all instances of that block type, you must not store instance-specific data (like animation timers or counters) inside the BER class. Instead, store that data within the BlockEntity instance itself. If you store a frame counter in the BER, it will increment globally for every block of that type in the world.