Skip to main content

LunarCore Fabric

LunarCore Fabric is a foundational SDK for Minecraft Fabric mod development. It provides essential utilities and abstractions that streamline common mod development tasks without introducing gameplay mechanics or forcing specific architectural decisions.

Philosophy

Minimal and Purpose-Driven

LunarCore Fabric deliberately avoids feature bloat. Every utility included serves a clear purpose and addresses a common pain point in Fabric mod development. The SDK does not include:

  • Gameplay mechanics or content
  • User interface systems
  • Forced mixins or bytecode manipulation
  • Opinionated frameworks that dictate architecture

Fabric-Native Design

Rather than creating abstractions that hide Fabric's APIs, LunarCore builds on top of them. This approach ensures:

  • Compatibility with other Fabric mods
  • No learning curve for developers familiar with Fabric
  • Transparent behavior that doesn't obscure underlying mechanics
  • Easy debugging and troubleshooting

Thread Safety

All utilities in LunarCore are designed with thread safety in mind. This is particularly important for:

  • Task scheduling across game ticks
  • Asynchronous operations like update checking
  • Event handling that may occur from different contexts

Core Components

Lifecycle Management

The LifecycleManager provides a clean way to handle mod initialization and shutdown without implementing multiple interfaces or scattering logic across your codebase.

Registration

Register callbacks for different lifecycle phases:

LifecycleManager.onInitialize(() -> {
// Called during mod initialization
LunarLogger.info(MOD_ID, "Mod initialized");
});

LifecycleManager.onShutdown(() -> {
// Called when the server stops or client closes
saveConfig();
cleanup();
});

Use Cases

Lifecycle callbacks are useful for:

  • Initializing configuration files
  • Setting up database connections
  • Registering dynamic content
  • Performing cleanup operations
  • Saving persistent data

Implementation Details

The lifecycle system hooks into Fabric's existing lifecycle events but provides a simpler interface. Callbacks are executed in registration order, and exceptions in one callback won't prevent others from executing.

Configuration System

The Config class provides JSON-based configuration management with automatic defaults and type-safe getters.

Basic Usage

Config config = new Config(MOD_ID);
config.load();

// Retrieve values with defaults
boolean featureEnabled = config.getBoolean("features.autoSave", true);
int tickDelay = config.getInt("timing.tickDelay", 20);
String serverName = config.getString("server.name", "Default Server");

File Location

Configuration files are stored in .minecraft/config/<modid>.json. The system automatically:

  • Creates the config directory if it doesn't exist
  • Generates a default config file on first run
  • Preserves comments in JSON (where supported)

Nested Configuration

Support for nested configuration structures:

// Access nested values with dot notation
String dbHost = config.getString("database.host", "localhost");
int dbPort = config.getInt("database.port", 5432);

Configuration Validation

The config system includes basic validation:

  • Type checking for numeric values
  • Range validation for numbers
  • Enum validation for string constants

Reloading Configuration

Configuration can be reloaded at runtime:

config.reload();

This is useful for:

  • Applying changes without restarting
  • Responding to file changes
  • Testing different configurations

Logging System

LunarLogger wraps SLF4J to provide a simplified logging interface with per-mod context.

Log Levels

LunarLogger.info(MOD_ID, "Server started successfully");
LunarLogger.warn(MOD_ID, "Deprecated configuration option used");
LunarLogger.error(MOD_ID, "Failed to load resource: {}", resourceName);
LunarLogger.debug(MOD_ID, "Player position: x={}, y={}, z={}", x, y, z);

Structured Logging

Support for parameterized log messages to avoid string concatenation:

// Good - parameters are only evaluated if log level is enabled
LunarLogger.debug(MOD_ID, "Processing {} items", expensiveCalculation());

// Bad - string concatenation always occurs
LunarLogger.debug(MOD_ID, "Processing " + expensiveCalculation() + " items");

Log Categories

Logs are automatically categorized by mod ID, making it easy to filter output:

[INFO] [yourmod] Feature initialized
[WARN] [yourmod] Config value out of range
[ERROR] [yourmod] Critical operation failed

Exception Logging

Dedicated methods for exception logging:

try {
riskyOperation();
} catch (Exception e) {
LunarLogger.error(MOD_ID, "Operation failed", e);
}

Event Helper

EventHelper simplifies event registration with automatic error handling and a fluent interface.

Basic Registration

