Fix keybinding conflicts on 1.14+ (fixes #268)

This fixes both, replay-exclusive keybindings blocking vanilla ones as well as
vanilla keybindings blocking replay-exclusive ones.

The magic happens in Mixin_ContextualKeyBindings.
This commit is contained in:
Jonas Herzig
2020-07-25 21:34:30 +02:00
parent 063657af4c
commit fd7b6030f9
11 changed files with 105 additions and 22 deletions

View File

@@ -35,7 +35,9 @@ import org.lwjgl.glfw.GLFW;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
public class KeyBindingRegistry extends EventRegistrations {
private static final String CATEGORY = "replaymod.title";
@@ -44,19 +46,20 @@ public class KeyBindingRegistry extends EventRegistrations {
//#endif
private Map<String, KeyBinding> keyBindings = new HashMap<String, KeyBinding>();
private Set<KeyBinding> onlyInReplay = new HashSet<>();
private Multimap<KeyBinding, Runnable> keyBindingHandlers = ArrayListMultimap.create();
private Multimap<KeyBinding, Runnable> repeatedKeyBindingHandlers = ArrayListMultimap.create();
private Multimap<Integer, Runnable> rawHandlers = ArrayListMultimap.create();
public void registerKeyBinding(String name, int keyCode, Runnable whenPressed) {
keyBindingHandlers.put(registerKeyBinding(name, keyCode), whenPressed);
public void registerKeyBinding(String name, int keyCode, Runnable whenPressed, boolean onlyInRepay) {
keyBindingHandlers.put(registerKeyBinding(name, keyCode, onlyInRepay), whenPressed);
}
public void registerRepeatedKeyBinding(String name, int keyCode, Runnable whenPressed) {
repeatedKeyBindingHandlers.put(registerKeyBinding(name, keyCode), whenPressed);
public void registerRepeatedKeyBinding(String name, int keyCode, Runnable whenPressed, boolean onlyInRepay) {
repeatedKeyBindingHandlers.put(registerKeyBinding(name, keyCode, onlyInRepay), whenPressed);
}
private KeyBinding registerKeyBinding(String name, int keyCode) {
private KeyBinding registerKeyBinding(String name, int keyCode, boolean onlyInRepay) {
KeyBinding keyBinding = keyBindings.get(name);
if (keyBinding == null) {
//#if FABRIC>=1
@@ -72,6 +75,11 @@ public class KeyBindingRegistry extends EventRegistrations {
//$$ ClientRegistry.registerKeyBinding(keyBinding);
//#endif
keyBindings.put(name, keyBinding);
if (onlyInRepay) {
this.onlyInReplay.add(keyBinding);
}
} else if (!onlyInRepay) {
this.onlyInReplay.remove(keyBinding);
}
return keyBinding;
}
@@ -84,6 +92,10 @@ public class KeyBindingRegistry extends EventRegistrations {
return Collections.unmodifiableMap(keyBindings);
}
public Set<KeyBinding> getOnlyInReplay() {
return Collections.unmodifiableSet(onlyInReplay);
}
//#if MC>=11400
{ on(KeyBindingEventCallback.EVENT, this::handleKeyBindings); }
{ on(KeyEventCallback.EVENT, (keyCode, scanCode, action, modifiers) -> handleRaw(keyCode, action)); }

View File

@@ -318,7 +318,7 @@ public class ReplayMod implements
public void registerKeyBindings(KeyBindingRegistry registry) {
registry.registerKeyBinding("replaymod.input.settings", 0, () -> {
new GuiReplaySettings(null, settingsRegistry).display();
});
}, false);
}
@Override

View File

@@ -0,0 +1,63 @@
//#if MC>=11400
package com.replaymod.core.mixin;
import com.replaymod.core.ReplayMod;
import com.replaymod.replay.ReplayModReplay;
import net.minecraft.client.options.KeyBinding;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* We have bunch of keybindings which only have an effect while in a replay but heavily conflict with vanilla ones
* otherwise. To work around this, we prevent our keybindings (or conflicting ones) from making it into the keysByCode
* map, depending on the current context.
*/
@Mixin(KeyBinding.class)
public class Mixin_ContextualKeyBindings {
@Shadow @Final private static Map<String, KeyBinding> keysById;
@Unique private static final List<KeyBinding> temporarilyRemoved = new ArrayList<>();
@Inject(method = "updateKeysByCode", at = @At("HEAD"))
private static void preContextualKeyBindings(CallbackInfo ci) {
Set<KeyBinding> onlyInReplay = ReplayMod.instance.getKeyBindingRegistry().getOnlyInReplay();
if (ReplayModReplay.instance.getReplayHandler() != null) {
// In replay, remove any conflicting key bindings, so that ours are guaranteed in
Mixin_ContextualKeyBindings.keysById.values().removeIf(keyBinding -> {
for (KeyBinding exclusiveBinding : onlyInReplay) {
if (keyBinding.equals(exclusiveBinding) && keyBinding != exclusiveBinding) {
temporarilyRemoved.add(keyBinding);
return true;
}
}
return false;
});
} else {
// Not in a replay, remove all replay-exclusive keybindings
for (KeyBinding keyBinding : onlyInReplay) {
if (Mixin_ContextualKeyBindings.keysById.remove(keyBinding.getId()) != null) {
temporarilyRemoved.add(keyBinding);
}
}
}
}
@Inject(method = "updateKeysByCode", at = @At("RETURN"))
private static void postContextualKeyBindings(CallbackInfo ci) {
for (KeyBinding keyBinding : temporarilyRemoved) {
Mixin_ContextualKeyBindings.keysById.put(keyBinding.getId(), keyBinding);
}
temporarilyRemoved.clear();
}
}
//#endif