About
LibJF is split into several modules, each of which provides separate functionality. This modularization is inspired by Fabric API. LibJF is only maintained for the latest version of minecraft and tries to provide a stable ABI during its lifespan but is open for breaking changes between versions.
Be aware that modules may depend on another and that those transitive dependencies must be JiJd separately. Modules may also require fabric-api to be present to work properly
Using LibJF
The recommended way to use LibJF is using the JfMods scripts, which include the LibJF maven repository and simplify building fabric mods. My own mods (including LibJF) use these.
Otherwise, you can add the repository as follows:
repositories {
maven { url 'https://maven.frohnmeyer-wds.de/artifacts' }
}
and include LibJF modules like this:
dependencies {
include modImplementation("io.gitlab.jfronny.libjf:libjf-config-core-v2:${project.libjf_version}")
include modRuntimeOnly("io.gitlab.jfronny.libjf:libjf-config-ui-tiny:${project.libjf_version}")
include("io.gitlab.jfronny.libjf:libjf-base:${project.libjf_version}")
modLocalRuntime("io.gitlab.jfronny.libjf:libjf-config-reflect-v1:${project.libjf_version}")
modLocalRuntime("io.gitlab.jfronny.libjf:libjf-devutil-v0:${project.libjf_version}")
}
For more information on specific modules, you can look at their own pages
Developing LibJF
To add a LibJF module, create a new directory for it and copy over a build.gradle file from elsewhere, Reference it in settings.gradle and develop it like any fabric mod. You should also create a testmod for it (look at the other modules for examples) JiJing and maven uploads are automatically managed by the JfMods scripts
libjf-base
libjf-base is a dependency of all other modules and provides common functionality between them. It has no dependencies. It includes:
- a Gson strategy to ignore fields annotated with GsonHidden, ClientOnly and ServerOnly
- LazySupplier, a supplier that caches the result of another supplier to which it delegates
- ThrowingRunnable and ThrowingSupplier, counterparts of their default lambdas which allow exceptions
- a "flags" system to allow dynamically enabling LibJF features through system properties and fabric.mod.json
- a shared logger and Gson instance
- the ResourcePath abstraction to describe locations of resources as simple strings
- the CoProcess system used internally to control threads separate from the game thread
- an HTTP abstraction from Inceptum
All of these are implemented in one reusable class (except the Gson strategy, whose annotation is separate), which should be simple to read
About
LibJF Config is a modular config library. Generally, using a combination of its modules is recommended. On the following pages, the modules of libjf-config are explained in more detail. If you just want to get started, a quick setup example for a client-side mod using the compiler plugin can be seen below:
build.gradle:
repositories {
maven { url 'https://maven.frohnmeyer-wds.de/artifacts' }
}
dependencies {
include modImplementation("io.gitlab.jfronny.libjf:libjf-config-core-v2:${project.libjf_version}")
include modRuntimeOnly("io.gitlab.jfronny.libjf:libjf-config-ui-tiny:${project.libjf_version}")
include("io.gitlab.jfronny.libjf:libjf-base:${project.libjf_version}")
annotationProcessor("io.gitlab.jfronny.libjf:libjf-config-compiler-plugin-v2:${project.libjf_version}")
}
loom {
mods {
register(name, {
sourceSet sourceSets.main
})
}
}
compileJava {
options.compilerArgs << "-AmodId=" + archivesName // optionally fill in your mod id explicitly
}
fabric.mod.json:
{
"entrypoints": {
"libjf:config": ["some.package.JFC_YourConfigClass"] // The JFC_ prefix is for the config-compiler-plugin-v2-generated classes
}
}
Config class:
@JfConfig
public class ConfigExample {
@Entry public static boolean someEntry = true;
static {
JFC_ConfigExample.ensureInitialized();
}
}
en_us.json:
{
"modid.jfconfig.title": "Your Mod",
"modid.jfconfig.someEntry": "Some Entry",
"modid.jfconfig.someEntry.tooltip": "Some comment"
}
I also recommend adding ModMenu in order to more easily test your config in dev.
libjf-config-core-v2
The core module contains the abstractions and annotations used by other modules to interact with configs. It also contains the code for registering configs to mods, serialization and automatic reloads on change.
DSL
The DSL is used to create or register instances of ConfigInstance.
You may obtain an instance via DSL.create()
ConfigHolder
You can use this class to obtain your registered config instance or those of other mods.
To do so, use ConfigHolder.getInstance().get("modId")
and ConfigHolder.getInstance().getRegistered()
Annotations
This module also provides the annotations used in other modules. Use them as follows:
@JfConfig // This class is a config. Also takes the optional parameter referencedConfigs to add a button to go to the config of another mod
class SomeConfig {
@Entry // This field is an entry. Also takes the optional parameters min and max for clamping number values
public static int someEntry = 15; // Any type can be used, but only int, long, float, double, boolean, their Boxes, Strings and enums are supported in ui-tiny
@Preset // This method is a preset. A UI entry for it will be created. When it is pressed, this method will be called
public static void somePreset() { // Must return void and take no arguments. The method name will be used as the preset name
someEntry = 12; // You may modify the entries of the config here
}
@Verifier // This method is a verifier. It will be called whenever a config is changed or loaded.
public static void someVerifier() {
if (someEntry % 2 != 0) someEntry++; // You can enforce additional constraints on values
}
@Category // This is a category. Just like JfConfig, you may specify referencedConfigs
public static class SomeCategory {
// A category can contain anything supported by the main config class, including fields, presets, verifiers and subcategories
}
}
Custom config registration
In order to register mods without the compiler plugin or runtime module,
you can use a libjf:config
entrypoint implementing JfCustomConfig.
As a parameter, you will be provided with a defaulted DSL with which you must interact to do so.
Comments
For enums, the possible values will automatically be written to the config as comments. You can add additional comments for entries or categories by registering tooltips in your language file as follows:
{
"<mod id>.jfconfig.<entry>.tooltip": "Some comment"
}
libjf-config-compiler-plugin-v2
This annotation processor provides finds classes annotated with @JfConfig
and produces the necessary code and registration for them.
Using this is the recommended way to use libjf-config.
Please be aware that the restrictions of the reflection implementation in regard to class loading
will also apply to configs using the plugin if it is loaded during runtime.
Please note that your fabric.mod.json
must reference the generated class, not your original config.
If you plan on accessing your config before onInitialize
, it is recommended that you call .ensureInitialized()
on your
generated class from your config classes static initializer as seen in the example.
The code necessary for using this annotation processor is available here
libjf-config-commands
This serverside module provides commands for modifying configs using this library.
If you are developing a serverside mod, you may wish to include it to enable easier configuration.
The commands are available under /libjf config
, using auto-complete is recommended.
libjf-config-ui-tiny
This module provides an automatically registered, TinyConfig-based UI for all mods using libjf-config.
Embedding this is recommended when developing client-side mods.
libjf-config-ui-tiny implements the config-core-provided ConfigScreenFactory
, so you can ConfigScreenFactory.getInstance().create(config, parent).get()
to obtain a screen for your config.
libjf-devutil
LibJF devutil is intended to be used as modLocalRuntime
.
It marks the running minecraft instance as a development instance (for example, this removes the need for eula.txt)
and disables the UserApi (removing that Yggdrasil error message)
It does not provide any useful functionality to end users
libjf-data-v0
libjf-data-v0 provides two additional tags for use in other mods or datapacks:
libjf:shulker_boxes_illegal
prevents items from being placed in shulker boxes (intended for backpacks)libjf:overpowered
makes entities wearing four or more armor items with this tag invincible
libjf-data-manipulation-v0
libjf-data-manipulation-v0 provides code for modifying existing resources
RecipeUtil
RecipeUtil provides to methods to end users:
removeRecipe
blocks minecraft from loading recipes using the specified tagremoved for 1.19.4removeRecipeFor
blocks minecraft from loading recipes producing the specified output. (Look at the log to find out which ones it blocks)
UserResourceEvents
UserResourceEvents provides four events (CONTAINS, FIND_RESOURCE, OPEN, OPEN_ROOT) which get called every time the corresponding method is called on and ResourcePack. They allow modifying the resulting data. This is used in respackopts to manipulate resource loading. To temporarily disable these hooks, call the "disable" function, which invokes the lambda it is passed and disables the hooks while it is running
Examples
@Override
public void onInitialize() {
// This should prevent resource packs from doing anything if my hooks are working and
UserResourceEvents.OPEN.register((type, id, previous, pack) -> {
if (pack instanceof DirectoryResourcePack) {
LibJf.LOGGER.info(pack.getName() + " opened " + type.name() + "/" + id.toString());
}
return previous.get();
});
UserResourceEvents.CONTAINS.register((type, id, previous, pack) -> {
if (pack instanceof DirectoryResourcePack) {
return false;
}
return previous.get();
});
RecipeUtil.removeRecipeFor(Items.DIAMOND_SWORD);
}
libjf-translate-v1
libjf-translate-v1 provides a utility class for translating strings through user-configurable services.
To use this, first obtain a TranslateService instance. You can use TranslateService.getConfigured()
to do so.
Please be aware that due to the nature of java generics, using var instead of a specific type for instances is recommended.
You can also directly access implementations, however, this is not recommended and is not subject to the API stability promise.
The TranslateService interface exposes all relevant functionality.
libjf-unsafe-v0
libjf-unsafe-v0 provides an entrypoint which is called extremely early and ASM modification. It also ensures dependency libraries are on the classpath during both. Due to the fact that it accesses several internal components of fabric loader, it is probably incompatible with versions of fabric loader it was not explicitly developed for.
UltraEarlyInit
Implement UltraEarlyInit
on your entrypoint and add it as a libjf:early
entrypoint
Please be aware that using any non-jdk classes during the ultra early init stage prevents them from being modified by Mixin/ASM.
ASM
Implement AsmConfig
on a class and add it to your fabric.mod.json as a libjf:asm
entrypoint.
Use skipClasses to prevent a class from being modified by any ASM patches.
getPatches should return a Set of patches that should be applied to classes (more on that later).
skipClasses and getPatches will only be called once during the initialization process.
Please be aware that using un-excluded classes during ASM modification can cause infinite loops.
Please also note that during runtime, class names may be in intermediary, yarn or other mappings.
You can use AsmTransformer.MAPPING_RESOLVER to resolve intermediary names to their runtime counterparts
Creating an ASM patch
The current ClassNode will be passed to the apply method of every registered Patch instance. You may modify it as you like, but some default things are provided to make this easier:
- InterfaceImplTargetPatch will apply a Patch only on classes implementing or extending the interface or class specified as the first parameter
- MethodModificationPatch allows applying MethodPatches only to specific methods on specific classes
- MethodReplacementPatch is a MethodPatch which replaces the content of its target method with a predefined InsnList
Of course, you can (and should) implement your own patches in addition to these, Examples can be found in libjf-data-manipulation-v0 and GWWHIT
Debugging ASM
Debugging ASM is difficult, but you can use the asm.log/asm.export flags to get additional output.
You can do so either by adding -Plibjf.asm.log -Plibjf.asm.export
to your JVM args or the following to your fabric.mod.json:
{
"custom": {
"libjf": {
"asm.log": true,
"asm.export": true
}
}
}
I also recommend using IDEAs bytecode viewer on resulting classes to understand the bytecode
libjf-web-v1
libjf-web-v1 provides an HTTP web server you can use in your serverside (and technically also clientside) mods to serve web content through a unified port. libjf-web-v1 depends on libjf-config-core-v2 to provide its config, libjf-base, fabric-lifecycle-events-v1 and fabric-command-api-v1
Getting started
Implement WebInit and register it as a libjf:web entrypoint. To enable the server, also add the following to your fabric.mod.json:
{
"custom": {
"libjf": {
"web": true
}
}
}
Please be aware that your entrypoint may be called multiple times during one session. You can register content providers using the register* methods on the WebServer parameter of your entrypoint. Please do not store it in a field, you may instead use WebServer.getInstance() if you wish to access it elsewhere.