Anuken/Mindustry · error · RuntimeException

Too many plans:

Error message

Too many plans: 

What it means

readClientPlans() reads a client-to-server preview-plan packet. It reads a short amount and throws if amount > maxPlayerPreviewPlans (1000). Clients normally truncate to this limit (NetClient.java:784, NetServer.java:1381), so exceeding it means the sender bypassed truncation or is hostile.

Source

Thrown at core/src/mindustry/io/TypeIO.java:702

        write.s((short)plans.size);
        //each plan should be ~7-12 bytes
        for(BuildPlan plan : plans){
            if(plan.breaking) throw new RuntimeException("Breaking plans should not be sent.");
            write.i(Point2.pack(plan.x, plan.y));

            write.s(plan.block.id);
            if(plan.block.rotate){ //no need to write rotation for blocks that don't use it
                write.b((byte)plan.rotation);
            }
            writeObject(write, validClientPlanConfig(plan.config) ? plan.config : null);
        }
    }

    public static ClientBuildPlans readClientPlans(Reads read){
        short amount = read.s();

        if(amount == 0) return null;
        if(amount > maxPlayerPreviewPlans) throw new RuntimeException("Too many plans: " + amount);

        var result = new ClientBuildPlans();
        result.ensureCapacity(Math.min(amount, maxPlayerPreviewPlans));
        for(int i = 0; i < amount; i++){
            int x = read.us();
            int y = read.us();
            Block block = Vars.content.block(read.us());
            if(block == null) throw new ArcRuntimeException("Invalid block ID in block plans! Client is likely using an incorrectly configured mod.");
            int rotation = (block.rotate ? read.b() : 0);
            Object config = readClientPlanConfig(read);
            BuildPlan plan = new BuildPlan(x, y, rotation, block, config);
            result.add(plan);
        }

        return result;
    }

    static @Nullable Object readClientPlanConfig(Reads read){

View on GitHub (pinned to f695ad7e60)

Solutions

  1. Ensure client-side truncation to maxPlayerPreviewPlans before send.
  2. Server: catch the RuntimeException and kick the sender.

Example fix

// before (client sends everything)
previewPlansAssembling.addAll(all);

// after (client truncates to the cap)
int added = Math.min(all.size, maxPlayerPreviewPlans - previewPlansAssembling.size);
for(int i = 0; i < added; i++) previewPlansAssembling.add(all.get(i));
Defensive patterns

Strategy: validation

Validate before calling

// Before sending preview plans, truncate to the cap.
if(plans.size > maxPlayerPreviewPlans){
    throw new IllegalStateException("Too many preview plans: " + plans.size);
}

Try / catch

try { ClientBuildPlans p = TypeIO.readClientPlans(read); }
catch(RuntimeException e){ Log.err("Too many client plans", e); con.kick("Invalid packet."); }

Prevention

When it happens

Trigger: A client preview-plans packet's short count field exceeds 1000.

Common situations: A modified client that skips truncation; a mod; packet fuzzing.

Related errors


AI-assisted analysis of Anuken/Mindustry@f695ad7e60 (2026-08-14). Data as JSON: /api/errors/0679e605238a836c. Report an issue: GitHub.