Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import dev.isxander.controlify.bindings.BindContext;
import dev.isxander.controlify.bindings.StateAccess;
import dev.isxander.controlify.bindings.input.CompoundInput;
import dev.isxander.controlify.bindings.input.EmptyInput;
import dev.isxander.controlify.bindings.input.Input;
import dev.isxander.controlify.bindings.output.AnalogueOutput;
Expand Down Expand Up @@ -100,6 +101,13 @@ default Component inputIcon() {
*/
void fakePress();

/**
* Prevents this binding from being used. Used every state update where the bind is meant to be suppressed.
* This is used to prevent individual bindings from being activated when a {@link CompoundInput} is being used.
* Does not prevent {@link #fakePress()} from being used.
*/
void limitActivity();

/**
* Set the input that this binding is bound to.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,12 @@ public void fakePress() {
fakePressState = 0;
}

@Override
public void limitActivity() {
if (fakePressState == -1)
fakePressState = 3;
}

@Override
public void setBoundInput(Input input) {
if (inputComponent != null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package dev.isxander.controlify.bindings.input;

import com.mojang.serialization.Codec;
import com.mojang.serialization.MapCodec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import dev.isxander.controlify.controller.input.ControllerStateView;
import net.minecraft.resources.Identifier;

import java.util.List;

public record CompoundInput(List<Input> inputs) implements Input {
public static final String INPUT_ID = "compound";

public static final MapCodec<CompoundInput> CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group(
Codec.lazyInitialized(()-> Input.CODEC).listOf().fieldOf(INPUT_ID).forGetter(CompoundInput::inputs)
).apply(instance, CompoundInput::new));


@Override
public float state(ControllerStateView state) {
for (Input input : inputs) {
if (input.state(state) == 0) return 0;
}
return 1;
}

@Override
public List<Identifier> getRelevantInputs() {
return inputs.stream().flatMap(input -> input.getRelevantInputs().stream()).toList();
}

@Override
public InputType<?> type() {
return InputType.COMPOUND;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,11 @@ public record InputType<T extends Input>(String id, MapCodec<T> codec) implement
public static final InputType<ButtonInput> BUTTON = new InputType<>(ButtonInput.INPUT_ID, ButtonInput.CODEC);
public static final InputType<AxisInput> AXIS = new InputType<>(AxisInput.INPUT_ID, AxisInput.CODEC);
public static final InputType<HatInput> HAT = new InputType<>(HatInput.INPUT_ID, HatInput.CODEC);
public static final InputType<CompoundInput> COMPOUND = new InputType<CompoundInput>(CompoundInput.INPUT_ID, CompoundInput.CODEC);
public static final InputType<EmptyInput> EMPTY = new InputType<>(EmptyInput.INPUT_ID, EmptyInput.CODEC);

public static final InputType<?>[] TYPES = {
InputType.BUTTON, InputType.AXIS, InputType.HAT, InputType.EMPTY
InputType.BUTTON, InputType.AXIS, InputType.HAT, InputType.COMPOUND, InputType.EMPTY
};

public static <T extends StringRepresentable, E> MapCodec<E> createCodec(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import dev.isxander.controlify.Controlify;
import dev.isxander.controlify.bindings.ControlifyBindApiImpl;
import dev.isxander.controlify.api.bind.InputBinding;
import dev.isxander.controlify.bindings.input.InputType;
import dev.isxander.controlify.config.settings.device.DeviceSettings;
import dev.isxander.controlify.config.settings.profile.InputSettings;
import dev.isxander.controlify.controller.*;
Expand Down Expand Up @@ -84,7 +85,24 @@ public void pushState(ControllerState state) {
this.stateNow = state;
this.updateDeadzoneView();

// Get all the CompoundInputs from the current inputs.
List<InputBinding> sortedBindings = this.inputBindings.values().stream()
.filter(object -> object.boundInput().type() == InputType.COMPOUND)
.toList();
List<Identifier> boundIdentifiers = new ArrayList<>();
// For all the active CompoundInputs, get a list of the underlying active inputs.
sortedBindings.forEach(inputBinding -> {
if (inputBinding.boundInput().state(this.deadzoneStateNow) == 1)
boundIdentifiers.addAll(inputBinding.boundInput().getRelevantInputs());
});
Set<Identifier> boundIdentifierSet = new HashSet<>(boundIdentifiers);
// Prevent those active inputs from being used in a non-compound bind.
for (InputBinding binding : this.inputBindings.values()) {
if (binding.boundInput().type() != InputType.COMPOUND) {
if (boundIdentifierSet.containsAll(binding.boundInput().getRelevantInputs())) {
binding.limitActivity();
}
}
binding.pushState(this.deadzoneStateNow);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,15 @@
import dev.isxander.yacl3.gui.YACLScreen;
import dev.isxander.yacl3.gui.controllers.ControllerWidget;
import net.minecraft.ChatFormatting;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphicsExtractor;
import net.minecraft.client.input.KeyEvent;
import net.minecraft.client.input.MouseButtonEvent;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.Identifier;
import org.lwjgl.glfw.GLFW;

import java.util.Optional;
import java.util.ArrayList;
import java.util.List;

public class BindController implements Controller<Input> {
private final Option<Input> option;
Expand Down Expand Up @@ -106,7 +106,7 @@ public boolean mouseClicked(MouseButtonEvent mouseButtonEvent, boolean doubleCli

private void openConsumerScreen() {
awaitingControllerInput = true;
MinecraftUtil.setScreen(new BindConsumerScreen(this::getPressedBind, control.option(), this, MinecraftUtil.getScreen()));
MinecraftUtil.setScreen(new BindConsumerScreen(this::getPressedBinds, control.option(), this, MinecraftUtil.getScreen()));
}

@Override
Expand Down Expand Up @@ -141,31 +141,37 @@ protected int getValueColor() {
return control.conflicting ? 0xFFFF5555 : super.getValueColor();
}

public Optional<Input> getPressedBind() {
public List<Input> getPressedBinds() {
InputComponent input = control.controller.input().orElseThrow();
ControllerStateView state = input.stateNow();
ControllerStateView prevState = input.stateThen();
List<Input> pressedBinds = new ArrayList<>();

for (Identifier button : state.getButtons()) {
if (state.isButtonDown(button) && !prevState.isButtonDown(button)) {
return Optional.of(new ButtonInput(button));
pressedBinds.add(new ButtonInput(button));
} else if (!state.isButtonDown(button) && prevState.isButtonDown(button)) {
return List.of(EmptyInput.INSTANCE);
}
}

for (Identifier axis : state.getAxes()) {
if (state.getAxisState(axis) > 0.5f && prevState.getAxisState(axis) <= 0.5f) {
return Optional.of(new AxisInput(axis));
pressedBinds.add(new AxisInput(axis));
} else if (state.getAxisState(axis) <= 0.5f && prevState.getAxisState(axis) > 0.5f) {
return List.of(EmptyInput.INSTANCE);
}
}

for (Identifier hat : state.getHats()) {
HatState hatState = state.getHatState(hat);
if (hatState != HatState.CENTERED && prevState.getHatState(hat) == HatState.CENTERED) {
return Optional.of(new HatInput(hat, hatState));
pressedBinds.add(new HatInput(hat, hatState));
} else if (hatState == HatState.CENTERED && prevState.getHatState(hat) != HatState.CENTERED) {
return List.of(EmptyInput.INSTANCE);
}
}

return Optional.empty();
return pressedBinds;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package dev.isxander.controlify.gui.screen;

import dev.isxander.controlify.bindings.input.CompoundInput;
import dev.isxander.controlify.bindings.input.EmptyInput;
import dev.isxander.controlify.bindings.input.Input;
import dev.isxander.controlify.bindings.input.InputType;
import dev.isxander.controlify.controller.ControllerEntity;
import dev.isxander.controlify.gui.controllers.BindController;
import dev.isxander.controlify.screenop.ScreenProcessor;
Expand All @@ -15,14 +18,16 @@
import net.minecraft.network.chat.Component;
import org.jspecify.annotations.NonNull;

import java.util.Optional;
import java.util.ArrayList;
import java.util.List;

public class BindConsumerScreen extends Screen implements ScreenProcessorProvider {
private final BindConsumer bindConsumer;
private final Option<Input> option;
private final Screen backgroundScreen;
private final BindController.BindControllerElement widgetToFocus;
private final ScreenProcessorImpl screenProcessor = new ScreenProcessorImpl(this);
private final List<Input> currentInputs = new ArrayList<>();

private int ticksTillClose;
private int ticksTillInput;
Expand Down Expand Up @@ -77,11 +82,26 @@ public void tick() {
}

// tick runs after all controller input ticks

Optional<Input> pressedBind = bindConsumer.getPressedBind();
if (pressedBind.isPresent()) {
option.requestSet(pressedBind.get());
returnToBackground();
List<Input> pressedBinds = bindConsumer.getPressedBinds();
if (!pressedBinds.isEmpty()) {
if (pressedBinds.getFirst().type() == InputType.EMPTY) { // Create bind if input released
if (currentInputs.size() == 1) {
option.requestSet(currentInputs.getFirst());
} else if (currentInputs.size() > 1) {
option.requestSet(new CompoundInput(currentInputs));
} else {
option.requestSet(EmptyInput.INSTANCE);
}
returnToBackground();
} else {
// Input validation
// This is done because sometimes it's possible to input a button twice.
for (Input input : pressedBinds) {
if (currentInputs.stream().noneMatch(storedInput -> storedInput.getRelevantInputs().containsAll(input.getRelevantInputs()))) {
currentInputs.add(input);
}
}
}
}
}

Expand Down Expand Up @@ -135,7 +155,10 @@ public ScreenProcessor<?> screenProcessor() {
}

public interface BindConsumer {
Optional<Input> getPressedBind();
/**
* @return A list of all newly pressed binds during this check. Or a list containing only {@link EmptyInput} to indicate that the user has released an input.
*/
List<Input> getPressedBinds();
}

private static class ScreenProcessorImpl extends ScreenProcessor<BindConsumerScreen> {
Expand Down