Skip to main content
int RADIUS_XZ = 3;
int RADIUS_Y = 2;
double REACH = 4.5;
double REACH_SQ  = REACH * REACH;
String[] FACES = new String[]{ "UP", "DOWN", "NORTH", "SOUTH", "WEST", "EAST" };
int[] DX = new int[]{ 0, 0, 0, 0,-1, 1 };
int[] DY = new int[]{ 1,-1, 0, 0, 0, 0 };
int[] DZ = new int[]{ 0, 0,-1, 1, 0, 0 };
float lastYaw = 0f;
float lastPitch = 0f;
boolean hasLastRot = false;
int baseX;
int baseY;
int baseZ;
String targetFace;

@Module(name="Scaffold2", category="MOVE", description="Simple Scaffold", defaultEnabled=false)
void meta(valueManager) {
    valueManager.registerSliderDouble("rotation_speed", "Rotation Speed", 30.0, 180.0, 180.0, 5.0);
    valueManager.registerBoolean("move_fix", "Move Fix", true);
    valueManager.registerBoolean("keep_rotation", "Keep Rotation", true);
}

@EventTarget(events="player_update")
void onPlayerUpdate(event) {
    player = me.getPlayer();
    if (player == null) return;

    double rotationSpeed = me.getNumber("rotation_speed");
    boolean moveFix = me.getBool("move_fix");
    boolean keepRotation  = me.getBool("keep_rotation");

    if (findTarget(player)) {
        int blockSlot = findBlockSlot();
        if (blockSlot < 0) {
            moduleManager.self().disable();
            return;
        }

        int currentSlot = inventory.getSlot();
        boolean didSwitch = (blockSlot != currentSlot);
        if (didSwitch) inventory.switchTo(blockSlot);

        double[] hit = faceCenter(baseX, baseY, baseZ, targetFace);
        float[] rot = me.getRotation(hit[0], hit[1], hit[2]);

        me.setRotation(rot[0], rot[1], (float) rotationSpeed, moveFix);
        me.placeBlock(baseX, baseY, baseZ, targetFace, "MAIN_HAND");
        lastYaw = rot[0];
        lastPitch = rot[1];
        hasLastRot = true;
    } else if (keepRotation && hasLastRot) {
        me.setRotation(lastYaw, lastPitch, (float) rotationSpeed, moveFix);
    }
}

int findBlockSlot() {
    int cur = inventory.getSlot();
    if (cur >= 0 && cur < 9) {
        cs = inventory.getStackInSlot(cur);
        if (cs != null && !cs.isEmpty() && cs.isBlock()) return cur;
    }
    for (int i = 0; i < 9; i++) {
        s = inventory.getStackInSlot(i);
        if (s != null && !s.isEmpty() && s.isBlock()) return i;
    }
    return -1;
}

boolean findTarget(player) {
    world = me.getWorld();
    if (world == null) return false;

    pos = player.getPosition();
    int tx = (int) Math.floor(pos.x);
    int ty = (int) Math.floor(pos.y) - 1;
    int tz = (int) Math.floor(pos.z);

    if (!world.isReplaceable(tx, ty, tz)) return false;

    double tcx = tx + 0.5;
    double tcy = ty + 0.5;
    double tcz = tz + 0.5;

    double bestDistSq = Double.MAX_VALUE;
    boolean found = false;

    for (int dx = -RADIUS_XZ; dx <= RADIUS_XZ; dx++) {
        for (int dy = -RADIUS_Y; dy <= 0; dy++) {
            for (int dz = -RADIUS_XZ; dz <= RADIUS_XZ; dz++) {
                int cx = tx + dx;
                int cy = ty + dy;
                int cz = tz + dz;

                if (world.isReplaceable(cx, cy, cz)) continue;
                if (world.isInteractable(cx, cy, cz)) continue;

                double pcx = cx + 0.5 - pos.x;
                double pcy = cy + 0.5 - pos.y;
                double pcz = cz + 0.5 - pos.z;
                if (pcx * pcx + pcy * pcy + pcz * pcz > REACH_SQ) continue;

                int bestFaceIdx = pickBestFace(cx, cy, cz, tx, ty, tz, world);
                if (bestFaceIdx < 0) continue;

                double ddx = cx + 0.5 - tcx;
                double ddy = cy + 0.5 - tcy;
                double ddz = cz + 0.5 - tcz;
                double distSq = ddx * ddx + ddy * ddy + ddz * ddz;

                if (distSq < bestDistSq) {
                    bestDistSq = distSq;
                    baseX = cx;
                    baseY = cy;
                    baseZ = cz;
                    targetFace = FACES[bestFaceIdx];
                    found = true;
                }
            }
        }
    }

    return found;
}

int pickBestFace(int cx, int cy, int cz, int tx, int ty, int tz, world) {
    int    best  = -1;
    double bestD = Double.MAX_VALUE;
    double tcx   = tx + 0.5;
    double tcy   = ty + 0.5;
    double tcz   = tz + 0.5;

    for (int i = 0; i < 6; i++) {
        if ("DOWN".equals(FACES[i])) continue;
        int nx = cx + DX[i];
        int ny = cy + DY[i];
        int nz = cz + DZ[i];
        if (!world.isReplaceable(nx, ny, nz)) continue;

        double ex = nx + 0.5 - tcx;
        double ey = ny + 0.5 - tcy;
        double ez = nz + 0.5 - tcz;
        double d  = ex * ex + ey * ey + ez * ez;
        if (d < bestD) {
            bestD = d;
            best  = i;
        }
    }
    return best;
}

double[] faceCenter(int x, int y, int z, String face) {
    double cx = x + 0.5;
    double cy = y + 0.5;
    double cz = z + 0.5;
    if ("UP".equals(face))    cy = y + 1.0;
    if ("DOWN".equals(face))  cy = y;
    if ("NORTH".equals(face)) cz = z;
    if ("SOUTH".equals(face)) cz = z + 1.0;
    if ("WEST".equals(face))  cx = x;
    if ("EAST".equals(face))  cx = x + 1.0;
    return new double[]{ cx, cy, cz };
}

@OnEnable
void onEnable()  {
  hasLastRot = false; me.chat("§a[Scaffold2] on§r");
}

@OnDisable
void onDisable() {
    inventory.switchBack();
    me.chat("§c[Scaffold2] off§r");
}

为什么是 player_update

setRotation(..., moveFix=true)placeBlock必须在这个事件里调:
  • player_update 派发时 RotationBus 正在整理下一帧要发的 rotation;此时 setRotation 能被当前 tick 的 motion 包带出去
  • 放在 tick / motion_update 会让旋转和放置包错相位,服务端会看到”视角还没变就 useItemOn”,Grim 直接拦