Skip to content

3. Mod Developer Things

dfdvdsf edited this page May 9, 2026 · 12 revisions

Mod Developer Things

Want to integrate with CreateWorldUI? Here's what you need to know.


API Overview

Want to know what APIs you can work with? Here's the lineup:

GameRuleApplier

Package: decok.dfcdvadstf.createworldui.api.gamerule.GameRuleApplier

Handles applying game rules when a world loads.

Method What it does
setPendingGameRules(Map<String, String>) Queue up game rules to apply when the next world is created.
getPendingGameRules() Get the currently pending rules (may be null).

Pending rules are applied via WorldEvent.Load and then cleared.

GameRuleMonitorNSetter

Package: decok.dfcdvadstf.createworldui.api.gamerule.GameRuleMonitorNSetter

For getting, setting, and adding game rules.

Method What it does
getAllGamerules(World) Get all game rules from a world as Map<String, GameruleValue>.
setGamerule(World, String, String) Set a game rule on a world.

The GameruleValue inner class stores values in multiple types (String, boolean, int, double) and has getOptimalValue() to pick the best one.

GameRuleTooltipRegistry (After 5.0.0)

Package: decok.dfcdvadstf.createworldui.api.gamerule.GameRuleTooltipRegistry

This is your go-to API for registering and managing game rule tooltips. We pulled it out from GuiScreenGameRuleEditor to keep things cleaner and easier to access.

Method What it does
registerTooltip(String ruleName, String tooltip) Register a single tooltip for a game rule
registerTooltips(Map<String, String> tooltips) Register multiple tooltips at once
getTooltip(String ruleName) Get tooltip for a rule (follows priority: localization > registered > default)
hasRegisteredTooltip(String ruleName) Check if a tooltip is already registered
removeTooltip(String ruleName) Remove a registered tooltip
clearAllTooltips() Clear all registered tooltips
getRegisteredCount() Get the number of registered tooltips
getAllRegisteredTooltips() Get a read-only copy of all registered tooltips

How it works: When you call getTooltip(), the system checks in this order:

  1. Localization filegamerule.{ruleName}.tooltip.description
  2. Registered tooltips — via this API
  3. Built-in defaults — only for vanilla rules

This means your mod's tooltip can be overridden by a resource pack, which is great for localization support!

Migration note: If you're using the old GuiScreenGameRuleEditor.registerTooltip(), don't worry — it still works (marked as @Deprecated), but we recommend switching to the new API when you have time.

GameRuleNameRegistry (After 5.0.0)

Package: decok.dfcdvadstf.createworldui.api.gamerule.GameRuleNameRegistry

Need to set display names for game rules? This API's got you covered. Before 5.0.0, you were stuck with localization files only. Now you can do it through code too!

Method What it does
registerName(String ruleName, String displayName) Register a display name for a game rule
registerNames(Map<String, String> names) Register multiple display names at once
getName(String ruleName) Get display name for a rule (follows priority: localization > registered > raw name)
hasRegisteredName(String ruleName) Check if a display name is already registered
removeName(String ruleName) Remove a registered display name
clearAllNames() Clear all registered display names
getRegisteredCount() Get the number of registered display names
getAllRegisteredNames() Get a read-only copy of all registered display names

How it works: When you call getName(), the system checks in this order:

  1. Localization filegamerule.{ruleName}.name
  2. Registered names — via this API
  3. Raw rule name — e.g., doFireTick

This way, resource packs can still override your display names for localization purposes!

GameRuleCategoryRegistry (After 5.0.0)

Package: decok.dfcdvadstf.createworldui.api.gamerule.GameRuleCategoryRegistry

Looking to organize game rules into categories? Think of this API like folders for your rules — makes the editor way more organized!