EventHelper.forMod(MOD_ID)
.on(ServerLifecycleEvents.SERVER_STARTED, server -> {
LunarLogger.info(MOD_ID, "Server started");
})
.on(ServerLifecycleEvents.SERVER_STOPPING, server -> {
LunarLogger.info(MOD_ID, "Server stopping");
})
.registerAll();

Error Handling

All event handlers are automatically wrapped in try-catch blocks. Exceptions are logged but don't crash the game:

EventHelper.forMod(MOD_ID)
.on(ServerTickEvents.START_SERVER_TICK, server -> {
// Even if this throws an exception, other mods' handlers will still run
potentiallyFailingOperation();
})
.registerAll();

Conditional Registration

Register events conditionally:

if (config.getBoolean("features.enableAutoSave", false)) {
EventHelper.forMod(MOD_ID)
.on(ServerTickEvents.END_SERVER_TICK, this::performAutoSave)
.registerAll();
}

Task Scheduler

TaskScheduler provides thread-safe task scheduling that respects Minecraft's tick cycle.

Delayed Tasks

Run a task after a specified delay:

// Run after 100 ticks (5 seconds)
TaskScheduler.runLater(MOD_ID, 100, () -> {
LunarLogger.info(MOD_ID, "Delayed task executed");
});

Repeating Tasks

Execute a task repeatedly at fixed intervals:

// Run every 20 ticks (1 second)
TaskScheduler.runRepeating(MOD_ID, 20, () -> {
performPeriodicCheck();
});

Cancellation

Tasks return a cancellation token:

var task = TaskScheduler.runRepeating(MOD_ID, 100, () -> {
updateStatus();
});

// Cancel later
task.cancel();

Thread Safety

All scheduled tasks execute on the main server thread, ensuring:

  • Safe access to game state
  • No concurrent modification exceptions
  • Predictable execution order

Update Checker

The UpdateChecker performs asynchronous version checking against GitHub releases or custom endpoints.

GitHub Integration

UpdateChecker.checkGitHubRelease(
MOD_ID,
"your-username",
"your-repo",
"1.0.0", // Current version
result -> {
if (result.isUpdateAvailable()) {
LunarLogger.info(MOD_ID, "Update available: {}",
result.getLatestVersion());
notifyPlayers(result.getDownloadUrl());
}
}
);

Custom Endpoints

For self-hosted update servers:

UpdateChecker.checkCustomEndpoint(
MOD_ID,
"https://your-server.com/api/version",
"1.0.0",
result -> {
handleUpdateCheck(result);
}
);

Update Information

The result object provides:

  • Latest version number
  • Download URL
  • Release notes
  • Whether an update is available
  • Comparison result (newer, same, older)

Rate Limiting

Update checks are automatically rate-limited to prevent excessive API calls:

  • Maximum one check per hour per endpoint
  • Cached results for repeated queries
  • Configurable rate limit through API

Integration Guide

Adding LunarCore to Your Project

Gradle Configuration

Add the repository and dependency to your build.gradle:

repositories {
maven {
name = "GitHubPackages"
url = uri("https://maven.pkg.github.com/LunarBit-dev/LunarCore-fabric")
credentials {
username = project.findProperty("gpr.user") ?: System.getenv("GITHUB_USER")
password = project.findProperty("gpr.token") ?: System.getenv("GITHUB_TOKEN")
}
}
}

dependencies {
modImplementation "dev.lunarbit.lunarcore:lunarcore:1.0.0"
include "dev.lunarbit.lunarcore:lunarcore:1.0.0"
}

GitHub Authentication

Create a GitHub personal access token with read:packages permission, then add to ~/.gradle/gradle.properties:

gpr.user=your-github-username
gpr.token=your-github-token

Basic Mod Structure

public class YourMod implements ModInitializer {
public static final String MOD_ID = "yourmod";
private Config config;

@Override
public void onInitialize() {
LunarLogger.info(MOD_ID, "Initializing " + MOD_ID);

// Setup
LifecycleManager.onInitialize(this::setup);
LifecycleManager.onShutdown(this::cleanup);

// Register events
registerEvents();

// Check for updates
checkForUpdates();
}

private void setup() {
config = new Config(MOD_ID);
config.load();

// Schedule periodic tasks
if (config.getBoolean("features.autoSave", true)) {
int interval = config.getInt("autoSave.interval", 6000);
TaskScheduler.runRepeating(MOD_ID, interval, this::autoSave);
}
}

private void registerEvents() {
EventHelper.forMod(MOD_ID)
.on(ServerLifecycleEvents.SERVER_STARTED, this::onServerStart)
.on(ServerLifecycleEvents.SERVER_STOPPING, this::onServerStop)
.registerAll();
}

private void cleanup() {
config.save();
LunarLogger.info(MOD_ID, "Cleanup complete");
}
}

