Skip to content

Commit

Permalink
Merge pull request #133 from boholder/1.20.x-camera-look-straight-up-…
Browse files Browse the repository at this point in the history
…down

[1.20] Camera look straight up down FINISH
  • Loading branch information
khanshoaib3 committed Aug 20, 2023
2 parents 4120841 + 0569348 commit eb1b3a3
Show file tree
Hide file tree
Showing 14 changed files with 290 additions and 84 deletions.
5 changes: 5 additions & 0 deletions .github/workflows/auto-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ jobs:
- name: Validate Gradle wrapper
uses: gradle/wrapper-validation-action@064a97fb0b4cef86a65f09898c572382f3af10e0

- name: Run unit test suite
uses: gradle/[email protected]
with:
arguments: :common:test

- name: Build Fabric with Gradle
uses: gradle/[email protected]
with:
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,6 @@ run/

# Ignore Gradle build output directory
build

# Ignore Gragle test report logs
/common/logs/
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,15 @@ public class OtherConfigsMap {
@SerializedName("Debug Mode")
private boolean debugMode;

/**
* The maximum time interval between two keystrokes in multiple click operations like "double-click", in milliseconds.
* According to the UX QA, 500ms is quite a good default time, but I still want to extend it a bit.
* <a href="https://ux.stackexchange.com/questions/40364/what-is-the-expected-timeframe-of-a-double-click"></a>
* <p>
* Can be translated into config item if someone needs it, but not now.
*/
private int multipleClickSpeedInMilliseconds = 750;

public boolean isBiomeIndicatorEnabled() {
return biomeIndicatorEnabled;
}
Expand Down Expand Up @@ -115,6 +124,14 @@ public void setFishingHarvestEnabled(boolean fishingHarvestEnabled) {
this.fishingHarvestEnabled = fishingHarvestEnabled;
}

public int getMultipleClickSpeedInMilliseconds() {
return multipleClickSpeedInMilliseconds;
}

public void setMultipleClickSpeedInMilliseconds(int multipleClickSpeedInMilliseconds) {
this.multipleClickSpeedInMilliseconds = multipleClickSpeedInMilliseconds;
}

public static OtherConfigsMap getDefaultOtherConfigsMap() {
OtherConfigsMap defaultOtherConfigsMap = new OtherConfigsMap();
defaultOtherConfigsMap.setBiomeIndicatorEnabled(true);
Expand All @@ -128,6 +145,7 @@ public static OtherConfigsMap getDefaultOtherConfigsMap() {
defaultOtherConfigsMap.setFishingHarvestEnabled(true);
defaultOtherConfigsMap.setMenuFixEnabled(true);
defaultOtherConfigsMap.setDebugMode(false);
defaultOtherConfigsMap.setMultipleClickSpeedInMilliseconds(750);

return defaultOtherConfigsMap;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@

import com.github.khanshoaib3.minecraft_access.MainClass;
import com.github.khanshoaib3.minecraft_access.config.config_maps.CameraControlsConfigMap;
import com.github.khanshoaib3.minecraft_access.utils.*;
import com.github.khanshoaib3.minecraft_access.utils.KeyBindingsHandler;
import com.github.khanshoaib3.minecraft_access.utils.KeyUtils;
import com.github.khanshoaib3.minecraft_access.utils.PlayerPositionUtils;
import com.github.khanshoaib3.minecraft_access.utils.condition.DoubleClick;
import com.github.khanshoaib3.minecraft_access.utils.condition.Interval;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
Expand All @@ -27,6 +30,8 @@
* 12) Right Alt + Look Left Key or Look West Key (default=keypad 1): Snaps the camera to the west block.<br>
* 13) Center Camera Key (default=keypad 5): Snaps the camera to the closest cardinal direction and center it.<br>
* 14) Left Alt + Center Camera Key : Snaps the camera to the closest opposite cardinal direction and center it.<br>
* 15) Right Alt + double Look Up Key or Look Straight Up Key (default: Keypad 0): Snaps the camera to the look above head direction.<br>
* 16) Right Alt + double Look Down Key or Look Straight Down Key (default: Keypad .): Snaps the camera to the look down at feet direction.
*/
@Environment(EnvType.CLIENT)
public class CameraControls {
Expand All @@ -36,6 +41,16 @@ public class CameraControls {
private float modifiedRotatingDeltaAngle;
private Interval interval;

private static final DoubleClick straightUpDoubleClick;
private static final DoubleClick straightDownDoubleClick;

static {
// config keystroke conditions
KeyBindingsHandler kbh = KeyBindingsHandler.getInstance();
straightUpDoubleClick = new DoubleClick(() -> KeyUtils.isAnyPressed(kbh.cameraControlsUp));
straightDownDoubleClick = new DoubleClick(() -> KeyUtils.isAnyPressed(kbh.cameraControlsDown));
}

public CameraControls() {
loadConfigurations();
}
Expand Down Expand Up @@ -94,9 +109,23 @@ private boolean keyListener() {
boolean isSouthKeyPressed = KeyUtils.isAnyPressed(kbh.cameraControlsSouth)
|| (isDownKeyPressed && isRightAltPressed && !isLeftAltPressed);
boolean isCenterCameraKeyPressed = KeyUtils.isAnyPressed(kbh.cameraControlsCenterCamera);
// TODO Add double click Up/Down, refactor F4 menu
boolean isStraightUpKeyPressed = KeyUtils.isAnyPressed(kbh.cameraControlsStraightUp);
boolean isStraightDownKeyPressed = KeyUtils.isAnyPressed(kbh.cameraControlsStraightDown);

boolean isStraightUpKeyPressed = KeyUtils.isAnyPressed(kbh.cameraControlsStraightUp) || straightUpDoubleClick.canBeTriggered();
boolean isStraightDownKeyPressed = KeyUtils.isAnyPressed(kbh.cameraControlsStraightDown) || straightDownDoubleClick.canBeTriggered();

straightUpDoubleClick.updateStateForNextTick();
straightDownDoubleClick.updateStateForNextTick();

// these two blocks of logic should be ahead of the normal up/down logic
if (isStraightUpKeyPressed) {
rotateCameraTo(CameraDirection.UP);
return true;
}

if (isStraightDownKeyPressed) {
rotateCameraTo(CameraDirection.DOWN);
return true;
}

if (isNorthKeyPressed) {
rotateCameraTo(CameraDirection.NORTH);
Expand Down Expand Up @@ -145,16 +174,6 @@ private boolean keyListener() {
return true;
}

if (isStraightUpKeyPressed) {
rotateCameraTo(CameraDirection.UP);
return true;
}

if (isStraightDownKeyPressed) {
rotateCameraTo(CameraDirection.DOWN);
return true;
}

return false;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ private void initializeInventoryControlsKeybindings() {
* 12) Right Alt + Look Left Key or Look West Key (default=keypad 1): Snaps the camera to the west block.<br>
* 13) Center Camera Key (default=keypad 5): Snaps the camera to the closest cardinal direction and center it.<br>
* 14) Left Alt + Center Camera Key : Snaps the camera to the closest opposite cardinal direction and center it.<br>
* 15) Right Alt + double Look Up Key or Look Straight Up Key (default: Keypad 0): Snaps the camera to the look above head direction.
* 15) Right Alt + double Look Up Key or Look Straight Up Key (default: Keypad 0): Snaps the camera to the look above head direction.<br>
* 16) Right Alt + double Look Down Key or Look Straight Down Key (default: Keypad .): Snaps the camera to the look down at feet direction.
*/
private void initializeCameraControlsKeybindings() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,51 @@

