1
0
mirror of https://github.com/chylex/Better-Controls.git synced 2025-10-18 03:39:40 +02:00

9 Commits

18 changed files with 153 additions and 79 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

@@ -33,7 +33,7 @@ You can also bind a key that resets all **Toggle Keybinds** at once. That makes
#### Flying
* **Double Tap 'Jump' To Fly** can be turned off.
* **Disable Flight Inertia** stops you instantly when you stop holding movement keys.
* **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. Stop flying by tapping Sneak while touching the ground.
* **Flight Speed Multiplier** (0.25x - 8x) changes flight speed in creative and spectator mode.

View File

@@ -28,7 +28,7 @@ You can also bind a key that resets all **Toggle Keybinds** at once. That makes
#### Flying
* **Double Tap 'Jump' To Fly** can be turned off.
* **Disable Flight Inertia** stops you instantly when you stop holding movement keys.
* **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. Stop flying by tapping Sneak while touching the ground.
* **Flight Speed Multiplier** (0.25x - 8x) changes flight speed in creative and spectator mode.

View File

@@ -28,7 +28,7 @@ You can also bind a key that resets all **Toggle Keybinds** at once. That makes
#### Flying
* **Double Tap 'Jump' To Fly** can be turned off.
* **Disable Flight Inertia** stops you instantly when you stop holding movement keys.
* **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. Stop flying by tapping Sneak while touching the ground.
* **Flight Speed Multiplier** (0.25x - 8x) changes flight speed in creative and spectator mode.

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.5.0
modVersion=1.6.2
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.5
neoForgeVersion=21.5.0-beta
neoModDevVersion=2.0.88
fabricVersion=0.16.14
loomVersion=1.9
minecraftVersion=1.21.9
neoForgeVersion=21.9.2-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.5
minimumNeoForgeVersion=21.5.0-beta
minimumMinecraftVersion=1.21.9
minimumNeoForgeVersion=21.9.0-beta
minimumFabricVersion=0.15.0
# Gradle

View File

@@ -32,7 +32,7 @@ public final class BetterControlsConfig {
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

@@ -46,7 +46,7 @@ final class ConfigSerializer implements JsonSerializer<BetterControlsConfig>, Js
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);
@@ -75,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);
@@ -88,7 +92,7 @@ final class ConfigSerializer implements JsonSerializer<BetterControlsConfig>, Js
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;
@@ -98,6 +101,14 @@ public class BetterControlsScreen extends OptionsSubScreen {
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")),
@@ -119,7 +130,8 @@ 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);
@@ -273,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,6 +2,7 @@ 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;
@@ -15,7 +16,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 +28,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 Mth.floor(Mth.clampedLerp(0.0, options.size() - 1.0, value));
}
@Override
@@ -35,6 +40,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 +78,8 @@ public final class DiscreteValueSliderWidget<T> extends AbstractSliderButton {
protected MutableComponent createNarrationMessage() {
return Component.translatable("gui.narrate.slider", narration.plainCopy().append(" ").append(getMessage()));
}
private static <T> double getOptionValue(ImmutableList<Option<T>> options, int optionIndex) {
return optionIndex / (options.size() - 1.0);
}
}

View File

@@ -6,6 +6,7 @@ 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.input.InputWithModifiers;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.MutableComponent;
import org.jetbrains.annotations.NotNull;
@@ -46,7 +47,7 @@ public final class KeyBindingWidget extends Button {
}
@Override
public void onPress() {
public void onPress(@NotNull InputWithModifiers input) {
isEditing = true;
onEditingStarted.accept(this);
updateKeyBindingText();

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,14 @@ package chylex.bettercontrols.input;
import com.mojang.blaze3d.platform.InputConstants.Type;
import net.minecraft.client.KeyMapping;
import net.minecraft.resources.ResourceLocation;
import org.jetbrains.annotations.Nullable;
public class KeyBindingWithModifier extends KeyMapping {
public static final String CATEGORY = "key.categories.bettercontrols";
public static final Category CATEGORY = new Category(ResourceLocation.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

@@ -3,16 +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)
@SuppressWarnings("StaticMethodOnlyUsedInOneClass")
public interface AccessKeyMappingFields {
@Accessor("CATEGORY_SORT_ORDER")
static Map<String, Integer> getCategoryOrderMap() {
throw new AssertionError();
}
@Accessor("isDown")
boolean isPressedField();

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());
AccessKeyMappingFields.getCategoryOrderMap().put(KeyBindingWithModifier.CATEGORY, Integer.valueOf(Integer.MAX_VALUE));
}
}

View File

@@ -18,7 +18,7 @@ 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);
}

View File

@@ -223,16 +223,21 @@ public final class PlayerTicker {
holdingSneakWhileTouchingGround = false;
}
if (FlightHelper.isFlyingCreativeOrSpectator(player) && getConfig().disableFlightInertia) {
ClientInput input = player.input;
Input keyPresses = input.keyPresses;
if (FlightHelper.isFlyingCreativeOrSpectator(player)) {
float inertiaMultiplier = getConfig().flightInertiaMultiplier;
if (!keyPresses.forward() && !keyPresses.backward() && !keyPresses.left() && !keyPresses.right()) {
player.setDeltaMovement(player.getDeltaMovement().multiply(0.0, 1.0, 0.0));
}
if (!keyPresses.jump() && !keyPresses.shift()) {
player.setDeltaMovement(player.getDeltaMovement().multiply(1.0, 0.0, 1.0));
if (inertiaMultiplier < 1F) {
ClientInput input = player.input;
Input keyPresses = input.keyPresses;
double inertiaMultiplierSqrt = Math.sqrt(inertiaMultiplier);
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));
}
}
}