From f964f77bc5942c27a649a7498d6aa4b1a322e054 Mon Sep 17 00:00:00 2001 From: Ellet Date: Fri, 31 Oct 2025 23:50:59 +0300 Subject: [PATCH 1/8] docs(entrypoint): add the add Controlify mod dependency section to copy-paste --- docs/developers/controlify-entrypoint.mdx | 41 +++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/docs/developers/controlify-entrypoint.mdx b/docs/developers/controlify-entrypoint.mdx index d51e1d5f4..513f4fac2 100644 --- a/docs/developers/controlify-entrypoint.mdx +++ b/docs/developers/controlify-entrypoint.mdx @@ -6,6 +6,47 @@ _Learn how to hook into Controlify._ Controlify provides a Fabric entrypoint to hook into important lifecycle stages of Controlify. You do this just like `ClientModInitializer`. +## Adding Controlify dependency + +In your `build.gradle` (or `build.gradle.kts`), you need to add the maven +repository and the Controlify mod dependency. + +
+Maven repository + +```kotlin +repositories { + exclusiveContent { + forRepository { maven { url "https://maven.isxander.dev/releases" } } + filter { includeGroup "dev.isxander" } + } +} +``` + +
+ +
+Dependency + +Add the version to your `gradle.properties`: + +```properties +# You can find the versions in here: https://maven.isxander.dev/#/releases/dev/isxander/controlify +controlify_version=2.4.3+1.21.1-neoforge +``` + +```kotlin +dependencies { + // You can change "compileOnly" to "implementation" for testing in-game. + compileOnly("dev.isxander:controlify:${project.controlify_version}") { + // Only need Controlify API, ignore the transitive dependencies (e.g, QuiltMC parsers) + transitive = false + } +} +``` + +
+ ## Registering the entrypoint - On Fabric, you register an entrypoint like any other, by adding an entry to your `fabric.mod.json` file under the `entrypoints` section. From 89ca0c4cdd79c05ce0841f9de757a7483e13937a Mon Sep 17 00:00:00 2001 From: Ellet Date: Fri, 31 Oct 2025 23:51:55 +0300 Subject: [PATCH 2/8] docs(entrypoint): allow copy-paste the SPI file for NeoForge users --- docs/developers/controlify-entrypoint.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/developers/controlify-entrypoint.mdx b/docs/developers/controlify-entrypoint.mdx index 513f4fac2..d973152a4 100644 --- a/docs/developers/controlify-entrypoint.mdx +++ b/docs/developers/controlify-entrypoint.mdx @@ -50,7 +50,7 @@ dependencies { ## Registering the entrypoint - On Fabric, you register an entrypoint like any other, by adding an entry to your `fabric.mod.json` file under the `entrypoints` section. -- On NeoForge, you register an entrypoint using a Java service provider interface (SPI) in your `META-INF/services` directory. +- On NeoForge, you register an entrypoint using a Java service provider interface (SPI) in **`META-INF/services/dev.isxander.controlify.api.entrypoint.ControlifyEntrypoint`**. ```json !!tabs (Fabric) fabric.mod.json From 8cc5f99baf283dcba95122e01d02590078bde374 Mon Sep 17 00:00:00 2001 From: Ellet Date: Fri, 31 Oct 2025 23:59:39 +0300 Subject: [PATCH 3/8] docs(api): make it clear which method to use when registering custom input binds --- .../controlify/api/entrypoint/ControlifyEntrypoint.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/dev/isxander/controlify/api/entrypoint/ControlifyEntrypoint.java b/src/main/java/dev/isxander/controlify/api/entrypoint/ControlifyEntrypoint.java index e83fd7720..bbae7e0fd 100644 --- a/src/main/java/dev/isxander/controlify/api/entrypoint/ControlifyEntrypoint.java +++ b/src/main/java/dev/isxander/controlify/api/entrypoint/ControlifyEntrypoint.java @@ -15,13 +15,17 @@ public interface ControlifyEntrypoint { * Called once Controlify has initialised some systems but controllers * have not yet been discovered and constructed. This is the ideal * time to register events in preparation for controller discovery. + * Input bindings cannot be registered here; use {@link #onControlifyPreInit} instead. */ void onControlifyInit(InitContext context); /** * Called at the end of Controlify's client-side entrypoint. - * You can register guides here. + * Use this to register guides, input bindings, radial icons, or screen processors, + * and to subscribe to events from {@link dev.isxander.controlify.api.event.ControlifyEvents}. + * Avoid referencing objects that are registered later (e.g. custom mod items), + * as they may not be initialized yet and could cause errors due to deferred registration. */ void onControlifyPreInit(PreInitContext context); From 42636998dff286c239eb9782536c5a7ca280033f Mon Sep 17 00:00:00 2001 From: Ellet Date: Sat, 1 Nov 2025 00:30:57 +0300 Subject: [PATCH 4/8] docs(bindings): update outdated API references --- docs/developers/bindings-api.mdx | 42 ++++++++++++++++++-------------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/docs/developers/bindings-api.mdx b/docs/developers/bindings-api.mdx index 5eb4b215c..c094a5324 100644 --- a/docs/developers/bindings-api.mdx +++ b/docs/developers/bindings-api.mdx @@ -7,40 +7,46 @@ _Learn how to register new controller bindings._ ## Registering a custom input binding - You should register bindings inside of the Controlify init entrypoint. Read more in [Controlify Entrypoint](controlify-entrypoint#using-the-entrypoint). + The custom input bindings must be registered inside the Controlify pre-init entrypoint. Read more in [Controlify Entrypoint](controlify-entrypoint#using-the-entrypoint). Controlify allows users to configure different buttons on their controllers to actions in-game. You may want your own mod's actions to be able to be invoked from the controller too. -To register a controller binding, you must use the `ControllerBindingsApi.` +To register a controller binding, use the `ControllerBindingsApi.get()` method: ```java -private BindingSupplier action1Binding; +private InputBindingSupplier actionBinding; @Override public void onControlifyInit(InitContext ctx) { - action1Binding = ctx.bindings().registerBinding( + final ControlifyBindApi registrar = ControlifyBindApi.get(); + registerInputBindings(registrar); +} + +private void registerInputBindings(ControlifyBindApi registrar) { + actionBinding = registrar.registerBinding( builder -> builder - .id("mymod", "action1") // the id of the binding, this should be unique to your mod - .category(Component.translatable("mymod.binding.category")) // the category of the binding, this is used to group bindings together in the settings - .allowedContexts(BindContext.IN_GAME) // a context is where the binding can be used, you can use multiple contexts - .radialCandiate(RadialIcons.getEffect/getIcon(...)) // if you want to allow your binding to be used in the radial menu + .id(ResourceLocation.fromNamespaceAndPath(MyMod.MODID, path)) + // The category of the binding, this is used to group bindings together in the settings. + .category(Component.translatable("mymod.binding.category")) + // A context is where the binding can be used. Multiple contexts can be used. + .allowedContexts(BindContext.IN_GAME) + // Allow using the binding in the radial menu. + // You can't use a custom modded item directly in here, since it's not registered yet + // and a runtime error will be thrown. Continue reading for a better alternative. + .radialCandidate(RadialIcons.getItem(Items.BARRIER))) + // Prevents Controlify from auto-registering controller bindings for Epic Fight's + // vanilla key mappings due explicit native support. + // You could also use ".keyEmulation(MyModKeyMappings.ACTION) to emulates + // the vanilla KeyMapping behavior, which may not always work well. + .addKeyCorrelation(MyModKeyMappings.ACTION) ); } ``` To add a name and description to the binding, you need to define the language keys `controlify.binding..` and `controlify.binding...desc` respectively, alternatively, you can set `.name(Component)` and `.description(Component)` -Registering the binding provides you with a `BindingSupplier`, where you can then access the binding with `action1Binding.on(controller);` - -Controlify automatically converts your existed modded `KeyMapping`s to controller bindings, but relying on this behaviour if you are going to explicitly support Controlify is not recommended. You can stop this conversion with the following... - -```java -@Override -public void onControlifyInit(InitContext ctx) { - ctx.bindings().exclude(MyMod.myKeyMapping); -} -``` +Registering the binding provides you with a `InputBindingSupplier`, where you can then access the binding with `actionBinding.on(controller);` ## Defining a default binding From a3d22b023376596e456fbcc870e10b07b6999870 Mon Sep 17 00:00:00 2001 From: Ellet Date: Sat, 1 Nov 2025 00:31:36 +0300 Subject: [PATCH 5/8] docs(bindings): document how to register a custom radial icons --- docs/developers/bindings-api.mdx | 62 +++++++++++++++++++++++++++++++- 1 file changed, 61 insertions(+), 1 deletion(-) diff --git a/docs/developers/bindings-api.mdx b/docs/developers/bindings-api.mdx index c094a5324..e9d838093 100644 --- a/docs/developers/bindings-api.mdx +++ b/docs/developers/bindings-api.mdx @@ -12,7 +12,7 @@ _Learn how to register new controller bindings._ Controlify allows users to configure different buttons on their controllers to actions in-game. You may want your own mod's actions to be able to be invoked from the controller too. -To register a controller binding, use the `ControllerBindingsApi.get()` method: +To register a controller binding, use the `ControlifyBindApi.get()` method: ```java private InputBindingSupplier actionBinding; @@ -89,3 +89,63 @@ There are more properties available inside of `InputBinding` which you can look There is nothing special about rendering glyphs for controller bindings, as Controlify utilises custom fonts. This means you can use the glyphs within localised text, or just render it with `graphics.drawString(myBinding.inputGlyph(), x, y, -1);` + +## Registering Custom Radial Icons + +When registering a radial menu candidate, you must provide an icon to render it in the GUI. +You can use `RadialIcons.getEffect(...)` or `RadialIcons.getItem(...)` (for example, `RadialIcons.getItem(Items.REDSTONE)`). + +However, **do not reference modded items directly** (e.g., `RadialIcons.getItem(ModItems.CUSTOM.get())`), +as item registration is deferred — meaning those items are not yet registered at this stage. + +What you can do instead, is to reference the texture image file in the bundled resource-pack of your mod, +using the following approach: + +```java +private enum MyRadialIcons { + CUSTOM(MyMod.rl("textures/item/custom.png")),; + + private final @NotNull ResourceLocation id; + + MyRadialIcons(@NotNull ResourceLocation id) { + this.id = id; + } + + public @NotNull ResourceLocation getId() { + return id; + } +} + +@Override +public void onControlifyPreInit(PreInitContext context) { + final ControlifyBindApi registrar = ControlifyBindApi.get(); + registerCustomRadialIcons(); + + // Must be called after registerCustomRadialIcons + registerInputBindings(registrar); +} + +private void registerCustomRadialIcons() { + for (MyRadialIcons icon : MyRadialIcons.values()) { + final ResourceLocation location = icon.getId(); + + // For consistency with the current Controlify radial icons, + // this code is equivalent to: + // https://github.com/isXander/Controlify/blob/f5c94c57d5e0d4954e413624a0d7ead937b6e8ab/src/main/java/dev/isxander/controlify/bindings/RadialIcons.java#L106-L112 + RadialIcons.registerIcon(location, (graphics, x, y, tickDelta) -> { + var pose = CGuiPose.ofPush(graphics); + pose.translate(x, y); + pose.scale(0.5f, 0.5f); + Blit.tex(graphics, location, 0, 0, 0, 0, 32, 32, 32, 32); + pose.pop(); + }); + } +} + +private void registerInputBindings(ControlifyBindApi registrar) { + actionBinding = registrar.registerBinding( + builder -> builder + .radialCandidate(MyRadialIcons.CUSTOM.getId()) + ); +} +``` From f6b51ed2d43d7c8d6b2492eec4500a69fbb424e6 Mon Sep 17 00:00:00 2001 From: Ellet Date: Sat, 20 Dec 2025 00:43:19 +0300 Subject: [PATCH 6/8] docs(api): update Fabric entrypoint registration to reflect https://github.com/isXander/Controlify/commit/545d4a7e766f689b7bd5ca2ad95e6f554257b651 --- docs/developers/controlify-entrypoint.mdx | 36 ++++++++++++----------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/docs/developers/controlify-entrypoint.mdx b/docs/developers/controlify-entrypoint.mdx index d973152a4..1aafd7d6a 100644 --- a/docs/developers/controlify-entrypoint.mdx +++ b/docs/developers/controlify-entrypoint.mdx @@ -49,24 +49,26 @@ dependencies { ## Registering the entrypoint -- On Fabric, you register an entrypoint like any other, by adding an entry to your `fabric.mod.json` file under the `entrypoints` section. -- On NeoForge, you register an entrypoint using a Java service provider interface (SPI) in **`META-INF/services/dev.isxander.controlify.api.entrypoint.ControlifyEntrypoint`**. - - - ```json !!tabs (Fabric) fabric.mod.json - { - "entrypoints": { - "controlify": [ - "com.example.mymod.ControlifyEntrypoint" - ] - } - } - ``` +The entrypoint can be registered using a +Java service provider interface (SPI) in **`META-INF/services/dev.isxander.controlify.api.entrypoint.ControlifyEntrypoint`** + +```txt +com.example.mymod.ControlifyEntrypoint +``` + +### Fabric Entrypoint - ```txt !!tabs (NeoForge) META-INF/services/dev.isxander.controlify.api.entrypoint.ControlifyEntrypoint - com.example.mymod.ControlifyEntrypoint - ``` - +On Fabric, the same entrypoint may **also** be registered via `fabric.mod.json`. + +```json +{ + "entrypoints": { + "controlify": [ + "com.example.mymod.ControlifyEntrypoint" + ] + } +} +``` Then simply create the class and implement the interface: From 4f892cc54360917dd33978996d5dcf3ea071cf48 Mon Sep 17 00:00:00 2001 From: Ellet Date: Sat, 20 Dec 2025 00:53:37 +0300 Subject: [PATCH 7/8] docs(api): address https://github.com/isXander/Controlify/pull/706#discussion_r2635192230 --- docs/developers/controlify-entrypoint.mdx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/developers/controlify-entrypoint.mdx b/docs/developers/controlify-entrypoint.mdx index 1aafd7d6a..a867c323f 100644 --- a/docs/developers/controlify-entrypoint.mdx +++ b/docs/developers/controlify-entrypoint.mdx @@ -37,7 +37,8 @@ controlify_version=2.4.3+1.21.1-neoforge ```kotlin dependencies { - // You can change "compileOnly" to "implementation" for testing in-game. + // Note: "compileOnly" must be replaced with "modCompileOnly" if Fabric Loom is used while targeting an obfuscated Minecraft version. + // This is not needed for deobfuscated Minecraft versions. compileOnly("dev.isxander:controlify:${project.controlify_version}") { // Only need Controlify API, ignore the transitive dependencies (e.g, QuiltMC parsers) transitive = false From fec28f1c42b09abaf2d3c83b0ad737a2016cf222 Mon Sep 17 00:00:00 2001 From: Ellet Date: Sat, 20 Dec 2025 01:07:14 +0300 Subject: [PATCH 8/8] docs: address key emulation confusion https://github.com/isXander/Controlify/pull/706#discussion_r2507097761 --- docs/developers/bindings-api.mdx | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/docs/developers/bindings-api.mdx b/docs/developers/bindings-api.mdx index e9d838093..91d0dd03e 100644 --- a/docs/developers/bindings-api.mdx +++ b/docs/developers/bindings-api.mdx @@ -30,16 +30,24 @@ private void registerInputBindings(ControlifyBindApi registrar) { // The category of the binding, this is used to group bindings together in the settings. .category(Component.translatable("mymod.binding.category")) // A context is where the binding can be used. Multiple contexts can be used. + // For example, using `BindContext.IN_GAME` will cause the binding to always return + // `false` inside a screen, even if it's pressed. .allowedContexts(BindContext.IN_GAME) // Allow using the binding in the radial menu. // You can't use a custom modded item directly in here, since it's not registered yet // and a runtime error will be thrown. Continue reading for a better alternative. .radialCandidate(RadialIcons.getItem(Items.BARRIER))) - // Prevents Controlify from auto-registering controller bindings for Epic Fight's - // vanilla key mappings due explicit native support. - // You could also use ".keyEmulation(MyModKeyMappings.ACTION) to emulates - // the vanilla KeyMapping behavior, which may not always work well. + // Prevents Controlify from auto-registering controller bindings for the + // vanilla key mappings due to explicit native support. .addKeyCorrelation(MyModKeyMappings.ACTION) + // Emulates the vanilla KeyMapping behavior. + // Note: "keyEmulation" calls "addKeyCorrelation" internally, so no need to call both. + // + // It will not work with key mappings that are handled using NeoForge `InputEvent`s. + // It such cases, you can fix the problem either by: + // 1. Patch the mod to migrate to vanilla `KeyMapping` handling (example: https://github.com/iron431/irons-spells-n-spellbooks/pull/989). + // 2. Provide explicit Controlify support. + .keyEmulation(MyModKeyMappings.ACTION) ); } ```