@@ -71,15 +71,14 @@ public class SkybotClient implements ClientModInitializer {
|
||||
|
||||
TASK_QUEUE.tick();
|
||||
|
||||
float delta = client.getRenderTickCounter().getTickProgress(false);
|
||||
|
||||
Controller.tickLook(client, delta);
|
||||
// float delta = client.getRenderTickCounter().getTickProgress(false);
|
||||
// Controller.tickLook(client, delta);
|
||||
}
|
||||
});
|
||||
|
||||
ClientTickEvents.END_CLIENT_TICK.register(client -> {
|
||||
if (client.world != null && !client.isPaused()) {
|
||||
Controller.tickInput(client);
|
||||
Controller.tick(client);
|
||||
|
||||
while (STOP_KEY.wasPressed()) {
|
||||
if (botEnabled) {
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
package skybot.controller;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.network.ClientPlayerEntity;
|
||||
import net.minecraft.client.option.GameOptions;
|
||||
import net.minecraft.client.option.KeyBinding;
|
||||
import net.minecraft.util.math.MathHelper;
|
||||
import net.minecraft.client.util.InputUtil;
|
||||
|
||||
/**
|
||||
* Owns simulated input state (movement keys + look direction) and applies it
|
||||
@@ -23,9 +26,14 @@ public class Controller {
|
||||
private final static KeyOverride attack = new KeyOverride(); // Left click
|
||||
private final static KeyOverride use = new KeyOverride(); // Right click
|
||||
|
||||
private static Double targetYaw = null;
|
||||
private static Double targetPitch = null;
|
||||
private static double turnSpeed = 10.0; // max degrees per tick
|
||||
// Overrides for arbitrary physical keys, keyed by whatever KeyBinding(s) the
|
||||
// player currently has bound to that key (same dispatch GLFW's real key
|
||||
// callback uses), so any bindable key works without a dedicated field.
|
||||
private final static Map<InputUtil.Key, KeyOverride> keyOverrides = new HashMap<>();
|
||||
|
||||
// private static Double targetYaw = null;
|
||||
// private static Double targetPitch = null;
|
||||
// private static double turnSpeed = 10.0; // max degrees per tick
|
||||
|
||||
public static void setForward(Boolean pressed) {
|
||||
forward.set(pressed);
|
||||
@@ -63,20 +71,44 @@ public class Controller {
|
||||
use.set(pressed);
|
||||
}
|
||||
|
||||
public static void lookAt(double yaw, double pitch) {
|
||||
targetYaw = yaw;
|
||||
targetPitch = MathHelper.clamp(pitch, -90.0, 90.0);
|
||||
/**
|
||||
* Overrides an arbitrary physical key, e.g.
|
||||
* Controller.setKey(InputUtil.GLFW_KEY_KP_7, true).
|
||||
*/
|
||||
public static void setKey(InputUtil.Key key, Boolean pressed) {
|
||||
keyOverrides.computeIfAbsent(key, k -> new KeyOverride()).set(pressed);
|
||||
}
|
||||
|
||||
public static void setTurnSpeed(double degreesPerTick) {
|
||||
turnSpeed = degreesPerTick;
|
||||
/**
|
||||
* Overrides an arbitrary physical key by its GLFW key code, e.g.
|
||||
* InputUtil.GLFW_KEY_KP_7.
|
||||
*/
|
||||
public static void setKey(int glfwKeyCode, Boolean pressed) {
|
||||
setKey(InputUtil.Type.KEYSYM.createFromCode(glfwKeyCode), pressed);
|
||||
}
|
||||
|
||||
public static void clearLook() {
|
||||
targetYaw = null;
|
||||
targetPitch = null;
|
||||
/**
|
||||
* A setter bound to one physical key, e.g.
|
||||
* new ClickButtonTask(Controller.key(InputUtil.GLFW_KEY_KP_7)).
|
||||
*/
|
||||
public static Consumer<Boolean> key(int glfwKeyCode) {
|
||||
return pressed -> setKey(glfwKeyCode, pressed);
|
||||
}
|
||||
|
||||
// public static void lookAt(double yaw, double pitch) {
|
||||
// targetYaw = yaw;
|
||||
// targetPitch = MathHelper.clamp(pitch, -90.0, 90.0);
|
||||
// }
|
||||
|
||||
// public static void setTurnSpeed(double degreesPerTick) {
|
||||
// turnSpeed = degreesPerTick;
|
||||
// }
|
||||
|
||||
// public static void clearLook() {
|
||||
// targetYaw = null;
|
||||
// targetPitch = null;
|
||||
// }
|
||||
|
||||
/**
|
||||
* Clears every override, handing all input back to the player. Call on
|
||||
* disconnect.
|
||||
@@ -91,28 +123,45 @@ public class Controller {
|
||||
sneaking.set(null);
|
||||
attack.set(null);
|
||||
use.set(null);
|
||||
clearLook();
|
||||
|
||||
for (InputUtil.Key key : keyOverrides.keySet()) {
|
||||
KeyBinding.setKeyPressed(key, false);
|
||||
}
|
||||
keyOverrides.clear();
|
||||
// clearLook();
|
||||
}
|
||||
|
||||
/**
|
||||
* Run this inside ClientTickEvents.END_CLIENT_TICK (20Hz).
|
||||
* Handles keyboard state overrides aligned with game engine updates.
|
||||
*/
|
||||
public static void tickInput(MinecraftClient client) {
|
||||
public static void tick(MinecraftClient client) {
|
||||
if (client.player == null)
|
||||
return;
|
||||
|
||||
GameOptions options = client.options;
|
||||
forward.apply(options.forwardKey);
|
||||
back.apply(options.backKey);
|
||||
left.apply(options.leftKey);
|
||||
right.apply(options.rightKey);
|
||||
jumping.apply(options.jumpKey);
|
||||
sprinting.apply(options.sprintKey);
|
||||
sneaking.apply(options.sneakKey);
|
||||
forward.apply(options.forwardKey::setPressed);
|
||||
back.apply(options.backKey::setPressed);
|
||||
left.apply(options.leftKey::setPressed);
|
||||
right.apply(options.rightKey::setPressed);
|
||||
jumping.apply(options.jumpKey::setPressed);
|
||||
sprinting.apply(options.sprintKey::setPressed);
|
||||
sneaking.apply(options.sneakKey::setPressed);
|
||||
|
||||
attack.apply(options.attackKey);
|
||||
use.apply(options.useKey);
|
||||
attack.apply(options.attackKey::setPressed);
|
||||
use.apply(options.useKey::setPressed);
|
||||
|
||||
for (Iterator<Map.Entry<InputUtil.Key, KeyOverride>> it = keyOverrides.entrySet().iterator(); it
|
||||
.hasNext();) {
|
||||
Map.Entry<InputUtil.Key, KeyOverride> entry = it.next();
|
||||
InputUtil.Key key = entry.getKey();
|
||||
KeyOverride override = entry.getValue();
|
||||
|
||||
override.apply(pressed -> KeyBinding.setKeyPressed(key, pressed));
|
||||
if (override.isIdle()) {
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -122,30 +171,34 @@ public class Controller {
|
||||
* @param tickDelta The fractional time elapsed since the last game tick (0.0 -
|
||||
* 1.0)
|
||||
*/
|
||||
public static void tickLook(MinecraftClient client, float tickDelta) {
|
||||
ClientPlayerEntity player = client.player;
|
||||
if (player == null || targetYaw == null || targetPitch == null)
|
||||
return;
|
||||
// public static void tickLook(MinecraftClient client, float tickDelta) {
|
||||
// ClientPlayerEntity player = client.player;
|
||||
// if (player == null || targetYaw == null || targetPitch == null)
|
||||
// return;
|
||||
|
||||
double yawDiff = MathHelper.wrapDegrees(targetYaw - player.getYaw());
|
||||
double pitchDiff = targetPitch - player.getPitch();
|
||||
// double yawDiff = MathHelper.wrapDegrees(targetYaw - player.getYaw());
|
||||
// double pitchDiff = targetPitch - player.getPitch();
|
||||
|
||||
// Scale turnSpeed using tickDelta so aiming remains consistent regardless of
|
||||
// FPS
|
||||
double scaledTurnSpeed = turnSpeed * tickDelta;
|
||||
// // Scale turnSpeed using tickDelta so aiming remains consistent regardless of
|
||||
// // FPS
|
||||
// double scaledTurnSpeed = turnSpeed * tickDelta;
|
||||
|
||||
// Fallback case: if tickDelta is 0 (e.g., game paused or frame drop), fallback
|
||||
// to a tiny step
|
||||
if (scaledTurnSpeed <= 0) {
|
||||
scaledTurnSpeed = turnSpeed * 0.1;
|
||||
}
|
||||
// // Fallback case: if tickDelta is 0 (e.g., game paused or frame drop),
|
||||
// fallback
|
||||
// // to a tiny step
|
||||
// if (scaledTurnSpeed <= 0) {
|
||||
// scaledTurnSpeed = turnSpeed * 0.1;
|
||||
// }
|
||||
|
||||
double yawStep = MathHelper.clamp(yawDiff, -scaledTurnSpeed, scaledTurnSpeed);
|
||||
double pitchStep = MathHelper.clamp(pitchDiff, -scaledTurnSpeed, scaledTurnSpeed);
|
||||
// double yawStep = MathHelper.clamp(yawDiff, -scaledTurnSpeed,
|
||||
// scaledTurnSpeed);
|
||||
// double pitchStep = MathHelper.clamp(pitchDiff, -scaledTurnSpeed,
|
||||
// scaledTurnSpeed);
|
||||
|
||||
player.setYaw((float) (player.getYaw() + yawStep));
|
||||
player.setPitch((float) MathHelper.clamp(player.getPitch() + pitchStep, -90.0, 90.0));
|
||||
}
|
||||
// player.setYaw((float) (player.getYaw() + yawStep));
|
||||
// player.setPitch((float) MathHelper.clamp(player.getPitch() + pitchStep,
|
||||
// -90.0, 90.0));
|
||||
// }
|
||||
|
||||
/**
|
||||
* Tracks one key's override state. null means "no override, leave real input
|
||||
@@ -163,14 +216,19 @@ public class Controller {
|
||||
state = pressed;
|
||||
}
|
||||
|
||||
void apply(KeyBinding binding) {
|
||||
void apply(Consumer<Boolean> sink) {
|
||||
if (state != null) {
|
||||
binding.setPressed(state);
|
||||
sink.accept(state);
|
||||
controlled = true;
|
||||
} else if (controlled) {
|
||||
binding.setPressed(false);
|
||||
sink.accept(false);
|
||||
controlled = false;
|
||||
}
|
||||
}
|
||||
|
||||
/** True once released and the forced-up frame has already been sent. */
|
||||
boolean isIdle() {
|
||||
return state == null && !controlled;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package skybot.controller.task;
|
||||
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/** Clicks a button (press then release) held for a log-normally distributed duration. */
|
||||
public class ClickButtonTask implements Task {
|
||||
private final Consumer<Boolean> setter;
|
||||
private long endTime;
|
||||
|
||||
public ClickButtonTask(Consumer<Boolean> setter) {
|
||||
this.setter = setter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
endTime = System.currentTimeMillis() + sampleLogNormalDistribution();
|
||||
setter.accept(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDone() {
|
||||
if (endTime == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return System.currentTimeMillis() >= endTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
setter.accept(false);
|
||||
}
|
||||
|
||||
private long sampleLogNormalDistribution() {
|
||||
long physicalLimitShift = 30;
|
||||
|
||||
// Adjust mu so our median tail addition is roughly 50ms
|
||||
// (30ms baseline + 50ms average tail = ~80ms human click hold time)
|
||||
double mu = Math.log(50);
|
||||
double sigma = 0.35; // Slightly wider variance for natural pacing inconsistencies
|
||||
|
||||
long randomTail = (long) Math.exp(ThreadLocalRandom.current().nextGaussian() * sigma + mu);
|
||||
|
||||
return physicalLimitShift + randomTail;
|
||||
}
|
||||
}
|
||||
@@ -13,4 +13,9 @@ public interface Task {
|
||||
/** Called once, right after isDone() first returns true, before the next task starts. */
|
||||
default void stop() {
|
||||
}
|
||||
|
||||
/** Short name for this task, used for logging (e.g. via ChatMenu). */
|
||||
default String name() {
|
||||
return getClass().getSimpleName();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,9 @@ public class TaskQueue {
|
||||
// Track the exact random delay we rolled for the active task
|
||||
private long rolledDelay = 0;
|
||||
|
||||
// Track when the active task actually started, to report how long it ran
|
||||
private long taskStartTime = 0;
|
||||
|
||||
public TaskQueue add(Task task) {
|
||||
pending.add(task);
|
||||
return this;
|
||||
@@ -27,6 +30,10 @@ public class TaskQueue {
|
||||
public void tick() {
|
||||
if (current != null && current.isDone()) {
|
||||
current.stop();
|
||||
if (currentStarted) {
|
||||
ChatMenu.send(
|
||||
String.format("%s finished in %dms", current.name(), System.currentTimeMillis() - taskStartTime));
|
||||
}
|
||||
current = null;
|
||||
currentStarted = false;
|
||||
}
|
||||
@@ -46,10 +53,11 @@ public class TaskQueue {
|
||||
long totalDelta = rolledDelay + actualOverhead;
|
||||
|
||||
ChatMenu.send(
|
||||
String.format("Starting new job... (delayUntil: %dms)", totalDelta));
|
||||
String.format("Starting %s... (delayUntil: %dms)", current.name(), totalDelta));
|
||||
|
||||
current.start();
|
||||
currentStarted = true;
|
||||
taskStartTime = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
current.tick();
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package skybot.farmers;
|
||||
|
||||
import net.minecraft.client.util.InputUtil;
|
||||
import skybot.controller.Controller;
|
||||
import skybot.controller.task.ClickButtonTask;
|
||||
import skybot.controller.task.HoldKeyTask;
|
||||
import skybot.controller.task.PauseTask;
|
||||
import skybot.controller.task.PressButtonTask;
|
||||
@@ -13,29 +15,40 @@ public class CarrotFarmer implements Farmer {
|
||||
public void enqueue(TaskQueue queue) {
|
||||
queue.clear();
|
||||
|
||||
queue.add(new PressButtonTask(Controller::setLeftClick));
|
||||
queue.add(new HoldKeyTask(Controller::setLeft, 119555));
|
||||
queue.add(new ReleaseButtonTask(Controller::setLeftClick));
|
||||
queue.add(new PauseTask(2000));
|
||||
queue.add(new PauseTask(1998));
|
||||
queue.add(new ClickButtonTask(Controller.key(InputUtil.GLFW_KEY_KP_7)));
|
||||
|
||||
queue.add(new PressButtonTask(Controller::setLeftClick));
|
||||
queue.add(new HoldKeyTask(Controller::setRight, 119202));
|
||||
queue.add(new ReleaseButtonTask(Controller::setLeftClick));
|
||||
queue.add(new PauseTask(2000));
|
||||
int EPOCHS = 2;
|
||||
|
||||
queue.add(new PressButtonTask(Controller::setLeftClick));
|
||||
queue.add(new HoldKeyTask(Controller::setLeft, 112023));
|
||||
queue.add(new ReleaseButtonTask(Controller::setLeftClick));
|
||||
queue.add(new PauseTask(2000));
|
||||
for (int i = 0; i < EPOCHS; i++) {
|
||||
queue.add(new PauseTask(2021));
|
||||
|
||||
queue.add(new PressButtonTask(Controller::setLeftClick));
|
||||
queue.add(new HoldKeyTask(Controller::setRight, 119355));
|
||||
queue.add(new ReleaseButtonTask(Controller::setLeftClick));
|
||||
queue.add(new PauseTask(2000));
|
||||
queue.add(new PressButtonTask(Controller::setLeftClick));
|
||||
queue.add(new HoldKeyTask(Controller::setLeft, 119555));
|
||||
queue.add(new ReleaseButtonTask(Controller::setLeftClick));
|
||||
queue.add(new PauseTask(2015));
|
||||
|
||||
queue.add(new PressButtonTask(Controller::setLeftClick));
|
||||
queue.add(new HoldKeyTask(Controller::setLeft, 120155));
|
||||
queue.add(new ReleaseButtonTask(Controller::setLeftClick));
|
||||
queue.add(new PauseTask(2000));
|
||||
queue.add(new PressButtonTask(Controller::setLeftClick));
|
||||
queue.add(new HoldKeyTask(Controller::setRight, 119402));
|
||||
queue.add(new ReleaseButtonTask(Controller::setLeftClick));
|
||||
queue.add(new PauseTask(1998));
|
||||
|
||||
queue.add(new PressButtonTask(Controller::setLeftClick));
|
||||
queue.add(new HoldKeyTask(Controller::setLeft, 119223));
|
||||
queue.add(new ReleaseButtonTask(Controller::setLeftClick));
|
||||
queue.add(new PauseTask(2203));
|
||||
|
||||
queue.add(new PressButtonTask(Controller::setLeftClick));
|
||||
queue.add(new HoldKeyTask(Controller::setRight, 119198));
|
||||
queue.add(new ReleaseButtonTask(Controller::setLeftClick));
|
||||
queue.add(new PauseTask(1998));
|
||||
|
||||
queue.add(new PressButtonTask(Controller::setLeftClick));
|
||||
queue.add(new HoldKeyTask(Controller::setLeft, 120155));
|
||||
queue.add(new ReleaseButtonTask(Controller::setLeftClick));
|
||||
queue.add(new PauseTask(2126));
|
||||
|
||||
queue.add(new ClickButtonTask(Controller.key(InputUtil.GLFW_KEY_KP_7)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
package skybot.farmers;
|
||||
|
||||
import net.minecraft.client.util.InputUtil;
|
||||
import skybot.controller.Controller;
|
||||
import skybot.controller.task.ClickButtonTask;
|
||||
import skybot.controller.task.PauseTask;
|
||||
import skybot.controller.task.PressButtonTask;
|
||||
import skybot.controller.task.TaskQueue;
|
||||
|
||||
public class TestFarmer implements Farmer {
|
||||
@@ -10,7 +11,8 @@ public class TestFarmer implements Farmer {
|
||||
@Override
|
||||
public void enqueue(TaskQueue queue) {
|
||||
queue.clear();
|
||||
queue.add(new PressButtonTask(Controller::setLeftClick));
|
||||
queue.add(new PauseTask(5000));
|
||||
queue.add(new ClickButtonTask(Controller.key(InputUtil.GLFW_KEY_D)));
|
||||
queue.add(new PauseTask(2000));
|
||||
queue.add(new ClickButtonTask(Controller.key(InputUtil.GLFW_KEY_D)));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user