Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
d7018f0
ugh (#12)
PoseidonsCave May 5, 2026
00110a4
testing text parsing for whisper patterns in chat messages
PoseidonsCave May 5, 2026
a4cb202
Add chat pipeline debugging
PoseidonsCave May 6, 2026
845836a
adding debugging
PoseidonsCave May 6, 2026
2f257c3
Merge origin/main into official-sandbox
PoseidonsCave May 6, 2026
96d10ce
maybe it's the environment variable?? god java is weird
PoseidonsCave May 6, 2026
ca18950
inconsistency
PoseidonsCave May 6, 2026
8ca5616
Merge origin/main into official-sandbox
PoseidonsCave May 6, 2026
810cb69
fixing environment variable data leak
PoseidonsCave May 6, 2026
1c8450e
whitespace fix
PoseidonsCave May 6, 2026
777cfcc
yeah so... i think the environment variable for the plugin is not bei…
PoseidonsCave May 6, 2026
27dcb51
Merge origin/main into official-sandbox
PoseidonsCave May 6, 2026
e7ee0f5
Release 0.2.0
PoseidonsCave May 6, 2026
e1b5e75
Testing in-game response, got successful connection
PoseidonsCave May 6, 2026
6f343ef
Merge origin/main into official-sandbox
PoseidonsCave May 6, 2026
e3870f9
it's not parsing properly!!!
PoseidonsCave May 6, 2026
00ecdb7
Merge origin/main into official-sandbox
PoseidonsCave May 6, 2026
ea06921
let's try again!
PoseidonsCave May 6, 2026
e08cde4
let's try this!
PoseidonsCave May 6, 2026
96efa92
clumsy me
PoseidonsCave May 6, 2026
d2d809f
Merge origin/main into official-sandbox
PoseidonsCave May 6, 2026
8422502
* Prompt Engineering 101: Crafting Effective Prompts for AI Models
PoseidonsCave May 6, 2026
104d15d
* updated semver
PoseidonsCave May 6, 2026
f29c923
Merge origin/main into official-sandbox
PoseidonsCave May 7, 2026
c62eb8f
ah.
PoseidonsCave May 7, 2026
967ca44
Merge origin/main into official-sandbox
PoseidonsCave May 7, 2026
60a7403
v0.2.4:
PoseidonsCave May 8, 2026
188a7dd
v0.3.0
PoseidonsCave May 13, 2026
c6362c1
yoop
PoseidonsCave May 13, 2026
cbc938e
Updating license to AGPL v3.0
PoseidonsCave May 14, 2026
c29f9d3
turns out zenith doesn't actually have raw pathfinding, so i've gotta…
PoseidonsCave May 17, 2026
b276252
Merge main into official-sandbox
PoseidonsCave May 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
plugin_version=0.3.0
plugin_version=0.3.1
plugin_name=OpenCraft
plugin_id=opencraft
mc=1.21.4
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package com.zenith.plugin.opencraft.automation;

import com.zenith.Proxy;
import com.zenith.cache.data.entity.EntityPlayer;
import com.zenith.feature.pathfinder.PathingRequestFuture;
import com.zenith.feature.pathfinder.goals.GoalNear;
import com.zenith.plugin.opencraft.auth.UserIdentity;
import com.zenith.plugin.opencraft.discord.DiscordNotifier;
import com.zenith.util.math.MathHelper;
Expand All @@ -15,6 +17,7 @@

import static com.zenith.Globals.BARITONE;
import static com.zenith.Globals.BOT;
import static com.zenith.Globals.CACHE;
import static com.zenith.Globals.EXECUTOR;

public final class CardinalMovementService {
Expand All @@ -36,62 +39,196 @@ public String moveFromCurrent(final String requestId,
final String direction,
final int blocks) {
ensureInGame();
if (blocks < 1 || blocks > 512) {
throw new IllegalArgumentException("Movement distance must be between 1 and 512 blocks.");
}
validateDistance(blocks);

final DirectionOffset offset = normalize(direction);
final int currentX = MathHelper.floorI(BOT.getX());
final int currentZ = MathHelper.floorI(BOT.getZ());
final int targetX = currentX + (offset.dx() * blocks);
final int targetZ = currentZ + (offset.dz() * blocks);

final PathingRequestFuture future = BARITONE.pathTo(targetX, targetZ);
if (!future.isAccepted()) {
throw new IllegalStateException("Pathfinder rejected the directional movement request.");
}

watchMoveCompletion(requestId, identity, offset.label(), blocks, targetX, targetZ);
pathTo(requestId, identity, "directional move", targetX, targetZ);

logger.info("[OpenCraft] Started directional move {} {} blocks from ({}, {}) to ({}, {})",
offset.label(), blocks, currentX, currentZ, targetX, targetZ);
return "Moving " + blocks + " block(s) " + offset.label()
+ " from the current position to (" + targetX + ", " + targetZ + ").";
}

public String moveThisWay(final String requestId,
final UserIdentity identity,
final int blocks) {
ensureInGame();
validateDistance(blocks);

final PathingRequestFuture future = BARITONE.thisWay(blocks);
if (!future.isAccepted()) {
throw new IllegalStateException("Pathfinder rejected the facing-direction movement request.");
}

watchMoveCompletion(requestId, identity, "facing direction", "Facing-direction move timed out.");

logger.info("[OpenCraft] Started facing-direction move {} blocks from ({}, {}) yaw={}",
blocks, MathHelper.floorI(BOT.getX()), MathHelper.floorI(BOT.getZ()), BOT.getYaw());
return "Moving " + blocks + " block(s) in the current facing direction.";
}

public String gotoXz(final String requestId,
final UserIdentity identity,
final int x,
final int z) {
ensureInGame();
pathTo(requestId, identity, "navigation", x, z);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid timing out unbounded navigation after five minutes

For pathfinder.goto.xz, targets can be arbitrarily far away, but routing through pathTo always arms the fixed MOVE_TIMEOUT watcher that was originally sized for capped 512-block cardinal moves. Any legitimate long-distance goto that takes more than five minutes will send a misleading “navigation timed out” notification even though Baritone continues pathing; use a distance-aware/operation timeout or omit this fixed watcher for unbounded coordinate navigation.

Useful? React with 👍 / 👎.

logger.info("[OpenCraft] Started navigation to ({}, {})", x, z);
return "Navigating to x=" + x + ", z=" + z + ".";
}

public String gotoXyz(final String requestId,
final UserIdentity identity,
final int x,
final int y,
final int z) {
ensureInGame();
final PathingRequestFuture future = BARITONE.pathTo(x, y, z);
if (!future.isAccepted()) {
throw new IllegalStateException("Pathfinder rejected the exact-coordinate navigation request.");
}
watchMoveCompletion(requestId, identity, "exact coordinate", "Exact-coordinate navigation timed out.");
logger.info("[OpenCraft] Started navigation to ({}, {}, {})", x, y, z);
return "Navigating to x=" + x + ", y=" + y + ", z=" + z + ".";
}

public String near(final String requestId,
final UserIdentity identity,
final int x,
final int y,
final int z,
final int rangeSq) {
ensureInGame();
if (rangeSq < 1 || rangeSq > 262144) {
throw new IllegalArgumentException("Near rangeSq must be between 1 and 262144.");
}
final PathingRequestFuture future = BARITONE.pathTo(new GoalNear(x, y, z, rangeSq));
if (!future.isAccepted()) {
throw new IllegalStateException("Pathfinder rejected the near-position navigation request.");
}
watchMoveCompletion(requestId, identity, "near-position", "Near-position navigation timed out.");
logger.info("[OpenCraft] Started navigation near ({}, {}, {}) rangeSq={}", x, y, z, rangeSq);
return "Navigating near x=" + x + ", y=" + y + ", z=" + z + ".";
}

public String followPlayer(final String requestId,
final UserIdentity identity,
final String playerName) {
ensureInGame();
final String safeName = validatePlayerName(playerName);
final EntityPlayer target = CACHE.getEntityCache().getPlayers().values().stream()
.filter(player -> player.getUuid() != null)
.filter(player -> CACHE.getTabListCache()
.get(player.getUuid())
.filter(entry -> entry.getName().equalsIgnoreCase(safeName))
.isPresent())
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("Player '" + safeName + "' is not currently visible."));

BARITONE.follow(target);

discordNotifier.notifyMilestone(requestId, identity, "pathfinder",
"Started following " + safeName + ".");
logger.info("[OpenCraft] Started following player {} ({})", safeName, target.getUuid());
return "Following " + safeName + ".";
}

public String pickup(final String requestId,
final UserIdentity identity) {
ensureInGame();
BARITONE.pickup();
discordNotifier.notifyMilestone(requestId, identity, "pathfinder", "Started pickup pathing.");
logger.info("[OpenCraft] Started pickup pathing.");
return "Picking up nearby items.";
}

public String stop() {
BARITONE.stop();
return "Stopped pathfinder navigation.";
}

public String status() {
if (!Proxy.getInstance().isConnected()) {
return "Pathfinder unavailable: bot is disconnected.";
}
if (Proxy.getInstance().isInQueue()) {
return "Pathfinder unavailable: bot is in queue.";
}
if (!BARITONE.isActive()) {
return "Pathfinder is idle.";
}
final var goal = BARITONE.currentGoal();
return "Pathfinder is active" + (goal == null ? "." : ": " + goal + ".");
}

private void pathTo(final String requestId,
final UserIdentity identity,
final String label,
final int targetX,
final int targetZ) {
final PathingRequestFuture future = BARITONE.pathTo(targetX, targetZ);
if (!future.isAccepted()) {
throw new IllegalStateException("Pathfinder rejected the " + label + " request.");
}
watchMoveCompletion(requestId, identity, label, label + " timed out.");
}

private void watchMoveCompletion(final String requestId,
final UserIdentity identity,
final String directionLabel,
final int blocks,
final int targetX,
final int targetZ) {
final String moveLabel,
final String timeoutDetail) {
final Instant deadline = Instant.now().plus(MOVE_TIMEOUT);
final ScheduledFuture<?>[] pollRef = new ScheduledFuture<?>[1];
pollRef[0] = EXECUTOR.scheduleAtFixedRate(() -> {
try {
if (Instant.now().isAfter(deadline)) {
final ScheduledFuture<?> poll = pollRef[0];
if (poll != null) poll.cancel(false);
discordNotifier.notifyMilestone(requestId, identity, "cardinal",
"Directional move " + directionLabel + " " + blocks + " block(s) timed out.");
discordNotifier.notifyMilestone(requestId, identity, "pathfinder",
timeoutDetail);
return;
}
if (!BARITONE.isActive()) {
final ScheduledFuture<?> poll = pollRef[0];
if (poll != null) poll.cancel(false);
discordNotifier.notifyMilestone(requestId, identity, "cardinal",
"Finished moving " + directionLabel + " " + blocks
+ " block(s) — arrived near (" + targetX + ", " + targetZ + ").");
discordNotifier.notifyMilestone(requestId, identity, "pathfinder",
"Finished " + moveLabel + ".");
}
} catch (final Exception e) {
final ScheduledFuture<?> poll = pollRef[0];
if (poll != null) poll.cancel(false);
discordNotifier.notifyMilestone(requestId, identity, "cardinal",
"Directional move completion watcher failed: " + e.getMessage());
discordNotifier.notifyMilestone(requestId, identity, "pathfinder",
"Pathfinder completion watcher failed: " + e.getMessage());
}
}, POLL_INTERVAL_SECONDS, POLL_INTERVAL_SECONDS, TimeUnit.SECONDS);
}

private static void validateDistance(final int blocks) {
if (blocks < 1 || blocks > 512) {
throw new IllegalArgumentException("Movement distance must be between 1 and 512 blocks.");
}
}

private static String validatePlayerName(final String playerName) {
final String value = playerName == null ? "" : playerName.strip();
if (value.length() < 1 || value.length() > 16) {
throw new IllegalArgumentException("Player name must be between 1 and 16 characters.");
}
for (int i = 0; i < value.length(); i++) {
final char ch = value.charAt(i);
if (!(Character.isLetterOrDigit(ch) || ch == '_')) {
throw new IllegalArgumentException("Player name may only use letters, digits, or underscores.");
}
}
return value;
}

static DirectionOffset normalize(final String rawDirection) {
if (rawDirection == null) {
throw new IllegalArgumentException("Direction is required.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,13 +186,55 @@ private String executeInternal(final String commandId,
final UserIdentity identity,
final String requestId) {
return switch (commandId) {
case "pathfinder.status" ->
cardinalMovementService.status();
case "pathfinder.stop" ->
cardinalMovementService.stop();
case "pathfinder.goto.xz" ->
cardinalMovementService.gotoXz(
requestId,
identity,
Integer.parseInt(args.get("x").strip()),
Integer.parseInt(args.get("z").strip())
);
case "pathfinder.goto.xyz" ->
cardinalMovementService.gotoXyz(
requestId,
identity,
Integer.parseInt(args.get("x").strip()),
Integer.parseInt(args.get("y").strip()),
Integer.parseInt(args.get("z").strip())
);
case "pathfinder.thisway" ->
cardinalMovementService.moveThisWay(
requestId,
identity,
Integer.parseInt(args.get("blocks").strip())
);
case "pathfinder.cardinal" ->
cardinalMovementService.moveFromCurrent(
requestId,
identity,
args.get("direction").strip(),
Integer.parseInt(args.get("blocks").strip())
);
case "pathfinder.near" ->
cardinalMovementService.near(
requestId,
identity,
Integer.parseInt(args.get("x").strip()),
Integer.parseInt(args.get("y").strip()),
Integer.parseInt(args.get("z").strip()),
Integer.parseInt(args.get("rangeSq").strip())
);
case "pathfinder.follow" ->
cardinalMovementService.followPlayer(
requestId,
identity,
args.get("player").strip()
);
case "pathfinder.pickup" ->
cardinalMovementService.pickup(requestId, identity);
case "patrol.once.current" ->
patrolService.patrolOnceCurrent(requestId, identity,
Integer.parseInt(args.get("radius").strip()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,35 +16,35 @@ public static List<CommandDefinition> definitions() {
define(
"pathfinder.status",
"Check current pathfinder navigation status and active goal",
"pathfinder status",
"@internal:pathfinder.status",
"admin", "low", false,
Map.of()
),
define(
"pathfinder.stop",
"Stop any active pathfinder navigation immediately",
"pathfinder stop",
"@internal:pathfinder.stop",
"admin", "low", false,
Map.of()
),
define(
"pathfinder.goto.xz",
"Navigate to X,Z coordinates (Y determined automatically)",
"pathfinder goto {x} {z}",
"@internal:pathfinder.goto.xz {x} {z}",
"admin", "medium", false,
Map.of("x", "integer", "z", "integer")
),
define(
"pathfinder.goto.xyz",
"Navigate to exact X,Y,Z coordinates",
"pathfinder goto {x} {y} {z}",
"@internal:pathfinder.goto.xyz {x} {y} {z}",
"admin", "medium", false,
Map.of("x", "integer", "y", "integer", "z", "integer")
),
define(
"pathfinder.thisway",
"Move N blocks in the current facing direction",
"pathfinder thisway {blocks}",
"@internal:pathfinder.thisway {blocks}",
"admin", "medium", false,
Map.of("blocks", "integer")
),
Expand All @@ -58,21 +58,21 @@ public static List<CommandDefinition> definitions() {
define(
"pathfinder.near",
"Navigate to anywhere within a radius of the target X,Y,Z position",
"pathfinder near {x} {y} {z} {rangeSq}",
"@internal:pathfinder.near {x} {y} {z} {rangeSq}",
"admin", "medium", false,
Map.of("x", "integer", "y", "integer", "z", "integer", "rangeSq", "integer")
),
define(
"pathfinder.follow",
"Follow a player by name (stops when command is cancelled)",
"pathfinder follow {player}",
"@internal:pathfinder.follow {player}",
"admin", "medium", false,
Map.of("player", "string")
),
define(
"pathfinder.pickup",
"Navigate toward and pick up all nearby items on the ground",
"pathfinder pickup",
"@internal:pathfinder.pickup",
"admin", "low", false,
Map.of()
),
Expand Down
Loading
Loading