@@ -1,14 +1,41 @@
|
|||||||
package skybot;
|
package skybot;
|
||||||
|
|
||||||
|
import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.literal;
|
||||||
|
|
||||||
import net.fabricmc.api.ClientModInitializer;
|
import net.fabricmc.api.ClientModInitializer;
|
||||||
|
import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback;
|
||||||
|
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
|
||||||
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents;
|
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents;
|
||||||
import net.minecraft.client.network.ServerInfo;
|
import net.minecraft.client.network.ServerInfo;
|
||||||
|
import skybot.controller.Controller;
|
||||||
|
import skybot.controller.task.HoldKeyTask;
|
||||||
|
import skybot.controller.task.PauseTask;
|
||||||
|
import skybot.controller.task.TaskQueue;
|
||||||
|
import skybot.utilities.ChatMenu;
|
||||||
|
|
||||||
public class SkybotClient implements ClientModInitializer {
|
public class SkybotClient implements ClientModInitializer {
|
||||||
public static final String HYPIXEL_URL = "hypixel.net";
|
public static final String HYPIXEL_URL = "hypixel.net";
|
||||||
|
|
||||||
|
private static final TaskQueue TASK_QUEUE = new TaskQueue();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onInitializeClient() {
|
public void onInitializeClient() {
|
||||||
|
ClientTickEvents.END_CLIENT_TICK.register(client -> {
|
||||||
|
TASK_QUEUE.tick();
|
||||||
|
Controller.tick(client);
|
||||||
|
});
|
||||||
|
|
||||||
|
ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> {
|
||||||
|
dispatcher.register(literal("test").executes(context -> {
|
||||||
|
TASK_QUEUE.clear();
|
||||||
|
TASK_QUEUE.add(new HoldKeyTask(Controller::setForward, 200));
|
||||||
|
TASK_QUEUE.add(new PauseTask(400));
|
||||||
|
TASK_QUEUE.add(new HoldKeyTask(Controller::setRight, 400));
|
||||||
|
ChatMenu.send("running: forward 2s -> pause 4s -> right 4s");
|
||||||
|
return 1;
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
ClientPlayConnectionEvents.JOIN.register((handler, sender, client) -> {
|
ClientPlayConnectionEvents.JOIN.register((handler, sender, client) -> {
|
||||||
client.execute(() -> {
|
client.execute(() -> {
|
||||||
if (client.player == null)
|
if (client.player == null)
|
||||||
@@ -21,6 +48,9 @@ public class SkybotClient implements ClientModInitializer {
|
|||||||
});
|
});
|
||||||
|
|
||||||
ClientPlayConnectionEvents.DISCONNECT.register((handler, client) -> {
|
ClientPlayConnectionEvents.DISCONNECT.register((handler, client) -> {
|
||||||
|
TASK_QUEUE.clear();
|
||||||
|
Controller.releaseAll();
|
||||||
|
|
||||||
ServerInfo server = client.getCurrentServerEntry();
|
ServerInfo server = client.getCurrentServerEntry();
|
||||||
if (server == null || !server.address.contains(HYPIXEL_URL))
|
if (server == null || !server.address.contains(HYPIXEL_URL))
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -0,0 +1,134 @@
|
|||||||
|
package skybot.controller;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Owns simulated input state (movement keys + look direction) and applies it
|
||||||
|
* to the client each tick. Higher-level behavior code should set state here
|
||||||
|
* rather than touching GameOptions/player rotation directly.
|
||||||
|
*/
|
||||||
|
public class Controller {
|
||||||
|
private final static KeyOverride forward = new KeyOverride();
|
||||||
|
private final static KeyOverride back = new KeyOverride();
|
||||||
|
private final static KeyOverride left = new KeyOverride();
|
||||||
|
private final static KeyOverride right = new KeyOverride();
|
||||||
|
private final static KeyOverride jumping = new KeyOverride();
|
||||||
|
private final static KeyOverride sprinting = new KeyOverride();
|
||||||
|
private final static KeyOverride sneaking = new KeyOverride();
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setBack(Boolean pressed) {
|
||||||
|
back.set(pressed);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setLeft(Boolean pressed) {
|
||||||
|
left.set(pressed);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setRight(Boolean pressed) {
|
||||||
|
right.set(pressed);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setJumping(Boolean pressed) {
|
||||||
|
jumping.set(pressed);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setSprinting(Boolean pressed) {
|
||||||
|
sprinting.set(pressed);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setSneaking(Boolean pressed) {
|
||||||
|
sneaking.set(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. */
|
||||||
|
public static void releaseAll() {
|
||||||
|
forward.set(null);
|
||||||
|
back.set(null);
|
||||||
|
left.set(null);
|
||||||
|
right.set(null);
|
||||||
|
jumping.set(null);
|
||||||
|
sprinting.set(null);
|
||||||
|
sneaking.set(null);
|
||||||
|
clearLook();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void tick(MinecraftClient client) {
|
||||||
|
ClientPlayerEntity player = client.player;
|
||||||
|
if (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);
|
||||||
|
|
||||||
|
if (targetYaw != null && targetPitch != null) {
|
||||||
|
applyLook(player);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void applyLook(ClientPlayerEntity player) {
|
||||||
|
double yawDiff = MathHelper.wrapDegrees(targetYaw - player.getYaw());
|
||||||
|
double pitchDiff = targetPitch - player.getPitch();
|
||||||
|
|
||||||
|
double yawStep = MathHelper.clamp(yawDiff, -turnSpeed, turnSpeed);
|
||||||
|
double pitchStep = MathHelper.clamp(pitchDiff, -turnSpeed, turnSpeed);
|
||||||
|
|
||||||
|
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 alone."
|
||||||
|
* When an override is released (goes back to null), forces the key up exactly once
|
||||||
|
* so it doesn't stay latched from the last forced value, then stops touching it.
|
||||||
|
*/
|
||||||
|
private static class KeyOverride {
|
||||||
|
private Boolean state;
|
||||||
|
private boolean controlled = false;
|
||||||
|
|
||||||
|
void set(Boolean pressed) {
|
||||||
|
state = pressed;
|
||||||
|
}
|
||||||
|
|
||||||
|
void apply(KeyBinding binding) {
|
||||||
|
if (state != null) {
|
||||||
|
binding.setPressed(state);
|
||||||
|
controlled = true;
|
||||||
|
} else if (controlled) {
|
||||||
|
binding.setPressed(false);
|
||||||
|
controlled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package skybot.controller.task;
|
||||||
|
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
/** Holds one Controller key override for a fixed duration, then releases it. */
|
||||||
|
public class HoldKeyTask implements Task {
|
||||||
|
private final Consumer<Boolean> setter;
|
||||||
|
private final long durationMillis;
|
||||||
|
private long endTime;
|
||||||
|
|
||||||
|
public HoldKeyTask(Consumer<Boolean> setter, long durationMillis) {
|
||||||
|
this.setter = setter;
|
||||||
|
this.durationMillis = durationMillis;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start() {
|
||||||
|
endTime = System.currentTimeMillis() + durationMillis;
|
||||||
|
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(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package skybot.controller.task;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does nothing for a fixed duration; leaves whatever keys were already released
|
||||||
|
* alone.
|
||||||
|
*/
|
||||||
|
public class PauseTask implements Task {
|
||||||
|
private final long durationMillis;
|
||||||
|
private long endTime;
|
||||||
|
|
||||||
|
public PauseTask(long durationMillis) {
|
||||||
|
this.durationMillis = durationMillis;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start() {
|
||||||
|
endTime = System.currentTimeMillis() + durationMillis;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void tick() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isDone() {
|
||||||
|
if (endTime == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return System.currentTimeMillis() >= endTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package skybot.controller.task;
|
||||||
|
|
||||||
|
public interface Task {
|
||||||
|
/** Called once, the tick this task becomes active. */
|
||||||
|
default void start() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Called every tick while this task is active. */
|
||||||
|
void tick();
|
||||||
|
|
||||||
|
boolean isDone();
|
||||||
|
|
||||||
|
/** Called once, right after isDone() first returns true, before the next task starts. */
|
||||||
|
default void stop() {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
package skybot.controller.task;
|
||||||
|
|
||||||
|
import java.util.ArrayDeque;
|
||||||
|
import java.util.Deque;
|
||||||
|
import java.util.concurrent.ThreadLocalRandom;
|
||||||
|
import skybot.utilities.ChatMenu;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs Tasks one at a time, in order, advancing once the active task reports
|
||||||
|
* done.
|
||||||
|
*/
|
||||||
|
public class TaskQueue {
|
||||||
|
private final Deque<Task> pending = new ArrayDeque<>();
|
||||||
|
private Task current;
|
||||||
|
private boolean currentStarted = false;
|
||||||
|
|
||||||
|
private long delayUntil = 0;
|
||||||
|
|
||||||
|
// Track the exact random delay we rolled for the active task
|
||||||
|
private long rolledDelay = 0;
|
||||||
|
|
||||||
|
public TaskQueue add(Task task) {
|
||||||
|
pending.add(task);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void tick() {
|
||||||
|
if (current != null && current.isDone()) {
|
||||||
|
current.stop();
|
||||||
|
current = null;
|
||||||
|
currentStarted = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current == null) {
|
||||||
|
current = pending.poll();
|
||||||
|
if (current != null) {
|
||||||
|
// Roll the random delay
|
||||||
|
rolledDelay = ThreadLocalRandom.current().nextLong(51, 200);
|
||||||
|
delayUntil = System.currentTimeMillis() + rolledDelay;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current != null && System.currentTimeMillis() >= delayUntil) {
|
||||||
|
if (!currentStarted) {
|
||||||
|
// Calculate the actual elapsed overhead due to Minecraft's 50ms tick rate
|
||||||
|
long actualOverhead = System.currentTimeMillis() - delayUntil;
|
||||||
|
long totalDelta = rolledDelay + actualOverhead;
|
||||||
|
|
||||||
|
ChatMenu.send(
|
||||||
|
String.format("Starting new job... (Target delay: %dms, Actual delta: %dms)", rolledDelay, totalDelta));
|
||||||
|
|
||||||
|
current.start();
|
||||||
|
currentStarted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
current.tick();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDone() {
|
||||||
|
return current == null && pending.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clear() {
|
||||||
|
pending.clear();
|
||||||
|
current = null;
|
||||||
|
delayUntil = 0;
|
||||||
|
rolledDelay = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user