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:
@@ -35,7 +35,9 @@ import org.lwjgl.glfw.GLFW;
|
|||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
public class KeyBindingRegistry extends EventRegistrations {
|
public class KeyBindingRegistry extends EventRegistrations {
|
||||||
private static final String CATEGORY = "replaymod.title";
|
private static final String CATEGORY = "replaymod.title";
|
||||||
@@ -44,19 +46,20 @@ public class KeyBindingRegistry extends EventRegistrations {
|
|||||||
//#endif
|
//#endif
|
||||||
|
|
||||||
private Map<String, KeyBinding> keyBindings = new HashMap<String, KeyBinding>();
|
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> keyBindingHandlers = ArrayListMultimap.create();
|
||||||
private Multimap<KeyBinding, Runnable> repeatedKeyBindingHandlers = ArrayListMultimap.create();
|
private Multimap<KeyBinding, Runnable> repeatedKeyBindingHandlers = ArrayListMultimap.create();
|
||||||
private Multimap<Integer, Runnable> rawHandlers = ArrayListMultimap.create();
|
private Multimap<Integer, Runnable> rawHandlers = ArrayListMultimap.create();
|
||||||
|
|
||||||
public void registerKeyBinding(String name, int keyCode, Runnable whenPressed) {
|
public void registerKeyBinding(String name, int keyCode, Runnable whenPressed, boolean onlyInRepay) {
|
||||||
keyBindingHandlers.put(registerKeyBinding(name, keyCode), whenPressed);
|
keyBindingHandlers.put(registerKeyBinding(name, keyCode, onlyInRepay), whenPressed);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void registerRepeatedKeyBinding(String name, int keyCode, Runnable whenPressed) {
|
public void registerRepeatedKeyBinding(String name, int keyCode, Runnable whenPressed, boolean onlyInRepay) {
|
||||||
repeatedKeyBindingHandlers.put(registerKeyBinding(name, keyCode), whenPressed);
|
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);
|
KeyBinding keyBinding = keyBindings.get(name);
|
||||||
if (keyBinding == null) {
|
if (keyBinding == null) {
|
||||||
//#if FABRIC>=1
|
//#if FABRIC>=1
|
||||||
@@ -72,6 +75,11 @@ public class KeyBindingRegistry extends EventRegistrations {
|
|||||||
//$$ ClientRegistry.registerKeyBinding(keyBinding);
|
//$$ ClientRegistry.registerKeyBinding(keyBinding);
|
||||||
//#endif
|
//#endif
|
||||||
keyBindings.put(name, keyBinding);
|
keyBindings.put(name, keyBinding);
|
||||||
|
if (onlyInRepay) {
|
||||||
|
this.onlyInReplay.add(keyBinding);
|
||||||
|
}
|
||||||
|
} else if (!onlyInRepay) {
|
||||||
|
this.onlyInReplay.remove(keyBinding);
|
||||||
}
|
}
|
||||||
return keyBinding;
|
return keyBinding;
|
||||||
}
|
}
|
||||||
@@ -84,6 +92,10 @@ public class KeyBindingRegistry extends EventRegistrations {
|
|||||||
return Collections.unmodifiableMap(keyBindings);
|
return Collections.unmodifiableMap(keyBindings);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Set<KeyBinding> getOnlyInReplay() {
|
||||||
|
return Collections.unmodifiableSet(onlyInReplay);
|
||||||
|
}
|
||||||
|
|
||||||
//#if MC>=11400
|
//#if MC>=11400
|
||||||
{ on(KeyBindingEventCallback.EVENT, this::handleKeyBindings); }
|
{ on(KeyBindingEventCallback.EVENT, this::handleKeyBindings); }
|
||||||
{ on(KeyEventCallback.EVENT, (keyCode, scanCode, action, modifiers) -> handleRaw(keyCode, action)); }
|
{ on(KeyEventCallback.EVENT, (keyCode, scanCode, action, modifiers) -> handleRaw(keyCode, action)); }
|
||||||
|
|||||||
@@ -318,7 +318,7 @@ public class ReplayMod implements
|
|||||||
public void registerKeyBindings(KeyBindingRegistry registry) {
|
public void registerKeyBindings(KeyBindingRegistry registry) {
|
||||||
registry.registerKeyBinding("replaymod.input.settings", 0, () -> {
|
registry.registerKeyBinding("replaymod.input.settings", 0, () -> {
|
||||||
new GuiReplaySettings(null, settingsRegistry).display();
|
new GuiReplaySettings(null, settingsRegistry).display();
|
||||||
});
|
}, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -53,7 +53,7 @@ public class FullBrightness extends EventRegistrations implements Extra {
|
|||||||
updateIndicator(replayHandler.getOverlay());
|
updateIndicator(replayHandler.getOverlay());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
}, true);
|
||||||
|
|
||||||
register();
|
register();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ public class PlayerOverview extends EventRegistrations implements Extra {
|
|||||||
new PlayerOverviewGui(PlayerOverview.this, players).display();
|
new PlayerOverviewGui(PlayerOverview.this, players).display();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
}, true);
|
||||||
|
|
||||||
register();
|
register();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ public class ReplayModRecording implements Module {
|
|||||||
core.printInfoToChat("replaymod.chat.addedmarker");
|
core.printInfoToChat("replaymod.chat.addedmarker");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
}, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -237,7 +237,7 @@ public class ReplayHandler {
|
|||||||
//#endif
|
//#endif
|
||||||
overlay.setVisible(false);
|
overlay.setVisible(false);
|
||||||
|
|
||||||
ReplayModReplay.instance.replayHandler = null;
|
ReplayModReplay.instance.forcefullyStopReplay();
|
||||||
|
|
||||||
mc.openScreen(null);
|
mc.openScreen(null);
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import com.replaymod.replaystudio.replay.ReplayFile;
|
|||||||
import com.replaymod.replaystudio.replay.ZipReplayFile;
|
import com.replaymod.replaystudio.replay.ZipReplayFile;
|
||||||
import com.replaymod.replaystudio.studio.ReplayStudio;
|
import com.replaymod.replaystudio.studio.ReplayStudio;
|
||||||
import net.minecraft.client.MinecraftClient;
|
import net.minecraft.client.MinecraftClient;
|
||||||
|
import net.minecraft.client.options.KeyBinding;
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
@@ -46,7 +47,7 @@ public class ReplayModReplay implements Module {
|
|||||||
|
|
||||||
public static Logger LOGGER = LogManager.getLogger();
|
public static Logger LOGGER = LogManager.getLogger();
|
||||||
|
|
||||||
protected ReplayHandler replayHandler;
|
private ReplayHandler replayHandler;
|
||||||
|
|
||||||
public ReplayHandler getReplayHandler() {
|
public ReplayHandler getReplayHandler() {
|
||||||
return replayHandler;
|
return replayHandler;
|
||||||
@@ -78,7 +79,7 @@ public class ReplayModReplay implements Module {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
}, true);
|
||||||
|
|
||||||
registry.registerKeyBinding("replaymod.input.thumbnail", Keyboard.KEY_N, new Runnable() {
|
registry.registerKeyBinding("replaymod.input.thumbnail", Keyboard.KEY_N, new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
@@ -113,7 +114,7 @@ public class ReplayModReplay implements Module {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
}, true);
|
||||||
|
|
||||||
registry.registerKeyBinding("replaymod.input.playpause", Keyboard.KEY_P, new Runnable() {
|
registry.registerKeyBinding("replaymod.input.playpause", Keyboard.KEY_P, new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
@@ -122,7 +123,7 @@ public class ReplayModReplay implements Module {
|
|||||||
replayHandler.getOverlay().playPauseButton.onClick();
|
replayHandler.getOverlay().playPauseButton.onClick();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
}, true);
|
||||||
|
|
||||||
registry.registerKeyBinding("replaymod.input.quickmode", Keyboard.KEY_Q, () -> {
|
registry.registerKeyBinding("replaymod.input.quickmode", Keyboard.KEY_Q, () -> {
|
||||||
if (replayHandler != null) {
|
if (replayHandler != null) {
|
||||||
@@ -133,19 +134,19 @@ public class ReplayModReplay implements Module {
|
|||||||
replayHandler.getReplaySender().setAsyncMode(true);
|
replayHandler.getReplaySender().setAsyncMode(true);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
});
|
}, true);
|
||||||
|
|
||||||
core.getKeyBindingRegistry().registerKeyBinding("replaymod.input.rollclockwise", Keyboard.KEY_L, () -> {
|
core.getKeyBindingRegistry().registerKeyBinding("replaymod.input.rollclockwise", Keyboard.KEY_L, () -> {
|
||||||
// Noop, actual handling logic in CameraEntity#update
|
// Noop, actual handling logic in CameraEntity#update
|
||||||
});
|
}, true);
|
||||||
|
|
||||||
core.getKeyBindingRegistry().registerKeyBinding("replaymod.input.rollcounterclockwise", Keyboard.KEY_J, () -> {
|
core.getKeyBindingRegistry().registerKeyBinding("replaymod.input.rollcounterclockwise", Keyboard.KEY_J, () -> {
|
||||||
// Noop, actual handling logic in CameraEntity#update
|
// Noop, actual handling logic in CameraEntity#update
|
||||||
});
|
}, true);
|
||||||
|
|
||||||
core.getKeyBindingRegistry().registerKeyBinding("replaymod.input.resettilt", Keyboard.KEY_K, () -> {
|
core.getKeyBindingRegistry().registerKeyBinding("replaymod.input.resettilt", Keyboard.KEY_K, () -> {
|
||||||
Optional.ofNullable(replayHandler).map(ReplayHandler::getCameraEntity).ifPresent(c -> c.roll = 0);
|
Optional.ofNullable(replayHandler).map(ReplayHandler::getCameraEntity).ifPresent(c -> c.roll = 0);
|
||||||
});
|
}, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -191,10 +192,16 @@ public class ReplayModReplay implements Module {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
replayHandler = new ReplayHandler(replayFile, true);
|
replayHandler = new ReplayHandler(replayFile, true);
|
||||||
|
//#if MC>=11400
|
||||||
|
KeyBinding.updateKeysByCode(); // see Mixin_ContextualKeyBindings
|
||||||
|
//#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
public void forcefullyStopReplay() {
|
public void forcefullyStopReplay() {
|
||||||
replayHandler = null;
|
replayHandler = null;
|
||||||
|
//#if MC>=11400
|
||||||
|
KeyBinding.updateKeysByCode(); // see Mixin_ContextualKeyBindings
|
||||||
|
//#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReplayMod getCore() {
|
public ReplayMod getCore() {
|
||||||
|
|||||||
@@ -82,13 +82,13 @@ public class ReplayModSimplePathing extends EventRegistrations implements Module
|
|||||||
pathPreview.registerKeyBindings(registry);
|
pathPreview.registerKeyBindings(registry);
|
||||||
core.getKeyBindingRegistry().registerKeyBinding("replaymod.input.keyframerepository", Keyboard.KEY_X, () -> {
|
core.getKeyBindingRegistry().registerKeyBinding("replaymod.input.keyframerepository", Keyboard.KEY_X, () -> {
|
||||||
if (guiPathing != null) guiPathing.keyframeRepoButtonPressed();
|
if (guiPathing != null) guiPathing.keyframeRepoButtonPressed();
|
||||||
});
|
}, true);
|
||||||
core.getKeyBindingRegistry().registerKeyBinding("replaymod.input.clearkeyframes", Keyboard.KEY_C, () -> {
|
core.getKeyBindingRegistry().registerKeyBinding("replaymod.input.clearkeyframes", Keyboard.KEY_C, () -> {
|
||||||
if (guiPathing != null) guiPathing.clearKeyframesButtonPressed();
|
if (guiPathing != null) guiPathing.clearKeyframesButtonPressed();
|
||||||
});
|
}, true);
|
||||||
core.getKeyBindingRegistry().registerRepeatedKeyBinding("replaymod.input.synctimeline", Keyboard.KEY_V, () -> {
|
core.getKeyBindingRegistry().registerRepeatedKeyBinding("replaymod.input.synctimeline", Keyboard.KEY_V, () -> {
|
||||||
if (guiPathing != null) guiPathing.syncTimeButtonPressed();
|
if (guiPathing != null) guiPathing.syncTimeButtonPressed();
|
||||||
});
|
}, true);
|
||||||
core.getKeyBindingRegistry().registerRaw(Keyboard.KEY_DELETE, () -> {
|
core.getKeyBindingRegistry().registerRaw(Keyboard.KEY_DELETE, () -> {
|
||||||
if (guiPathing != null) guiPathing.deleteButtonPressed();
|
if (guiPathing != null) guiPathing.deleteButtonPressed();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ public class PathPreview extends EventRegistrations {
|
|||||||
SettingsRegistry settings = mod.getCore().getSettingsRegistry();
|
SettingsRegistry settings = mod.getCore().getSettingsRegistry();
|
||||||
settings.set(Setting.PATH_PREVIEW, !settings.get(Setting.PATH_PREVIEW));
|
settings.set(Setting.PATH_PREVIEW, !settings.get(Setting.PATH_PREVIEW));
|
||||||
settings.save();
|
settings.save();
|
||||||
});
|
}, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void update() {
|
private void update() {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
"client": [
|
"client": [
|
||||||
//#if MC>=11400
|
//#if MC>=11400
|
||||||
"AbstractButtonWidgetAccessor",
|
"AbstractButtonWidgetAccessor",
|
||||||
|
"Mixin_ContextualKeyBindings",
|
||||||
"MixinGameRenderer",
|
"MixinGameRenderer",
|
||||||
"MixinModResourcePackUtil",
|
"MixinModResourcePackUtil",
|
||||||
//#endif
|
//#endif
|
||||||
|
|||||||
Reference in New Issue
Block a user