Compare commits

..

9 Commits

Author SHA1 Message Date
burkkyy 990eb72753 dont disconnect
build / build (push) Successful in 2m8s
2026-07-04 21:58:03 -07:00
burkkyy 89a49e2d58 disconnect if healthcheck fails
build / build (push) Successful in 1m53s
2026-07-04 18:22:56 -07:00
burkkyy 5c94367584 healthcheck func impl 2026-07-04 18:12:16 -07:00
burkkyy b9869c57f2 Adding netherwart and wheat farmer
build / build (push) Successful in 2m1s
2026-07-04 16:50:02 -07:00
burkkyy 6a6b92a549 upload artifact version downgrade for gitea
build / build (push) Successful in 2m10s
2026-07-04 15:20:18 -07:00
burkkyy 8637b89668 fixing build workflow
build / build (push) Failing after 4m40s
2026-07-04 15:00:25 -07:00
burkkyy 431aae4f62 Moved task out of controller
build / build (push) Failing after 2s
2026-07-04 05:46:58 -07:00
burkkyy 2fba8b6299 adding potato farmer
build / build (push) Failing after 2s
2026-07-04 05:44:22 -07:00
burkkyy 39aeecad1a CarrotFarmer simplification
build / build (push) Failing after 2s
2026-07-04 05:41:33 -07:00
18 changed files with 349 additions and 47 deletions
+11 -6
View File
@@ -11,20 +11,25 @@ jobs:
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
steps: steps:
- name: checkout repository - name: checkout repository
uses: actions/checkout@v4 uses: actions/checkout@v4.2.2
- name: validate gradle wrapper - name: validate gradle wrapper
uses: gradle/actions/wrapper-validation@v4 uses: gradle/actions/wrapper-validation@v4.3.1
- name: setup jdk - name: setup jdk
uses: actions/setup-java@v4 uses: actions/setup-java@v4.7.1
with: with:
java-version: '25' java-version: "25"
distribution: 'microsoft' distribution: "microsoft"
- name: make gradle wrapper executable - name: make gradle wrapper executable
run: chmod +x ./gradlew run: chmod +x ./gradlew
- name: build - name: build
run: ./gradlew build run: ./gradlew build
- name: capture build artifacts - name: capture build artifacts
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3.1.3
with: with:
name: Artifacts name: Artifacts
path: build/libs/ path: build/libs/
+57 -2
View File
@@ -6,17 +6,20 @@ import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper; import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper;
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents; import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents;
import net.fabricmc.fabric.api.client.rendering.v1.world.WorldRenderEvents; import net.fabricmc.fabric.api.client.rendering.v1.world.WorldRenderEvents;
import net.fabricmc.fabric.api.event.client.player.ClientPlayerBlockBreakEvents;
import net.minecraft.client.MinecraftClient; import net.minecraft.client.MinecraftClient;
import net.minecraft.client.option.KeyBinding; import net.minecraft.client.option.KeyBinding;
import net.minecraft.client.util.InputUtil; import net.minecraft.client.util.InputUtil;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
import net.minecraft.util.math.Vec3d;
import org.lwjgl.glfw.GLFW; import org.lwjgl.glfw.GLFW;
import skybot.commands.CommandManager; import skybot.commands.CommandManager;
import skybot.controller.Controller; import skybot.controller.Controller;
import skybot.controller.task.TaskQueue;
import skybot.farmers.Farmer; import skybot.farmers.Farmer;
import skybot.task.TaskQueue;
import skybot.utilities.ChatMenu; import skybot.utilities.ChatMenu;
import skybot.utilities.TickScheduler;
public class SkybotClient implements ClientModInitializer { public class SkybotClient implements ClientModInitializer {
@@ -24,16 +27,44 @@ public class SkybotClient implements ClientModInitializer {
private static volatile Farmer activeFarmer = null; private static volatile Farmer activeFarmer = null;
private static volatile boolean botEnabled = false; private static volatile boolean botEnabled = false;
private static long tickCount = 0;
private static final long HEALTHCHECK_INTERVAL_TICKS = 40;
private static volatile long lastBreakTime = 0;
private static volatile long lastMoveTime = 0;
private static Vec3d lastPosition = null;
private static final double MOVE_EPSILON_SQUARED = 1.0;
private static final KeyBinding.Category GENERAL_CATEGORY = KeyBinding.Category
.create(Identifier.of("skybot", "general"));
private static final KeyBinding STOP_KEY = new KeyBinding( private static final KeyBinding STOP_KEY = new KeyBinding(
"key.skybot.stop", "key.skybot.stop",
InputUtil.Type.KEYSYM, InputUtil.Type.KEYSYM,
GLFW.GLFW_KEY_CAPS_LOCK, GLFW.GLFW_KEY_CAPS_LOCK,
KeyBinding.Category.create(Identifier.of("skybot", "general"))); GENERAL_CATEGORY);
public static boolean isBotEnabled() { public static boolean isBotEnabled() {
return botEnabled; return botEnabled;
} }
public static long getLastBreakTime() {
return lastBreakTime;
}
public static long getLastMoveTime() {
return lastMoveTime;
}
public static String botStatus() {
if (!botEnabled) {
return "§cIdle";
}
return "§aRunning";
}
public static void stopBot() { public static void stopBot() {
activeFarmer = null; activeFarmer = null;
botEnabled = false; botEnabled = false;
@@ -51,6 +82,9 @@ public class SkybotClient implements ClientModInitializer {
activeFarmer = strategy; activeFarmer = strategy;
botEnabled = true; botEnabled = true;
lastBreakTime = System.currentTimeMillis();
lastMoveTime = System.currentTimeMillis();
lastPosition = null;
activeFarmer.enqueue(TASK_QUEUE); activeFarmer.enqueue(TASK_QUEUE);
ChatMenu.send("§aFarmer started successfully.§r"); ChatMenu.send("§aFarmer started successfully.§r");
@@ -60,6 +94,10 @@ public class SkybotClient implements ClientModInitializer {
@Override @Override
public void onInitializeClient() { public void onInitializeClient() {
KeyBindingHelper.registerKeyBinding(STOP_KEY); KeyBindingHelper.registerKeyBinding(STOP_KEY);
TickScheduler.register();
ClientPlayerBlockBreakEvents.AFTER
.register((world, player, pos, state) -> lastBreakTime = System.currentTimeMillis());
WorldRenderEvents.END_MAIN.register(context -> { WorldRenderEvents.END_MAIN.register(context -> {
MinecraftClient client = MinecraftClient.getInstance(); MinecraftClient client = MinecraftClient.getInstance();
@@ -80,12 +118,29 @@ public class SkybotClient implements ClientModInitializer {
if (client.world != null && !client.isPaused()) { if (client.world != null && !client.isPaused()) {
Controller.tick(client); Controller.tick(client);
if (client.player != null) {
Vec3d pos = client.player.getEntityPos();
if (lastPosition == null) {
lastPosition = pos;
lastMoveTime = System.currentTimeMillis();
} else if (lastPosition.squaredDistanceTo(pos) > MOVE_EPSILON_SQUARED) {
lastMoveTime = System.currentTimeMillis();
lastPosition = pos;
}
}
while (STOP_KEY.wasPressed()) { while (STOP_KEY.wasPressed()) {
if (botEnabled) { if (botEnabled) {
ChatMenu.send("§cBot stopped via hotkey.§r"); ChatMenu.send("§cBot stopped via hotkey.§r");
stopBot(); stopBot();
} }
} }
tickCount++;
if (botEnabled && tickCount % HEALTHCHECK_INTERVAL_TICKS == 0 && !activeFarmer.healthcheck()) {
ChatMenu.send("§cHealthcheck failed, stopping bot.§r");
stopBot();
}
} }
}); });
@@ -7,7 +7,10 @@ import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
import net.minecraft.command.CommandRegistryAccess; import net.minecraft.command.CommandRegistryAccess;
import skybot.SkybotClient; import skybot.SkybotClient;
import skybot.farmers.CarrotFarmer; import skybot.farmers.CarrotFarmer;
import skybot.farmers.NetherwartFarmer;
import skybot.farmers.PotatoFarmer;
import skybot.farmers.TestFarmer; import skybot.farmers.TestFarmer;
import skybot.farmers.WheatFarmer;
import skybot.utilities.ChatMenu; import skybot.utilities.ChatMenu;
public class CommandManager { public class CommandManager {
@@ -23,17 +26,34 @@ public class CommandManager {
} }
return 1; return 1;
})) }))
.then(literal("carrots").executes(context -> { .then(literal("carrot").executes(context -> {
if (SkybotClient.startBot(new CarrotFarmer())) { if (SkybotClient.startBot(new CarrotFarmer())) {
ChatMenu.send("§aStarting Carrot Farmer...§r"); ChatMenu.send("§aStarting Carrot Farmer...§r");
} }
return 1; return 1;
}))
.then(literal("netherwart").executes(context -> {
if (SkybotClient.startBot(new NetherwartFarmer())) {
ChatMenu.send("§aStarting Netherwart Farmer...§r");
}
return 1;
}))
.then(literal("wheat").executes(context -> {
if (SkybotClient.startBot(new WheatFarmer())) {
ChatMenu.send("§aStarting Wheat Farmer...§r");
}
return 1;
}))
.then(literal("potato").executes(context -> {
if (SkybotClient.startBot(new PotatoFarmer())) {
ChatMenu.send("§aStarting Potato Farmer...§r");
}
return 1;
}))); })));
dispatcher.register(literal("skybot") dispatcher.register(literal("skybot")
.then(literal("status").executes(context -> { .then(literal("status").executes(context -> {
String msg = SkybotClient.isBotEnabled() ? "§aRunning" : "§cIdle"; ChatMenu.send("Status: " + SkybotClient.botStatus());
ChatMenu.send("Status: " + msg);
return 1; return 1;
})) }))
.then(literal("stop").executes(context -> { .then(literal("stop").executes(context -> {
@@ -1,54 +1,56 @@
package skybot.farmers; package skybot.farmers;
import net.minecraft.client.util.InputUtil; import skybot.SkybotClient;
import skybot.controller.Controller; import skybot.controller.Controller;
import skybot.controller.task.ClickButtonTask; import skybot.task.HoldKeyTask;
import skybot.controller.task.HoldKeyTask; import skybot.task.PressButtonTask;
import skybot.controller.task.PauseTask; import skybot.task.ReleaseButtonTask;
import skybot.controller.task.PressButtonTask; import skybot.task.RunCommandTask;
import skybot.controller.task.ReleaseButtonTask; import skybot.task.TaskQueue;
import skybot.controller.task.TaskQueue;
public class CarrotFarmer implements Farmer { public class CarrotFarmer implements Farmer {
private static final long HEALTHCHECK_TIMEOUT_MILLIS = 4500;
private static final long MOVEMENT_TIMEOUT_MILLIS = 5000;
@Override
public boolean healthcheck() {
long now = System.currentTimeMillis();
boolean brokeRecently = now - SkybotClient.getLastBreakTime() < HEALTHCHECK_TIMEOUT_MILLIS;
boolean movedRecently = now - SkybotClient.getLastMoveTime() < MOVEMENT_TIMEOUT_MILLIS;
return brokeRecently && movedRecently;
}
@Override @Override
public void enqueue(TaskQueue queue) { public void enqueue(TaskQueue queue) {
queue.clear(); queue.clear();
queue.add(new PauseTask(1998)); queue.add(new RunCommandTask("warp garden"), 1998);
queue.add(new ClickButtonTask(Controller.key(InputUtil.GLFW_KEY_KP_7)));
int EPOCHS = 2; int EPOCHS = 2;
for (int i = 0; i < EPOCHS; i++) { for (int i = 0; i < EPOCHS; i++) {
queue.add(new PauseTask(2021)); queue.add(new PressButtonTask(Controller::setLeftClick), 2021);
queue.add(new PressButtonTask(Controller::setLeftClick));
queue.add(new HoldKeyTask(Controller::setLeft, 119555)); queue.add(new HoldKeyTask(Controller::setLeft, 119555));
queue.add(new ReleaseButtonTask(Controller::setLeftClick)); queue.add(new ReleaseButtonTask(Controller::setLeftClick));
queue.add(new PauseTask(2015));
queue.add(new PressButtonTask(Controller::setLeftClick)); queue.add(new PressButtonTask(Controller::setLeftClick), 2015);
queue.add(new HoldKeyTask(Controller::setRight, 119402)); queue.add(new HoldKeyTask(Controller::setRight, 119402));
queue.add(new ReleaseButtonTask(Controller::setLeftClick)); queue.add(new ReleaseButtonTask(Controller::setLeftClick));
queue.add(new PauseTask(1998));
queue.add(new PressButtonTask(Controller::setLeftClick)); queue.add(new PressButtonTask(Controller::setLeftClick), 1998);
queue.add(new HoldKeyTask(Controller::setLeft, 119223)); queue.add(new HoldKeyTask(Controller::setLeft, 119223));
queue.add(new ReleaseButtonTask(Controller::setLeftClick)); queue.add(new ReleaseButtonTask(Controller::setLeftClick));
queue.add(new PauseTask(2203));
queue.add(new PressButtonTask(Controller::setLeftClick)); queue.add(new PressButtonTask(Controller::setLeftClick), 2203);
queue.add(new HoldKeyTask(Controller::setRight, 119198)); queue.add(new HoldKeyTask(Controller::setRight, 119198));
queue.add(new ReleaseButtonTask(Controller::setLeftClick)); queue.add(new ReleaseButtonTask(Controller::setLeftClick));
queue.add(new PauseTask(1998));
queue.add(new PressButtonTask(Controller::setLeftClick)); queue.add(new PressButtonTask(Controller::setLeftClick), 1998);
queue.add(new HoldKeyTask(Controller::setLeft, 120155)); queue.add(new HoldKeyTask(Controller::setLeft, 120155));
queue.add(new ReleaseButtonTask(Controller::setLeftClick)); queue.add(new ReleaseButtonTask(Controller::setLeftClick));
queue.add(new PauseTask(2126));
queue.add(new ClickButtonTask(Controller.key(InputUtil.GLFW_KEY_KP_7))); queue.add(new RunCommandTask("warp garden"), 2126);
} }
} }
} }
+6 -1
View File
@@ -1,7 +1,12 @@
package skybot.farmers; package skybot.farmers;
import skybot.controller.task.TaskQueue; import skybot.task.TaskQueue;
public interface Farmer { public interface Farmer {
void enqueue(TaskQueue queue); void enqueue(TaskQueue queue);
/** Periodic sanity check while this farmer is running. Returning false stops the bot. */
default boolean healthcheck() {
return true;
}
} }
@@ -0,0 +1,56 @@
package skybot.farmers;
import skybot.SkybotClient;
import skybot.controller.Controller;
import skybot.task.HoldKeyTask;
import skybot.task.PressButtonTask;
import skybot.task.ReleaseButtonTask;
import skybot.task.RunCommandTask;
import skybot.task.TaskQueue;
public class NetherwartFarmer implements Farmer {
private static final long HEALTHCHECK_TIMEOUT_MILLIS = 4500;
private static final long MOVEMENT_TIMEOUT_MILLIS = 5000;
@Override
public boolean healthcheck() {
long now = System.currentTimeMillis();
boolean brokeRecently = now - SkybotClient.getLastBreakTime() < HEALTHCHECK_TIMEOUT_MILLIS;
boolean movedRecently = now - SkybotClient.getLastMoveTime() < MOVEMENT_TIMEOUT_MILLIS;
return brokeRecently && movedRecently;
}
@Override
public void enqueue(TaskQueue queue) {
queue.clear();
queue.add(new RunCommandTask("warp garden"), 1998);
int EPOCHS = 2;
for (int i = 0; i < EPOCHS; i++) {
queue.add(new PressButtonTask(Controller::setLeftClick), 2021);
queue.add(new HoldKeyTask(Controller::setLeft, 119555));
queue.add(new ReleaseButtonTask(Controller::setLeftClick));
queue.add(new PressButtonTask(Controller::setLeftClick), 2015);
queue.add(new HoldKeyTask(Controller::setRight, 119402));
queue.add(new ReleaseButtonTask(Controller::setLeftClick));
queue.add(new PressButtonTask(Controller::setLeftClick), 1998);
queue.add(new HoldKeyTask(Controller::setLeft, 119223));
queue.add(new ReleaseButtonTask(Controller::setLeftClick));
queue.add(new PressButtonTask(Controller::setLeftClick), 2203);
queue.add(new HoldKeyTask(Controller::setRight, 119198));
queue.add(new ReleaseButtonTask(Controller::setLeftClick));
queue.add(new PressButtonTask(Controller::setLeftClick), 1998);
queue.add(new HoldKeyTask(Controller::setLeft, 120155));
queue.add(new ReleaseButtonTask(Controller::setLeftClick));
queue.add(new RunCommandTask("warp garden"), 2126);
}
}
}
@@ -0,0 +1,56 @@
package skybot.farmers;
import skybot.SkybotClient;
import skybot.controller.Controller;
import skybot.task.HoldKeyTask;
import skybot.task.PressButtonTask;
import skybot.task.ReleaseButtonTask;
import skybot.task.RunCommandTask;
import skybot.task.TaskQueue;
public class PotatoFarmer implements Farmer {
private static final long HEALTHCHECK_TIMEOUT_MILLIS = 4500;
private static final long MOVEMENT_TIMEOUT_MILLIS = 5000;
@Override
public boolean healthcheck() {
long now = System.currentTimeMillis();
boolean brokeRecently = now - SkybotClient.getLastBreakTime() < HEALTHCHECK_TIMEOUT_MILLIS;
boolean movedRecently = now - SkybotClient.getLastMoveTime() < MOVEMENT_TIMEOUT_MILLIS;
return brokeRecently && movedRecently;
}
@Override
public void enqueue(TaskQueue queue) {
queue.clear();
queue.add(new RunCommandTask("warp garden"), 1998);
int EPOCHS = 2;
for (int i = 0; i < EPOCHS; i++) {
queue.add(new PressButtonTask(Controller::setLeftClick), 2021);
queue.add(new HoldKeyTask(Controller::setRight, 119555));
queue.add(new ReleaseButtonTask(Controller::setLeftClick));
queue.add(new PressButtonTask(Controller::setLeftClick), 2015);
queue.add(new HoldKeyTask(Controller::setLeft, 119402));
queue.add(new ReleaseButtonTask(Controller::setLeftClick));
queue.add(new PressButtonTask(Controller::setLeftClick), 1998);
queue.add(new HoldKeyTask(Controller::setRight, 119223));
queue.add(new ReleaseButtonTask(Controller::setLeftClick));
queue.add(new PressButtonTask(Controller::setLeftClick), 2203);
queue.add(new HoldKeyTask(Controller::setLeft, 119198));
queue.add(new ReleaseButtonTask(Controller::setLeftClick));
queue.add(new PressButtonTask(Controller::setLeftClick), 1998);
queue.add(new HoldKeyTask(Controller::setRight, 120155));
queue.add(new ReleaseButtonTask(Controller::setLeftClick));
queue.add(new RunCommandTask("warp garden"), 2126);
}
}
}
@@ -2,9 +2,9 @@ package skybot.farmers;
import net.minecraft.client.util.InputUtil; import net.minecraft.client.util.InputUtil;
import skybot.controller.Controller; import skybot.controller.Controller;
import skybot.controller.task.ClickButtonTask; import skybot.task.ClickButtonTask;
import skybot.controller.task.PauseTask; import skybot.task.PauseTask;
import skybot.controller.task.TaskQueue; import skybot.task.TaskQueue;
public class TestFarmer implements Farmer { public class TestFarmer implements Farmer {
@@ -0,0 +1,56 @@
package skybot.farmers;
import skybot.SkybotClient;
import skybot.controller.Controller;
import skybot.task.HoldKeyTask;
import skybot.task.PressButtonTask;
import skybot.task.ReleaseButtonTask;
import skybot.task.RunCommandTask;
import skybot.task.TaskQueue;
public class WheatFarmer implements Farmer {
private static final long HEALTHCHECK_TIMEOUT_MILLIS = 4500;
private static final long MOVEMENT_TIMEOUT_MILLIS = 5000;
@Override
public boolean healthcheck() {
long now = System.currentTimeMillis();
boolean brokeRecently = now - SkybotClient.getLastBreakTime() < HEALTHCHECK_TIMEOUT_MILLIS;
boolean movedRecently = now - SkybotClient.getLastMoveTime() < MOVEMENT_TIMEOUT_MILLIS;
return brokeRecently && movedRecently;
}
@Override
public void enqueue(TaskQueue queue) {
queue.clear();
queue.add(new RunCommandTask("warp garden"), 1998);
int EPOCHS = 2;
for (int i = 0; i < EPOCHS; i++) {
queue.add(new PressButtonTask(Controller::setLeftClick), 2021);
queue.add(new HoldKeyTask(Controller::setRight, 119555));
queue.add(new ReleaseButtonTask(Controller::setLeftClick));
queue.add(new PressButtonTask(Controller::setLeftClick), 2015);
queue.add(new HoldKeyTask(Controller::setLeft, 119402));
queue.add(new ReleaseButtonTask(Controller::setLeftClick));
queue.add(new PressButtonTask(Controller::setLeftClick), 1998);
queue.add(new HoldKeyTask(Controller::setRight, 119223));
queue.add(new ReleaseButtonTask(Controller::setLeftClick));
queue.add(new PressButtonTask(Controller::setLeftClick), 2203);
queue.add(new HoldKeyTask(Controller::setLeft, 119198));
queue.add(new ReleaseButtonTask(Controller::setLeftClick));
queue.add(new PressButtonTask(Controller::setLeftClick), 1998);
queue.add(new HoldKeyTask(Controller::setRight, 120155));
queue.add(new ReleaseButtonTask(Controller::setLeftClick));
queue.add(new RunCommandTask("warp garden"), 2126);
}
}
}
@@ -1,9 +1,12 @@
package skybot.controller.task; package skybot.task;
import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.ThreadLocalRandom;
import java.util.function.Consumer; import java.util.function.Consumer;
/** Clicks a button (press then release) held for a log-normally distributed duration. */ /**
* Clicks a button (press then release) held for a log-normally distributed
* duration.
*/
public class ClickButtonTask implements Task { public class ClickButtonTask implements Task {
private final Consumer<Boolean> setter; private final Consumer<Boolean> setter;
private long endTime; private long endTime;
@@ -1,4 +1,4 @@
package skybot.controller.task; package skybot.task;
import java.util.function.Consumer; import java.util.function.Consumer;
@@ -1,4 +1,4 @@
package skybot.controller.task; package skybot.task;
/** /**
* Does nothing for a fixed duration; leaves whatever keys were already released * Does nothing for a fixed duration; leaves whatever keys were already released
@@ -1,4 +1,4 @@
package skybot.controller.task; package skybot.task;
import java.util.function.Consumer; import java.util.function.Consumer;
@@ -1,4 +1,4 @@
package skybot.controller.task; package skybot.task;
import java.util.function.Consumer; import java.util.function.Consumer;
@@ -1,4 +1,4 @@
package skybot.controller.task; package skybot.task;
import net.minecraft.client.MinecraftClient; import net.minecraft.client.MinecraftClient;
@@ -1,4 +1,4 @@
package skybot.controller.task; package skybot.task;
public interface Task { public interface Task {
/** Called once, the tick this task becomes active. */ /** Called once, the tick this task becomes active. */
@@ -10,7 +10,10 @@ public interface Task {
boolean isDone(); boolean isDone();
/** Called once, right after isDone() first returns true, before the next task starts. */ /**
* Called once, right after isDone() first returns true, before the next task
* starts.
*/
default void stop() { default void stop() {
} }
@@ -1,4 +1,4 @@
package skybot.controller.task; package skybot.task;
import java.util.ArrayDeque; import java.util.ArrayDeque;
import java.util.Deque; import java.util.Deque;
@@ -27,6 +27,12 @@ public class TaskQueue {
return this; return this;
} }
public TaskQueue add(Task task, long delayMs) {
pending.add(new PauseTask(delayMs));
pending.add(task);
return this;
}
public void tick() { public void tick() {
if (current != null && current.isDone()) { if (current != null && current.isDone()) {
current.stop(); current.stop();
@@ -0,0 +1,35 @@
package skybot.utilities;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
import java.util.concurrent.ConcurrentLinkedQueue;
public class TickScheduler {
private record Task(Runnable action, int ticksRemaining) {
}
private static final ConcurrentLinkedQueue<Task> tasks = new ConcurrentLinkedQueue<>();
public static void register() {
ClientTickEvents.END_CLIENT_TICK.register(client -> tick());
}
public static void schedule(Runnable action, int delayTicks) {
tasks.add(new Task(action, delayTicks));
}
private static void tick() {
int size = tasks.size();
for (int i = 0; i < size; i++) {
Task t = tasks.poll();
if (t == null)
break;
int remaining = t.ticksRemaining() - 1;
if (remaining <= 0) {
t.action().run();
} else {
tasks.add(new Task(t.action(), remaining));
}
}
}
}