Method What it does
createCategory(String categoryKey, List<String> ruleNames) Create a new category with rules
addRuleToCategory(String categoryKey, String ruleName) Add a single rule to a category
addRulesToCategory(String categoryKey, List<String> ruleNames) Add multiple rules to a category
getRulesInCategory(String categoryKey) Get all rules in a category (read-only list)
getCategoryForRule(String ruleName) Get the category that a rule belongs to
getAllCategories() Get all category keys (in registration order)
getCategoryDisplayName(String categoryKey) Get localized display name for a category
removeRuleFromCategory(String ruleName) Remove a rule from its category
removeCategory(String categoryKey) Remove an entire category
clearCustomCategories() Clear custom categories (keep defaults)
clearAllCategories() Clear all categories (including defaults)
getCategoryCount() Get the number of categories
getAllCategoriesMap() Get complete category mapping (read-only)

Default Categories: Vanilla rules come pre-categorized:

  • World — doFireTick, doTileDrops, doDaylightCycle
  • Mobs — doMobSpawning, doMobLoot, mobGriefing
  • Player — naturalRegeneration, keepInventory
  • Chat — commandBlockOutput

Localizing category names:

gamerule.category.myMod=My Mod Rules

And then the category will display with that name in the editor!

DifficultyApplier

Package: decok.dfcdvadstf.createworldui.api.DifficultyApplier

Applies difficulty settings when a world is created. Works with the tab system to set difficulty on the IntegratedServer via Mixin.

And here's the thing — vanilla GuiCreateWorld has no difficulty field at all, so the mod caches it locally and syncs it to mc.gameSettings.difficulty on every write. DifficultyApplier then queues it up as a "pending" value, and MixinIntegratedServer consumes it when the world finishes loading.

Method What it does
setPendingDifficulty(EnumDifficulty) Queue a difficulty to apply on the next world load.
consumePendingDifficulty() Get and clear the pending difficulty (consume pattern).
hasPendingDifficulty() Check if there's a pending difficulty waiting.

Notes: The ruleName in it

GuiCyclableButton

Package: decok.dfcdvadstf.createworldui.api.GuiCyclableButton

A button that cycles through values — click it or scroll your mouse wheel over it to switch. Left-click or scroll down goes forward (+1), scroll up goes backward (−1). Used for game mode, difficulty, and world type in the built-in tabs, and you can use it in your own tabs too.

How it works

It takes two callbacks:

  • TextSupplier — a getText() method that returns the current display string. Called every time the button updates.
  • CycleHandler — an onCycle(int direction) method that gets called when the user clicks or scrolls. direction is +1 or −1.

Example

// A button that cycles through "Easy / Normal / Hard"
GuiCyclableButton diffButton = new GuiCyclableButton(
    200,                    // button ID
    width / 2 - 104,       // x
    height / 2,            // y
    208, 20,               // width, height
    this::getDifficultyText, // TextSupplier — returns current text
    direction -> cycleDifficulty(direction) // CycleHandler — direction is +1 or -1
);
addButton(diffButton);

// Your TextSupplier
private String getDifficultyText() {
    return "Difficulty: " + difficulties[currentIndex];
}

// Your CycleHandler
private void cycleDifficulty(int direction) {
    currentIndex += direction;
    if (currentIndex >= difficulties.length) currentIndex = 0;
    if (currentIndex < 0) currentIndex = difficulties.length - 1;
}

Tip: Call diffButton.updateText() in your drawScreen() to keep the display text in sync — especially if the value can change from somewhere else (like hardcore mode locking difficulty to Hard).

IGuiCreateWorldAccess

Package: decok.dfcdvadstf.createworldui.mixin.access.IGuiCreateWorldAccess

Vanilla GuiCreateWorld hides everything behind private — so this is an Accessor mixin interface that auto-generates getters/setters at runtime. External mods (or our own non-mixin code) can just cast the screen instance to IGuiCreateWorldAccess and read/write state directly — no need to write another mixin or reflect into private fields.

Here's where it gets interesting — this interface lives under the mixin.* package (not api/) because Mixin's package config "seals" that package tree. Classes inside become mixin templates. But don't worry — external code can still import and cast it just fine. The sealing only blocks Class.forName-style direct loads from outside the mixin pipeline.

What you can access

