1
0
mirror of https://github.com/chylex/Better-Controls.git synced 2026-02-15 15:06:12 +01:00

19 Commits

Author SHA1 Message Date
a8cc4e115b Release v1.6.4 for Minecraft 1.21.11+ 2026-01-16 05:19:35 +01:00
13fc85ff1c Fix broken slider options
Closes #48
2026-01-16 05:15:21 +01:00
1c7c5402ad Release v1.6.3 for Minecraft 1.21.11+ 2025-12-21 18:24:26 +01:00
cdb9dadaec Update key binding button UI to match vanilla 2025-12-21 06:27:25 +01:00
76afa6bc40 Update for Minecraft 1.21.11 2025-12-21 05:52:22 +01:00
cbf5adb4a7 Release v1.6.2 for Minecraft 1.21.9+ 2025-10-12 22:06:49 +02:00
5a67786d0b Update for Minecraft 1.21.9 2025-10-01 12:34:15 +02:00
2e19fffe44 Release v1.6.1 for Minecraft 1.21.6+ 2025-07-20 16:28:02 +02:00
32f6a6e7fe Update for Minecraft 1.21.6 2025-07-20 16:28:02 +02:00
dfefab3482 Fix typo in filename 2025-05-25 14:43:55 +02:00
ffd56a4407 Release v1.6.0 for Minecraft 1.21.5+ 2025-05-25 13:36:03 +02:00
6b574f8568 Fix not scrolling to focused option when using Tab 2025-05-25 08:42:35 +02:00
76a1353dbf Jump to previous/next option when pressing left/right arrow key in discrete value sliders 2025-05-25 06:40:00 +02:00
0757d30f12 Replace flight inertia toggle with a multiplier slider
Closes #17
2025-05-25 06:19:11 +02:00
64c4b51f0e Update for Minecraft 1.21.5
Closes #40
2025-05-23 12:27:49 +02:00
5111ac5f7e Release v1.5.0 for Minecraft 1.21.4+ 2025-01-31 04:09:02 +01:00
ee3db91f0c Do not reset Toggle Sprint/Sneak after switching worlds to maintain consistency with vanilla toggles
Closes #30
2025-01-31 00:46:26 +01:00
9da758937e Refactor mixins 2025-01-30 23:53:10 +01:00
aa499bead5 Add keybinding to start a glide & option to turn off double tapping 'Jump' key to glide
Closes #18
2025-01-30 22:46:36 +01:00
33 changed files with 420 additions and 199 deletions

View File

