Skip to main content

Overview

Lithostitched extends Minecraft’s registry system with custom registries for worldgen modifiers, surface rules, placement conditions, and more. This system uses Mojang’s Codec API for serialization and type-safe registration.
Custom registries allow Lithostitched to integrate seamlessly with Minecraft’s data pack system while maintaining type safety and extensibility.

Registry Keys

All custom registries are defined in LithostitchedRegistryKeys:
/home/daytona/workspace/source/src/common/main/java/dev/worldgen/lithostitched/registry/LithostitchedRegistryKeys.java:20-36
public interface LithostitchedRegistryKeys {
    // Dynamic
    ResourceKey<Registry<Modifier>> WORLDGEN_MODIFIER = create("worldgen_modifier");
    ResourceKey<Registry<SurfaceRules.RuleSource>> SURFACE_RULE = create("surface_rule");
    ResourceKey<Registry<Bandlands>> BANDLANDS = create("bandlands");
    ResourceKey<Registry<TemplateList>> TEMPLATE_LIST = create("template_list");

    // Static
    ResourceKey<Registry<MapCodec<? extends Modifier>>> MODIFIER_TYPE = create("modifier_type");
    ResourceKey<Registry<MapCodec<? extends PlacementCondition>>> PLACEMENT_CONDITION_TYPE = create("placement_condition_type");
    ResourceKey<Registry<MapCodec<? extends ProcessorCondition>>> PROCESSOR_CONDITION_TYPE = create("processor_condition_type");
    ResourceKey<Registry<MapCodec<? extends Band>>> BANDLANDS_BAND_TYPE = create("bandlands_band_type");

    private static <T> ResourceKey<Registry<T>> create(String name) {
        return ResourceKey.createRegistryKey(Lithostitched.id(name));
    }
}

Registry Types

Dynamic registries store data-driven content loaded from JSON files in data packs:
  • WORLDGEN_MODIFIER - Individual modifier instances from JSON files
  • SURFACE_RULE - Named surface rule configurations
  • BANDLANDS - Bandlands biome definitions
  • TEMPLATE_LIST - Lists of structure templates
Dynamic registry content can be changed by data packs without modifying code.

Registration Process

Types are registered during mod initialization through registration methods in the main Lithostitched class:
/home/daytona/workspace/source/src/common/main/java/dev/worldgen/lithostitched/Lithostitched.java:103-118
public static void registerCommonModifiers(BiConsumer<String, MapCodec<? extends Modifier>> consumer) {
    consumer.accept("internal/compile_raw_templates", CompileRawTemplatesModifier.CODEC);
    consumer.accept("add_processor_list_processors", AddProcessorListProcessorsModifier.CODEC);
    consumer.accept("add_structure_set_entries", AddStructureSetEntriesModifier.CODEC);
    consumer.accept("add_structure_templates", AddStructureTemplatesModifier.CODEC);
    consumer.accept("add_surface_rule", AddSurfaceRuleModifier.CODEC);
    consumer.accept("add_template_pool_elements", AddTemplatePoolElementsModifier.CODEC);
    consumer.accept("no_op", NoOpModifier.CODEC);
    consumer.accept("remove_structure_set_entries", RemoveStructureSetEntriesModifier.CODEC);
    consumer.accept("set_pool_aliases", SetPoolAliasesModifier.CODEC);
    consumer.accept("set_pool_element_processors", SetPoolElementProcessorsModifier.CODEC);
    consumer.accept("set_structure_spawn_condition", SetStructureSpawnConditionModifier.CODEC);
    consumer.accept("stack_feature", StackFeatureModifier.CODEC);
    consumer.accept("wrap_density_function", WrapDensityFunctionModifier.CODEC);
    consumer.accept("wrap_noise_router", WrapNoiseRouterModifier.CODEC);
}

Registration Categories

Lithostitched provides registration methods for multiple categories:

Modifiers

registerCommonModifiers()

Block Predicates

registerCommonBlockPredicateTypes()

State Providers

registerCommonStateProviders()

Placement Modifiers

registerCommonPlacementModifiers()

Feature Types

registerCommonFeatureTypes()

Pool Elements

registerCommonPoolElementTypes()

Density Functions

registerCommonDensityFunctions()

Structure Processors

registerCommonStructureProcessors()
Additional Registration Methods
// Block predicates
registerCommonBlockPredicateTypes(BiConsumer<String, BlockPredicateType<?>> consumer)

// State providers
registerCommonStateProviders(BiConsumer<String, BlockStateProviderType<?>> consumer)

// Placement modifiers
registerCommonPlacementModifiers(BiConsumer<String, PlacementModifierType<?>> consumer)

// Feature types
registerCommonFeatureTypes(BiConsumer<String, Feature<?>> consumer)

// Pool elements
registerCommonPoolElementTypes(BiConsumer<String, StructurePoolElementType<?>> consumer)

// Density functions
registerCommonDensityFunctions(BiConsumer<String, MapCodec<? extends DensityFunction>> consumer)

// Pool alias bindings
registerCommonPoolAliasBindings(BiConsumer<String, MapCodec<? extends PoolAliasBinding>> consumer)

// Structure types
registerCommonStructureTypes(BiConsumer<String, StructureType<?>> consumer)

// Placement conditions
registerCommonPlacementConditions(BiConsumer<String, MapCodec<? extends PlacementCondition>> consumer)

// Structure processors
registerCommonStructureProcessors(BiConsumer<String, StructureProcessorType<?>> consumer)

// Processor conditions
registerCommonProcessorConditions(BiConsumer<String, MapCodec<? extends ProcessorCondition>> consumer)

