compound binds, complete vanilla compat, better vmouse screen handling, controller uuid, beta notice screen, configurable movement keys, vmouse shift key, icon, optimize controller save/load
2
.github/README.md
vendored
@ -1,6 +1,6 @@
|
|||||||
<div align="center">
|
<div align="center">
|
||||||
|
|
||||||
<img src="https://raw.githubusercontent.com/isXander/FabricModTemplate/1.19/src/main/resources/icon.png" alt="Icon">
|
<img src="https://raw.githubusercontent.com/isXander/Controlify/1.19.x/dev/src/main/resources/icon.png" alt="Icon">
|
||||||
|
|
||||||
# Control-ify
|
# Control-ify
|
||||||
|
|
||||||
|
BIN
assets/icon.psd
Normal file
@ -30,6 +30,10 @@ repositories {
|
|||||||
maven("https://jitpack.io")
|
maven("https://jitpack.io")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
loom {
|
||||||
|
accessWidenerPath.set(file("src/main/resources/controlify.accesswidener"))
|
||||||
|
}
|
||||||
|
|
||||||
val minecraftVersion = libs.versions.minecraft.get()
|
val minecraftVersion = libs.versions.minecraft.get()
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
@ -52,10 +56,6 @@ dependencies {
|
|||||||
include(libs.hid4java)
|
include(libs.hid4java)
|
||||||
}
|
}
|
||||||
|
|
||||||
machete {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
tasks {
|
tasks {
|
||||||
processResources {
|
processResources {
|
||||||
val modId: String by project
|
val modId: String by project
|
||||||
|
@ -9,8 +9,8 @@ grgit = "5.0.+"
|
|||||||
|
|
||||||
minecraft = "23w05a"
|
minecraft = "23w05a"
|
||||||
quilt_mappings = "1"
|
quilt_mappings = "1"
|
||||||
fabric_loader = "0.14.13"
|
fabric_loader = "0.14.14"
|
||||||
fabric_api = "0.73.3+1.19.4"
|
fabric_api = "0.73.4+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"
|
yet_another_config_lib = "2.2.0+update.1.19.4-SNAPSHOT"
|
||||||
mod_menu = "6.0.0-beta.1"
|
mod_menu = "6.0.0-beta.1"
|
||||||
|
@ -36,37 +36,36 @@ public class Controlify {
|
|||||||
controllerHIDService = new ControllerHIDService();
|
controllerHIDService = new ControllerHIDService();
|
||||||
|
|
||||||
// find already connected controllers
|
// find already connected controllers
|
||||||
for (int i = 0; i < GLFW.GLFW_JOYSTICK_LAST; i++) {
|
for (int i = 0; i <= GLFW.GLFW_JOYSTICK_LAST; i++) {
|
||||||
if (GLFW.glfwJoystickPresent(i)) {
|
if (GLFW.glfwJoystickPresent(i)) {
|
||||||
int jid = i;
|
int jid = i;
|
||||||
controllerHIDService.awaitNextDevice(device -> {
|
controllerHIDService.awaitNextController(device -> {
|
||||||
setCurrentController(Controller.create(jid, device));
|
setCurrentController(Controller.create(jid, device));
|
||||||
LOGGER.info("Controller found: " + currentController.name());
|
LOGGER.info("Controller found: " + currentController.name());
|
||||||
|
config().loadOrCreateControllerData(currentController);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
controllerHIDService.start();
|
controllerHIDService.start();
|
||||||
|
|
||||||
config().load(); // load after initial controller discovery
|
config().load();
|
||||||
config().save(); // save new controller configs if they don't exist
|
|
||||||
|
|
||||||
// listen for new controllers
|
// listen for new controllers
|
||||||
GLFW.glfwSetJoystickCallback((jid, event) -> {
|
GLFW.glfwSetJoystickCallback((jid, event) -> {
|
||||||
if (event == GLFW.GLFW_CONNECTED) {
|
if (event == GLFW.GLFW_CONNECTED) {
|
||||||
controllerHIDService.awaitNextDevice(device -> {
|
controllerHIDService.awaitNextController(device -> {
|
||||||
setCurrentController(Controller.create(jid, device));
|
setCurrentController(Controller.create(jid, device));
|
||||||
LOGGER.info("Controller connected: " + currentController.name() + " (" + device.getPath() + ")");
|
LOGGER.info("Controller connected: " + currentController.name());
|
||||||
this.setCurrentInputMode(InputMode.CONTROLLER);
|
this.setCurrentInputMode(InputMode.CONTROLLER);
|
||||||
|
|
||||||
config().load(); // load config again if a configuration already exists for this controller
|
config().loadOrCreateControllerData(currentController);
|
||||||
config().save(); // save config if it doesn't exist
|
|
||||||
|
|
||||||
minecraft.getToasts().addToast(SystemToast.multiline(
|
minecraft.getToasts().addToast(SystemToast.multiline(
|
||||||
minecraft,
|
minecraft,
|
||||||
SystemToast.SystemToastIds.PERIODIC_NOTIFICATION,
|
SystemToast.SystemToastIds.PERIODIC_NOTIFICATION,
|
||||||
Component.translatable("controlify.toast.controller_connected.title"),
|
Component.translatable("controlify.toast.controller_connected.title"),
|
||||||
Component.translatable("controlify.toast.controller_connected.description")
|
Component.translatable("controlify.toast.controller_connected.description", currentController.name())
|
||||||
));
|
));
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -98,6 +97,8 @@ public class Controlify {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ControllerState state = currentController == null ? ControllerState.EMPTY : currentController.state();
|
ControllerState state = currentController == null ? ControllerState.EMPTY : currentController.state();
|
||||||
|
if (!config().globalSettings().outOfFocusInput && !client.isWindowActive())
|
||||||
|
state = ControllerState.EMPTY;
|
||||||
|
|
||||||
if (state.hasAnyInput())
|
if (state.hasAnyInput())
|
||||||
this.setCurrentInputMode(InputMode.CONTROLLER);
|
this.setCurrentInputMode(InputMode.CONTROLLER);
|
||||||
@ -108,12 +109,13 @@ public class Controlify {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (client.screen != null) {
|
if (client.screen != null) {
|
||||||
if (!this.virtualMouseHandler().isVirtualMouseEnabled())
|
ScreenProcessorProvider.provide(client.screen).onControllerUpdate(currentController);
|
||||||
ScreenProcessorProvider.provide(client.screen).onControllerUpdate(currentController);
|
|
||||||
} else {
|
} else {
|
||||||
this.inGameInputHandler().inputTick();
|
this.inGameInputHandler().inputTick();
|
||||||
}
|
}
|
||||||
this.virtualMouseHandler().handleControllerInput(currentController);
|
this.virtualMouseHandler().handleControllerInput(currentController);
|
||||||
|
|
||||||
|
ControlifyEvents.CONTROLLER_STATE_UPDATED.invoker().onControllerStateUpdate(currentController);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ControlifyConfig config() {
|
public ControlifyConfig config() {
|
||||||
|
@ -1,21 +1,25 @@
|
|||||||
package dev.isxander.controlify.bindings;
|
package dev.isxander.controlify.bindings;
|
||||||
|
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonPrimitive;
|
||||||
|
import com.mojang.blaze3d.vertex.PoseStack;
|
||||||
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.gui.ButtonRenderer;
|
||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
|
||||||
import java.util.function.BiFunction;
|
import java.util.function.BiFunction;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
public enum Bind {
|
public enum Bind implements IBind {
|
||||||
A_BUTTON(state -> state.buttons().a(), "a_button"),
|
A_BUTTON(state -> state.buttons().a(), "a_button"),
|
||||||
B_BUTTON(state -> state.buttons().b(), "b_button"),
|
B_BUTTON(state -> state.buttons().b(), "b_button"),
|
||||||
X_BUTTON(state -> state.buttons().x(), "x_button"),
|
X_BUTTON(state -> state.buttons().x(), "x_button"),
|
||||||
Y_BUTTON(state -> state.buttons().y(), "y_button"),
|
Y_BUTTON(state -> state.buttons().y(), "y_button"),
|
||||||
LEFT_BUMPER(state -> state.buttons().leftBumper(), "left_bumper"),
|
LEFT_BUMPER(state -> state.buttons().leftBumper(), "left_bumper"),
|
||||||
RIGHT_BUMPER(state -> state.buttons().rightBumper(), "right_bumper"),
|
RIGHT_BUMPER(state -> state.buttons().rightBumper(), "right_bumper"),
|
||||||
LEFT_STICK(state -> state.buttons().leftStick(), "left_stick"),
|
LEFT_STICK_PRESS(state -> state.buttons().leftStick(), "left_stick_press"),
|
||||||
RIGHT_STICK(state -> state.buttons().rightStick(), "right_stick"),
|
RIGHT_STICK_PRESS(state -> state.buttons().rightStick(), "right_stick_press"),
|
||||||
START(state -> state.buttons().start(), "start"),
|
START(state -> state.buttons().start(), "start"),
|
||||||
BACK(state -> state.buttons().back(), "back"),
|
BACK(state -> state.buttons().back(), "back"),
|
||||||
GUIDE(state -> state.buttons().guide(), "guide"), // the middle button
|
GUIDE(state -> state.buttons().guide(), "guide"), // the middle button
|
||||||
@ -23,23 +27,42 @@ public enum Bind {
|
|||||||
DPAD_DOWN(state -> state.buttons().dpadDown(), "dpad_down"),
|
DPAD_DOWN(state -> state.buttons().dpadDown(), "dpad_down"),
|
||||||
DPAD_LEFT(state -> state.buttons().dpadLeft(), "dpad_left"),
|
DPAD_LEFT(state -> state.buttons().dpadLeft(), "dpad_left"),
|
||||||
DPAD_RIGHT(state -> state.buttons().dpadRight(), "dpad_right"),
|
DPAD_RIGHT(state -> state.buttons().dpadRight(), "dpad_right"),
|
||||||
LEFT_TRIGGER((state, controller) -> state.axes().leftTrigger() >= controller.config().leftTriggerActivationThreshold, "left_trigger"),
|
LEFT_TRIGGER((state, controller) -> state.axes().leftTrigger(), "left_trigger"),
|
||||||
RIGHT_TRIGGER((state, controller) -> state.axes().rightTrigger() >= controller.config().rightTriggerActivationThreshold, "right_trigger");
|
RIGHT_TRIGGER((state, controller) -> state.axes().rightTrigger(), "right_trigger"),
|
||||||
|
LEFT_STICK_FORWARD((state, controller) -> -Math.min(0, state.axes().leftStickY()), "left_stick_up"),
|
||||||
|
LEFT_STICK_BACKWARD((state, controller) -> Math.max(0, state.axes().leftStickY()), "left_stick_down"),
|
||||||
|
LEFT_STICK_LEFT((state, controller) -> -Math.min(0, state.axes().leftStickX()), "left_stick_left"),
|
||||||
|
LEFT_STICK_RIGHT((state, controller) -> Math.max(0, state.axes().leftStickX()), "left_stick_right"),
|
||||||
|
RIGHT_STICK_FORWARD((state, controller) -> -Math.min(0, state.axes().rightStickY()), "right_stick_up"),
|
||||||
|
RIGHT_STICK_BACKWARD((state, controller) -> Math.max(0, state.axes().rightStickY()), "right_stick_down"),
|
||||||
|
RIGHT_STICK_LEFT((state, controller) -> -Math.min(0, state.axes().rightStickX()), "right_stick_left"),
|
||||||
|
RIGHT_STICK_RIGHT((state, controller) -> Math.max(0, state.axes().rightStickX()), "right_stick_right");
|
||||||
|
|
||||||
private final BiFunction<ControllerState, Controller, Boolean> state;
|
private final BiFunction<ControllerState, Controller, Float> state;
|
||||||
private final String identifier;
|
private final String identifier;
|
||||||
|
|
||||||
Bind(BiFunction<ControllerState, Controller, Boolean> state, String identifier) {
|
Bind(BiFunction<ControllerState, Controller, Float> state, String identifier) {
|
||||||
this.state = state;
|
this.state = state;
|
||||||
this.identifier = identifier;
|
this.identifier = identifier;
|
||||||
}
|
}
|
||||||
|
|
||||||
Bind(Function<ControllerState, Boolean> state, String identifier) {
|
Bind(Function<ControllerState, Boolean> state, String identifier) {
|
||||||
this((state1, controller) -> state.apply(state1), identifier);
|
this((state1, controller) -> state.apply(state1) ? 1f : 0f, identifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean state(ControllerState controllerState, Controller controller) {
|
@Override
|
||||||
return state.apply(controllerState, controller);
|
public float state(ControllerState state, Controller controller) {
|
||||||
|
return this.state.apply(state, controller);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void draw(PoseStack matrices, int x, int centerY, Controller controller) {
|
||||||
|
ButtonRenderer.drawButton(this, controller, matrices, x, centerY);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ButtonRenderer.DrawSize drawSize() {
|
||||||
|
return new ButtonRenderer.DrawSize(22, 22);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String identifier() {
|
public String identifier() {
|
||||||
@ -50,6 +73,11 @@ public enum Bind {
|
|||||||
return new ResourceLocation("controlify", "textures/gui/buttons/" + controller.config().theme.id() + "/" + identifier + ".png");
|
return new ResourceLocation("controlify", "textures/gui/buttons/" + controller.config().theme.id() + "/" + identifier + ".png");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JsonElement toJson() {
|
||||||
|
return new JsonPrimitive(identifier);
|
||||||
|
}
|
||||||
|
|
||||||
public static Bind fromIdentifier(String identifier) {
|
public static Bind fromIdentifier(String identifier) {
|
||||||
for (Bind bind : values()) {
|
for (Bind bind : values()) {
|
||||||
if (bind.identifier.equals(identifier)) return bind;
|
if (bind.identifier.equals(identifier)) return bind;
|
||||||
|
@ -0,0 +1,77 @@
|
|||||||
|
package dev.isxander.controlify.bindings;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableSet;
|
||||||
|
import com.google.gson.JsonArray;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.mojang.blaze3d.vertex.PoseStack;
|
||||||
|
import dev.isxander.controlify.controller.Controller;
|
||||||
|
import dev.isxander.controlify.controller.ControllerState;
|
||||||
|
import dev.isxander.controlify.gui.ButtonRenderer;
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public class CompoundBind implements IBind {
|
||||||
|
private final Set<Bind> binds;
|
||||||
|
|
||||||
|
CompoundBind(Bind... binds) {
|
||||||
|
this.binds = new LinkedHashSet<>(Arrays.asList(binds));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<Bind> binds() {
|
||||||
|
return ImmutableSet.copyOf(binds);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float state(ControllerState state, Controller controller) {
|
||||||
|
return held(state, controller) ? 1f : 0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean held(ControllerState state, Controller controller) {
|
||||||
|
return binds.stream().allMatch(bind -> bind.held(state, controller));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void draw(PoseStack matrices, int x, int centerY, Controller controller) {
|
||||||
|
var font = Minecraft.getInstance().font;
|
||||||
|
|
||||||
|
var iterator = binds.iterator();
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
var bind = iterator.next();
|
||||||
|
|
||||||
|
bind.draw(matrices, x, centerY, controller);
|
||||||
|
x += bind.drawSize().width();
|
||||||
|
|
||||||
|
if (iterator.hasNext()) {
|
||||||
|
font.drawShadow(matrices, "+", x + 1, centerY - font.lineHeight / 2f, 0xFFFFFF);
|
||||||
|
x += font.width("+") + 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ButtonRenderer.DrawSize drawSize() {
|
||||||
|
return new ButtonRenderer.DrawSize(
|
||||||
|
binds.stream().map(IBind::drawSize).mapToInt(ButtonRenderer.DrawSize::width).sum() + (binds.size() - 1) * (2 + Minecraft.getInstance().font.width("+")),
|
||||||
|
binds.stream().map(IBind::drawSize).mapToInt(ButtonRenderer.DrawSize::height).max().orElse(0)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JsonElement toJson() {
|
||||||
|
var list = new JsonArray();
|
||||||
|
for (IBind bind : binds) {
|
||||||
|
list.add(bind.toJson());
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
return obj instanceof CompoundBind compoundBind && compoundBind.binds.equals(binds)
|
||||||
|
|| obj instanceof Bind bind && Set.of(bind).equals(binds);
|
||||||
|
}
|
||||||
|
}
|
@ -1,52 +1,76 @@
|
|||||||
package dev.isxander.controlify.bindings;
|
package dev.isxander.controlify.bindings;
|
||||||
|
|
||||||
import dev.isxander.controlify.controller.Controller;
|
import dev.isxander.controlify.controller.Controller;
|
||||||
|
import dev.isxander.controlify.controller.ControllerState;
|
||||||
import net.minecraft.client.KeyMapping;
|
import net.minecraft.client.KeyMapping;
|
||||||
|
import net.minecraft.locale.Language;
|
||||||
import net.minecraft.network.chat.Component;
|
import net.minecraft.network.chat.Component;
|
||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
public class ControllerBinding {
|
public class ControllerBinding {
|
||||||
private final Controller controller;
|
private final Controller controller;
|
||||||
private Bind bind;
|
private IBind bind;
|
||||||
private final Bind defaultBind;
|
private final IBind defaultBind;
|
||||||
private final ResourceLocation id;
|
private final ResourceLocation id;
|
||||||
private final Component name, description;
|
private final Component name, description;
|
||||||
private final KeyMapping override;
|
private final KeyMapping override;
|
||||||
|
|
||||||
public ControllerBinding(Controller controller, Bind defaultBind, ResourceLocation id, Component description, KeyMapping override) {
|
private static final Map<Controller, Set<Bind>> pressedBinds = new HashMap<>();
|
||||||
|
|
||||||
|
public ControllerBinding(Controller controller, IBind defaultBind, ResourceLocation id, KeyMapping override) {
|
||||||
this.controller = controller;
|
this.controller = controller;
|
||||||
this.bind = this.defaultBind = defaultBind;
|
this.bind = this.defaultBind = defaultBind;
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.name = Component.translatable("controlify.binding." + id.getNamespace() + "." + id.getPath());
|
this.name = Component.translatable("controlify.binding." + id.getNamespace() + "." + id.getPath());
|
||||||
this.description = description;
|
var descKey = "controlify.binding." + id.getNamespace() + "." + id.getPath() + ".desc";
|
||||||
|
this.description = Language.getInstance().has(descKey) ? Component.translatable(descKey) : Component.empty();
|
||||||
this.override = override;
|
this.override = override;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ControllerBinding(Controller controller, Bind defaultBind, ResourceLocation id, KeyMapping override) {
|
public float state() {
|
||||||
this(controller, defaultBind, id, Component.empty(), override);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean held() {
|
|
||||||
return bind.state(controller.state(), controller);
|
return bind.state(controller.state(), controller);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean held() {
|
||||||
|
return bind.held(controller.state(), controller);
|
||||||
|
}
|
||||||
|
|
||||||
public boolean justPressed() {
|
public boolean justPressed() {
|
||||||
return held() && !bind.state(controller.prevState(), controller);
|
if (hasBindPressed(this)) return false;
|
||||||
|
|
||||||
|
if (held() && !bind.held(controller.prevState(), controller)) {
|
||||||
|
addPressedBind(this);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean justReleased() {
|
public boolean justReleased() {
|
||||||
return !held() && bind.state(controller.prevState(), controller);
|
if (hasBindPressed(this)) return false;
|
||||||
|
|
||||||
|
if (!held() && bind.held(controller.prevState(), controller)) {
|
||||||
|
addPressedBind(this);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Bind currentBind() {
|
public IBind currentBind() {
|
||||||
return bind;
|
return bind;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setCurrentBind(Bind bind) {
|
public void setCurrentBind(IBind bind) {
|
||||||
this.bind = bind;
|
this.bind = bind;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Bind defaultBind() {
|
public IBind defaultBind() {
|
||||||
return defaultBind;
|
return defaultBind;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,4 +89,29 @@ public class ControllerBinding {
|
|||||||
public KeyMapping override() {
|
public KeyMapping override() {
|
||||||
return override;
|
return override;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME: very hack solution please remove me
|
||||||
|
|
||||||
|
public static void clearPressedBinds(Controller controller) {
|
||||||
|
if (pressedBinds.containsKey(controller)) {
|
||||||
|
pressedBinds.get(controller).clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean hasBindPressed(ControllerBinding binding) {
|
||||||
|
var pressed = pressedBinds.getOrDefault(binding.controller, Set.of());
|
||||||
|
return pressed.containsAll(getBinds(binding.bind));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void addPressedBind(ControllerBinding binding) {
|
||||||
|
pressedBinds.computeIfAbsent(binding.controller, c -> new HashSet<>()).addAll(getBinds(binding.bind));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Set<Bind> getBinds(IBind bind) {
|
||||||
|
if (bind instanceof CompoundBind compoundBind) {
|
||||||
|
return compoundBind.binds();
|
||||||
|
} else {
|
||||||
|
return Set.of((Bind) bind);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@ import java.util.*;
|
|||||||
|
|
||||||
public class ControllerBindings {
|
public class ControllerBindings {
|
||||||
public final ControllerBinding
|
public final ControllerBinding
|
||||||
|
WALK_FORWARD, WALK_BACKWARD, WALK_LEFT, WALK_RIGHT,
|
||||||
JUMP, SNEAK,
|
JUMP, SNEAK,
|
||||||
ATTACK, USE,
|
ATTACK, USE,
|
||||||
SPRINT,
|
SPRINT,
|
||||||
@ -24,18 +25,24 @@ public class ControllerBindings {
|
|||||||
CHANGE_PERSPECTIVE,
|
CHANGE_PERSPECTIVE,
|
||||||
OPEN_CHAT,
|
OPEN_CHAT,
|
||||||
GUI_PRESS, GUI_BACK,
|
GUI_PRESS, GUI_BACK,
|
||||||
VMOUSE_LCLICK, VMOUSE_RCLICK, VMOUSE_MCLICK, VMOUSE_ESCAPE, VMOUSE_TOGGLE;
|
GUI_NEXT_TAB, GUI_PREV_TAB,
|
||||||
|
VMOUSE_LCLICK, VMOUSE_RCLICK, VMOUSE_MCLICK, VMOUSE_ESCAPE, VMOUSE_SHIFT, VMOUSE_TOGGLE,
|
||||||
|
TOGGLE_DEBUG_MENU;
|
||||||
|
|
||||||
private final Map<ResourceLocation, ControllerBinding> registry = new LinkedHashMap<>();
|
private final Map<ResourceLocation, ControllerBinding> registry = new LinkedHashMap<>();
|
||||||
|
|
||||||
public ControllerBindings(Controller controller) {
|
public ControllerBindings(Controller controller) {
|
||||||
var options = Minecraft.getInstance().options;
|
var options = Minecraft.getInstance().options;
|
||||||
|
|
||||||
|
register(WALK_FORWARD = new ControllerBinding(controller, Bind.LEFT_STICK_FORWARD, new ResourceLocation("controlify", "walk_forward"), null));
|
||||||
|
register(WALK_BACKWARD = new ControllerBinding(controller, Bind.LEFT_STICK_BACKWARD, new ResourceLocation("controlify", "walk_backward"), null));
|
||||||
|
register(WALK_LEFT = new ControllerBinding(controller, Bind.LEFT_STICK_LEFT, new ResourceLocation("controlify", "strafe_left"), null));
|
||||||
|
register(WALK_RIGHT = new ControllerBinding(controller, Bind.LEFT_STICK_RIGHT, new ResourceLocation("controlify", "strafe_right"), null));
|
||||||
register(JUMP = new ControllerBinding(controller, Bind.A_BUTTON, new ResourceLocation("controlify", "jump"), options.keyJump));
|
register(JUMP = new ControllerBinding(controller, Bind.A_BUTTON, new ResourceLocation("controlify", "jump"), options.keyJump));
|
||||||
register(SNEAK = new ControllerBinding(controller, Bind.RIGHT_STICK, new ResourceLocation("controlify", "sneak"), options.keyShift));
|
register(SNEAK = new ControllerBinding(controller, Bind.RIGHT_STICK_PRESS, new ResourceLocation("controlify", "sneak"), options.keyShift));
|
||||||
register(ATTACK = new ControllerBinding(controller, Bind.RIGHT_TRIGGER, new ResourceLocation("controlify", "attack"), options.keyAttack));
|
register(ATTACK = new ControllerBinding(controller, Bind.RIGHT_TRIGGER, new ResourceLocation("controlify", "attack"), options.keyAttack));
|
||||||
register(USE = new ControllerBinding(controller, Bind.LEFT_TRIGGER, new ResourceLocation("controlify", "use"), options.keyUse));
|
register(USE = new ControllerBinding(controller, Bind.LEFT_TRIGGER, new ResourceLocation("controlify", "use"), options.keyUse));
|
||||||
register(SPRINT = new ControllerBinding(controller, Bind.LEFT_STICK, new ResourceLocation("controlify", "sprint"), options.keySprint));
|
register(SPRINT = new ControllerBinding(controller, Bind.LEFT_STICK_PRESS, new ResourceLocation("controlify", "sprint"), options.keySprint));
|
||||||
register(DROP = new ControllerBinding(controller, Bind.DPAD_DOWN, new ResourceLocation("controlify", "drop"), options.keyDrop));
|
register(DROP = new ControllerBinding(controller, Bind.DPAD_DOWN, new ResourceLocation("controlify", "drop"), options.keyDrop));
|
||||||
register(NEXT_SLOT = new ControllerBinding(controller, Bind.RIGHT_BUMPER, new ResourceLocation("controlify", "next_slot"), null));
|
register(NEXT_SLOT = new ControllerBinding(controller, Bind.RIGHT_BUMPER, new ResourceLocation("controlify", "next_slot"), null));
|
||||||
register(PREV_SLOT = new ControllerBinding(controller, Bind.LEFT_BUMPER, new ResourceLocation("controlify", "prev_slot"), null));
|
register(PREV_SLOT = new ControllerBinding(controller, Bind.LEFT_BUMPER, new ResourceLocation("controlify", "prev_slot"), null));
|
||||||
@ -45,11 +52,15 @@ public class ControllerBindings {
|
|||||||
register(OPEN_CHAT = new ControllerBinding(controller, Bind.DPAD_UP, new ResourceLocation("controlify", "open_chat"), options.keyChat));
|
register(OPEN_CHAT = new ControllerBinding(controller, Bind.DPAD_UP, new ResourceLocation("controlify", "open_chat"), options.keyChat));
|
||||||
register(GUI_PRESS = new ControllerBinding(controller, Bind.A_BUTTON, new ResourceLocation("controlify", "gui_press"), null));
|
register(GUI_PRESS = new ControllerBinding(controller, Bind.A_BUTTON, new ResourceLocation("controlify", "gui_press"), null));
|
||||||
register(GUI_BACK = new ControllerBinding(controller, Bind.B_BUTTON, new ResourceLocation("controlify", "gui_back"), null));
|
register(GUI_BACK = new ControllerBinding(controller, Bind.B_BUTTON, new ResourceLocation("controlify", "gui_back"), null));
|
||||||
|
register(GUI_NEXT_TAB = new ControllerBinding(controller, Bind.RIGHT_BUMPER, new ResourceLocation("controlify", "gui_next_tab"), null));
|
||||||
|
register(GUI_PREV_TAB = new ControllerBinding(controller, Bind.LEFT_BUMPER, new ResourceLocation("controlify", "gui_prev_tab"), null));
|
||||||
register(VMOUSE_LCLICK = new ControllerBinding(controller, Bind.A_BUTTON, new ResourceLocation("controlify", "vmouse_lclick"), null));
|
register(VMOUSE_LCLICK = new ControllerBinding(controller, Bind.A_BUTTON, new ResourceLocation("controlify", "vmouse_lclick"), null));
|
||||||
register(VMOUSE_RCLICK = new ControllerBinding(controller, Bind.X_BUTTON, new ResourceLocation("controlify", "vmouse_rclick"), null));
|
register(VMOUSE_RCLICK = new ControllerBinding(controller, Bind.X_BUTTON, new ResourceLocation("controlify", "vmouse_rclick"), null));
|
||||||
register(VMOUSE_MCLICK = new ControllerBinding(controller, Bind.Y_BUTTON, new ResourceLocation("controlify", "vmouse_mclick"), null));
|
register(VMOUSE_MCLICK = new ControllerBinding(controller, Bind.Y_BUTTON, new ResourceLocation("controlify", "vmouse_mclick"), null));
|
||||||
register(VMOUSE_ESCAPE = new ControllerBinding(controller, Bind.B_BUTTON, new ResourceLocation("controlify", "vmouse_escape"), null));
|
register(VMOUSE_ESCAPE = new ControllerBinding(controller, Bind.B_BUTTON, new ResourceLocation("controlify", "vmouse_escape"), null));
|
||||||
|
register(VMOUSE_SHIFT = new ControllerBinding(controller, Bind.LEFT_STICK_PRESS, new ResourceLocation("controlify", "vmouse_shift"), null));
|
||||||
register(VMOUSE_TOGGLE = new ControllerBinding(controller, Bind.BACK, new ResourceLocation("controlify", "vmouse_toggle"), null));
|
register(VMOUSE_TOGGLE = new ControllerBinding(controller, Bind.BACK, new ResourceLocation("controlify", "vmouse_toggle"), null));
|
||||||
|
register(TOGGLE_DEBUG_MENU = new ControllerBinding(controller, new CompoundBind(Bind.BACK, Bind.START), new ResourceLocation("controlify", "toggle_debug_menu"), null));
|
||||||
|
|
||||||
ControlifyEvents.CONTROLLER_BIND_REGISTRY.invoker().onRegisterControllerBinds(this, controller);
|
ControlifyEvents.CONTROLLER_BIND_REGISTRY.invoker().onRegisterControllerBinds(this, controller);
|
||||||
|
|
||||||
@ -73,7 +84,7 @@ public class ControllerBindings {
|
|||||||
public JsonObject toJson() {
|
public JsonObject toJson() {
|
||||||
JsonObject json = new JsonObject();
|
JsonObject json = new JsonObject();
|
||||||
for (var binding : registry().values()) {
|
for (var binding : registry().values()) {
|
||||||
json.addProperty(binding.id().toString(), binding.currentBind().identifier());
|
json.add(binding.id().toString(), binding.currentBind().toJson());
|
||||||
}
|
}
|
||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
@ -82,13 +93,17 @@ public class ControllerBindings {
|
|||||||
for (var binding : registry().values()) {
|
for (var binding : registry().values()) {
|
||||||
var bind = json.get(binding.id().toString());
|
var bind = json.get(binding.id().toString());
|
||||||
if (bind == null) continue;
|
if (bind == null) continue;
|
||||||
binding.setCurrentBind(Bind.fromIdentifier(bind.getAsString()));
|
binding.setCurrentBind(IBind.fromJson(bind));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void imitateVanillaClick(Controller controller) {
|
private void imitateVanillaClick(Controller controller) {
|
||||||
|
ControllerBinding.clearPressedBinds(controller);
|
||||||
|
|
||||||
if (Controlify.instance().currentInputMode() != InputMode.CONTROLLER)
|
if (Controlify.instance().currentInputMode() != InputMode.CONTROLLER)
|
||||||
return;
|
return;
|
||||||
|
if (Minecraft.getInstance().screen != null && !Minecraft.getInstance().screen.passEvents)
|
||||||
|
return;
|
||||||
|
|
||||||
for (var binding : registry().values()) {
|
for (var binding : registry().values()) {
|
||||||
KeyMapping vanillaKey = binding.override();
|
KeyMapping vanillaKey = binding.override();
|
||||||
|
38
src/main/java/dev/isxander/controlify/bindings/IBind.java
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
package dev.isxander.controlify.bindings;
|
||||||
|
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.mojang.blaze3d.vertex.PoseStack;
|
||||||
|
import dev.isxander.controlify.controller.Controller;
|
||||||
|
import dev.isxander.controlify.controller.ControllerState;
|
||||||
|
import dev.isxander.controlify.gui.ButtonRenderer;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
public interface IBind {
|
||||||
|
float state(ControllerState state, Controller controller);
|
||||||
|
default boolean held(ControllerState state, Controller controller) {
|
||||||
|
return state(state, controller) > controller.config().buttonActivationThreshold;
|
||||||
|
}
|
||||||
|
|
||||||
|
void draw(PoseStack matrices, int x, int centerY, Controller controller);
|
||||||
|
ButtonRenderer.DrawSize drawSize();
|
||||||
|
|
||||||
|
JsonElement toJson();
|
||||||
|
|
||||||
|
static IBind fromJson(JsonElement json) {
|
||||||
|
if (json.isJsonArray()) {
|
||||||
|
return new CompoundBind(json.getAsJsonArray().asList().stream().map(element -> Bind.fromIdentifier(element.getAsString())).toArray(Bind[]::new));
|
||||||
|
} else {
|
||||||
|
return Bind.fromIdentifier(json.getAsString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static IBind create(Collection<Bind> binds) {
|
||||||
|
if (binds.size() == 1) return binds.stream().findAny().orElseThrow();
|
||||||
|
return new CompoundBind(binds.toArray(new Bind[0]));
|
||||||
|
}
|
||||||
|
static IBind create(Bind... binds) {
|
||||||
|
if (binds.length == 1) return binds[0];
|
||||||
|
return new CompoundBind(binds);
|
||||||
|
}
|
||||||
|
}
|
@ -27,8 +27,12 @@ public class ScreenProcessor<T extends Screen> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void onControllerUpdate(Controller controller) {
|
public void onControllerUpdate(Controller controller) {
|
||||||
handleComponentNavigation(controller);
|
if (!Controlify.instance().virtualMouseHandler().isVirtualMouseEnabled()) {
|
||||||
handleButtons(controller);
|
handleComponentNavigation(controller);
|
||||||
|
handleButtons(controller);
|
||||||
|
} else {
|
||||||
|
handleVMouseNavigation(controller);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onInputModeChanged(InputMode mode) {
|
public void onInputModeChanged(InputMode mode) {
|
||||||
@ -43,6 +47,9 @@ public class ScreenProcessor<T extends Screen> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected void handleComponentNavigation(Controller controller) {
|
protected void handleComponentNavigation(Controller controller) {
|
||||||
|
if (screen.getFocused() == null)
|
||||||
|
setInitialFocus();
|
||||||
|
|
||||||
var focusTree = getFocusTree();
|
var focusTree = getFocusTree();
|
||||||
while (!focusTree.isEmpty()) {
|
while (!focusTree.isEmpty()) {
|
||||||
var focused = focusTree.poll();
|
var focused = focusTree.poll();
|
||||||
@ -102,6 +109,10 @@ public class ScreenProcessor<T extends Screen> {
|
|||||||
screen.onClose();
|
screen.onClose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void handleVMouseNavigation(Controller controller) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
public void onWidgetRebuild() {
|
public void onWidgetRebuild() {
|
||||||
setInitialFocus();
|
setInitialFocus();
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,31 @@
|
|||||||
|
package dev.isxander.controlify.compatibility.vanilla;
|
||||||
|
|
||||||
|
import dev.isxander.controlify.compatibility.screen.ScreenProcessor;
|
||||||
|
import dev.isxander.controlify.controller.Controller;
|
||||||
|
import dev.isxander.controlify.mixins.compat.screen.vanilla.CreativeModeInventoryScreenAccessor;
|
||||||
|
import net.minecraft.client.gui.screens.inventory.CreativeModeInventoryScreen;
|
||||||
|
import net.minecraft.world.item.CreativeModeTabs;
|
||||||
|
|
||||||
|
public class CreativeModeInventoryScreenProcessor extends ScreenProcessor<CreativeModeInventoryScreen> {
|
||||||
|
public CreativeModeInventoryScreenProcessor(CreativeModeInventoryScreen screen) {
|
||||||
|
super(screen);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void handleVMouseNavigation(Controller controller) {
|
||||||
|
var accessor = (CreativeModeInventoryScreenAccessor) screen;
|
||||||
|
|
||||||
|
if (controller.bindings().GUI_NEXT_TAB.justPressed()) {
|
||||||
|
var tabs = CreativeModeTabs.tabs();
|
||||||
|
int newIndex = tabs.indexOf(accessor.getSelectedTab()) + 1;
|
||||||
|
if (newIndex >= tabs.size()) newIndex = 0;
|
||||||
|
accessor.invokeSelectTab(tabs.get(newIndex));
|
||||||
|
}
|
||||||
|
if (controller.bindings().GUI_PREV_TAB.justPressed()) {
|
||||||
|
var tabs = CreativeModeTabs.tabs();
|
||||||
|
int newIndex = tabs.indexOf(accessor.getSelectedTab()) - 1;
|
||||||
|
if (newIndex < 0) newIndex = tabs.size() - 1;
|
||||||
|
accessor.invokeSelectTab(tabs.get(newIndex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
package dev.isxander.controlify.compatibility.vanilla;
|
||||||
|
|
||||||
|
import dev.isxander.controlify.compatibility.screen.ScreenProcessor;
|
||||||
|
import dev.isxander.controlify.controller.Controller;
|
||||||
|
import net.minecraft.client.gui.components.Button;
|
||||||
|
import net.minecraft.client.gui.screens.multiplayer.JoinMultiplayerScreen;
|
||||||
|
import net.minecraft.client.gui.screens.multiplayer.ServerSelectionList;
|
||||||
|
|
||||||
|
public class JoinMultiplayerScreenProcessor extends ScreenProcessor<JoinMultiplayerScreen> {
|
||||||
|
private final ServerSelectionList list;
|
||||||
|
|
||||||
|
public JoinMultiplayerScreenProcessor(JoinMultiplayerScreen screen, ServerSelectionList list) {
|
||||||
|
super(screen);
|
||||||
|
this.list = list;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void handleButtons(Controller controller) {
|
||||||
|
if (screen.getFocused() instanceof Button && controller.bindings().GUI_BACK.justPressed()) {
|
||||||
|
screen.setFocused(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
super.handleButtons(controller);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
package dev.isxander.controlify.compatibility.vanilla;
|
||||||
|
|
||||||
|
import dev.isxander.controlify.compatibility.screen.ScreenProcessor;
|
||||||
|
import dev.isxander.controlify.compatibility.screen.component.ComponentProcessor;
|
||||||
|
import dev.isxander.controlify.controller.Controller;
|
||||||
|
import dev.isxander.controlify.mixins.compat.screen.vanilla.OptionsSubScreenAccessor;
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
|
||||||
|
public class LanguageSelectionListComponentProcessor implements ComponentProcessor {
|
||||||
|
private final String code;
|
||||||
|
|
||||||
|
public LanguageSelectionListComponentProcessor(String code) {
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean overrideControllerButtons(ScreenProcessor<?> screen, Controller controller) {
|
||||||
|
if (controller.bindings().GUI_PRESS.justPressed()) {
|
||||||
|
var minecraft = Minecraft.getInstance();
|
||||||
|
var languageManager = minecraft.getLanguageManager();
|
||||||
|
if (!code.equals(languageManager.getSelected())) {
|
||||||
|
languageManager.setSelected(code);
|
||||||
|
minecraft.options.languageCode = code;
|
||||||
|
minecraft.reloadResourcePacks();
|
||||||
|
minecraft.options.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
minecraft.setScreen(((OptionsSubScreenAccessor) screen.screen).getLastScreen());
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
package dev.isxander.controlify.compatibility.vanilla;
|
||||||
|
|
||||||
|
import dev.isxander.controlify.compatibility.screen.ScreenProcessor;
|
||||||
|
import dev.isxander.controlify.compatibility.screen.component.ComponentProcessor;
|
||||||
|
import dev.isxander.controlify.controller.Controller;
|
||||||
|
import dev.isxander.controlify.mixins.compat.screen.vanilla.JoinMultiplayerScreenAccessor;
|
||||||
|
|
||||||
|
public class ServerSelectionListEntryComponentProcessor implements ComponentProcessor {
|
||||||
|
@Override
|
||||||
|
public boolean overrideControllerButtons(ScreenProcessor<?> screen, Controller controller) {
|
||||||
|
if (controller.bindings().GUI_PRESS.justPressed()) {
|
||||||
|
screen.screen.setFocused(((JoinMultiplayerScreenAccessor) screen.screen).getSelectButton());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
package dev.isxander.controlify.config;
|
||||||
|
|
||||||
|
import com.google.gson.*;
|
||||||
|
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
|
||||||
|
public class ClassTypeAdapter implements JsonSerializer<Class<?>>, JsonDeserializer<Class<?>> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<?> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
||||||
|
try {
|
||||||
|
return Class.forName(json.getAsString());
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JsonElement serialize(Class<?> src, Type typeOfSrc, JsonSerializationContext context) {
|
||||||
|
return new JsonPrimitive(src.getName());
|
||||||
|
}
|
||||||
|
}
|
@ -17,10 +17,12 @@ public class ControlifyConfig {
|
|||||||
.serializeNulls()
|
.serializeNulls()
|
||||||
.setPrettyPrinting()
|
.setPrettyPrinting()
|
||||||
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
|
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
|
||||||
|
.registerTypeHierarchyAdapter(Class.class, new ClassTypeAdapter())
|
||||||
.create();
|
.create();
|
||||||
|
|
||||||
private JsonObject controllerData = new JsonObject();
|
private JsonObject controllerData = new JsonObject();
|
||||||
private GlobalSettings globalSettings = new GlobalSettings();
|
private GlobalSettings globalSettings = new GlobalSettings();
|
||||||
|
private boolean firstLaunch;
|
||||||
|
|
||||||
public void save() {
|
public void save() {
|
||||||
Controlify.LOGGER.info("Saving Controlify config...");
|
Controlify.LOGGER.info("Saving Controlify config...");
|
||||||
@ -37,6 +39,7 @@ public class ControlifyConfig {
|
|||||||
Controlify.LOGGER.info("Loading Controlify config...");
|
Controlify.LOGGER.info("Loading Controlify config...");
|
||||||
|
|
||||||
if (!Files.exists(CONFIG_PATH)) {
|
if (!Files.exists(CONFIG_PATH)) {
|
||||||
|
firstLaunch = true;
|
||||||
save();
|
save();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -55,8 +58,7 @@ public class ControlifyConfig {
|
|||||||
|
|
||||||
for (var controller : Controller.CONTROLLERS.values()) {
|
for (var controller : Controller.CONTROLLERS.values()) {
|
||||||
// `add` replaces if already existing
|
// `add` replaces if already existing
|
||||||
// TODO: find a better way to identify controllers, GUID will report the same for multiple controllers of the same model
|
newControllerData.add(controller.uid().toString(), generateControllerConfig(controller));
|
||||||
newControllerData.add(controller.uid(), generateControllerConfig(controller));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
controllerData = newControllerData;
|
controllerData = newControllerData;
|
||||||
@ -82,15 +84,22 @@ public class ControlifyConfig {
|
|||||||
|
|
||||||
JsonObject controllers = object.getAsJsonObject("controllers");
|
JsonObject controllers = object.getAsJsonObject("controllers");
|
||||||
if (controllers != null) {
|
if (controllers != null) {
|
||||||
|
this.controllerData = controllers;
|
||||||
for (var controller : Controller.CONTROLLERS.values()) {
|
for (var controller : Controller.CONTROLLERS.values()) {
|
||||||
var settings = controllers.getAsJsonObject(controller.uid());
|
loadOrCreateControllerData(controller);
|
||||||
if (settings != null) {
|
|
||||||
applyControllerConfig(controller, settings);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void loadOrCreateControllerData(Controller controller) {
|
||||||
|
var uid = controller.uid().toString();
|
||||||
|
if (controllerData.has(uid)) {
|
||||||
|
applyControllerConfig(controller, controllerData.getAsJsonObject(uid));
|
||||||
|
} else {
|
||||||
|
save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void applyControllerConfig(Controller controller, JsonObject object) {
|
private void applyControllerConfig(Controller controller, JsonObject object) {
|
||||||
controller.setConfig(GSON.fromJson(object.getAsJsonObject("config"), Controller.ControllerConfig.class));
|
controller.setConfig(GSON.fromJson(object.getAsJsonObject("config"), Controller.ControllerConfig.class));
|
||||||
controller.bindings().fromJson(object.getAsJsonObject("bindings"));
|
controller.bindings().fromJson(object.getAsJsonObject("bindings"));
|
||||||
@ -99,4 +108,8 @@ public class ControlifyConfig {
|
|||||||
public GlobalSettings globalSettings() {
|
public GlobalSettings globalSettings() {
|
||||||
return globalSettings;
|
return globalSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isFirstLaunch() {
|
||||||
|
return firstLaunch;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,16 @@
|
|||||||
package dev.isxander.controlify.config;
|
package dev.isxander.controlify.config;
|
||||||
|
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class GlobalSettings {
|
public class GlobalSettings {
|
||||||
public static final GlobalSettings DEFAULT = new GlobalSettings();
|
public static final GlobalSettings DEFAULT = new GlobalSettings();
|
||||||
|
|
||||||
public List<String> virtualMouseScreens = Lists.newArrayList(
|
public List<Class<?>> virtualMouseScreens = Lists.newArrayList(
|
||||||
|
AbstractContainerScreen.class
|
||||||
);
|
);
|
||||||
|
|
||||||
|
public boolean outOfFocusInput = false;
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ package dev.isxander.controlify.config.gui;
|
|||||||
|
|
||||||
import com.mojang.blaze3d.vertex.PoseStack;
|
import com.mojang.blaze3d.vertex.PoseStack;
|
||||||
import dev.isxander.controlify.bindings.Bind;
|
import dev.isxander.controlify.bindings.Bind;
|
||||||
|
import dev.isxander.controlify.bindings.IBind;
|
||||||
import dev.isxander.controlify.compatibility.screen.ScreenProcessor;
|
import dev.isxander.controlify.compatibility.screen.ScreenProcessor;
|
||||||
import dev.isxander.controlify.compatibility.screen.component.ComponentProcessor;
|
import dev.isxander.controlify.compatibility.screen.component.ComponentProcessor;
|
||||||
import dev.isxander.controlify.compatibility.screen.component.ComponentProcessorProvider;
|
import dev.isxander.controlify.compatibility.screen.component.ComponentProcessorProvider;
|
||||||
@ -17,23 +18,27 @@ import net.minecraft.ChatFormatting;
|
|||||||
import net.minecraft.network.chat.Component;
|
import net.minecraft.network.chat.Component;
|
||||||
import org.lwjgl.glfw.GLFW;
|
import org.lwjgl.glfw.GLFW;
|
||||||
|
|
||||||
public class BindButtonController implements Controller<Bind> {
|
import java.util.HashSet;
|
||||||
private final Option<Bind> option;
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public class BindButtonController implements Controller<IBind> {
|
||||||
|
private final Option<IBind> option;
|
||||||
private final dev.isxander.controlify.controller.Controller controller;
|
private final dev.isxander.controlify.controller.Controller controller;
|
||||||
|
|
||||||
public BindButtonController(Option<Bind> option, dev.isxander.controlify.controller.Controller controller) {
|
public BindButtonController(Option<IBind> option, dev.isxander.controlify.controller.Controller controller) {
|
||||||
this.option = option;
|
this.option = option;
|
||||||
this.controller = controller;
|
this.controller = controller;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Option<Bind> option() {
|
public Option<IBind> option() {
|
||||||
return this.option;
|
return this.option;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Component formatValue() {
|
public Component formatValue() {
|
||||||
return Component.literal(option().pendingValue().identifier());
|
return Component.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -42,8 +47,9 @@ public class BindButtonController implements Controller<Bind> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static class BindButtonWidget extends ControllerWidget<BindButtonController> implements ComponentProcessorProvider, ComponentProcessor {
|
public static class BindButtonWidget extends ControllerWidget<BindButtonController> implements ComponentProcessorProvider, ComponentProcessor {
|
||||||
private boolean awaitingControllerInput = false;
|
private boolean awaitingControllerInput = false, skipFirstTickInput = false;
|
||||||
private final Component awaitingText = Component.translatable("controlify.gui.bind_input_awaiting").withStyle(ChatFormatting.ITALIC);
|
private final Component awaitingText = Component.translatable("controlify.gui.bind_input_awaiting").withStyle(ChatFormatting.ITALIC);
|
||||||
|
private final Set<Bind> pressedBinds = new LinkedHashSet<>();
|
||||||
|
|
||||||
public BindButtonWidget(BindButtonController control, YACLScreen screen, Dimension<Integer> dim) {
|
public BindButtonWidget(BindButtonController control, YACLScreen screen, Dimension<Integer> dim) {
|
||||||
super(control, screen, dim);
|
super(control, screen, dim);
|
||||||
@ -52,9 +58,17 @@ public class BindButtonController implements Controller<Bind> {
|
|||||||
@Override
|
@Override
|
||||||
protected void drawValueText(PoseStack matrices, int mouseX, int mouseY, float delta) {
|
protected void drawValueText(PoseStack matrices, int mouseX, int mouseY, float delta) {
|
||||||
if (awaitingControllerInput) {
|
if (awaitingControllerInput) {
|
||||||
textRenderer.drawShadow(matrices, awaitingText, getDimension().xLimit() - textRenderer.width(awaitingText) - getXPadding(), getDimension().centerY() - textRenderer.lineHeight / 2f, 0xFFFFFF);
|
if (pressedBinds.isEmpty()) {
|
||||||
|
textRenderer.drawShadow(matrices, awaitingText, getDimension().xLimit() - textRenderer.width(awaitingText) - getXPadding(), getDimension().centerY() - textRenderer.lineHeight / 2f, 0xFFFFFF);
|
||||||
|
} else {
|
||||||
|
var bind = IBind.create(pressedBinds);
|
||||||
|
var plusSize = 2 + textRenderer.width("+");
|
||||||
|
bind.draw(matrices, getDimension().xLimit() - bind.drawSize().width() - getXPadding() - plusSize, getDimension().centerY(), control.controller);
|
||||||
|
textRenderer.drawShadow(matrices, "+", getDimension().xLimit() - getXPadding() - plusSize, getDimension().centerY() - textRenderer.lineHeight / 2f, 0xFFFFFF);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
ButtonRenderer.drawButton(control.option().pendingValue(), control.controller, matrices, getDimension().xLimit() - ButtonRenderer.BUTTON_SIZE / 2, getDimension().centerY(), ButtonRenderer.BUTTON_SIZE);
|
var bind = control.option().pendingValue();
|
||||||
|
bind.draw(matrices, getDimension().xLimit() - bind.drawSize().width(), getDimension().centerY(), control.controller);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -84,24 +98,32 @@ public class BindButtonController implements Controller<Bind> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean overrideControllerButtons(ScreenProcessor screen, dev.isxander.controlify.controller.Controller controller) {
|
public boolean overrideControllerButtons(ScreenProcessor<?> screen, dev.isxander.controlify.controller.Controller controller) {
|
||||||
if (!awaitingControllerInput || !isFocused()) return false;
|
if (controller.bindings().GUI_PRESS.justPressed()) {
|
||||||
|
return awaitingControllerInput = true;
|
||||||
for (var bind : Bind.values()) {
|
|
||||||
boolean stateNow = bind.state(controller.state(), controller);
|
|
||||||
boolean stateBefore = bind.state(controller.prevState(), controller);
|
|
||||||
if (stateNow && !stateBefore) {
|
|
||||||
control.option().requestSet(bind);
|
|
||||||
awaitingControllerInput = false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
if (!awaitingControllerInput) return false;
|
||||||
|
|
||||||
|
if (pressedBinds.stream().anyMatch(bind -> !bind.held(controller.state(), controller))) {
|
||||||
|
// finished
|
||||||
|
awaitingControllerInput = false;
|
||||||
|
control.option().requestSet(IBind.create(pressedBinds));
|
||||||
|
pressedBinds.clear();
|
||||||
|
} else {
|
||||||
|
for (var bind : Bind.values()) {
|
||||||
|
if (bind.held(controller.state(), controller) && !bind.held(controller.prevState(), controller)) {
|
||||||
|
pressedBinds.add(bind);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
control.controller.consumeButtonState();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean overrideControllerNavigation(ScreenProcessor screen, dev.isxander.controlify.controller.Controller controller) {
|
public boolean overrideControllerNavigation(ScreenProcessor<?> screen, dev.isxander.controlify.controller.Controller controller) {
|
||||||
return awaitingControllerInput;
|
return awaitingControllerInput;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -115,7 +137,7 @@ public class BindButtonController implements Controller<Bind> {
|
|||||||
if (awaitingControllerInput)
|
if (awaitingControllerInput)
|
||||||
return textRenderer.width(awaitingText);
|
return textRenderer.width(awaitingText);
|
||||||
|
|
||||||
return ButtonRenderer.BUTTON_SIZE;
|
return control.option().pendingValue().drawSize().width();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,28 +1,41 @@
|
|||||||
package dev.isxander.controlify.config.gui;
|
package dev.isxander.controlify.config.gui;
|
||||||
|
|
||||||
import dev.isxander.controlify.Controlify;
|
import dev.isxander.controlify.Controlify;
|
||||||
import dev.isxander.controlify.bindings.Bind;
|
import dev.isxander.controlify.bindings.IBind;
|
||||||
import dev.isxander.controlify.config.GlobalSettings;
|
import dev.isxander.controlify.config.GlobalSettings;
|
||||||
import dev.isxander.controlify.controller.ControllerTheme;
|
import dev.isxander.controlify.controller.ControllerTheme;
|
||||||
import dev.isxander.controlify.controller.Controller;
|
import dev.isxander.controlify.controller.Controller;
|
||||||
import dev.isxander.yacl.api.*;
|
import dev.isxander.yacl.api.*;
|
||||||
|
import dev.isxander.yacl.gui.controllers.ActionController;
|
||||||
import dev.isxander.yacl.gui.controllers.TickBoxController;
|
import dev.isxander.yacl.gui.controllers.TickBoxController;
|
||||||
import dev.isxander.yacl.gui.controllers.cycling.CyclingListController;
|
import dev.isxander.yacl.gui.controllers.cycling.CyclingListController;
|
||||||
import dev.isxander.yacl.gui.controllers.cycling.EnumController;
|
import dev.isxander.yacl.gui.controllers.cycling.EnumController;
|
||||||
import dev.isxander.yacl.gui.controllers.slider.FloatSliderController;
|
import dev.isxander.yacl.gui.controllers.slider.FloatSliderController;
|
||||||
import dev.isxander.yacl.gui.controllers.slider.IntegerSliderController;
|
import dev.isxander.yacl.gui.controllers.slider.IntegerSliderController;
|
||||||
import net.minecraft.ChatFormatting;
|
import net.minecraft.ChatFormatting;
|
||||||
|
import net.minecraft.Util;
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
import net.minecraft.client.gui.screens.AlertScreen;
|
||||||
import net.minecraft.client.gui.screens.Screen;
|
import net.minecraft.client.gui.screens.Screen;
|
||||||
import net.minecraft.network.chat.Component;
|
import net.minecraft.network.chat.Component;
|
||||||
|
|
||||||
public class YACLHelper {
|
public class YACLHelper {
|
||||||
public static Screen generateConfigScreen(Screen parent) {
|
public static Screen generateConfigScreen(Screen parent) {
|
||||||
|
if (Controlify.instance().currentController() == null) {
|
||||||
|
return new AlertScreen(
|
||||||
|
() -> Minecraft.getInstance().setScreen(parent),
|
||||||
|
Component.translatable("controlify.gui.error.title").withStyle(ChatFormatting.RED, ChatFormatting.BOLD),
|
||||||
|
Component.translatable("controlify.gui.error.message").withStyle(ChatFormatting.RED)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
var controlify = Controlify.instance();
|
var controlify = Controlify.instance();
|
||||||
|
|
||||||
var yacl = YetAnotherConfigLib.createBuilder()
|
var yacl = YetAnotherConfigLib.createBuilder()
|
||||||
.title(Component.literal("Controlify"))
|
.title(Component.literal("Controlify"))
|
||||||
.save(() -> controlify.config().save());
|
.save(() -> controlify.config().save());
|
||||||
|
|
||||||
|
var globalSettings = Controlify.instance().config().globalSettings();
|
||||||
var globalCategory = ConfigCategory.createBuilder()
|
var globalCategory = ConfigCategory.createBuilder()
|
||||||
.name(Component.translatable("controlify.gui.category.global"))
|
.name(Component.translatable("controlify.gui.category.global"))
|
||||||
.option(Option.createBuilder(Controller.class)
|
.option(Option.createBuilder(Controller.class)
|
||||||
@ -31,6 +44,17 @@ public class YACLHelper {
|
|||||||
.binding(Controlify.instance().currentController(), () -> Controlify.instance().currentController(), v -> Controlify.instance().setCurrentController(v))
|
.binding(Controlify.instance().currentController(), () -> Controlify.instance().currentController(), v -> Controlify.instance().setCurrentController(v))
|
||||||
.controller(opt -> new CyclingListController<>(opt, Controller.CONTROLLERS.values().stream().filter(Controller::connected).toList(), c -> Component.literal(c.name())))
|
.controller(opt -> new CyclingListController<>(opt, Controller.CONTROLLERS.values().stream().filter(Controller::connected).toList(), c -> Component.literal(c.name())))
|
||||||
.instant(true)
|
.instant(true)
|
||||||
|
.build())
|
||||||
|
.option(Option.createBuilder(boolean.class)
|
||||||
|
.name(Component.translatable("controlify.gui.out_of_focus_input"))
|
||||||
|
.tooltip(Component.translatable("controlify.gui.out_of_focus_input.tooltip"))
|
||||||
|
.binding(GlobalSettings.DEFAULT.outOfFocusInput, () -> globalSettings.outOfFocusInput, v -> globalSettings.outOfFocusInput = v)
|
||||||
|
.controller(TickBoxController::new)
|
||||||
|
.build())
|
||||||
|
.option(ButtonOption.createBuilder()
|
||||||
|
.name(Component.translatable("controlify.gui.open_issue_tracker"))
|
||||||
|
.action((screen, button) -> Util.getPlatform().openUri("https://github.com/isxander/controlify/issues"))
|
||||||
|
.controller(opt -> new ActionController(opt, Component.translatable("controlify.gui.format.open")))
|
||||||
.build());
|
.build());
|
||||||
|
|
||||||
yacl.category(globalCategory.build());
|
yacl.category(globalCategory.build());
|
||||||
@ -85,15 +109,9 @@ public class YACLHelper {
|
|||||||
.controller(opt -> new FloatSliderController(opt, 0, 1, 0.01f, v -> Component.literal(String.format("%.0f%%", v*100))))
|
.controller(opt -> new FloatSliderController(opt, 0, 1, 0.01f, v -> Component.literal(String.format("%.0f%%", v*100))))
|
||||||
.build())
|
.build())
|
||||||
.option(Option.createBuilder(float.class)
|
.option(Option.createBuilder(float.class)
|
||||||
.name(Component.translatable("controlify.gui.left_trigger_threshold"))
|
.name(Component.translatable("controlify.gui.button_activation_threshold"))
|
||||||
.tooltip(Component.translatable("controlify.gui.left_trigger_threshold.tooltip"))
|
.tooltip(Component.translatable("controlify.gui.button_activation_threshold.tooltip"))
|
||||||
.binding(def.leftTriggerActivationThreshold, () -> config.leftTriggerActivationThreshold, v -> config.leftTriggerActivationThreshold = v)
|
.binding(def.buttonActivationThreshold, () -> config.buttonActivationThreshold, v -> config.buttonActivationThreshold = 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))))
|
.controller(opt -> new FloatSliderController(opt, 0, 1, 0.05f, v -> Component.literal(String.format("%.0f%%", v*100))))
|
||||||
.build())
|
.build())
|
||||||
.option(Option.createBuilder(ControllerTheme.class)
|
.option(Option.createBuilder(ControllerTheme.class)
|
||||||
@ -108,10 +126,12 @@ public class YACLHelper {
|
|||||||
var controlsGroup = OptionGroup.createBuilder()
|
var controlsGroup = OptionGroup.createBuilder()
|
||||||
.name(Component.translatable("controlify.gui.group.controls"));
|
.name(Component.translatable("controlify.gui.group.controls"));
|
||||||
for (var control : controller.bindings().registry().values()) {
|
for (var control : controller.bindings().registry().values()) {
|
||||||
controlsGroup.option(Option.createBuilder(Bind.class)
|
controlsGroup.option(Option.createBuilder(IBind.class)
|
||||||
.name(control.name())
|
.name(control.name())
|
||||||
.binding(control.defaultBind(), control::currentBind, control::setCurrentBind)
|
.binding(control.defaultBind(), control::currentBind, control::setCurrentBind)
|
||||||
.controller(opt -> new BindButtonController(opt, controller))
|
.controller(opt -> new BindButtonController(opt, controller))
|
||||||
|
.tooltip(control.description())
|
||||||
|
.instant(true)
|
||||||
.build());
|
.build());
|
||||||
}
|
}
|
||||||
category.group(controlsGroup.build());
|
category.group(controlsGroup.build());
|
||||||
|
@ -7,19 +7,21 @@ import org.hid4java.HidDevice;
|
|||||||
import org.lwjgl.glfw.GLFW;
|
import org.lwjgl.glfw.GLFW;
|
||||||
import org.lwjgl.glfw.GLFWGamepadState;
|
import org.lwjgl.glfw.GLFWGamepadState;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
public final class Controller {
|
public final class Controller {
|
||||||
public static final Map<Integer, Controller> CONTROLLERS = new HashMap<>();
|
public static final Map<Integer, Controller> CONTROLLERS = new HashMap<>();
|
||||||
public static final Controller DUMMY = new Controller(-1, "DUMMY", "DUMMY", false, "DUMMY", ControllerType.UNKNOWN);
|
public static final Controller DUMMY = new Controller(-1, "DUMMY", "DUMMY", false, UUID.randomUUID(), ControllerType.UNKNOWN);
|
||||||
|
|
||||||
private final int joystickId;
|
private final int joystickId;
|
||||||
private final String guid;
|
private final String guid;
|
||||||
private final String name;
|
private final String name;
|
||||||
private final boolean gamepad;
|
private final boolean gamepad;
|
||||||
private final String uid;
|
private final UUID uid;
|
||||||
private final ControllerType type;
|
private final ControllerType type;
|
||||||
|
|
||||||
private ControllerState state = ControllerState.EMPTY;
|
private ControllerState state = ControllerState.EMPTY;
|
||||||
@ -28,7 +30,7 @@ public final class Controller {
|
|||||||
private final ControllerBindings bindings = new ControllerBindings(this);
|
private final ControllerBindings bindings = new ControllerBindings(this);
|
||||||
private ControllerConfig config, defaultConfig;
|
private ControllerConfig config, defaultConfig;
|
||||||
|
|
||||||
public Controller(int joystickId, String guid, String name, boolean gamepad, String uid, ControllerType type) {
|
public Controller(int joystickId, String guid, String name, boolean gamepad, UUID uid, ControllerType type) {
|
||||||
this.joystickId = joystickId;
|
this.joystickId = joystickId;
|
||||||
this.guid = guid;
|
this.guid = guid;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
@ -62,8 +64,10 @@ public final class Controller {
|
|||||||
.rightTriggerDeadZone(config().rightTriggerDeadzone);
|
.rightTriggerDeadZone(config().rightTriggerDeadzone);
|
||||||
ButtonState buttonState = ButtonState.fromController(this);
|
ButtonState buttonState = ButtonState.fromController(this);
|
||||||
state = new ControllerState(axesState, buttonState);
|
state = new ControllerState(axesState, buttonState);
|
||||||
|
}
|
||||||
|
|
||||||
ControlifyEvents.CONTROLLER_STATE_UPDATED.invoker().onControllerStateUpdate(this);
|
public void consumeButtonState() {
|
||||||
|
this.state = new ControllerState(state().axes(), ButtonState.EMPTY);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ControllerBindings bindings() {
|
public ControllerBindings bindings() {
|
||||||
@ -89,7 +93,7 @@ public final class Controller {
|
|||||||
return guid;
|
return guid;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String uid() {
|
public UUID uid() {
|
||||||
return uid;
|
return uid;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -141,7 +145,7 @@ public final class Controller {
|
|||||||
String guid = GLFW.glfwGetJoystickGUID(id);
|
String guid = GLFW.glfwGetJoystickGUID(id);
|
||||||
boolean gamepad = GLFW.glfwJoystickIsGamepad(id);
|
boolean gamepad = GLFW.glfwJoystickIsGamepad(id);
|
||||||
String fallbackName = gamepad ? GLFW.glfwGetGamepadName(id) : GLFW.glfwGetJoystickName(id);
|
String fallbackName = gamepad ? GLFW.glfwGetGamepadName(id) : GLFW.glfwGetJoystickName(id);
|
||||||
String uid = device.getPath();
|
UUID uid = UUID.nameUUIDFromBytes(device.getPath().getBytes(StandardCharsets.UTF_8));
|
||||||
ControllerType type = ControllerType.getTypeForHID(new HIDIdentifier(device.getVendorId(), device.getProductId()));
|
ControllerType type = ControllerType.getTypeForHID(new HIDIdentifier(device.getVendorId(), device.getProductId()));
|
||||||
String name = type != ControllerType.UNKNOWN || fallbackName == null ? type.friendlyName() : fallbackName;
|
String name = type != ControllerType.UNKNOWN || fallbackName == null ? type.friendlyName() : fallbackName;
|
||||||
int tries = 1;
|
int tries = 1;
|
||||||
@ -166,8 +170,7 @@ public final class Controller {
|
|||||||
public float leftTriggerDeadzone = 0.0f;
|
public float leftTriggerDeadzone = 0.0f;
|
||||||
public float rightTriggerDeadzone = 0.0f;
|
public float rightTriggerDeadzone = 0.0f;
|
||||||
|
|
||||||
public float leftTriggerActivationThreshold = 0.5f;
|
public float buttonActivationThreshold = 0.5f;
|
||||||
public float rightTriggerActivationThreshold = 0.5f;
|
|
||||||
|
|
||||||
public int screenRepeatNavigationDelay = 4;
|
public int screenRepeatNavigationDelay = 4;
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package dev.isxander.controlify.controller.hid;
|
package dev.isxander.controlify.controller.hid;
|
||||||
|
|
||||||
import dev.isxander.controlify.Controlify;
|
import dev.isxander.controlify.Controlify;
|
||||||
|
import dev.isxander.controlify.controller.ControllerType;
|
||||||
import org.hid4java.*;
|
import org.hid4java.*;
|
||||||
import org.hid4java.event.HidServicesEvent;
|
import org.hid4java.event.HidServicesEvent;
|
||||||
|
|
||||||
@ -35,7 +36,7 @@ public class ControllerHIDService implements HidServicesListener {
|
|||||||
services.start();
|
services.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void awaitNextDevice(Consumer<HidDevice> consumer) {
|
public void awaitNextController(Consumer<HidDevice> consumer) {
|
||||||
deviceQueue.add(consumer);
|
deviceQueue.add(consumer);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,6 +47,8 @@ public class ControllerHIDService implements HidServicesListener {
|
|||||||
if (isController(device)) {
|
if (isController(device)) {
|
||||||
if (deviceQueue.peek() != null) {
|
if (deviceQueue.peek() != null) {
|
||||||
deviceQueue.poll().accept(event.getHidDevice());
|
deviceQueue.poll().accept(event.getHidDevice());
|
||||||
|
} else {
|
||||||
|
Controlify.LOGGER.error("Unhandled controller: " + ControllerType.getTypeForHID(new HIDIdentifier(device.getVendorId(), device.getProductId())).friendlyName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,10 +9,12 @@ import net.minecraft.client.gui.GuiComponent;
|
|||||||
public class ButtonRenderer {
|
public class ButtonRenderer {
|
||||||
public static final int BUTTON_SIZE = 22;
|
public static final int BUTTON_SIZE = 22;
|
||||||
|
|
||||||
public static void drawButton(Bind button, Controller controller, PoseStack poseStack, int x, int y, int size) {
|
public static void drawButton(Bind button, Controller controller, PoseStack poseStack, int x, int centerY) {
|
||||||
RenderSystem.setShaderTexture(0, button.textureLocation(controller));
|
RenderSystem.setShaderTexture(0, button.textureLocation(controller));
|
||||||
RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F);
|
RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F);
|
||||||
|
|
||||||
GuiComponent.blit(poseStack, x - size / 2, y - size / 2, 0, 0, BUTTON_SIZE, BUTTON_SIZE, BUTTON_SIZE, BUTTON_SIZE);
|
GuiComponent.blit(poseStack, x, centerY - BUTTON_SIZE / 2, 0, 0, BUTTON_SIZE, BUTTON_SIZE, BUTTON_SIZE, BUTTON_SIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public record DrawSize(int width, int height) { }
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,63 @@
|
|||||||
|
package dev.isxander.controlify.gui.screen;
|
||||||
|
|
||||||
|
import com.mojang.blaze3d.vertex.PoseStack;
|
||||||
|
import net.minecraft.ChatFormatting;
|
||||||
|
import net.minecraft.Util;
|
||||||
|
import net.minecraft.client.gui.components.AccessibilityOnboardingTextWidget;
|
||||||
|
import net.minecraft.client.gui.components.Button;
|
||||||
|
import net.minecraft.client.gui.components.MultiLineTextWidget;
|
||||||
|
import net.minecraft.client.gui.layouts.FrameLayout;
|
||||||
|
import net.minecraft.client.gui.layouts.GridLayout;
|
||||||
|
import net.minecraft.client.gui.screens.Screen;
|
||||||
|
import net.minecraft.client.gui.screens.TitleScreen;
|
||||||
|
import net.minecraft.network.chat.ClickEvent;
|
||||||
|
import net.minecraft.network.chat.CommonComponents;
|
||||||
|
import net.minecraft.network.chat.Component;
|
||||||
|
|
||||||
|
public class BetaNoticeScreen extends Screen {
|
||||||
|
private MultiLineTextWidget textWidget;
|
||||||
|
|
||||||
|
public BetaNoticeScreen() {
|
||||||
|
super(Component.translatable("controlify.beta.title"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void init() {
|
||||||
|
textWidget = new AccessibilityOnboardingTextWidget(
|
||||||
|
font,
|
||||||
|
Component.translatable("controlify.beta.message",
|
||||||
|
Component.translatable("controlify.beta.message.link")
|
||||||
|
.withStyle(ChatFormatting.AQUA)
|
||||||
|
),
|
||||||
|
this.width - 10
|
||||||
|
);
|
||||||
|
textWidget.setX(this.width / 2 - textWidget.getWidth() / 2);
|
||||||
|
textWidget.setY(30);
|
||||||
|
addRenderableWidget(textWidget);
|
||||||
|
|
||||||
|
addRenderableWidget(
|
||||||
|
Button.builder(
|
||||||
|
Component.translatable("controlify.beta.button"),
|
||||||
|
btn -> Util.getPlatform().openUri("https://github.com/isXander/controlify/issues")
|
||||||
|
)
|
||||||
|
.pos(this.width / 2 - 75, this.height - 8 - 20 - 20 - 4)
|
||||||
|
.width(150)
|
||||||
|
.build()
|
||||||
|
);
|
||||||
|
addRenderableWidget(
|
||||||
|
Button.builder(
|
||||||
|
CommonComponents.GUI_CONTINUE,
|
||||||
|
btn -> minecraft.setScreen(new TitleScreen())
|
||||||
|
)
|
||||||
|
.pos(this.width / 2 - 75, this.height - 8 - 20)
|
||||||
|
.width(150)
|
||||||
|
.build()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void render(PoseStack matrices, int mouseX, int mouseY, float delta) {
|
||||||
|
renderBackground(matrices);
|
||||||
|
super.render(matrices, mouseX, mouseY, delta);
|
||||||
|
}
|
||||||
|
}
|
@ -25,25 +25,23 @@ public class ControllerPlayerMovement extends Input {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var axes = controller.state().axes();
|
var bindings = controller.bindings();
|
||||||
|
|
||||||
this.up = axes.leftStickY() < 0;
|
this.forwardImpulse = bindings.WALK_FORWARD.state() - bindings.WALK_BACKWARD.state();
|
||||||
this.down = axes.leftStickY() > 0;
|
this.leftImpulse = bindings.WALK_LEFT.state() - bindings.WALK_RIGHT.state();
|
||||||
this.left = axes.leftStickX() < 0;
|
|
||||||
this.right = axes.leftStickX() > 0;
|
// .1 to prevent using boat turning absolute hell with left/right left/right
|
||||||
this.leftImpulse = -axes.leftStickX();
|
this.up = bindings.WALK_FORWARD.state() > 0.1;
|
||||||
this.forwardImpulse = -axes.leftStickY();
|
this.down = bindings.WALK_BACKWARD.state() > 0.1;
|
||||||
|
this.left = bindings.WALK_LEFT.state() > 0.1;
|
||||||
|
this.right = bindings.WALK_RIGHT.state() > 0.1;
|
||||||
|
|
||||||
if (slowDown) {
|
if (slowDown) {
|
||||||
this.leftImpulse *= f;
|
this.leftImpulse *= f;
|
||||||
this.forwardImpulse *= f;
|
this.forwardImpulse *= f;
|
||||||
}
|
}
|
||||||
|
|
||||||
var bindings = controller.bindings();
|
|
||||||
|
|
||||||
this.jumping = bindings.JUMP.held();
|
this.jumping = bindings.JUMP.held();
|
||||||
if (bindings.SNEAK.justPressed()) {
|
this.shiftKeyDown = bindings.SNEAK.held();
|
||||||
this.shiftKeyDown = !this.shiftKeyDown;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,17 +34,20 @@ public class InGameInputHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void inputTick() {
|
public void inputTick() {
|
||||||
var axes = controller.state().axes();
|
handlePlayerLookInput();
|
||||||
if (minecraft.mouseHandler.isMouseGrabbed() && minecraft.isWindowActive()) {
|
handleKeybinds();
|
||||||
accumulatedDX += axes.rightStickX();
|
}
|
||||||
accumulatedDY += axes.rightStickY();
|
|
||||||
}
|
|
||||||
|
|
||||||
processPlayerLook();
|
protected void handleKeybinds() {
|
||||||
|
if (Minecraft.getInstance().screen != null && !Minecraft.getInstance().screen.passEvents)
|
||||||
|
return;
|
||||||
|
|
||||||
if (controller.bindings().PAUSE.justPressed()) {
|
if (controller.bindings().PAUSE.justPressed()) {
|
||||||
minecraft.pauseGame(false);
|
minecraft.pauseGame(false);
|
||||||
}
|
}
|
||||||
|
if (controller.bindings().TOGGLE_DEBUG_MENU.justPressed()) {
|
||||||
|
minecraft.options.renderDebug = !minecraft.options.renderDebug;
|
||||||
|
}
|
||||||
if (minecraft.player != null) {
|
if (minecraft.player != null) {
|
||||||
if (controller.bindings().NEXT_SLOT.justPressed()) {
|
if (controller.bindings().NEXT_SLOT.justPressed()) {
|
||||||
minecraft.player.getInventory().swapPaint(-1);
|
minecraft.player.getInventory().swapPaint(-1);
|
||||||
@ -55,6 +58,16 @@ public class InGameInputHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void handlePlayerLookInput() {
|
||||||
|
var axes = controller.state().axes();
|
||||||
|
if (minecraft.mouseHandler.isMouseGrabbed() && minecraft.isWindowActive()) {
|
||||||
|
accumulatedDX += axes.rightStickX();
|
||||||
|
accumulatedDY += axes.rightStickY();
|
||||||
|
}
|
||||||
|
|
||||||
|
processPlayerLook();
|
||||||
|
}
|
||||||
|
|
||||||
public void processPlayerLook() {
|
public void processPlayerLook() {
|
||||||
var time = Blaze3D.getTime();
|
var time = Blaze3D.getTime();
|
||||||
var delta = time - deltaTime;
|
var delta = time - deltaTime;
|
||||||
|
@ -1,12 +1,17 @@
|
|||||||
package dev.isxander.controlify.mixins.compat.screen.vanilla;
|
package dev.isxander.controlify.mixins.compat.screen.vanilla;
|
||||||
|
|
||||||
|
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
|
||||||
|
import dev.isxander.controlify.Controlify;
|
||||||
|
import dev.isxander.controlify.InputMode;
|
||||||
import dev.isxander.controlify.compatibility.screen.component.ComponentProcessor;
|
import dev.isxander.controlify.compatibility.screen.component.ComponentProcessor;
|
||||||
import dev.isxander.controlify.compatibility.screen.component.ComponentProcessorProvider;
|
import dev.isxander.controlify.compatibility.screen.component.ComponentProcessorProvider;
|
||||||
import dev.isxander.controlify.compatibility.vanilla.SliderComponentProcessor;
|
import dev.isxander.controlify.compatibility.vanilla.SliderComponentProcessor;
|
||||||
|
import net.minecraft.client.InputType;
|
||||||
import net.minecraft.client.gui.components.AbstractSliderButton;
|
import net.minecraft.client.gui.components.AbstractSliderButton;
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
import org.spongepowered.asm.mixin.Shadow;
|
import org.spongepowered.asm.mixin.Shadow;
|
||||||
import org.spongepowered.asm.mixin.Unique;
|
import org.spongepowered.asm.mixin.Unique;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mixin to insert a custom {@link ComponentProcessor} into slider to support left/right movement without navigating to next component.
|
* Mixin to insert a custom {@link ComponentProcessor} into slider to support left/right movement without navigating to next component.
|
||||||
@ -22,6 +27,13 @@ public class AbstractSliderButtonMixin implements ComponentProcessorProvider {
|
|||||||
val -> this.canChangeValue = val
|
val -> this.canChangeValue = val
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ModifyExpressionValue(method = "setFocused", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Minecraft;getLastInputType()Lnet/minecraft/client/InputType;"))
|
||||||
|
private InputType shouldChangeValue(InputType type) {
|
||||||
|
if (Controlify.instance().currentInputMode() == InputMode.CONTROLLER)
|
||||||
|
return InputType.NONE; // none doesn't pass condition
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ComponentProcessor componentProcessor() {
|
public ComponentProcessor componentProcessor() {
|
||||||
return controlify$processor;
|
return controlify$processor;
|
||||||
|
@ -0,0 +1,16 @@
|
|||||||
|
package dev.isxander.controlify.mixins.compat.screen.vanilla;
|
||||||
|
|
||||||
|
import net.minecraft.client.gui.screens.inventory.CreativeModeInventoryScreen;
|
||||||
|
import net.minecraft.world.item.CreativeModeTab;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||||
|
import org.spongepowered.asm.mixin.gen.Invoker;
|
||||||
|
|
||||||
|
@Mixin(CreativeModeInventoryScreen.class)
|
||||||
|
public interface CreativeModeInventoryScreenAccessor {
|
||||||
|
@Accessor
|
||||||
|
CreativeModeTab getSelectedTab();
|
||||||
|
|
||||||
|
@Invoker
|
||||||
|
void invokeSelectTab(CreativeModeTab tab);
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
package dev.isxander.controlify.mixins.compat.screen.vanilla;
|
||||||
|
|
||||||
|
import dev.isxander.controlify.compatibility.screen.ScreenProcessor;
|
||||||
|
import dev.isxander.controlify.compatibility.screen.ScreenProcessorProvider;
|
||||||
|
import dev.isxander.controlify.compatibility.vanilla.CreativeModeInventoryScreenProcessor;
|
||||||
|
import net.minecraft.client.gui.screens.inventory.CreativeModeInventoryScreen;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.Unique;
|
||||||
|
|
||||||
|
@Mixin(CreativeModeInventoryScreen.class)
|
||||||
|
public class CreativeModeInventoryScreenMixin implements ScreenProcessorProvider {
|
||||||
|
@Unique private final CreativeModeInventoryScreenProcessor controlify$screenProcessor
|
||||||
|
= new CreativeModeInventoryScreenProcessor((CreativeModeInventoryScreen) (Object) this);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ScreenProcessor<?> screenProcessor() {
|
||||||
|
return controlify$screenProcessor;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
package dev.isxander.controlify.mixins.compat.screen.vanilla;
|
||||||
|
|
||||||
|
import net.minecraft.client.gui.components.Button;
|
||||||
|
import net.minecraft.client.gui.screens.multiplayer.JoinMultiplayerScreen;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||||
|
|
||||||
|
@Mixin(JoinMultiplayerScreen.class)
|
||||||
|
public interface JoinMultiplayerScreenAccessor {
|
||||||
|
@Accessor
|
||||||
|
Button getSelectButton();
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
package dev.isxander.controlify.mixins.compat.screen.vanilla;
|
||||||
|
|
||||||
|
import dev.isxander.controlify.compatibility.screen.ScreenProcessor;
|
||||||
|
import dev.isxander.controlify.compatibility.screen.ScreenProcessorProvider;
|
||||||
|
import dev.isxander.controlify.compatibility.vanilla.JoinMultiplayerScreenProcessor;
|
||||||
|
import net.minecraft.client.gui.screens.multiplayer.JoinMultiplayerScreen;
|
||||||
|
import net.minecraft.client.gui.screens.multiplayer.ServerSelectionList;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.Shadow;
|
||||||
|
import org.spongepowered.asm.mixin.Unique;
|
||||||
|
|
||||||
|
@Mixin(JoinMultiplayerScreen.class)
|
||||||
|
public class JoinMultiplayerScreenMixin implements ScreenProcessorProvider {
|
||||||
|
@Shadow protected ServerSelectionList serverSelectionList;
|
||||||
|
|
||||||
|
@Unique private final JoinMultiplayerScreenProcessor controlify$processor
|
||||||
|
= new JoinMultiplayerScreenProcessor((JoinMultiplayerScreen) (Object) this, serverSelectionList);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ScreenProcessor<?> screenProcessor() {
|
||||||
|
return controlify$processor;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
package dev.isxander.controlify.mixins.compat.screen.vanilla;
|
||||||
|
|
||||||
|
import dev.isxander.controlify.compatibility.screen.component.ComponentProcessor;
|
||||||
|
import dev.isxander.controlify.compatibility.screen.component.ComponentProcessorProvider;
|
||||||
|
import dev.isxander.controlify.compatibility.vanilla.LanguageSelectionListComponentProcessor;
|
||||||
|
import net.minecraft.client.gui.screens.LanguageSelectScreen;
|
||||||
|
import org.spongepowered.asm.mixin.Final;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.Shadow;
|
||||||
|
import org.spongepowered.asm.mixin.Unique;
|
||||||
|
|
||||||
|
@Mixin(LanguageSelectScreen.LanguageSelectionList.Entry.class)
|
||||||
|
public class LanguageSelectionListEntryMixin implements ComponentProcessorProvider {
|
||||||
|
@Shadow @Final String code;
|
||||||
|
|
||||||
|
@Unique private LanguageSelectionListComponentProcessor controlify$componentProcessor = null;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ComponentProcessor componentProcessor() {
|
||||||
|
// lazily create the component processor so `code` is defined
|
||||||
|
if (controlify$componentProcessor == null)
|
||||||
|
controlify$componentProcessor = new LanguageSelectionListComponentProcessor(code);
|
||||||
|
|
||||||
|
return controlify$componentProcessor;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
package dev.isxander.controlify.mixins.compat.screen.vanilla;
|
||||||
|
|
||||||
|
import net.minecraft.client.gui.screens.OptionsSubScreen;
|
||||||
|
import net.minecraft.client.gui.screens.Screen;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||||
|
|
||||||
|
@Mixin(OptionsSubScreen.class)
|
||||||
|
public interface OptionsSubScreenAccessor {
|
||||||
|
@Accessor
|
||||||
|
Screen getLastScreen();
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
package dev.isxander.controlify.mixins.compat.screen.vanilla;
|
||||||
|
|
||||||
|
import dev.isxander.controlify.compatibility.screen.component.ComponentProcessor;
|
||||||
|
import dev.isxander.controlify.compatibility.screen.component.ComponentProcessorProvider;
|
||||||
|
import dev.isxander.controlify.compatibility.vanilla.ServerSelectionListEntryComponentProcessor;
|
||||||
|
import net.minecraft.client.gui.screens.multiplayer.ServerSelectionList;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.Unique;
|
||||||
|
|
||||||
|
@Mixin(ServerSelectionList.Entry.class)
|
||||||
|
public class ServerSelectionListEntryMixin implements ComponentProcessorProvider {
|
||||||
|
@Unique private final ServerSelectionListEntryComponentProcessor controlify$componentProcessor
|
||||||
|
= new ServerSelectionListEntryComponentProcessor();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ComponentProcessor componentProcessor() {
|
||||||
|
return ((ServerSelectionList.Entry) (Object) this) instanceof ServerSelectionList.LANHeader
|
||||||
|
? ComponentProcessor.EMPTY
|
||||||
|
: controlify$componentProcessor;
|
||||||
|
}
|
||||||
|
}
|
@ -1,14 +1,21 @@
|
|||||||
package dev.isxander.controlify.mixins.core;
|
package dev.isxander.controlify.mixins.core;
|
||||||
|
|
||||||
import dev.isxander.controlify.Controlify;
|
import dev.isxander.controlify.Controlify;
|
||||||
|
import dev.isxander.controlify.gui.screen.BetaNoticeScreen;
|
||||||
import net.minecraft.client.Minecraft;
|
import net.minecraft.client.Minecraft;
|
||||||
|
import net.minecraft.client.gui.screens.Screen;
|
||||||
|
import net.minecraft.client.main.GameConfig;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.Shadow;
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
import org.spongepowered.asm.mixin.injection.Inject;
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||||
|
|
||||||
@Mixin(Minecraft.class)
|
@Mixin(Minecraft.class)
|
||||||
public class MinecraftMixin {
|
public abstract class MinecraftMixin {
|
||||||
|
@Shadow public abstract void setScreen(@Nullable Screen screen);
|
||||||
|
|
||||||
@Inject(method = "<init>", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/KeyboardHandler;setup(J)V", shift = At.Shift.AFTER))
|
@Inject(method = "<init>", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/KeyboardHandler;setup(J)V", shift = At.Shift.AFTER))
|
||||||
private void onInputInitialized(CallbackInfo ci) {
|
private void onInputInitialized(CallbackInfo ci) {
|
||||||
Controlify.instance().onInitializeInput();
|
Controlify.instance().onInitializeInput();
|
||||||
@ -18,4 +25,10 @@ public class MinecraftMixin {
|
|||||||
private void doPlayerLook(boolean tick, CallbackInfo ci) {
|
private void doPlayerLook(boolean tick, CallbackInfo ci) {
|
||||||
Controlify.instance().inGameInputHandler().processPlayerLook();
|
Controlify.instance().inGameInputHandler().processPlayerLook();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Inject(method = "<init>", at = @At("TAIL"))
|
||||||
|
private void showBetaScreen(GameConfig args, CallbackInfo ci) {
|
||||||
|
if (Controlify.instance().config().isFirstLaunch())
|
||||||
|
setScreen(new BetaNoticeScreen());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,21 @@
|
|||||||
|
package dev.isxander.controlify.mixins.feature.virtualmouse;
|
||||||
|
|
||||||
|
import com.llamalad7.mixinextras.injector.ModifyReturnValue;
|
||||||
|
import com.mojang.blaze3d.platform.InputConstants;
|
||||||
|
import dev.isxander.controlify.Controlify;
|
||||||
|
import org.lwjgl.glfw.GLFW;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
|
||||||
|
@Mixin(InputConstants.class)
|
||||||
|
public class InputConstantsMixin {
|
||||||
|
// must modify isKeyDown here because Screen.hasShiftDown has some instances that ask for this directly.
|
||||||
|
@ModifyReturnValue(method = "isKeyDown", at = @At("RETURN"))
|
||||||
|
private static boolean modifyIsKeyDown(boolean keyDown, long window, int key) {
|
||||||
|
if (key == GLFW.GLFW_KEY_LEFT_SHIFT) {
|
||||||
|
return keyDown || Controlify.instance().currentController().bindings().VMOUSE_SHIFT.held();
|
||||||
|
}
|
||||||
|
|
||||||
|
return keyDown;
|
||||||
|
}
|
||||||
|
}
|
@ -180,20 +180,21 @@ public class VirtualMouseHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean requiresVirtualMouse() {
|
public boolean requiresVirtualMouse() {
|
||||||
return Controlify.instance().currentInputMode() == InputMode.CONTROLLER
|
var isController = Controlify.instance().currentInputMode() == InputMode.CONTROLLER;
|
||||||
&& minecraft.screen != null
|
var hasScreen = minecraft.screen != null;
|
||||||
&& (ScreenProcessorProvider.provide(minecraft.screen).forceVirtualMouse()
|
var forceVirtualMouse = hasScreen && ScreenProcessorProvider.provide(minecraft.screen).forceVirtualMouse();
|
||||||
|| Controlify.instance().config().globalSettings().virtualMouseScreens.contains(minecraft.screen.getClass().getName())
|
var screenIsVMouseScreen = hasScreen && Controlify.instance().config().globalSettings().virtualMouseScreens.stream().anyMatch(s -> s.isAssignableFrom(minecraft.screen.getClass()));
|
||||||
);
|
|
||||||
|
return isController && hasScreen && (forceVirtualMouse || screenIsVMouseScreen);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void toggleVirtualMouse() {
|
public void toggleVirtualMouse() {
|
||||||
if (minecraft.screen == null) return;
|
if (minecraft.screen == null) return;
|
||||||
|
|
||||||
var screens = Controlify.instance().config().globalSettings().virtualMouseScreens;
|
var screens = Controlify.instance().config().globalSettings().virtualMouseScreens;
|
||||||
var screenName = minecraft.screen.getClass().getName();
|
var screenClass = minecraft.screen.getClass();
|
||||||
if (screens.contains(screenName)) {
|
if (screens.contains(screenClass)) {
|
||||||
screens.remove(screenName);
|
screens.remove(screenClass);
|
||||||
disableVirtualMouse();
|
disableVirtualMouse();
|
||||||
Controlify.instance().hideMouse(true);
|
Controlify.instance().hideMouse(true);
|
||||||
|
|
||||||
@ -204,7 +205,7 @@ public class VirtualMouseHandler {
|
|||||||
Component.translatable("controlify.toast.vmouse_disabled.description")
|
Component.translatable("controlify.toast.vmouse_disabled.description")
|
||||||
));
|
));
|
||||||
} else {
|
} else {
|
||||||
screens.add(screenName);
|
screens.add(screenClass);
|
||||||
enableVirtualMouse();
|
enableVirtualMouse();
|
||||||
|
|
||||||
minecraft.getToasts().addToast(SystemToast.multiline(
|
minecraft.getToasts().addToast(SystemToast.multiline(
|
||||||
|
@ -2,9 +2,9 @@
|
|||||||
"controlify.gui.category.global": "Global",
|
"controlify.gui.category.global": "Global",
|
||||||
"controlify.gui.current_controller": "Current Controller",
|
"controlify.gui.current_controller": "Current Controller",
|
||||||
"controlify.gui.current_controller.tooltip": "In Controlify's infancy, only one controller can be used at a time, this selects which one you want to use.",
|
"controlify.gui.current_controller.tooltip": "In Controlify's infancy, only one controller can be used at a time, this selects which one you want to use.",
|
||||||
"controlify.gui.vmouse_screens": "Virtual Mouse Screens",
|
"controlify.gui.out_of_focus_input": "Out of Focus Input",
|
||||||
"controlify.gui.vmouse_screens.tooltip": "A list of Screen class names that require virtual mouse to operate, this is usually due to the screen not being compatible with controller input.",
|
"controlify.gui.out_of_focus_input.tooltip": "If enabled, Controlify will still receive input even if the game window is not focused.",
|
||||||
"controlify.gui.vmouse_screens.placeholder": "Screen class name here...",
|
"controlify.gui.open_issue_tracker": "Open Issue Tracker",
|
||||||
|
|
||||||
"controlify.gui.group.config": "Config",
|
"controlify.gui.group.config": "Config",
|
||||||
"controlify.gui.group.config.tooltip": "Adjust the controller configuration.",
|
"controlify.gui.group.config.tooltip": "Adjust the controller configuration.",
|
||||||
@ -21,10 +21,8 @@
|
|||||||
"controlify.gui.right_stick_deadzone": "Right Stick Deadzone",
|
"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.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.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.button_activation_threshold": "Button Activation Threshold",
|
||||||
"controlify.gui.left_trigger_threshold.tooltip": "How far the left trigger needs to be pushed before registering as pressed.",
|
"controlify.gui.button_activation_threshold.tooltip": "How far a button 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.",
|
|
||||||
"controlify.gui.controller_theme": "Controller Theme",
|
"controlify.gui.controller_theme": "Controller Theme",
|
||||||
"controlify.gui.controller_theme.tooltip": "The theme to use for rendering controller buttons.",
|
"controlify.gui.controller_theme.tooltip": "The theme to use for rendering controller buttons.",
|
||||||
|
|
||||||
@ -33,6 +31,10 @@
|
|||||||
"controlify.gui.bind_input_awaiting": "Press any button",
|
"controlify.gui.bind_input_awaiting": "Press any button",
|
||||||
|
|
||||||
"controlify.gui.format.ticks": "%s ticks",
|
"controlify.gui.format.ticks": "%s ticks",
|
||||||
|
"controlify.gui.format.open": "OPEN URL",
|
||||||
|
|
||||||
|
"controlify.gui.error.title": "Could not open Controlify settings",
|
||||||
|
"controlify.gui.error.message": "You cannot change Controlify setttings when you have no controllers connected. Please connect a controller first.",
|
||||||
|
|
||||||
"controlify.gui.button": "Controller Settings...",
|
"controlify.gui.button": "Controller Settings...",
|
||||||
|
|
||||||
@ -48,6 +50,10 @@
|
|||||||
"controlify.controller_theme.xbox_one": "Xbox",
|
"controlify.controller_theme.xbox_one": "Xbox",
|
||||||
"controlify.controller_theme.dualshock4": "PS4",
|
"controlify.controller_theme.dualshock4": "PS4",
|
||||||
|
|
||||||
|
"controlify.binding.controlify.walk_forward": "Walk Forward",
|
||||||
|
"controlify.binding.controlify.walk_backward": "Walk Backward",
|
||||||
|
"controlify.binding.controlify.strafe_left": "Strafe Left",
|
||||||
|
"controlify.binding.controlify.strafe_right": "Strafe Right",
|
||||||
"controlify.binding.controlify.jump": "Jump",
|
"controlify.binding.controlify.jump": "Jump",
|
||||||
"controlify.binding.controlify.sneak": "Sneak",
|
"controlify.binding.controlify.sneak": "Sneak",
|
||||||
"controlify.binding.controlify.attack": "Attack",
|
"controlify.binding.controlify.attack": "Attack",
|
||||||
@ -61,10 +67,19 @@
|
|||||||
"controlify.binding.controlify.open_chat": "Open Chat",
|
"controlify.binding.controlify.open_chat": "Open Chat",
|
||||||
"controlify.binding.controlify.gui_press": "GUI Press",
|
"controlify.binding.controlify.gui_press": "GUI Press",
|
||||||
"controlify.binding.controlify.gui_back": "GUI Back",
|
"controlify.binding.controlify.gui_back": "GUI Back",
|
||||||
|
"controlify.binding.controlify.gui_next_tab": "GUI Next Tab",
|
||||||
|
"controlify.binding.controlify.gui_prev_tab": "GUI Previous Tab",
|
||||||
"controlify.binding.controlify.drop": "Drop Item",
|
"controlify.binding.controlify.drop": "Drop Item",
|
||||||
"controlify.binding.controlify.vmouse_lclick": "Virtual Mouse LClick",
|
"controlify.binding.controlify.vmouse_lclick": "Virtual Mouse LClick",
|
||||||
"controlify.binding.controlify.vmouse_rclick": "Virtual Mouse RClick",
|
"controlify.binding.controlify.vmouse_rclick": "Virtual Mouse RClick",
|
||||||
"controlify.binding.controlify.vmouse_mclick": "Virtual Mouse MClick",
|
"controlify.binding.controlify.vmouse_mclick": "Virtual Mouse MClick",
|
||||||
"controlify.binding.controlify.vmouse_escape": "Virtual Mouse Key Escape",
|
"controlify.binding.controlify.vmouse_escape": "Virtual Mouse Key Escape",
|
||||||
"controlify.binding.controlify.vmouse_toggle": "Toggle Virtual Mouse"
|
"controlify.binding.controlify.vmouse_shift": "Virtual Mouse Key Shift",
|
||||||
|
"controlify.binding.controlify.vmouse_toggle": "Toggle Virtual Mouse",
|
||||||
|
"controlify.binding.controlify.toggle_debug_menu": "Toggle F3 Menu",
|
||||||
|
|
||||||
|
"controlify.beta.title": "Controlify Beta Notice",
|
||||||
|
"controlify.beta.message": "You are currently using Controlify Beta.\n\nThis mod is a work in progress and will contain many bugs. Please, if you spot a bug in this mod or have a suggestion to make it even better, please create an issue on the %s!\n\nYou can always find the link to the issue tracker in Controlify's settings menu.",
|
||||||
|
"controlify.beta.message.link": "issue tracker",
|
||||||
|
"controlify.beta.button": "Open Issue Tracker..."
|
||||||
}
|
}
|
||||||
|
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 1.8 KiB |
3
src/main/resources/controlify.accessWidener
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
accessWidener v2 named
|
||||||
|
|
||||||
|
accessible class net/minecraft/client/gui/screens/LanguageSelectScreen$LanguageSelectionList
|
@ -10,10 +10,17 @@
|
|||||||
"compat.screen.vanilla.AbstractSelectionListMixin",
|
"compat.screen.vanilla.AbstractSelectionListMixin",
|
||||||
"compat.screen.vanilla.AbstractSliderButtonMixin",
|
"compat.screen.vanilla.AbstractSliderButtonMixin",
|
||||||
"compat.screen.vanilla.ContainerObjectSelectionListEntryMixin",
|
"compat.screen.vanilla.ContainerObjectSelectionListEntryMixin",
|
||||||
|
"compat.screen.vanilla.CreativeModeInventoryScreenAccessor",
|
||||||
|
"compat.screen.vanilla.CreativeModeInventoryScreenMixin",
|
||||||
|
"compat.screen.vanilla.JoinMultiplayerScreenAccessor",
|
||||||
|
"compat.screen.vanilla.JoinMultiplayerScreenMixin",
|
||||||
|
"compat.screen.vanilla.LanguageSelectionListEntryMixin",
|
||||||
|
"compat.screen.vanilla.OptionsSubScreenAccessor",
|
||||||
"compat.screen.vanilla.ScreenAccessor",
|
"compat.screen.vanilla.ScreenAccessor",
|
||||||
"compat.screen.vanilla.ScreenMixin",
|
"compat.screen.vanilla.ScreenMixin",
|
||||||
"compat.screen.vanilla.SelectWorldScreenAccessor",
|
"compat.screen.vanilla.SelectWorldScreenAccessor",
|
||||||
"compat.screen.vanilla.SelectWorldScreenMixin",
|
"compat.screen.vanilla.SelectWorldScreenMixin",
|
||||||
|
"compat.screen.vanilla.ServerSelectionListEntryMixin",
|
||||||
"compat.screen.vanilla.WorldSelectionListEntryMixin",
|
"compat.screen.vanilla.WorldSelectionListEntryMixin",
|
||||||
"core.ClientPacketListenerMixin",
|
"core.ClientPacketListenerMixin",
|
||||||
"core.KeyboardHandlerMixin",
|
"core.KeyboardHandlerMixin",
|
||||||
@ -22,6 +29,7 @@
|
|||||||
"feature.bind.KeyMappingAccessor",
|
"feature.bind.KeyMappingAccessor",
|
||||||
"feature.settingsbutton.ControlsScreenMixin",
|
"feature.settingsbutton.ControlsScreenMixin",
|
||||||
"feature.virtualmouse.GameRendererMixin",
|
"feature.virtualmouse.GameRendererMixin",
|
||||||
|
"feature.virtualmouse.InputConstantsMixin",
|
||||||
"feature.virtualmouse.KeyboardHandlerAccessor",
|
"feature.virtualmouse.KeyboardHandlerAccessor",
|
||||||
"feature.virtualmouse.MinecraftMixin",
|
"feature.virtualmouse.MinecraftMixin",
|
||||||
"feature.virtualmouse.MouseHandlerAccessor",
|
"feature.virtualmouse.MouseHandlerAccessor",
|
||||||
|
@ -25,6 +25,7 @@
|
|||||||
"mixins": [
|
"mixins": [
|
||||||
"controlify.mixins.json"
|
"controlify.mixins.json"
|
||||||
],
|
],
|
||||||
|
"accessWidener": "controlify.accesswidener",
|
||||||
"depends": {
|
"depends": {
|
||||||
"fabricloader": ">=0.14.0",
|
"fabricloader": ">=0.14.0",
|
||||||
"minecraft": "~1.19.4-",
|
"minecraft": "~1.19.4-",
|
||||||
|
BIN
src/main/resources/icon.png
Normal file
After Width: | Height: | Size: 32 KiB |