@@ -15,7 +15,7 @@ import javax.annotation.Nullable;
@Mod("bettercontrols")
public final class BetterControlsMod {
public BetterControlsMod() {
if (FMLEnvironment.dist == Dist.CLIENT) {
if (FMLEnvironment.getDist() == Dist.CLIENT) {
BetterControlsCommon.setConfig(BetterControlsConfig.load(FMLPaths.CONFIGDIR.get().resolve("BetterControls.json")));
ModLoadingContext.get().registerExtensionPoint(IConfigScreenFactory.class, () -> BetterControlsMod::createOptionsScreen);
}

View File

@@ -11,28 +11,33 @@ Another major difference is the amount and granularity of options. Better Contro
## Features
The mod adds **Toggle Keybinds** for sprinting, sneaking, flying (creative mode), walking, and jumping. You can use modifier keys (`Control` / `Shift` / `Alt`) for each, including for example setting `Control` to Sneak, and `Control + Y` to Toggle Sneak. If you press the original key, the toggle will be canceled (in the previous example, you can Toggle Sneak by pressing `Control + Y`, and stop sneaking by simply tapping Sneak). Note that the vanilla options for toggling sprinting/sneaking are disabled to avoid conflicts with the custom keybinds.
The mod adds **Toggle Keybinds** for sprinting, sneaking, flying (creative mode), walking, and jumping. You can use modifier keys (`Control` / `Shift` / `Alt`) for each, including for example setting `Control` to Sneak, and `Control + Y` to Toggle Sneak. If you press the original key, the toggle will be canceled (in the previous example, you can Toggle Sneak by pressing `Control + Y`, and stop sneaking by simply tapping `Control`). Note that the vanilla options for toggling sprinting/sneaking are disabled to avoid conflicts with the custom keybinds.
You can also bind a key that resets all **Toggle Keybinds** at once. That makes it easy to for ex. turn on walking, jumping, and sprinting, and then turn all of them off again by pressing one key instead of three.
#### Sprinting
* **Sprint Key Mode** changes how the Sprint key behaves. You can choose between *Tap To Start Sprinting*, *Tap To Start / Stop Sprinting*, and *Hold To Sprint*.
* **Double Tap 'Walk Forwards' To Sprint** can be turned off to prevent accidental sprinting.
* **Resume Sprinting After Hitting Obstacle** automatically presses the Sprint key once you are no longer touching any blocks (helpful when climbing hills, especially if the previous option is enabled).
* **Double Tap 'Walk Forwards' To Sprint** can be turned off.
* **Resume Sprinting After Hitting Obstacle** re-activates sprinting once you are no longer touching any blocks.
#### Sneaking
* **Move Camera Smoothly** lets you disable the smooth movement when sneaking or unsneaking.
* **Move Camera Smoothly** can be turned off to disable the smooth movement when sneaking or unsneaking.
#### Gliding
* **Start a Glide** with a dedicated key.
* **Double Tap 'Jump' To Glide** can be turned off.
#### Flying
* **Double Tap 'Jump' To Fly** can be turned off to prevent accidental flight toggling.
* **Disable Flight Inertia** stops you instantly when you stop holding movement keys.
* **Double Tap 'Jump' To Fly** can be turned off.
* **Flight Inertia Multiplier** changes how quickly you stop moving in the air when you stop holding movement keys.
* **Disable Field Of View Changing** prevents sprinting, potions, and other factors from changing the FOV while flying in creative and spectator mode.
* **Fly On Ground** lets you fly while touching the ground in creative mode (and also lets you stop flying by tapping Sneak while touching the ground).
* **Flight Speed Multiplier** (0.25x - 8x) changes how fast you fly in creative and spectator mode.
* **Vertical Speed Boost** (up to +300%) adds additional vertical speed boost while flying in creative and spectator mode.
* **Fly On Ground** lets you fly while touching the ground in creative mode. Stop flying by tapping Sneak while touching the ground.
* **Flight Speed Multiplier** (0.25x - 8x) changes flight speed in creative and spectator mode.
* **Vertical Speed Boost** (up to +300%) adds additional vertical flight speed boost in creative and spectator mode.
Both speed boosts can be configured separately for sprinting, which will be active when the Sprint key is held. Unlike in vanilla, the sprinting flight boost works in all directions.

View File

@@ -6,28 +6,33 @@ Another major difference is the amount and granularity of options. Better Contro
## Features
The mod adds **Toggle Keybinds** for sprinting, sneaking, flying (creative mode), walking, and jumping. You can use modifier keys (`Control` / `Shift` / `Alt`) for each, including for example setting `Control` to Sneak, and `Control + Y` to Toggle Sneak. If you press the original key, the toggle will be canceled (in the previous example, you can Toggle Sneak by pressing `Control + Y`, and stop sneaking by simply tapping Sneak). Note that the vanilla options for toggling sprinting/sneaking are disabled to avoid conflicts with the custom keybinds.
The mod adds **Toggle Keybinds** for sprinting, sneaking, flying (creative mode), walking, and jumping. You can use modifier keys (`Control` / `Shift` / `Alt`) for each, including for example setting `Control` to Sneak, and `Control + Y` to Toggle Sneak. If you press the original key, the toggle will be canceled (in the previous example, you can Toggle Sneak by pressing `Control + Y`, and stop sneaking by simply tapping `Control`). Note that the vanilla options for toggling sprinting/sneaking are disabled to avoid conflicts with the custom keybinds.
You can also bind a key that resets all **Toggle Keybinds** at once. That makes it easy to for ex. turn on walking, jumping, and sprinting, and then turn all of them off again by pressing one key instead of three.
#### Sprinting
* **Sprint Key Mode** changes how the Sprint key behaves. You can choose between *Tap To Start Sprinting*, *Tap To Start / Stop Sprinting*, and *Hold To Sprint*.
* **Double Tap 'Walk Forwards' To Sprint** can be turned off to prevent accidental sprinting.
* **Resume Sprinting After Hitting Obstacle** automatically presses the Sprint key once you are no longer touching any blocks (helpful when climbing hills, especially if the previous option is enabled).
* **Double Tap 'Walk Forwards' To Sprint** can be turned off.
* **Resume Sprinting After Hitting Obstacle** re-activates sprinting once you are no longer touching any blocks.
#### Sneaking
* **Move Camera Smoothly** lets you disable the smooth movement when sneaking or unsneaking.
* **Move Camera Smoothly** can be turned off to disable the smooth movement when sneaking or unsneaking.
#### Gliding
* **Start a Glide** with a dedicated key.
* **Double Tap 'Jump' To Glide** can be turned off.
#### Flying
* **Double Tap 'Jump' To Fly** can be turned off to prevent accidental flight toggling.
* **Disable Flight Inertia** stops you instantly when you stop holding movement keys.
* **Double Tap 'Jump' To Fly** can be turned off.
* **Flight Inertia Multiplier** changes how quickly you stop moving in the air when you stop holding movement keys.
* **Disable Field Of View Changing** prevents sprinting, potions, and other factors from changing the FOV while flying in creative and spectator mode.
* **Fly On Ground** lets you fly while touching the ground in creative mode (and also lets you stop flying by tapping Sneak while touching the ground).
* **Flight Speed Multiplier** (0.25x - 8x) changes how fast you fly in creative and spectator mode.
* **Vertical Speed Boost** (up to +300%) adds additional vertical speed boost while flying in creative and spectator mode.
* **Fly On Ground** lets you fly while touching the ground in creative mode. Stop flying by tapping Sneak while touching the ground.
* **Flight Speed Multiplier** (0.25x - 8x) changes flight speed in creative and spectator mode.
* **Vertical Speed Boost** (up to +300%) adds additional vertical flight speed boost in creative and spectator mode.
Both speed boosts can be configured separately for sprinting, which will be active when the Sprint key is held. Unlike in vanilla, the sprinting flight boost works in all directions.

View File

@@ -6,28 +6,33 @@ Another major difference is the amount and granularity of options. Better Contro
## Features
The mod adds **Toggle Keybinds** for sprinting, sneaking, flying (creative mode), walking, and jumping. You can use modifier keys (`Control` / `Shift` / `Alt`) for each, including for example setting `Control` to Sneak, and `Control + Y` to Toggle Sneak. If you press the original key, the toggle will be canceled (in the previous example, you can Toggle Sneak by pressing `Control + Y`, and stop sneaking by simply tapping Sneak). Note that the vanilla options for toggling sprinting/sneaking are disabled to avoid conflicts with the custom keybinds.
The mod adds **Toggle Keybinds** for sprinting, sneaking, flying (creative mode), walking, and jumping. You can use modifier keys (`Control` / `Shift` / `Alt`) for each, including for example setting `Control` to Sneak, and `Control + Y` to Toggle Sneak. If you press the original key, the toggle will be canceled (in the previous example, you can Toggle Sneak by pressing `Control + Y`, and stop sneaking by simply tapping `Control`). Note that the vanilla options for toggling sprinting/sneaking are disabled to avoid conflicts with the custom keybinds.
You can also bind a key that resets all **Toggle Keybinds** at once. That makes it easy to for ex. turn on walking, jumping, and sprinting, and then turn all of them off again by pressing one key instead of three.
#### Sprinting
* **Sprint Key Mode** changes how the Sprint key behaves. You can choose between *Tap To Start Sprinting*, *Tap To Start / Stop Sprinting*, and *Hold To Sprint*.
* **Double Tap 'Walk Forwards' To Sprint** can be turned off to prevent accidental sprinting.
* **Resume Sprinting After Hitting Obstacle** automatically presses the Sprint key once you are no longer touching any blocks (helpful when climbing hills, especially if the previous option is enabled).
* **Double Tap 'Walk Forwards' To Sprint** can be turned off.
* **Resume Sprinting After Hitting Obstacle** re-activates sprinting once you are no longer touching any blocks.
#### Sneaking
* **Move Camera Smoothly** lets you disable the smooth movement when sneaking or unsneaking.
* **Move Camera Smoothly** can be turned off to disable the smooth movement when sneaking or unsneaking.
#### Gliding
* **Start a Glide** with a dedicated key.
* **Double Tap 'Jump' To Glide** can be turned off.
#### Flying
* **Double Tap 'Jump' To Fly** can be turned off to prevent accidental flight toggling.
* **Disable Flight Inertia** stops you instantly when you stop holding movement keys.
* **Double Tap 'Jump' To Fly** can be turned off.
* **Flight Inertia Multiplier** changes how quickly you stop moving in the air when you stop holding movement keys.
* **Disable Field Of View Changing** prevents sprinting, potions, and other factors from changing the FOV while flying in creative and spectator mode.
* **Fly On Ground** lets you fly while touching the ground in creative mode (and also lets you stop flying by tapping Sneak while touching the ground).
* **Flight Speed Multiplier** (0.25x - 8x) changes how fast you fly in creative and spectator mode.
* **Vertical Speed Boost** (up to +300%) adds additional vertical speed boost while flying in creative and spectator mode.
* **Fly On Ground** lets you fly while touching the ground in creative mode. Stop flying by tapping Sneak while touching the ground.
* **Flight Speed Multiplier** (0.25x - 8x) changes flight speed in creative and spectator mode.
* **Vertical Speed Boost** (up to +300%) adds additional vertical flight speed boost in creative and spectator mode.
Both speed boosts can be configured separately for sprinting, which will be active when the Sprint key is held. Unlike in vanilla, the sprinting flight boost works in all directions.

View File

@@ -3,18 +3,18 @@ modId=bettercontrols
modName=Better Controls
modDescription=Adds many powerful key bindings and options to control your movement.\\n\\nThe features complement vanilla mechanics without giving unfair advantages, so server use should be fine.
modAuthor=chylex
modVersion=1.4.0
modVersion=1.6.4
modLicense=MPL-2.0
modSourcesURL=https://github.com/chylex/Better-Controls
modIssuesURL=https://github.com/chylex/Better-Controls/issues
modSides=client
# Dependencies
minecraftVersion=1.21.4
neoForgeVersion=21.4.77-beta
neoModDevVersion=2.0.76
fabricVersion=0.16.10
loomVersion=1.9
minecraftVersion=1.21.11
neoForgeVersion=21.11.12-beta
neoModDevVersion=2.0.110
fabricVersion=0.17.2
loomVersion=1.10
mixinVersion=0.12.5+mixin.0.8.5
mixinExtrasVersion=0.4.1
@@ -24,8 +24,8 @@ mixinExtrasVersion=0.4.1
# https://github.com/FabricMC/fabric-loom/releases
# Constraints
minimumMinecraftVersion=1.21.4
minimumNeoForgeVersion=21.4.0-beta
minimumMinecraftVersion=1.21.11
minimumNeoForgeVersion=21.11.0-beta
minimumFabricVersion=0.15.0
# Gradle

View File

@@ -0,0 +1,42 @@
package chylex.bettercontrols;
import chylex.bettercontrols.mixin.AccessCameraFields;
import chylex.bettercontrols.mixin.AccessClientPlayerFields;
import chylex.bettercontrols.mixin.AccessKeyMappingFields;
import chylex.bettercontrols.mixin.AccessPlayerFields;
import chylex.bettercontrols.mixin.AccessToggleKeyMappingFields;
import net.minecraft.client.Camera;
import net.minecraft.client.KeyMapping;
import net.minecraft.client.ToggleKeyMapping;
import net.minecraft.client.player.LocalPlayer;
import net.minecraft.world.entity.player.Player;
@SuppressWarnings("CastToIncompatibleInterface")
public final class Mixins {
private Mixins() {}
@SuppressWarnings("unchecked")
public static <T> T me(Object object) {
return (T) object;
}
public static AccessCameraFields cameraFields(Camera camera) {
return (AccessCameraFields) camera;
}
public static AccessClientPlayerFields clientPlayerFields(LocalPlayer localPlayer) {
return (AccessClientPlayerFields) localPlayer;
}
public static AccessKeyMappingFields keyMappingFields(KeyMapping keyMapping) {
return (AccessKeyMappingFields) keyMapping;
}
public static AccessPlayerFields playerFields(Player player) {
return (AccessPlayerFields) player;
}
public static AccessToggleKeyMappingFields toggleKeyMappingFields(ToggleKeyMapping toggleKeyMapping) {
return (AccessToggleKeyMappingFields) toggleKeyMapping;
}
}

View File

@@ -27,9 +27,12 @@ public final class BetterControlsConfig {
public final KeyBindingWithModifier keyToggleSneak = new KeyBindingWithModifier("key.bettercontrols.toggle_sneak");
public boolean sneakingMovesCameraSmoothly = true;
public final KeyBindingWithModifier keyStartGlide = new KeyBindingWithModifier("key.bettercontrols.start_glide");
public boolean doubleTapJumpToGlide = true;
public final KeyBindingWithModifier keyToggleFlight = new KeyBindingWithModifier("key.bettercontrols.toggle_flight");
public boolean doubleTapJumpToToggleFlight = true;
public boolean disableFlightInertia = false;
public float flightInertiaMultiplier = 1F;
public boolean disableChangingFovWhileFlying = false;
public boolean flyOnGroundInCreative = false;
public float flightHorizontalSpeedMpCreativeDefault = 1F;

View File

@@ -41,9 +41,12 @@ final class ConfigSerializer implements JsonSerializer<BetterControlsConfig>, Js
Json.writeKeyBinding(obj, "Sneak.KeyToggle", cfg.keyToggleSneak);
Json.setBool(obj, "Sneak.SmoothCamera", cfg.sneakingMovesCameraSmoothly);
Json.writeKeyBinding(obj, "Glide.KeyStart", cfg.keyStartGlide);
Json.setBool(obj, "Glide.DoubleTapJump", cfg.doubleTapJumpToGlide);
Json.writeKeyBinding(obj, "Flight.KeyToggle.Creative", cfg.keyToggleFlight);
Json.setBool(obj, "Flight.DoubleTapJump", cfg.doubleTapJumpToToggleFlight);
Json.setBool(obj, "Flight.DisableInertia", cfg.disableFlightInertia);
Json.setFloat(obj, "Flight.InertiaMultiplier", cfg.flightInertiaMultiplier);
Json.setBool(obj, "Flight.DisableChangingFOV", cfg.disableChangingFovWhileFlying);
Json.setBool(obj, "Flight.FlyOnGround.Creative", cfg.flyOnGroundInCreative);
Json.setFloat(obj, "Flight.SpeedMp.Creative.Default", cfg.flightHorizontalSpeedMpCreativeDefault);
@@ -72,6 +75,10 @@ final class ConfigSerializer implements JsonSerializer<BetterControlsConfig>, Js
cfg.sprintMode = SprintMode.TAP_TO_TOGGLE;
}
if (obj.has("Flight.DisableInertia") && obj.get("Flight.DisableInertia").getAsBoolean()) {
cfg.flightInertiaMultiplier = 0F;
}
Json.readKeyBinding(obj, "Sprint.KeyToggle", cfg.keyToggleSprint);
cfg.sprintMode = Json.getEnum(obj, "Sprint.Mode", cfg.sprintMode, SprintMode.class);
cfg.doubleTapForwardToSprint = Json.getBool(obj, "Sprint.DoubleTapForward", cfg.doubleTapForwardToSprint);
@@ -80,9 +87,12 @@ final class ConfigSerializer implements JsonSerializer<BetterControlsConfig>, Js
Json.readKeyBinding(obj, "Sneak.KeyToggle", cfg.keyToggleSneak);
cfg.sneakingMovesCameraSmoothly = Json.getBool(obj, "Sneak.SmoothCamera", cfg.sneakingMovesCameraSmoothly);
Json.readKeyBinding(obj, "Glide.KeyStart", cfg.keyStartGlide);
cfg.doubleTapJumpToGlide = Json.getBool(obj, "Glide.DoubleTapJump", cfg.doubleTapJumpToGlide);
Json.readKeyBinding(obj, "Flight.KeyToggle.Creative", cfg.keyToggleFlight);
cfg.doubleTapJumpToToggleFlight = Json.getBool(obj, "Flight.DoubleTapJump", cfg.doubleTapJumpToToggleFlight);
cfg.disableFlightInertia = Json.getBool(obj, "Flight.DisableInertia", cfg.disableFlightInertia);
cfg.flightInertiaMultiplier = Json.getFloat(obj, "Flight.InertiaMultiplier", cfg.flightInertiaMultiplier, 0F, 1F);
cfg.disableChangingFovWhileFlying = Json.getBool(obj, "Flight.DisableChangingFOV", cfg.disableChangingFovWhileFlying);
cfg.flyOnGroundInCreative = Json.getBool(obj, "Flight.FlyOnGround.Creative", cfg.flyOnGroundInCreative);
cfg.flightHorizontalSpeedMpCreativeDefault = readHorizontalSpeedMultiplier(obj, "Flight.SpeedMp.Creative.Default", cfg.flightHorizontalSpeedMpCreativeDefault);

View File

@@ -18,7 +18,10 @@ import net.minecraft.client.gui.components.CycleButton;
import net.minecraft.client.gui.components.events.GuiEventListener;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.client.gui.screens.options.OptionsSubScreen;
import net.minecraft.client.input.KeyEvent;
import net.minecraft.client.input.MouseButtonEvent;
import net.minecraft.network.chat.Component;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.lwjgl.glfw.GLFW;
import java.util.ArrayList;
@@ -83,9 +86,30 @@ public class BetterControlsScreen extends OptionsSubScreen {
return y;
}
private int generateGlidingOptions(int y, List<GuiEventListener> elements) {
BetterControlsConfig cfg = BetterControlsCommon.getConfig();
generateKeyBindingWithModifierRow(y, elements, text("Start a Glide"), cfg.keyStartGlide);
y += ROW_HEIGHT;
generateBooleanOptionRow(y, elements, text("Double Tap 'Jump' To Start a Glide"), cfg.doubleTapJumpToGlide, value -> cfg.doubleTapJumpToGlide = value);
y += ROW_HEIGHT;
return y;
}
@SuppressWarnings({ "AutoBoxing", "AutoUnboxing" })
private int generateFlightOptions(int y, List<GuiEventListener> elements) {
BetterControlsConfig cfg = BetterControlsCommon.getConfig();
ImmutableList<Option<Float>> flightInertiaOptions = ImmutableList.of(
new Option<>(Float.valueOf(0.00F), text("0x")),
new Option<>(Float.valueOf(0.25F), text("0.25x")),
new Option<>(Float.valueOf(0.50F), text("0.5x")),
new Option<>(Float.valueOf(0.75F), text("0.75x")),
new Option<>(Float.valueOf(1.00F), text("1x"))
);
ImmutableList<Option<Float>> flightSpeedOptions = ImmutableList.of(
new Option<>(Float.valueOf(0.25F), text("0.25x")),
new Option<>(Float.valueOf(0.50F), text("0.5x")),
@@ -107,13 +131,14 @@ public class BetterControlsScreen extends OptionsSubScreen {
generateBooleanOptionRow(y, elements, text("Double Tap 'Jump' To Fly (Creative)"), cfg.doubleTapJumpToToggleFlight, value -> cfg.doubleTapJumpToToggleFlight = value);
y += ROW_HEIGHT;
generateBooleanOptionRow(y, elements, text("Disable Flight Inertia"), cfg.disableFlightInertia, value -> cfg.disableFlightInertia = value);
generateLeftSideText(y, elements, text("Flight Inertia Multiplier"));
elements.add(new DiscreteValueSliderWidget<>(col2(1), y, COL2_W, text("Flight Inertia Multiplier"), flightInertiaOptions, cfg.flightInertiaMultiplier, value -> cfg.flightInertiaMultiplier = value));
y += ROW_HEIGHT;
generateBooleanOptionRow(y, elements, text("Disable Field Of View Changing"), cfg.disableChangingFovWhileFlying, value -> cfg.disableChangingFovWhileFlying = value);
y += ROW_HEIGHT;
generateBooleanOptionRow(y, elements, text("Fly On Ground (Creative Mode)"), cfg.flyOnGroundInCreative, value -> cfg.flyOnGroundInCreative = value);
generateBooleanOptionRow(y, elements, text("Fly On Ground (Creative)"), cfg.flyOnGroundInCreative, value -> cfg.flyOnGroundInCreative = value);
y += ROW_HEIGHT;
y += ROW_HEIGHT / 3;
@@ -189,9 +214,8 @@ public class BetterControlsScreen extends OptionsSubScreen {
private static void generateBooleanOptionRow(int y, List<GuiEventListener> elements, Component text, boolean initialValue, BooleanConsumer onValueChanged) {
generateLeftSideText(y, elements, text);
elements.add(CycleButton.onOffBuilder()
elements.add(CycleButton.onOffBuilder(initialValue)
.displayOnlyValue()
.withInitialValue(Boolean.valueOf(initialValue))
.create(col2(1), y, COL2_W, 20, text, (btn, newValue) -> onValueChanged.accept(newValue.booleanValue())));
}
@@ -223,6 +247,9 @@ public class BetterControlsScreen extends OptionsSubScreen {
elements.add(new TextWidget(0, y, ROW_WIDTH, ROW_HEIGHT, text("Sneaking"), CENTER));
y = generateSneakingOptions(y + ROW_HEIGHT, elements) + TITLE_MARGIN_TOP;
elements.add(new TextWidget(0, y, ROW_WIDTH, ROW_HEIGHT, text("Gliding"), CENTER));
y = generateGlidingOptions(y + ROW_HEIGHT, elements) + TITLE_MARGIN_TOP;
elements.add(new TextWidget(0, y, ROW_WIDTH, ROW_HEIGHT, text("Flying"), CENTER));
y = generateFlightOptions(y + ROW_HEIGHT, elements) + TITLE_MARGIN_TOP;
@@ -258,32 +285,32 @@ public class BetterControlsScreen extends OptionsSubScreen {
}
@Override
public boolean mouseClicked(double mouseX, double mouseY, int button) {
public boolean mouseClicked(@NotNull MouseButtonEvent event, boolean isDoubleClick) {
if (editingKeyBinding != null) {
editingKeyBinding.bindAndStopEditing(InputConstants.Type.MOUSE.getOrCreate(button));
editingKeyBinding.bindAndStopEditing(InputConstants.Type.MOUSE.getOrCreate(event.button()));
onKeyBindingEditingFinished();
return true;
}
else {
return super.mouseClicked(mouseX, mouseY, button);
return super.mouseClicked(event, isDoubleClick);
}
}
@Override
public boolean keyPressed(int keyCode, int scanCode, int modifiers) {
public boolean keyPressed(@NotNull KeyEvent event) {
if (editingKeyBinding != null) {
if (keyCode == GLFW.GLFW_KEY_ESCAPE) {
if (event.key() == GLFW.GLFW_KEY_ESCAPE) {
editingKeyBinding.bindAndStopEditing(InputConstants.UNKNOWN);
}
else {
editingKeyBinding.bindAndStopEditing(InputConstants.getKey(keyCode, scanCode));
editingKeyBinding.bindAndStopEditing(InputConstants.getKey(event));
}
onKeyBindingEditingFinished();
return true;
}
else {
return super.keyPressed(keyCode, scanCode, modifiers);
return super.keyPressed(event);
}
}

View File

@@ -9,12 +9,11 @@ import net.minecraft.client.gui.components.Renderable;
import net.minecraft.client.gui.components.events.GuiEventListener;
import net.minecraft.client.gui.narration.NarratableEntry;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Collections;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import static java.util.stream.Collectors.toMap;
public final class OptionListWidget extends ContainerObjectSelectionList<Entry> {
public static final int ROW_WIDTH = 408;
@@ -35,28 +34,32 @@ public final class OptionListWidget extends ContainerObjectSelectionList<Entry>
private static Offset getElementOffset(GuiEventListener element) {
if (element instanceof OptionWidget widget) {
return new Offset(widget.getX(), widget.getY());
return new Offset(widget.getX(), widget.getY(), widget.getHeight());
}
else if (element instanceof AbstractWidget widget) {
return new Offset(widget.getX(), widget.getY());
return new Offset(widget.getX(), widget.getY(), widget.getHeight());
}
else {
return new Offset(0, 0);
return new Offset(0, 0, 0);
}
}
public interface OptionWidget extends GuiEventListener, Renderable {
int getX();
int getY();
void setX(int x);
int getX();
void setY(int y);
int getY();
int getHeight();
}
private record Offset(int x, int y) {}
private record Offset(int x, int y, int height) {}
@SuppressWarnings("ThisEscapedInObjectConstruction")
public OptionListWidget(int width, int height, int top, int innerHeight, List<GuiEventListener> widgets) {
super(Minecraft.getInstance(), width, height, top, innerHeight);
addEntry(new Entry(widgets));
addEntry(new Entry(this, widgets));
}
@Override
@@ -80,41 +83,60 @@ public final class OptionListWidget extends ContainerObjectSelectionList<Entry>
return true;
}
@Override
protected void scrollToEntry(@NotNull Entry entry) {
// Scrolling to focused item is implemented in Entry.
}
protected static final class Entry extends ContainerObjectSelectionList.Entry<Entry> {
private final OptionListWidget parentWidget;
private final List<GuiEventListener> elements;
private final List<NarratableEntry> narratables;
private final Map<GuiEventListener, Offset> offsets;
public Entry(List<GuiEventListener> elements) {
this.elements = new ArrayList<>(elements);
this.narratables = elements.stream().filter(e -> e instanceof NarratableEntry).map(e -> (NarratableEntry)e).collect(Collectors.toList());
this.offsets = elements.stream().collect(Collectors.toMap(Function.identity(), OptionListWidget::getElementOffset));
public Entry(OptionListWidget parentWidget, List<GuiEventListener> elements) {
this.parentWidget = parentWidget;
this.elements = List.copyOf(elements);
this.narratables = elements.stream().filter(e -> e instanceof NarratableEntry).map(e -> (NarratableEntry)e).toList();
this.offsets = elements.stream().collect(toMap(Function.identity(), OptionListWidget::getElementOffset));
}
@Override
public void setFocused(@Nullable GuiEventListener element) {
super.setFocused(element);
if (Minecraft.getInstance().getLastInputType().isKeyboard()) {
Offset offset = offsets.get(element);
if (offset != null) {
parentWidget.setScrollAmount(offset.y + (offset.height * 0.5F) - (parentWidget.getHeight() * 0.5F) + 4);
}
}
}
@NotNull
@Override
public List<? extends GuiEventListener> children() {
return Collections.unmodifiableList(elements);
return elements;
}
@NotNull
@Override
public List<? extends NarratableEntry> narratables() {
return Collections.unmodifiableList(narratables);
return narratables;
}
@Override
public void render(@NotNull GuiGraphics graphics, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float tickDelta) {
public void renderContent(@NotNull GuiGraphics graphics, int mouseX, int mouseY, boolean hovered, float tickDelta) {
for (GuiEventListener element : elements) {
Offset offset = offsets.get(element);
if (element instanceof AbstractWidget widget) {
widget.setX(x + offset.x);
widget.setY(y + offset.y);
widget.setX(getX() + offset.x);
widget.setY(getY() + offset.y);
}
else if (element instanceof OptionWidget widget) {
widget.setX(x + offset.x);
widget.setY(y + offset.y);
widget.setX(getX() + offset.x);
widget.setY(getY() + offset.y);
}
if (element instanceof Renderable renderable) {

View File

@@ -2,9 +2,9 @@ package chylex.bettercontrols.gui.elements;
import com.google.common.collect.ImmutableList;
import net.minecraft.client.gui.components.AbstractSliderButton;
import net.minecraft.client.input.KeyEvent;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.MutableComponent;
import net.minecraft.util.Mth;
import org.jetbrains.annotations.NotNull;
import java.util.function.Consumer;
@@ -15,7 +15,7 @@ public final class DiscreteValueSliderWidget<T> extends AbstractSliderButton {
private T selectedValue;
public DiscreteValueSliderWidget(int x, int y, int width, int height, Component narration, ImmutableList<Option<T>> options, T selectedValue, Consumer<T> onChanged) {
super(x, y, width, height, Option.find(options, selectedValue).text(), options.indexOf(Option.find(options, selectedValue)) / (options.size() - 1.0));
super(x, y, width, height, Option.find(options, selectedValue).text(), getOptionValue(options, options.indexOf(Option.find(options, selectedValue))));
this.narration = narration;
this.options = options;
this.selectedValue = selectedValue;
@@ -27,7 +27,11 @@ public final class DiscreteValueSliderWidget<T> extends AbstractSliderButton {
}
public Option<T> getSelectedOption() {
return options.get(Mth.floor(Mth.clampedLerp(0.0, options.size() - 1.0, value)));
return options.get(getSelectedOptionIndex());
}
private int getSelectedOptionIndex() {
return getOptionIndex(value, options.size());
}
@Override
@@ -35,6 +39,29 @@ public final class DiscreteValueSliderWidget<T> extends AbstractSliderButton {
setMessage(getSelectedOption().text());
}
@Override
public boolean keyPressed(KeyEvent keyEvent) {
if (keyEvent.isSelection()) {
return super.keyPressed(keyEvent);
}
if (keyEvent.isLeft() || keyEvent.isRight()) {
int newOptionIndex = keyEvent.isLeft()
? getSelectedOptionIndex() - 1
: getSelectedOptionIndex() + 1;
if (newOptionIndex >= 0 && newOptionIndex < options.size()) {
value = getOptionValue(options, newOptionIndex);
applyValue();
updateMessage();
}
return true;
}
return false;
}
@Override
protected void applyValue() {
T newSelectedValue = getSelectedOption().value();
@@ -50,4 +77,20 @@ public final class DiscreteValueSliderWidget<T> extends AbstractSliderButton {
protected MutableComponent createNarrationMessage() {
return Component.translatable("gui.narrate.slider", narration.plainCopy().append(" ").append(getMessage()));
}
public static int getOptionIndex(double value, int optionCount) {
if (value < 0.0) {
return 0;
}
else if (value > 1.0) {
return optionCount - 1;
}
else {
return (int) (value * (optionCount - 1));
}
}
private static <T> double getOptionValue(ImmutableList<Option<T>> options, int optionIndex) {
return optionIndex / (options.size() - 1.0);
}
}

View File

@@ -6,6 +6,8 @@ import net.minecraft.client.KeyMapping;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.components.AbstractButton;
import net.minecraft.client.gui.components.Button;
import net.minecraft.client.gui.components.Tooltip;
import net.minecraft.client.input.InputWithModifiers;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.MutableComponent;
import org.jetbrains.annotations.NotNull;
@@ -13,7 +15,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
public final class KeyBindingWidget extends Button {
public final class KeyBindingWidget extends Button.Plain {
private final KeyMapping binding;
private final Component bindingName;
@@ -42,11 +44,13 @@ public final class KeyBindingWidget extends Button {
@NotNull
@Override
protected MutableComponent createNarrationMessage() {
return binding.isUnbound() ? Component.translatable("narrator.controls.unbound", bindingName) : Component.translatable("narrator.controls.bound", bindingName, super.createNarrationMessage());
return binding.isUnbound()
? Component.translatable("narrator.controls.unbound", bindingName)
: Component.translatable("narrator.controls.bound", bindingName, super.createNarrationMessage());
}
@Override
public void onPress() {
public void onPress(@NotNull InputWithModifiers input) {
isEditing = true;
onEditingStarted.accept(this);
updateKeyBindingText();
@@ -68,24 +72,59 @@ public final class KeyBindingWidget extends Button {
public void updateKeyBindingText() {
boolean hasConflict = false;
MutableComponent conflictText = Component.empty();
if (!binding.isUnbound()) {
for (KeyMapping other : Minecraft.getInstance().options.keyMappings) {
if (binding != other && binding.same(other)) {
if (hasConflict) {
conflictText.append(", ");
}
hasConflict = true;
break;
conflictText.append(Component.translatable(other.getName()));
}
}
}
if (isEditing) {
setMessage(Component.literal("> ").append(binding.getTranslatedKeyMessage().copy().withStyle(ChatFormatting.YELLOW)).append(" <").withStyle(ChatFormatting.YELLOW));
}
else if (hasConflict) {
setMessage(binding.getTranslatedKeyMessage().copy().withStyle(ChatFormatting.RED));
if (hasConflict) {
setMessage(getMessageWithConflict(binding));
setTooltip(Tooltip.create(Component.translatable("controls.keybinds.duplicateKeybinds", conflictText)));
}
else {
setMessage(binding.isUnbound() ? Component.literal("(No Binding)") : binding.getTranslatedKeyMessage());
setMessage(getMessageWithoutConflict(binding));
setTooltip(null);
}
if (isEditing) {
setMessage(getEditingMessage(getMessage()));
}
}
private static MutableComponent getMessageWithConflict(KeyMapping binding) {
return Component
.literal("[ ")
.append(binding.getTranslatedKeyMessage().copy().withStyle(ChatFormatting.WHITE))
.append(" ]")
.withStyle(ChatFormatting.YELLOW);
}
private static Component getMessageWithoutConflict(KeyMapping binding) {
if (binding.isUnbound()) {
return Component
.literal("(")
.append(Component.translatable("key.keyboard.unknown"))
.append(")");
}
return binding.getTranslatedKeyMessage();
}
private static MutableComponent getEditingMessage(Component originalMessage) {
return Component
.literal("> ")
.append(originalMessage.copy().withStyle(ChatFormatting.WHITE, ChatFormatting.UNDERLINE))
.append(" <")
.withStyle(ChatFormatting.YELLOW);
}
}

View File

@@ -8,14 +8,13 @@ import java.util.function.Consumer;
public record Option<T>(T value, Component text) {
public static <T> Option<T> find(List<Option<T>> options, T value) {
return options.stream().filter(it -> Objects.equals(it.value, value)).findFirst().orElseGet(() -> options.get(0));
return options.stream().filter(it -> Objects.equals(it.value, value)).findFirst().orElseGet(options::getFirst);
}
public static <T> CycleButton<Option<T>> button(int x, int y, int width, Component text, List<Option<T>> options, T initialValue, Consumer<T> onValueChanged) {
return CycleButton.<Option<T>>builder(Option::text)
return CycleButton.builder(Option::text, find(options, initialValue))
.displayOnlyValue()
.withValues(options)
.withInitialValue(find(options, initialValue))
.create(x, y, width, 20, text, (btn, newValue) -> onValueChanged.accept(newValue.value()));
}
}

View File

@@ -13,7 +13,7 @@ public final class TextWidget implements OptionWidget {
public static final int LEFT = 0;
public static final int CENTER = 1;
public static final int WHITE = 0xFF_FF_FF;
public static final int WHITE = 0xFF_FF_FF_FF;
private final Component text;
private int x;
@@ -39,24 +39,29 @@ public final class TextWidget implements OptionWidget {
this(x, y, width, 20, text, LEFT);
}
@Override
public void setX(int x) {
this.x = x;
}
@Override
public int getX() {
return x;
}
@Override
public void setY(int y) {
this.y = y;
}
@Override
public int getY() {
return y;
}
@Override
public void setX(int x) {
this.x = x;
}
@Override
public void setY(int y) {
this.y = y;
public int getHeight() {
return height;
}
@Override

View File

@@ -2,13 +2,15 @@ package chylex.bettercontrols.input;
import com.mojang.blaze3d.platform.InputConstants.Type;
import net.minecraft.client.KeyMapping;
import net.minecraft.resources.Identifier;
import org.jetbrains.annotations.Nullable;
public class KeyBindingWithModifier extends KeyMapping {
public static final String CATEGORY = "key.categories.bettercontrols";
@SuppressWarnings("SpellCheckingInspection")
public static final Category CATEGORY = new Category(Identifier.fromNamespaceAndPath("bettercontrols", "all"));
@Nullable
private ModifierKey modifier = null;
private ModifierKey modifier;
public KeyBindingWithModifier(String translationKey) {
super(translationKey, Type.KEYSYM, -1, CATEGORY);

View File

@@ -1,26 +1,26 @@
package chylex.bettercontrols.input;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.client.Minecraft;
public enum ModifierKey {
CONTROL(0) {
@Override
public boolean isPressed() {
return Screen.hasControlDown();
return Minecraft.getInstance().hasControlDown();
}
},
SHIFT(1) {
@Override
public boolean isPressed() {
return Screen.hasShiftDown();
return Minecraft.getInstance().hasShiftDown();
}
},
ALT(2) {
@Override
public boolean isPressed() {
return Screen.hasAltDown();
return Minecraft.getInstance().hasAltDown();
}
};

View File

@@ -6,15 +6,19 @@ public class ToggleTracker {
protected final KeyMapping bindingToggle;
protected final KeyMapping bindingReset;
protected boolean isToggled;
private boolean isToggled;
private boolean waitForRelease;
private boolean hasToggledWhileHoldingReset;
private boolean skipNextToggle;
public ToggleTracker(KeyMapping bindingToggle, KeyMapping bindingReset) {
protected ToggleTracker(KeyMapping bindingToggle, KeyMapping bindingReset, boolean initialState) {
this.bindingToggle = bindingToggle;
this.bindingReset = bindingReset;
this.isToggled = initialState;
}
public ToggleTracker(KeyMapping bindingToggle, KeyMapping bindingReset) {
this(bindingToggle, bindingReset, false);
}
/*

View File

@@ -1,36 +1,36 @@
package chylex.bettercontrols.input;
import chylex.bettercontrols.mixin.AccessKeyBindingFields;
import it.unimi.dsi.fastutil.booleans.BooleanConsumer;
import chylex.bettercontrols.Mixins;
import net.minecraft.client.KeyMapping;
import net.minecraft.client.OptionInstance;
import java.util.HashSet;
import java.util.Set;
public final class ToggleTrackerForStickyKey extends ToggleTracker {
private static final Set<KeyMapping> enabledOverrides = new HashSet<>();
@SuppressWarnings("StaticMethodOnlyUsedInOneClass")
public static boolean isOverrideEnabled(KeyMapping binding) {
return enabledOverrides.contains(binding);
}
private final BooleanConsumer setToggleState;
private final OptionInstance<Boolean> toggleOption;
public ToggleTrackerForStickyKey(KeyMapping bindingToggle, KeyMapping bindingStickyReset, BooleanConsumer setToggleState) {
super(bindingToggle, bindingStickyReset);
this.setToggleState = setToggleState;
this.setToggleState.accept(false);
public ToggleTrackerForStickyKey(KeyMapping bindingToggle, KeyMapping bindingStickyReset, OptionInstance<Boolean> toggleOption) {
super(bindingToggle, bindingStickyReset, toggleOption.get().booleanValue());
this.toggleOption = toggleOption;
enabledOverrides.add(bindingStickyReset);
}
@Override
public boolean tick() {
boolean isToggled = super.tick();
setToggleState.accept(isToggled);
toggleOption.set(Boolean.valueOf(isToggled));
return isToggled;
}
@Override
protected boolean isResetKeyPressed() {
return ((AccessKeyBindingFields)bindingReset).isPressedField();
return Mixins.keyMappingFields(bindingReset).isPressedField();
}
}

View File

@@ -3,15 +3,10 @@ package chylex.bettercontrols.mixin;
import net.minecraft.client.KeyMapping;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
import java.util.Map;
@Mixin(KeyMapping.class)
public interface AccessKeyBindingFields {
@Accessor("CATEGORY_SORT_ORDER")
static Map<String, Integer> getCategoryOrderMap() {
throw new AssertionError();
}
@SuppressWarnings("StaticMethodOnlyUsedInOneClass")
public interface AccessKeyMappingFields {
@Accessor("isDown")
boolean isPressedField();

View File

@@ -7,7 +7,7 @@ import org.spongepowered.asm.mixin.gen.Accessor;
import java.util.function.BooleanSupplier;
@Mixin(ToggleKeyMapping.class)
public interface AccessStickyKeyBindingStateGetter {
public interface AccessToggleKeyMappingFields {
@Accessor
BooleanSupplier getNeedsToggle();

View File

@@ -1,5 +1,6 @@
package chylex.bettercontrols.mixin;
import chylex.bettercontrols.Mixins;
import chylex.bettercontrols.player.PlayerTicker;
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
import net.minecraft.client.player.AbstractClientPlayer;
@@ -14,9 +15,9 @@ public abstract class HookClientPlayerFOV {
at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Abilities;getWalkingSpeed()F")
)
private float overrideWalkingSpeed(float walkingSpeed) {
AbstractClientPlayer me = (AbstractClientPlayer)(Object)this;
AbstractClientPlayer me = Mixins.me(this);
if (me instanceof LocalPlayer localPlayer && PlayerTicker.get(localPlayer).shouldResetFOV(localPlayer)) {
if (me instanceof LocalPlayer localPlayer && PlayerTicker.shouldResetFOV(localPlayer)) {
return 0F;
}
else {

View File

@@ -11,7 +11,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import static org.spongepowered.asm.mixin.injection.At.Shift.AFTER;
@Mixin(KeyboardInput.class)
@SuppressWarnings({ "MethodMayBeStatic", "UnreachableCode" })
@SuppressWarnings("MethodMayBeStatic")
public abstract class HookClientPlayerInputTick {
@Inject(
method = "tick",

View File

@@ -1,5 +1,6 @@
package chylex.bettercontrols.mixin;
import chylex.bettercontrols.Mixins;
import chylex.bettercontrols.player.PlayerTicker;
import com.mojang.authlib.GameProfile;
import net.minecraft.client.multiplayer.ClientLevel;
@@ -12,7 +13,6 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import static org.spongepowered.asm.mixin.injection.At.Shift.AFTER;
@Mixin(LocalPlayer.class)
@SuppressWarnings("UnreachableCode")
public abstract class HookClientPlayerTick extends AbstractClientPlayer {
protected HookClientPlayerTick(ClientLevel world, GameProfile profile) {
super(world, profile);
@@ -20,22 +20,19 @@ public abstract class HookClientPlayerTick extends AbstractClientPlayer {
@Inject(method = "aiStep()V", at = @At("HEAD"))
private void atHead(CallbackInfo info) {
@SuppressWarnings("ConstantConditions")
LocalPlayer player = (LocalPlayer)(Object)this;
LocalPlayer player = Mixins.me(this);
PlayerTicker.get(player).atHead(player);
}
@Inject(method = "aiStep()V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/player/ClientInput;tick()V", ordinal = 0, shift = AFTER))
private void afterInputTick(CallbackInfo info) {
@SuppressWarnings("ConstantConditions")
LocalPlayer player = (LocalPlayer)(Object)this;
LocalPlayer player = Mixins.me(this);
PlayerTicker.get(player).afterInputTick(player);
}
@Inject(method = "aiStep()V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/player/AbstractClientPlayer;aiStep()V", ordinal = 0, shift = AFTER))
private void afterSuperCall(CallbackInfo info) {
@SuppressWarnings("ConstantConditions")
LocalPlayer player = (LocalPlayer)(Object)this;
LocalPlayer player = Mixins.me(this);
PlayerTicker.get(player).afterSuperCall(player);
}
}

View File

@@ -1,5 +1,6 @@
package chylex.bettercontrols.mixin;
import chylex.bettercontrols.Mixins;
import chylex.bettercontrols.player.FlightHelper;
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
import net.minecraft.client.player.LocalPlayer;
@@ -11,7 +12,6 @@ import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Slice;
@Mixin(LocalPlayer.class)
@SuppressWarnings({ "SameReturnValue", "UnreachableCode" })
public abstract class HookClientPlayerVerticalFlightSpeed extends LivingEntity {
protected HookClientPlayerVerticalFlightSpeed(EntityType<? extends LivingEntity> type, Level world) {
super(type, world);
@@ -26,8 +26,7 @@ public abstract class HookClientPlayerVerticalFlightSpeed extends LivingEntity {
)
)
private float modifyVerticalFlightSpeed(float flyingSpeed) {
@SuppressWarnings("ConstantConditions")
LocalPlayer me = (LocalPlayer)(Object)this;
LocalPlayer me = Mixins.me(this);
return flyingSpeed * FlightHelper.getVerticalSpeedMultiplier(me);
}
}

View File

@@ -1,5 +1,6 @@
package chylex.bettercontrols.mixin;
import chylex.bettercontrols.Mixins;
import chylex.bettercontrols.gui.BetterControlsScreen;
import net.minecraft.client.Minecraft;
import net.minecraft.client.Options;
@@ -17,7 +18,6 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.List;
@Mixin(ControlsScreen.class)
@SuppressWarnings("UnreachableCode")
public abstract class HookControlsScreen extends OptionsSubScreen {
public HookControlsScreen(Screen parentScreen, Options options, Component title) {
super(parentScreen, options, title);
@@ -26,8 +26,7 @@ public abstract class HookControlsScreen extends OptionsSubScreen {
@Inject(method = "addOptions", at = @At("RETURN"))
public void afterAddOptions(CallbackInfo ci) {
if (list != null) {
@SuppressWarnings("ConstantConditions")
ControlsScreen screen = (ControlsScreen)(Object)this;
ControlsScreen screen = Mixins.me(this);
MutableComponent buttonTitle = BetterControlsScreen.TITLE.plainCopy().append("...");
list.addSmall(List.of(Button.builder(buttonTitle, btn -> showOptionsScreen(screen)).build()));
}

View File

@@ -2,7 +2,6 @@ package chylex.bettercontrols.mixin;
import chylex.bettercontrols.BetterControlsCommon;
import chylex.bettercontrols.config.BetterControlsConfig;
import chylex.bettercontrols.input.KeyBindingWithModifier;
import net.minecraft.client.KeyMapping;
import net.minecraft.client.Options;
import org.apache.commons.lang3.ArrayUtils;
@@ -38,6 +37,5 @@ public abstract class HookLoadGameOptions {
hasLoaded = true;
keyMappings = ArrayUtils.addAll(keyMappings, config.getAllKeyBindings());
AccessKeyBindingFields.getCategoryOrderMap().put(KeyBindingWithModifier.CATEGORY, Integer.valueOf(Integer.MAX_VALUE));
}
}

View File

@@ -0,0 +1,23 @@
package chylex.bettercontrols.mixin;
import chylex.bettercontrols.player.FlightHelper;
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
import net.minecraft.client.player.LocalPlayer;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Slice;
@Mixin(LocalPlayer.class)
@SuppressWarnings("MethodMayBeStatic")
public abstract class HookPlayerGliding {
@ModifyExpressionValue(
method = "aiStep",
at = @At(value = "INVOKE:LAST", target = "Lnet/minecraft/world/entity/player/Input;jump()Z"),
slice = @Slice(
to = @At(value = "INVOKE", target = "Lnet/minecraft/client/player/LocalPlayer;tryToStartFallFlying()Z")
)
)
private boolean shouldStartGliding(boolean isHoldingJump) {
return FlightHelper.shouldStartGliding(isHoldingJump);
}
}

View File

@@ -1,5 +1,6 @@
package chylex.bettercontrols.mixin;
import chylex.bettercontrols.Mixins;
import chylex.bettercontrols.player.FlightHelper;
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
import com.llamalad7.mixinextras.injector.ModifyReturnValue;
@@ -13,7 +14,6 @@ import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Slice;
@Mixin(Player.class)
@SuppressWarnings({ "SameReturnValue", "UnreachableCode" })
public abstract class HookPlayerHorizontalFlightSpeed extends LivingEntity {
protected HookPlayerHorizontalFlightSpeed(EntityType<? extends LivingEntity> type, Level world) {
super(type, world);
@@ -29,8 +29,7 @@ public abstract class HookPlayerHorizontalFlightSpeed extends LivingEntity {
)
)
private boolean disableVanillaSprintBoost(boolean isSprinting) {
@SuppressWarnings("ConstantConditions")
Player me = (Player)(Object)this;
Player me = Mixins.me(this);
if (me instanceof LocalPlayer localPlayer && FlightHelper.isFlyingCreativeOrSpectator(localPlayer)) {
return false;
@@ -42,8 +41,7 @@ public abstract class HookPlayerHorizontalFlightSpeed extends LivingEntity {
@ModifyReturnValue(method = "getFlyingSpeed", at = @At("RETURN"))
private float modifyHorizontalFlyingSpeed(float flyingSpeed) {
@SuppressWarnings("ConstantConditions")
Player me = (Player)(Object)this;
Player me = Mixins.me(this);
if (me instanceof LocalPlayer localPlayer && localPlayer.getAbilities().flying) {
return flyingSpeed * FlightHelper.getHorizontalSpeedMultiplier(localPlayer);

View File

@@ -1,5 +1,6 @@
package chylex.bettercontrols.mixin;
import chylex.bettercontrols.Mixins;
import chylex.bettercontrols.input.ToggleTrackerForStickyKey;
import net.minecraft.client.KeyMapping;
import net.minecraft.client.ToggleKeyMapping;
@@ -17,14 +18,14 @@ public abstract class HookStickyKeyBindingState extends KeyMapping {
@Final
private BooleanSupplier needsToggle;
public HookStickyKeyBindingState(String translationKey, int code, String category) {
public HookStickyKeyBindingState(String translationKey, int code, KeyMapping.Category category) {
super(translationKey, code, category);
}
@Inject(method = "setDown", at = @At("HEAD"), cancellable = true)
public void setPressed(boolean pressed, CallbackInfo info) {
if (ToggleTrackerForStickyKey.isOverrideEnabled(this)) {
((AccessKeyBindingFields)this).setPressedField(pressed);
Mixins.keyMappingFields(this).setPressedField(pressed);
info.cancel();
}
}

View File

@@ -1,5 +1,6 @@
package chylex.bettercontrols.mixin;
import chylex.bettercontrols.Mixins;
import net.minecraft.client.OptionInstance;
import net.minecraft.client.Options;
import net.minecraft.client.gui.components.AbstractWidget;
@@ -10,12 +11,10 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.function.Consumer;
@Mixin(OptionInstance.class)
@SuppressWarnings("UnreachableCode")
public abstract class HookToggleOptionButtons {
@Inject(method = "createButton(Lnet/minecraft/client/Options;IIILjava/util/function/Consumer;)Lnet/minecraft/client/gui/components/AbstractWidget;", at = @At("RETURN"))
private <T> void disableToggleOptions(Options options, int x, int y, int width, Consumer<T> callback, CallbackInfoReturnable<AbstractWidget> cir) {
@SuppressWarnings("ConstantConditions")
OptionInstance<?> me = (OptionInstance<?>)(Object)this;
OptionInstance<?> me = Mixins.me(this);
if (me == options.toggleCrouch() || me == options.toggleSprint()) {
cir.getReturnValue().active = false;

View File

@@ -1,10 +1,9 @@
package chylex.bettercontrols.player;
import chylex.bettercontrols.BetterControlsCommon;
import chylex.bettercontrols.config.BetterControlsConfig;
import net.minecraft.client.KeyMapping;
import net.minecraft.client.Minecraft;
import net.minecraft.client.player.LocalPlayer;
import static chylex.bettercontrols.BetterControlsCommon.getConfig;
public final class FlightHelper {
private FlightHelper() {}
@@ -15,8 +14,8 @@ public final class FlightHelper {
return KEY_SPRINT.isDown();
}
private static BetterControlsConfig cfg() {
return BetterControlsCommon.getConfig();
public static boolean shouldStartGliding(boolean isHoldingJump) {
return getConfig().keyStartGlide.isDown() || (getConfig().doubleTapJumpToGlide && isHoldingJump);
}
public static boolean isFlyingCreativeOrSpectator(LocalPlayer player) {
@@ -24,15 +23,15 @@ public final class FlightHelper {
}
static boolean shouldFlyOnGround(LocalPlayer player) {
return cfg().flyOnGroundInCreative && player.isCreative() && player.getAbilities().flying;
return getConfig().flyOnGroundInCreative && player.isCreative() && player.getAbilities().flying;
}
public static float getHorizontalSpeedMultiplier(LocalPlayer player) {
if (player.isCreative()) {
return isSprinting() ? cfg().flightHorizontalSpeedMpCreativeSprinting : cfg().flightHorizontalSpeedMpCreativeDefault;
return isSprinting() ? getConfig().flightHorizontalSpeedMpCreativeSprinting : getConfig().flightHorizontalSpeedMpCreativeDefault;
}
else if (player.isSpectator()) {
return isSprinting() ? cfg().flightHorizontalSpeedMpSpectatorSprinting : cfg().flightHorizontalSpeedMpSpectatorDefault;
return isSprinting() ? getConfig().flightHorizontalSpeedMpSpectatorSprinting : getConfig().flightHorizontalSpeedMpSpectatorDefault;
}
else {
return 1F;
@@ -41,10 +40,10 @@ public final class FlightHelper {
public static float getVerticalSpeedMultiplier(LocalPlayer player) {
if (player.isCreative()) {
return isSprinting() ? cfg().flightVerticalSpeedMpCreativeSprinting : cfg().flightVerticalSpeedMpCreativeDefault;
return isSprinting() ? getConfig().flightVerticalSpeedMpCreativeSprinting : getConfig().flightVerticalSpeedMpCreativeDefault;
}
else if (player.isSpectator()) {
return isSprinting() ? cfg().flightVerticalSpeedMpSpectatorSprinting : cfg().flightVerticalSpeedMpSpectatorDefault;
return isSprinting() ? getConfig().flightVerticalSpeedMpSpectatorSprinting : getConfig().flightVerticalSpeedMpSpectatorDefault;
}
else {
return 1F;

View File

@@ -1,30 +1,28 @@
package chylex.bettercontrols.player;
import chylex.bettercontrols.BetterControlsCommon;
import chylex.bettercontrols.config.BetterControlsConfig;
import chylex.bettercontrols.Mixins;
import chylex.bettercontrols.gui.BetterControlsScreen;
import chylex.bettercontrols.input.SprintMode;
import chylex.bettercontrols.input.ToggleTracker;
import chylex.bettercontrols.input.ToggleTrackerForStickyKey;
import chylex.bettercontrols.mixin.AccessCameraFields;
import chylex.bettercontrols.mixin.AccessClientPlayerFields;
import chylex.bettercontrols.mixin.AccessPlayerFields;
import chylex.bettercontrols.mixin.AccessStickyKeyBindingStateGetter;
import chylex.bettercontrols.mixin.AccessToggleKeyMappingFields;
import net.minecraft.client.Camera;
import net.minecraft.client.KeyMapping;
import net.minecraft.client.Minecraft;
import net.minecraft.client.Options;
import net.minecraft.client.ToggleKeyMapping;
import net.minecraft.client.player.ClientInput;
import net.minecraft.client.player.LocalPlayer;
import net.minecraft.world.entity.player.Input;
import java.lang.ref.WeakReference;
import java.util.function.BooleanSupplier;
import static chylex.bettercontrols.BetterControlsCommon.getConfig;
public final class PlayerTicker {
private static final Minecraft MINECRAFT = Minecraft.getInstance();
private static final Options OPTIONS = MINECRAFT.options;
private static final KeyMapping KEY_SPRINT = OPTIONS.keySprint;
private static final ToggleKeyMapping KEY_SPRINT = (ToggleKeyMapping) OPTIONS.keySprint;
private static final KeyMapping KEY_SNEAK = OPTIONS.keyShift;
private static final KeyMapping KEY_FORWARD = OPTIONS.keyUp;
private static final KeyMapping KEY_JUMP = OPTIONS.keyJump;
@@ -39,10 +37,6 @@ public final class PlayerTicker {
return ticker;
}
private static BetterControlsConfig cfg() {
return BetterControlsCommon.getConfig();
}
private final WeakReference<LocalPlayer> ref;
private PlayerTicker(LocalPlayer player) {
@@ -52,10 +46,10 @@ public final class PlayerTicker {
// Logic
private final ToggleTracker toggleSprint = new ToggleTrackerForStickyKey(cfg().keyToggleSprint, KEY_SPRINT, OPTIONS.toggleSprint()::set);
private final ToggleTracker toggleSneak = new ToggleTrackerForStickyKey(cfg().keyToggleSneak, KEY_SNEAK, OPTIONS.toggleCrouch()::set);
private final ToggleTracker toggleWalkForward = new ToggleTracker(cfg().keyToggleWalkForward, KEY_FORWARD);
private final ToggleTracker toggleJump = new ToggleTracker(cfg().keyToggleJump, KEY_JUMP);
private final ToggleTracker toggleSprint = new ToggleTrackerForStickyKey(getConfig().keyToggleSprint, KEY_SPRINT, OPTIONS.toggleSprint());
private final ToggleTracker toggleSneak = new ToggleTrackerForStickyKey(getConfig().keyToggleSneak, KEY_SNEAK, OPTIONS.toggleCrouch());
private final ToggleTracker toggleWalkForward = new ToggleTracker(getConfig().keyToggleWalkForward, KEY_FORWARD);
private final ToggleTracker toggleJump = new ToggleTracker(getConfig().keyToggleJump, KEY_JUMP);
private boolean waitingForSprintKeyRelease = false;
private boolean stopSprintingAfterReleasingSprintKey = false;
@@ -69,7 +63,7 @@ public final class PlayerTicker {
private int temporaryFlyOnGroundTimer = 0;
private void setup() {
AccessStickyKeyBindingStateGetter sprint = (AccessStickyKeyBindingStateGetter)KEY_SPRINT;
AccessToggleKeyMappingFields sprint = Mixins.toggleKeyMappingFields(KEY_SPRINT);
BooleanSupplier getter = sprint.getNeedsToggle();
if (getter instanceof SprintPressGetter g) {
@@ -84,16 +78,16 @@ public final class PlayerTicker {
player.setOnGround(false);
}
if (!cfg().doubleTapForwardToSprint) {
((AccessClientPlayerFields)player).setSprintTriggerTime(0);
if (!getConfig().doubleTapForwardToSprint) {
Mixins.clientPlayerFields(player).setSprintTriggerTime(0);
}
if (!cfg().doubleTapJumpToToggleFlight) {
((AccessPlayerFields)player).setJumpTriggerTime(0);
if (!getConfig().doubleTapJumpToToggleFlight) {
Mixins.playerFields(player).setJumpTriggerTime(0);
}
SprintMode sprintMode = cfg().sprintMode;
boolean wasSprintToggled = Boolean.TRUE.equals(OPTIONS.toggleSprint().get());
SprintMode sprintMode = getConfig().sprintMode;
boolean wasSprintToggled = OPTIONS.toggleSprint().get().booleanValue();
boolean isSprintToggled = toggleSprint.tick();
if (temporarySprintTimer > 0) {
@@ -170,7 +164,7 @@ public final class PlayerTicker {
player.input.makeJump();
}
if (cfg().resumeSprintingAfterHittingObstacle) {
if (getConfig().resumeSprintingAfterHittingObstacle) {
if (wasHittingObstacle != player.horizontalCollision) {
if (!wasHittingObstacle) {
wasSprintingBeforeHittingObstacle = player.isSprinting() || KEY_SPRINT.isDown();
@@ -229,20 +223,26 @@ public final class PlayerTicker {
holdingSneakWhileTouchingGround = false;
}
if (FlightHelper.isFlyingCreativeOrSpectator(player) && cfg().disableFlightInertia) {
ClientInput input = player.input;
if (FlightHelper.isFlyingCreativeOrSpectator(player)) {
float inertiaMultiplier = getConfig().flightInertiaMultiplier;
if (input.forwardImpulse == 0F && input.leftImpulse == 0F) {
player.setDeltaMovement(player.getDeltaMovement().multiply(0.0, 1.0, 0.0));
}
if (inertiaMultiplier < 1F) {
ClientInput input = player.input;
Input keyPresses = input.keyPresses;
double inertiaMultiplierSqrt = Math.sqrt(inertiaMultiplier);
if (!input.keyPresses.jump() && !input.keyPresses.shift()) {
player.setDeltaMovement(player.getDeltaMovement().multiply(1.0, 0.0, 1.0));
if (!keyPresses.forward() && !keyPresses.backward() && !keyPresses.left() && !keyPresses.right()) {
player.setDeltaMovement(player.getDeltaMovement().multiply(inertiaMultiplierSqrt, 1.0, inertiaMultiplierSqrt));
}
if (!keyPresses.jump() && !keyPresses.shift()) {
player.setDeltaMovement(player.getDeltaMovement().multiply(1.0, inertiaMultiplierSqrt, 1.0));
}
}
}
if (player.isCreative()) {
if (cfg().keyToggleFlight.consumeClick()) {
if (getConfig().keyToggleFlight.consumeClick()) {
boolean isFlying = !player.getAbilities().flying;
player.getAbilities().flying = isFlying;
@@ -267,27 +267,27 @@ public final class PlayerTicker {
temporaryFlyOnGroundTimer = 0;
}
if (!cfg().sneakingMovesCameraSmoothly) {
if (!getConfig().sneakingMovesCameraSmoothly) {
Camera camera = MINECRAFT.gameRenderer.getMainCamera();
if (camera.getEntity() == player) {
((AccessCameraFields)camera).setEyeHeight(player.getEyeHeight());
if (camera.entity() == player) {
Mixins.cameraFields(camera).setEyeHeight(player.getEyeHeight());
}
}
if (cfg().keyResetAllToggles.consumeClick()) {
if (getConfig().keyResetAllToggles.consumeClick()) {
toggleSprint.reset();
toggleSneak.reset();
toggleWalkForward.reset();
toggleJump.reset();
}
if (cfg().keyOpenMenu.isDown()) {
if (getConfig().keyOpenMenu.isDown()) {
MINECRAFT.setScreen(new BetterControlsScreen(null));
}
}
public boolean shouldResetFOV(LocalPlayer player) {
return cfg().disableChangingFovWhileFlying && FlightHelper.isFlyingCreativeOrSpectator(player);
public static boolean shouldResetFOV(LocalPlayer player) {
return getConfig().disableChangingFovWhileFlying && FlightHelper.isFlyingCreativeOrSpectator(player);
}
}

View File

@@ -7,9 +7,9 @@
"client": [
"AccessCameraFields",
"AccessClientPlayerFields",
"AccessKeyBindingFields",
"AccessKeyMappingFields",
"AccessPlayerFields",
"AccessStickyKeyBindingStateGetter",
"AccessToggleKeyMappingFields",
"HookClientPlayerFOV",
"HookClientPlayerInputTick",
"HookClientPlayerTick",
@@ -17,6 +17,7 @@
"HookControlsListWidget",
"HookControlsScreen",
"HookLoadGameOptions",
"HookPlayerGliding",
"HookPlayerHorizontalFlightSpeed",
"HookStickyKeyBindingState",
"HookToggleOptionButtons"