diff --git a/src/main/java/me/lambdaurora/lambdacontrols/ControllerInput.java b/src/main/java/me/lambdaurora/lambdacontrols/ControllerInput.java new file mode 100644 index 0000000..b66a246 --- /dev/null +++ b/src/main/java/me/lambdaurora/lambdacontrols/ControllerInput.java @@ -0,0 +1,487 @@ +/* + * 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 me.lambdaurora.lambdacontrols.mixin.AbstractContainerScreenAccessor; +import me.lambdaurora.lambdacontrols.util.CreativeInventoryScreenAccessor; +import me.lambdaurora.lambdacontrols.util.LambdaKeyBinding; +import me.lambdaurora.lambdacontrols.util.MouseAccessor; +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.world.WorldListWidget; +import net.minecraft.client.gui.widget.*; +import net.minecraft.client.options.KeyBinding; +import net.minecraft.container.Slot; +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 java.nio.ByteBuffer; +import java.nio.FloatBuffer; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +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; + private double prev_target_pitch = 0.0; + private double target_yaw = 0.0; + private double target_pitch = 0.0; + 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; + + public ControllerInput(@NotNull LambdaControls mod) + { + this.mod = mod; + this.config = mod.config; + } + + /** + * This method is called every Minecraft tick. + * + * @param client The client instance. + */ + public void on_tick(@NotNull MinecraftClient client) + { + BUTTON_COOLDOWNS.entrySet().stream().filter(entry -> entry.getValue() > 0).forEach(entry -> BUTTON_COOLDOWNS.put(entry.getKey(), entry.getValue() - 1)); + // Decreases the last_sneak counter which allows to double press to sneak continuously. + if (this.last_sneak > 0) + --this.last_sneak; + // Decreases the cooldown for GUI actions. + if (this.action_gui_cooldown > 0) + --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); + } + + 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); + } + } + + public void on_render(@NotNull MinecraftClient client) + { + if (client.currentScreen == null && (this.prev_target_yaw != this.target_yaw || this.prev_target_pitch != this.target_pitch)) { + float rotation_yaw = (float) (client.player.prevYaw + (this.target_yaw - client.player.prevYaw) * client.getTickDelta()); + float rotation_pitch = (float) (client.player.prevPitch + (this.target_pitch - client.player.prevPitch) * client.getTickDelta()); + client.player.yaw = rotation_yaw; + client.player.pitch = MathHelper.clamp(rotation_pitch, -90.F, 90.F); + if (client.player.isRiding()) { + client.player.getVehicle().copyPositionAndRotation(client.player); + } + } + } + + public void on_screen_open(@NotNull MinecraftClient client, int window_width, int window_height) + { + if (client.currentScreen == null) { + 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) + { + 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); + + 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; + } + } + } + + private void fetch_axe_input(@NotNull MinecraftClient client) + { + 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); + + 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); + } + } + } + + private void handle_button(@NotNull MinecraftClient client, int button, int action, boolean state) + { + 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)); + return; + } + + // Handles when the player presses the Start button. + if (this.config.is_start_button(button)) { + // If in game, then pause the game. + if (client.currentScreen == null) + client.openPauseMenu(false); + else // Else just close the current screen. + client.currentScreen.onClose(); + + return; + } + + if (this.config.is_jump_button(button) && client.currentScreen != null) { + if (this.action_gui_cooldown == 0) { + Element focused = client.currentScreen.getFocused(); + if (focused != null) + 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; + } + } + } + + // Handles sneak button and continuous sneak. + if (this.config.is_sneak_button(button) && client.player != null) { + if (action == 0) { + if (this.continuous_sneak) { + this.set_sneaking(client, this.continuous_sneak = false); + } else if (this.last_sneak > 3) { + this.set_sneaking(client, this.continuous_sneak = true); + } else { + this.set_sneaking(client, true); + this.last_sneak = 15; + } + } else if (action == 1) { + if (this.continuous_sneak) + return; + this.set_sneaking(client, false); + } + 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 (client.currentScreen == null && action != 2) { + Optional key_binding = this.config.get_keybind("button_" + button); + key_binding.ifPresent(keyBinding -> ((LambdaKeyBinding) keyBinding).handle_press_state(action != 1)); + } + } + + private void handle_axe(@NotNull MinecraftClient client, int axe, float value, float abs_value, int state) + { + int as_button_state = value > 0.5F ? 1 : (value < -0.5F ? 2 : 0); + double dead_zone = this.config.get_dead_zone(); + if (client.currentScreen == null) { + this.config.get_keybind("axe_" + axe + "+").ifPresent(key_binding -> ((LambdaKeyBinding) key_binding).handle_press_state(as_button_state == 1)); + this.config.get_keybind("axe_" + axe + "-").ifPresent(key_binding -> ((LambdaKeyBinding) key_binding).handle_press_state(as_button_state == 2)); + + // Handles the look direction. + if (this.config.is_look_axis(axe) && client.player != null) { + if (this.config.is_view_down_control(axe, state)) { + if (this.config.get_view_down_control().endsWith("+")) + this.target_pitch = client.player.pitch + (this.config.get_rotation_speed() * (abs_value - dead_zone) / (1.0 - dead_zone)) * 0.33D; + else + this.target_pitch = client.player.pitch - (this.config.get_rotation_speed() * (abs_value + dead_zone) / (1.0 - dead_zone)) * 0.33D; + this.target_pitch = MathHelper.clamp(this.target_pitch, -90.0D, 90.0D); + } else if (this.config.is_view_up_control(axe, state)) { + if (this.config.get_view_up_control().endsWith("+")) + this.target_pitch = client.player.pitch + (this.config.get_rotation_speed() * (abs_value - dead_zone) / (1.0 - dead_zone)) * 0.33D; + else + this.target_pitch = client.player.pitch - (this.config.get_rotation_speed() * (abs_value + dead_zone) / (1.0 - dead_zone)) * 0.33D; + this.target_pitch = MathHelper.clamp(this.target_pitch, -90.0D, 90.0D); + } + if (this.config.is_view_left_control(axe, state)) { + if (this.config.get_view_left_control().endsWith("+")) + this.target_yaw = client.player.yaw + (this.config.get_rotation_speed() * (abs_value - dead_zone) / (1.0 - dead_zone)) * 0.33D; + else + this.target_yaw = client.player.yaw - (this.config.get_rotation_speed() * (abs_value + dead_zone) / (1.0 - dead_zone)) * 0.33D; + } else if (this.config.is_view_right_control(axe, state)) { + if (this.config.get_view_right_control().endsWith("+")) + this.target_yaw = client.player.yaw + (this.config.get_rotation_speed() * (abs_value - dead_zone) / (1.0 - dead_zone)) * 0.33D; + else + this.target_yaw = client.player.yaw - (this.config.get_rotation_speed() * (abs_value + dead_zone) / (1.0 - dead_zone)) * 0.33D; + } + } + } else { + boolean allow_mouse_control = true; + + if (this.action_gui_cooldown == 0 && this.config.is_movement_axis(axe)) { + 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)) { + allow_mouse_control = this.change_focus(client.currentScreen, true); + } else if (this.config.is_left_button(axe, false, as_button_state)) { + allow_mouse_control = this.handle_left_right(client.currentScreen, false); + } else if (this.config.is_right_button(axe, false, as_button_state)) { + allow_mouse_control = this.handle_left_right(client.currentScreen, true); + } + } + + float movement_x = 0.0F; + float movement_y = 0.0F; + + if (this.config.is_back_button(axe, false, (value > 0 ? 1 : 2))) { + movement_y = abs_value; + } else if (this.config.is_forward_button(axe, false, (value > 0 ? 1 : 2))) { + movement_y = -abs_value; + } else if (this.config.is_left_button(axe, false, (value > 0 ? 1 : 2))) { + movement_x = -abs_value; + } else if (this.config.is_right_button(axe, false, (value > 0 ? 1 : 2))) { + movement_x = abs_value; + } + + if (client.currentScreen != null && allow_mouse_control) { + boolean moving = Math.abs(movement_y) >= dead_zone || Math.abs(movement_x) >= dead_zone; + if (moving) { + /* + Updates the target mouse position when the initial movement stick movement is detected. + It prevents the cursor to jump to the old target mouse position if the user moves the cursor with the mouse. + */ + if (Math.abs(prev_x_axis) < dead_zone && Math.abs(prev_y_axis) < dead_zone) { + double mouse_x = client.mouse.getX(); + double mouse_y = client.mouse.getY(); + prev_target_mouse_x = target_mouse_x = (int) mouse_x; + prev_target_mouse_y = target_mouse_y = (int) mouse_y; + } + + if (Math.abs(movement_x) >= dead_zone) + this.mouse_speed_x = movement_x; + else + this.mouse_speed_x = 0.F; + + if (Math.abs(movement_y) >= dead_zone) + this.mouse_speed_y = movement_y; + else + this.mouse_speed_y = 0.F; + } else { + this.mouse_speed_x = 0.F; + this.mouse_speed_y = 0.F; + } + + 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 = 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 = MathHelper.clamp(this.target_mouse_y, 0, client.window.getHeight()); + } + + //this.move_mouse_to_closest_slot(client, client.currentScreen); + } + + this.prev_x_axis = movement_x; + this.prev_y_axis = movement_y; + } + } + + /** + * Handles the press on RB on LB. + * + * @param client The client's instance. + * @param right True if RB is pressed, else false. + */ + private void handle_rb_lb(@NotNull MinecraftClient client, boolean right) + { + // When ingame + if (client.currentScreen == null) { + if (right) + client.player.inventory.selectedSlot = client.player.inventory.selectedSlot == 8 ? 0 : client.player.inventory.selectedSlot + 1; + else + client.player.inventory.selectedSlot = client.player.inventory.selectedSlot == 0 ? 8 : client.player.inventory.selectedSlot - 1; + } else if (client.currentScreen instanceof CreativeInventoryScreen) { + CreativeInventoryScreenAccessor creative_inventory = (CreativeInventoryScreenAccessor) client.currentScreen; + int current_selected_tab = creative_inventory.get_selected_tab(); + int next_tab = current_selected_tab + (right ? 1 : -1); + if (next_tab < 0) + next_tab = ItemGroup.GROUPS.length - 1; + else if (next_tab >= ItemGroup.GROUPS.length) + next_tab = 0; + creative_inventory.set_selected_tab(ItemGroup.GROUPS[next_tab]); + } + } + + private void handle_a_button(@NotNull Element focused) + { + if (focused instanceof AbstractPressableButtonWidget) { + AbstractPressableButtonWidget button_widget = (AbstractPressableButtonWidget) focused; + button_widget.playDownSound(MinecraftClient.getInstance().getSoundManager()); + button_widget.onPress(); + } else if (focused instanceof WorldListWidget) { + WorldListWidget list = (WorldListWidget) focused; + list.method_20159().ifPresent(WorldListWidget.LevelItem::play); + } else if (focused instanceof ParentElement) { + Element child_focused = ((ParentElement) focused).getFocused(); + if (child_focused != null) + this.handle_a_button(child_focused); + } + } + + /** + * Handles the left and right buttons. + * + * @param screen The current screen. + * @param right True if the right button is pressed, else false. + */ + private boolean handle_left_right(@NotNull Screen screen, boolean right) + { + Element focused = screen.getFocused(); + if (focused != null) + return this.handle_right_left_element(focused, right); + return true; + } + + private boolean handle_right_left_element(@NotNull Element element, boolean right) + { + if (element instanceof SliderWidget) { + SliderWidget slider = (SliderWidget) element; + slider.keyPressed(right ? 262 : 263, 0, 0); + this.action_gui_cooldown = 2; // Prevent to press too quickly the focused element, so we have to skip 5 ticks. + return false; + } else if (element instanceof ParentElement) { + ParentElement entry_list = (ParentElement) element; + Element focused = entry_list.getFocused(); + if (focused == null) + return true; + return this.handle_right_left_element(focused, right); + } + return true; + } + + /** + * Sets if the player is sneaking. + * + * @param client The client's instance. + * @param sneaking True if the player is sneaking, else false. + */ + private void set_sneaking(@NotNull MinecraftClient client, boolean sneaking) + { + ((LambdaKeyBinding) client.options.keySneak).handle_press_state(sneaking); + } + + private boolean change_focus(@NotNull Screen screen, boolean down) + { + if (!screen.changeFocus(down)) { + if (screen.changeFocus(down)) { + this.action_gui_cooldown = 5; + return false; + } + return true; + } else { + this.action_gui_cooldown = 5; + return false; + } + } + + // 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) + { + // Makes the mouse attracted to slots. This helps with selecting items when using a controller. + if (screen instanceof AbstractContainerScreen) { + AbstractContainerScreen inventory_screen = (AbstractContainerScreen) screen; + AbstractContainerScreenAccessor accessor = (AbstractContainerScreenAccessor) inventory_screen; + int gui_left = accessor.get_left(); + int gui_top = accessor.get_top(); + int mouse_x = (int) (target_mouse_x * (double) client.window.getScaledWidth() / (double) client.window.getWidth()); + int mouse_y = (int) (target_mouse_y * (double) client.window.getScaledHeight() / (double) client.window.getHeight()); + + // Finds the closest slot in the GUI within 14 pixels. + Optional> closest_slot = inventory_screen.getContainer().slotList.parallelStream() + .map(slot -> { + int pos_x = gui_left + slot.xPosition + 8; + int pos_y = gui_top + slot.yPosition + 8; + + // Distance between the slot and the cursor. + double distance = Math.sqrt(Math.pow(pos_x - mouse_x, 2) + Math.pow(pos_y - mouse_y, 2)); + return Pair.of(slot, distance); + }).filter(entry -> entry.get_value() <= 14.0) + .min(Comparator.comparingDouble(Pair::get_value)); + + if (closest_slot.isPresent()) { + Slot slot = closest_slot.get().get_key(); + if (slot.hasStack() || !client.player.inventory.getMainHandStack().isEmpty()) { + int slot_center_x_scaled = gui_left + slot.xPosition + 8; + int slot_center_y_scaled = gui_top + slot.yPosition + 8; + int slot_center_x = (int) (slot_center_x_scaled / ((double) client.window.getScaledWidth() / (double) client.window.getWidth())); + int slot_center_y = (int) (slot_center_y_scaled / ((double) client.window.getScaledHeight() / (double) client.window.getHeight())); + double delta_x = slot_center_x - target_mouse_x; + double delta_y = slot_center_y - target_mouse_y; + + if (mouse_x != slot_center_x_scaled || mouse_y != slot_center_y_scaled) { + 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 *= .75F; + this.mouse_speed_y *= .75F; + } + } else { + this.mouse_speed_x *= .1F; + this.mouse_speed_y *= .1F; + } + } else { + this.mouse_speed_x = 0.F; + this.mouse_speed_y = 0.F; + } + } +} diff --git a/src/main/java/me/lambdaurora/lambdacontrols/LambdaControls.java b/src/main/java/me/lambdaurora/lambdacontrols/LambdaControls.java index 84b9b1a..71f5c39 100644 --- a/src/main/java/me/lambdaurora/lambdacontrols/LambdaControls.java +++ b/src/main/java/me/lambdaurora/lambdacontrols/LambdaControls.java @@ -9,19 +9,37 @@ 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 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; @@ -32,10 +50,22 @@ import static org.lwjgl.glfw.GLFW.GLFW_JOYSTICK_1; public class LambdaControls implements ClientModInitializer { private static LambdaControls INSTANCE; - public final Logger logger = LogManager.getLogger("LambdaControls"); - public final LambdaControlsConfig config = new LambdaControlsConfig(this); - private final Map BUTTON_STATES = new HashMap<>(); - private int cid = GLFW_JOYSTICK_1; + 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; @Override public void onInitializeClient() @@ -64,82 +94,25 @@ public class LambdaControls implements ClientModInitializer * * @param client The client instance. */ - public void on_tick(MinecraftClient client) + public void on_tick(@NotNull MinecraftClient client) { - GameOptions options = client.options; - ByteBuffer btn_buffer = GLFW.glfwGetJoystickButtons(GLFW.GLFW_JOYSTICK_3); - if (btn_buffer != null) { - for (int i = 0; i < btn_buffer.limit(); i++) { - boolean btn_state = btn_buffer.get() == (byte) 1; + 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; - int current_state = BUTTON_STATES.getOrDefault(i, 0); - if (current_state == 0 && btn_state) { - BUTTON_STATES.put(i, 1); + 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);*/ + } - int f_i = i; - this.config.get_keybind("button_" + i).ifPresent(key_binding -> { - ((LambdaKeyBinding) key_binding).lambdacontrols_press(); - if (key_binding == options.keyInventory && client.player != null && client.player.container != client.player.playerContainer) { - BUTTON_STATES.put(f_i, 2); - } - }); - if (this.config.is_hotbar_left_button(i)) { - client.player.inventory.selectedSlot = client.player.inventory.selectedSlot == 0 ? 8 : client.player.inventory.selectedSlot - 1; - } else if (this.config.is_hotbar_right_button(i)) { - client.player.inventory.selectedSlot = client.player.inventory.selectedSlot == 8 ? 0 : client.player.inventory.selectedSlot + 1; - } - } else if (current_state != 0 && !btn_state) { - this.config.get_keybind("button_" + i).ifPresent(key_binding -> { - if (key_binding == options.keyInventory && current_state == 2 && client.player != null && client.player.container != client.player.playerContainer) { - client.player.closeContainer(); - } - ((LambdaKeyBinding) key_binding).lambdacontrols_unpress(); - }); - - BUTTON_STATES.put(i, 0); - } - } - } - FloatBuffer axes_buffer = GLFW.glfwGetJoystickAxes(GLFW.GLFW_JOYSTICK_3); - if (axes_buffer != null) { - for (int i = 0; i < axes_buffer.limit(); i++) { - float value = axes_buffer.get(); - { - int state = value > 0.5F ? 1 : (value < -0.5F ? 2 : 0); - this.config.get_keybind("axe_" + i + "+").ifPresent(key_binding -> ((LambdaKeyBinding) key_binding).handle_press_state(state == 1)); - this.config.get_keybind("axe_" + i + "-").ifPresent(key_binding -> ((LambdaKeyBinding) key_binding).handle_press_state(state == 2)); - } - if (this.config.is_look_axis(i) && (value > 0.25F || value < -0.25F)) { - int state = value > 0.25F ? 1 : (value < -0.25F ? 2 : 0); - float multiplier = 50.f; - double x = 0.0D; - double y = 0.0D; - if (this.config.is_view_down_control(i, state)) { - if (this.config.get_view_down_control().endsWith("+")) - y = Math.abs(value * multiplier); - else - y = -Math.abs(value * multiplier); - } else if (this.config.is_view_up_control(i, state)) { - if (this.config.get_view_up_control().endsWith("+")) - y = Math.abs(value * multiplier); - else - y = -Math.abs(value * multiplier); - } - if (this.config.is_view_left_control(i, state)) { - if (this.config.get_view_left_control().endsWith("+")) - x = Math.abs(value * multiplier); - else - x = -Math.abs(value * multiplier); - } else if (this.config.is_view_right_control(i, state)) { - if (this.config.get_view_right_control().endsWith("+")) - x = Math.abs(value * multiplier); - else - x = -Math.abs(value * multiplier); - } - client.player.changeLookDirection(x, y); - } - } - } + public void on_render(MinecraftClient client) + { + this.controller_input.on_render(client); } /** diff --git a/src/main/java/me/lambdaurora/lambdacontrols/LambdaControlsConfig.java b/src/main/java/me/lambdaurora/lambdacontrols/LambdaControlsConfig.java index 11020b8..b5d53bd 100644 --- a/src/main/java/me/lambdaurora/lambdacontrols/LambdaControlsConfig.java +++ b/src/main/java/me/lambdaurora/lambdacontrols/LambdaControlsConfig.java @@ -28,6 +28,16 @@ public class LambdaControlsConfig private final LambdaControls mod; private ControlsMode controls_mode; private HudSide hud_side; + // Controller settings + private double dead_zone; + private double rotation_speed; + // Controller controls + private String back_button; + private String forward_button; + private String jump_button; + private String left_button; + private String right_button; + private String sneak_button; public LambdaControlsConfig(@NotNull LambdaControls mod) { @@ -41,53 +51,59 @@ public class LambdaControlsConfig this.mod.log("Configuration loaded."); 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); + // Controller controls + this.back_button = this.config.getOrElse("controller.controls.back", "none").toLowerCase(); + this.forward_button = this.config.getOrElse("controller.controls.forward", "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(); } public void init_keybindings(GameOptions options) { - String str = this.config.getOrElse("controller.attack", "none").toLowerCase(); + String str = this.config.getOrElse("controller.controls.attack", "none").toLowerCase(); if (!str.equals("none")) this.keybinding_mappings.put(str, options.keyAttack); - str = this.config.getOrElse("controller.back", "none").toLowerCase(); - if (!str.equals("none")) - this.keybinding_mappings.put(str, options.keyBack); - str = this.config.getOrElse("controller.drop", "none").toLowerCase(); + 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); - str = this.config.getOrElse("controller.forward", "none").toLowerCase(); - if (!str.equals("none")) - this.keybinding_mappings.put(str, options.keyForward); - str = this.config.getOrElse("controller.inventory", "none").toLowerCase(); + 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); - str = this.config.getOrElse("controller.jump", "none").toLowerCase(); - if (!str.equals("none")) - this.keybinding_mappings.put(str, options.keyJump); - str = this.config.getOrElse("controller.left", "none").toLowerCase(); - if (!str.equals("none")) - this.keybinding_mappings.put(str, options.keyLeft); - str = this.config.getOrElse("controller.right", "none").toLowerCase(); - if (!str.equals("none")) - this.keybinding_mappings.put(str, options.keyRight); - str = this.config.getOrElse("controller.sneak", "none").toLowerCase(); - if (!str.equals("none")) - this.keybinding_mappings.put(str, options.keySneak); - str = this.config.getOrElse("controller.sprint", "none").toLowerCase(); + if (!this.jump_button.equals("none")) + this.keybinding_mappings.put(this.jump_button, options.keyJump); + if (!this.left_button.equals("none")) + this.keybinding_mappings.put(this.left_button, options.keyLeft); + if (!this.right_button.equals("none")) + this.keybinding_mappings.put(this.right_button, options.keyRight); + if (!this.sneak_button.equals("none")) + this.keybinding_mappings.put(this.sneak_button, options.keySneak); + str = this.config.getOrElse("controller.controls.sprint", "none").toLowerCase(); if (!str.equals("none")) this.keybinding_mappings.put(str, options.keySprint); - str = this.config.getOrElse("controller.use", "none").toLowerCase(); + str = this.config.getOrElse("controller.controls.use", "none").toLowerCase(); if (!str.equals("none")) this.keybinding_mappings.put(str, options.keyUse); } public void save() { + this.config.set("controller.dead_zone", this.dead_zone); + this.config.set("controller.rotation_speed", this.rotation_speed); this.config.save(); this.mod.log("Configuration saved."); } /** - * Returns the controls mode from the configuration. + * Gets the controls mode from the configuration. * * @return The controls mode. */ @@ -108,7 +124,7 @@ public class LambdaControlsConfig } /** - * Returns the HUD side from the configuration. + * Gets the HUD side from the configuration. * * @return The HUD side. */ @@ -128,6 +144,46 @@ public class LambdaControlsConfig this.config.set("hud.side", hud_side.get_name()); } + /** + * Gets the controller's dead zone from the configuration. + * + * @return The controller's dead zone value. + */ + public double get_dead_zone() + { + return this.dead_zone; + } + + /** + * Sets the controller's dead zone in the configuration. + * + * @param dead_zone The new controller's dead zone value. + */ + public void set_dead_zone(double dead_zone) + { + this.dead_zone = dead_zone; + } + + /** + * Gets the controller's rotation speed. + * + * @return The rotation speed. + */ + public double get_rotation_speed() + { + return this.rotation_speed; + } + + /** + * Sets the controller's rotation speed. + * + * @param rotation_speed The rotation speed. + */ + public void set_rotation_speed(double rotation_speed) + { + this.rotation_speed = rotation_speed; + } + /** * Returns the keybindings. * @@ -143,14 +199,49 @@ public class LambdaControlsConfig return Optional.ofNullable(this.keybinding_mappings.get(id)); } + public String get_back_button() + { + return this.back_button; + } + + public String get_forward_button() + { + return this.forward_button; + } + public String get_hotbar_left_button() { - return this.config.getOrElse("controller.hotbar_left", "none").toLowerCase(); + return this.config.getOrElse("controller.controls.hotbar_left", "none").toLowerCase(); } public String get_hotbar_right_button() { - return this.config.getOrElse("controller.hotbar_right", "none").toLowerCase(); + return this.config.getOrElse("controller.controls.hotbar_right", "none").toLowerCase(); + } + + public String get_jump_button() + { + return this.jump_button; + } + + public String get_left_button() + { + return this.left_button; + } + + public String get_right_button() + { + return this.right_button; + } + + public String get_sneak_button() + { + return this.sneak_button; + } + + public String get_start_button() + { + return this.config.getOrElse("controller.controls.start", "none").toLowerCase(); } public boolean is_hotbar_left_button(int button) @@ -163,24 +254,67 @@ public class LambdaControlsConfig return this.get_hotbar_right_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_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_jump_button(int btn) + { + return this.get_jump_button().equals("button_" + btn); + } + + public boolean is_left_button(int btn, boolean is_btn, int state) + { + if (!is_btn && state == 0) + return false; + return this.get_left_button().equals((is_btn ? "button_" : "axe_") + btn + (is_btn ? "" : (state == 1 ? "+" : "-"))); + } + + public boolean is_right_button(int btn, boolean is_btn, int state) + { + if (!is_btn && state == 0) + return false; + return this.get_right_button().equals((is_btn ? "button_" : "axe_") + btn + (is_btn ? "" : (state == 1 ? "+" : "-"))); + } + + public boolean is_sneak_button(int btn) + { + return this.get_sneak_button().equals("button_" + btn); + } + + public boolean is_start_button(int btn) + { + return this.get_start_button().equals("button_" + btn); + } + public String get_view_down_control() { - return this.config.getOrElse("controller.view_down", "none").toLowerCase(); + return this.config.getOrElse("controller.controls.view_down", "none").toLowerCase(); } public String get_view_left_control() { - return this.config.getOrElse("controller.view_left", "none").toLowerCase(); + return this.config.getOrElse("controller.controls.view_left", "none").toLowerCase(); } public String get_view_right_control() { - return this.config.getOrElse("controller.view_right", "none").toLowerCase(); + return this.config.getOrElse("controller.controls.view_right", "none").toLowerCase(); } public String get_view_up_control() { - return this.config.getOrElse("controller.view_up", "none").toLowerCase(); + return this.config.getOrElse("controller.controls.view_up", "none").toLowerCase(); } public boolean is_view_down_control(int axe, int state) @@ -211,9 +345,27 @@ public class LambdaControlsConfig return this.get_view_up_control().contains(axe + (state == 1 ? "+" : "-")); } + /** + * Returns whether the specified axis is an axis used for look direction. + * + * @param i The axis index. + * @return True if the axis is used for look direction, else false. + */ public boolean is_look_axis(int i) { return this.get_view_down_control().startsWith("axe_" + i) || this.get_view_left_control().startsWith("axe_" + i) || this.get_view_right_control().startsWith("axe_" + i) || this.get_view_up_control().startsWith("axe_" + i); } + + /** + * Returns whether the specified axis is an axis used for movements. + * + * @param i The axis index. + * @return True if the axis is used for movements, else false. + */ + public boolean is_movement_axis(int i) + { + return this.get_forward_button().startsWith("axe_" + i) || this.get_back_button().startsWith("axe_" + i) || this.get_left_button().startsWith("axe_" + i) + || this.get_right_button().startsWith("axe_" + i); + } } diff --git a/src/main/java/me/lambdaurora/lambdacontrols/gui/LambdaControlsSettingsScreen.java b/src/main/java/me/lambdaurora/lambdacontrols/gui/LambdaControlsSettingsScreen.java new file mode 100644 index 0000000..fde4a62 --- /dev/null +++ b/src/main/java/me/lambdaurora/lambdacontrols/gui/LambdaControlsSettingsScreen.java @@ -0,0 +1,100 @@ +/* + * 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.gui; + +import me.lambdaurora.lambdacontrols.ControlsMode; +import me.lambdaurora.lambdacontrols.HudSide; +import me.lambdaurora.lambdacontrols.LambdaControls; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.gui.screen.controls.ControlsOptionsScreen; +import net.minecraft.client.gui.widget.ButtonWidget; +import net.minecraft.client.options.DoubleOption; +import net.minecraft.client.options.GameOptions; +import net.minecraft.client.options.Option; +import net.minecraft.client.resource.language.I18n; +import net.minecraft.text.TranslatableText; +import org.jetbrains.annotations.NotNull; + +/** + * Represents the LambdaControls settings screen. + */ +public class LambdaControlsSettingsScreen extends Screen +{ + private final LambdaControls mod; + private final Screen parent; + private final GameOptions options; + private final Option dead_zone_option; + private final Option rotation_speed_option; + + public LambdaControlsSettingsScreen(Screen parent, @NotNull GameOptions options) + { + super(new TranslatableText("lambdacontrols.title.settings")); + 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(), + (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, new_value) -> { + synchronized (this.mod.config) { + this.mod.config.set_rotation_speed(new_value); + } + }, (game_options, option) -> option.getDisplayPrefix() + option.get(options)); + } + + @Override + public void onClose() + { + this.mod.config.save(); + super.onClose(); + } + + @Override + protected void init() + { + super.init(); + int y = 18; + int button_height = 20, spacing = 5; + this.addButton(new ButtonWidget(this.width / 2 - 155, y, 150, button_height, I18n.translate("lambdacontrols.menu.controls_mode") + ": " + this.mod.config.get_controls_mode().get_translated_name(), + btn -> { + ControlsMode next = this.mod.config.get_controls_mode().next(); + btn.setMessage(I18n.translate("lambdacontrols.menu.controls_mode") + ": " + next.get_translated_name()); + this.mod.config.set_controls_mode(next); + this.mod.config.save(); + })); + this.addButton(new ButtonWidget(this.width / 2 - 155 + 160, y, 150, button_height, I18n.translate("options.controls"), + btn -> this.minecraft.openScreen(new ControlsOptionsScreen(this, this.options)))); + this.addButton(new ButtonWidget(this.width / 2 - 155, (y += spacing + button_height), 150, button_height, I18n.translate("lambdacontrols.menu.hud_side") + ": " + this.mod.config.get_hud_side().get_translated_name(), + btn -> { + HudSide next = this.mod.config.get_hud_side().next(); + btn.setMessage(I18n.translate("lambdacontrols.menu.hud_side") + ": " + next.get_translated_name()); + this.mod.config.set_hud_side(next); + this.mod.config.save(); + })); + this.addButton(new ButtonWidget(this.width / 2 - 155 + 160, y, 150, button_height, I18n.translate("lambdacontrols.menu.controller_controls"), + 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(new ButtonWidget(this.width / 2 - 155 + 160, this.height - 29, 150, button_height, I18n.translate("gui.done"), (buttonWidget) -> { + this.minecraft.openScreen(this.parent); + })); + } + + @Override + public void render(int mouseX, int mouseY, float delta) + { + this.renderBackground(); + super.render(mouseX, mouseY, delta); + } +} diff --git a/src/main/java/me/lambdaurora/lambdacontrols/gui/TouchscreenOverlay.java b/src/main/java/me/lambdaurora/lambdacontrols/gui/TouchscreenOverlay.java index 9553fef..0a33e63 100644 --- a/src/main/java/me/lambdaurora/lambdacontrols/gui/TouchscreenOverlay.java +++ b/src/main/java/me/lambdaurora/lambdacontrols/gui/TouchscreenOverlay.java @@ -102,9 +102,10 @@ public class TouchscreenOverlay extends Screen this.fly_button.visible = true; this.fly_up_button.visible = true; this.fly_down_button.visible = true; - if (old_state_fly != this.fly_button.visible) + if (old_state_fly != this.fly_button.visible) { this.fly_button_enable_ticks = 5; - else if (this.fly_button_enable_ticks > 0) + this.handle_jump(false); + } else if (this.fly_button_enable_ticks > 0) this.fly_button_enable_ticks--; } else { this.jump_button.visible = true; diff --git a/src/main/java/me/lambdaurora/lambdacontrols/mixin/AbstractButtonWidgetAccessor.java b/src/main/java/me/lambdaurora/lambdacontrols/mixin/AbstractButtonWidgetAccessor.java new file mode 100644 index 0000000..2a7f182 --- /dev/null +++ b/src/main/java/me/lambdaurora/lambdacontrols/mixin/AbstractButtonWidgetAccessor.java @@ -0,0 +1,21 @@ +/* + * 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.widget.AbstractButtonWidget; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(AbstractButtonWidget.class) +public interface AbstractButtonWidgetAccessor +{ + @Accessor("height") + int get_height(); +} diff --git a/src/main/java/me/lambdaurora/lambdacontrols/mixin/AbstractContainerScreenAccessor.java b/src/main/java/me/lambdaurora/lambdacontrols/mixin/AbstractContainerScreenAccessor.java new file mode 100644 index 0000000..85c6865 --- /dev/null +++ b/src/main/java/me/lambdaurora/lambdacontrols/mixin/AbstractContainerScreenAccessor.java @@ -0,0 +1,24 @@ +/* + * 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/CreativeInventoryScreenMixin.java b/src/main/java/me/lambdaurora/lambdacontrols/mixin/CreativeInventoryScreenMixin.java new file mode 100644 index 0000000..fbf10f5 --- /dev/null +++ b/src/main/java/me/lambdaurora/lambdacontrols/mixin/CreativeInventoryScreenMixin.java @@ -0,0 +1,34 @@ +/* + * 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.CreativeInventoryScreenAccessor; +import net.minecraft.client.gui.screen.ingame.CreativeInventoryScreen; +import net.minecraft.item.ItemGroup; +import org.jetbrains.annotations.NotNull; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(CreativeInventoryScreen.class) +public abstract class CreativeInventoryScreenMixin implements CreativeInventoryScreenAccessor +{ + @Shadow + protected abstract void setSelectedTab(ItemGroup itemGroup); + + @Accessor("selectedTab") + public abstract int get_selected_tab(); + + @Override + public void set_selected_tab(@NotNull ItemGroup group) + { + this.setSelectedTab(group); + } +} diff --git a/src/main/java/me/lambdaurora/lambdacontrols/mixin/GameRendererMixin.java b/src/main/java/me/lambdaurora/lambdacontrols/mixin/GameRendererMixin.java new file mode 100644 index 0000000..a7a9bd5 --- /dev/null +++ b/src/main/java/me/lambdaurora/lambdacontrols/mixin/GameRendererMixin.java @@ -0,0 +1,36 @@ +/* + * 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.ControlsMode; +import me.lambdaurora.lambdacontrols.LambdaControls; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.render.GameRenderer; +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(GameRenderer.class) +public class GameRendererMixin +{ + @Shadow + @Final + private MinecraftClient client; + + @Inject(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/Screen;render(IIF)V")) + 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) + LambdaControls.get().controller_input.on_pre_render_screen(this.client, this.client.currentScreen); + } +} diff --git a/src/main/java/me/lambdaurora/lambdacontrols/mixin/MinecraftClientMixin.java b/src/main/java/me/lambdaurora/lambdacontrols/mixin/MinecraftClientMixin.java index fbec21f..27f8998 100644 --- a/src/main/java/me/lambdaurora/lambdacontrols/mixin/MinecraftClientMixin.java +++ b/src/main/java/me/lambdaurora/lambdacontrols/mixin/MinecraftClientMixin.java @@ -31,7 +31,8 @@ public abstract class MinecraftClientMixin @Shadow public boolean skipGameRender; - @Shadow public Screen currentScreen; + @Shadow + public Screen currentScreen; @Inject(method = "init", at = @At("RETURN")) private void on_init(CallbackInfo ci) @@ -39,11 +40,16 @@ public abstract class MinecraftClientMixin LambdaControls.get().on_mc_init((MinecraftClient) (Object) this); } - @Inject(method = "handleInputEvents", at = @At("HEAD")) + @Inject(method = "render", at = @At("HEAD")) + private void on_render(boolean full_render, CallbackInfo ci) + { + LambdaControls.get().on_render((MinecraftClient) (Object) (this)); + } + + @Inject(method = "tick", at = @At("HEAD")) private void on_handle_input_events(CallbackInfo ci) { - if (LambdaControls.get().config.get_controls_mode() == ControlsMode.CONTROLLER) - LambdaControls.get().on_tick((MinecraftClient) (Object) this); + LambdaControls.get().on_tick((MinecraftClient) (Object) this); } @Inject(method = "openScreen", at = @At("RETURN")) @@ -55,6 +61,8 @@ public abstract class MinecraftClientMixin screen.init(((MinecraftClient) (Object) this), this.window.getScaledWidth(), this.window.getScaledHeight()); this.skipGameRender = false; this.currentScreen = screen; + } else if (screen != null) { + mod.controller_input.on_screen_open(((MinecraftClient) (Object) this), this.window.getWidth(), this.window.getHeight()); } } } diff --git a/src/main/java/me/lambdaurora/lambdacontrols/mixin/MouseMixin.java b/src/main/java/me/lambdaurora/lambdacontrols/mixin/MouseMixin.java index 7015368..9ee1229 100644 --- a/src/main/java/me/lambdaurora/lambdacontrols/mixin/MouseMixin.java +++ b/src/main/java/me/lambdaurora/lambdacontrols/mixin/MouseMixin.java @@ -11,6 +11,7 @@ package me.lambdaurora.lambdacontrols.mixin; import me.lambdaurora.lambdacontrols.ControlsMode; import me.lambdaurora.lambdacontrols.LambdaControls; +import me.lambdaurora.lambdacontrols.util.MouseAccessor; import net.minecraft.client.MinecraftClient; import net.minecraft.client.Mouse; import org.spongepowered.asm.mixin.Final; @@ -21,17 +22,13 @@ import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @Mixin(Mouse.class) -public class MouseMixin +public abstract class MouseMixin implements MouseAccessor { @Shadow - @Final - private MinecraftClient client; + protected abstract void onCursorPos(long window, double x, double y); @Shadow - private double x; - - @Shadow - private double y; + protected abstract void onMouseButton(long window, int button, int action, int mods); @Inject(method = "lockCursor", at = @At("HEAD"), cancellable = true) private void on_mouse_locked(CallbackInfo ci) @@ -39,4 +36,16 @@ public class MouseMixin if (LambdaControls.get().config.get_controls_mode() == ControlsMode.TOUCHSCREEN) ci.cancel(); } + + @Override + public void on_mouse_button(long window, int button, int action, int mods) + { + this.onMouseButton(window, button, action, mods); + } + + @Override + public void on_cursor_pos(long window, double x, double y) + { + this.onCursorPos(window, x, y); + } } diff --git a/src/main/java/me/lambdaurora/lambdacontrols/mixin/SettingsScreenMixin.java b/src/main/java/me/lambdaurora/lambdacontrols/mixin/SettingsScreenMixin.java new file mode 100644 index 0000000..8cbf84f --- /dev/null +++ b/src/main/java/me/lambdaurora/lambdacontrols/mixin/SettingsScreenMixin.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.gui.LambdaControlsSettingsScreen; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.gui.screen.SettingsScreen; +import net.minecraft.client.gui.widget.ButtonWidget; +import net.minecraft.client.options.GameOptions; +import net.minecraft.client.resource.language.I18n; +import net.minecraft.text.Text; +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(SettingsScreen.class) +public class SettingsScreenMixin extends Screen +{ + @Final + @Shadow + private GameOptions settings; + + protected SettingsScreenMixin(Text title) + { + super(title); + } + + @Inject(method = "init", at = @At("RETURN")) + private void on_init(CallbackInfo ci) + { + this.buttons.stream().filter(button -> button.getMessage().equals(I18n.translate("options.controls"))) + .findFirst() + .ifPresent(btn -> { + this.buttons.remove(btn); + this.children.remove(btn); + this.addButton(new ButtonWidget(btn.x, btn.y, btn.getWidth(), ((AbstractButtonWidgetAccessor) btn).get_height(), btn.getMessage(), + b -> this.minecraft.openScreen(new LambdaControlsSettingsScreen(this, this.settings)))); + }); + } +} diff --git a/src/main/java/me/lambdaurora/lambdacontrols/util/CreativeInventoryScreenAccessor.java b/src/main/java/me/lambdaurora/lambdacontrols/util/CreativeInventoryScreenAccessor.java new file mode 100644 index 0000000..cd222a3 --- /dev/null +++ b/src/main/java/me/lambdaurora/lambdacontrols/util/CreativeInventoryScreenAccessor.java @@ -0,0 +1,33 @@ +/* + * 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.item.ItemGroup; +import org.jetbrains.annotations.NotNull; + +/** + * Represents an accessor to CreativeInventoryScreen. + */ +public interface CreativeInventoryScreenAccessor +{ + /** + * Gets the selected tab. + * + * @return The selected tab index. + */ + int get_selected_tab(); + + /** + * Sets the selected tab. + * + * @param group The tab's item group. + */ + void set_selected_tab(@NotNull ItemGroup group); +} diff --git a/src/main/java/me/lambdaurora/lambdacontrols/util/MouseAccessor.java b/src/main/java/me/lambdaurora/lambdacontrols/util/MouseAccessor.java new file mode 100644 index 0000000..676105a --- /dev/null +++ b/src/main/java/me/lambdaurora/lambdacontrols/util/MouseAccessor.java @@ -0,0 +1,20 @@ +/* + * 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; + +/** + * Represents mouse's extra access. + */ +public interface MouseAccessor +{ + void on_mouse_button(long window, int button, int action, int mods); + + void on_cursor_pos(long window, double x, double y); +} diff --git a/src/main/resources/assets/lambdacontrols/lang/en_us.json b/src/main/resources/assets/lambdacontrols/lang/en_us.json new file mode 100644 index 0000000..4af76b9 --- /dev/null +++ b/src/main/resources/assets/lambdacontrols/lang/en_us.json @@ -0,0 +1,12 @@ +{ + "lambdacontrols.menu.controller_controls": "Controller controls...", + "lambdacontrols.menu.controls_mode": "Controls mode", + "lambdacontrols.menu.dead_zone": "Dead zone", + "lambdacontrols.menu.hud_side": "HUD side", + "lambdacontrols.menu.rotation_speed": "Rotation speed", + "lambdacontrols.controls_mode.default": "Keyboard/Mouse", + "lambdacontrols.controls_mode.controller": "Controller", + "lambdacontrols.controls_mode.touchscreen": "Touchscreen", + "lambdacontrols.hud_side.left": "left", + "lambdacontrols.hud_side.right": "right" +} \ No newline at end of file diff --git a/src/main/resources/config.toml b/src/main/resources/config.toml index ea7d3a4..c4df999 100644 --- a/src/main/resources/config.toml +++ b/src/main/resources/config.toml @@ -4,29 +4,33 @@ controls = "default" [hud] -# Dertermines where the movements buttons are. -side = "left" + # Dertermines where the movements buttons are. + side = "left" # Controller settings [controller] -attack = "button_7" -back = "axe_1+" -drop = "button_2" -forward = "axe_1-" -hotbar_left = "button_4" -hotbar_right = "button_5" -inventory = "button_3" -jump = "button_0" -left = "axe_0-" -right = "axe_0+" -sneak = "button_12" -sprint = "button_11" -start = "button_4" -use = "button_6" -view_down = "axe_3+" -view_left = "axe_2-" -view_right = "axe_2+" -view_up = "axe_3-" + dead_zone = 0.25 + rotation_speed = 25.0 + # Controller controls + [controller.controls] + attack = "button_7" + back = "axe_1+" + drop = "button_2" + forward = "axe_1-" + hotbar_left = "button_4" + hotbar_right = "button_5" + inventory = "button_3" + jump = "button_0" + left = "axe_0-" + right = "axe_0+" + sneak = "button_12" + sprint = "button_11" + start = "button_9" + use = "button_6" + view_down = "axe_3+" + view_left = "axe_2-" + view_right = "axe_2+" + view_up = "axe_3-" # Colors [colors] diff --git a/src/main/resources/lambdacontrols.mixins.json b/src/main/resources/lambdacontrols.mixins.json index 1b94c2c..76f4f31 100644 --- a/src/main/resources/lambdacontrols.mixins.json +++ b/src/main/resources/lambdacontrols.mixins.json @@ -3,10 +3,15 @@ "package": "me.lambdaurora.lambdacontrols.mixin", "compatibilityLevel": "JAVA_8", "client": [ + "AbstractButtonWidgetAccessor", + "AbstractContainerScreenAccessor", + "CreativeInventoryScreenMixin", + "GameRendererMixin", "InGameHudMixin", "KeyBindingMixin", "MinecraftClientMixin", - "MouseMixin" + "MouseMixin", + "SettingsScreenMixin" ], "injectors": { "defaultRequire": 1