diff --git a/src/main/java/me/lambdaurora/lambdacontrols/Controller.java b/src/main/java/me/lambdaurora/lambdacontrols/Controller.java new file mode 100644 index 0000000..b57a9dd --- /dev/null +++ b/src/main/java/me/lambdaurora/lambdacontrols/Controller.java @@ -0,0 +1,188 @@ +/* + * Copyright © 2019 LambdAurora + * + * This file is part of LambdaControls. + * + * Licensed under the MIT license. For more information, + * see the LICENSE file. + */ + +package me.lambdaurora.lambdacontrols; + +import org.aperlambda.lambdacommon.utils.Nameable; +import org.jetbrains.annotations.NotNull; +import org.lwjgl.BufferUtils; +import org.lwjgl.glfw.GLFW; +import org.lwjgl.glfw.GLFWGamepadState; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.nio.channels.Channels; +import java.nio.channels.ReadableByteChannel; +import java.nio.channels.SeekableByteChannel; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +import static org.lwjgl.BufferUtils.createByteBuffer; + +/** + * Represents a controller. + */ +public class Controller implements Nameable +{ + private static final Map CONTROLLERS = new HashMap<>(); + private final int id; + + public Controller(int id) + { + this.id = id; + } + + /** + * Gets the identifier of this controller. + * + * @return The identifier of this controller. + */ + public int get_id() + { + return this.id; + } + + /** + * Gets the controller's globally unique identifier. + * + * @return The controller's GUID. + */ + public String get_guid() + { + String guid = GLFW.glfwGetJoystickGUID(this.id); + return guid == null ? "" : guid; + } + + /** + * Returns whether this controller is connected or not. + * + * @return True if this controller is connected, else false. + */ + public boolean is_connected() + { + return GLFW.glfwJoystickPresent(this.id); + } + + /** + * Returns whether this controller is a gamepad or not. + * + * @return True if this controller is a gamepad, else false. + */ + public boolean is_gamepad() + { + return GLFW.glfwJoystickIsGamepad(this.id); + } + + /** + * Gets the name of the controller. + * + * @return The controller's name. + */ + @Override + public @NotNull String get_name() + { + String name = this.is_gamepad() ? GLFW.glfwGetGamepadName(this.id) : GLFW.glfwGetJoystickName(this.id); + return name == null ? "" : name; + } + + /** + * Gets the state of the controller. + * + * @return The state of the controller input. + */ + public GLFWGamepadState get_state() + { + GLFWGamepadState state = GLFWGamepadState.create(); + if (this.is_gamepad()) + GLFW.glfwGetGamepadState(this.id, state); + return state; + } + + public static @NotNull Controller by_id(int id) + { + if (id > GLFW.GLFW_JOYSTICK_LAST) { + LambdaControls.get().log("Controller '" + id + "' doesn't exist."); + id = GLFW.GLFW_JOYSTICK_LAST; + } + Controller controller; + if (CONTROLLERS.containsKey(id)) + return CONTROLLERS.get(id); + else { + controller = new Controller(id); + CONTROLLERS.put(id, controller); + return controller; + } + } + + public static @NotNull Optional by_guid(@NotNull String guid) + { + return CONTROLLERS.values().stream().filter(Controller::is_connected) + .filter(controller -> controller.get_guid().equals(guid)) + .findFirst(); + } + + private static ByteBuffer resizeBuffer(ByteBuffer buffer, int new_capacity) + { + ByteBuffer newBuffer = BufferUtils.createByteBuffer(new_capacity); + buffer.flip(); + newBuffer.put(buffer); + return newBuffer; + } + + /** + * Reads the specified resource and returns the raw data as a ByteBuffer. + * + * @param resource The resource to read. + * @param buffer_size The initial buffer size. + * @return The resource data. + * @throws IOException If an IO error occurs. + */ + private static ByteBuffer io_resource_to_buffer(String resource, int buffer_size) throws IOException + { + ByteBuffer buffer = null; + + Path path = Paths.get(resource); + if (Files.isReadable(path)) { + try (SeekableByteChannel fc = Files.newByteChannel(path)) { + buffer = createByteBuffer((int) fc.size() + 2); + while (fc.read(buffer) != -1) { + ; + } + buffer.put((byte) 0); + } + } + + buffer.flip(); + return buffer; + } + + /** + * Updates the controller mappings. + */ + public static void update_mappings() + { + try { + File mappings_file = new File("config/gamecontrollerdb.txt"); + if (!mappings_file.exists()) + return; + ByteBuffer buffer = io_resource_to_buffer(mappings_file.getPath(), 1024); + //buffer.rewind(); + System.out.println(buffer); + GLFW.glfwUpdateGamepadMappings(buffer); + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/src/main/java/me/lambdaurora/lambdacontrols/ControllerInput.java b/src/main/java/me/lambdaurora/lambdacontrols/ControllerInput.java index b66a246..7d9d611 100644 --- a/src/main/java/me/lambdaurora/lambdacontrols/ControllerInput.java +++ b/src/main/java/me/lambdaurora/lambdacontrols/ControllerInput.java @@ -9,7 +9,7 @@ package me.lambdaurora.lambdacontrols; -import me.lambdaurora.lambdacontrols.mixin.AbstractContainerScreenAccessor; +import me.lambdaurora.lambdacontrols.util.AbstractContainerScreenAccessor; import me.lambdaurora.lambdacontrols.util.CreativeInventoryScreenAccessor; import me.lambdaurora.lambdacontrols.util.LambdaKeyBinding; import me.lambdaurora.lambdacontrols.util.MouseAccessor; @@ -17,18 +17,21 @@ import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.Element; import net.minecraft.client.gui.ParentElement; import net.minecraft.client.gui.screen.Screen; -import net.minecraft.client.gui.screen.ingame.AbstractContainerScreen; -import net.minecraft.client.gui.screen.ingame.CreativeInventoryScreen; +import net.minecraft.client.gui.screen.advancement.AdvancementsScreen; +import net.minecraft.client.gui.screen.ingame.*; import net.minecraft.client.gui.screen.world.WorldListWidget; import net.minecraft.client.gui.widget.*; import net.minecraft.client.options.KeyBinding; import net.minecraft.container.Slot; +import net.minecraft.container.SlotActionType; import net.minecraft.item.ItemGroup; import net.minecraft.util.math.MathHelper; import org.aperlambda.lambdacommon.utils.Pair; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.lwjgl.glfw.GLFW; +import org.lwjgl.glfw.GLFWGamepadState; +import org.lwjgl.system.CallbackI; import java.nio.ByteBuffer; import java.nio.FloatBuffer; @@ -41,12 +44,8 @@ public class ControllerInput { private static final Map BUTTON_STATES = new HashMap<>(); private static final Map BUTTON_COOLDOWNS = new HashMap<>(); - private static final Map AXE_STATES = new HashMap<>(); - private final LambdaControls mod; private final LambdaControlsConfig config; - private int controller = GLFW.GLFW_JOYSTICK_3; private int action_gui_cooldown = 0; - private boolean last_a_state = false; private boolean continuous_sneak = false; private int last_sneak = 0; private double prev_target_yaw = 0.0; @@ -64,7 +63,6 @@ public class ControllerInput public ControllerInput(@NotNull LambdaControls mod) { - this.mod = mod; this.config = mod.config; } @@ -84,16 +82,26 @@ public class ControllerInput --this.action_gui_cooldown; this.prev_target_yaw = this.target_yaw; this.prev_target_pitch = this.target_pitch; - this.fetch_button_input(client); - this.fetch_axe_input(client); + this.prev_target_mouse_x = this.target_mouse_x; + this.prev_target_mouse_y = this.target_mouse_y; + + Controller controller = this.config.get_controller(); + if (controller.is_connected()) { + GLFWGamepadState state = controller.get_state(); + this.fetch_button_input(client, state); + this.fetch_axe_input(client, state); + } } public void on_pre_render_screen(@NotNull MinecraftClient client, @NotNull Screen screen) { - if (this.prev_target_mouse_x != this.target_mouse_x || this.prev_target_mouse_y != this.target_mouse_y) { - double mouse_x = prev_target_mouse_x + (this.target_mouse_x - this.prev_target_mouse_x) * client.getTickDelta() + 0.5; - double mouse_y = prev_target_mouse_y + (this.target_mouse_y - this.prev_target_mouse_y) * client.getTickDelta() + 0.5; - GLFW.glfwSetCursorPos(client.window.getHandle(), mouse_x, mouse_y); + if (!this.is_screen_interactive(screen)) { + if (this.prev_target_mouse_x != this.target_mouse_x || this.prev_target_mouse_y != this.target_mouse_y) { + double mouse_x = this.prev_target_mouse_x + (this.target_mouse_x - this.prev_target_mouse_x) * client.getTickDelta() + 0.5; + double mouse_y = this.prev_target_mouse_y + (this.target_mouse_y - this.prev_target_mouse_y) * client.getTickDelta() + 0.5; + GLFW.glfwSetCursorPos(client.window.getHandle(), mouse_x, mouse_y); + ((MouseAccessor) client.mouse).on_cursor_pos(client.window.getHandle(), mouse_x, mouse_y); + } } } @@ -113,48 +121,43 @@ public class ControllerInput public void on_screen_open(@NotNull MinecraftClient client, int window_width, int window_height) { if (client.currentScreen == null) { + this.mouse_speed_x = this.mouse_speed_y = 0.0F; this.target_mouse_x = this.prev_target_mouse_x = (int) (window_width / 2.F); this.target_mouse_y = this.prev_target_mouse_y = (int) (window_height / 2.F); } } - private void fetch_button_input(@NotNull MinecraftClient client) + private void fetch_button_input(@NotNull MinecraftClient client, @NotNull GLFWGamepadState gamepad_state) { - ByteBuffer buffer = GLFW.glfwGetJoystickButtons(this.controller); - if (buffer != null) { - for (int i = 0; i < buffer.limit(); i++) { - boolean btn_state = buffer.get() == (byte) 1; - boolean previous_state = BUTTON_STATES.getOrDefault(i, false); + ByteBuffer buffer = gamepad_state.buttons(); + for (int i = 0; i < buffer.limit(); i++) { + boolean btn_state = buffer.get() == (byte) 1; + boolean previous_state = BUTTON_STATES.getOrDefault(i, false); - if (btn_state != previous_state) { - this.handle_button(client, i, btn_state ? 0 : 1, btn_state); - if (btn_state) - BUTTON_COOLDOWNS.put(i, 5); - } else if (btn_state) { - if (BUTTON_COOLDOWNS.getOrDefault(i, 0) == 0) { - BUTTON_COOLDOWNS.put(i, 5); - this.handle_button(client, i, 2, true); - } + if (btn_state != previous_state) { + this.handle_button(client, i, btn_state ? 0 : 1, btn_state); + if (btn_state) + BUTTON_COOLDOWNS.put(i, 5); + } else if (btn_state) { + if (BUTTON_COOLDOWNS.getOrDefault(i, 0) == 0) { + BUTTON_COOLDOWNS.put(i, 5); + this.handle_button(client, i, 2, true); } - - BUTTON_STATES.put(i, btn_state); - if (this.config.is_jump_button(i)) - this.last_a_state = btn_state; } + + BUTTON_STATES.put(i, btn_state); } } - private void fetch_axe_input(@NotNull MinecraftClient client) + private void fetch_axe_input(@NotNull MinecraftClient client, @NotNull GLFWGamepadState gamepad_state) { - FloatBuffer buffer = GLFW.glfwGetJoystickAxes(this.controller); - if (buffer != null) { - for (int i = 0; i < buffer.limit(); i++) { - float value = buffer.get(); - float abs_value = Math.abs(value); + FloatBuffer buffer = gamepad_state.axes(); + for (int i = 0; i < buffer.limit(); i++) { + float value = buffer.get(); + float abs_value = Math.abs(value); - int state = value > this.config.get_dead_zone() ? 1 : (value < -this.config.get_dead_zone() ? 2 : 0); - this.handle_axe(client, i, value, abs_value, state); - } + int state = value > this.config.get_dead_zone() ? 1 : (value < -this.config.get_dead_zone() ? 2 : 0); + this.handle_axe(client, i, value, abs_value, state); } } @@ -162,35 +165,60 @@ public class ControllerInput { if (action == 0) { // Handles RB and LB buttons. - if (this.config.is_hotbar_left_button(button) || this.config.is_hotbar_right_button(button)) { - this.handle_rb_lb(client, this.config.is_hotbar_right_button(button)); + if (button == GLFW.GLFW_GAMEPAD_BUTTON_LEFT_BUMPER || button == GLFW.GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER) { + this.handle_rb_lb(client, button == GLFW.GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER); return; } // Handles when the player presses the Start button. - if (this.config.is_start_button(button)) { + if (button == GLFW.GLFW_GAMEPAD_BUTTON_START) { // If in game, then pause the game. if (client.currentScreen == null) client.openPauseMenu(false); - else // Else just close the current screen. + else if (client.currentScreen instanceof AbstractContainerScreen) // If the current screen is a container then close it. + client.player.closeContainer(); + else// Else just close the current screen. client.currentScreen.onClose(); - return; } - if (this.config.is_jump_button(button) && client.currentScreen != null) { + if (button == GLFW.GLFW_GAMEPAD_BUTTON_A && client.currentScreen != null) { if (this.action_gui_cooldown == 0) { Element focused = client.currentScreen.getFocused(); - if (focused != null) + if (focused != null && this.is_screen_interactive(client.currentScreen)) this.handle_a_button(focused); this.action_gui_cooldown = 5; // Prevent to press too quickly the focused element, so we have to skip 5 ticks. return; } } + + if (client.currentScreen instanceof AbstractContainerScreen) { + double pos_x = client.mouse.getX() * (double) client.window.getScaledWidth() / (double) client.window.getWidth(); + double pos_y = client.mouse.getY() * (double) client.window.getScaledHeight() / (double) client.window.getHeight(); + Slot slot = ((AbstractContainerScreenAccessor) client.currentScreen).get_slot_at(pos_x, pos_y); + if (button == GLFW.GLFW_GAMEPAD_BUTTON_A && slot != null) { + client.interactionManager.method_2906(((AbstractContainerScreen) client.currentScreen).getContainer().syncId, slot.id, GLFW.GLFW_MOUSE_BUTTON_1, SlotActionType.PICKUP, client.player); + return; + } else if (button == GLFW.GLFW_GAMEPAD_BUTTON_B) { + client.player.closeContainer(); + return; + } else if (button == GLFW.GLFW_GAMEPAD_BUTTON_X && slot != null) { + client.interactionManager.method_2906(((AbstractContainerScreen) client.currentScreen).getContainer().syncId, slot.id, GLFW.GLFW_MOUSE_BUTTON_2, SlotActionType.PICKUP, client.player); + return; + } else if (button == GLFW.GLFW_GAMEPAD_BUTTON_Y && slot != null) { + client.interactionManager.method_2906(((AbstractContainerScreen) client.currentScreen).getContainer().syncId, slot.id, GLFW.GLFW_MOUSE_BUTTON_1, SlotActionType.QUICK_MOVE, client.player); + return; + } + } else if (button == GLFW.GLFW_GAMEPAD_BUTTON_B) { + if (client.currentScreen != null) { + client.currentScreen.onClose(); + return; + } + } } // Handles sneak button and continuous sneak. - if (this.config.is_sneak_button(button) && client.player != null) { + if (button == GLFW.GLFW_GAMEPAD_BUTTON_RIGHT_THUMB && client.player != null) { if (action == 0) { if (this.continuous_sneak) { this.set_sneaking(client, this.continuous_sneak = false); @@ -208,17 +236,20 @@ public class ControllerInput return; } - if (this.config.is_jump_button(button) && client.currentScreen != null) { - if (this.last_a_state != state) { - double mouse_x = client.mouse.getX() * (double) client.window.getScaledWidth() / (double) client.window.getWidth(); - double mouse_y = client.mouse.getY() * (double) client.window.getScaledHeight() / (double) client.window.getHeight(); - if (state) { - client.currentScreen.mouseClicked(mouse_x, mouse_y, GLFW.GLFW_MOUSE_BUTTON_1); - } else { - client.currentScreen.mouseReleased(mouse_x, mouse_y, GLFW.GLFW_MOUSE_BUTTON_1); - } - return; + if (button == GLFW.GLFW_GAMEPAD_BUTTON_A && client.currentScreen != null && !this.is_screen_interactive(client.currentScreen)) { + double mouse_x = client.mouse.getX() * (double) client.window.getScaledWidth() / (double) client.window.getWidth(); + double mouse_y = client.mouse.getY() * (double) client.window.getScaledHeight() / (double) client.window.getHeight(); + if (client.currentScreen instanceof AbstractContainerScreen) { + Slot slot = ((AbstractContainerScreenAccessor) client.currentScreen).get_slot_at(mouse_x, mouse_y); + if (slot != null) + return; } + if (action == 0) { + client.currentScreen.mouseClicked(mouse_x, mouse_y, GLFW.GLFW_MOUSE_BUTTON_1); + } else if (action == 1) { + client.currentScreen.mouseReleased(mouse_x, mouse_y, GLFW.GLFW_MOUSE_BUTTON_1); + } + return; } if (client.currentScreen == null && action != 2) { @@ -265,7 +296,7 @@ public class ControllerInput } else { boolean allow_mouse_control = true; - if (this.action_gui_cooldown == 0 && this.config.is_movement_axis(axe)) { + if (this.action_gui_cooldown == 0 && this.config.is_movement_axis(axe) && this.is_screen_interactive(client.currentScreen)) { if (this.config.is_forward_button(axe, false, as_button_state)) { allow_mouse_control = this.change_focus(client.currentScreen, false); } else if (this.config.is_back_button(axe, false, as_button_state)) { @@ -319,13 +350,13 @@ public class ControllerInput } if (Math.abs(this.mouse_speed_x) > .05F || Math.abs(this.mouse_speed_y) > .05F) { - this.target_mouse_x += this.mouse_speed_x * this.config.get_rotation_speed(); + this.target_mouse_x += this.mouse_speed_x * this.config.get_mouse_speed(); this.target_mouse_x = MathHelper.clamp(this.target_mouse_x, 0, client.window.getWidth()); - this.target_mouse_y += this.mouse_speed_y * this.config.get_rotation_speed(); + this.target_mouse_y += this.mouse_speed_y * this.config.get_mouse_speed(); this.target_mouse_y = MathHelper.clamp(this.target_mouse_y, 0, client.window.getHeight()); } - //this.move_mouse_to_closest_slot(client, client.currentScreen); + this.move_mouse_to_closest_slot(client, client.currentScreen); } this.prev_x_axis = movement_x; @@ -431,6 +462,11 @@ public class ControllerInput } } + private boolean is_screen_interactive(@NotNull Screen screen) + { + return !(screen instanceof AdvancementsScreen || screen instanceof AbstractContainerScreen); + } + // Inspired from https://github.com/MrCrayfish/Controllable/blob/1.14.X/src/main/java/com/mrcrayfish/controllable/client/ControllerInput.java#L686. private void move_mouse_to_closest_slot(@NotNull MinecraftClient client, @Nullable Screen screen) { @@ -469,15 +505,18 @@ public class ControllerInput this.target_mouse_x += delta_x * 0.75; this.target_mouse_y += delta_y * 0.75; } else { - mouse_speed_x = 0.F; - mouse_speed_y = 0.F; + this.mouse_speed_x *= 0.3F; + this.mouse_speed_y *= 0.3F; } this.mouse_speed_x *= .75F; this.mouse_speed_y *= .75F; + } else { + this.mouse_speed_x *= .1F; + this.mouse_speed_y *= .1F; } } else { - this.mouse_speed_x *= .1F; - this.mouse_speed_y *= .1F; + this.mouse_speed_x *= .3F; + this.mouse_speed_y *= .3F; } } else { this.mouse_speed_x = 0.F; diff --git a/src/main/java/me/lambdaurora/lambdacontrols/LambdaControls.java b/src/main/java/me/lambdaurora/lambdacontrols/LambdaControls.java index 71f5c39..0a937f2 100644 --- a/src/main/java/me/lambdaurora/lambdacontrols/LambdaControls.java +++ b/src/main/java/me/lambdaurora/lambdacontrols/LambdaControls.java @@ -9,63 +9,26 @@ package me.lambdaurora.lambdacontrols; -import me.lambdaurora.lambdacontrols.mixin.AbstractContainerScreenAccessor; -import me.lambdaurora.lambdacontrols.util.CreativeInventoryScreenAccessor; -import me.lambdaurora.lambdacontrols.util.LambdaKeyBinding; import net.fabricmc.api.ClientModInitializer; import net.minecraft.client.MinecraftClient; -import net.minecraft.client.gui.Element; -import net.minecraft.client.gui.screen.Screen; -import net.minecraft.client.gui.screen.ingame.AbstractContainerScreen; -import net.minecraft.client.gui.screen.ingame.CreativeInventoryScreen; -import net.minecraft.client.gui.screen.ingame.InventoryScreen; -import net.minecraft.client.gui.screen.world.WorldListWidget; -import net.minecraft.client.gui.widget.AbstractPressableButtonWidget; -import net.minecraft.client.gui.widget.EntryListWidget; -import net.minecraft.client.gui.widget.SliderWidget; -import net.minecraft.client.options.GameOptions; -import net.minecraft.container.Slot; -import net.minecraft.item.ItemGroup; -import net.minecraft.util.math.MathHelper; +import net.minecraft.client.toast.SystemToast; +import net.minecraft.text.LiteralText; +import net.minecraft.text.TranslatableText; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.aperlambda.lambdacommon.utils.Pair; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import org.lwjgl.glfw.GLFW; -import java.nio.ByteBuffer; -import java.nio.FloatBuffer; -import java.util.Comparator; -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; - -import static org.lwjgl.glfw.GLFW.GLFW_JOYSTICK_1; - /** * Represents the LambdaControls mod. */ public class LambdaControls implements ClientModInitializer { - private static LambdaControls INSTANCE; - public final Logger logger = LogManager.getLogger("LambdaControls"); - public final LambdaControlsConfig config = new LambdaControlsConfig(this); - public final ControllerInput controller_input = new ControllerInput(this); - private final Map BUTTON_STATES = new HashMap<>(); - private float prev_x_axis = 0.F; - private float prev_y_axis = 0.F; - private int prev_target_mouse_x = 0; - private int prev_target_mouse_y = 0; - private int target_mouse_x = 0; - private int target_mouse_y = 0; - private float mouse_speed_x = 0.F; - private float mouse_speed_y = 0.F; - private boolean last_a_state = false; - private int cid = GLFW_JOYSTICK_1; - private boolean allow_controller_mouse = true; - private int action_gui_cooldown = 0; + private static LambdaControls INSTANCE; + public final Logger logger = LogManager.getLogger("LambdaControls"); + public final LambdaControlsConfig config = new LambdaControlsConfig(this); + public final ControllerInput controller_input = new ControllerInput(this); @Override public void onInitializeClient() @@ -80,11 +43,16 @@ public class LambdaControls implements ClientModInitializer */ public void on_mc_init(@NotNull MinecraftClient client) { + Controller.update_mappings(); this.config.init_keybindings(client.options); GLFW.glfwSetJoystickCallback((jid, event) -> { if (event == GLFW.GLFW_CONNECTED) { - this.log("CONNECTED " + jid); - cid = jid; + Controller controller = Controller.by_id(jid); + client.getToastManager().add(new SystemToast(SystemToast.Type.TUTORIAL_HINT, new TranslatableText("lambdacontrols.controller.connected", jid), + new LiteralText(controller.get_name()))); + } else if (event == GLFW.GLFW_DISCONNECTED) { + client.getToastManager().add(new SystemToast(SystemToast.Type.TUTORIAL_HINT, new TranslatableText("lambdacontrols.controller.disconnected", jid), + null)); } }); } @@ -98,16 +66,6 @@ public class LambdaControls implements ClientModInitializer { if (this.config.get_controls_mode() == ControlsMode.CONTROLLER) this.controller_input.on_tick(client); - /* Decreases the cooldown for the screen focus change. - if (this.action_gui_cooldown > 0) - --this.action_gui_cooldown; - if (this.action_gui_cooldown == 0) - this.allow_controller_mouse = true; - - this.prev_target_mouse_x = this.target_mouse_x; - this.prev_target_mouse_y = this.target_mouse_y; - if (LambdaControls.get().config.get_controls_mode() == ControlsMode.CONTROLLER) - this.on_controller_tick(client);*/ } public void on_render(MinecraftClient client) diff --git a/src/main/java/me/lambdaurora/lambdacontrols/LambdaControlsConfig.java b/src/main/java/me/lambdaurora/lambdacontrols/LambdaControlsConfig.java index b5d53bd..998ede7 100644 --- a/src/main/java/me/lambdaurora/lambdacontrols/LambdaControlsConfig.java +++ b/src/main/java/me/lambdaurora/lambdacontrols/LambdaControlsConfig.java @@ -13,6 +13,7 @@ import com.electronwill.nightconfig.core.file.FileConfig; import net.minecraft.client.options.GameOptions; import net.minecraft.client.options.KeyBinding; import org.jetbrains.annotations.NotNull; +import org.lwjgl.glfw.GLFW; import java.util.HashMap; import java.util.Map; @@ -31,13 +32,21 @@ public class LambdaControlsConfig // Controller settings private double dead_zone; private double rotation_speed; + private double mouse_speed; // Controller controls + private String b_button; private String back_button; + private String dpad_up; + private String dpad_right; + private String dpad_down; + private String dpad_left; private String forward_button; + private String inventory_button; private String jump_button; private String left_button; private String right_button; private String sneak_button; + private String x_button; public LambdaControlsConfig(@NotNull LambdaControls mod) { @@ -52,15 +61,23 @@ public class LambdaControlsConfig this.controls_mode = ControlsMode.by_id(this.config.getOrElse("controls", "default")).orElse(ControlsMode.DEFAULT); this.hud_side = HudSide.by_id(this.config.getOrElse("hud.side", "left")).orElse(HudSide.LEFT); // Controller settings - this.dead_zone = this.config.getOrElse("controller.dead_zone", 0.25D); - this.rotation_speed = this.config.getOrElse("controller.rotation_speed", 25.D); + this.dead_zone = this.config.getOrElse("controller.dead_zone", 0.25); + this.rotation_speed = this.config.getOrElse("controller.rotation_speed", 40.0); + this.mouse_speed = this.config.getOrElse("controller.mouse_speed", 25.0); // Controller controls + this.b_button = this.config.getOrElse("controller.controls.b", "none").toLowerCase(); this.back_button = this.config.getOrElse("controller.controls.back", "none").toLowerCase(); + this.dpad_up = this.config.getOrElse("controller.controls.dpad_up", "none").toLowerCase(); + this.dpad_right = this.config.getOrElse("controller.controls.dpad_right", "none").toLowerCase(); + this.dpad_down = this.config.getOrElse("controller.controls.dpad_down", "none").toLowerCase(); + this.dpad_left = this.config.getOrElse("controller.controls.dpad_left", "none").toLowerCase(); this.forward_button = this.config.getOrElse("controller.controls.forward", "none").toLowerCase(); + this.inventory_button = this.config.getOrElse("controller.controls.inventory", "none").toLowerCase(); this.jump_button = this.config.getOrElse("controller.controls.jump", "none").toLowerCase(); this.left_button = this.config.getOrElse("controller.controls.left", "none").toLowerCase(); this.right_button = this.config.getOrElse("controller.controls.right", "none").toLowerCase(); this.sneak_button = this.config.getOrElse("controller.controls.sneak", "none").toLowerCase(); + this.x_button = this.config.getOrElse("controller.controls.x", "none").toLowerCase(); } public void init_keybindings(GameOptions options) @@ -70,14 +87,12 @@ public class LambdaControlsConfig this.keybinding_mappings.put(str, options.keyAttack); if (!this.back_button.equals("none")) this.keybinding_mappings.put(this.back_button, options.keyBack); - str = this.config.getOrElse("controller.controls.drop", "none").toLowerCase(); - if (!str.equals("none")) - this.keybinding_mappings.put(str, options.keyDrop); + if (!this.b_button.equals("none")) + this.keybinding_mappings.put(this.b_button, options.keyDrop); if (!this.forward_button.equals("none")) this.keybinding_mappings.put(this.forward_button, options.keyForward); - str = this.config.getOrElse("controller.controls.inventory", "none").toLowerCase(); - if (!str.equals("none")) - this.keybinding_mappings.put(str, options.keyInventory); + if (!this.inventory_button.equals("none")) + this.keybinding_mappings.put(this.inventory_button, options.keyInventory); if (!this.jump_button.equals("none")) this.keybinding_mappings.put(this.jump_button, options.keyJump); if (!this.left_button.equals("none")) @@ -92,12 +107,15 @@ public class LambdaControlsConfig str = this.config.getOrElse("controller.controls.use", "none").toLowerCase(); if (!str.equals("none")) this.keybinding_mappings.put(str, options.keyUse); + if (!this.x_button.equals("none")) + this.keybinding_mappings.put(this.x_button, options.keySwapHands); } public void save() { this.config.set("controller.dead_zone", this.dead_zone); this.config.set("controller.rotation_speed", this.rotation_speed); + this.config.set("controller.mouse_speed", this.mouse_speed); this.config.save(); this.mod.log("Configuration saved."); } @@ -144,6 +162,33 @@ public class LambdaControlsConfig this.config.set("hud.side", hud_side.get_name()); } + /** + * Gets the used controller. + * + * @return The used controller. + */ + public @NotNull Controller get_controller() + { + Object raw = this.config.getRaw("controller.id"); + if (raw instanceof Number) { + return Controller.by_id((Integer) raw); + } else if (raw instanceof String) { + return Controller.by_guid((String) raw).orElse(Controller.by_id(GLFW.GLFW_JOYSTICK_1)); + } + return Controller.by_id(GLFW.GLFW_JOYSTICK_1); + } + + /** + * Sets the used controller. + * + * @param controller The used controller. + */ + public void set_controller(@NotNull Controller controller) + { + String guid = controller.get_guid(); + this.config.set("controller.id", guid.equals("") ? controller.get_id() : guid); + } + /** * Gets the controller's dead zone from the configuration. * @@ -184,6 +229,26 @@ public class LambdaControlsConfig this.rotation_speed = rotation_speed; } + /** + * Gets the controller's mouse speed. + * + * @return The mouse speed. + */ + public double get_mouse_speed() + { + return this.mouse_speed; + } + + /** + * Sets the controller's mouse speed. + * + * @param mouse_speed The mouse speed. + */ + public void set_mouse_speed(double mouse_speed) + { + this.mouse_speed = mouse_speed; + } + /** * Returns the keybindings. * @@ -199,11 +264,41 @@ public class LambdaControlsConfig return Optional.ofNullable(this.keybinding_mappings.get(id)); } + /** + * Returns the B button mapping. + * + * @return The B button mapping. + */ + public String get_b_button() + { + return this.b_button; + } + public String get_back_button() { return this.back_button; } + public String get_dpad_up() + { + return this.dpad_up; + } + + public String get_dpad_right() + { + return this.dpad_right; + } + + public String get_dpad_down() + { + return this.dpad_down; + } + + public String get_dpad_left() + { + return this.dpad_left; + } + public String get_forward_button() { return this.forward_button; @@ -219,6 +314,16 @@ public class LambdaControlsConfig return this.config.getOrElse("controller.controls.hotbar_right", "none").toLowerCase(); } + /** + * Gets the Inventory button mapping. + * + * @return The Inventory button mapping. + */ + public String get_inventory_button() + { + return this.inventory_button; + } + public String get_jump_button() { return this.jump_button; @@ -244,6 +349,50 @@ public class LambdaControlsConfig return this.config.getOrElse("controller.controls.start", "none").toLowerCase(); } + public String get_x_button() + { + return this.x_button; + } + + public boolean is_b_button(int button) + { + return this.get_b_button().equals("button_" + button); + } + + public boolean is_back_button(int btn, boolean is_btn, int state) + { + if (!is_btn && state == 0) + return false; + return this.get_back_button().equals((is_btn ? "button_" : "axe_") + btn + (is_btn ? "" : (state == 1 ? "+" : "-"))); + } + + public boolean is_dpad_up(int button) + { + return this.get_dpad_up().equals("button_" + button); + } + + public boolean is_dpad_right(int button) + { + return this.get_dpad_right().equals("button_" + button); + } + + public boolean is_dpad_down(int button) + { + return this.get_dpad_down().equals("button_" + button); + } + + public boolean is_dpad_left(int button) + { + return this.get_dpad_left().equals("button_" + button); + } + + public boolean is_forward_button(int btn, boolean is_btn, int state) + { + if (!is_btn && state == 0) + return false; + return this.get_forward_button().equals((is_btn ? "button_" : "axe_") + btn + (is_btn ? "" : (state == 1 ? "+" : "-"))); + } + public boolean is_hotbar_left_button(int button) { return this.get_hotbar_left_button().equals("button_" + button); @@ -254,18 +403,9 @@ public class LambdaControlsConfig return this.get_hotbar_right_button().equals("button_" + button); } - public boolean is_back_button(int btn, boolean is_btn, int state) + public boolean is_inventory_button(int btn) { - if (!is_btn && state == 0) - return false; - return this.get_back_button().equals((is_btn ? "button_" : "axe_") + btn + (is_btn ? "" : (state == 1 ? "+" : "-"))); - } - - public boolean is_forward_button(int btn, boolean is_btn, int state) - { - if (!is_btn && state == 0) - return false; - return this.get_forward_button().equals((is_btn ? "button_" : "axe_") + btn + (is_btn ? "" : (state == 1 ? "+" : "-"))); + return this.get_inventory_button().equals("button_" + btn); } public boolean is_jump_button(int btn) @@ -297,6 +437,11 @@ public class LambdaControlsConfig return this.get_start_button().equals("button_" + btn); } + public boolean is_x_button(int btn) + { + return this.get_x_button().equals("button_" + btn); + } + public String get_view_down_control() { return this.config.getOrElse("controller.controls.view_down", "none").toLowerCase(); diff --git a/src/main/java/me/lambdaurora/lambdacontrols/gui/LambdaControlsSettingsScreen.java b/src/main/java/me/lambdaurora/lambdacontrols/gui/LambdaControlsSettingsScreen.java index fde4a62..10051e5 100644 --- a/src/main/java/me/lambdaurora/lambdacontrols/gui/LambdaControlsSettingsScreen.java +++ b/src/main/java/me/lambdaurora/lambdacontrols/gui/LambdaControlsSettingsScreen.java @@ -32,6 +32,7 @@ public class LambdaControlsSettingsScreen extends Screen private final GameOptions options; private final Option dead_zone_option; private final Option rotation_speed_option; + private final Option mouse_speed_option; public LambdaControlsSettingsScreen(Screen parent, @NotNull GameOptions options) { @@ -39,18 +40,34 @@ public class LambdaControlsSettingsScreen extends Screen this.mod = LambdaControls.get(); this.parent = parent; this.options = options; - this.dead_zone_option = new DoubleOption("lambdacontrols.menu.dead_zone", 0.05D, 1.0D, 0.05F, game_options -> this.mod.config.get_dead_zone(), + this.dead_zone_option = new DoubleOption("lambdacontrols.menu.dead_zone", 0.05, 1.0, 0.05F, game_options -> this.mod.config.get_dead_zone(), (game_options, new_value) -> { synchronized (this.mod.config) { this.mod.config.set_dead_zone(new_value); } - }, (game_options, option) -> option.getDisplayPrefix() + option.get(options)); - this.rotation_speed_option = new DoubleOption("lambdacontrols.menu.rotation_speed", 0.0D, 50.0D, 0.5F, game_options -> this.mod.config.get_rotation_speed(), + }, (game_options, option) -> { + String value = String.valueOf(option.get(options)); + return option.getDisplayPrefix() + value.substring(0, value.length() > 5 ? 5 : value.length()); + }); + this.rotation_speed_option = new DoubleOption("lambdacontrols.menu.rotation_speed", 0.0, 50.0, 0.5F, game_options -> this.mod.config.get_rotation_speed(), (game_options, new_value) -> { synchronized (this.mod.config) { this.mod.config.set_rotation_speed(new_value); } }, (game_options, option) -> option.getDisplayPrefix() + option.get(options)); + this.mouse_speed_option = new DoubleOption("lambdacontrols.menu.mouse_speed", 0.0, 50.0, 0.5F, game_options -> this.mod.config.get_mouse_speed(), + (game_options, new_value) -> { + synchronized (this.mod.config) { + this.mod.config.set_mouse_speed(new_value); + } + }, (game_options, option) -> option.getDisplayPrefix() + option.get(options)); + } + + @Override + public void removed() + { + this.mod.config.save(); + super.removed(); } @Override @@ -86,6 +103,7 @@ public class LambdaControlsSettingsScreen extends Screen btn -> this.minecraft.openScreen(new ControlsOptionsScreen(this, this.options)))); this.addButton(this.dead_zone_option.createButton(this.options, this.width / 2 - 155, (y += spacing + button_height), 150)); this.addButton(this.rotation_speed_option.createButton(this.options, this.width / 2 - 155 + 160, y, 150)); + this.addButton(this.mouse_speed_option.createButton(this.options, this.width / 2 - 155, (y += spacing + button_height), 150)); this.addButton(new ButtonWidget(this.width / 2 - 155 + 160, this.height - 29, 150, button_height, I18n.translate("gui.done"), (buttonWidget) -> { this.minecraft.openScreen(this.parent); })); diff --git a/src/main/java/me/lambdaurora/lambdacontrols/mixin/AbstractContainerScreenAccessor.java b/src/main/java/me/lambdaurora/lambdacontrols/mixin/AbstractContainerScreenAccessor.java deleted file mode 100644 index 85c6865..0000000 --- a/src/main/java/me/lambdaurora/lambdacontrols/mixin/AbstractContainerScreenAccessor.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright © 2019 LambdAurora - * - * This file is part of LambdaControls. - * - * Licensed under the MIT license. For more information, - * see the LICENSE file. - */ - -package me.lambdaurora.lambdacontrols.mixin; - -import net.minecraft.client.gui.screen.ingame.AbstractContainerScreen; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.gen.Accessor; - -@Mixin(AbstractContainerScreen.class) -public interface AbstractContainerScreenAccessor -{ - @Accessor("left") - int get_left(); - - @Accessor("top") - int get_top(); -} diff --git a/src/main/java/me/lambdaurora/lambdacontrols/mixin/AbstractContainerScreenMixin.java b/src/main/java/me/lambdaurora/lambdacontrols/mixin/AbstractContainerScreenMixin.java new file mode 100644 index 0000000..7ec8d1a --- /dev/null +++ b/src/main/java/me/lambdaurora/lambdacontrols/mixin/AbstractContainerScreenMixin.java @@ -0,0 +1,50 @@ +/* + * Copyright © 2019 LambdAurora + * + * This file is part of LambdaControls. + * + * Licensed under the MIT license. For more information, + * see the LICENSE file. + */ + +package me.lambdaurora.lambdacontrols.mixin; + +import me.lambdaurora.lambdacontrols.util.AbstractContainerScreenAccessor; +import net.minecraft.client.gui.screen.ingame.AbstractContainerScreen; +import net.minecraft.container.Slot; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +/** + * Represents the mixin for the class AbstractContainerScreen. + */ +@Mixin(AbstractContainerScreen.class) +public abstract class AbstractContainerScreenMixin implements AbstractContainerScreenAccessor +{ + @Shadow + protected int left; + + @Shadow + protected int top; + + @Shadow + protected abstract Slot getSlotAt(double xPosition, double yPosition); + + @Override + public int get_left() + { + return this.left; + } + + @Override + public int get_top() + { + return this.top; + } + + @Override + public Slot get_slot_at(double pos_x, double pos_y) + { + return this.getSlotAt(pos_x, pos_y); + } +} diff --git a/src/main/java/me/lambdaurora/lambdacontrols/mixin/GameRendererMixin.java b/src/main/java/me/lambdaurora/lambdacontrols/mixin/GameRendererMixin.java index a7a9bd5..be4b750 100644 --- a/src/main/java/me/lambdaurora/lambdacontrols/mixin/GameRendererMixin.java +++ b/src/main/java/me/lambdaurora/lambdacontrols/mixin/GameRendererMixin.java @@ -27,7 +27,7 @@ public class GameRendererMixin @Final private MinecraftClient client; - @Inject(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/Screen;render(IIF)V")) + @Inject(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Mouse;getX()D")) private void on_render(float tick_delta, long start_time, boolean full_render, CallbackInfo ci) { if (this.client.currentScreen != null && LambdaControls.get().config.get_controls_mode() == ControlsMode.CONTROLLER) diff --git a/src/main/java/me/lambdaurora/lambdacontrols/util/AbstractContainerScreenAccessor.java b/src/main/java/me/lambdaurora/lambdacontrols/util/AbstractContainerScreenAccessor.java new file mode 100644 index 0000000..6b8b9d8 --- /dev/null +++ b/src/main/java/me/lambdaurora/lambdacontrols/util/AbstractContainerScreenAccessor.java @@ -0,0 +1,44 @@ +/* + * Copyright © 2019 LambdAurora + * + * This file is part of LambdaControls. + * + * Licensed under the MIT license. For more information, + * see the LICENSE file. + */ + +package me.lambdaurora.lambdacontrols.util; + +import net.minecraft.container.Slot; +import net.minecraft.item.ItemGroup; +import org.jetbrains.annotations.NotNull; +import org.spongepowered.asm.mixin.gen.Accessor; + +/** + * Represents an accessor to AbstractContainerScreen. + */ +public interface AbstractContainerScreenAccessor +{ + /** + * Gets the left coordinate of the GUI. + * + * @return The left coordinate of the GUI. + */ + int get_left(); + + /** + * Gets the top coordinate of the GUI. + * + * @return The top coordinate of the GUI. + */ + int get_top(); + + /** + * Gets the slot at position. + * + * @param pos_x The X position to check. + * @param pos_y The Y position to check. + * @return The slot at the specified position. + */ + Slot get_slot_at(double pos_x, double pos_y); +} diff --git a/src/main/resources/assets/lambdacontrols/lang/en_us.json b/src/main/resources/assets/lambdacontrols/lang/en_us.json index 4af76b9..a912b80 100644 --- a/src/main/resources/assets/lambdacontrols/lang/en_us.json +++ b/src/main/resources/assets/lambdacontrols/lang/en_us.json @@ -4,6 +4,9 @@ "lambdacontrols.menu.dead_zone": "Dead zone", "lambdacontrols.menu.hud_side": "HUD side", "lambdacontrols.menu.rotation_speed": "Rotation speed", + "lambdacontrols.menu.mouse_speed": "Mouse speed", + "lambdacontrols.controller.connected": "Controller %d connected.", + "lambdacontrols.controller.disconnected": "Controller %d disconnected.", "lambdacontrols.controls_mode.default": "Keyboard/Mouse", "lambdacontrols.controls_mode.controller": "Controller", "lambdacontrols.controls_mode.touchscreen": "Touchscreen", diff --git a/src/main/resources/config.toml b/src/main/resources/config.toml index c4df999..3ab35cf 100644 --- a/src/main/resources/config.toml +++ b/src/main/resources/config.toml @@ -9,13 +9,23 @@ controls = "default" # Controller settings [controller] - dead_zone = 0.25 - rotation_speed = 25.0 + # Controller to use. + id = 0 + # Controller's dead zone. + dead_zone = 0.20 + # Rotation speed for look directions. + rotation_speed = 40.0 + # Mouse speed in GUI. + mouse_speed = 30.0 # Controller controls [controller.controls] attack = "button_7" + b = "button_1" back = "axe_1+" - drop = "button_2" + dpad_up = "button_13" + dpad_right = "button_14" + dpad_down = "button_15" + dpad_left = "button_16" forward = "axe_1-" hotbar_left = "button_4" hotbar_right = "button_5" @@ -31,6 +41,7 @@ controls = "default" view_left = "axe_2-" view_right = "axe_2+" view_up = "axe_3-" + x = "button_2" # Colors [colors] diff --git a/src/main/resources/lambdacontrols.mixins.json b/src/main/resources/lambdacontrols.mixins.json index 76f4f31..65133a4 100644 --- a/src/main/resources/lambdacontrols.mixins.json +++ b/src/main/resources/lambdacontrols.mixins.json @@ -4,7 +4,7 @@ "compatibilityLevel": "JAVA_8", "client": [ "AbstractButtonWidgetAccessor", - "AbstractContainerScreenAccessor", + "AbstractContainerScreenMixin", "CreativeInventoryScreenMixin", "GameRendererMixin", "InGameHudMixin",