Accessor method Vanilla field Type
createWorldUI$getWorldName() / setWorldName(String) field_146330_J String
createWorldUI$getGameMode() / setGameMode(String) field_146342_r String
createWorldUI$getSeed() / setSeed(String) field_146329_I String
createWorldUI$getWorldTypeIndex() / setWorldTypeIndex(int) field_146331_K int
createWorldUI$getGenerateStructures() / setGenerateStructures(boolean) field_146341_s boolean
createWorldUI$getBonusChest() / setBonusChest(boolean) field_146338_v boolean
createWorldUI$getAllowCheats() / setAllowCheats(boolean) field_146340_t boolean
createWorldUI$getHardcore() / setHardcore(boolean) field_146337_w boolean
createWorldUI$getParentScreen() field_146332_f GuiScreen (read-only)

Quick Example

GuiCreateWorld gui = ...;
IGuiCreateWorldAccess access = (IGuiCreateWorldAccess)(Object) gui;
String name = access.createWorldUI$getWorldName();
access.createWorldUI$setWorldName("My Shiny New World");

Note: All accessor methods carry the createWorldUI$ prefix to avoid name clashes with other mods that might define their own accessor interfaces.

ContentPanelRenderer

Package: decok.dfcdvadstf.createworldui.api.ContentPanelRenderer

A shared renderer for the content panel — gives you the header separator, a tiled panel background, and the footer separator. Use the pieces you want individually, or let drawContentPanel() do the whole thing in one shot.(After 4.2.0)

Pretty straightforward, really — the GameRule Editor and other screens all draw these panels the same way, so this thing packages it all up so you don't have to copy-paste rendering code into your own screen.

What you get

Method What it draws
drawContentPanel(x, top, width, bottom) The whole thing — header line, tiled background, footer line.
drawHeaderSeparator(x, y, width) Just the top separator line (2px tall, tiled).
drawFooterSeparator(x, y, width) Just the bottom separator line (2px tall, tiled).
drawSeparator(x, y, width, texture) A separator line with your own 32×2 texture.
drawPanelBackground(x, y, width, height) Just the panel background (tiles the 16×16 texture).

Textures

Field Path Size
HEADER_SEPARATOR createworldui:textures/gui/header_separator.png 32×2
FOOTER_SEPARATOR createworldui:textures/gui/footer_separator.png 32×2
PANEL_BACKGROUND createworldui:textures/gui/panel_background.png 16×16

You can override these in your resource pack — or pass a custom ResourceLocation to drawSeparator() if you want a different style.

Quick Example

// Draw a full content panel in one call
ContentPanelRenderer.drawContentPanel(panelX, panelTop, panelWidth, panelBottom);

// Or draw pieces separately
ContentPanelRenderer.drawHeaderSeparator(panelX, panelTop, panelWidth);
ContentPanelRenderer.drawPanelBackground(panelX, panelTop + 2, panelWidth, panelHeight - 4);
ContentPanelRenderer.drawFooterSeparator(panelX, panelBottom - 2, panelWidth);

Tip: The separator height is always 2 GUI pixels (SEPARATOR_HEIGHT = 2). When using drawContentPanel(), the background is automatically placed between the two separators.

TabRegistry

Package: decok.dfcdvadstf.createworldui.api.tab.TabRegistry

The static registry for external mods to register custom tabs. See Adding a Custom Tab for details and examples.


Tab System

The world creation screen uses a tab system. Each tab implements AbstractScreenTab.

Built-in Tabs

Class Tab ID What it does
GameTab 100 World name, game mode, difficulty, cheats
WorldTab 101 Seed, world type, structures, bonus chest
MoreTab 102 GameRule Editor, experiments, data packs

TabManager

Package: decok.dfcdvadstf.createworldui.api.tab.TabManager

TabManager handles tab switching, state persistence (survives window resizes), and gives you accessors for the shared state. After 5.0.0, all 8 vanilla-field accessors proxy straight to GuiCreateWorld through IGuiCreateWorldAccess — no local cache, no bridge, no drift. The only exception is difficulty — vanilla GuiCreateWorld has no difficulty field, so it's cached locally and synced to mc.gameSettings.difficulty on write.

