controller stuff
build / build (push) Failing after 3s

This commit is contained in:
2026-07-03 02:48:02 -07:00
parent d816036a79
commit e629a14b08
6 changed files with 321 additions and 0 deletions
+30
View File
@@ -1,14 +1,41 @@
package skybot;
import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.literal;
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.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 static final String HYPIXEL_URL = "hypixel.net";
private static final TaskQueue TASK_QUEUE = new TaskQueue();
@Override
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) -> {
client.execute(() -> {
if (client.player == null)
@@ -21,6 +48,9 @@ public class SkybotClient implements ClientModInitializer {
});
ClientPlayConnectionEvents.DISCONNECT.register((handler, client) -> {
TASK_QUEUE.clear();
Controller.releaseAll();
ServerInfo server = client.getCurrentServerEntry();
if (server == null || !server.address.contains(HYPIXEL_URL))
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;
}
}