// Block entity modifiers
registerCommonBlockEntityModifiers(BiConsumer<String, RuleBlockEntityModifierType<?>> consumer)

// Surface rule sources
registerCommonRuleSources(BiConsumer<String, MapCodec<? extends SurfaceRules.RuleSource>> consumer)

// Surface conditions
registerCommonSurfaceConditions(BiConsumer<String, MapCodec<? extends SurfaceRules.ConditionSource>> consumer)

// Bandlands band types
registerCommonBandlandsBandTypes(BiConsumer<String, MapCodec<? extends Band>> consumer)

The Codec System

Lithostitched uses Mojang’s Codec API for type-safe serialization and deserialization. Each registered type must provide a MapCodec that defines its JSON structure.

Anatomy of a Codec

Here’s how the AddSurfaceRuleModifier defines its codec:
/home/daytona/workspace/source/src/common/main/java/dev/worldgen/lithostitched/worldgen/modifier/AddSurfaceRuleModifier.java:18-23
public record AddSurfaceRuleModifier(int priority, List<ResourceKey<LevelStem>> levels, SurfaceRules.RuleSource surfaceRule) implements Modifier {
    public static final MapCodec<AddSurfaceRuleModifier> CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group(
        PRIORITY_DEFAULT.forGetter(AddSurfaceRuleModifier::priority),
        ResourceKey.codec(Registries.LEVEL_STEM).listOf().fieldOf("levels").forGetter(AddSurfaceRuleModifier::levels),
        SurfaceRules.RuleSource.CODEC.fieldOf("surface_rule").forGetter(AddSurfaceRuleModifier::surfaceRule)
    ).apply(instance, AddSurfaceRuleModifier::new));

Codec Components

1

RecordCodecBuilder

Creates a codec for record classes with named fields
2

Field Definitions

Each parameter maps to a JSON field:
  • PRIORITY_DEFAULT.forGetter() - Optional priority field with default value
  • .fieldOf("field_name") - Required field in JSON
  • .optionalFieldOf("field_name", default) - Optional field with default
3

Getters

.forGetter() specifies how to extract the value when encoding to JSON
4

Constructor Application

.apply(instance, Constructor::new) creates instances when decoding from JSON

Example Codec Usage

This codec definition:
RecordCodecBuilder.mapCodec(instance -> instance.group(
    Codec.INT.optionalFieldOf("priority", 1000).forGetter(MyModifier::priority),
    ResourceKey.codec(Registries.STRUCTURE).fieldOf("structure").forGetter(MyModifier::structure)
).apply(instance, MyModifier::new))
Maps to this JSON:
{
  "type": "mymod:my_modifier",
  "priority": 1500,
  "structure": "minecraft:village_plains"
}

Type Dispatch Codec

The Modifier interface uses a dispatch codec to determine which concrete type to deserialize based on the type field:
/home/daytona/workspace/source/src/common/main/java/dev/worldgen/lithostitched/worldgen/modifier/Modifier.java:23-28
Codec<Modifier> CODEC = Codec.lazyInitialized(() -> {
    var modifierRegistry = BuiltInRegistries.REGISTRY.getOptional(LithostitchedRegistryKeys.MODIFIER_TYPE.identifier());
    if (modifierRegistry.isEmpty()) throw new NullPointerException("Worldgen modifier registry does not exist yet!");
    return ((Registry<MapCodec<? extends Modifier>>) modifierRegistry.get()).byNameCodec();
}).dispatch(Modifier::codec, Function.identity());
This dispatch codec looks up the appropriate concrete codec based on the type field in JSON, allowing polymorphic deserialization.

How Type Dispatch Works

Creating Resource Keys

Lithostitched provides utility methods for creating resource keys:
/home/daytona/workspace/source/src/common/main/java/dev/worldgen/lithostitched/Lithostitched.java:77-83
public static <T> ResourceKey<T> key(ResourceKey<? extends Registry<T>> resourceKey, String name) {
    return ResourceKey.create(resourceKey, id(name));
}

public static Identifier id(String name) {
    return Identifier.fromNamespaceAndPath(MOD_ID, name);
}

Usage Example

// Create a resource key for a worldgen modifier
ResourceKey<Modifier> modifierKey = Lithostitched.key(
    LithostitchedRegistryKeys.WORLDGEN_MODIFIER,
    "my_custom_modifier"
);
// Results in: lithostitched:my_custom_modifier

// Create an identifier
Identifier id = Lithostitched.id("add_surface_rule");
// Results in: lithostitched:add_surface_rule

Registry Access at Runtime

Modifiers can access registries during application through the RegistryAccess parameter:
@Override
public void applyModifier(RegistryAccess registryAccess) {
    // Access the structure registry
    Registry<Structure> structures = registryAccess.registryOrThrow(Registries.STRUCTURE);
    
    // Access a Lithostitched registry
    Registry<SurfaceRules.RuleSource> surfaceRules = 
        Lithostitched.registry(registryAccess, LithostitchedRegistryKeys.SURFACE_RULE);
    
    // Use registries for your modifications
    // ...
}

Best Practices

Use RecordCodecBuilder

Record classes with RecordCodecBuilder provide type-safe, concise codec definitions

Provide Defaults

Use optionalFieldOf with sensible defaults for optional fields

Lazy Initialization

Use Codec.lazyInitialized() to avoid circular dependency issues during registry creation

Consistent Naming

Follow Minecraft’s naming conventions: lowercase with underscores
Avoid accessing registries in static initializers. Registries may not be fully initialized until runtime.

Modifiers

Learn about the Modifier interface

Data-Driven Config

Write JSON configurations

Build docs developers (and LLMs) love