Building DedinMod: A Comprehensive Minecraft Fabric Mod
After diving into Fabric modding, I wanted to create something substantial—not just adding a few blocks or items, but building a comprehensive mod that demonstrates the full capabilities of the Fabric framework. The result is DedinMod, a feature-rich Minecraft mod for version 1.21.11 that adds over 30 custom blocks, numerous items, custom entities, status effects, and complex game systems.
Project Scope and Architecture
DedinMod is built on a solid technical foundation:
- Java 21 - Modern language features and performance
- Fabric Loader 0.18.2 - Lightweight mod loading
- Fabric API 0.139.5 - Core modding capabilities
- Gradle 8.x - Build automation
- Yarn Mappings - Human-readable deobfuscation
Architectural Patterns
Centralized Initialization
Rather than scattering registration code throughout the mod, I use a centralized initialization pattern with dedicated "Init" classes:
// Main entry point
public class Dedinmod implements ModInitializer {
@Override
public void onInitialize() {
BlockInit.load(); // Register all blocks
ItemInit.load(); // Register all items
EntityInit.load(); // Register all entities
EffectInit.load(); // Register status effects
// ... more systems
}
}
Registry Pattern with Utilities
All content registration uses a custom RegisterUtil helper that wraps vanilla registry operations for cleaner, more maintainable code:
public class BlockInit {
public static final Block TELEPORTER_BLOCK =
RegisterUtil.registerBlock("teleporter_block",
new TeleporterBlock(AbstractBlock.Settings.create()
.strength(4.0f)
.requiresTool()));
public static final Block PHASE_BLOCK =
RegisterUtil.registerBlock("phase_block",
new PhaseBlock(AbstractBlock.Settings.create()
.nonOpaque()
.noCollision()));
}
Split Source Sets
The project uses separate source directories for client and server code:
- src/main/java - Common/server-side code (blocks, items, entities)
- src/client/java - Client-only code (rendering, screens, models)
- src/main/generated - Auto-generated data (recipes, tags, loot tables)
This separation prevents accidentally referencing client-side classes on the server, which would cause crashes.
Key Features Implementation
1. Teleportation Network System
One of the most complex features is the teleportation network. It required:
- Block Entity: Stores teleporter name and destination
- Screen Handler: Custom GUI for setting destinations
- Network Payload: Client-server communication for teleportation
- Persistent Data: Server-side storage of teleporter networks
public class TeleporterBlockEntity extends BlockEntity {
private String teleporterName = "";
private String destinationName = "";
public void teleportPlayer(ServerPlayerEntity player) {
if (destinationName.isEmpty()) return;
BlockPos destination = findDestination(destinationName);
if (destination != null) {
player.teleport(
destination.getX() + 0.5,
destination.getY() + 1,
destination.getZ() + 0.5
);
// Play sound and particle effects
}
}
}
2. Custom Status Effects
The mod includes 9+ unique status effects that modify player behavior:
- Anti-Gravity: Players float upward slowly
- Phase Drifting: Pass through certain blocks
- Minimized: Player hitbox becomes smaller
- Enhanced Speed/Strength: Amplified attributes
- Vulnerability: Increased damage taken
public class AntiGravityEffect extends StatusEffect {
@Override
public boolean applyUpdateEffect(LivingEntity entity, int amplifier) {
if (!entity.getWorld().isClient) {
// Apply upward velocity
Vec3d velocity = entity.getVelocity();
entity.setVelocity(velocity.x, 0.15, velocity.z);
entity.velocityModified = true;
}
return true;
}
}
3. Custom Entities and NPCs
The mod adds several custom entities including the "Kasper" NPC character:
public class KasperEntity extends PassiveEntity {
@Override
protected void initGoals() {
this.goalSelector.add(0, new SwimGoal(this));
this.goalSelector.add(1, new WanderAroundFarGoal(this, 1.0));
this.goalSelector.add(2, new LookAtEntityGoal(this,
PlayerEntity.class, 8.0f));
}
@Override
public ActionResult interactMob(PlayerEntity player, Hand hand) {
if (!this.getWorld().isClient) {
// Custom interaction logic
player.sendMessage(Text.literal("Hello, traveler!"));
}
return ActionResult.SUCCESS;
}
}
4. Data Generation API
One of Fabric's most powerful features is the data generation API. Instead of manually writing JSON files for recipes, loot tables, and models, I generate them programmatically:
public class DedinmodRecipeProvider extends FabricRecipeProvider {
@Override
public void generate(RecipeExporter exporter) {
// Shaped recipe for teleporter block
ShapedRecipeJsonBuilder.create(RecipeCategory.MISC,
BlockInit.TELEPORTER_BLOCK)
.pattern("OEO")
.pattern("EPE")
.pattern("OEO")
.input('O', Items.OBSIDIAN)
.input('E', Items.ENDER_PEARL)
.input('P', Items.ENDER_EYE)
.criterion(hasItem(Items.ENDER_PEARL),
conditionsFromItem(Items.ENDER_PEARL))
.offerTo(exporter);
}
}
This approach has several benefits:
- Type safety - Compile-time checking of item/block references
- Consistency - Recipes follow the same pattern
- Maintainability - Easy to update when items change
- Bulk generation - Create hundreds of recipes with loops
Technical Challenges and Solutions
Challenge 1: Client-Server Synchronization
Minecraft's architecture separates client and server logic, even in single-player. Synchronizing custom data (like teleporter names) required implementing custom network payloads:
public record TeleportPayload(String destination)
implements CustomPayload {
public static final Id ID =
new Id<>(new Identifier("dedinmod", "teleport"));
@Override
public Id extends CustomPayload> getId() {
return ID;
}
}
Challenge 2: Mixin Integration
Sometimes you need to modify vanilla Minecraft behavior. Mixins allow bytecode injection without modifying Minecraft's source:
@Mixin(LivingEntity.class)
public abstract class LivingEntityMixin {
@Inject(method = "travel", at = @At("HEAD"))
private void modifyTravel(Vec3d movementInput,
CallbackInfo ci) {
LivingEntity self = (LivingEntity)(Object)this;
// Custom movement modification logic
if (self.hasStatusEffect(EffectInit.ANTI_GRAVITY)) {
// Override normal gravity behavior
}
}
}
Challenge 3: Resource Loading and Textures
Properly organizing and loading textures, models, and blockstates is crucial. The mod follows Minecraft's resource conventions:
assets/dedinmod/textures/block/- Block texturesassets/dedinmod/textures/item/- Item texturesassets/dedinmod/models/block/- Block models (JSON)assets/dedinmod/blockstates/- Blockstate definitions
Development Workflow
Gradle Tasks
The development cycle uses several Gradle tasks:
# Run client for testing
./gradlew runClient
# Generate data files (recipes, models, etc.)
./gradlew runDatagen
# Build final mod JAR
./gradlew build
# Clean build artifacts
./gradlew clean
Testing Methodology
- Write feature code
- Run data generation to create JSON files
- Launch test client with
runClient - Test in-game functionality
- Iterate on issues
Custom Blocks Showcase
The mod includes diverse custom blocks with unique mechanics:
- Teleporter Block: Network-based instant travel system
- Phase Block: Players can pass through when phasing
- Trampoline Block: Launches entities upward
- Sensor Block: Detects nearby entities and outputs redstone signal
- Magic Anvil: Custom crafting station with unique GUI
- Custom Ores: New ore types with custom generation
Performance Considerations
When adding content to Minecraft, performance is crucial:
- Tick Efficiently: Block entities only tick when necessary
- Optimize Rendering: Use efficient render layers
- Lazy Loading: Don't create objects until needed
- Cache References: Store frequently-used block/item instances
Lessons Learned
1. Plan Your Registry Names Early
Registry names are permanent once players start using your mod. Changing them breaks existing worlds. Use descriptive, future-proof names from the start.
2. Use Data Generation
Writing JSON files by hand is error-prone and tedious. The data generation API saves countless hours and prevents mistakes.
3. Test on Dedicated Servers
Single-player testing isn't enough. Always test on a dedicated server to catch client-server synchronization bugs.
4. Document Your Mixins
Mixins are powerful but can be fragile when Minecraft updates. Document why each mixin exists and what it modifies.
What's Next for DedinMod?
Future planned features include:
- Boss dimension with custom biomes
- Advanced automation blocks (pipes, conveyors)
- Custom enchantments and potion effects
- Multiplayer-focused mechanics and challenges
- Integration with other popular mods
Resources for Aspiring Modders
- Fabric Wiki - Official documentation
- Fabric Example Mod - Reference implementations
- Getting Started with Fabric Modding - My beginner's guide
- Fabric Discord - Community support
Conclusion
Building DedinMod has been an incredible learning experience. Fabric's modular architecture and powerful APIs make it possible to create complex, feature-rich mods without fighting the framework. Whether you're adding simple blocks or building entire game systems, Fabric provides the tools you need.
The source code organization, data generation workflow, and architectural patterns demonstrated in DedinMod can serve as a template for your own ambitious modding projects. Start small, iterate often, and don't be afraid to dive deep into Minecraft's source code to understand how it all works.
Happy modding!