1
0
mirror of https://github.com/chylex/Better-Controls.git synced 2025-04-11 21:15:43 +02:00

Add core files w/ config serialization, options screen, and keybind registration

This commit is contained in:
chylex 2020-10-15 12:13:18 +02:00
parent 999f2bcec0
commit bee7dee256
24 changed files with 925 additions and 0 deletions

BIN
logo.afdesign Normal file

Binary file not shown.

View File

@ -0,0 +1,11 @@
package chylex.bettercontrols;
import chylex.bettercontrols.config.BetterControlsConfig;
import net.fabricmc.api.ClientModInitializer;
import net.fabricmc.loader.api.FabricLoader;
public final class BetterControlsMod implements ClientModInitializer{
public static final BetterControlsConfig config = BetterControlsConfig.load(FabricLoader.getInstance().getConfigDir().resolve("BetterControls.json"));
@Override
public void onInitializeClient(){}
}

View File

@ -0,0 +1,26 @@
package chylex.bettercontrols.config;
import net.minecraft.client.options.KeyBinding;
import java.nio.file.Path;
public final class BetterControlsConfig{
public static BetterControlsConfig load(final Path path){
return ConfigSerializer.deserialize(path).setPath(path);
}
private Path path;
BetterControlsConfig(){}
private BetterControlsConfig setPath(final Path path){
this.path = path;
return this;
}
public KeyBinding[] getAllKeyBindings(){
return new KeyBinding[0];
}
public void save(){
ConfigSerializer.serialize(path, this);
}
}

View File

@ -0,0 +1,62 @@
package chylex.bettercontrols.config;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
final class ConfigSerializer implements JsonSerializer<BetterControlsConfig>, JsonDeserializer<BetterControlsConfig>{
private static final Logger logger = LogManager.getLogger();
private static final Gson gson = new GsonBuilder().registerTypeAdapter(BetterControlsConfig.class, new ConfigSerializer()).setPrettyPrinting().create();
private ConfigSerializer(){}
@Override
public JsonElement serialize(final BetterControlsConfig cfg, final Type typeOfSrc, final JsonSerializationContext context){
final JsonObject obj = new JsonObject();
return obj;
}
@Override
public BetterControlsConfig deserialize(final JsonElement json, final Type typeOfT, final JsonDeserializationContext context) throws JsonParseException{
final BetterControlsConfig cfg = new BetterControlsConfig();
final JsonObject obj = json.getAsJsonObject();
return cfg;
}
static void serialize(final Path path, final BetterControlsConfig config){
try(final JsonWriter writer = gson.newJsonWriter(Files.newBufferedWriter(path, StandardCharsets.UTF_8))){
gson.getAdapter(BetterControlsConfig.class).write(writer, config);
}catch(final IOException e){
logger.error("Error saving BetterControls configuration file!", e);
}
}
static BetterControlsConfig deserialize(final Path path){
try(final JsonReader jsonReader = new JsonReader(Files.newBufferedReader(path, StandardCharsets.UTF_8))){
return gson.getAdapter(BetterControlsConfig.class).read(jsonReader);
}catch(final FileNotFoundException | NoSuchFileException ignored){
}catch(final IOException e){
logger.error("Error reading BetterControls configuration file!", e);
}
return new BetterControlsConfig();
}
}

View File

