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.