import java.util.function.BooleanSupplier;

public class DoubleClick extends KeystrokeTiming{
public class DoubleClick extends KeystrokeTiming {
/**
* @param condition Expression that checking if the key (combination) is pressed now.
* @param interval The maximum interval between first and second keystroke.
*/
public DoubleClick(BooleanSupplier condition, Interval interval) {
super(condition, interval);
public DoubleClick(BooleanSupplier condition) {
super(condition);
}

/**
* @param condition Expression that checking if the key (combination) is pressed now.
* @param timing When the corresponding logic is triggered.
*/
public DoubleClick(BooleanSupplier condition, TriggeredAt timing) {
super(condition, timing);
}

/**
* @param condition Expression that checking if the key (combination) is pressed now.
* @param timing When the corresponding logic is triggered.
* @param interval The maximum interval between first and second keystroke, default is 750ms.
*/
public DoubleClick(BooleanSupplier condition, TriggeredAt timing, Interval interval) {
super(condition, timing, interval);
}

@Override
public void updateStateForNextTick() {
super.updateStateForNextTick();
hasKeyPressed = isPressing();

boolean waitingTooLong = this.interval.isReady();
if (waitingTooLong || canBeTriggered()) {
this.triggeredCount = 0;
return;
}

// count as one valid keystroke
if (this.timing.happen(this)) {
this.triggeredCount += 1;
this.interval.reset();
}
}

@Override
protected boolean otherTriggerConditions() {
// triggered at the second keystroke
return this.triggeredCount == 1;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@
* An auto-refresh countdown timer for controlling interval execution of features.
*/
public class Interval {
private long lastRunTime;
protected long lastRunTime;
private final long delay;
private boolean isRunning;
private final boolean disabled;

private Interval(long lastRunTime, long delayInNanoTime) {
protected Interval(long lastRunTime, long delayInNanoTime) {
this.lastRunTime = lastRunTime;
this.delay = delayInNanoTime;
this.disabled = delayInNanoTime == 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,23 @@ public class Keystroke {
/**
* Save the state of keystroke at the previous tick.
*/
private boolean hasKeyPressed = false;
protected boolean hasKeyPressed = false;

/**
* Expression that checking if the key (combination) is pressed now.
*/
private final BooleanSupplier condition;
protected final BooleanSupplier condition;

/**
* For checking feature triggering condition,
* like "only-trigger-once-before-release".
*/
private final TriggeredAt timing;
protected final TriggeredAt timing;

/**
* Times that logic has been triggered before reset (when opposite state appears).
*/
private int triggeredCount;
protected int triggeredCount;

/**
* Use this class as a condition checker.
Expand Down Expand Up @@ -70,7 +70,7 @@ public boolean isNotPressing() {
return !isPressing();
}

private boolean hasPressedPreviousTick() {
protected boolean hasPressedPreviousTick() {
return hasKeyPressed;
}

Expand All @@ -83,11 +83,20 @@ public boolean isPressed() {
}

public boolean canBeTriggered() {
boolean happen = this.timing.happen(this);
boolean haveNotTriggered = this.triggeredCount == 0;
return happen && haveNotTriggered;
boolean correctKeystrokeState = this.timing.happen(this);
return correctKeystrokeState && otherTriggerConditions();
}

protected boolean otherTriggerConditions() {
return this.triggeredCount == 0;
}

/**
* When the corresponding logic is triggered.<br>
* --- released (short) ---><br>
* pressing (long) not-pressing (long)<br>
* <--- pressed (short) ---
*/
public enum TriggeredAt {
PRESSING(Keystroke::isPressing, Keystroke::isNotPressing),
NOT_PRESSING(Keystroke::isNotPressing, Keystroke::isPressing),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,40 @@
package com.github.khanshoaib3.minecraft_access.utils.condition;

import com.github.khanshoaib3.minecraft_access.MainClass;

import java.util.function.BooleanSupplier;
import java.util.function.Supplier;

/**
* For checking keystroke conditions that related to time,
* like "interval/rate-limitation-on-feature-execution", "double/triple-click"
*/
public abstract class KeystrokeTiming extends Keystroke {
protected Interval interval;
public static final Supplier<Interval> DEFAULT_INTERVAL = () -> Interval.inMilliseconds(MainClass.config.getConfigMap().getOtherConfigsMap().getMultipleClickSpeedInMilliseconds());

/**
* @param condition Expression that checking if the key (combination) is pressed now.
*/
public KeystrokeTiming(BooleanSupplier condition) {
this(condition, TriggeredAt.PRESSING, DEFAULT_INTERVAL.get());
}

/**
* @param condition Expression that checking if the key (combination) is pressed now.
* @param timing When the corresponding logic is triggered.
*/
public KeystrokeTiming(BooleanSupplier condition, TriggeredAt timing) {
this(condition, timing, DEFAULT_INTERVAL.get());
}

/**
* @param condition Expression that checking if the key (combination) is pressed now.
* @param timing When the corresponding logic is triggered.
* @param interval The interval setting, the meaning is to be determined.
*/
public KeystrokeTiming(BooleanSupplier condition, Interval interval) {
super(condition);
public KeystrokeTiming(BooleanSupplier condition, TriggeredAt timing, Interval interval) {
super(condition, timing);
this.interval = interval;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package com.github.khanshoaib3.minecraft_access.utils.condition;

import com.github.khanshoaib3.minecraft_access.MainClass;
import com.github.khanshoaib3.minecraft_access.config.Config;
import com.github.khanshoaib3.minecraft_access.config.ConfigMap;
import com.github.khanshoaib3.minecraft_access.config.config_maps.OtherConfigsMap;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

class DoubleClickTest {
@BeforeAll
static void beforeAll() {
ConfigMap mockConfigMap = mock(ConfigMap.class);
when(mockConfigMap.getOtherConfigsMap()).thenReturn(OtherConfigsMap.getDefaultOtherConfigsMap());
Config mockConfig = mock(Config.class);
when(mockConfig.getConfigMap()).thenReturn(mockConfigMap);
MainClass.config = mockConfig;
}

@Test
void testCanCountTrigger() {
MockKeystrokeAction m = new MockKeystrokeAction(true);
DoubleClick k = new DoubleClick(m.supplier, Keystroke.TriggeredAt.PRESSING);

k.updateStateForNextTick();
assertThat(k.canBeTriggered()).as("there should be one valid count").isTrue();

k.updateStateForNextTick();
assertThat(k.canBeTriggered()).as("once reaches triggered condition, the counter will be cleaned after update").isFalse();
}

@Test
void testCanCleanStaleCountIfTimeOut() throws InterruptedException {
MockKeystrokeAction m = new MockKeystrokeAction(true);
MockInterval i = new MockInterval(0, 0);
DoubleClick k = new DoubleClick(m.supplier, Keystroke.TriggeredAt.PRESSING, i);

// record first keystroke
k.updateStateForNextTick();
// simulate time passing through the interval
i.setReady(true);
k.updateStateForNextTick();

assertThat(k.canBeTriggered()).as("first count should be cleaned").isFalse();
}

@Test
void testCanTriggerIfProperlyTriggerAgain() {
MockKeystrokeAction m = new MockKeystrokeAction(true);
MockInterval i = new MockInterval(0, 0);
DoubleClick k = new DoubleClick(m.supplier, Keystroke.TriggeredAt.PRESSING, i);

// record first keystroke
k.updateStateForNextTick();
// simulate time passing through the interval
i.setReady(true);
k.updateStateForNextTick();
assertThat(k.canBeTriggered()).as("first count should be cleaned").isFalse();

// third keystroke
i.setReady(false);
k.updateStateForNextTick();
assertThat(k.canBeTriggered()).as("third keystroke should be regarded as first valid keystroke").isTrue();
}
}
Loading

0 comments on commit eb1b3a3

Please sign in to comment.