@ -0,0 +1,54 @@
package chylex.bettercontrols.config;
import chylex.bettercontrols.input.KeyBindingWithModifier;
import chylex.bettercontrols.input.ModifierKey;
import com.google.gson.JsonObject;
import net.minecraft.client.util.InputUtil;
final class Json{
private Json(){}
static void setInt(final JsonObject obj, final String key, final int value){
obj.addProperty(key, Integer.valueOf(value));
}
static int getInt(final JsonObject obj, final String key, final int defaultValue){
return obj.has(key) ? obj.get(key).getAsInt() : defaultValue;
}
static void setFloat(final JsonObject obj, final String key, final float value){
obj.addProperty(key, Float.valueOf(value));
}
static float getFloat(final JsonObject obj, final String key, final float defaultValue){
return obj.has(key) ? obj.get(key).getAsFloat() : defaultValue;
}
static void setBool(final JsonObject obj, final String key, final boolean value){
obj.addProperty(key, Boolean.valueOf(value));
}
static boolean getBool(final JsonObject obj, final String key, final boolean defaultValue){
return obj.has(key) ? obj.get(key).getAsBoolean() : defaultValue;
}
private static final String KEY_SUFFIX = ".Key";
private static final String MOD_SUFFIX = ".Mod";
static void writeKeyBinding(final JsonObject obj, final String key, final KeyBindingWithModifier keyBinding){
obj.addProperty(key + KEY_SUFFIX, keyBinding.getBoundKeyTranslationKey());
if (keyBinding.getModifier() != null){
obj.addProperty(key + MOD_SUFFIX, Integer.valueOf(keyBinding.getModifier().id));
}
}
static void readKeyBinding(final JsonObject obj, final String key, final KeyBindingWithModifier keyBinding){
if (obj.has(key + KEY_SUFFIX)){
keyBinding.setBoundKey(InputUtil.fromTranslationKey(obj.get(key + KEY_SUFFIX).getAsString()));
}
if (obj.has(key + MOD_SUFFIX)){
keyBinding.setModifier(ModifierKey.getById(obj.get(key + MOD_SUFFIX).getAsInt()));
}
}
}

View File

@ -0,0 +1,104 @@
package chylex.bettercontrols.gui;
import chylex.bettercontrols.BetterControlsMod;
import chylex.bettercontrols.gui.elements.KeyBindingWidget;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.Element;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.client.gui.screen.ScreenTexts;
import net.minecraft.client.gui.screen.options.GameOptionsScreen;
import net.minecraft.client.gui.widget.ButtonWidget;
import net.minecraft.client.options.KeyBinding;
import net.minecraft.client.util.InputUtil;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.text.LiteralText;
import org.lwjgl.glfw.GLFW;
import java.util.ArrayList;
import java.util.List;
public class BetterControlsScreen extends GameOptionsScreen{
public static final LiteralText TITLE = new LiteralText("Better Controls");
private static final int BOTTOM_PADDING = 3;
private static final int TEXT_PADDING_RIGHT = 4;
private static final int TITLE_MARGIN_TOP = 3;
private static final int ROW_HEIGHT = 22;
private OptionListWidget optionsWidget;
private KeyBindingWidget editingKeyBinding;
private final List<KeyBindingWidget> allKeyBindings = new ArrayList<>();
public BetterControlsScreen(final Screen parentScreen){
super(parentScreen, MinecraftClient.getInstance().options, TITLE);
}
@Override
public void init(){
allKeyBindings.clear();
final List<Element> elements = new ArrayList<>();
int y = 0;
addButton(new ButtonWidget(width / 2 - 99, height - 29, 200, 20, ScreenTexts.DONE, btn -> client.openScreen(parent)));
addChild(optionsWidget = new OptionListWidget(21, height - 32, width, height, elements, y - TITLE_MARGIN_TOP + BOTTOM_PADDING));
}
@Override
public void removed(){
BetterControlsMod.config.save();
}
@Override
public void render(final MatrixStack matrices, final int mouseX, final int mouseY, final float delta){
renderBackground(matrices);
optionsWidget.render(matrices, mouseX, mouseY, delta);
drawCenteredText(matrices, textRenderer, title, width / 2, 8, (255 << 16) | (255 << 8) | 255);
super.render(matrices, mouseX, mouseY, delta);
}
private void startEditingKeyBinding(final KeyBindingWidget widget){
if (editingKeyBinding != null){
editingKeyBinding.stopEditing();
}
editingKeyBinding = widget;
}
@Override
public boolean mouseClicked(final double mouseX, final double mouseY, final int button){
if (editingKeyBinding != null){
editingKeyBinding.bindAndStopEditing(InputUtil.Type.MOUSE.createFromCode(button));
onKeyBindingEditingFinished();
return true;
}
else{
return super.mouseClicked(mouseX, mouseY, button);
}
}
@Override
public boolean keyPressed(final int keyCode, final int scanCode, final int modifiers){
if (editingKeyBinding != null){
if (keyCode == GLFW.GLFW_KEY_ESCAPE){
editingKeyBinding.bindAndStopEditing(InputUtil.UNKNOWN_KEY);
}
else{
editingKeyBinding.bindAndStopEditing(InputUtil.fromKeyCode(keyCode, scanCode));
}
onKeyBindingEditingFinished();
return true;
}
else{
return super.keyPressed(keyCode, scanCode, modifiers);
}
}
private void onKeyBindingEditingFinished(){
editingKeyBinding = null;
KeyBinding.updateKeysByCode();
for(final KeyBindingWidget widget : allKeyBindings){
widget.updateKeyBindingText();
}
}
}

