diff --git a/src/main/java/me/lambdaurora/lambdacontrols/HudSide.java b/src/main/java/me/lambdaurora/lambdacontrols/HudSide.java new file mode 100644 index 0000000..19d1c3f --- /dev/null +++ b/src/main/java/me/lambdaurora/lambdacontrols/HudSide.java @@ -0,0 +1,66 @@ +/* + * 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 net.minecraft.client.resource.language.I18n; +import org.aperlambda.lambdacommon.utils.Nameable; +import org.jetbrains.annotations.NotNull; + +import java.util.Arrays; +import java.util.Optional; + +/** + * Represents the hud side which is the side where the movements buttons are. + */ +public enum HudSide implements Nameable +{ + LEFT, + RIGHT; + + /** + * Returns the next side available. + * + * @return The next available side. + */ + public HudSide next() + { + HudSide[] v = values(); + if (v.length == this.ordinal() + 1) + return v[0]; + return v[this.ordinal() + 1]; + } + + /** + * Gets the translated name of this hud side. + * + * @return The translated name of this hud side. + */ + public String get_translated_name() + { + return I18n.translate("lambdacontrols.hud_side." + this.get_name()); + } + + @Override + public @NotNull String get_name() + { + return this.name().toLowerCase(); + } + + /** + * Gets the hud side from its identifier. + * + * @param id The identifier of the hud side. + * @return The hud side if found, else empty. + */ + public static Optional by_id(@NotNull String id) + { + return Arrays.stream(values()).filter(mode -> mode.get_name().equalsIgnoreCase(id)).findFirst(); + } +} diff --git a/src/main/java/me/lambdaurora/lambdacontrols/LambdaControlsConfig.java b/src/main/java/me/lambdaurora/lambdacontrols/LambdaControlsConfig.java index 8941579..11020b8 100644 --- a/src/main/java/me/lambdaurora/lambdacontrols/LambdaControlsConfig.java +++ b/src/main/java/me/lambdaurora/lambdacontrols/LambdaControlsConfig.java @@ -10,7 +10,6 @@ package me.lambdaurora.lambdacontrols; import com.electronwill.nightconfig.core.file.FileConfig; -import net.minecraft.client.MinecraftClient; import net.minecraft.client.options.GameOptions; import net.minecraft.client.options.KeyBinding; import org.jetbrains.annotations.NotNull; @@ -28,6 +27,7 @@ public class LambdaControlsConfig private final Map keybinding_mappings = new HashMap<>(); private final LambdaControls mod; private ControlsMode controls_mode; + private HudSide hud_side; public LambdaControlsConfig(@NotNull LambdaControls mod) { @@ -40,6 +40,7 @@ public class LambdaControlsConfig this.config.load(); 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); } public void init_keybindings(GameOptions options) @@ -106,6 +107,27 @@ public class LambdaControlsConfig this.config.set("controls", controls_mode.get_name()); } + /** + * Returns the HUD side from the configuration. + * + * @return The HUD side. + */ + public @NotNull HudSide get_hud_side() + { + return this.hud_side; + } + + /** + * Sets the HUD side in the configuration. + * + * @param hud_side The HUD side. + */ + public void set_hud_side(@NotNull HudSide hud_side) + { + this.hud_side = hud_side; + this.config.set("hud.side", hud_side.get_name()); + } + /** * Returns the keybindings. * diff --git a/src/main/java/me/lambdaurora/lambdacontrols/gui/LambdaControlsHud.java b/src/main/java/me/lambdaurora/lambdacontrols/gui/LambdaControlsHud.java index 79947e9..3aabfa5 100644 --- a/src/main/java/me/lambdaurora/lambdacontrols/gui/LambdaControlsHud.java +++ b/src/main/java/me/lambdaurora/lambdacontrols/gui/LambdaControlsHud.java @@ -9,9 +9,13 @@ package me.lambdaurora.lambdacontrols.gui; +import me.lambdaurora.lambdacontrols.ControlsMode; import me.lambdaurora.lambdacontrols.LambdaControls; +import me.lambdaurora.lambdacontrols.util.LambdaKeyBinding; import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.widget.ButtonWidget; import org.jetbrains.annotations.NotNull; +import org.lwjgl.glfw.GLFW; /** * Represents the LambdaControls HUD. @@ -19,15 +23,31 @@ import org.jetbrains.annotations.NotNull; public class LambdaControlsHud { private final MinecraftClient client; - private final LambdaControls mod; + private final LambdaControls mod; + private ButtonWidget jump_button; public LambdaControlsHud(@NotNull MinecraftClient client, @NotNull LambdaControls mod) { this.client = client; this.mod = mod; + this.jump_button = new ButtonWidget(50, 50, 20, 20, "J", button-> {}); } - public void render() { + public void render(float delta) + { + if (this.mod.config.get_controls_mode() == ControlsMode.TOUCHSCREEN) + this.render_touchscreen(delta); + } + public void render_touchscreen(float delta) + { + //this.jump_button.render((int) this.client.mouse.getX(), (int) this.client.mouse.getY(), delta); + } + + public void on_input(double x, double y, int button, int action) + { + if (this.jump_button.mouseClicked(x, y, button)) { + ((LambdaKeyBinding) this.client.options.keyJump).handle_press_state(action != GLFW.GLFW_RELEASE); + } } } diff --git a/src/main/java/me/lambdaurora/lambdacontrols/gui/TouchscreenButtonWidget.java b/src/main/java/me/lambdaurora/lambdacontrols/gui/TouchscreenButtonWidget.java new file mode 100644 index 0000000..b8352f9 --- /dev/null +++ b/src/main/java/me/lambdaurora/lambdacontrols/gui/TouchscreenButtonWidget.java @@ -0,0 +1,54 @@ +/* + * 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 net.minecraft.client.gui.widget.TexturedButtonWidget; +import net.minecraft.util.Identifier; +import org.jetbrains.annotations.NotNull; + +import java.util.function.Consumer; + +/** + * Represents a touchscreen button widget. + */ +public class TouchscreenButtonWidget extends TexturedButtonWidget +{ + private final Consumer on_change_state; + + public TouchscreenButtonWidget(int x, int y, int width, int height, int u, int v, int hovered_v_offset, Identifier texture, @NotNull Consumer on_changed_state) + { + super(x, y, width, height, u, v, hovered_v_offset, texture, 256, 256, btn -> on_changed_state.accept(true)); + this.on_change_state = on_changed_state; + } + + @Override + public void onRelease(double mouseX, double mouseY) + { + super.onRelease(mouseX, mouseY); + this.on_change_state.accept(false); + } + + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) + { + if (this.active && this.visible) { + if (this.isValidClickButton(button)) { + boolean clicked = this.clicked(mouseX, mouseY); + if (clicked) { + this.onClick(mouseX, mouseY); + return true; + } + } + + return false; + } else + return false; + } +} diff --git a/src/main/java/me/lambdaurora/lambdacontrols/gui/TouchscreenOverlay.java b/src/main/java/me/lambdaurora/lambdacontrols/gui/TouchscreenOverlay.java new file mode 100644 index 0000000..8472456 --- /dev/null +++ b/src/main/java/me/lambdaurora/lambdacontrols/gui/TouchscreenOverlay.java @@ -0,0 +1,163 @@ +/* + * 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.HudSide; +import me.lambdaurora.lambdacontrols.LambdaControls; +import me.lambdaurora.lambdacontrols.util.LambdaKeyBinding; +import net.minecraft.client.gui.screen.ChatScreen; +import net.minecraft.client.gui.screen.GameMenuScreen; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.gui.screen.ingame.InventoryScreen; +import net.minecraft.client.gui.widget.ButtonWidget; +import net.minecraft.client.gui.widget.TexturedButtonWidget; +import net.minecraft.server.network.packet.PlayerActionC2SPacket; +import net.minecraft.text.LiteralText; +import net.minecraft.util.Arm; +import net.minecraft.util.Identifier; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; +import org.jetbrains.annotations.NotNull; + +/** + * Represents the touchscreen overlay + */ +public class TouchscreenOverlay extends Screen +{ + public static final Identifier WIDGETS_LOCATION = new Identifier("lambdacontrols", "textures/gui/widgets.png"); + private LambdaControls mod; + private TouchscreenButtonWidget start_sneak_button; + private TouchscreenButtonWidget end_sneak_button; + + public TouchscreenOverlay(@NotNull LambdaControls mod) + { + super(new LiteralText("Touchscreen overlay")); + this.mod = mod; + this.passEvents = true; + } + + @Override + public boolean shouldCloseOnEsc() + { + return false; + } + + @Override + public boolean isPauseScreen() + { + return false; + } + + @Override + public void onClose() + { + super.onClose(); + } + + private void pause_game(boolean bl) + { + if (this.minecraft == null) + return; + boolean bl2 = this.minecraft.isIntegratedServerRunning() && !this.minecraft.getServer().isRemote(); + if (bl2) { + this.minecraft.openScreen(new GameMenuScreen(!bl)); + this.minecraft.getSoundManager().pauseAll(); + } else { + this.minecraft.openScreen(new GameMenuScreen(true)); + } + } + + @Override + protected void init() + { + super.init(); + int scaled_width = this.minecraft.window.getScaledWidth(); + int scaled_height = this.minecraft.window.getScaledHeight(); + this.addButton(new TexturedButtonWidget(scaled_width / 2 - 20, 0, 20, 20, 0, 106, 20, ButtonWidget.WIDGETS_LOCATION, 256, 256, + btn -> this.minecraft.openScreen(new ChatScreen("")), "")); + this.addButton(new TexturedButtonWidget(scaled_width / 2, 0, 20, 20, 0, 0, 20, WIDGETS_LOCATION, 256, 256, + btn -> this.pause_game(false))); + // Inventory buttons. + int inventory_button_x = scaled_width / 2; + int inventory_button_y = scaled_height - 16 - 5; + if (this.minecraft.options.mainArm == Arm.LEFT) { + inventory_button_x = inventory_button_x - 91 - 24; + } else { + inventory_button_x = inventory_button_x + 91 + 4; + } + this.addButton(new TexturedButtonWidget(inventory_button_x, inventory_button_y, 20, 20, 20, 0, 20, WIDGETS_LOCATION, 256, 256, + btn -> { + if (this.minecraft.interactionManager.hasRidingInventory()) { + this.minecraft.player.openRidingInventory(); + } else { + this.minecraft.getTutorialManager().onInventoryOpened(); + this.minecraft.openScreen(new InventoryScreen(this.minecraft.player)); + } + })); + int jump_button_x, swap_hands_x, sneak_button_x; + int sneak_button_y = scaled_height - 10 - 40 - 5; + if (this.mod.config.get_hud_side() == HudSide.LEFT) { + jump_button_x = scaled_width - 20 - 20; + swap_hands_x = jump_button_x - 5 - 20; + sneak_button_x = 10 + 20 + 5; + } else { + jump_button_x = 20; + swap_hands_x = jump_button_x + 5 + 20; + sneak_button_x = scaled_width - 10 - 40 - 5; + } + // Swap items hand. + this.addButton(new TouchscreenButtonWidget(swap_hands_x, scaled_height - 5 - 20 - 40, 20, 20, 0, 0, 20, WIDGETS_LOCATION, + state -> { + if (state) { + if (!this.minecraft.player.isSpectator()) { + this.minecraft.getNetworkHandler().sendPacket(new PlayerActionC2SPacket(PlayerActionC2SPacket.Action.SWAP_HELD_ITEMS, BlockPos.ORIGIN, Direction.DOWN)); + } + } + })); + // Drop + this.addButton(new TouchscreenButtonWidget(swap_hands_x, scaled_height - 40, 20, 20, 0, 0, 20, WIDGETS_LOCATION, + state -> ((LambdaKeyBinding) this.minecraft.options.keyDrop).handle_press_state(state))); + // Jump keys + this.addButton(new TouchscreenButtonWidget(jump_button_x, scaled_height - 40, 20, 20, 0, 40, 20, WIDGETS_LOCATION, + state -> ((LambdaKeyBinding) this.minecraft.options.keyJump).handle_press_state(state))); + // Movements keys + this.addButton((this.start_sneak_button = new TouchscreenButtonWidget(sneak_button_x, sneak_button_y, 20, 20, 0, 120, 20, WIDGETS_LOCATION, + state -> { + if (state) { + ((LambdaKeyBinding) this.minecraft.options.keySneak).handle_press_state(true); + this.start_sneak_button.visible = false; + this.end_sneak_button.visible = true; + } + }))); + this.addButton((this.end_sneak_button = new TouchscreenButtonWidget(sneak_button_x, sneak_button_y, 20, 20, 20, 120, 20, WIDGETS_LOCATION, + state -> { + if (state) { + ((LambdaKeyBinding) this.minecraft.options.keySneak).handle_press_state(false); + this.end_sneak_button.visible = false; + this.start_sneak_button.visible = true; + } + }))); + this.end_sneak_button.visible = false; + this.addButton(new TouchscreenButtonWidget(sneak_button_x, sneak_button_y - 5 - 20, 20, 20, 0, 80, 20, WIDGETS_LOCATION, + state -> ((LambdaKeyBinding) this.minecraft.options.keyForward).handle_press_state(state))); + this.addButton(new TouchscreenButtonWidget(sneak_button_x + 20 + 5, sneak_button_y, 20, 20, 20, 80, 20, WIDGETS_LOCATION, + state -> ((LambdaKeyBinding) this.minecraft.options.keyRight).handle_press_state(state))); + this.addButton(new TouchscreenButtonWidget(sneak_button_x, sneak_button_y + 20 + 5, 20, 20, 40, 80, 20, WIDGETS_LOCATION, + state -> ((LambdaKeyBinding) this.minecraft.options.keyBack).handle_press_state(state))); + this.addButton(new TouchscreenButtonWidget(sneak_button_x - 20 - 5, sneak_button_y, 20, 20, 60, 80, 20, WIDGETS_LOCATION, + state -> ((LambdaKeyBinding) this.minecraft.options.keyLeft).handle_press_state(state))); + } + + @Override + public void render(int mouseX, int mouseY, float delta) + { + super.render(mouseX, mouseY, delta); + } +} diff --git a/src/main/java/me/lambdaurora/lambdacontrols/mixin/InGameHudMixin.java b/src/main/java/me/lambdaurora/lambdacontrols/mixin/InGameHudMixin.java new file mode 100644 index 0000000..adfcdee --- /dev/null +++ b/src/main/java/me/lambdaurora/lambdacontrols/mixin/InGameHudMixin.java @@ -0,0 +1,45 @@ +/* + * 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.LambdaControls; +import me.lambdaurora.lambdacontrols.gui.LambdaControlsHud; +import me.lambdaurora.lambdacontrols.util.CustomInGameHud; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.hud.InGameHud; +import org.jetbrains.annotations.NotNull; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(InGameHud.class) +public class InGameHudMixin implements CustomInGameHud +{ + private LambdaControlsHud lambdacontrols_hud; + + @Inject(method = "", at = @At("RETURN")) + public void on_new(MinecraftClient client, CallbackInfo ci) + { + this.lambdacontrols_hud = new LambdaControlsHud(client, LambdaControls.get()); + } + + @Inject(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/hud/InGameHud;renderMountHealth()V")) + public void on_render(float tick_delta, CallbackInfo ci) + { + lambdacontrols_hud.render(tick_delta); + } + + @Override + public @NotNull LambdaControlsHud get_lambdacontrols_hud() + { + return this.lambdacontrols_hud; + } +} diff --git a/src/main/java/me/lambdaurora/lambdacontrols/mixin/MinecraftClientMixin.java b/src/main/java/me/lambdaurora/lambdacontrols/mixin/MinecraftClientMixin.java index 2134920..fbec21f 100644 --- a/src/main/java/me/lambdaurora/lambdacontrols/mixin/MinecraftClientMixin.java +++ b/src/main/java/me/lambdaurora/lambdacontrols/mixin/MinecraftClientMixin.java @@ -11,8 +11,13 @@ package me.lambdaurora.lambdacontrols.mixin; import me.lambdaurora.lambdacontrols.ControlsMode; import me.lambdaurora.lambdacontrols.LambdaControls; +import me.lambdaurora.lambdacontrols.gui.TouchscreenOverlay; import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.util.Window; +import org.jetbrains.annotations.Nullable; 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; @@ -20,6 +25,14 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @Mixin(MinecraftClient.class) public abstract class MinecraftClientMixin { + @Shadow + public Window window; + + @Shadow + public boolean skipGameRender; + + @Shadow public Screen currentScreen; + @Inject(method = "init", at = @At("RETURN")) private void on_init(CallbackInfo ci) { @@ -32,4 +45,16 @@ public abstract class MinecraftClientMixin if (LambdaControls.get().config.get_controls_mode() == ControlsMode.CONTROLLER) LambdaControls.get().on_tick((MinecraftClient) (Object) this); } + + @Inject(method = "openScreen", at = @At("RETURN")) + private void on_open_screen(@Nullable Screen screen, CallbackInfo ci) + { + LambdaControls mod = LambdaControls.get(); + if (screen == null && mod.config.get_controls_mode() == ControlsMode.TOUCHSCREEN) { + screen = new TouchscreenOverlay(mod); + screen.init(((MinecraftClient) (Object) this), this.window.getScaledWidth(), this.window.getScaledHeight()); + this.skipGameRender = false; + this.currentScreen = screen; + } + } } diff --git a/src/main/java/me/lambdaurora/lambdacontrols/mixin/MouseMixin.java b/src/main/java/me/lambdaurora/lambdacontrols/mixin/MouseMixin.java new file mode 100644 index 0000000..7015368 --- /dev/null +++ b/src/main/java/me/lambdaurora/lambdacontrols/mixin/MouseMixin.java @@ -0,0 +1,42 @@ +/* + * 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.Mouse; +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(Mouse.class) +public class MouseMixin +{ + @Shadow + @Final + private MinecraftClient client; + + @Shadow + private double x; + + @Shadow + private double y; + + @Inject(method = "lockCursor", at = @At("HEAD"), cancellable = true) + private void on_mouse_locked(CallbackInfo ci) + { + if (LambdaControls.get().config.get_controls_mode() == ControlsMode.TOUCHSCREEN) + ci.cancel(); + } +} diff --git a/src/main/java/me/lambdaurora/lambdacontrols/util/CustomInGameHud.java b/src/main/java/me/lambdaurora/lambdacontrols/util/CustomInGameHud.java new file mode 100644 index 0000000..942e4f6 --- /dev/null +++ b/src/main/java/me/lambdaurora/lambdacontrols/util/CustomInGameHud.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.util; + +import me.lambdaurora.lambdacontrols.gui.LambdaControlsHud; +import org.jetbrains.annotations.NotNull; + +/** + * Represents a custom ingame hud with an accessor to an added hud. + */ +public interface CustomInGameHud +{ + @NotNull LambdaControlsHud get_lambdacontrols_hud(); +} diff --git a/src/main/resources/assets/lambdacontrols/textures/gui/widgets.png b/src/main/resources/assets/lambdacontrols/textures/gui/widgets.png new file mode 100644 index 0000000..7139890 Binary files /dev/null and b/src/main/resources/assets/lambdacontrols/textures/gui/widgets.png differ diff --git a/src/main/resources/config.toml b/src/main/resources/config.toml index 5221aee..ea7d3a4 100644 --- a/src/main/resources/config.toml +++ b/src/main/resources/config.toml @@ -4,8 +4,8 @@ controls = "default" [hud] -# Dertermines which side is used depending of the main hand. -side = "right_handed" +# Dertermines where the movements buttons are. +side = "left" # Controller settings [controller] diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index e13dedb..5b1dd37 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -26,7 +26,8 @@ "depends": { "fabricloader": ">=0.4.0", "fabric": "*", - "minecraft": "1.14.x" + "minecraft": "1.14.x", + "cotton-client-commands": ">=0.4.2+1.14.3-SNAPSHOT" }, "suggests": { "flamingo": "*" diff --git a/src/main/resources/lambdacontrols.mixins.json b/src/main/resources/lambdacontrols.mixins.json index 807bad4..1b94c2c 100644 --- a/src/main/resources/lambdacontrols.mixins.json +++ b/src/main/resources/lambdacontrols.mixins.json @@ -3,8 +3,10 @@ "package": "me.lambdaurora.lambdacontrols.mixin", "compatibilityLevel": "JAVA_8", "client": [ + "InGameHudMixin", + "KeyBindingMixin", "MinecraftClientMixin", - "KeyBindingMixin" + "MouseMixin" ], "injectors": { "defaultRequire": 1