forked from Clones/Controlify
compound joysticks, button guide in screens, improve API
This commit is contained in:
@ -3,8 +3,10 @@ package dev.isxander.controlify;
|
||||
import com.mojang.blaze3d.Blaze3D;
|
||||
import com.mojang.logging.LogUtils;
|
||||
import dev.isxander.controlify.api.ControlifyApi;
|
||||
import dev.isxander.controlify.api.entrypoint.ControlifyEntrypoint;
|
||||
import dev.isxander.controlify.controller.Controller;
|
||||
import dev.isxander.controlify.controller.ControllerState;
|
||||
import dev.isxander.controlify.controller.joystick.CompoundJoystickController;
|
||||
import dev.isxander.controlify.gui.screen.ControllerDeadzoneCalibrationScreen;
|
||||
import dev.isxander.controlify.screenop.ScreenProcessorProvider;
|
||||
import dev.isxander.controlify.config.ControlifyConfig;
|
||||
@ -16,8 +18,8 @@ import dev.isxander.controlify.mixins.feature.virtualmouse.MouseHandlerAccessor;
|
||||
import dev.isxander.controlify.utils.ToastUtils;
|
||||
import dev.isxander.controlify.virtualmouse.VirtualMouseHandler;
|
||||
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.components.toasts.SystemToast;
|
||||
import net.minecraft.client.gui.screens.Screen;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@ -72,6 +74,8 @@ public class Controlify implements ControlifyApi {
|
||||
}
|
||||
}
|
||||
|
||||
checkCompoundJoysticks();
|
||||
|
||||
if (Controller.CONTROLLERS.isEmpty()) {
|
||||
LOGGER.info("No controllers found.");
|
||||
}
|
||||
@ -82,19 +86,41 @@ public class Controlify implements ControlifyApi {
|
||||
|
||||
// listen for new controllers
|
||||
GLFW.glfwSetJoystickCallback((jid, event) -> {
|
||||
if (event == GLFW.GLFW_CONNECTED) {
|
||||
this.onControllerHotplugged(jid);
|
||||
} else if (event == GLFW.GLFW_DISCONNECTED) {
|
||||
this.onControllerDisconnect(jid);
|
||||
try {
|
||||
if (event == GLFW.GLFW_CONNECTED) {
|
||||
this.onControllerHotplugged(jid);
|
||||
} else if (event == GLFW.GLFW_DISCONNECTED) {
|
||||
this.onControllerDisconnect(jid);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
|
||||
ClientTickEvents.START_CLIENT_TICK.register(this::tick);
|
||||
|
||||
FabricLoader.getInstance().getEntrypoints("controlify", ControlifyEntrypoint.class).forEach(entrypoint -> {
|
||||
try {
|
||||
entrypoint.onControllersDiscovered(this);
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Failed to run `onControllersDiscovered` on Controlify entrypoint: " + entrypoint.getClass().getName(), e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void initializeControlify() {
|
||||
LOGGER.info("Pre-initializing Controlify...");
|
||||
|
||||
this.inGameInputHandler = new InGameInputHandler(Controller.DUMMY); // initialize with dummy controller before connection in case of no controller
|
||||
this.virtualMouseHandler = new VirtualMouseHandler();
|
||||
|
||||
FabricLoader.getInstance().getEntrypoints("controlify", ControlifyEntrypoint.class).forEach(entrypoint -> {
|
||||
try {
|
||||
entrypoint.onControlifyPreInit(this);
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Failed to run `onControlifyPreInit` on Controlify entrypoint: " + entrypoint.getClass().getName(), e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void tick(Minecraft client) {
|
||||
@ -109,8 +135,13 @@ public class Controlify implements ControlifyApi {
|
||||
}
|
||||
}
|
||||
|
||||
boolean outOfFocus = !config().globalSettings().outOfFocusInput && !client.isWindowActive();
|
||||
|
||||
for (var controller : Controller.CONTROLLERS.values()) {
|
||||
controller.updateState();
|
||||
if (!outOfFocus)
|
||||
controller.updateState();
|
||||
else
|
||||
controller.clearState();
|
||||
}
|
||||
|
||||
ControllerState state = currentController == null ? ControllerState.EMPTY : currentController.state();
|
||||
@ -127,7 +158,7 @@ public class Controlify implements ControlifyApi {
|
||||
}
|
||||
}
|
||||
|
||||
if (!config().globalSettings().outOfFocusInput && !client.isWindowActive())
|
||||
if (outOfFocus)
|
||||
state = ControllerState.EMPTY;
|
||||
|
||||
if (state.hasAnyInput())
|
||||
@ -171,11 +202,14 @@ public class Controlify implements ControlifyApi {
|
||||
config().loadOrCreateControllerData(currentController);
|
||||
|
||||
this.askToSwitchController(controller);
|
||||
|
||||
checkCompoundJoysticks();
|
||||
}
|
||||
|
||||
private void onControllerDisconnect(int jid) {
|
||||
var controller = Controller.CONTROLLERS.remove(jid);
|
||||
if (controller != null) {
|
||||
Controller.CONTROLLERS.values().stream().filter(controller -> controller.joystickId() == jid).findAny().ifPresent(controller -> {
|
||||
Controller.CONTROLLERS.remove(controller.uid(), controller);
|
||||
|
||||
setCurrentController(Controller.CONTROLLERS.values().stream().findFirst().orElse(null));
|
||||
LOGGER.info("Controller disconnected: " + controller.name());
|
||||
this.setInputMode(currentController == null ? InputMode.KEYBOARD_MOUSE : InputMode.CONTROLLER);
|
||||
@ -185,7 +219,29 @@ public class Controlify implements ControlifyApi {
|
||||
Component.translatable("controlify.toast.controller_disconnected.description", controller.name()),
|
||||
false
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
checkCompoundJoysticks();
|
||||
}
|
||||
|
||||
private void checkCompoundJoysticks() {
|
||||
config().getCompoundJoysticks().values().forEach(info -> {
|
||||
try {
|
||||
if (info.isLoaded() && !info.canBeUsed()) {
|
||||
LOGGER.warn("Unloading compound joystick " + info.friendlyName() + " due to missing controllers.");
|
||||
Controller.CONTROLLERS.remove(info.type().identifier());
|
||||
}
|
||||
|
||||
if (!info.isLoaded() && info.canBeUsed()) {
|
||||
LOGGER.info("Loading compound joystick " + info.type().identifier() + ".");
|
||||
CompoundJoystickController controller = info.attemptCreate().orElseThrow();
|
||||
Controller.CONTROLLERS.put(info.type().identifier(), controller);
|
||||
config().loadOrCreateControllerData(controller);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void askToSwitchController(Controller<?, ?> controller) {
|
||||
|
@ -9,6 +9,11 @@ import org.jetbrains.annotations.NotNull;
|
||||
/**
|
||||
* Interface with Controlify in a manner where you don't need to worry about updates
|
||||
* breaking! This is the recommended way to interact with Controlify.
|
||||
* <p>
|
||||
* Alternatively, to use Controlify directly, you can use {@link Controlify#instance()}. Though
|
||||
* beware, things may break at any time!
|
||||
* <p>
|
||||
* Anything that is asked for from this API is safe to use, even if it is not in the API package.
|
||||
*/
|
||||
public interface ControlifyApi {
|
||||
/**
|
||||
@ -16,6 +21,9 @@ public interface ControlifyApi {
|
||||
*/
|
||||
@NotNull Controller<?, ?> currentController();
|
||||
|
||||
/**
|
||||
* Get the current input mode for the game.
|
||||
*/
|
||||
@NotNull InputMode currentInputMode();
|
||||
void setInputMode(@NotNull InputMode mode);
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
package dev.isxander.controlify.api.bind;
|
||||
|
||||
import dev.isxander.controlify.api.ControlifyApi;
|
||||
import dev.isxander.controlify.bindings.BindingSupplier;
|
||||
import dev.isxander.controlify.bindings.ControllerBindings;
|
||||
import dev.isxander.controlify.bindings.GamepadBinds;
|
||||
@ -8,12 +9,17 @@ import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
import java.util.function.BooleanSupplier;
|
||||
|
||||
/**
|
||||
* Handles registering new bindings for controllers.
|
||||
* <p>
|
||||
* Should be called within {@link dev.isxander.controlify.api.entrypoint.ControlifyEntrypoint#onControlifyPreInit(ControlifyApi)}
|
||||
*/
|
||||
public interface ControlifyBindingsApi {
|
||||
/**
|
||||
* Registers a custom binding for all available controllers.
|
||||
* If the controller is not a gamepad, the binding with be empty by default.
|
||||
*
|
||||
* @param bind the default gamepad bind
|
||||
* @param bind the default gamepad bind - joysticks are unset by default
|
||||
* @param id the identifier for the binding, the namespace should be your modid.
|
||||
* @return the binding supplier to fetch the binding for a specific controller.
|
||||
*/
|
||||
|
@ -0,0 +1,34 @@
|
||||
package dev.isxander.controlify.api.buttonguide;
|
||||
|
||||
import dev.isxander.controlify.bindings.ControllerBinding;
|
||||
import dev.isxander.controlify.bindings.ControllerBindings;
|
||||
import dev.isxander.controlify.gui.ButtonGuideRenderer;
|
||||
import net.minecraft.client.gui.components.AbstractButton;
|
||||
import net.minecraft.client.gui.screens.Screen;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Adds a guide to a button. This does not invoke the button press on binding trigger, only renders the guide.
|
||||
* This should be called every time a button is initialised, like in {@link Screen#init()}
|
||||
*/
|
||||
public interface ButtonGuideApi {
|
||||
/**
|
||||
* Makes the button render the image of the binding specified.
|
||||
* This does not invoke the button press on binding trigger, only renders the guide.
|
||||
* Custom behaviour should be handled inside a {@link dev.isxander.controlify.screenop.ScreenProcessor} or {@link dev.isxander.controlify.screenop.ComponentProcessor}
|
||||
*
|
||||
* @param button button to render the guide for
|
||||
* @param binding gets the binding to render
|
||||
* @param position where the guide should be rendered relative to the button
|
||||
* @param renderPredicate whether the guide should be rendered
|
||||
*/
|
||||
static <T extends AbstractButton> void addGuideToButton(
|
||||
T button,
|
||||
Function<ControllerBindings<?>, ControllerBinding<?>> binding,
|
||||
ButtonRenderPosition position,
|
||||
ButtonGuidePredicate<T> renderPredicate) {
|
||||
ButtonGuideRenderer.registerBindingForButton(button, binding, position, renderPredicate);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,13 @@
|
||||
package dev.isxander.controlify.api.buttonguide;
|
||||
|
||||
import net.minecraft.client.gui.components.AbstractButton;
|
||||
import net.minecraft.client.gui.components.AbstractWidget;
|
||||
import net.minecraft.client.gui.screens.Screen;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface ButtonGuidePredicate<T extends AbstractButton> {
|
||||
boolean shouldDisplay(T button);
|
||||
|
||||
ButtonGuidePredicate<AbstractButton> FOCUS_ONLY = AbstractWidget::isFocused;
|
||||
ButtonGuidePredicate<AbstractButton> ALWAYS = btn -> true;
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package dev.isxander.controlify.api.buttonguide;
|
||||
|
||||
/**
|
||||
* Where the guide should be rendered relative to the button.
|
||||
*/
|
||||
public enum ButtonRenderPosition {
|
||||
/**
|
||||
* Renders outside the button the left.
|
||||
*/
|
||||
LEFT,
|
||||
/**
|
||||
* Renders outside the button the right.
|
||||
*/
|
||||
RIGHT,
|
||||
/**
|
||||
* Renders inside the button on the left of the button text.
|
||||
*/
|
||||
TEXT
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
package dev.isxander.controlify.api.buttonguide;
|
||||
|
||||
import dev.isxander.controlify.controller.Controller;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.multiplayer.ClientLevel;
|
||||
import net.minecraft.client.player.LocalPlayer;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.world.phys.HitResult;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Supplies the text to display for a guide action based on the current context.
|
||||
* If return is empty, the action will not be displayed.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface GuideActionNameSupplier {
|
||||
Optional<Component> supply(
|
||||
Minecraft client,
|
||||
LocalPlayer player,
|
||||
ClientLevel level,
|
||||
HitResult hitResult,
|
||||
Controller<?, ?> controller
|
||||
);
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package dev.isxander.controlify.api.entrypoint;
|
||||
|
||||
import dev.isxander.controlify.api.ControlifyApi;
|
||||
|
||||
public interface ControlifyEntrypoint {
|
||||
/**
|
||||
* Called once Controlify has been fully initialised. And all controllers have
|
||||
* been discovered and loaded.
|
||||
* Due to the nature of the resource-pack system, this is called
|
||||
* very late in the game's lifecycle (once the resources have been reloaded).
|
||||
*/
|
||||
void onControllersDiscovered(ControlifyApi controlify);
|
||||
|
||||
/**
|
||||
* Called once Controlify has initialised some systems but controllers
|
||||
* have not yet been discovered and constructed. This is the ideal
|
||||
* time to register events in preparation for controller discovery.
|
||||
*/
|
||||
void onControlifyPreInit(ControlifyApi controlify);
|
||||
}
|
@ -3,7 +3,7 @@ package dev.isxander.controlify.api.event;
|
||||
import dev.isxander.controlify.InputMode;
|
||||
import dev.isxander.controlify.bindings.ControllerBindings;
|
||||
import dev.isxander.controlify.controller.Controller;
|
||||
import dev.isxander.controlify.api.buttonguide.ButtonGuideRegistry;
|
||||
import dev.isxander.controlify.api.ingameguide.IngameGuideRegistry;
|
||||
import net.fabricmc.fabric.api.event.Event;
|
||||
import net.fabricmc.fabric.api.event.EventFactory;
|
||||
|
||||
@ -29,9 +29,9 @@ public final class ControlifyEvents {
|
||||
/**
|
||||
* Triggers when the button guide entries are being populated, so you can add more of your own.
|
||||
*/
|
||||
public static final Event<ButtonGuideRegistryEvent> BUTTON_GUIDE_REGISTRY = EventFactory.createArrayBacked(ButtonGuideRegistryEvent.class, callbacks -> (bindings, registry) -> {
|
||||
for (ButtonGuideRegistryEvent callback : callbacks) {
|
||||
callback.onRegisterButtonGuide(bindings, registry);
|
||||
public static final Event<IngameGuideRegistryEvent> INGAME_GUIDE_REGISTRY = EventFactory.createArrayBacked(IngameGuideRegistryEvent.class, callbacks -> (bindings, registry) -> {
|
||||
for (IngameGuideRegistryEvent callback : callbacks) {
|
||||
callback.onRegisterIngameGuide(bindings, registry);
|
||||
}
|
||||
});
|
||||
|
||||
@ -55,8 +55,8 @@ public final class ControlifyEvents {
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface ButtonGuideRegistryEvent {
|
||||
void onRegisterButtonGuide(ControllerBindings<?> bindings, ButtonGuideRegistry registry);
|
||||
public interface IngameGuideRegistryEvent {
|
||||
void onRegisterIngameGuide(ControllerBindings<?> bindings, IngameGuideRegistry registry);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
|
@ -1,4 +1,4 @@
|
||||
package dev.isxander.controlify.api.buttonguide;
|
||||
package dev.isxander.controlify.api.ingameguide;
|
||||
|
||||
/**
|
||||
* Whether the action should be on the left or right list.
|
@ -1,4 +1,4 @@
|
||||
package dev.isxander.controlify.api.buttonguide;
|
||||
package dev.isxander.controlify.api.ingameguide;
|
||||
|
||||
/**
|
||||
* Defines how the action is sorted in the list. All default Controlify actions are {@link #NORMAL}.
|
@ -0,0 +1,16 @@
|
||||
package dev.isxander.controlify.api.ingameguide;
|
||||
|
||||
import net.minecraft.network.chat.Component;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Supplies the text to display for a guide action based on the current context.
|
||||
* If return is empty, the action will not be displayed.
|
||||
* <p>
|
||||
* This is supplied once every tick.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface GuideActionNameSupplier {
|
||||
Optional<Component> supply(IngameGuideContext ctx);
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
package dev.isxander.controlify.api.ingameguide;
|
||||
|
||||
import dev.isxander.controlify.controller.Controller;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.multiplayer.ClientLevel;
|
||||
import net.minecraft.client.player.LocalPlayer;
|
||||
import net.minecraft.world.phys.HitResult;
|
||||
|
||||
public record IngameGuideContext(Minecraft client,
|
||||
LocalPlayer player,
|
||||
ClientLevel level,
|
||||
HitResult hitResult,
|
||||
Controller<?, ?> controller) {
|
||||
}
|
@ -1,13 +1,13 @@
|
||||
package dev.isxander.controlify.api.buttonguide;
|
||||
package dev.isxander.controlify.api.ingameguide;
|
||||
|
||||
import dev.isxander.controlify.bindings.ControllerBinding;
|
||||
|
||||
/**
|
||||
* Allows you to register your own actions to the button guide.
|
||||
* This should be called through {@link dev.isxander.controlify.api.event.ControlifyEvents#BUTTON_GUIDE_REGISTRY} as
|
||||
* This should be called through {@link dev.isxander.controlify.api.event.ControlifyEvents#INGAME_GUIDE_REGISTRY} as
|
||||
* these should be called every time the guide is initialised.
|
||||
*/
|
||||
public interface ButtonGuideRegistry {
|
||||
public interface IngameGuideRegistry {
|
||||
/**
|
||||
* Registers a new action to the button guide.
|
||||
*
|
@ -4,6 +4,7 @@ import java.util.Set;
|
||||
|
||||
/**
|
||||
* An interface to implement by gui components to define snap points for virtual mouse snapping.
|
||||
* Can also be implemented in a mixin to improve compatibility.
|
||||
*/
|
||||
public interface ISnapBehaviour {
|
||||
Set<SnapPoint> getSnapPoints();
|
||||
|
@ -34,6 +34,7 @@ public class ControllerBindings<T extends ControllerState> {
|
||||
OPEN_CHAT,
|
||||
GUI_PRESS, GUI_BACK,
|
||||
GUI_NEXT_TAB, GUI_PREV_TAB,
|
||||
GUI_ABSTRACT_ACTION_1, GUI_ABSTRACT_ACTION_2,
|
||||
PICK_BLOCK,
|
||||
TOGGLE_HUD_VISIBILITY,
|
||||
SHOW_PLAYER_LIST,
|
||||
@ -77,6 +78,8 @@ public class ControllerBindings<T extends ControllerState> {
|
||||
register(GUI_BACK = new ControllerBinding<>(controller, GamepadBinds.B_BUTTON, new ResourceLocation("controlify", "gui_back")));
|
||||
register(GUI_NEXT_TAB = new ControllerBinding<>(controller, GamepadBinds.RIGHT_BUMPER, new ResourceLocation("controlify", "gui_next_tab")));
|
||||
register(GUI_PREV_TAB = new ControllerBinding<>(controller, GamepadBinds.LEFT_BUMPER, new ResourceLocation("controlify", "gui_prev_tab")));
|
||||
register(GUI_ABSTRACT_ACTION_1 = new ControllerBinding<>(controller, GamepadBinds.X_BUTTON, new ResourceLocation("controlify", "gui_abstract_action_1")));
|
||||
register(GUI_ABSTRACT_ACTION_2 = new ControllerBinding<>(controller, GamepadBinds.Y_BUTTON, new ResourceLocation("controlify", "gui_abstract_action_2")));
|
||||
register(PICK_BLOCK = new ControllerBinding<>(controller, GamepadBinds.DPAD_LEFT, new ResourceLocation("controlify", "pick_block"), options.keyPickItem, () -> false));
|
||||
register(TOGGLE_HUD_VISIBILITY = new ControllerBinding<>(controller, new EmptyBind<>(), new ResourceLocation("controlify", "toggle_hud_visibility")));
|
||||
register(SHOW_PLAYER_LIST = new ControllerBinding<>(controller, GamepadBinds.DPAD_RIGHT, new ResourceLocation("controlify", "show_player_list"), options.keyPlayerList, () -> false));
|
||||
@ -138,6 +141,11 @@ public class ControllerBindings<T extends ControllerState> {
|
||||
|
||||
public void fromJson(JsonObject json) {
|
||||
for (var binding : registry().values()) {
|
||||
if (!json.has(binding.id().toString())) {
|
||||
Controlify.LOGGER.warn("Missing control: " + binding.id() + " in config file. Skipping!");
|
||||
continue;
|
||||
}
|
||||
|
||||
var bind = json.get(binding.id().toString()).getAsJsonObject();
|
||||
if (bind == null) {
|
||||
Controlify.LOGGER.warn("Unknown control: " + binding.id() + " in config file. Skipping!");
|
||||
|
@ -4,7 +4,7 @@ import com.google.gson.JsonObject;
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import dev.isxander.controlify.controller.*;
|
||||
import dev.isxander.controlify.controller.gamepad.GamepadController;
|
||||
import dev.isxander.controlify.controller.joystick.JoystickController;
|
||||
import dev.isxander.controlify.controller.joystick.SingleJoystickController;
|
||||
import dev.isxander.controlify.gui.DrawSize;
|
||||
|
||||
public interface IBind<S extends ControllerState> {
|
||||
@ -28,7 +28,7 @@ public interface IBind<S extends ControllerState> {
|
||||
|
||||
if (controller instanceof GamepadController gamepad && type.equals(GamepadBinds.BIND_ID)) {
|
||||
return GamepadBinds.fromJson(json).map(bind -> (IBind<T>) bind.forGamepad(gamepad)).orElse(new EmptyBind<>());
|
||||
} else if (controller instanceof JoystickController joystick) {
|
||||
} else if (controller instanceof SingleJoystickController joystick) {
|
||||
return (IBind<T>) switch (type) {
|
||||
case JoystickButtonBind.BIND_ID -> JoystickButtonBind.fromJson(json, joystick);
|
||||
case JoystickHatBind.BIND_ID -> JoystickHatBind.fromJson(json, joystick);
|
||||
|
@ -18,11 +18,11 @@ import java.util.Objects;
|
||||
public class JoystickAxisBind implements IBind<JoystickState> {
|
||||
public static final String BIND_ID = "joystick_axis";
|
||||
|
||||
private final JoystickController joystick;
|
||||
private final JoystickController<?> joystick;
|
||||
private final int axisIndex;
|
||||
private final AxisDirection direction;
|
||||
|
||||
public JoystickAxisBind(JoystickController joystick, int axisIndex, AxisDirection direction) {
|
||||
public JoystickAxisBind(JoystickController<?> joystick, int axisIndex, AxisDirection direction) {
|
||||
this.joystick = joystick;
|
||||
this.axisIndex = axisIndex;
|
||||
this.direction = direction;
|
||||
@ -93,7 +93,7 @@ public class JoystickAxisBind implements IBind<JoystickState> {
|
||||
return Objects.hash(axisIndex, direction);
|
||||
}
|
||||
|
||||
public static JoystickAxisBind fromJson(JsonObject object, JoystickController joystick) {
|
||||
public static JoystickAxisBind fromJson(JsonObject object, JoystickController<?> joystick) {
|
||||
var axisIndex = object.get("axis").getAsInt();
|
||||
var direction = AxisDirection.valueOf(object.get("direction").getAsString());
|
||||
return new JoystickAxisBind(joystick, axisIndex, direction);
|
||||
|
@ -7,7 +7,6 @@ import dev.isxander.controlify.controller.Controller;
|
||||
import dev.isxander.controlify.controller.joystick.JoystickController;
|
||||
import dev.isxander.controlify.controller.joystick.JoystickState;
|
||||
import dev.isxander.controlify.gui.DrawSize;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.GuiComponent;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
@ -16,10 +15,10 @@ import java.util.Objects;
|
||||
public class JoystickButtonBind implements IBind<JoystickState> {
|
||||
public static final String BIND_ID = "joystick_button";
|
||||
|
||||
private final JoystickController joystick;
|
||||
private final JoystickController<?> joystick;
|
||||
private final int buttonIndex;
|
||||
|
||||
public JoystickButtonBind(JoystickController joystick, int buttonIndex) {
|
||||
public JoystickButtonBind(JoystickController<?> joystick, int buttonIndex) {
|
||||
this.joystick = joystick;
|
||||
this.buttonIndex = buttonIndex;
|
||||
}
|
||||
@ -71,7 +70,7 @@ public class JoystickButtonBind implements IBind<JoystickState> {
|
||||
return Objects.hash(buttonIndex, joystick.uid());
|
||||
}
|
||||
|
||||
public static JoystickButtonBind fromJson(JsonObject object, JoystickController joystick) {
|
||||
public static JoystickButtonBind fromJson(JsonObject object, JoystickController<?> joystick) {
|
||||
var buttonIndex = object.get("button").getAsInt();
|
||||
return new JoystickButtonBind(joystick, buttonIndex);
|
||||
}
|
||||
|
@ -7,7 +7,6 @@ import dev.isxander.controlify.controller.Controller;
|
||||
import dev.isxander.controlify.controller.joystick.JoystickController;
|
||||
import dev.isxander.controlify.controller.joystick.JoystickState;
|
||||
import dev.isxander.controlify.gui.DrawSize;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.GuiComponent;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
@ -16,11 +15,11 @@ import java.util.Objects;
|
||||
public class JoystickHatBind implements IBind<JoystickState> {
|
||||
public static final String BIND_ID = "joystick_hat";
|
||||
|
||||
private final JoystickController joystick;
|
||||
private final JoystickController<?> joystick;
|
||||
private final int hatIndex;
|
||||
private final JoystickState.HatState hatState;
|
||||
|
||||
public JoystickHatBind(JoystickController joystick, int hatIndex, JoystickState.HatState hatState) {
|
||||
public JoystickHatBind(JoystickController<?> joystick, int hatIndex, JoystickState.HatState hatState) {
|
||||
this.joystick = joystick;
|
||||
this.hatIndex = hatIndex;
|
||||
this.hatState = hatState;
|
||||
@ -84,7 +83,7 @@ public class JoystickHatBind implements IBind<JoystickState> {
|
||||
return Objects.hash(hatIndex, hatState, joystick.uid());
|
||||
}
|
||||
|
||||
public static JoystickHatBind fromJson(JsonObject object, JoystickController joystick) {
|
||||
public static JoystickHatBind fromJson(JsonObject object, JoystickController<?> joystick) {
|
||||
var hatIndex = object.get("hat").getAsInt();
|
||||
var hatState = JoystickState.HatState.valueOf(object.get("state").getAsString());
|
||||
return new JoystickHatBind(joystick, hatIndex, hatState);
|
||||
|
@ -3,12 +3,17 @@ package dev.isxander.controlify.config;
|
||||
import com.google.gson.*;
|
||||
import dev.isxander.controlify.Controlify;
|
||||
import dev.isxander.controlify.controller.Controller;
|
||||
import dev.isxander.controlify.controller.joystick.CompoundJoystickInfo;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class ControlifyConfig {
|
||||
public static final Path CONFIG_PATH = FabricLoader.getInstance().getConfigDir().resolve("controlify.json");
|
||||
@ -24,6 +29,7 @@ public class ControlifyConfig {
|
||||
|
||||
private String currentControllerUid;
|
||||
private JsonObject controllerData = new JsonObject();
|
||||
private Map<String, CompoundJoystickInfo> compoundJoysticks = Map.of();
|
||||
private GlobalSettings globalSettings = new GlobalSettings();
|
||||
private boolean firstLaunch;
|
||||
|
||||
@ -69,9 +75,10 @@ public class ControlifyConfig {
|
||||
}
|
||||
|
||||
controllerData = newControllerData;
|
||||
config.add("controllers", controllerData);
|
||||
config.add("global", GSON.toJsonTree(globalSettings));
|
||||
config.addProperty("current_controller", currentControllerUid = controlify.currentController().uid());
|
||||
config.add("controllers", controllerData);
|
||||
config.add("compound_joysticks", GSON.toJsonTree(compoundJoysticks.values().toArray(new CompoundJoystickInfo[0])));
|
||||
config.add("global", GSON.toJsonTree(globalSettings));
|
||||
|
||||
return config;
|
||||
}
|
||||
@ -97,6 +104,13 @@ public class ControlifyConfig {
|
||||
}
|
||||
}
|
||||
|
||||
this.compoundJoysticks = object
|
||||
.getAsJsonArray("compound_joysticks")
|
||||
.asList()
|
||||
.stream()
|
||||
.map(element -> GSON.fromJson(element, CompoundJoystickInfo.class))
|
||||
.collect(Collectors.toMap(info -> info.type().identifier(), Function.identity()));
|
||||
|
||||
if (object.has("current_controller")) {
|
||||
currentControllerUid = object.get("current_controller").getAsString();
|
||||
} else {
|
||||
@ -128,6 +142,14 @@ public class ControlifyConfig {
|
||||
}
|
||||
}
|
||||
|
||||
public Optional<JsonObject> getLoadedControllerConfig(String uid) {
|
||||
return Optional.ofNullable(controllerData.getAsJsonObject(uid));
|
||||
}
|
||||
|
||||
public Map<String, CompoundJoystickInfo> getCompoundJoysticks() {
|
||||
return compoundJoysticks;
|
||||
}
|
||||
|
||||
public GlobalSettings globalSettings() {
|
||||
return globalSettings;
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package dev.isxander.controlify.config.gui;
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import dev.isxander.controlify.bindings.*;
|
||||
import dev.isxander.controlify.controller.joystick.JoystickController;
|
||||
import dev.isxander.controlify.controller.joystick.SingleJoystickController;
|
||||
import dev.isxander.controlify.controller.joystick.JoystickState;
|
||||
import dev.isxander.controlify.screenop.ComponentProcessor;
|
||||
import dev.isxander.controlify.screenop.ScreenProcessor;
|
||||
@ -18,9 +19,9 @@ import org.lwjgl.glfw.GLFW;
|
||||
|
||||
public class JoystickBindController implements Controller<IBind<JoystickState>> {
|
||||
private final Option<IBind<JoystickState>> option;
|
||||
private final JoystickController controller;
|
||||
private final JoystickController<?> controller;
|
||||
|
||||
public JoystickBindController(Option<IBind<JoystickState>> option, JoystickController controller) {
|
||||
public JoystickBindController(Option<IBind<JoystickState>> option, JoystickController<?> controller) {
|
||||
this.option = option;
|
||||
this.controller = controller;
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import dev.isxander.controlify.controller.gamepad.GamepadController;
|
||||
import dev.isxander.controlify.controller.gamepad.GamepadState;
|
||||
import dev.isxander.controlify.controller.gamepad.BuiltinGamepadTheme;
|
||||
import dev.isxander.controlify.controller.joystick.JoystickController;
|
||||
import dev.isxander.controlify.controller.joystick.SingleJoystickController;
|
||||
import dev.isxander.controlify.controller.joystick.JoystickState;
|
||||
import dev.isxander.controlify.gui.screen.ControllerDeadzoneCalibrationScreen;
|
||||
import dev.isxander.yacl.api.*;
|
||||
@ -79,14 +80,18 @@ public class YACLHelper {
|
||||
}
|
||||
|
||||
private static ConfigCategory createControllerCategory(Controller<?, ?> controller) {
|
||||
if (!controller.canBeUsed()) {
|
||||
return PlaceholderCategory.createBuilder()
|
||||
.name(Component.literal(controller.name()))
|
||||
.tooltip(Component.translatable("controlify.gui.controller_unavailable"))
|
||||
.screen((minecraft, yacl) -> yacl)
|
||||
.build();
|
||||
}
|
||||
|
||||
var category = ConfigCategory.createBuilder();
|
||||
|
||||
category.name(Component.literal(controller.name()));
|
||||
|
||||
if (!controller.canBeUsed()) {
|
||||
category.tooltip(Component.translatable("controlify.gui.controller_unavailable"));
|
||||
}
|
||||
|
||||
var config = controller.config();
|
||||
var def = controller.defaultConfig();
|
||||
|
||||
@ -126,9 +131,15 @@ public class YACLHelper {
|
||||
.controller(BooleanController::new)
|
||||
.build())
|
||||
.option(Option.createBuilder(boolean.class)
|
||||
.name(Component.translatable("controlify.gui.show_guide"))
|
||||
.tooltip(Component.translatable("controlify.gui.show_guide.tooltip"))
|
||||
.binding(def.showGuide, () -> config.showGuide, v -> config.showGuide = v)
|
||||
.name(Component.translatable("controlify.gui.show_ingame_guide"))
|
||||
.tooltip(Component.translatable("controlify.gui.show_ingame_guide.tooltip"))
|
||||
.binding(def.showIngameGuide, () -> config.showIngameGuide, v -> config.showIngameGuide = v)
|
||||
.controller(TickBoxController::new)
|
||||
.build())
|
||||
.option(Option.createBuilder(boolean.class)
|
||||
.name(Component.translatable("controlify.gui.show_screen_guide"))
|
||||
.tooltip(Component.translatable("controlify.gui.show_screen_guide.tooltip"))
|
||||
.binding(def.showScreenGuide, () -> config.showScreenGuide, v -> config.showScreenGuide = v)
|
||||
.controller(TickBoxController::new)
|
||||
.build())
|
||||
.option(Option.createBuilder(float.class)
|
||||
@ -182,8 +193,8 @@ public class YACLHelper {
|
||||
var gpCfgDef = gamepad.defaultConfig();
|
||||
advancedGroup
|
||||
.option(Option.createBuilder(float.class)
|
||||
.name(Component.translatable("controlify.gui.left_stick_deadzone"))
|
||||
.tooltip(Component.translatable("controlify.gui.left_stick_deadzone.tooltip"))
|
||||
.name(Component.translatable("controlify.gui.axis_deadzone", Component.translatable("controlify.gui.left_stick")))
|
||||
.tooltip(Component.translatable("controlify.gui.axis_deadzone.tooltip", Component.translatable("controlify.gui.left_stick")))
|
||||
.tooltip(Component.translatable("controlify.gui.stickdrift_warning").withStyle(ChatFormatting.RED))
|
||||
.binding(
|
||||
Math.max(gpCfgDef.leftStickDeadzoneX, gpCfgDef.leftStickDeadzoneY),
|
||||
@ -193,8 +204,8 @@ public class YACLHelper {
|
||||
.controller(opt -> new FloatSliderController(opt, 0, 1, 0.01f, v -> Component.literal(String.format("%.0f%%", v*100))))
|
||||
.build())
|
||||
.option(Option.createBuilder(float.class)
|
||||
.name(Component.translatable("controlify.gui.right_stick_deadzone"))
|
||||
.tooltip(Component.translatable("controlify.gui.right_stick_deadzone.tooltip"))
|
||||
.name(Component.translatable("controlify.gui.axis_deadzone", Component.translatable("controlify.gui.right_stick")))
|
||||
.tooltip(Component.translatable("controlify.gui.axis_deadzone.tooltip", Component.translatable("controlify.gui.right_stick")))
|
||||
.tooltip(Component.translatable("controlify.gui.stickdrift_warning").withStyle(ChatFormatting.RED))
|
||||
.binding(
|
||||
Math.max(gpCfgDef.rightStickDeadzoneX, gpCfgDef.rightStickDeadzoneY),
|
||||
@ -203,7 +214,7 @@ public class YACLHelper {
|
||||
)
|
||||
.controller(opt -> new FloatSliderController(opt, 0, 1, 0.01f, v -> Component.literal(String.format("%.0f%%", v*100))))
|
||||
.build());
|
||||
} else if (controller instanceof JoystickController joystick) {
|
||||
} else if (controller instanceof SingleJoystickController joystick) {
|
||||
Collection<Integer> deadzoneAxes = IntStream.range(0, joystick.axisCount())
|
||||
.filter(i -> joystick.mapping().axis(i).requiresDeadzone())
|
||||
.boxed()
|
||||
@ -254,7 +265,7 @@ public class YACLHelper {
|
||||
.tooltip(binding.description())
|
||||
.build());
|
||||
}
|
||||
} else if (controller instanceof JoystickController joystick) {
|
||||
} else if (controller instanceof JoystickController<?> joystick) {
|
||||
for (var binding : joystick.bindings().registry().values()) {
|
||||
controlsGroup.option(Option.createBuilder((Class<IBind<JoystickState>>) (Class<?>) IBind.class)
|
||||
.name(binding.name())
|
||||
|
@ -65,8 +65,8 @@ public abstract class AbstractController<S extends ControllerState, C extends Co
|
||||
}
|
||||
|
||||
@Override
|
||||
public String guid() {
|
||||
return this.guid;
|
||||
public int joystickId() {
|
||||
return this.joystickId;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -5,7 +5,7 @@ import com.google.gson.JsonElement;
|
||||
import dev.isxander.controlify.bindings.ControllerBindings;
|
||||
import dev.isxander.controlify.controller.gamepad.GamepadController;
|
||||
import dev.isxander.controlify.controller.hid.ControllerHIDService;
|
||||
import dev.isxander.controlify.controller.joystick.JoystickController;
|
||||
import dev.isxander.controlify.controller.joystick.SingleJoystickController;
|
||||
import dev.isxander.controlify.debug.DebugProperties;
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
|
||||
@ -14,7 +14,7 @@ import java.util.Map;
|
||||
|
||||
public interface Controller<S extends ControllerState, C extends ControllerConfig> {
|
||||
String uid();
|
||||
String guid();
|
||||
int joystickId();
|
||||
|
||||
ControllerBindings<S> bindings();
|
||||
|
||||
@ -31,26 +31,27 @@ public interface Controller<S extends ControllerState, C extends ControllerConfi
|
||||
String name();
|
||||
|
||||
void updateState();
|
||||
void clearState();
|
||||
|
||||
default boolean canBeUsed() {
|
||||
return true;
|
||||
}
|
||||
|
||||
Map<Integer, Controller<?, ?>> CONTROLLERS = new HashMap<>();
|
||||
Map<String, Controller<?, ?>> CONTROLLERS = new HashMap<>();
|
||||
|
||||
static Controller<?, ?> createOrGet(int joystickId, ControllerHIDService.ControllerHIDInfo hidInfo) {
|
||||
if (CONTROLLERS.containsKey(joystickId)) {
|
||||
return CONTROLLERS.get(joystickId);
|
||||
if (CONTROLLERS.containsKey(hidInfo.createControllerUID())) {
|
||||
return CONTROLLERS.get(hidInfo.createControllerUID());
|
||||
}
|
||||
|
||||
if (GLFW.glfwJoystickIsGamepad(joystickId) && !DebugProperties.FORCE_JOYSTICK) {
|
||||
GamepadController controller = new GamepadController(joystickId, hidInfo);
|
||||
CONTROLLERS.put(joystickId, controller);
|
||||
CONTROLLERS.put(controller.uid(), controller);
|
||||
return controller;
|
||||
}
|
||||
|
||||
JoystickController controller = new JoystickController(joystickId, hidInfo);
|
||||
CONTROLLERS.put(joystickId, controller);
|
||||
SingleJoystickController controller = new SingleJoystickController(joystickId, hidInfo);
|
||||
CONTROLLERS.put(controller.uid(), controller);
|
||||
return controller;
|
||||
}
|
||||
|
||||
@ -74,8 +75,8 @@ public interface Controller<S extends ControllerState, C extends ControllerConfi
|
||||
}
|
||||
|
||||
@Override
|
||||
public String guid() {
|
||||
return "DUMMY";
|
||||
public int joystickId() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -127,5 +128,10 @@ public interface Controller<S extends ControllerState, C extends ControllerConfi
|
||||
public void updateState() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearState() {
|
||||
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -16,7 +16,8 @@ public abstract class ControllerConfig {
|
||||
|
||||
public String customName = null;
|
||||
|
||||
public boolean showGuide = true;
|
||||
public boolean showIngameGuide = true;
|
||||
public boolean showScreenGuide = true;
|
||||
|
||||
public float chatKeyboardHeight = 0f;
|
||||
|
||||
|
@ -43,6 +43,11 @@ public class GamepadController extends AbstractController<GamepadState, GamepadC
|
||||
state = new GamepadState(axesState, rawAxesState, buttonState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearState() {
|
||||
state = GamepadState.EMPTY;
|
||||
}
|
||||
|
||||
public void consumeButtonState() {
|
||||
this.state = new GamepadState(state().gamepadAxes(), state().rawGamepadAxes(), GamepadState.ButtonState.EMPTY);
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ public class ControllerHIDService implements HidServicesListener {
|
||||
services.scan();
|
||||
try {
|
||||
// wait for scan to complete on separate thread
|
||||
Thread.sleep(800);
|
||||
Thread.sleep(1000);
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
@ -78,5 +78,8 @@ public class ControllerHIDService implements HidServicesListener {
|
||||
}
|
||||
|
||||
public record ControllerHIDInfo(ControllerType type, Optional<String> path) {
|
||||
public String createControllerUID() {
|
||||
return UUID.nameUUIDFromBytes(path().get().getBytes()).toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,158 @@
|
||||
package dev.isxander.controlify.controller.joystick;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonElement;
|
||||
import dev.isxander.controlify.Controlify;
|
||||
import dev.isxander.controlify.bindings.ControllerBindings;
|
||||
import dev.isxander.controlify.controller.ControllerType;
|
||||
import dev.isxander.controlify.controller.joystick.mapping.JoystickMapping;
|
||||
import dev.isxander.controlify.controller.joystick.mapping.RPJoystickMapping;
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class CompoundJoystickController implements JoystickController<JoystickConfig> {
|
||||
private final String uid;
|
||||
private final List<Integer> joysticks;
|
||||
private final int axisCount, buttonCount, hatCount;
|
||||
private final ControllerType compoundType;
|
||||
private final ControllerBindings<JoystickState> bindings;
|
||||
private final JoystickMapping mapping;
|
||||
|
||||
private JoystickConfig config;
|
||||
private final JoystickConfig defaultConfig;
|
||||
|
||||
private JoystickState state = JoystickState.EMPTY, prevState = JoystickState.EMPTY;
|
||||
|
||||
public CompoundJoystickController(List<Integer> joystickIds, String uid, ControllerType compoundType) {
|
||||
this.joysticks = ImmutableList.copyOf(joystickIds);
|
||||
this.uid = uid;
|
||||
this.compoundType = compoundType;
|
||||
|
||||
this.axisCount = joystickIds.stream().mapToInt(this::getAxisCountForJoystick).sum();
|
||||
this.buttonCount = joystickIds.stream().mapToInt(this::getButtonCountForJoystick).sum();
|
||||
this.hatCount = joystickIds.stream().mapToInt(this::getHatCountForJoystick).sum();
|
||||
|
||||
this.mapping = RPJoystickMapping.fromType(type());
|
||||
|
||||
this.config = new JoystickConfig(this);
|
||||
this.defaultConfig = new JoystickConfig(this);
|
||||
|
||||
this.bindings = new ControllerBindings<>(this);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String uid() {
|
||||
return this.uid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ControllerBindings<JoystickState> bindings() {
|
||||
return this.bindings;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JoystickState state() {
|
||||
return this.state;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JoystickState prevState() {
|
||||
return this.prevState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateState() {
|
||||
this.prevState = this.state;
|
||||
|
||||
var states = this.joysticks.stream().map(joystick -> JoystickState.fromJoystick(this, joystick)).toList();
|
||||
this.state = JoystickState.merged(mapping(), states);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearState() {
|
||||
this.state = JoystickState.empty(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JoystickConfig config() {
|
||||
return this.config;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JoystickConfig defaultConfig() {
|
||||
return this.defaultConfig;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetConfig() {
|
||||
this.config = new JoystickConfig(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setConfig(Gson gson, JsonElement json) {
|
||||
JoystickConfig newConfig = gson.fromJson(json, JoystickConfig.class);
|
||||
if (newConfig != null) {
|
||||
this.config = newConfig;
|
||||
} else {
|
||||
Controlify.LOGGER.error("Could not set config for controller " + name() + " (" + uid() + ")! Using default config instead.");
|
||||
this.config = defaultConfig();
|
||||
}
|
||||
this.config.setup(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ControllerType type() {
|
||||
return this.compoundType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return type().friendlyName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public JoystickMapping mapping() {
|
||||
return this.mapping;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int axisCount() {
|
||||
return this.axisCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int buttonCount() {
|
||||
return this.buttonCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hatCount() {
|
||||
return this.hatCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canBeUsed() {
|
||||
return JoystickController.super.canBeUsed()
|
||||
&& joysticks.stream().allMatch(GLFW::glfwJoystickPresent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int joystickId() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
private int getAxisCountForJoystick(int joystick) {
|
||||
return GLFW.glfwGetJoystickAxes(joystick).capacity();
|
||||
}
|
||||
|
||||
private int getButtonCountForJoystick(int joystick) {
|
||||
return GLFW.glfwGetJoystickButtons(joystick).capacity();
|
||||
}
|
||||
|
||||
private int getHatCountForJoystick(int joystick) {
|
||||
return GLFW.glfwGetJoystickHats(joystick).capacity();
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
package dev.isxander.controlify.controller.joystick;
|
||||
|
||||
import dev.isxander.controlify.controller.Controller;
|
||||
import dev.isxander.controlify.controller.ControllerType;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public record CompoundJoystickInfo(Collection<String> joystickUids, String friendlyName) {
|
||||
public ControllerType type() {
|
||||
return new ControllerType(friendlyName, createUID(joystickUids));
|
||||
}
|
||||
|
||||
public boolean canBeUsed() {
|
||||
List<Controller<?, ?>> joysticks = Controller.CONTROLLERS.values().stream().filter(c -> joystickUids.contains(c.uid())).toList();
|
||||
if (joysticks.size() != joystickUids().size()) {
|
||||
return false; // not all controllers are connected
|
||||
}
|
||||
if (joysticks.stream().anyMatch(c -> !c.canBeUsed())) {
|
||||
return false; // not all controllers can be used
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean isLoaded() {
|
||||
return Controller.CONTROLLERS.containsKey(createUID(joystickUids));
|
||||
}
|
||||
|
||||
public Optional<CompoundJoystickController> attemptCreate() {
|
||||
if (!canBeUsed()) return Optional.empty();
|
||||
|
||||
List<Integer> joystickIDs = Controller.CONTROLLERS.values().stream()
|
||||
.filter(c -> joystickUids.contains(c.uid()))
|
||||
.map(Controller::joystickId)
|
||||
.toList();
|
||||
|
||||
ControllerType type = type();
|
||||
return Optional.of(new CompoundJoystickController(joystickIDs, type.identifier(), type));
|
||||
}
|
||||
|
||||
public static String createUID(Collection<String> joystickUIDs) {
|
||||
return "compound-" + String.join("_", joystickUIDs);
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
package dev.isxander.controlify.controller.joystick;
|
||||
|
||||
import dev.isxander.controlify.controller.ControllerConfig;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
@ -8,9 +9,10 @@ import java.util.Map;
|
||||
public class JoystickConfig extends ControllerConfig {
|
||||
private Map<String, Float> deadzones;
|
||||
|
||||
private transient JoystickController controller;
|
||||
private transient JoystickController<?> controller;
|
||||
|
||||
public JoystickConfig(JoystickController controller) {
|
||||
public JoystickConfig(JoystickController<?> controller) {
|
||||
Validate.notNull(controller);
|
||||
setup(controller);
|
||||
}
|
||||
|
||||
@ -30,7 +32,7 @@ public class JoystickConfig extends ControllerConfig {
|
||||
return deadzones.getOrDefault(controller.mapping().axis(axis).identifier(), 0.2f);
|
||||
}
|
||||
|
||||
void setup(JoystickController controller) {
|
||||
void setup(JoystickController<?> controller) {
|
||||
this.controller = controller;
|
||||
if (this.deadzones == null) {
|
||||
deadzones = new HashMap<>();
|
||||
|
@ -1,74 +1,20 @@
|
||||
package dev.isxander.controlify.controller.joystick;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonElement;
|
||||
import dev.isxander.controlify.controller.AbstractController;
|
||||
import dev.isxander.controlify.controller.hid.ControllerHIDService;
|
||||
import dev.isxander.controlify.controller.joystick.mapping.RPJoystickMapping;
|
||||
import dev.isxander.controlify.controller.Controller;
|
||||
import dev.isxander.controlify.controller.joystick.JoystickConfig;
|
||||
import dev.isxander.controlify.controller.joystick.JoystickState;
|
||||
import dev.isxander.controlify.controller.joystick.mapping.JoystickMapping;
|
||||
import dev.isxander.controlify.controller.joystick.mapping.UnmappedJoystickMapping;
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
|
||||
import java.util.Objects;
|
||||
public interface JoystickController<T extends JoystickConfig> extends Controller<JoystickState, T> {
|
||||
JoystickMapping mapping();
|
||||
|
||||
public class JoystickController extends AbstractController<JoystickState, JoystickConfig> {
|
||||
private JoystickState state = JoystickState.EMPTY, prevState = JoystickState.EMPTY;
|
||||
private final int axisCount, buttonCount, hatCount;
|
||||
private final JoystickMapping mapping;
|
||||
|
||||
public JoystickController(int joystickId, ControllerHIDService.ControllerHIDInfo hidInfo) {
|
||||
super(joystickId, hidInfo);
|
||||
|
||||
this.axisCount = GLFW.glfwGetJoystickAxes(joystickId).capacity();
|
||||
this.buttonCount = GLFW.glfwGetJoystickButtons(joystickId).capacity();
|
||||
this.hatCount = GLFW.glfwGetJoystickHats(joystickId).capacity();
|
||||
|
||||
this.mapping = Objects.requireNonNull(RPJoystickMapping.fromType(type()));
|
||||
|
||||
this.config = new JoystickConfig(this);
|
||||
this.defaultConfig = new JoystickConfig(this);
|
||||
}
|
||||
int axisCount();
|
||||
int buttonCount();
|
||||
int hatCount();
|
||||
|
||||
@Override
|
||||
public JoystickState state() {
|
||||
return state;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JoystickState prevState() {
|
||||
return prevState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateState() {
|
||||
prevState = state;
|
||||
state = JoystickState.fromJoystick(this, joystickId);
|
||||
}
|
||||
|
||||
public JoystickMapping mapping() {
|
||||
return mapping;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canBeUsed() {
|
||||
default boolean canBeUsed() {
|
||||
return !(mapping() instanceof UnmappedJoystickMapping);
|
||||
}
|
||||
|
||||
public int axisCount() {
|
||||
return axisCount;
|
||||
}
|
||||
|
||||
public int buttonCount() {
|
||||
return buttonCount;
|
||||
}
|
||||
|
||||
public int hatCount() {
|
||||
return hatCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setConfig(Gson gson, JsonElement json) {
|
||||
super.setConfig(gson, json);
|
||||
this.config.setup(this);
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ import org.lwjgl.glfw.GLFW;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
@ -58,7 +59,17 @@ public class JoystickState implements ControllerState {
|
||||
|| hats().stream().anyMatch(hat -> hat != HatState.CENTERED);
|
||||
}
|
||||
|
||||
public static JoystickState fromJoystick(JoystickController joystick, int joystickId) {
|
||||
@Override
|
||||
public String toString() {
|
||||
return "JoystickState{" +
|
||||
"axes=" + axes +
|
||||
", rawAxes=" + rawAxes +
|
||||
", buttons=" + buttons +
|
||||
", hats=" + hats +
|
||||
'}';
|
||||
}
|
||||
|
||||
public static JoystickState fromJoystick(JoystickController<?> joystick, int joystickId) {
|
||||
FloatBuffer axesBuffer = GLFW.glfwGetJoystickAxes(joystickId);
|
||||
List<Float> axes = new ArrayList<>();
|
||||
List<Float> rawAxes = new ArrayList<>();
|
||||
@ -107,6 +118,40 @@ public class JoystickState implements ControllerState {
|
||||
return new JoystickState(joystick.mapping(), axes, rawAxes, buttons, hats);
|
||||
}
|
||||
|
||||
public static JoystickState empty(JoystickController<?> joystick) {
|
||||
var axes = new ArrayList<Float>();
|
||||
var buttons = new ArrayList<Boolean>();
|
||||
var hats = new ArrayList<HatState>();
|
||||
|
||||
for (int i = 0; i < joystick.axisCount(); i++) {
|
||||
axes.add(joystick.mapping().axis(i).restingValue());
|
||||
}
|
||||
for (int i = 0; i < joystick.buttonCount(); i++) {
|
||||
buttons.add(false);
|
||||
}
|
||||
for (int i = 0; i < joystick.hatCount(); i++) {
|
||||
hats.add(HatState.CENTERED);
|
||||
}
|
||||
|
||||
return new JoystickState(joystick.mapping(), axes, axes, buttons, hats);
|
||||
}
|
||||
|
||||
public static JoystickState merged(JoystickMapping mapping, Collection<JoystickState> states) {
|
||||
var axes = new ArrayList<Float>();
|
||||
var rawAxes = new ArrayList<Float>();
|
||||
var buttons = new ArrayList<Boolean>();
|
||||
var hats = new ArrayList<HatState>();
|
||||
|
||||
for (var state : states) {
|
||||
axes.addAll(state.axes);
|
||||
rawAxes.addAll(state.rawAxes);
|
||||
buttons.addAll(state.buttons);
|
||||
hats.addAll(state.hats);
|
||||
}
|
||||
|
||||
return new JoystickState(mapping, axes, rawAxes, buttons, hats);
|
||||
}
|
||||
|
||||
public enum HatState implements NameableEnum {
|
||||
CENTERED,
|
||||
UP,
|
||||
|
@ -0,0 +1,83 @@
|
||||
package dev.isxander.controlify.controller.joystick;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonElement;
|
||||
import dev.isxander.controlify.controller.AbstractController;
|
||||
import dev.isxander.controlify.controller.hid.ControllerHIDService;
|
||||
import dev.isxander.controlify.controller.joystick.mapping.RPJoystickMapping;
|
||||
import dev.isxander.controlify.controller.joystick.mapping.JoystickMapping;
|
||||
import dev.isxander.controlify.controller.joystick.mapping.UnmappedJoystickMapping;
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class SingleJoystickController extends AbstractController<JoystickState, JoystickConfig> implements JoystickController<JoystickConfig> {
|
||||
private JoystickState state = JoystickState.EMPTY, prevState = JoystickState.EMPTY;
|
||||
private final int axisCount, buttonCount, hatCount;
|
||||
private final JoystickMapping mapping;
|
||||
|
||||
public SingleJoystickController(int joystickId, ControllerHIDService.ControllerHIDInfo hidInfo) {
|
||||
super(joystickId, hidInfo);
|
||||
|
||||
this.axisCount = GLFW.glfwGetJoystickAxes(joystickId).capacity();
|
||||
this.buttonCount = GLFW.glfwGetJoystickButtons(joystickId).capacity();
|
||||
this.hatCount = GLFW.glfwGetJoystickHats(joystickId).capacity();
|
||||
|
||||
this.mapping = Objects.requireNonNull(RPJoystickMapping.fromType(type()));
|
||||
|
||||
this.config = new JoystickConfig(this);
|
||||
this.defaultConfig = new JoystickConfig(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JoystickState state() {
|
||||
return state;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JoystickState prevState() {
|
||||
return prevState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateState() {
|
||||
prevState = state;
|
||||
state = JoystickState.fromJoystick(this, joystickId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearState() {
|
||||
this.state = JoystickState.empty(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JoystickMapping mapping() {
|
||||
return mapping;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int axisCount() {
|
||||
return axisCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int buttonCount() {
|
||||
return buttonCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hatCount() {
|
||||
return hatCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int joystickId() {
|
||||
return joystickId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setConfig(Gson gson, JsonElement json) {
|
||||
super.setConfig(gson, json);
|
||||
this.config.setup(this);
|
||||
}
|
||||
}
|
@ -21,6 +21,8 @@ public interface JoystickMapping {
|
||||
|
||||
boolean isAxisResting(float value);
|
||||
|
||||
float restingValue();
|
||||
|
||||
String getDirectionIdentifier(int axis, JoystickAxisBind.AxisDirection direction);
|
||||
}
|
||||
|
||||
|
@ -111,7 +111,7 @@ public class RPJoystickMapping implements JoystickMapping {
|
||||
}
|
||||
}
|
||||
|
||||
private record AxisMapping(List<Integer> ids, String identifier, Vec2 inpRange, Vec2 outRange, float restState, boolean requiresDeadzone, String typeId, List<List<String>> axisNames) implements Axis {
|
||||
private record AxisMapping(List<Integer> ids, String identifier, Vec2 inpRange, Vec2 outRange, float restingValue, boolean requiresDeadzone, String typeId, List<List<String>> axisNames) implements Axis {
|
||||
@Override
|
||||
public float modifyAxis(float value) {
|
||||
if (inpRange() == null || outRange() == null)
|
||||
@ -122,7 +122,7 @@ public class RPJoystickMapping implements JoystickMapping {
|
||||
|
||||
@Override
|
||||
public boolean isAxisResting(float value) {
|
||||
return value == restState();
|
||||
return value == restingValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -45,7 +45,12 @@ public class UnmappedJoystickMapping implements JoystickMapping {
|
||||
|
||||
@Override
|
||||
public boolean isAxisResting(float value) {
|
||||
return value == 0;
|
||||
return value == restingValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public float restingValue() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -0,0 +1,24 @@
|
||||
package dev.isxander.controlify.gui;
|
||||
|
||||
import dev.isxander.controlify.api.buttonguide.ButtonGuidePredicate;
|
||||
import dev.isxander.controlify.api.buttonguide.ButtonRenderPosition;
|
||||
import dev.isxander.controlify.bindings.ControllerBinding;
|
||||
import dev.isxander.controlify.bindings.ControllerBindings;
|
||||
import net.minecraft.client.gui.components.AbstractButton;
|
||||
import net.minecraft.client.gui.screens.Screen;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* @see dev.isxander.controlify.mixins.feature.guide.screen.AbstractButtonMixin
|
||||
*/
|
||||
public interface ButtonGuideRenderer<T extends AbstractButton> {
|
||||
void setButtonGuide(RenderData<T> renderData);
|
||||
|
||||
static <T extends AbstractButton> void registerBindingForButton(T button, Function<ControllerBindings<?>, ControllerBinding<?>> binding, ButtonRenderPosition position, ButtonGuidePredicate<T> renderPredicate) {
|
||||
((ButtonGuideRenderer<T>) button).setButtonGuide(new RenderData<>(binding, position, renderPredicate));
|
||||
}
|
||||
|
||||
record RenderData<T extends AbstractButton>(Function<ControllerBindings<?>, ControllerBinding<?>> binding, ButtonRenderPosition position, ButtonGuidePredicate<T> renderPredicate) {
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
package dev.isxander.controlify.ingame.guide;
|
||||
|
||||
import dev.isxander.controlify.api.buttonguide.ActionLocation;
|
||||
import dev.isxander.controlify.api.buttonguide.ActionPriority;
|
||||
import dev.isxander.controlify.api.ingameguide.ActionLocation;
|
||||
import dev.isxander.controlify.api.ingameguide.ActionPriority;
|
||||
import dev.isxander.controlify.bindings.ControllerBinding;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
@ -1,10 +1,7 @@
|
||||
package dev.isxander.controlify.ingame.guide;
|
||||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import dev.isxander.controlify.api.buttonguide.ActionLocation;
|
||||
import dev.isxander.controlify.api.buttonguide.ActionPriority;
|
||||
import dev.isxander.controlify.api.buttonguide.ButtonGuideRegistry;
|
||||
import dev.isxander.controlify.api.buttonguide.GuideActionNameSupplier;
|
||||
import dev.isxander.controlify.api.ingameguide.*;
|
||||
import dev.isxander.controlify.bindings.ControllerBinding;
|
||||
import dev.isxander.controlify.compatibility.ControlifyCompat;
|
||||
import dev.isxander.controlify.controller.Controller;
|
||||
@ -23,7 +20,7 @@ import net.minecraft.world.phys.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class InGameButtonGuide implements ButtonGuideRegistry {
|
||||
public class InGameButtonGuide implements IngameGuideRegistry {
|
||||
private final Controller<?, ?> controller;
|
||||
private final LocalPlayer player;
|
||||
private final Minecraft minecraft = Minecraft.getInstance();
|
||||
@ -38,11 +35,11 @@ public class InGameButtonGuide implements ButtonGuideRegistry {
|
||||
this.player = localPlayer;
|
||||
|
||||
registerDefaultActions();
|
||||
ControlifyEvents.BUTTON_GUIDE_REGISTRY.invoker().onRegisterButtonGuide(controller.bindings(), this);
|
||||
ControlifyEvents.INGAME_GUIDE_REGISTRY.invoker().onRegisterIngameGuide(controller.bindings(), this);
|
||||
}
|
||||
|
||||
public void renderHud(PoseStack poseStack, float tickDelta, int width, int height) {
|
||||
if (!controller.config().showGuide || minecraft.screen != null || minecraft.options.renderDebug)
|
||||
if (!controller.config().showIngameGuide || minecraft.screen != null || minecraft.options.renderDebug)
|
||||
return;
|
||||
|
||||
ControlifyCompat.ifBeginHudBatching();
|
||||
@ -98,7 +95,7 @@ public class InGameButtonGuide implements ButtonGuideRegistry {
|
||||
leftGuides.clear();
|
||||
rightGuides.clear();
|
||||
|
||||
if (!controller.config().showGuide || minecraft.screen != null)
|
||||
if (!controller.config().showIngameGuide || minecraft.screen != null)
|
||||
return;
|
||||
|
||||
for (var actionPredicate : guidePredicates) {
|
||||
@ -131,7 +128,8 @@ public class InGameButtonGuide implements ButtonGuideRegistry {
|
||||
|
||||
private void registerDefaultActions() {
|
||||
var options = Minecraft.getInstance().options;
|
||||
registerGuideAction(controller.bindings().JUMP, ActionLocation.LEFT, (client, player, level, hitResult, controller) -> {
|
||||
registerGuideAction(controller.bindings().JUMP, ActionLocation.LEFT, (ctx) -> {
|
||||
var player = ctx.player();
|
||||
if (player.getAbilities().flying)
|
||||
return Optional.of(Component.translatable("controlify.guide.fly_up"));
|
||||
|
||||
@ -149,14 +147,15 @@ public class InGameButtonGuide implements ButtonGuideRegistry {
|
||||
|
||||
return Optional.empty();
|
||||
});
|
||||
registerGuideAction(controller.bindings().SNEAK, ActionLocation.LEFT, (client, player, level, hitResult, controller) -> {
|
||||
registerGuideAction(controller.bindings().SNEAK, ActionLocation.LEFT, (ctx) -> {
|
||||
var player = ctx.player();
|
||||
if (player.getVehicle() != null)
|
||||
return Optional.of(Component.translatable("controlify.guide.dismount"));
|
||||
if (player.getAbilities().flying)
|
||||
return Optional.of(Component.translatable("controlify.guide.fly_down"));
|
||||
if (player.isInWater())
|
||||
return Optional.of(Component.translatable("controlify.guide.swim_down"));
|
||||
if (controller.config().toggleSneak) {
|
||||
if (ctx.controller().config().toggleSneak) {
|
||||
if (player.input.shiftKeyDown)
|
||||
return Optional.of(Component.translatable("controlify.guide.stop_sneaking"));
|
||||
else
|
||||
@ -167,33 +166,37 @@ public class InGameButtonGuide implements ButtonGuideRegistry {
|
||||
}
|
||||
return Optional.empty();
|
||||
});
|
||||
registerGuideAction(controller.bindings().SPRINT, ActionLocation.LEFT, (client, player, level, hitResult, controller) -> {
|
||||
registerGuideAction(controller.bindings().SPRINT, ActionLocation.LEFT, (ctx) -> {
|
||||
var player = ctx.player();
|
||||
if (!options.keySprint.isDown()) {
|
||||
if (!player.input.getMoveVector().equals(Vec2.ZERO)) {
|
||||
if (player.isUnderWater())
|
||||
return Optional.of(Component.translatable("controlify.guide.start_swimming"));
|
||||
return Optional.of(Component.translatable("controlify.guide.start_sprinting"));
|
||||
}
|
||||
} else if (controller.config().toggleSprint) {
|
||||
} else if (ctx.controller().config().toggleSprint) {
|
||||
if (player.isUnderWater())
|
||||
return Optional.of(Component.translatable("controlify.guide.stop_swimming"));
|
||||
return Optional.of(Component.translatable("controlify.guide.stop_sprinting"));
|
||||
}
|
||||
return Optional.empty();
|
||||
});
|
||||
registerGuideAction(controller.bindings().INVENTORY, ActionLocation.RIGHT, (client, player, level, hitResult, controller) -> {
|
||||
if (client.screen == null)
|
||||
registerGuideAction(controller.bindings().INVENTORY, ActionLocation.RIGHT, (ctx) -> {
|
||||
if (ctx.client().screen == null)
|
||||
return Optional.of(Component.translatable("controlify.guide.inventory"));
|
||||
return Optional.empty();
|
||||
});
|
||||
registerGuideAction(controller.bindings().ATTACK, ActionLocation.RIGHT, (client, player, level, hitResult, controller) -> {
|
||||
registerGuideAction(controller.bindings().ATTACK, ActionLocation.RIGHT, (ctx) -> {
|
||||
var hitResult = ctx.hitResult();
|
||||
if (hitResult.getType() == HitResult.Type.ENTITY)
|
||||
return Optional.of(Component.translatable("controlify.guide.attack"));
|
||||
if (hitResult.getType() == HitResult.Type.BLOCK)
|
||||
return Optional.of(Component.translatable("controlify.guide.break"));
|
||||
return Optional.empty();
|
||||
});
|
||||
registerGuideAction(controller.bindings().USE, ActionLocation.RIGHT, (client, player, level, hitResult, controller) -> {
|
||||
registerGuideAction(controller.bindings().USE, ActionLocation.RIGHT, (ctx) -> {
|
||||
var hitResult = ctx.hitResult();
|
||||
var player = ctx.player();
|
||||
if (hitResult.getType() == HitResult.Type.ENTITY)
|
||||
if (player.isSpectator())
|
||||
return Optional.of(Component.translatable("controlify.guide.spectate"));
|
||||
@ -203,18 +206,20 @@ public class InGameButtonGuide implements ButtonGuideRegistry {
|
||||
return Optional.of(Component.translatable("controlify.guide.use"));
|
||||
return Optional.empty();
|
||||
});
|
||||
registerGuideAction(controller.bindings().DROP, ActionLocation.RIGHT, (client, player, level, hitResult, controller) -> {
|
||||
registerGuideAction(controller.bindings().DROP, ActionLocation.RIGHT, (ctx) -> {
|
||||
var player = ctx.player();
|
||||
if (player.hasItemInSlot(EquipmentSlot.MAINHAND) || player.hasItemInSlot(EquipmentSlot.OFFHAND))
|
||||
return Optional.of(Component.translatable("controlify.guide.drop"));
|
||||
return Optional.empty();
|
||||
});
|
||||
registerGuideAction(controller.bindings().SWAP_HANDS, ActionLocation.RIGHT, (client, player, level, hitResult, controller) -> {
|
||||
registerGuideAction(controller.bindings().SWAP_HANDS, ActionLocation.RIGHT, (ctx) -> {
|
||||
var player = ctx.player();
|
||||
if (player.hasItemInSlot(EquipmentSlot.MAINHAND) || player.hasItemInSlot(EquipmentSlot.OFFHAND))
|
||||
return Optional.of(Component.translatable("controlify.guide.swap_hands"));
|
||||
return Optional.empty();
|
||||
});
|
||||
registerGuideAction(controller.bindings().PICK_BLOCK, ActionLocation.RIGHT, (client, player, level, hitResult, controller) -> {
|
||||
if (hitResult.getType() == HitResult.Type.BLOCK && player.isCreative())
|
||||
registerGuideAction(controller.bindings().PICK_BLOCK, ActionLocation.RIGHT, (ctx) -> {
|
||||
if (ctx.hitResult().getType() == HitResult.Type.BLOCK && ctx.player().isCreative())
|
||||
return Optional.of(Component.translatable("controlify.guide.pick_block"));
|
||||
return Optional.empty();
|
||||
});
|
||||
@ -248,7 +253,7 @@ public class InGameButtonGuide implements ButtonGuideRegistry {
|
||||
|
||||
private record GuideActionSupplier(ControllerBinding<?> binding, ActionLocation location, ActionPriority priority, GuideActionNameSupplier nameSupplier) {
|
||||
public Optional<GuideAction> supply(Minecraft client, LocalPlayer player, ClientLevel level, HitResult hitResult, Controller<?, ?> controller) {
|
||||
return nameSupplier.supply(client, player, level, hitResult, controller)
|
||||
return nameSupplier.supply(new IngameGuideContext(client, player, level, hitResult, controller))
|
||||
.map(name -> new GuideAction(binding, name, location, priority));
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
package dev.isxander.controlify.mixins.feature.guide;
|
||||
package dev.isxander.controlify.mixins.feature.guide.ingame;
|
||||
|
||||
import dev.isxander.controlify.Controlify;
|
||||
import dev.isxander.controlify.InputMode;
|
@ -1,4 +1,4 @@
|
||||
package dev.isxander.controlify.mixins.feature.guide;
|
||||
package dev.isxander.controlify.mixins.feature.guide.ingame;
|
||||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import dev.isxander.controlify.Controlify;
|
@ -0,0 +1,86 @@
|
||||
package dev.isxander.controlify.mixins.feature.guide.screen;
|
||||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import dev.isxander.controlify.Controlify;
|
||||
import dev.isxander.controlify.InputMode;
|
||||
import dev.isxander.controlify.api.buttonguide.ButtonRenderPosition;
|
||||
import dev.isxander.controlify.bindings.IBind;
|
||||
import dev.isxander.controlify.gui.ButtonGuideRenderer;
|
||||
import dev.isxander.controlify.screenop.ComponentProcessor;
|
||||
import dev.isxander.controlify.screenop.ComponentProcessorProvider;
|
||||
import dev.isxander.controlify.screenop.compat.vanilla.AbstractButtonComponentProcessor;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.Font;
|
||||
import net.minecraft.client.gui.components.AbstractButton;
|
||||
import net.minecraft.network.chat.CommonComponents;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.network.chat.contents.TranslatableContents;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
@Mixin(AbstractButton.class)
|
||||
public abstract class AbstractButtonMixin extends AbstractWidgetMixin implements ButtonGuideRenderer<AbstractButton> {
|
||||
@Unique private RenderData<AbstractButton> renderData = null;
|
||||
|
||||
@Inject(method = "renderString", at = @At("RETURN"))
|
||||
private void renderButtonGuide(PoseStack matrices, Font renderer, int color, CallbackInfo ci) {
|
||||
if (shouldRender()) {
|
||||
switch (renderData.position()) {
|
||||
case LEFT -> getBind().draw(matrices, getX() - getBind().drawSize().width() - 1, getY() + getHeight() / 2);
|
||||
case RIGHT -> getBind().draw(matrices, getX() + getWidth() + 1, getY() + getHeight() / 2);
|
||||
case TEXT -> {
|
||||
Font font = Minecraft.getInstance().font;
|
||||
int x;
|
||||
if (font.width(getMessage()) > getWidth()) {
|
||||
x = getX();
|
||||
} else {
|
||||
x = getX() + getWidth() / 2 - font.width(getMessage()) / 2 - getBind().drawSize().width();
|
||||
}
|
||||
|
||||
getBind().draw(matrices, x, getY() + getHeight() / 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "renderString", at = @At("HEAD"))
|
||||
private void shiftXOffset(PoseStack matrices, Font renderer, int color, CallbackInfo ci) {
|
||||
matrices.pushPose();
|
||||
if (!shouldRender() || Minecraft.getInstance().font.width(getMessage()) > getWidth() || renderData.position() != ButtonRenderPosition.TEXT) return;
|
||||
matrices.translate(getBind().drawSize().width() / 2f, 0, 0);
|
||||
}
|
||||
|
||||
@Inject(method = "renderString", at = @At("RETURN"))
|
||||
private void finishShiftXOffset(PoseStack matrices, Font renderer, int color, CallbackInfo ci) {
|
||||
matrices.popPose();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int shiftDrawSize(int x) {
|
||||
if (!shouldRender() || Minecraft.getInstance().font.width(getMessage()) < getWidth() || renderData.position() != ButtonRenderPosition.TEXT) return x;
|
||||
return x + getBind().drawSize().width();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setButtonGuide(RenderData<AbstractButton> renderData) {
|
||||
this.renderData = renderData;
|
||||
}
|
||||
|
||||
private boolean shouldRender() {
|
||||
return renderData != null
|
||||
&& this.isActive()
|
||||
&& Controlify.instance().currentInputMode() == InputMode.CONTROLLER
|
||||
&& Controlify.instance().currentController().config().showScreenGuide
|
||||
&& !renderData.binding().apply(Controlify.instance().currentController().bindings()).unbound()
|
||||
&& renderData.renderPredicate().shouldDisplay((AbstractButton) (Object) this);
|
||||
}
|
||||
|
||||
private IBind<?> getBind() {
|
||||
return renderData.binding().apply(Controlify.instance().currentController().bindings()).currentBind();
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package dev.isxander.controlify.mixins.feature.guide.screen;
|
||||
|
||||
import dev.isxander.controlify.gui.DrawSize;
|
||||
import net.minecraft.client.gui.GuiComponent;
|
||||
import net.minecraft.client.gui.components.AbstractWidget;
|
||||
import net.minecraft.network.chat.Component;
|
||||
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.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.ModifyArg;
|
||||
import org.spongepowered.asm.mixin.injection.ModifyVariable;
|
||||
|
||||
@Mixin(AbstractWidget.class)
|
||||
public abstract class AbstractWidgetMixin extends GuiComponent {
|
||||
@Shadow public abstract int getX();
|
||||
|
||||
@Shadow public abstract int getY();
|
||||
|
||||
@Shadow public abstract int getHeight();
|
||||
|
||||
@Shadow public abstract Component getMessage();
|
||||
|
||||
@Shadow public abstract int getWidth();
|
||||
|
||||
@Shadow public abstract boolean isActive();
|
||||
|
||||
@ModifyArg(method = "renderScrollingString(Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/gui/Font;II)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/components/AbstractWidget;renderScrollingString(Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/gui/Font;Lnet/minecraft/network/chat/Component;IIIII)V"), index = 3)
|
||||
protected int shiftDrawSize(int x) {
|
||||
return x;
|
||||
}
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
package dev.isxander.controlify.mixins.feature.guide.screen;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import dev.isxander.controlify.Controlify;
|
||||
import dev.isxander.controlify.InputMode;
|
||||
import dev.isxander.controlify.bindings.IBind;
|
||||
import dev.isxander.controlify.compatibility.ControlifyCompat;
|
||||
import dev.isxander.controlify.controller.Controller;
|
||||
import dev.isxander.controlify.gui.DrawSize;
|
||||
import net.minecraft.client.gui.components.TabButton;
|
||||
import net.minecraft.client.gui.components.tabs.TabNavigationBar;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
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.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(TabNavigationBar.class)
|
||||
public class TabNavigationBarMixin {
|
||||
@Shadow @Final private ImmutableList<TabButton> tabButtons;
|
||||
|
||||
@Shadow private int width;
|
||||
|
||||
@Inject(method = "render", at = @At("RETURN"))
|
||||
private void renderControllerButtonOverlay(PoseStack matrices, int mouseX, int mouseY, float delta, CallbackInfo ci) {
|
||||
if (Controlify.instance().currentInputMode() == InputMode.CONTROLLER) {
|
||||
var controller = Controlify.instance().currentController();
|
||||
if (controller.config().showScreenGuide) {
|
||||
this.renderControllerButtonOverlay(matrices, controller);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void renderControllerButtonOverlay(PoseStack matrices, Controller<?, ?> controller) {
|
||||
ControlifyCompat.ifBeginHudBatching();
|
||||
|
||||
TabButton firstTab = tabButtons.get(0);
|
||||
TabButton lastTab = tabButtons.get(tabButtons.size() - 1);
|
||||
|
||||
IBind<?> prevBind = controller.bindings().GUI_PREV_TAB.currentBind();
|
||||
DrawSize prevBindDrawSize = prevBind.drawSize();
|
||||
int firstButtonX = Math.max(firstTab.getX() - 2 - prevBindDrawSize.width(), firstTab.getX() / 2 - prevBindDrawSize.width() / 2);
|
||||
int firstButtonY = 12;
|
||||
prevBind.draw(matrices, firstButtonX, firstButtonY);
|
||||
|
||||
IBind<?> nextBind = controller.bindings().GUI_NEXT_TAB.currentBind();
|
||||
DrawSize nextBindDrawSize = nextBind.drawSize();
|
||||
int lastButtonEnd = lastTab.getX() + lastTab.getWidth();
|
||||
int lastButtonX = Math.min(lastTab.getX() + lastTab.getWidth() + 2, lastButtonEnd + (width - lastButtonEnd) / 2 - nextBindDrawSize.width() / 2);
|
||||
int lastButtonY = 12;
|
||||
nextBind.draw(matrices, lastButtonX, lastButtonY);
|
||||
|
||||
ControlifyCompat.ifEndHudBatching();
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
package dev.isxander.controlify.mixins.feature.screenop.vanilla;
|
||||
|
||||
import dev.isxander.controlify.api.buttonguide.ButtonGuidePredicate;
|
||||
import dev.isxander.controlify.api.buttonguide.ButtonRenderPosition;
|
||||
import dev.isxander.controlify.gui.ButtonGuideRenderer;
|
||||
import dev.isxander.controlify.screenop.ScreenProcessor;
|
||||
import dev.isxander.controlify.screenop.ScreenProcessorProvider;
|
||||
import dev.isxander.controlify.screenop.compat.vanilla.CreateWorldScreenProcessor;
|
||||
import net.minecraft.client.gui.components.AbstractButton;
|
||||
import net.minecraft.client.gui.layouts.LayoutElement;
|
||||
import net.minecraft.client.gui.screens.worldselection.CreateWorldScreen;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.ModifyArg;
|
||||
|
||||
@Mixin(CreateWorldScreen.class)
|
||||
public abstract class CreateWorldScreenMixin implements ScreenProcessorProvider {
|
||||
@Shadow protected abstract void onCreate();
|
||||
|
||||
@Unique private final ScreenProcessor<CreateWorldScreen> processor = new CreateWorldScreenProcessor((CreateWorldScreen) (Object) this, this::onCreate);
|
||||
|
||||
@ModifyArg(method = "init()V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/layouts/GridLayout$RowHelper;addChild(Lnet/minecraft/client/gui/layouts/LayoutElement;)Lnet/minecraft/client/gui/layouts/LayoutElement;", ordinal = 1))
|
||||
private LayoutElement modifyCancelButton(LayoutElement button) {
|
||||
ButtonGuideRenderer.registerBindingForButton((AbstractButton) button, bindings -> bindings.GUI_BACK, ButtonRenderPosition.TEXT, ButtonGuidePredicate.ALWAYS);
|
||||
return button;
|
||||
}
|
||||
|
||||
@ModifyArg(method = "init()V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/layouts/GridLayout$RowHelper;addChild(Lnet/minecraft/client/gui/layouts/LayoutElement;)Lnet/minecraft/client/gui/layouts/LayoutElement;", ordinal = 0))
|
||||
private LayoutElement modifyCreateButton(LayoutElement button) {
|
||||
ButtonGuideRenderer.registerBindingForButton((AbstractButton) button, bindings -> bindings.GUI_ABSTRACT_ACTION_1, ButtonRenderPosition.TEXT, ButtonGuidePredicate.ALWAYS);
|
||||
return button;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScreenProcessor<?> screenProcessor() {
|
||||
return processor;
|
||||
}
|
||||
}
|
@ -1,10 +1,18 @@
|
||||
package dev.isxander.controlify.mixins.feature.screenop.vanilla;
|
||||
|
||||
import dev.isxander.controlify.api.buttonguide.ButtonGuidePredicate;
|
||||
import dev.isxander.controlify.api.buttonguide.ButtonRenderPosition;
|
||||
import dev.isxander.controlify.gui.ButtonGuideRenderer;
|
||||
import dev.isxander.controlify.screenop.ScreenProcessor;
|
||||
import dev.isxander.controlify.screenop.ScreenProcessorProvider;
|
||||
import dev.isxander.controlify.screenop.compat.vanilla.SelectWorldScreenProcessor;
|
||||
import net.minecraft.client.gui.components.AbstractButton;
|
||||
import net.minecraft.client.gui.components.Renderable;
|
||||
import net.minecraft.client.gui.components.events.GuiEventListener;
|
||||
import net.minecraft.client.gui.screens.worldselection.SelectWorldScreen;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.ModifyArg;
|
||||
|
||||
@Mixin(SelectWorldScreen.class)
|
||||
public class SelectWorldScreenMixin implements ScreenProcessorProvider {
|
||||
@ -14,4 +22,16 @@ public class SelectWorldScreenMixin implements ScreenProcessorProvider {
|
||||
public ScreenProcessor<?> screenProcessor() {
|
||||
return controlify$processor;
|
||||
}
|
||||
|
||||
@ModifyArg(method = "init()V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screens/worldselection/SelectWorldScreen;addRenderableWidget(Lnet/minecraft/client/gui/components/events/GuiEventListener;)Lnet/minecraft/client/gui/components/events/GuiEventListener;", ordinal = 5))
|
||||
private <T extends GuiEventListener & Renderable> T modifyCancelButton(T button) {
|
||||
ButtonGuideRenderer.registerBindingForButton((AbstractButton) button, bindings -> bindings.GUI_BACK, ButtonRenderPosition.TEXT, ButtonGuidePredicate.ALWAYS);
|
||||
return button;
|
||||
}
|
||||
|
||||
@ModifyArg(method = "init()V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screens/worldselection/SelectWorldScreen;addRenderableWidget(Lnet/minecraft/client/gui/components/events/GuiEventListener;)Lnet/minecraft/client/gui/components/events/GuiEventListener;", ordinal = 1))
|
||||
private <T extends GuiEventListener & Renderable> T modifyCreateButton(T button) {
|
||||
ButtonGuideRenderer.registerBindingForButton((AbstractButton) button, bindings -> bindings.GUI_ABSTRACT_ACTION_1, ButtonRenderPosition.TEXT, ButtonGuidePredicate.ALWAYS);
|
||||
return button;
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import dev.isxander.controlify.controller.Controller;
|
||||
import dev.isxander.controlify.api.event.ControlifyEvents;
|
||||
import dev.isxander.controlify.mixins.feature.screenop.vanilla.ScreenAccessor;
|
||||
import dev.isxander.controlify.mixins.feature.screenop.vanilla.TabNavigationBarAccessor;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.ComponentPath;
|
||||
import net.minecraft.client.gui.components.events.GuiEventListener;
|
||||
import net.minecraft.client.gui.components.tabs.Tab;
|
||||
@ -13,6 +14,8 @@ import net.minecraft.client.gui.components.tabs.TabNavigationBar;
|
||||
import net.minecraft.client.gui.navigation.FocusNavigationEvent;
|
||||
import net.minecraft.client.gui.navigation.ScreenDirection;
|
||||
import net.minecraft.client.gui.screens.Screen;
|
||||
import net.minecraft.client.resources.sounds.SimpleSoundInstance;
|
||||
import net.minecraft.sounds.SoundEvents;
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
|
||||
import java.util.*;
|
||||
@ -20,6 +23,7 @@ import java.util.*;
|
||||
public class ScreenProcessor<T extends Screen> {
|
||||
public final T screen;
|
||||
protected int lastMoved = 0;
|
||||
protected final Minecraft minecraft = Minecraft.getInstance();
|
||||
|
||||
public ScreenProcessor(T screen) {
|
||||
this.screen = screen;
|
||||
@ -101,8 +105,10 @@ public class ScreenProcessor<T extends Screen> {
|
||||
|
||||
if (controller.bindings().GUI_PRESS.justPressed())
|
||||
screen.keyPressed(GLFW.GLFW_KEY_ENTER, 0, 0);
|
||||
if (controller.bindings().GUI_BACK.justPressed())
|
||||
if (controller.bindings().GUI_BACK.justPressed()) {
|
||||
this.playClackSound();
|
||||
screen.onClose();
|
||||
}
|
||||
}
|
||||
|
||||
protected void handleVMouseNavigation(Controller<?, ?> controller) {
|
||||
@ -174,4 +180,8 @@ public class ScreenProcessor<T extends Screen> {
|
||||
|
||||
return tree;
|
||||
}
|
||||
|
||||
protected void playClackSound() {
|
||||
minecraft.getSoundManager().play(SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK, 1.0F));
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,24 @@
|
||||
package dev.isxander.controlify.screenop.compat.vanilla;
|
||||
|
||||
import dev.isxander.controlify.controller.Controller;
|
||||
import dev.isxander.controlify.screenop.ScreenProcessor;
|
||||
import net.minecraft.client.gui.screens.worldselection.CreateWorldScreen;
|
||||
|
||||
public class CreateWorldScreenProcessor extends ScreenProcessor<CreateWorldScreen> {
|
||||
private final Runnable onCreateButton;
|
||||
|
||||
public CreateWorldScreenProcessor(CreateWorldScreen screen, Runnable onCreateButton) {
|
||||
super(screen);
|
||||
this.onCreateButton = onCreateButton;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleButtons(Controller<?, ?> controller) {
|
||||
if (controller.bindings().GUI_ABSTRACT_ACTION_1.justPressed()) {
|
||||
this.onCreateButton.run();
|
||||
this.playClackSound();
|
||||
}
|
||||
|
||||
super.handleButtons(controller);
|
||||
}
|
||||
}
|
@ -3,7 +3,9 @@ package dev.isxander.controlify.screenop.compat.vanilla;
|
||||
import dev.isxander.controlify.controller.Controller;
|
||||
import dev.isxander.controlify.screenop.ScreenProcessor;
|
||||
import dev.isxander.controlify.mixins.feature.screenop.vanilla.SelectWorldScreenAccessor;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.components.Button;
|
||||
import net.minecraft.client.gui.screens.worldselection.CreateWorldScreen;
|
||||
import net.minecraft.client.gui.screens.worldselection.SelectWorldScreen;
|
||||
|
||||
public class SelectWorldScreenProcessor extends ScreenProcessor<SelectWorldScreen> {
|
||||
@ -13,6 +15,12 @@ public class SelectWorldScreenProcessor extends ScreenProcessor<SelectWorldScree
|
||||
|
||||
@Override
|
||||
protected void handleButtons(Controller<?, ?> controller) {
|
||||
if (controller.bindings().GUI_ABSTRACT_ACTION_1.justPressed()) {
|
||||
this.playClackSound();
|
||||
CreateWorldScreen.openFresh(Minecraft.getInstance(), screen);
|
||||
return;
|
||||
}
|
||||
|
||||
if (screen.getFocused() != null && screen.getFocused() instanceof Button) {
|
||||
if (controller.bindings().GUI_BACK.justPressed()) {
|
||||
screen.setFocused(((SelectWorldScreenAccessor) screen).getList());
|
||||
|
@ -20,8 +20,10 @@
|
||||
"controlify.gui.toggle_sprint.tooltip": "How the state of the sprint button behaves.",
|
||||
"controlify.gui.auto_jump": "Auto Jump",
|
||||
"controlify.gui.auto_jump.tooltip": "If the player should automatically jump when you reach a block.",
|
||||
"controlify.gui.show_guide": "Show Button Guide",
|
||||
"controlify.gui.show_guide.tooltip": "Show a HUD in-game displaying actions you can do with controller buttons.",
|
||||
"controlify.gui.show_ingame_guide": "Show Ingame Button Guide",
|
||||
"controlify.gui.show_ingame_guide.tooltip": "Show a HUD in-game displaying actions you can do with controller buttons.",
|
||||
"controlify.gui.show_screen_guide": "Show Screen Button Guide",
|
||||
"controlify.gui.show_screen_guide.tooltip": "Show various helpers in GUIs to assist in using controller input.",
|
||||
"controlify.gui.vmouse_sensitivity": "Virtual Mouse Sensitivity",
|
||||
"controlify.gui.vmouse_sensitivity.tooltip": "How fast the virtual mouse moves.",
|
||||
"controlify.gui.chat_screen_offset": "On-screen keyboard height",
|
||||
@ -55,6 +57,8 @@
|
||||
|
||||
"controlify.gui.button": "Controller Settings...",
|
||||
|
||||
"controlify.gui.controller_unavailable": "Controller unavailable and cannot be edited.",
|
||||
|
||||
"controlify.toast.vmouse_enabled.title": "Virtual Mouse Enabled",
|
||||
"controlify.toast.vmouse_enabled.description": "Controlify virtual mouse is now enabled for this screen.",
|
||||
"controlify.toast.vmouse_disabled.title": "Virtual Mouse Disabled",
|
||||
|
@ -0,0 +1,176 @@
|
||||
{
|
||||
"axes": [
|
||||
{
|
||||
"ids": [0, 1],
|
||||
"identifier": "ps4_left_stick",
|
||||
"deadzone": true,
|
||||
"rest": 0.0,
|
||||
"axis_names": [
|
||||
["right", "left"],
|
||||
["down", "up"]
|
||||
]
|
||||
},
|
||||
{
|
||||
"ids": [2, 5],
|
||||
"identifier": "ps4_right_stick",
|
||||
"deadzone": true,
|
||||
"axis_names": [
|
||||
["right", "left"],
|
||||
["down", "up"]
|
||||
],
|
||||
"rest": 0.0
|
||||
},
|
||||
{
|
||||
"ids": [3],
|
||||
"identifier": "ps4_left_trigger",
|
||||
"rest": 0.0,
|
||||
"deadzone": false,
|
||||
"range": [0.0, 1.0],
|
||||
"axis_names": [
|
||||
["down", "up"]
|
||||
]
|
||||
},
|
||||
{
|
||||
"ids": [4],
|
||||
"identifier": "ps4_right_trigger",
|
||||
"rest": 0.0,
|
||||
"deadzone": false,
|
||||
"range": [0.0, 1.0],
|
||||
"axis_names": [
|
||||
["down", "up"]
|
||||
]
|
||||
},
|
||||
{
|
||||
"ids": [6, 7],
|
||||
"identifier": "xbox_left_stick",
|
||||
"deadzone": true,
|
||||
"rest": 0.0,
|
||||
"axis_names": [
|
||||
["right", "left"],
|
||||
["down", "up"]
|
||||
]
|
||||
},
|
||||
{
|
||||
"ids": [8, 9],
|
||||
"identifier": "xbox_right_stick",
|
||||
"deadzone": true,
|
||||
"axis_names": [
|
||||
["right", "left"],
|
||||
["down", "up"]
|
||||
],
|
||||
"rest": 0.0
|
||||
},
|
||||
{
|
||||
"ids": [10],
|
||||
"identifier": "xbox_left_trigger",
|
||||
"deadzone": false,
|
||||
"rest": 0.0,
|
||||
"range": [0.0, 1.0],
|
||||
"axis_names": [
|
||||
["down", "up"]
|
||||
]
|
||||
},
|
||||
{
|
||||
"ids": [11],
|
||||
"identifier": "xbox_right_trigger",
|
||||
"deadzone": false,
|
||||
"rest": 0.0,
|
||||
"range": [0.0, 1.0],
|
||||
"axis_names": [
|
||||
["down", "up"]
|
||||
]
|
||||
}
|
||||
],
|
||||
"buttons": [
|
||||
{
|
||||
"button": 0,
|
||||
"name": "a"
|
||||
},
|
||||
{
|
||||
"button": 1,
|
||||
"name": "b"
|
||||
},
|
||||
{
|
||||
"button": 2,
|
||||
"name": "x"
|
||||
},
|
||||
{
|
||||
"button": 3,
|
||||
"name": "y"
|
||||
},
|
||||
{
|
||||
"button": 4,
|
||||
"name": "left_bumper"
|
||||
},
|
||||
{
|
||||
"button": 5,
|
||||
"name": "right_bumper"
|
||||
},
|
||||
{
|
||||
"button": 6,
|
||||
"name": "back"
|
||||
},
|
||||
{
|
||||
"button": 7,
|
||||
"name": "start"
|
||||
},
|
||||
{
|
||||
"button": 8,
|
||||
"name": "left_stick"
|
||||
},
|
||||
{
|
||||
"button": 9,
|
||||
"name": "right_stick"
|
||||
},
|
||||
{
|
||||
"button": 10,
|
||||
"name": "a1"
|
||||
},
|
||||
{
|
||||
"button": 11,
|
||||
"name": "b1"
|
||||
},
|
||||
{
|
||||
"button": 12,
|
||||
"name": "x1"
|
||||
},
|
||||
{
|
||||
"button": 13,
|
||||
"name": "y1"
|
||||
},
|
||||
{
|
||||
"button": 14,
|
||||
"name": "left_bumper1"
|
||||
},
|
||||
{
|
||||
"button": 15,
|
||||
"name": "right_bumper1"
|
||||
},
|
||||
{
|
||||
"button": 16,
|
||||
"name": "back1"
|
||||
},
|
||||
{
|
||||
"button": 17,
|
||||
"name": "start1"
|
||||
},
|
||||
{
|
||||
"button": 18,
|
||||
"name": "left_stick1"
|
||||
},
|
||||
{
|
||||
"button": 19,
|
||||
"name": "right_stick1"
|
||||
}
|
||||
],
|
||||
"hats": [
|
||||
{
|
||||
"hat": 0,
|
||||
"name": "dpad_xbox"
|
||||
},
|
||||
{
|
||||
"hat": 1,
|
||||
"name": "dpad_ps4"
|
||||
}
|
||||
]
|
||||
}
|
@ -1,92 +0,0 @@
|
||||
{
|
||||
"axes": [
|
||||
{
|
||||
"ids": [0, 1],
|
||||
"identifier": "left_stick",
|
||||
"deadzone": true,
|
||||
"rest": 0.0,
|
||||
"axis_names": [
|
||||
["right", "left"],
|
||||
["down", "up"]
|
||||
]
|
||||
},
|
||||
{
|
||||
"ids": [2, 3],
|
||||
"identifier": "right_stick",
|
||||
"deadzone": true,
|
||||
"axis_names": [
|
||||
["right", "left"],
|
||||
["down", "up"]
|
||||
],
|
||||
"rest": 0.0
|
||||
},
|
||||
{
|
||||
"ids": [4],
|
||||
"identifier": "left_trigger",
|
||||
"deadzone": false,
|
||||
"rest": 0.0,
|
||||
"range": [0.0, 1.0],
|
||||
"axis_names": [
|
||||
["down", "up"]
|
||||
]
|
||||
},
|
||||
{
|
||||
"ids": [5],
|
||||
"identifier": "right_trigger",
|
||||
"deadzone": false,
|
||||
"rest": 0.0,
|
||||
"range": [0.0, 1.0],
|
||||
"axis_names": [
|
||||
["down", "up"]
|
||||
]
|
||||
}
|
||||
],
|
||||
"buttons": [
|
||||
{
|
||||
"button": 0,
|
||||
"name": "a"
|
||||
},
|
||||
{
|
||||
"button": 1,
|
||||
"name": "b"
|
||||
},
|
||||
{
|
||||
"button": 2,
|
||||
"name": "x"
|
||||
},
|
||||
{
|
||||
"button": 3,
|
||||
"name": "y"
|
||||
},
|
||||
{
|
||||
"button": 4,
|
||||
"name": "left_bumper"
|
||||
},
|
||||
{
|
||||
"button": 5,
|
||||
"name": "right_bumper"
|
||||
},
|
||||
{
|
||||
"button": 6,
|
||||
"name": "back"
|
||||
},
|
||||
{
|
||||
"button": 7,
|
||||
"name": "start"
|
||||
},
|
||||
{
|
||||
"button": 8,
|
||||
"name": "left_stick"
|
||||
},
|
||||
{
|
||||
"button": 9,
|
||||
"name": "right_stick"
|
||||
}
|
||||
],
|
||||
"hats": [
|
||||
{
|
||||
"hat": 0,
|
||||
"name": "dpad"
|
||||
}
|
||||
]
|
||||
}
|
@ -25,14 +25,17 @@
|
||||
"feature.bind.KeyMappingAccessor",
|
||||
"feature.chatkbheight.ChatComponentMixin",
|
||||
"feature.chatkbheight.ChatScreenMixin",
|
||||
"feature.guide.ClientPacketListenerMixin",
|
||||
"feature.guide.GuiMixin",
|
||||
"feature.guide.ingame.ClientPacketListenerMixin",
|
||||
"feature.guide.ingame.GuiMixin",
|
||||
"feature.guide.screen.AbstractButtonMixin",
|
||||
"feature.guide.screen.AbstractWidgetMixin",
|
||||
"feature.guide.screen.TabNavigationBarMixin",
|
||||
"feature.screenop.vanilla.AbstractButtonMixin",
|
||||
"feature.screenop.vanilla.AbstractContainerEventHandlerMixin",
|
||||
"feature.screenop.vanilla.AbstractSelectionListMixin",
|
||||
"feature.screenop.vanilla.AbstractSliderButtonMixin",
|
||||
"feature.screenop.vanilla.ContainerObjectSelectionListEntryMixin",
|
||||
|
||||
"feature.screenop.vanilla.CreateWorldScreenMixin",
|
||||
"feature.screenop.vanilla.CreativeModeInventoryScreenAccessor",
|
||||
"feature.screenop.vanilla.CreativeModeInventoryScreenMixin",
|
||||
"feature.screenop.vanilla.JoinMultiplayerScreenAccessor",
|
||||
|
@ -73,8 +73,8 @@ public class ClientTestHelper {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String guid() {
|
||||
return "FAKE";
|
||||
public int joystickId() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
Reference in New Issue
Block a user