View File

@ -0,0 +1,36 @@
package chylex.bettercontrols.gui;
import chylex.bettercontrols.mixin.AccessScreenButtons;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.Element;
import net.minecraft.client.gui.screen.options.ControlsOptionsScreen;
import net.minecraft.client.gui.widget.AbstractButtonWidget;
import net.minecraft.client.gui.widget.ButtonWidget;
import net.minecraft.client.gui.widget.OptionButtonWidget;
import net.minecraft.client.options.Option;
import java.util.List;
public final class ControlsScreenPatcher{
private ControlsScreenPatcher(){}
public static void onOpened(final ControlsOptionsScreen screen){
final AccessScreenButtons accessor = (AccessScreenButtons)screen;
final List<? extends Element> children = screen.children();
final List<AbstractButtonWidget> buttons = accessor.getButtons();
final AbstractButtonWidget autoJump = buttons
.stream()
.filter(it -> it instanceof OptionButtonWidget && ((OptionButtonWidget)it).getOption() == Option.AUTO_JUMP)
.findAny()
.orElse(null);
if (autoJump != null){
children.remove(autoJump);
buttons.remove(autoJump);
accessor.callAddButton(new ButtonWidget(autoJump.x, autoJump.y, autoJump.getWidth(), autoJump.getHeight(), BetterControlsScreen.TITLE.copy().append("..."), btn -> {
MinecraftClient.getInstance().openScreen(new BetterControlsScreen(screen));
}));
}
}
}

View File

@ -0,0 +1,126 @@
package chylex.bettercontrols.gui;
import chylex.bettercontrols.gui.OptionListWidget.Entry;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.Drawable;
import net.minecraft.client.gui.Element;
import net.minecraft.client.gui.widget.AbstractButtonWidget;
import net.minecraft.client.gui.widget.ElementListWidget;
import net.minecraft.client.util.math.MatrixStack;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
public final class OptionListWidget extends ElementListWidget<Entry>{
public static final int ROW_WIDTH = 408;
public static final int ROW_PADDING = 2;
public static final int COL2_W = (ROW_WIDTH / 2) - ROW_PADDING;
public static final int COL3_W = (ROW_WIDTH / 3) - ROW_PADDING;
public static final int COL4_W = (ROW_WIDTH / 4) - ROW_PADDING;
public static final int COL6_W = (ROW_WIDTH / 6) - ROW_PADDING;
public static int col2(final int column){
return (column * ROW_WIDTH) / 2;
}
public static int col3(final int column){
return (column * ROW_WIDTH) / 3;
}
public static int col4(final int column){
return (column * ROW_WIDTH) / 4;
}
public static int col6(final int column){
return (column * ROW_WIDTH) / 6;
}
private static Offset getElementOffset(final Element element){
if (element instanceof Widget){
return new Offset(((Widget)element).getX(), ((Widget)element).getY());
}
else if (element instanceof AbstractButtonWidget){
return new Offset(((AbstractButtonWidget)element).x, ((AbstractButtonWidget)element).y);
}
else{
return new Offset(0, 0);
}
}
public interface Widget extends Element, Drawable{
int getX();
int getY();
void setX(int x);
void setY(int y);
}
private static final class Offset{
public final int x;
public final int y;
private Offset(final int x, final int y){
this.x = x;
this.y = y;
}
}
public OptionListWidget(final int top, final int bottom, final int width, final int height, final List<Element> widgets, final int innerHeight){
super(MinecraftClient.getInstance(), width, height, top, bottom, innerHeight);
addEntry(new Entry(widgets));
}
@Override
protected int getRowLeft(){
return super.getRowLeft() - ROW_PADDING;
}
@Override
public int getRowWidth(){
return ROW_WIDTH;
}
@Override
protected int getScrollbarPositionX(){
return (width + ROW_WIDTH) / 2 + 4;
}
protected static final class Entry extends ElementListWidget.Entry<Entry>{
private final List<Element> elements;
private final Map<Element, Offset> offsets;
public Entry(final List<Element> elements){
this.elements = new ArrayList<>(elements);
this.offsets = elements.stream().collect(Collectors.toMap(Function.identity(), OptionListWidget::getElementOffset));
}
@Override
public List<? extends Element> children(){
return Collections.unmodifiableList(elements);
}
@Override
public void render(final MatrixStack matrices, final int index, final int y, final int x, final int entryWidth, final int entryHeight, final int mouseX, final int mouseY, final boolean hovered, final float tickDelta){
for(final Element element : elements){
final Offset offset = offsets.get(element);
if (element instanceof AbstractButtonWidget){
final AbstractButtonWidget button = (AbstractButtonWidget)element;
button.x = x + offset.x;
button.y = y + offset.y;
}
else if (element instanceof Widget){
final Widget widget = (Widget)element;
widget.setX(x + offset.x);
widget.setY(y + offset.y);
}
if (element instanceof Drawable){
((Drawable)element).render(matrices, mouseX, mouseY, tickDelta);
}
}
}
}
}

