|
| 1 | +--- |
| 2 | +created: 2025-05-24T15:31 |
| 3 | +updated: 2025-05-24T15:40 |
| 4 | +--- |
| 5 | + |
| 6 | +So a minecraft jail? |
| 7 | + |
| 8 | +I made my alt travel to me from a few thousand blocks away using baritone for some extra wood so I can make a trapdoor to go through the hole. |
| 9 | + |
| 10 | +Wait I am given a lot of trapdoors? |
| 11 | + |
| 12 | +Oh... |
| 13 | + |
| 14 | +## analysis |
| 15 | + |
| 16 | +Using free-cam I see a chest at the bottom of this maze, and I realized that there is no way I'll navigate this manually. |
| 17 | + |
| 18 | + |
| 19 | + |
| 20 | +So I wrote (with GPT) a small client side mod to A* search a path to the chest at the bottom. |
| 21 | + |
| 22 | +```java |
| 23 | +package me.kumo.pathfinder.client; |
| 24 | + |
| 25 | +import com.mojang.brigadier.Command; |
| 26 | +import com.mojang.brigadier.arguments.IntegerArgumentType; |
| 27 | +import me.x150.renderer.event.RenderEvents; |
| 28 | +import me.x150.renderer.render.CustomRenderLayers; |
| 29 | +import me.x150.renderer.render.WorldRenderContext; |
| 30 | +import me.x150.renderer.util.Color; |
| 31 | +import net.fabricmc.api.ClientModInitializer; |
| 32 | +import net.fabricmc.fabric.api.client.command.v2.ClientCommandManager; |
| 33 | +import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; |
| 34 | +import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; |
| 35 | +import net.minecraft.client.MinecraftClient; |
| 36 | +import net.minecraft.client.render.VertexConsumerProvider; |
| 37 | +import net.minecraft.client.util.BufferAllocator; |
| 38 | +import net.minecraft.client.world.ClientWorld; |
| 39 | +import net.minecraft.text.Text; |
| 40 | +import net.minecraft.util.math.BlockPos; |
| 41 | +import net.minecraft.util.math.Vec3d; |
| 42 | + |
| 43 | +import java.util.*; |
| 44 | + |
| 45 | +public class PathfinderClient implements ClientModInitializer { |
| 46 | + private static List<Vec3d> pathPoints = Collections.emptyList(); |
| 47 | + |
| 48 | + @Override |
| 49 | + public void onInitializeClient() { |
| 50 | + // Register client-side command: /pathfind <x> <y> <z> |
| 51 | + ClientCommandRegistrationCallback.EVENT.register((dispatcher, regAccess) -> dispatcher.register(ClientCommandManager.literal("pathfind").then(ClientCommandManager.argument("x", IntegerArgumentType.integer()).then(ClientCommandManager.argument("y", IntegerArgumentType.integer()).then(ClientCommandManager.argument("z", IntegerArgumentType.integer()).executes(ctx -> { |
| 52 | + int x = IntegerArgumentType.getInteger(ctx, "x"); |
| 53 | + int y = IntegerArgumentType.getInteger(ctx, "y"); |
| 54 | + int z = IntegerArgumentType.getInteger(ctx, "z"); |
| 55 | + BlockPos target = new BlockPos(x, y, z); |
| 56 | + ClientWorld world = MinecraftClient.getInstance().world; |
| 57 | + BlockPos start = MinecraftClient.getInstance().player.getBlockPos(); |
| 58 | + List<BlockPos> path = findPath(world, start, target); |
| 59 | + FabricClientCommandSource source = ctx.getSource(); |
| 60 | + if (path == null) { |
| 61 | + source.sendError(Text.literal("No path found to " + target)); |
| 62 | + return 0; |
| 63 | + } |
| 64 | + List<Vec3d> pts = new ArrayList<>(); |
| 65 | + for (BlockPos p : path) { |
| 66 | + pts.add(new Vec3d(p.getX() + 0.5, p.getY() + 0.5, p.getZ() + 0.5)); |
| 67 | + } |
| 68 | + pathPoints = pts; |
| 69 | + source.sendFeedback(Text.literal("Rendered path of length " + path.size())); |
| 70 | + return Command.SINGLE_SUCCESS; |
| 71 | + })))))); |
| 72 | + |
| 73 | + RenderEvents.AFTER_WORLD.register(ms -> { |
| 74 | + if (pathPoints.isEmpty()) return; |
| 75 | + VertexConsumerProvider.Immediate vcp = VertexConsumerProvider.immediate(new BufferAllocator(1024)); |
| 76 | + WorldRenderContext rc = new WorldRenderContext(MinecraftClient.getInstance(), vcp); |
| 77 | + |
| 78 | + for (int i = 0; i < pathPoints.size() - 1; i++) { |
| 79 | + Vec3d a = pathPoints.get(i); |
| 80 | + Vec3d b = pathPoints.get(i + 1); |
| 81 | + rc.drawLine(ms, CustomRenderLayers.LINES_NO_DEPTH_TEST.apply(0d), a, b, new Color(0xFF0000FF)); |
| 82 | + } |
| 83 | + vcp.draw(); |
| 84 | + }); |
| 85 | + } |
| 86 | + |
| 87 | + private List<BlockPos> findPath(ClientWorld world, BlockPos start, BlockPos goal) { |
| 88 | + Set<BlockPos> closed = new HashSet<>(); |
| 89 | + PriorityQueue<Node> open = new PriorityQueue<>(Comparator.comparingInt(n -> n.f)); |
| 90 | + Map<BlockPos, Integer> gScore = new HashMap<>(); |
| 91 | + |
| 92 | + gScore.put(start, 0); |
| 93 | + open.add(new Node(start, null, 0, heuristic(start, goal))); |
| 94 | + List<BlockPos> dirs = Arrays.asList(new BlockPos(1, 0, 0), new BlockPos(-1, 0, 0), new BlockPos(0, 1, 0), new BlockPos(0, -1, 0), new BlockPos(0, 0, 1), new BlockPos(0, 0, -1)); |
| 95 | + int maxIters = 1000_000; |
| 96 | + while (!open.isEmpty() && maxIters-- > 0) { |
| 97 | + Node curr = open.poll(); |
| 98 | + if (curr.pos.equals(goal)) { |
| 99 | + return reconstruct(curr); |
| 100 | + } |
| 101 | + closed.add(curr.pos); |
| 102 | + |
| 103 | + for (BlockPos d : dirs) { |
| 104 | + BlockPos next = curr.pos.add(d); |
| 105 | + if (next.getY() > 114) continue; |
| 106 | + if (closed.contains(next)) continue; |
| 107 | + if (world.isOutOfHeightLimit(next)) continue; |
| 108 | + if (!world.getBlockState(next).isAir()) continue; |
| 109 | + |
| 110 | + int tentativeG = curr.g + 1; |
| 111 | + if (tentativeG < gScore.getOrDefault(next, Integer.MAX_VALUE)) { |
| 112 | + gScore.put(next, tentativeG); |
| 113 | + open.add(new Node(next, curr, tentativeG, heuristic(next, goal))); |
| 114 | + } |
| 115 | + } |
| 116 | + } |
| 117 | + return null; |
| 118 | + } |
| 119 | + |
| 120 | + private int heuristic(BlockPos a, BlockPos b) { |
| 121 | + return Math.abs(a.getX() - b.getX()) + Math.abs(a.getY() - b.getY()) + Math.abs(a.getZ() - b.getZ()); |
| 122 | + } |
| 123 | + |
| 124 | + private List<BlockPos> reconstruct(Node node) { |
| 125 | + List<BlockPos> path = new ArrayList<>(); |
| 126 | + for (Node n = node; n != null; n = n.parent) path.add(n.pos); |
| 127 | + Collections.reverse(path); |
| 128 | + return path; |
| 129 | + } |
| 130 | + |
| 131 | + private static class Node { |
| 132 | + BlockPos pos; |
| 133 | + Node parent; |
| 134 | + int g; |
| 135 | + int f; |
| 136 | + |
| 137 | + Node(BlockPos pos, Node parent, int g, int h) { |
| 138 | + this.pos = pos; |
| 139 | + this.parent = parent; |
| 140 | + this.g = g; |
| 141 | + this.f = g + h; |
| 142 | + } |
| 143 | + } |
| 144 | +} |
| 145 | +``` |
| 146 | + |
| 147 | +It seems to work well (after some debugging). |
| 148 | + |
| 149 | + |
| 150 | + |
| 151 | +## the flag |
| 152 | + |
| 153 | +So the path has been found, I have to navigate down there using trapdoors, avoiding fall damage, etc. |
| 154 | + |
| 155 | + |
| 156 | + |
| 157 | +Well the journey took a lot more effort than I would prefer. |
| 158 | + |
| 159 | +I have now become a master of minecraft 1x1 tunnel navigation using trapdoors. |
| 160 | + |
| 161 | +I would strangle the author from behind while stabbing him several times in the chest if I ever find him on the streets. |
| 162 | + |
| 163 | + |
| 164 | + |
| 165 | +```flag |
| 166 | +SAS{c4v3_d1v3rs_f0r_n0_r34s0n_wh3n_th3y_h3ar_th3r3_1s_a_d1amond_d0wn_th3_h3r0br1n3s_p1t} |
| 167 | +``` |
0 commit comments