package speedytools.clientside.userinput;
/**
* The purpose of this class is to intercept key presses (especially left and right mouse button clicks) and allow
* greater flexibility in responding to them.
* The class replaces KeyBindings in GameSettings. When interception is on:
* .isPressed() is overridden to return false so that the vanilla code never receives the clicks.
* .pressed is always false.
* The true .isPressed() and .pressed are available using .retrieveClick() and .isKeyDown()
* Usage:
* (1) replace KeyBinding with a newly generated interceptor
* eg
* KeyBindingInterceptor attackButtonInterceptor(GameSettings.keyBindAttack);
* GameSettings.keyBindAttack = attackButtonInterceptor;
* This creates an interceptor linked to the existing keyBindAttack. The original keyBindAttack remains in the
* KeyBinding hashmap and keyBindArray.
* (2) Set the interception mode (eg true = on)
* eg setInterceptionActive(false);
* (3) read the underlying clicks using .retrieveClick() or .isUnderlyingKeyDown();
* (4) when Interceptor is no longer required, call .getOriginalKeyBinding();
* eg GameSettings.keyBindAttack = attackButtonInterceptor.getOriginalKeyBinding();
*
* NOTES -
* (a) the interceptor does not update the .pressed field until .isPressed() is called. The vanilla Minecraft.runTick
* currently always accesses .isPressed() before attempting to read .pressed.
* (b) In the current vanilla code, if the bindings are changed it will affect the original keybinding. The new binding will
* be copied to the interceptor at the first call to .retrieveClick(), .isKeyDown(), or .isPressed().
* (c) Will not work in GUI
*/
import com.google.common.base.Throwables;
import net.minecraftforge.fml.relauncher.ReflectionHelper;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
import net.minecraft.client.settings.KeyBinding;
import java.lang.reflect.Field;
import java.util.List;
@SideOnly(Side.CLIENT)
public class KeyBindingInterceptor extends KeyBinding
{
private static final Field keybindArrayField = ReflectionHelper.findField(KeyBinding.class, "keybindArray", "field_74516_a");
private static final Field keyCodeField = ReflectionHelper.findField(KeyBinding.class, "keyCode", "field_74512_d");
private static final Field pressedField = ReflectionHelper.findField(KeyBinding.class, "pressed", "field_74513_e");
private static final Field pressTimeField = ReflectionHelper.findField(KeyBinding.class, "pressTime", "field_151474_i");
/**
* Create an Interceptor based on an existing binding.
* The initial interception mode is OFF.
* If existingKeyBinding is already a KeyBindingInterceptor, a reinitialised copy will be created but no further effect.
* @param existingKeyBinding - the binding that will be intercepted.
*/
public KeyBindingInterceptor(KeyBinding existingKeyBinding)
{
super(existingKeyBinding.getKeyDescription(), existingKeyBinding.getKeyCode(), existingKeyBinding.getKeyCategory());
try {
// the base constructor automatically adds the class to the keybindArray and hash, which we don't want, so undo it
List reflectkeybindArray = (List) keybindArrayField.get(this);
reflectkeybindArray.remove(this);
pressedField.setBoolean(this, false);
pressTimeField.setInt(this, 0);
// this.pressed = false;
// this.pressTime = 0;
} catch (Exception e) {
throw Throwables.propagate(e);
}
this.interceptionActive = false;
this.interceptedPressTime = 0;
if (existingKeyBinding instanceof KeyBindingInterceptor) {
interceptedKeyBinding = ((KeyBindingInterceptor)existingKeyBinding).getOriginalKeyBinding();
} else {
interceptedKeyBinding = existingKeyBinding;
}
KeyBinding.resetKeyBindingArrayAndHash();
}
public void setInterceptionActive(boolean newMode)
{
if (newMode && !interceptionActive) {
this.interceptedPressTime = 0;
}
interceptionActive = newMode;
}
@Override
public boolean isKeyDown()
{
if (interceptionActive) {
return false;
} else {
return super.isKeyDown();
}
}
public boolean isUnderlyingKeyDown()
{
copyKeyCodeToOriginal();
// return interceptedKeyBinding.pressed;
try {
return pressedField.getBoolean(interceptedKeyBinding);
} catch (Exception e) {
Throwables.propagate(e);
return false;
}
}
/**
*
* @return returns false if interception isn't active. Otherwise, retrieves one of the clicks (true) or false if no clicks left
*/
public boolean retrieveClick()
{
copyKeyCodeToOriginal();
if (interceptionActive) {
copyClickInfoFromOriginal();
if (this.interceptedPressTime == 0) {
return false;
} else {
--this.interceptedPressTime;
return true;
}
} else {
return false;
}
}
/** A better name for this method would be retrieveClick.
* If interception is on, resets .pressed and .pressTime to zero.
* Otherwise, copies these from the intercepted KeyBinding.
* @return If interception is on, this will return false; Otherwise, it will pass on any clicks in the intercepted KeyBinding
*/
@Override
public boolean isPressed()
{
copyKeyCodeToOriginal();
copyClickInfoFromOriginal();
try {
if (interceptionActive) {
pressTimeField.setInt(this, 0);
pressedField.setBoolean(this, false);
// this.pressTime = 0;
// this.pressed = false;
return false;
} else {
// if (this.pressTime == 0) {
if (pressTimeField.getInt(this) == 0) {
return false;
} else {
pressTimeField.setInt(this, pressTimeField.getInt(this) - 1);
// --this.pressTime;
return true;
}
}
} catch (Exception e) {
Throwables.propagate(e);
return false;
}
}
public KeyBinding getOriginalKeyBinding() {
return interceptedKeyBinding;
}
protected KeyBinding interceptedKeyBinding;
private boolean interceptionActive;
private int interceptedPressTime;
protected void copyClickInfoFromOriginal()
{
try {
// this.pressTime += interceptedKeyBinding.pressTime;
// this.interceptedPressTime += interceptedKeyBinding.pressTime;
// interceptedKeyBinding.pressTime = 0;
// this.pressed = interceptedKeyBinding.pressed;
int value = pressTimeField.getInt(this);
value += pressTimeField.getInt(interceptedKeyBinding);
pressTimeField.setInt(this, value);
this.interceptedPressTime += pressTimeField.getInt(interceptedKeyBinding);
pressTimeField.setInt(interceptedKeyBinding, 0);
pressedField.setBoolean(this, pressedField.getBoolean(interceptedKeyBinding));
} catch (Exception e) {
Throwables.propagate(e);
}
}
protected void copyKeyCodeToOriginal()
{
try {
// only copy if necessary
// if (this.keyCode != interceptedKeyBinding.keyCode) {
// this.keyCode = interceptedKeyBinding.keyCode;
if (keyCodeField.getInt(this) != keyCodeField.getInt(interceptedKeyBinding)) {
keyCodeField.setInt(this, keyCodeField.getInt(interceptedKeyBinding));
resetKeyBindingArrayAndHash();
}
} catch (Exception e) {
Throwables.propagate(e);
}
}
}