View File

@ -0,0 +1,27 @@
package chylex.bettercontrols.gui.elements;
import it.unimi.dsi.fastutil.booleans.BooleanConsumer;
import net.minecraft.client.gui.screen.ScreenTexts;
import net.minecraft.client.gui.widget.ButtonWidget;
public final class BooleanValueWidget extends ButtonWidget{
private final BooleanConsumer onChanged;
private boolean value;
public BooleanValueWidget(final int x, final int y, final int width, final int height, final boolean currentValue, final BooleanConsumer onChanged){
super(x, y, width, height, currentValue ? ScreenTexts.ON : ScreenTexts.OFF, ignore -> {});
this.value = currentValue;
this.onChanged = onChanged;
}
public BooleanValueWidget(final int x, final int y, final int width, final boolean currentValue, final BooleanConsumer onChanged){
this(x, y, width, 20, currentValue, onChanged);
}
@Override
public void onPress(){
super.onPress();
value = !value;
setMessage(value ? ScreenTexts.ON : ScreenTexts.OFF);
onChanged.accept(value);
}
}

View File

@ -0,0 +1,36 @@
package chylex.bettercontrols.gui.elements;
import net.minecraft.client.gui.widget.ButtonWidget;
import java.util.List;
import java.util.function.Consumer;
public class CycleButtonWidget<T> extends ButtonWidget{
private final List<Option<T>> options;
private final Consumer<T> onChanged;
private T selectedValue;
public CycleButtonWidget(final int x, final int y, final int width, final int height, final List<Option<T>> options, final T selectedValue, final Consumer<T> onChanged){
super(x, y, width, height, Option.find(options, selectedValue).getText(), btn -> {});
this.options = options;
this.selectedValue = selectedValue;
this.onChanged = onChanged;
}
public CycleButtonWidget(final int x, final int y, final int width, final List<Option<T>> options, final T selectedValue, final Consumer<T> onChanged){
this(x, y, width, 20, options, selectedValue, onChanged);
}
@Override
public void onPress(){
int nextIndex = options.indexOf(Option.find(options, selectedValue)) + 1;
if (nextIndex >= options.size()){
nextIndex = 0;
}
final Option<T> newSelectedOption = options.get(nextIndex);
selectedValue = newSelectedOption.getValue();
onChanged.accept(selectedValue);
setMessage(newSelectedOption.getText());
}
}

View File