Method What it does
getWorldName() / setWorldName(String) Get or set the world name. Proxies to vanilla via accessor.
getGameMode() / setGameMode(String) Get or set the game mode. Proxies to vanilla via accessor.
getDifficulty() / setDifficulty(EnumDifficulty) Get or set the difficulty. Cached locally — synced to mc.gameSettings.
getHardcore() / setHardcore(boolean) Get or set hardcore mode. Proxies to vanilla via accessor.
getAllowCheats() / setAllowCheats(boolean) Get or set allow cheats. Proxies to vanilla via accessor.
getSeed() / setSeed(String) Get or set the world seed. Proxies to vanilla via accessor.
getWorldTypeIndex() / setWorldTypeIndex(int) Get or set the world type. Proxies to vanilla via accessor.
getGenerateStructures() / setGenerateStructures(boolean) Get or set generate structures. Proxies to vanilla via accessor.
getBonusChest() / setBonusChest(boolean) Get or set bonus chest. Proxies to vanilla via accessor.
getWorldNameForCreation() Returns the actual name used for world creation — falls back to default if blank (depends on config).

TabState

Package: decok.dfcdvadstf.createworldui.api.tab.TabState

An enum that defines texture coordinates and text colors for the four tab interaction states — NORMAL, HOVER, SELECTED, SELECTED_HOVER. Used by the tab button renderer to pick the right texture slice and color.

And there's a little twist — when ArchaicFix is loaded and topTabCharatorModernWhite config is on, the highlight states use white instead of yellow. Just so the tab text matches the rest of the modern UI look.


Adding a Custom Tab

You can register custom tabs into the world creation screen using the TabRegistry API.

TabRegistry

Package: decok.dfcdvadstf.createworldui.api.tab.TabRegistry

It's a static registry — register your tabs during mod init, and when the world creation screen opens, TabManager freezes the registry and creates all the tabs. Simple as that.

Key Methods

Method What it does
registerTab(Supplier<Tab>, int, String, int) Register a tab with factory, ID, name key, and priority.
registerTab(Supplier<Tab>, int, String) Same thing, but priority defaults to tabId.
getEntries() Get all entries sorted by priority (unmodifiable).
isFrozen() Check if the registry is frozen (GUI already initialized).
freeze() Freeze the registry. Called internally by TabManager.

Parameters

Parameter Type What it means
factory Supplier<Tab> A factory that creates your Tab instance. Called once when TabManager is constructed.
tabId int Unique tab ID. Built-in tabs use 100–102, so start from 103.
nameKey String Localization key for the tab name (like "mymod.tab.custom").
priority int Sort order — lower comes first. Defaults to tabId if not specified. Built-in tabs use 0, 1, 2.

Entries are sorted by priority ascending, then tabId ascending as a tiebreaker.

Registration Example

import cpw.mods.fml.common.Mod;
import cpw.mods.fml.common.event.FMLPreInitializationEvent;
import decok.dfcdvadstf.createworldui.api.tab.AbstractScreenTab;
import decok.dfcdvadstf.createworldui.api.tab.Tab;
import decok.dfcdvadstf.createworldui.api.tab.TabManager;
import decok.dfcdvadstf.createworldui.api.tab.TabRegistry;
import net.minecraft.client.gui.GuiButton;

@Mod(modid = "mymod", name = "My Mod", version = "1.0")
public class MyMod {

    @Mod.EventHandler
    public void preInit(FMLPreInitializationEvent event) {
        TabRegistry.registerTab(
            MyCustomTab::new,        // Supplier<Tab> factory
            103,                     // Unique tab ID (start from 103)
            "mymod.tab.custom",      // Localization key
            5                        // Sort priority (lower = earlier)
        );
    }
}

// Your custom tab
public class MyCustomTab extends AbstractScreenTab {

