diff --git a/build.gradle.kts b/build.gradle.kts index b4f690b..21d536f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -13,12 +13,13 @@ plugins { } group = "dev.isxander" -version = "1.0.0+1.19.3" +version = "1.0.0+1.19.4" repositories { mavenCentral() maven("https://maven.terraformersmc.com") maven("https://maven.isxander.dev/releases") + maven("https://maven.isxander.dev/snapshots") maven("https://maven.quiltmc.org/repository/release") maven("https://api.modrinth.com/maven") { name = "Modrinth" @@ -40,6 +41,8 @@ dependencies { modImplementation(libs.fabric.loader) modImplementation(libs.fabric.api) + modImplementation(libs.yet.another.config.lib) + modImplementation(libs.mod.menu) implementation(libs.mixin.extras) annotationProcessor(libs.mixin.extras) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 96d9c06..e04dfa8 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -12,6 +12,8 @@ quilt_mappings = "10" fabric_loader = "0.14.13" fabric_api = "0.73.1+1.19.4" 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] 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" } 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] loom = { id = "fabric-loom", version.ref = "loom" } diff --git a/src/main/java/dev/isxander/controlify/Controlify.java b/src/main/java/dev/isxander/controlify/Controlify.java index 70c8739..93987b9 100644 --- a/src/main/java/dev/isxander/controlify/Controlify.java +++ b/src/main/java/dev/isxander/controlify/Controlify.java @@ -1,6 +1,7 @@ package dev.isxander.controlify; import dev.isxander.controlify.compatibility.screen.ScreenProcessorProvider; +import dev.isxander.controlify.config.ControlifyConfig; import dev.isxander.controlify.controller.Controller; import dev.isxander.controlify.controller.ControllerState; import dev.isxander.controlify.event.ControlifyEvents; @@ -26,6 +27,9 @@ public class Controlify { } } + // load after initial controller discovery + ControlifyConfig.load(); + // listen for new controllers GLFW.glfwSetJoystickCallback((jid, event) -> { System.out.println("Event: " + event); @@ -33,6 +37,9 @@ public class Controlify { setCurrentController(Controller.byId(jid)); System.out.println("Connected: " + currentController.name()); 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) { Controller.CONTROLLERS.remove(jid); 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()) { controller.updateState(); } diff --git a/src/main/java/dev/isxander/controlify/bindings/Bind.java b/src/main/java/dev/isxander/controlify/bindings/Bind.java index 6ad0ff9..3a6fd61 100644 --- a/src/main/java/dev/isxander/controlify/bindings/Bind.java +++ b/src/main/java/dev/isxander/controlify/bindings/Bind.java @@ -1,37 +1,60 @@ package dev.isxander.controlify.bindings; +import dev.isxander.controlify.controller.Controller; import dev.isxander.controlify.controller.ControllerState; +import net.minecraft.resources.ResourceLocation; -@FunctionalInterface -public interface Bind { - boolean state(ControllerState controllerState); +import java.util.function.BiFunction; +import java.util.function.Function; - Bind A_BUTTON = state -> state.buttons().a(); - Bind B_BUTTON = state -> state.buttons().b(); - Bind X_BUTTON = state -> state.buttons().x(); - Bind Y_BUTTON = state -> state.buttons().y(); - Bind LEFT_BUMPER = state -> state.buttons().leftBumper(); - Bind RIGHT_BUMPER = state -> state.buttons().rightBumper(); - Bind LEFT_STICK = state -> state.buttons().leftStick(); - Bind RIGHT_STICK = state -> state.buttons().rightStick(); - Bind START = state -> state.buttons().start(); - Bind BACK = state -> state.buttons().back(); - Bind LEFT_TRIGGER = leftTrigger(0.5f); - Bind RIGHT_TRIGGER = rightTrigger(0.5f); +public enum Bind { + A_BUTTON(state -> state.buttons().a(), "a_button"), + B_BUTTON(state -> state.buttons().b(), "b_button"), + X_BUTTON(state -> state.buttons().x(), "x_button"), + Y_BUTTON(state -> state.buttons().y(), "y_button"), + LEFT_BUMPER(state -> state.buttons().leftBumper(), "left_bumper"), + RIGHT_BUMPER(state -> state.buttons().rightBumper(), "right_bumper"), + LEFT_STICK(state -> state.buttons().leftStick(), "left_stick"), + RIGHT_STICK(state -> state.buttons().rightStick(), "right_stick"), + START(state -> state.buttons().start(), "start"), + BACK(state -> state.buttons().back(), "back"), + 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 = { - A_BUTTON, B_BUTTON, X_BUTTON, Y_BUTTON, - LEFT_BUMPER, RIGHT_BUMPER, - LEFT_STICK, RIGHT_STICK, - START, BACK, - LEFT_TRIGGER, RIGHT_TRIGGER - }; + private final BiFunction state; + private final String identifier; + private final ResourceLocation textureLocation; - static Bind leftTrigger(float threshold) { - return state -> state.axes().leftTrigger() > threshold; + Bind(BiFunction state, String identifier) { + this.state = state; + this.identifier = identifier; + this.textureLocation = new ResourceLocation("controlify", "textures/gui/buttons/" + identifier + ".png"); } - static Bind rightTrigger(float threshold) { - return state -> state.axes().rightTrigger() > threshold; + Bind(Function state, String identifier) { + 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; } } diff --git a/src/main/java/dev/isxander/controlify/bindings/ControllerBinding.java b/src/main/java/dev/isxander/controlify/bindings/ControllerBinding.java index bba9edb..4b47b97 100644 --- a/src/main/java/dev/isxander/controlify/bindings/ControllerBinding.java +++ b/src/main/java/dev/isxander/controlify/bindings/ControllerBinding.java @@ -1,34 +1,56 @@ package dev.isxander.controlify.bindings; import dev.isxander.controlify.controller.Controller; +import net.minecraft.client.KeyMapping; import net.minecraft.network.chat.Component; public class ControllerBinding { 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 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.bind = defaultBind; + this.bind = this.defaultBind = defaultBind; + this.id = id; this.name = Component.translatable("controlify.binding." + id); this.description = description; + this.override = override; } - public ControllerBinding(Controller controller, Bind defaultBind, String id) { - this(controller, defaultBind, id, Component.empty()); + public ControllerBinding(Controller controller, Bind defaultBind, String id, KeyMapping override) { + this(controller, defaultBind, id, Component.empty(), override); } public boolean held() { - return bind.state(controller.state()); + return bind.state(controller.state(), controller); } public boolean justPressed() { - return held() && !bind.state(controller.prevState()); + return held() && !bind.state(controller.prevState(), controller); } 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() { @@ -38,4 +60,8 @@ public class ControllerBinding { public Component description() { return description; } + + public KeyMapping override() { + return override; + } } diff --git a/src/main/java/dev/isxander/controlify/bindings/ControllerBindings.java b/src/main/java/dev/isxander/controlify/bindings/ControllerBindings.java index 1a6c82f..637c5f9 100644 --- a/src/main/java/dev/isxander/controlify/bindings/ControllerBindings.java +++ b/src/main/java/dev/isxander/controlify/bindings/ControllerBindings.java @@ -1,22 +1,75 @@ package dev.isxander.controlify.bindings; +import com.google.gson.JsonObject; 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 final ControllerBinding JUMP, SNEAK, ATTACK, USE, SPRINT, NEXT_SLOT, PREV_SLOT; - public final ControllerBinding[] ALL; + public final ControllerBinding JUMP, SNEAK, ATTACK, USE, SPRINT, NEXT_SLOT, PREV_SLOT, PAUSE, INVENTORY, CHANGE_PERSPECTIVE, OPEN_CHAT; + + private final List registry = new ArrayList<>(); public ControllerBindings(Controller controller) { - JUMP = new ControllerBinding(controller, Bind.A_BUTTON, "jump"); - 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"); + var options = Minecraft.getInstance().options; - ALL = new ControllerBinding[] { - JUMP, SNEAK, ATTACK, USE, SPRINT, NEXT_SLOT, PREV_SLOT - }; + JUMP = register(new ControllerBinding(controller, Bind.A_BUTTON, "jump", options.keyJump)); + 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 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); + } } } diff --git a/src/main/java/dev/isxander/controlify/config/ControlifyConfig.java b/src/main/java/dev/isxander/controlify/config/ControlifyConfig.java new file mode 100644 index 0000000..e517643 --- /dev/null +++ b/src/main/java/dev/isxander/controlify/config/ControlifyConfig.java @@ -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")); + } +} diff --git a/src/main/java/dev/isxander/controlify/config/gui/ModMenuIntegration.java b/src/main/java/dev/isxander/controlify/config/gui/ModMenuIntegration.java new file mode 100644 index 0000000..2b8f762 --- /dev/null +++ b/src/main/java/dev/isxander/controlify/config/gui/ModMenuIntegration.java @@ -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; + } +} diff --git a/src/main/java/dev/isxander/controlify/config/gui/YACLHelper.java b/src/main/java/dev/isxander/controlify/config/gui/YACLHelper.java new file mode 100644 index 0000000..eac7783 --- /dev/null +++ b/src/main/java/dev/isxander/controlify/config/gui/YACLHelper.java @@ -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); + } +} diff --git a/src/main/java/dev/isxander/controlify/controller/Controller.java b/src/main/java/dev/isxander/controlify/controller/Controller.java index ae864d5..b43fe47 100644 --- a/src/main/java/dev/isxander/controlify/controller/Controller.java +++ b/src/main/java/dev/isxander/controlify/controller/Controller.java @@ -21,6 +21,7 @@ public final class Controller { private ControllerState prevState = ControllerState.EMPTY; private final ControllerBindings bindings = new ControllerBindings(this); + private final ControllerConfig config = new ControllerConfig(); public Controller(int id, String guid, String name, boolean gamepad) { this.id = id; @@ -46,10 +47,10 @@ public final class Controller { prevState = state; AxesState axesState = AxesState.fromController(this) - .leftJoystickDeadZone(0.2f, 0.2f) - .rightJoystickDeadZone(0.2f, 0.2f) - .leftTriggerDeadZone(0.1f) - .rightTriggerDeadZone(0.1f); + .leftJoystickDeadZone(config().leftStickDeadzone, config().leftStickDeadzone) + .rightJoystickDeadZone(config().rightStickDeadzone, config().rightStickDeadzone) + .leftTriggerDeadZone(config().leftTriggerDeadzone) + .rightTriggerDeadZone(config().rightTriggerDeadzone); ButtonState buttonState = ButtonState.fromController(this); state = new ControllerState(axesState, buttonState); @@ -87,15 +88,16 @@ public final class Controller { return gamepad; } + public ControllerConfig config() { + return config; + } + @Override public boolean equals(Object obj) { if (obj == this) return true; if (obj == null || obj.getClass() != this.getClass()) return false; var that = (Controller) obj; - return this.id == that.id && - Objects.equals(this.guid, that.guid) && - Objects.equals(this.name, that.name) && - this.gamepad == that.gamepad; + return Objects.equals(this.guid, that.guid); } @Override @@ -103,13 +105,6 @@ public final class Controller { return Objects.hash(guid); } - @Override - public String toString() { - return "Controller[" + - "id=" + id + ", " + - "name=" + name + ']'; - } - public static Controller byId(int id) { if (id > GLFW.GLFW_JOYSTICK_LAST) throw new IllegalArgumentException("Invalid joystick id: " + id); diff --git a/src/main/java/dev/isxander/controlify/controller/ControllerConfig.java b/src/main/java/dev/isxander/controlify/controller/ControllerConfig.java new file mode 100644 index 0000000..ba15e18 --- /dev/null +++ b/src/main/java/dev/isxander/controlify/controller/ControllerConfig.java @@ -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; + } +} diff --git a/src/main/java/dev/isxander/controlify/event/ControlifyEvents.java b/src/main/java/dev/isxander/controlify/event/ControlifyEvents.java index 5fa27f4..6ac0a53 100644 --- a/src/main/java/dev/isxander/controlify/event/ControlifyEvents.java +++ b/src/main/java/dev/isxander/controlify/event/ControlifyEvents.java @@ -1,6 +1,7 @@ package dev.isxander.controlify.event; import dev.isxander.controlify.InputMode; +import dev.isxander.controlify.bindings.ControllerBindings; import dev.isxander.controlify.controller.Controller; import net.fabricmc.fabric.api.event.Event; import net.fabricmc.fabric.api.event.EventFactory; @@ -18,6 +19,12 @@ public class ControlifyEvents { } }); + public static final Event CONTROLLER_BIND_REGISTRY = EventFactory.createArrayBacked(ControllerBindRegistry.class, callbacks -> bindings -> { + for (ControllerBindRegistry callback : callbacks) { + callback.onRegisterControllerBinds(bindings); + } + }); + @FunctionalInterface public interface InputModeChanged { void onInputModeChanged(InputMode mode); @@ -27,4 +34,9 @@ public class ControlifyEvents { public interface ControllerStateUpdate { void onControllerStateUpdate(Controller controller); } + + @FunctionalInterface + public interface ControllerBindRegistry { + void onRegisterControllerBinds(ControllerBindings bindings); + } } diff --git a/src/main/java/dev/isxander/controlify/ingame/ControllerPlayerMovement.java b/src/main/java/dev/isxander/controlify/ingame/ControllerPlayerMovement.java index a902662..b4ddfc0 100644 --- a/src/main/java/dev/isxander/controlify/ingame/ControllerPlayerMovement.java +++ b/src/main/java/dev/isxander/controlify/ingame/ControllerPlayerMovement.java @@ -1,6 +1,7 @@ package dev.isxander.controlify.ingame; import dev.isxander.controlify.controller.Controller; +import net.minecraft.client.Minecraft; import net.minecraft.client.player.Input; public class ControllerPlayerMovement extends Input { @@ -12,6 +13,18 @@ public class ControllerPlayerMovement extends Input { @Override 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(); this.up = axes.leftStickY() < 0; diff --git a/src/main/java/dev/isxander/controlify/ingame/InGameInputHandler.java b/src/main/java/dev/isxander/controlify/ingame/InGameInputHandler.java index aad1da3..c573707 100644 --- a/src/main/java/dev/isxander/controlify/ingame/InGameInputHandler.java +++ b/src/main/java/dev/isxander/controlify/ingame/InGameInputHandler.java @@ -5,6 +5,7 @@ import dev.isxander.controlify.InputMode; import dev.isxander.controlify.controller.Controller; import dev.isxander.controlify.event.ControlifyEvents; import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.screens.PauseScreen; import net.minecraft.client.player.KeyboardInput; public class InGameInputHandler { @@ -35,6 +36,18 @@ public class InGameInputHandler { } 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() { diff --git a/src/main/java/dev/isxander/controlify/mixins/KeyMappingAccessor.java b/src/main/java/dev/isxander/controlify/mixins/KeyMappingAccessor.java new file mode 100644 index 0000000..567e610 --- /dev/null +++ b/src/main/java/dev/isxander/controlify/mixins/KeyMappingAccessor.java @@ -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(); +} diff --git a/src/main/resources/assets/controlify/atlases/buttons.json b/src/main/resources/assets/controlify/atlases/buttons.json new file mode 100644 index 0000000..d10a89e --- /dev/null +++ b/src/main/resources/assets/controlify/atlases/buttons.json @@ -0,0 +1,9 @@ +{ + "sources": [ + { + "type": "directory", + "source": "gui/buttons", + "prefix": "gui/buttons/" + } + ] +} diff --git a/src/main/resources/assets/controlify/lang/en_us.json b/src/main/resources/assets/controlify/lang/en_us.json new file mode 100644 index 0000000..fd538bf --- /dev/null +++ b/src/main/resources/assets/controlify/lang/en_us.json @@ -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." +} diff --git a/src/main/resources/controlify.mixins.json b/src/main/resources/controlify.mixins.json index d3ac661..ffdb4b0 100644 --- a/src/main/resources/controlify.mixins.json +++ b/src/main/resources/controlify.mixins.json @@ -9,6 +9,7 @@ "AbstractSliderButtonMixin", "ClientPacketListenerMixin", "KeyboardHandlerMixin", + "KeyMappingAccessor", "MinecraftMixin", "MouseHandlerMixin", "ScreenAccessor", diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index d104f32..dd103aa 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -17,6 +17,9 @@ "entrypoints": { "preLaunch": [ "com.llamalad7.mixinextras.MixinExtrasBootstrap::init" + ], + "modmenu": [ + "dev.isxander.controlify.config.gui.ModMenuIntegration" ] }, "mixins": [