@ -0,0 +1,41 @@
package chylex.bettercontrols.gui.elements;
import net.minecraft.client.gui.widget.SliderWidget;
import net.minecraft.util.math.MathHelper;
import java.util.List;
import java.util.function.Consumer;
public final class DiscreteValueSliderWidget<T> extends SliderWidget{
private final List<Option<T>> options;
private final Consumer<T> onChanged;
private T selectedValue;
public DiscreteValueSliderWidget(final int x, final int y, final int width, final int height, final List<Option<T>> options, final T selectedValue, final Consumer<T> onChanged){
super(x, y, width, height, Option.find(options, selectedValue).getText(), options.indexOf(Option.find(options, selectedValue)) / (options.size() - 1.0));
this.options = options;
this.selectedValue = selectedValue;
this.onChanged = onChanged;
}
public DiscreteValueSliderWidget(final int x, final int y, final int width, final List<Option<T>> options, final T selectedValue, final Consumer<T> onChanged){
this(x, y, width, 20, options, selectedValue, onChanged);
}
public Option<T> getSelectedOption(){
return options.get(MathHelper.floor(MathHelper.clampedLerp(0.0, options.size() - 1.0, value)));
}
@Override
protected void updateMessage(){
setMessage(getSelectedOption().getText());
}
@Override
protected void applyValue(){
final T newSelectedValue = getSelectedOption().getValue();
if (selectedValue != newSelectedValue){
selectedValue = newSelectedValue;
onChanged.accept(newSelectedValue);
}
}
}

View File

@ -0,0 +1,89 @@
package chylex.bettercontrols.gui.elements;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.widget.AbstractButtonWidget;
import net.minecraft.client.gui.widget.ButtonWidget;
import net.minecraft.client.options.KeyBinding;
import net.minecraft.client.util.InputUtil;
import net.minecraft.text.LiteralText;
import net.minecraft.text.MutableText;
import net.minecraft.text.Text;
import net.minecraft.text.TranslatableText;
import net.minecraft.util.Formatting;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
public final class KeyBindingWidget extends ButtonWidget{
private final KeyBinding binding;
private final Text bindingName;
private final List<AbstractButtonWidget> linkedButtons = new ArrayList<>(1);
private final Consumer<KeyBindingWidget> onEditingStarted;
private boolean isEditing;
public KeyBindingWidget(final int x, final int y, final int width, final int height, final KeyBinding binding, final Consumer<KeyBindingWidget> onEditingStarted){
super(x, y, width, height, LiteralText.EMPTY, btn -> {});
this.binding = binding;
this.bindingName = new TranslatableText(binding.getTranslationKey());
this.onEditingStarted = onEditingStarted;
updateKeyBindingText();
}
public KeyBindingWidget(final int x, final int y, final int width, final KeyBinding binding, final Consumer<KeyBindingWidget> onEditingStarted){
this(x, y, width, 20, binding, onEditingStarted);
}
public void linkButtonToBoundState(final AbstractButtonWidget button){
linkedButtons.add(button);
button.active = !binding.isUnbound();
}
@Override
protected MutableText getNarrationMessage(){
return binding.isUnbound() ? new TranslatableText("narrator.controls.unbound", bindingName) : new TranslatableText("narrator.controls.bound", bindingName, super.getNarrationMessage());
}
@Override
public void onPress(){
isEditing = true;
onEditingStarted.accept(this);
updateKeyBindingText();
}
public void bindAndStopEditing(final InputUtil.Key key){
binding.setBoundKey(key);
stopEditing();
for(final AbstractButtonWidget button : linkedButtons){
button.active = !binding.isUnbound();
}
}
public void stopEditing(){
isEditing = false;
updateKeyBindingText();
}
public void updateKeyBindingText(){
boolean hasConflict = false;
if (!binding.isUnbound()){
for(final KeyBinding other : MinecraftClient.getInstance().options.keysAll){
if (binding != other && binding.equals(other)){
hasConflict = true;
}
}
}
if (isEditing){
setMessage((new LiteralText("> ")).append(binding.getBoundKeyLocalizedText().shallowCopy().formatted(Formatting.YELLOW)).append(" <").formatted(Formatting.YELLOW));
}
else if (hasConflict){
setMessage(binding.getBoundKeyLocalizedText().shallowCopy().formatted(Formatting.RED));
}
else{
setMessage(binding.isUnbound() ? Text.of("(No Binding)") : binding.getBoundKeyLocalizedText());
}
}
}

View File