    public MyCustomTab() {
        super(103, "mymod.tab.custom"); // Must match what you registered
    }

    @Override
    public void initGui(TabManager tabManager, int width, int height) {
        super.initGui(tabManager, width, height);
        // Add your buttons and fields here
        setVisible(false);
    }

    @Override
    public void drawScreen(int mouseX, int mouseY, float partialTicks) {
        if (!visible) return;
        // Draw your tab content here
    }

    @Override
    public void actionPerformed(GuiButton button) {
        // Handle button clicks here
    }

    @Override
    public void mouseClicked(int mouseX, int mouseY, int mouseButton) {
        // Handle mouse clicks here
    }

    @Override
    public void keyTyped(char typedChar, int keyCode) {
        // Handle key input here
    }
}

And don't forget the localization in your lang file:

mymod.tab.custom=My Tab

Things to Keep in Mind

  • Register early — like FMLPreInitializationEvent. Once TabManager is constructed the registry freezes, and registering after that throws an IllegalStateException.
  • Tab IDs must be unique — duplicate IDs throw an IllegalArgumentException.
  • AbstractScreenTab constructor must match — the tabId and nameKey you pass there need to be the same ones you gave TabRegistry.registerTab().
  • initGui() gets called by TabManager — on init and on resize. Always call super.initGui() and set setVisible(false) at the end.
  • Use tabManager accessors (like getWorldName(), setGameMode()) to read/write the shared world creation state.

GameRule Editor

Want to add custom game rules to the editor? Pretty straightforward — just add the game rules through the GameRule class like you used to do, name your Rule name then set a default boolean or number value. And just like that, they pop up on the page with their names and default values ready to go.
But here's the catch — what if you want tooltip descriptions for your rules? Or customize how vanilla rule names appear? That's where things get interesting, and we've got a couple of approaches for you.

Adding Tooltip Descriptions

Simple way (Recommended)

The modpack usage is showing how to use it. Just add entries like this:

gamerule.yourRuleName.tooltip.description=Your description here

That's it.

Another hard way (After 1.0.1 - Legacy)

It can only be achieved by the hard code, and this doesn't like the simple way if you cannot handle it carefully it cannot show the localized name. so it's not welcomed to the only using simple ways.

Note for 5.0.0+: This method is now deprecated. Please use the new GameRuleTooltipRegistry API (see API Overview section below). But if you're on older versions, here's how it works:

Codes are belowed:

package idk.unknown.which;

import cpw.mods.fml.common.Mod;
import cpw.mods.fml.common.event.FMLInitializationEvent;
import decok.dfcdvadstf.createworldui.gamerule.GuiScreenGameRuleEditor;

@Mod(modid = "idk", name = "idk", version = "idk")
public class idk {
    @Mod.EventHandler
    public void init(FMLInitializationEvent event){
        // There are two ways to register this
        // registerTooltip() can add one single tooltip
        // registerTooltips() can add a Map, which contains many rules and tooltips, to the tooltip list. 
        GuiScreenGameRuleEditor.registerTooltip("randomTickSpeed", "Controls how fast random ticks occur");
        GuiScreenGameRuleEditor.registerTooltip("myCustomRule", "This is a rule added by another mod"); // Former is ruleName, latter is tooltips. 
        Map<String,String> myCustomTooltips = new HashMap<>();
        myCustomTooltips.put("myCustomRule2","This is a rule added by another mod");
        myCustomTooltips.put("myCustomRule3","This is a rule added by another mod");
        myCustomTooltips.put("myCustomRule4","This is a rule added by another mod");
        // myCustomTooltips.put(........);
        GuiScreenGameRuleEditor.registerTooltips(myCustomTooltips);
    }
}

New API way (After 5.0.0 - Recommended!)

Here's the deal — starting from 5.0.0, we pulled the tooltip registration out into its own API class. So much cleaner!

Package: decok.dfcdvadstf.createworldui.api.gamerule.GameRuleTooltipRegistry

package idk.unknown.which;