fabric.mod.json Configuration

Declare LunarCore as a dependency:

{
"schemaVersion": 1,
"id": "yourmod",
"version": "1.0.0",
"name": "Your Mod",
"entrypoints": {
"main": [
"com.example.yourmod.YourMod"
]
},
"depends": {
"fabricloader": ">=0.18.3",
"minecraft": "~1.21.11",
"fabric-api": "*",
"lunarcore": ">=1.0.0"
}
}

Best Practices

Configuration Management

  • Provide sensible defaults for all configuration values
  • Document configuration options in comments or separate files
  • Validate configuration values during loading
  • Save configuration only when values change

Logging

  • Use appropriate log levels (DEBUG for detailed traces, INFO for general events, WARN for recoverable issues, ERROR for critical failures)
  • Include contextual information in log messages
  • Use parameterized logging to avoid unnecessary string operations
  • Log exceptions with stack traces for debugging

Task Scheduling

  • Keep scheduled tasks lightweight to avoid blocking the game thread
  • Cancel repeating tasks when they're no longer needed
  • Handle exceptions within task callbacks
  • Use appropriate delays to balance responsiveness and performance

Event Handling

  • Register events during initialization, not dynamically at runtime
  • Keep event handlers focused and single-purpose
  • Avoid heavy computation in event handlers
  • Use conditional registration for optional features

Compatibility

Minecraft Versions

LunarCore Fabric targets Minecraft 1.21.11 but is designed to be version-agnostic where possible. The API surface uses stable Fabric APIs that rarely change between Minecraft versions.

Mod Compatibility

LunarCore does not:

  • Modify vanilla behavior
  • Register items, blocks, or entities
  • Inject mixins
  • Override game systems

This design ensures compatibility with virtually all other mods.

Performance Impact

The performance overhead of LunarCore is minimal:

  • Event helpers add negligible overhead to event dispatch
  • Task scheduler uses Minecraft's existing tick cycle
  • Configuration loading is lazy and cached
  • Update checking is asynchronous and rate-limited

Troubleshooting

Common Issues

LunarCore Not Found

Ensure your Gradle credentials are correctly configured and you've run ./gradlew --refresh-dependencies.

Events Not Firing

Verify that you've called .registerAll() on your EventHelper chain.

Tasks Not Executing

Check that the server is running and ticks are progressing. Tasks only execute during normal game ticks.

Configuration Not Saving

Ensure the config directory is writable and there's sufficient disk space.

Debug Logging

Enable debug logging to troubleshoot issues:

LunarLogger.setLevel(MOD_ID, LogLevel.DEBUG);

Advanced Topics

Custom Configuration Loaders

Extend the Config system to support alternative formats:

public class YamlConfig extends Config {
@Override
protected void loadFromFile(Path path) {
// Custom YAML loading logic
}
}

Distributed Task Execution

For server networks, tasks can be distributed across multiple servers by extending the TaskScheduler.

Custom Update Endpoints

Implement custom version checking logic:

public class CustomUpdateChecker {
public static void check(String modId, String currentVersion,
Consumer<UpdateResult> callback) {
// Custom update checking implementation
}
}

API Documentation

Complete API documentation is available in the project's JavaDoc. Key packages:

  • dev.lunarbit.lunarcore.api.lifecycle - Lifecycle management
  • dev.lunarbit.lunarcore.api.config - Configuration system
  • dev.lunarbit.lunarcore.api.log - Logging utilities
  • dev.lunarbit.lunarcore.api.event - Event helpers
  • dev.lunarbit.lunarcore.api.task - Task scheduling
  • dev.lunarbit.lunarcore.api.update - Update checking

License

LunarCore Fabric is released under the MIT License, permitting free use, modification, and distribution with proper attribution.

Community and Support

For questions, bug reports, or feature requests, please use the GitHub issue tracker. Contributions are welcome and should follow the project's coding standards and design principles.