@ -0,0 +1,26 @@
package chylex.bettercontrols.gui.elements;
import net.minecraft.text.Text;
import java.util.List;
import java.util.Objects;
public final class Option<T>{
private final T value;
private final Text text;
public Option(final T value, final Text text){
this.value = value;
this.text = text;
}
public T getValue(){
return value;
}
public Text getText(){
return text;
}
public static <T> Option<T> find(final List<Option<T>> options, final T value){
return options.stream().filter(it -> Objects.equals(it.value, value)).findFirst().orElseGet(() -> options.get(0));
}
}

View File

@ -0,0 +1,73 @@
package chylex.bettercontrols.gui.elements;
import chylex.bettercontrols.gui.OptionListWidget.Widget;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.font.TextRenderer;
import net.minecraft.client.gui.DrawableHelper;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.text.OrderedText;
import net.minecraft.text.Text;
import java.util.List;
public final class TextWidget extends DrawableHelper implements Widget{
public static final int LEFT = 0;
public static final int CENTER = 1;
private final Text text;
private int x;
private int y;
private final int width;
private final int height;
private final int align;
public TextWidget(final int x, final int y, final int width, final int height, final Text text, final int align){
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.text = text;
this.align = align;
}
public TextWidget(final int x, final int y, final int width, final Text text, final int align){
this(x, y, width, 20, text, align);
}
public TextWidget(final int x, final int y, final int width, final Text text){
this(x, y, width, 20, text, LEFT);
}
@Override
public int getX(){
return x;
}
@Override
public int getY(){
return y;
}
@Override
public void setX(final int x){
this.x = x;
}
@Override
public void setY(final int y){
this.y = y;
}
@Override
public void render(final MatrixStack matrices, final int mouseX, final int mouseY, final float delta){
final TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer;
final List<OrderedText> lines = textRenderer.wrapLines(text, width);
final int lineHeight = textRenderer.fontHeight + 1;
final int finalX = align == CENTER ? x + (width / 2) - (lines.stream().mapToInt(textRenderer::getWidth).max().orElse(0) / 2) : x;
final int finalY = y + (height / 2) - (lineHeight * lines.size() / 2) + 1;
for(int i = 0; i < lines.size(); i++){
final OrderedText line = lines.get(i);
textRenderer.drawWithShadow(matrices, line, finalX, finalY + (i * lineHeight), (255 << 16) | (255 << 8) | 255);
}
}
}

View File

@ -0,0 +1,33 @@
package chylex.bettercontrols.input;
import net.minecraft.client.options.KeyBinding;
import net.minecraft.client.util.InputUtil.Type;
import org.jetbrains.annotations.Nullable;
public class KeyBindingWithModifier extends KeyBinding{
public static final String CATEGORY = "key.categories.bettercontrols";
@Nullable
private ModifierKey modifier = null;
public KeyBindingWithModifier(final String translationKey){
super(translationKey, Type.KEYSYM, -1, CATEGORY);
}
public void setModifier(final @Nullable ModifierKey modifier){
this.modifier = modifier;
}
public @Nullable ModifierKey getModifier(){
return modifier;
}
@Override
public boolean isPressed(){
return super.isPressed() && (modifier == null || modifier.isPressed());
}
@Override
public boolean wasPressed(){
return super.wasPressed() && (modifier == null || modifier.isPressed());
}
}

View File

@ -0,0 +1,42 @@
package chylex.bettercontrols.input;
import net.minecraft.client.gui.screen.Screen;
public enum ModifierKey{
CONTROL(0){
@Override
public boolean isPressed(){
return Screen.hasControlDown();
}
},
SHIFT(1){
@Override
public boolean isPressed(){
return Screen.hasShiftDown();
}
},
ALT(2){
@Override
public boolean isPressed(){
return Screen.hasAltDown();
}
};
public final int id;
ModifierKey(final int id){
this.id = id;
}
public abstract boolean isPressed();
public static ModifierKey getById(final int id){
switch(id){
case 0: return CONTROL;
case 1: return SHIFT;
case 2: return ALT;
default: return null;
}
}
}

View File