import cpw.mods.fml.common.Mod;
import cpw.mods.fml.common.event.FMLInitializationEvent;
import decok.dfcdvadstf.createworldui.api.gamerule.GameRuleTooltipRegistry;

@Mod(modid = "idk", name = "idk", version = "idk")
public class idk {
    @Mod.EventHandler
    public void init(FMLInitializationEvent event){
        // Method 1: Register a single tooltip
        GameRuleTooltipRegistry.registerTooltip("randomTickSpeed", "Controls how fast random ticks occur");
        GameRuleTooltipRegistry.registerTooltip("myCustomRule", "This is a rule added by another mod");
        
        // Method 2: Register multiple tooltips at once
        Map<String, String> myCustomTooltips = new HashMap<>();
        myCustomTooltips.put("myCustomRule2", "This is a rule added by another mod");
        myCustomTooltips.put("myCustomRule3", "This is a rule added by another mod");
        GameRuleTooltipRegistry.registerTooltips(myCustomTooltips);
    }
}

Why the new API is better:

  • ✅ Separate from GUI class — cleaner architecture
  • ✅ More methods: removeTooltip(), clearAllTooltips(), hasRegisteredTooltip(), etc.
  • ✅ Better organized and easier to maintain
  • ✅ Still backward compatible!

Localizing GameRule Display Names (After 3.1.0)

Simple way (Recommended)

Same idea as tooltips — just add this to your lang file:

gamerule.yourRuleName.name=Your Display Name

If no name localization is provided, the raw rule name is shown instead.

New API way (After 5.0.0)

Guess what — you can register display names through code now too! Follows the exact same pattern as the tooltip API:

Package: decok.dfcdvadstf.createworldui.api.gamerule.GameRuleNameRegistry

import decok.dfcdvadstf.createworldui.api.gamerule.GameRuleNameRegistry;

// Register a single display name
GameRuleNameRegistry.registerName("myCustomRule", "My Custom Rule");

// Or register multiple at once
Map<String, String> names = new HashMap<>();
names.put("customRule1", "Custom Rule One");
names.put("customRule2", "Custom Rule Two");
GameRuleNameRegistry.registerNames(names);

Priority: Just like tooltips, it checks localization file first, then registered names, then falls back to raw rule name.

Organizing GameRules into Categories (After 5.0.0)

Got a ton of custom game rules and wondering how to keep them organized? You're in luck — we built a category system just for that!

Default Categories for Vanilla Rules

Out of the box, vanilla game rules are already categorized:

  • 世界 (World): doFireTick, doTileDrops, doDaylightCycle
  • 生物 (Mobs): doMobSpawning, doMobLoot, mobGriefing
  • 玩家 (Player): naturalRegeneration, keepInventory
  • 聊天 (Chat): commandBlockOutput

UI Layout

In the GameRule Editor, categories are displayed like this:

|--------------|
|     生物     |    ← Category title (centered, localizable)
| .....    []  |    ← Game rule controls
| .....    []  |
|--------------|

The category title is centered and always visible (not collapsible). Below it are all the game rule controls for that category.

Adding Your Custom Rules to Categories

Package: decok.dfcdvadstf.createworldui.api.gamerule.GameRuleCategoryRegistry

import decok.dfcdvadstf.createworldui.api.gamerule.GameRuleCategoryRegistry;
import java.util.Arrays;

// Method 1: Create a new category with rules
GameRuleCategoryRegistry.createCategory(
    "gamerule.category.myMod",  // Category key (will be localized)
    Arrays.asList("myCustomRule1", "myCustomRule2", "myCustomRule3")
);

// Method 2: Add rules to an existing category
GameRuleCategoryRegistry.addRuleToCategory("gamerule.category.myMod", "anotherRule");

// Method 3: Add multiple rules at once
GameRuleCategoryRegistry.addRulesToCategory(
    "gamerule.category.myMod",
    Arrays.asList("rule1", "rule2", "rule3")
);

Localizing Category Names

Just add this to your lang file:

