forked from Clones/Controlify
vanilla keybind override and config system
This commit is contained in:
@ -13,12 +13,13 @@ plugins {
|
|||||||
}
|
}
|
||||||
|
|
||||||
group = "dev.isxander"
|
group = "dev.isxander"
|
||||||
version = "1.0.0+1.19.3"
|
version = "1.0.0+1.19.4"
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
maven("https://maven.terraformersmc.com")
|
maven("https://maven.terraformersmc.com")
|
||||||
maven("https://maven.isxander.dev/releases")
|
maven("https://maven.isxander.dev/releases")
|
||||||
|
maven("https://maven.isxander.dev/snapshots")
|
||||||
maven("https://maven.quiltmc.org/repository/release")
|
maven("https://maven.quiltmc.org/repository/release")
|
||||||
maven("https://api.modrinth.com/maven") {
|
maven("https://api.modrinth.com/maven") {
|
||||||
name = "Modrinth"
|
name = "Modrinth"
|
||||||
@ -40,6 +41,8 @@ dependencies {
|
|||||||
modImplementation(libs.fabric.loader)
|
modImplementation(libs.fabric.loader)
|
||||||
|
|
||||||
modImplementation(libs.fabric.api)
|
modImplementation(libs.fabric.api)
|
||||||
|
modImplementation(libs.yet.another.config.lib)
|
||||||
|
modImplementation(libs.mod.menu)
|
||||||
|
|
||||||
implementation(libs.mixin.extras)
|
implementation(libs.mixin.extras)
|
||||||
annotationProcessor(libs.mixin.extras)
|
annotationProcessor(libs.mixin.extras)
|
||||||
|
@ -12,6 +12,8 @@ quilt_mappings = "10"
|
|||||||
fabric_loader = "0.14.13"
|
fabric_loader = "0.14.13"
|
||||||
fabric_api = "0.73.1+1.19.4"
|
fabric_api = "0.73.1+1.19.4"
|
||||||
mixin_extras = "0.2.0-beta.1"
|
mixin_extras = "0.2.0-beta.1"
|
||||||
|
yet_another_config_lib = "2.2.0+update.1.19.4-SNAPSHOT"
|
||||||
|
mod_menu = "6.0.0-beta.1"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
minecraft = { module = "com.mojang:minecraft", version.ref = "minecraft" }
|
minecraft = { module = "com.mojang:minecraft", version.ref = "minecraft" }
|
||||||
@ -19,6 +21,8 @@ fabric_loader = { module = "net.fabricmc:fabric-loader", version.ref = "fabric_l
|
|||||||
|
|
||||||
fabric_api = { module = "net.fabricmc.fabric-api:fabric-api", version.ref = "fabric_api" }
|
fabric_api = { module = "net.fabricmc.fabric-api:fabric-api", version.ref = "fabric_api" }
|
||||||
mixin_extras = { module = "com.github.llamalad7:mixinextras", version.ref = "mixin_extras" }
|
mixin_extras = { module = "com.github.llamalad7:mixinextras", version.ref = "mixin_extras" }
|
||||||
|
yet_another_config_lib = { module = "dev.isxander:yet-another-config-lib", version.ref = "yet_another_config_lib" }
|
||||||
|
mod_menu = { module = "com.terraformersmc:modmenu", version.ref = "mod_menu" }
|
||||||
|
|
||||||
[plugins]
|
[plugins]
|
||||||
loom = { id = "fabric-loom", version.ref = "loom" }
|
loom = { id = "fabric-loom", version.ref = "loom" }
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package dev.isxander.controlify;
|
package dev.isxander.controlify;
|
||||||
|
|
||||||
import dev.isxander.controlify.compatibility.screen.ScreenProcessorProvider;
|
import dev.isxander.controlify.compatibility.screen.ScreenProcessorProvider;
|
||||||
|
import dev.isxander.controlify.config.ControlifyConfig;
|
||||||
import dev.isxander.controlify.controller.Controller;
|
import dev.isxander.controlify.controller.Controller;
|
||||||
import dev.isxander.controlify.controller.ControllerState;
|
import dev.isxander.controlify.controller.ControllerState;
|
||||||
import dev.isxander.controlify.event.ControlifyEvents;
|
import dev.isxander.controlify.event.ControlifyEvents;
|
||||||
@ -26,6 +27,9 @@ public class Controlify {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// load after initial controller discovery
|
||||||
|
ControlifyConfig.load();
|
||||||
|
|
||||||
// listen for new controllers
|
// listen for new controllers
|
||||||
GLFW.glfwSetJoystickCallback((jid, event) -> {
|
GLFW.glfwSetJoystickCallback((jid, event) -> {
|
||||||
System.out.println("Event: " + event);
|
System.out.println("Event: " + event);
|
||||||
@ -33,6 +37,9 @@ public class Controlify {
|
|||||||
setCurrentController(Controller.byId(jid));
|
setCurrentController(Controller.byId(jid));
|
||||||
System.out.println("Connected: " + currentController.name());
|
System.out.println("Connected: " + currentController.name());
|
||||||
this.setCurrentInputMode(InputMode.CONTROLLER);
|
this.setCurrentInputMode(InputMode.CONTROLLER);
|
||||||
|
|
||||||
|
ControlifyConfig.load(); // load config again if a configuration already exists for this controller
|
||||||
|
ControlifyConfig.save(); // save config if it doesn't exist
|
||||||
} else if (event == GLFW.GLFW_DISCONNECTED) {
|
} else if (event == GLFW.GLFW_DISCONNECTED) {
|
||||||
Controller.CONTROLLERS.remove(jid);
|
Controller.CONTROLLERS.remove(jid);
|
||||||
setCurrentController(Controller.CONTROLLERS.values().stream().filter(Controller::connected).findFirst().orElse(null));
|
setCurrentController(Controller.CONTROLLERS.values().stream().filter(Controller::connected).findFirst().orElse(null));
|
||||||
@ -41,10 +48,10 @@ public class Controlify {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ClientTickEvents.START_CLIENT_TICK.register(this::updateControllers);
|
ClientTickEvents.START_CLIENT_TICK.register(this::tick);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateControllers(Minecraft client) {
|
public void tick(Minecraft client) {
|
||||||
for (Controller controller : Controller.CONTROLLERS.values()) {
|
for (Controller controller : Controller.CONTROLLERS.values()) {
|
||||||
controller.updateState();
|
controller.updateState();
|
||||||
}
|
}
|
||||||
|
@ -1,37 +1,60 @@
|
|||||||
package dev.isxander.controlify.bindings;
|
package dev.isxander.controlify.bindings;
|
||||||
|
|
||||||
|
import dev.isxander.controlify.controller.Controller;
|
||||||
import dev.isxander.controlify.controller.ControllerState;
|
import dev.isxander.controlify.controller.ControllerState;
|
||||||
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
|
||||||
@FunctionalInterface
|
import java.util.function.BiFunction;
|
||||||
public interface Bind {
|
import java.util.function.Function;
|
||||||
boolean state(ControllerState controllerState);
|
|
||||||
|
|
||||||
Bind A_BUTTON = state -> state.buttons().a();
|
public enum Bind {
|
||||||
Bind B_BUTTON = state -> state.buttons().b();
|
A_BUTTON(state -> state.buttons().a(), "a_button"),
|
||||||
Bind X_BUTTON = state -> state.buttons().x();
|
B_BUTTON(state -> state.buttons().b(), "b_button"),
|
||||||
Bind Y_BUTTON = state -> state.buttons().y();
|
X_BUTTON(state -> state.buttons().x(), "x_button"),
|
||||||
Bind LEFT_BUMPER = state -> state.buttons().leftBumper();
|
Y_BUTTON(state -> state.buttons().y(), "y_button"),
|
||||||
Bind RIGHT_BUMPER = state -> state.buttons().rightBumper();
|
LEFT_BUMPER(state -> state.buttons().leftBumper(), "left_bumper"),
|
||||||
Bind LEFT_STICK = state -> state.buttons().leftStick();
|
RIGHT_BUMPER(state -> state.buttons().rightBumper(), "right_bumper"),
|
||||||
Bind RIGHT_STICK = state -> state.buttons().rightStick();
|
LEFT_STICK(state -> state.buttons().leftStick(), "left_stick"),
|
||||||
Bind START = state -> state.buttons().start();
|
RIGHT_STICK(state -> state.buttons().rightStick(), "right_stick"),
|
||||||
Bind BACK = state -> state.buttons().back();
|
START(state -> state.buttons().start(), "start"),
|
||||||
Bind LEFT_TRIGGER = leftTrigger(0.5f);
|
BACK(state -> state.buttons().back(), "back"),
|
||||||
Bind RIGHT_TRIGGER = rightTrigger(0.5f);
|
DPAD_UP(state -> state.buttons().dpadUp(), "dpad_up"),
|
||||||
|
DPAD_DOWN(state -> state.buttons().dpadDown(), "dpad_down"),
|
||||||
|
DPAD_LEFT(state -> state.buttons().dpadLeft(), "dpad_left"),
|
||||||
|
DPAD_RIGHT(state -> state.buttons().dpadRight(), "dpad_right"),
|
||||||
|
LEFT_TRIGGER((state, controller) -> state.axes().leftTrigger() >= controller.config().leftTriggerActivationThreshold, "left_trigger"),
|
||||||
|
RIGHT_TRIGGER((state, controller) -> state.axes().rightTrigger() >= controller.config().rightTriggerActivationThreshold, "right_trigger");
|
||||||
|
|
||||||
Bind[] ALL = {
|
private final BiFunction<ControllerState, Controller, Boolean> state;
|
||||||
A_BUTTON, B_BUTTON, X_BUTTON, Y_BUTTON,
|
private final String identifier;
|
||||||
LEFT_BUMPER, RIGHT_BUMPER,
|
private final ResourceLocation textureLocation;
|
||||||
LEFT_STICK, RIGHT_STICK,
|
|
||||||
START, BACK,
|
|
||||||
LEFT_TRIGGER, RIGHT_TRIGGER
|
|
||||||
};
|
|
||||||
|
|
||||||
static Bind leftTrigger(float threshold) {
|
Bind(BiFunction<ControllerState, Controller, Boolean> state, String identifier) {
|
||||||
return state -> state.axes().leftTrigger() > threshold;
|
this.state = state;
|
||||||
|
this.identifier = identifier;
|
||||||
|
this.textureLocation = new ResourceLocation("controlify", "textures/gui/buttons/" + identifier + ".png");
|
||||||
}
|
}
|
||||||
|
|
||||||
static Bind rightTrigger(float threshold) {
|
Bind(Function<ControllerState, Boolean> state, String identifier) {
|
||||||
return state -> state.axes().rightTrigger() > threshold;
|
this((state1, controller) -> state.apply(state1), identifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean state(ControllerState controllerState, Controller controller) {
|
||||||
|
return state.apply(controllerState, controller);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String identifier() {
|
||||||
|
return identifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ResourceLocation textureLocation() {
|
||||||
|
return textureLocation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Bind fromIdentifier(String identifier) {
|
||||||
|
for (Bind bind : values()) {
|
||||||
|
if (bind.identifier.equals(identifier)) return bind;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,34 +1,56 @@
|
|||||||
package dev.isxander.controlify.bindings;
|
package dev.isxander.controlify.bindings;
|
||||||
|
|
||||||
import dev.isxander.controlify.controller.Controller;
|
import dev.isxander.controlify.controller.Controller;
|
||||||
|
import net.minecraft.client.KeyMapping;
|
||||||
import net.minecraft.network.chat.Component;
|
import net.minecraft.network.chat.Component;
|
||||||
|
|
||||||
public class ControllerBinding {
|
public class ControllerBinding {
|
||||||
private final Controller controller;
|
private final Controller controller;
|
||||||
private final Bind bind;
|
private Bind bind;
|
||||||
|
private final Bind defaultBind;
|
||||||
|
private final String id;
|
||||||
private final Component name, description;
|
private final Component name, description;
|
||||||
|
private final KeyMapping override;
|
||||||
|
|
||||||
public ControllerBinding(Controller controller, Bind defaultBind, String id, Component description) {
|
public ControllerBinding(Controller controller, Bind defaultBind, String id, Component description, KeyMapping override) {
|
||||||
this.controller = controller;
|
this.controller = controller;
|
||||||
this.bind = defaultBind;
|
this.bind = this.defaultBind = defaultBind;
|
||||||
|
this.id = id;
|
||||||
this.name = Component.translatable("controlify.binding." + id);
|
this.name = Component.translatable("controlify.binding." + id);
|
||||||
this.description = description;
|
this.description = description;
|
||||||
|
this.override = override;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ControllerBinding(Controller controller, Bind defaultBind, String id) {
|
public ControllerBinding(Controller controller, Bind defaultBind, String id, KeyMapping override) {
|
||||||
this(controller, defaultBind, id, Component.empty());
|
this(controller, defaultBind, id, Component.empty(), override);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean held() {
|
public boolean held() {
|
||||||
return bind.state(controller.state());
|
return bind.state(controller.state(), controller);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean justPressed() {
|
public boolean justPressed() {
|
||||||
return held() && !bind.state(controller.prevState());
|
return held() && !bind.state(controller.prevState(), controller);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean justReleased() {
|
public boolean justReleased() {
|
||||||
return !held() && bind.state(controller.prevState());
|
return !held() && bind.state(controller.prevState(), controller);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Bind currentBind() {
|
||||||
|
return bind;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCurrentBind(Bind bind) {
|
||||||
|
this.bind = bind;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Bind defaultBind() {
|
||||||
|
return defaultBind;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String id() {
|
||||||
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Component name() {
|
public Component name() {
|
||||||
@ -38,4 +60,8 @@ public class ControllerBinding {
|
|||||||
public Component description() {
|
public Component description() {
|
||||||
return description;
|
return description;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public KeyMapping override() {
|
||||||
|
return override;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,22 +1,75 @@
|
|||||||
package dev.isxander.controlify.bindings;
|
package dev.isxander.controlify.bindings;
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
import dev.isxander.controlify.controller.Controller;
|
import dev.isxander.controlify.controller.Controller;
|
||||||
|
import dev.isxander.controlify.event.ControlifyEvents;
|
||||||
|
import dev.isxander.controlify.mixins.KeyMappingAccessor;
|
||||||
|
import net.minecraft.client.KeyMapping;
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public class ControllerBindings {
|
public class ControllerBindings {
|
||||||
public final ControllerBinding JUMP, SNEAK, ATTACK, USE, SPRINT, NEXT_SLOT, PREV_SLOT;
|
public final ControllerBinding JUMP, SNEAK, ATTACK, USE, SPRINT, NEXT_SLOT, PREV_SLOT, PAUSE, INVENTORY, CHANGE_PERSPECTIVE, OPEN_CHAT;
|
||||||
public final ControllerBinding[] ALL;
|
|
||||||
|
private final List<ControllerBinding> registry = new ArrayList<>();
|
||||||
|
|
||||||
public ControllerBindings(Controller controller) {
|
public ControllerBindings(Controller controller) {
|
||||||
JUMP = new ControllerBinding(controller, Bind.A_BUTTON, "jump");
|
var options = Minecraft.getInstance().options;
|
||||||
SNEAK = new ControllerBinding(controller, Bind.RIGHT_STICK, "sneak");
|
|
||||||
ATTACK = new ControllerBinding(controller, Bind.RIGHT_TRIGGER, "attack");
|
|
||||||
USE = new ControllerBinding(controller, Bind.LEFT_TRIGGER, "use");
|
|
||||||
SPRINT = new ControllerBinding(controller, Bind.LEFT_STICK, "sprint");
|
|
||||||
NEXT_SLOT = new ControllerBinding(controller, Bind.RIGHT_BUMPER, "next_slot");
|
|
||||||
PREV_SLOT = new ControllerBinding(controller, Bind.LEFT_BUMPER, "prev_slot");
|
|
||||||
|
|
||||||
ALL = new ControllerBinding[] {
|
JUMP = register(new ControllerBinding(controller, Bind.A_BUTTON, "jump", options.keyJump));
|
||||||
JUMP, SNEAK, ATTACK, USE, SPRINT, NEXT_SLOT, PREV_SLOT
|
SNEAK = register(new ControllerBinding(controller, Bind.RIGHT_STICK, "sneak", options.keyShift));
|
||||||
};
|
ATTACK = register(new ControllerBinding(controller, Bind.RIGHT_TRIGGER, "attack", options.keyAttack));
|
||||||
|
USE = register(new ControllerBinding(controller, Bind.LEFT_TRIGGER, "use", options.keyUse));
|
||||||
|
SPRINT = register(new ControllerBinding(controller, Bind.LEFT_STICK, "sprint", options.keySprint));
|
||||||
|
NEXT_SLOT = register(new ControllerBinding(controller, Bind.RIGHT_BUMPER, "next_slot", null));
|
||||||
|
PREV_SLOT = register(new ControllerBinding(controller, Bind.LEFT_BUMPER, "prev_slot", null));
|
||||||
|
PAUSE = register(new ControllerBinding(controller, Bind.START, "pause", null));
|
||||||
|
INVENTORY = register(new ControllerBinding(controller, Bind.Y_BUTTON, "inventory", options.keyInventory));
|
||||||
|
CHANGE_PERSPECTIVE = register(new ControllerBinding(controller, Bind.BACK, "change_perspective", options.keyTogglePerspective));
|
||||||
|
OPEN_CHAT = register(new ControllerBinding(controller, Bind.DPAD_UP, "open_chat", options.keyChat));
|
||||||
|
|
||||||
|
ControlifyEvents.CONTROLLER_BIND_REGISTRY.invoker().onRegisterControllerBinds(this);
|
||||||
|
|
||||||
|
ControlifyEvents.CONTROLLER_STATE_UPDATED.register(this::imitateVanillaClick);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ControllerBinding register(ControllerBinding binding) {
|
||||||
|
registry.add(binding);
|
||||||
|
return binding;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ControllerBinding> registry() {
|
||||||
|
return Collections.unmodifiableList(registry);
|
||||||
|
}
|
||||||
|
|
||||||
|
public JsonObject toJson() {
|
||||||
|
JsonObject json = new JsonObject();
|
||||||
|
for (var binding : registry()) {
|
||||||
|
json.addProperty(binding.id(), binding.currentBind().identifier());
|
||||||
|
}
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void fromJson(JsonObject json) {
|
||||||
|
for (var binding : registry()) {
|
||||||
|
var bind = json.get(binding.id());
|
||||||
|
if (bind == null) continue;
|
||||||
|
binding.setCurrentBind(Bind.fromIdentifier(bind.getAsString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void imitateVanillaClick(Controller controller) {
|
||||||
|
for (var binding : registry()) {
|
||||||
|
KeyMapping vanillaKey = binding.override();
|
||||||
|
if (vanillaKey == null) continue;
|
||||||
|
|
||||||
|
var vanillaKeyCode = ((KeyMappingAccessor) vanillaKey).getKey();
|
||||||
|
|
||||||
|
KeyMapping.set(vanillaKeyCode, binding.held());
|
||||||
|
if (binding.justPressed()) KeyMapping.click(vanillaKeyCode);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,81 @@
|
|||||||
|
package dev.isxander.controlify.config;
|
||||||
|
|
||||||
|
import com.google.gson.*;
|
||||||
|
import dev.isxander.controlify.controller.Controller;
|
||||||
|
import dev.isxander.controlify.controller.ControllerConfig;
|
||||||
|
import net.fabricmc.loader.api.FabricLoader;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.StandardOpenOption;
|
||||||
|
|
||||||
|
public class ControlifyConfig {
|
||||||
|
public static final Path CONFIG_PATH = FabricLoader.getInstance().getConfigDir().resolve("controlify.json");
|
||||||
|
|
||||||
|
private static final Gson GSON = new GsonBuilder()
|
||||||
|
.serializeNulls()
|
||||||
|
.setPrettyPrinting()
|
||||||
|
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
|
||||||
|
.create();
|
||||||
|
|
||||||
|
private static JsonObject config = new JsonObject();
|
||||||
|
|
||||||
|
public static void save() {
|
||||||
|
try {
|
||||||
|
generateConfig();
|
||||||
|
|
||||||
|
Files.deleteIfExists(CONFIG_PATH);
|
||||||
|
Files.writeString(CONFIG_PATH, GSON.toJson(config), StandardOpenOption.CREATE_NEW, StandardOpenOption.TRUNCATE_EXISTING);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new IllegalStateException("Failed to save config!", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void load() {
|
||||||
|
if (!Files.exists(CONFIG_PATH)) {
|
||||||
|
save();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
applyConfig(GSON.fromJson(Files.readString(CONFIG_PATH), JsonObject.class));
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new IllegalStateException("Failed to load config!", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void generateConfig() {
|
||||||
|
JsonObject configCopy = config.deepCopy(); // we use the old config, so we don't lose disconnected controller data
|
||||||
|
|
||||||
|
for (var controller : Controller.CONTROLLERS.values()) {
|
||||||
|
// `add` replaces if already existing
|
||||||
|
configCopy.add(controller.guid(), generateControllerConfig(controller));
|
||||||
|
}
|
||||||
|
|
||||||
|
config = configCopy;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static JsonObject generateControllerConfig(Controller controller) {
|
||||||
|
JsonObject object = new JsonObject();
|
||||||
|
|
||||||
|
object.add("config", GSON.toJsonTree(controller.config()));
|
||||||
|
object.add("bindings", controller.bindings().toJson());
|
||||||
|
|
||||||
|
return object;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void applyConfig(JsonObject object) {
|
||||||
|
for (var controller : Controller.CONTROLLERS.values()) {
|
||||||
|
var settings = object.getAsJsonObject(controller.guid());
|
||||||
|
if (settings != null) {
|
||||||
|
applyControllerConfig(controller, settings);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void applyControllerConfig(Controller controller, JsonObject object) {
|
||||||
|
controller.config().overwrite(GSON.fromJson(object.getAsJsonObject("config"), ControllerConfig.class));
|
||||||
|
controller.bindings().fromJson(object.getAsJsonObject("bindings"));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
package dev.isxander.controlify.config.gui;
|
||||||
|
|
||||||
|
import com.terraformersmc.modmenu.api.ConfigScreenFactory;
|
||||||
|
import com.terraformersmc.modmenu.api.ModMenuApi;
|
||||||
|
|
||||||
|
public class ModMenuIntegration implements ModMenuApi {
|
||||||
|
@Override
|
||||||
|
public ConfigScreenFactory<?> getModConfigScreenFactory() {
|
||||||
|
return YACLHelper::generateConfigScreen;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,65 @@
|
|||||||
|
package dev.isxander.controlify.config.gui;
|
||||||
|
|
||||||
|
import dev.isxander.controlify.config.ControlifyConfig;
|
||||||
|
import dev.isxander.controlify.controller.Controller;
|
||||||
|
import dev.isxander.controlify.controller.ControllerConfig;
|
||||||
|
import dev.isxander.yacl.api.ConfigCategory;
|
||||||
|
import dev.isxander.yacl.api.Option;
|
||||||
|
import dev.isxander.yacl.api.OptionGroup;
|
||||||
|
import dev.isxander.yacl.api.YetAnotherConfigLib;
|
||||||
|
import dev.isxander.yacl.gui.controllers.slider.FloatSliderController;
|
||||||
|
import net.minecraft.ChatFormatting;
|
||||||
|
import net.minecraft.client.gui.screens.Screen;
|
||||||
|
import net.minecraft.network.chat.Component;
|
||||||
|
|
||||||
|
public class YACLHelper {
|
||||||
|
public static Screen generateConfigScreen(Screen parent) {
|
||||||
|
var yacl = YetAnotherConfigLib.createBuilder()
|
||||||
|
.title(Component.literal("Controlify"))
|
||||||
|
.save(ControlifyConfig::save);
|
||||||
|
|
||||||
|
for (var controller : Controller.CONTROLLERS.values()) {
|
||||||
|
var category = ConfigCategory.createBuilder();
|
||||||
|
|
||||||
|
var customName = controller.config().customName;
|
||||||
|
category.name(Component.literal(customName == null ? controller.name() : customName));
|
||||||
|
|
||||||
|
var config = controller.config();
|
||||||
|
var def = ControllerConfig.DEFAULT;
|
||||||
|
var configGroup = OptionGroup.createBuilder()
|
||||||
|
.name(Component.translatable("controlify.gui.group.config"))
|
||||||
|
.tooltip(Component.translatable("controlify.gui.group.config.tooltip"))
|
||||||
|
.option(Option.createBuilder(float.class)
|
||||||
|
.name(Component.translatable("controlify.gui.left_stick_deadzone"))
|
||||||
|
.tooltip(Component.translatable("controlify.gui.left_stick_deadzone.tooltip"))
|
||||||
|
.tooltip(Component.translatable("controlify.gui.stickdrift_warning").withStyle(ChatFormatting.RED))
|
||||||
|
.binding(def.leftStickDeadzone, () -> config.leftStickDeadzone, v -> config.leftStickDeadzone = v)
|
||||||
|
.controller(opt -> new FloatSliderController(opt, 0, 1, 0.02f, v -> Component.literal(String.format("%.0f%%", v*100))))
|
||||||
|
.build())
|
||||||
|
.option(Option.createBuilder(float.class)
|
||||||
|
.name(Component.translatable("controlify.gui.right_stick_deadzone"))
|
||||||
|
.tooltip(Component.translatable("controlify.gui.right_stick_deadzone.tooltip"))
|
||||||
|
.tooltip(Component.translatable("controlify.gui.stickdrift_warning").withStyle(ChatFormatting.RED))
|
||||||
|
.binding(def.rightStickDeadzone, () -> config.rightStickDeadzone, v -> config.rightStickDeadzone = v)
|
||||||
|
.controller(opt -> new FloatSliderController(opt, 0, 1, 0.02f, v -> Component.literal(String.format("%.0f%%", v*100))))
|
||||||
|
.build())
|
||||||
|
.option(Option.createBuilder(float.class)
|
||||||
|
.name(Component.translatable("controlify.gui.left_trigger_threshold"))
|
||||||
|
.tooltip(Component.translatable("controlify.gui.left_trigger_threshold.tooltip"))
|
||||||
|
.binding(def.leftTriggerActivationThreshold, () -> config.leftTriggerActivationThreshold, v -> config.leftTriggerActivationThreshold = v)
|
||||||
|
.controller(opt -> new FloatSliderController(opt, 0, 1, 0.05f, v -> Component.literal(String.format("%.0f%%", v*100))))
|
||||||
|
.build())
|
||||||
|
.option(Option.createBuilder(float.class)
|
||||||
|
.name(Component.translatable("controlify.gui.right_trigger_threshold"))
|
||||||
|
.tooltip(Component.translatable("controlify.gui.right_trigger_threshold.tooltip"))
|
||||||
|
.binding(def.rightTriggerActivationThreshold, () -> config.rightTriggerActivationThreshold, v -> config.rightTriggerActivationThreshold = v)
|
||||||
|
.controller(opt -> new FloatSliderController(opt, 0, 1, 0.05f, v -> Component.literal(String.format("%.0f%%", v*100))))
|
||||||
|
.build());
|
||||||
|
category.group(configGroup.build());
|
||||||
|
|
||||||
|
yacl.category(category.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
return yacl.build().generateScreen(parent);
|
||||||
|
}
|
||||||
|
}
|
@ -21,6 +21,7 @@ public final class Controller {
|
|||||||
private ControllerState prevState = ControllerState.EMPTY;
|
private ControllerState prevState = ControllerState.EMPTY;
|
||||||
|
|
||||||
private final ControllerBindings bindings = new ControllerBindings(this);
|
private final ControllerBindings bindings = new ControllerBindings(this);
|
||||||
|
private final ControllerConfig config = new ControllerConfig();
|
||||||
|
|
||||||
public Controller(int id, String guid, String name, boolean gamepad) {
|
public Controller(int id, String guid, String name, boolean gamepad) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
@ -46,10 +47,10 @@ public final class Controller {
|
|||||||
prevState = state;
|
prevState = state;
|
||||||
|
|
||||||
AxesState axesState = AxesState.fromController(this)
|
AxesState axesState = AxesState.fromController(this)
|
||||||
.leftJoystickDeadZone(0.2f, 0.2f)
|
.leftJoystickDeadZone(config().leftStickDeadzone, config().leftStickDeadzone)
|
||||||
.rightJoystickDeadZone(0.2f, 0.2f)
|
.rightJoystickDeadZone(config().rightStickDeadzone, config().rightStickDeadzone)
|
||||||
.leftTriggerDeadZone(0.1f)
|
.leftTriggerDeadZone(config().leftTriggerDeadzone)
|
||||||
.rightTriggerDeadZone(0.1f);
|
.rightTriggerDeadZone(config().rightTriggerDeadzone);
|
||||||
ButtonState buttonState = ButtonState.fromController(this);
|
ButtonState buttonState = ButtonState.fromController(this);
|
||||||
state = new ControllerState(axesState, buttonState);
|
state = new ControllerState(axesState, buttonState);
|
||||||
|
|
||||||
@ -87,15 +88,16 @@ public final class Controller {
|
|||||||
return gamepad;
|
return gamepad;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ControllerConfig config() {
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object obj) {
|
public boolean equals(Object obj) {
|
||||||
if (obj == this) return true;
|
if (obj == this) return true;
|
||||||
if (obj == null || obj.getClass() != this.getClass()) return false;
|
if (obj == null || obj.getClass() != this.getClass()) return false;
|
||||||
var that = (Controller) obj;
|
var that = (Controller) obj;
|
||||||
return this.id == that.id &&
|
return Objects.equals(this.guid, that.guid);
|
||||||
Objects.equals(this.guid, that.guid) &&
|
|
||||||
Objects.equals(this.name, that.name) &&
|
|
||||||
this.gamepad == that.gamepad;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -103,13 +105,6 @@ public final class Controller {
|
|||||||
return Objects.hash(guid);
|
return Objects.hash(guid);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "Controller[" +
|
|
||||||
"id=" + id + ", " +
|
|
||||||
"name=" + name + ']';
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Controller byId(int id) {
|
public static Controller byId(int id) {
|
||||||
if (id > GLFW.GLFW_JOYSTICK_LAST)
|
if (id > GLFW.GLFW_JOYSTICK_LAST)
|
||||||
throw new IllegalArgumentException("Invalid joystick id: " + id);
|
throw new IllegalArgumentException("Invalid joystick id: " + id);
|
||||||
|
@ -0,0 +1,33 @@
|
|||||||
|
package dev.isxander.controlify.controller;
|
||||||
|
|
||||||
|
import dev.isxander.controlify.config.ControlifyConfig;
|
||||||
|
|
||||||
|
public class ControllerConfig {
|
||||||
|
public static final ControllerConfig DEFAULT = new ControllerConfig();
|
||||||
|
|
||||||
|
public float leftStickDeadzone = 0.2f;
|
||||||
|
public float rightStickDeadzone = 0.2f;
|
||||||
|
|
||||||
|
// not sure if triggers need deadzones
|
||||||
|
public float leftTriggerDeadzone = 0.0f;
|
||||||
|
public float rightTriggerDeadzone = 0.0f;
|
||||||
|
|
||||||
|
public float leftTriggerActivationThreshold = 0.5f;
|
||||||
|
public float rightTriggerActivationThreshold = 0.5f;
|
||||||
|
|
||||||
|
public String customName = null;
|
||||||
|
|
||||||
|
public void notifyChanged() {
|
||||||
|
ControlifyConfig.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void overwrite(ControllerConfig from) {
|
||||||
|
this.leftStickDeadzone = from.leftStickDeadzone;
|
||||||
|
this.rightStickDeadzone = from.rightStickDeadzone;
|
||||||
|
this.leftTriggerDeadzone = from.leftTriggerDeadzone;
|
||||||
|
this.rightTriggerDeadzone = from.rightTriggerDeadzone;
|
||||||
|
this.leftTriggerActivationThreshold = from.leftTriggerActivationThreshold;
|
||||||
|
this.rightTriggerActivationThreshold = from.rightTriggerActivationThreshold;
|
||||||
|
this.customName = from.customName;
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
package dev.isxander.controlify.event;
|
package dev.isxander.controlify.event;
|
||||||
|
|
||||||
import dev.isxander.controlify.InputMode;
|
import dev.isxander.controlify.InputMode;
|
||||||
|
import dev.isxander.controlify.bindings.ControllerBindings;
|
||||||
import dev.isxander.controlify.controller.Controller;
|
import dev.isxander.controlify.controller.Controller;
|
||||||
import net.fabricmc.fabric.api.event.Event;
|
import net.fabricmc.fabric.api.event.Event;
|
||||||
import net.fabricmc.fabric.api.event.EventFactory;
|
import net.fabricmc.fabric.api.event.EventFactory;
|
||||||
@ -18,6 +19,12 @@ public class ControlifyEvents {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
public static final Event<ControllerBindRegistry> CONTROLLER_BIND_REGISTRY = EventFactory.createArrayBacked(ControllerBindRegistry.class, callbacks -> bindings -> {
|
||||||
|
for (ControllerBindRegistry callback : callbacks) {
|
||||||
|
callback.onRegisterControllerBinds(bindings);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
@FunctionalInterface
|
@FunctionalInterface
|
||||||
public interface InputModeChanged {
|
public interface InputModeChanged {
|
||||||
void onInputModeChanged(InputMode mode);
|
void onInputModeChanged(InputMode mode);
|
||||||
@ -27,4 +34,9 @@ public class ControlifyEvents {
|
|||||||
public interface ControllerStateUpdate {
|
public interface ControllerStateUpdate {
|
||||||
void onControllerStateUpdate(Controller controller);
|
void onControllerStateUpdate(Controller controller);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface ControllerBindRegistry {
|
||||||
|
void onRegisterControllerBinds(ControllerBindings bindings);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package dev.isxander.controlify.ingame;
|
package dev.isxander.controlify.ingame;
|
||||||
|
|
||||||
import dev.isxander.controlify.controller.Controller;
|
import dev.isxander.controlify.controller.Controller;
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
import net.minecraft.client.player.Input;
|
import net.minecraft.client.player.Input;
|
||||||
|
|
||||||
public class ControllerPlayerMovement extends Input {
|
public class ControllerPlayerMovement extends Input {
|
||||||
@ -12,6 +13,18 @@ public class ControllerPlayerMovement extends Input {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void tick(boolean slowDown, float f) {
|
public void tick(boolean slowDown, float f) {
|
||||||
|
if (Minecraft.getInstance().screen != null) {
|
||||||
|
this.up = false;
|
||||||
|
this.down = false;
|
||||||
|
this.left = false;
|
||||||
|
this.right = false;
|
||||||
|
this.leftImpulse = 0;
|
||||||
|
this.forwardImpulse = 0;
|
||||||
|
this.jumping = false;
|
||||||
|
this.shiftKeyDown = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var axes = controller.state().axes();
|
var axes = controller.state().axes();
|
||||||
|
|
||||||
this.up = axes.leftStickY() < 0;
|
this.up = axes.leftStickY() < 0;
|
||||||
|
@ -5,6 +5,7 @@ import dev.isxander.controlify.InputMode;
|
|||||||
import dev.isxander.controlify.controller.Controller;
|
import dev.isxander.controlify.controller.Controller;
|
||||||
import dev.isxander.controlify.event.ControlifyEvents;
|
import dev.isxander.controlify.event.ControlifyEvents;
|
||||||
import net.minecraft.client.Minecraft;
|
import net.minecraft.client.Minecraft;
|
||||||
|
import net.minecraft.client.gui.screens.PauseScreen;
|
||||||
import net.minecraft.client.player.KeyboardInput;
|
import net.minecraft.client.player.KeyboardInput;
|
||||||
|
|
||||||
public class InGameInputHandler {
|
public class InGameInputHandler {
|
||||||
@ -35,6 +36,18 @@ public class InGameInputHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
processPlayerLook();
|
processPlayerLook();
|
||||||
|
|
||||||
|
if (controller.bindings().PAUSE.justPressed()) {
|
||||||
|
minecraft.pauseGame(false);
|
||||||
|
}
|
||||||
|
if (minecraft.player != null) {
|
||||||
|
if (controller.bindings().NEXT_SLOT.justPressed()) {
|
||||||
|
minecraft.player.getInventory().swapPaint(-1);
|
||||||
|
}
|
||||||
|
if (controller.bindings().PREV_SLOT.justPressed()) {
|
||||||
|
minecraft.player.getInventory().swapPaint(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void processPlayerLook() {
|
public void processPlayerLook() {
|
||||||
|
@ -0,0 +1,12 @@
|
|||||||
|
package dev.isxander.controlify.mixins;
|
||||||
|
|
||||||
|
import com.mojang.blaze3d.platform.InputConstants;
|
||||||
|
import net.minecraft.client.KeyMapping;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||||
|
|
||||||
|
@Mixin(KeyMapping.class)
|
||||||
|
public interface KeyMappingAccessor {
|
||||||
|
@Accessor
|
||||||
|
InputConstants.Key getKey();
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"type": "directory",
|
||||||
|
"source": "gui/buttons",
|
||||||
|
"prefix": "gui/buttons/"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
13
src/main/resources/assets/controlify/lang/en_us.json
Normal file
13
src/main/resources/assets/controlify/lang/en_us.json
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"controlify.gui.group.config": "Config",
|
||||||
|
"controlify.gui.group.config.tooltip": "Adjust the controller configuration.",
|
||||||
|
"controlify.gui.left_stick_deadzone": "Left Stick Deadzone",
|
||||||
|
"controlify.gui.left_stick_deadzone.tooltip": "How far the left joystick needs to be pushed before registering input.",
|
||||||
|
"controlify.gui.right_stick_deadzone": "Right Stick Deadzone",
|
||||||
|
"controlify.gui.right_stick_deadzone.tooltip": "How far the right joystick needs to be pushed before registering input.",
|
||||||
|
"controlify.gui.stickdrift_warning": "Warning: Setting this too low will cause stickdrift! This is where the internals of your controller become mis-calibrated and register small amounts of input when there shouldn't be.",
|
||||||
|
"controlify.gui.left_trigger_threshold": "Left Trigger Threshold",
|
||||||
|
"controlify.gui.left_trigger_threshold.tooltip": "How far the left trigger needs to be pushed before registering as pressed.",
|
||||||
|
"controlify.gui.right_trigger_threshold": "Right Trigger Threshold",
|
||||||
|
"controlify.gui.right_trigger_threshold.tooltip": "How far the right trigger needs to be pushed before registering as pressed."
|
||||||
|
}
|
@ -9,6 +9,7 @@
|
|||||||
"AbstractSliderButtonMixin",
|
"AbstractSliderButtonMixin",
|
||||||
"ClientPacketListenerMixin",
|
"ClientPacketListenerMixin",
|
||||||
"KeyboardHandlerMixin",
|
"KeyboardHandlerMixin",
|
||||||
|
"KeyMappingAccessor",
|
||||||
"MinecraftMixin",
|
"MinecraftMixin",
|
||||||
"MouseHandlerMixin",
|
"MouseHandlerMixin",
|
||||||
"ScreenAccessor",
|
"ScreenAccessor",
|
||||||
|
@ -17,6 +17,9 @@
|
|||||||
"entrypoints": {
|
"entrypoints": {
|
||||||
"preLaunch": [
|
"preLaunch": [
|
||||||
"com.llamalad7.mixinextras.MixinExtrasBootstrap::init"
|
"com.llamalad7.mixinextras.MixinExtrasBootstrap::init"
|
||||||
|
],
|
||||||
|
"modmenu": [
|
||||||
|
"dev.isxander.controlify.config.gui.ModMenuIntegration"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"mixins": [
|
"mixins": [
|
||||||
|
Reference in New Issue
Block a user