@ -0,0 +1,13 @@
package chylex.bettercontrols.mixin;
import net.minecraft.client.options.KeyBinding;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
import java.util.Map;
@Mixin(KeyBinding.class)
public interface AccessKeyBindingFields{
@Accessor
static Map<String, Integer> getCategoryOrderMap(){
throw new AssertionError();
}
}

View File

@ -0,0 +1,16 @@
package chylex.bettercontrols.mixin;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.client.gui.widget.AbstractButtonWidget;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
import org.spongepowered.asm.mixin.gen.Invoker;
import java.util.List;
@Mixin(Screen.class)
public interface AccessScreenButtons{
@Accessor
List<AbstractButtonWidget> getButtons();
@Invoker
<T extends AbstractButtonWidget> T callAddButton(T button);
}

View File

@ -0,0 +1,34 @@
package chylex.bettercontrols.mixin;
import chylex.bettercontrols.BetterControlsMod;
import chylex.bettercontrols.input.KeyBindingWithModifier;
import net.minecraft.client.options.GameOptions;
import net.minecraft.client.options.KeyBinding;
import org.apache.commons.lang3.ArrayUtils;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Mutable;
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(GameOptions.class)
public abstract class HookLoadGameOptions{
private boolean hasLoaded = false;
@Mutable
@Final
@Shadow
public KeyBinding[] keysAll;
@Inject(method = "load()V", at = @At("HEAD"))
private void load(final CallbackInfo info){
if (hasLoaded){
return;
}
hasLoaded = true;
keysAll = ArrayUtils.addAll(keysAll, BetterControlsMod.config.getAllKeyBindings());
AccessKeyBindingFields.getCategoryOrderMap().put(KeyBindingWithModifier.CATEGORY, Integer.valueOf(Integer.MAX_VALUE));
}
}

View File

@ -0,0 +1,22 @@
package chylex.bettercontrols.mixin;
import chylex.bettercontrols.gui.ControlsScreenPatcher;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.client.gui.screen.options.ControlsOptionsScreen;
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(MinecraftClient.class)
public abstract class HookOpenScreen{
@Inject(method = "openScreen(Lnet/minecraft/client/gui/screen/Screen;)V", at = @At("TAIL"))
private void openScreen(final Screen ignore, final CallbackInfo ci){
final MinecraftClient mc = MinecraftClient.getInstance();
final Screen screen = mc.currentScreen;
if (screen != null && screen.getClass() == ControlsOptionsScreen.class && !Screen.hasAltDown()){
ControlsScreenPatcher.onOpened((ControlsOptionsScreen)screen);
}
}
}

Binary file not shown.

After

(image error) Size: 21 KiB

View File

@ -0,0 +1,3 @@
{
"key.categories.bettercontrols": "Better Controls"
}

View File

@ -0,0 +1,36 @@
{
"schemaVersion": 1,
"id": "bettercontrols",
"version": "${version}",
"name": "Better Controls",
"description": "Adds many powerful key bindings and options to control your movement.\nThe features complement vanilla mechanics without giving unfair advantages, so server use should be fine.\nTo configure the mod, open Options - Controls - Better Controls. If you want to see the vanilla Controls screen, hold Alt while opening it.",
"icon": "assets/bettercontrols/icon.png",
"license": "MPL-2.0",
"authors": [ "chylex" ],
"contact": {
"homepage": "",
"sources": "https://github.com/chylex/Better-Controls",
"issues": "https://github.com/chylex/Better-Controls/issues"
},
"environment": "client",
"entrypoints": {
"client": [ "chylex.bettercontrols.BetterControlsMod" ]
},
"mixins": [{
"config": "mixins.json",
"environment": "client"
}],
"depends": {
"fabricloader": ">=0.7.4",
"minecraft": "1.16.x"
},
"custom": {
"modmenu:clientsideOnly": true
}
}

View File

@ -0,0 +1,15 @@
{
"required": true,
"minVersion": "0.8",
"package": "chylex.bettercontrols.mixin",
"compatibilityLevel": "JAVA_8",
"mixins": [
"AccessKeyBindingFields",
"AccessScreenButtons",
"HookLoadGameOptions",
"HookOpenScreen"
],
"injectors": {
"defaultRequire": 1
}
}