gamerule.category.world=世界
gamerule.category.mobs=生物
gamerule.category.player=玩家
gamerule.category.chat=聊天
gamerule.category.myMod=My Mod Rules

So there you have it — your categories show up with nice localized names!

In-Game /gameruleEditor Command

Class: decok.dfcdvadstf.createworldui.command.CommandGameRuleEditor

Opens the GameRule Editor in-game. You need either:

  • igGameruleEdit config option enabled, or
  • ModernDifficultyLocker installed.

Requires permission level 2 (same as /gamerule).


GameRule API Examples

Want to see how it all works together? Here are some practical examples.

Example 1: Register a single tooltip

import decok.dfcdvadstf.createworldui.api.gamerule.GameRuleTooltipRegistry;

// Call this during your mod initialization
GameRuleTooltipRegistry.registerTooltip(
    "myCustomRule",
    "This is my custom game rule description"
);

Example 2: Register multiple tooltips at once

import decok.dfcdvadstf.createworldui.api.gamerule.GameRuleTooltipRegistry;
import java.util.HashMap;
import java.util.Map;

// Create a tooltip map
Map<String, String> tooltips = new HashMap<>();
tooltips.put("customRule1", "Description for custom rule 1");
tooltips.put("customRule2", "Description for custom rule 2");
tooltips.put("customRule3", "Description for custom rule 3");

// Register all at once
GameRuleTooltipRegistry.registerTooltips(tooltips);

Example 3: Register display names

import decok.dfcdvadstf.createworldui.api.gamerule.GameRuleNameRegistry;

// Register a single display name
GameRuleNameRegistry.registerName("myCustomRule", "My Custom Rule");

// Or register multiple at once
Map<String, String> names = new HashMap<>();
names.put("customRule1", "Custom Rule One");
names.put("customRule2", "Custom Rule Two");
GameRuleNameRegistry.registerNames(names);

Example 4: Create categories and organize rules

import decok.dfcdvadstf.createworldui.api.gamerule.GameRuleCategoryRegistry;
import java.util.Arrays;

// Create a new category with rules
GameRuleCategoryRegistry.createCategory(
    "gamerule.category.myMod",  // Category key
    Arrays.asList("myCustomRule1", "myCustomRule2", "myCustomRule3")
);

// Add more rules later
GameRuleCategoryRegistry.addRuleToCategory("gamerule.category.myMod", "anotherRule");

Example 5: Complete mod integration

Here's how you'd do it all together — names, tooltips, and categories:

package com.example.mymod;

import cpw.mods.fml.common.Mod;
import cpw.mods.fml.common.event.FMLInitializationEvent;
import decok.dfcdvadstf.createworldui.api.gamerule.GameRuleTooltipRegistry;
import decok.dfcdvadstf.createworldui.api.gamerule.GameRuleNameRegistry;
import decok.dfcdvadstf.createworldui.api.gamerule.GameRuleCategoryRegistry;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

@Mod(modid = "mymod", name = "My Mod", version = "1.0")
public class MyMod {
    
    @Mod.EventHandler
    public void init(FMLInitializationEvent event) {
        // 1. Register display names
        Map<String, String> names = new HashMap<>();
        names.put("enableCustomFeature", "Enable Custom Feature");
        names.put("customDamageMultiplier", "Custom Damage Multiplier");
        names.put("allowCustomSpawning", "Allow Custom Spawning");
        GameRuleNameRegistry.registerNames(names);
        
        // 2. Register tooltip descriptions
        Map<String, String> tooltips = new HashMap<>();
        tooltips.put("enableCustomFeature", "Enable or disable the custom feature");
        tooltips.put("customDamageMultiplier", "Set the damage multiplier for custom entities (0.0 - 10.0)");
        tooltips.put("allowCustomSpawning", "Allow custom entities to spawn naturally in the world");
        GameRuleTooltipRegistry.registerTooltips(tooltips);
        
        // 3. Create category and organize rules
        GameRuleCategoryRegistry.createCategory(
            "gamerule.category.mymod",
            Arrays.asList("enableCustomFeature", "customDamageMultiplier", "allowCustomSpawning")
        );
    }
}

Example 6: Query and manage registrations

import decok.dfcdvadstf.createworldui.api.gamerule.GameRuleTooltipRegistry;
import decok.dfcdvadstf.createworldui.api.gamerule.GameRuleNameRegistry;
import decok.dfcdvadstf.createworldui.api.gamerule.GameRuleCategoryRegistry;

// Check if tooltip is already registered
if (!GameRuleTooltipRegistry.hasRegisteredTooltip("myRule")) {
    GameRuleTooltipRegistry.registerTooltip("myRule", "My description");
}

// Check if display name is already registered
if (!GameRuleNameRegistry.hasRegisteredName("myRule")) {
    GameRuleNameRegistry.registerName("myRule", "My Rule");
}

// Get tooltip (automatically handles priority)
String tooltip = GameRuleTooltipRegistry.getTooltip("myRule");

// Get display name (automatically handles priority)
String displayName = GameRuleNameRegistry.getName("myRule");

// Get all rules in a category
List<String> rules = GameRuleCategoryRegistry.getRulesInCategory("gamerule.category.mymod");

// Get category for a rule
String category = GameRuleCategoryRegistry.getCategoryForRule("myCustomRule");

// Remove registrations
GameRuleTooltipRegistry.removeTooltip("myRule");
GameRuleNameRegistry.removeName("myRule");

// Clear all registrations
GameRuleTooltipRegistry.clearAllTooltips();
GameRuleNameRegistry.clearAllNames();

Priority System

When you call getTooltip() or getName(), the system checks in this order:

Tooltip Priority:

  1. Localization file - gamerule.{ruleName}.tooltip.description
  2. Tooltips registered via API
  3. Built-in default descriptions (vanilla rules only)

Name Priority:

  1. Localization file - gamerule.{ruleName}.name
  2. Display names registered via API
  3. Raw rule name (e.g., doFireTick)

This means resource packs can override your API-registered content, perfect for multi-language support!

For example:

  • You register the English name "My Custom Rule" via API
  • A Chinese resource pack can override it with gamerule.myCustomRule.name=我的自定义规则
  • Pretty neat, right? Chinese players see the localized name!

Default Category Structure

Vanilla game rules are pre-categorized as follows:

UI Display:

Each category is displayed as a section with a centered title:

|-------------------|
|      Mobs         |    ← Category title (centered, localizable)
| doMobSpawning  [] |    ← Game rule controls
| doMobLoot      [] |
| mobGriefing    [] |
|-------------------|

Note: Categories are not collapsible — they're always expanded and visible.

World
  ├─ doFireTick
  ├─ doTileDrops
  └─ doDaylightCycle

Mobs
  ├─ doMobSpawning
  ├─ doMobLoot
  └─ mobGriefing

Player
  ├─ naturalRegeneration
  └─ keepInventory

Chat
  └─ commandBlockOutput

Why This Matters

  • Better organization - Categories make the UI clearer when you have many rules
  • User experience - Players can quickly find related rules
  • Extensibility - Each mod can have its own category
  • Backward compatible - Old code still works!

So there you go — your game rules are all neatly organized in the editor!


Mixin Classes

Here's what gets mixed into what:

Mixin Class Target What it does
MixinModernCreateWorld GuiCreateWorld Replaces the world creation screen with the tabbed UI.
MixinGuiSelectWorld GuiSelectWorld Enhances the world selection screen.
MixinIntegratedServer IntegratedServer Applies difficulty and game rules when the world loads.
access.IGuiCreateWorldAccess GuiCreateWorld Accessor mixin — exposes private fields via getters/setters for external use.

Dependency Setup

To use CreateWorldUI as a dependency, add this to your build.gradle:

dependencies {
    implementation 'com.github.song682:CreateWorldUI:Tag'
}

Replace Tag with the version you want from the repository.
Also, the -deobf suffix jar is available.

Clone this wiki locally