Skip to content

Commit

Permalink
[1.20.4] Improve NeoForge's chunk generation command (neoforged#364)
Browse files Browse the repository at this point in the history
Special thanks to Jasmine and Gegy for allowing us to use their Fabric Chunk Pregenerator as a basis for this PR.
https://github.com/jaskarth/fabric-chunkpregenerator/tree/master
Fixes neoforged#331
  • Loading branch information
TelepathicGrunt authored Dec 16, 2023
1 parent 0358748 commit 823e543
Show file tree
Hide file tree
Showing 8 changed files with 604 additions and 149 deletions.

This file was deleted.

142 changes: 125 additions & 17 deletions src/main/java/net/neoforged/neoforge/server/command/GenerateCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,41 +5,149 @@

package net.neoforged.neoforge.server.command;

import com.mojang.brigadier.Command;
import com.mojang.brigadier.arguments.BoolArgumentType;
import com.mojang.brigadier.arguments.IntegerArgumentType;
import com.mojang.brigadier.builder.ArgumentBuilder;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import net.minecraft.commands.arguments.DimensionArgument;
import net.minecraft.commands.arguments.coordinates.BlockPosArgument;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.neoforged.neoforge.common.WorldWorkerManager;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.level.ChunkPos;
import net.neoforged.neoforge.server.command.generation.GenerationBar;
import net.neoforged.neoforge.server.command.generation.GenerationTask;

/**
* Special thanks to Jasmine and Gegy for allowing us to use their pregenerator mod as a model to use in NeoForge!
* Original code: <a href="https://github.com/jaskarth/fabric-chunkpregenerator">https://github.com/jaskarth/fabric-chunkpregenerator</a>
*/
class GenerateCommand {
private static GenerationTask activeTask;
private static GenerationBar generationBar;

static ArgumentBuilder<CommandSourceStack, ?> register() {
return Commands.literal("generate")
.requires(cs -> cs.hasPermission(4)) //permission
LiteralArgumentBuilder<CommandSourceStack> builder = Commands.literal("generate").requires(cs -> cs.hasPermission(4)); //permission

builder.then(Commands.literal("start")
.then(Commands.argument("pos", BlockPosArgument.blockPos())
.then(Commands.argument("count", IntegerArgumentType.integer(1))
.then(Commands.argument("dim", DimensionArgument.dimension())
.then(Commands.argument("interval", IntegerArgumentType.integer())
.executes(ctx -> execute(ctx.getSource(), BlockPosArgument.getSpawnablePos(ctx, "pos"), getInt(ctx, "count"), DimensionArgument.getDimension(ctx, "dim"), getInt(ctx, "interval"))))
.executes(ctx -> execute(ctx.getSource(), BlockPosArgument.getSpawnablePos(ctx, "pos"), getInt(ctx, "count"), DimensionArgument.getDimension(ctx, "dim"), -1)))
.executes(ctx -> execute(ctx.getSource(), BlockPosArgument.getSpawnablePos(ctx, "pos"), getInt(ctx, "count"), ctx.getSource().getLevel(), -1))));
.then(Commands.argument("chunkRadius", IntegerArgumentType.integer(1, 1250)) // 20000 block radius limit
.then(Commands.argument("progressBar", BoolArgumentType.bool())
.executes(ctx -> executeGeneration(ctx.getSource(), BlockPosArgument.getSpawnablePos(ctx, "pos"), getInt(ctx, "chunkRadius"), getBool(ctx, "progressBar"))))
.executes(ctx -> executeGeneration(ctx.getSource(), BlockPosArgument.getSpawnablePos(ctx, "pos"), getInt(ctx, "chunkRadius"), true)))));

builder.then(Commands.literal("stop")
.executes(ctx -> stopGeneration(ctx.getSource())));

builder.then(Commands.literal("status")
.executes(ctx -> getGenerationStatus(ctx.getSource())));

builder.then(Commands.literal("help")
.executes(ctx -> getGenerationHelp(ctx.getSource())));

return builder;
}

private static int getInt(CommandContext<CommandSourceStack> ctx, String name) {
return IntegerArgumentType.getInteger(ctx, name);
}

private static int execute(CommandSourceStack source, BlockPos pos, int count, ServerLevel dim, int interval) {
BlockPos chunkpos = new BlockPos(pos.getX() >> 4, 0, pos.getZ() >> 4);
private static boolean getBool(CommandContext<CommandSourceStack> ctx, String name) {
return BoolArgumentType.getBool(ctx, name);
}

private static int executeGeneration(CommandSourceStack source, BlockPos pos, int chunkRadius, boolean progressBar) {
if (activeTask != null) {
source.sendSuccess(() -> Component.translatable("commands.neoforge.chunkgen.already_running"), true);
return Command.SINGLE_SUCCESS;
}

ChunkPos origin = new ChunkPos(pos);

activeTask = new GenerationTask(source.getLevel(), origin.x, origin.z, chunkRadius);
int diameter = chunkRadius * 2 + 1;

if (progressBar) {
generationBar = new GenerationBar();

if (source.getEntity() instanceof ServerPlayer) {
generationBar.addPlayer(source.getPlayer());
}
}

source.sendSuccess(() -> Component.translatable("commands.neoforge.chunkgen.started",
activeTask.getTotalCount(), diameter, diameter, diameter * 16, diameter * 16), true);

activeTask.run(createPregenListener(source));

return Command.SINGLE_SUCCESS;
}

private static int stopGeneration(CommandSourceStack source) {
if (activeTask != null) {
activeTask.stop();

int count = activeTask.getOkCount() + activeTask.getErrorCount() + activeTask.getSkippedCount();
int total = activeTask.getTotalCount();

double percent = (double) count / total * 100.0;
source.sendSuccess(() -> Component.translatable("commands.neoforge.chunkgen.stopped", count, total, percent), true);

generationBar.close();
generationBar = null;
activeTask = null;
} else {
source.sendSuccess(() -> Component.translatable("commands.neoforge.chunkgen.not_running"), false);
}

return Command.SINGLE_SUCCESS;
}

private static int getGenerationStatus(CommandSourceStack source) {
if (activeTask != null) {
int count = activeTask.getOkCount() + activeTask.getErrorCount() + activeTask.getSkippedCount();
int total = activeTask.getTotalCount();

double percent = (double) count / total * 100.0;
source.sendSuccess(() -> Component.translatable("commands.neoforge.chunkgen.status", count, total, percent), true);
} else {
source.sendSuccess(() -> Component.translatable("commands.neoforge.chunkgen.not_running"), false);
}

return Command.SINGLE_SUCCESS;
}

private static int getGenerationHelp(CommandSourceStack source) {
source.sendSuccess(() -> Component.translatable("commands.neoforge.chunkgen.help_line"), false);
return Command.SINGLE_SUCCESS;
}

private static GenerationTask.Listener createPregenListener(CommandSourceStack source) {
return new GenerationTask.Listener() {
@Override
public void update(int ok, int error, int skipped, int total) {
if (generationBar != null) {
generationBar.update(ok, error, skipped, total);
}
}

@Override
public void complete(int error) {
source.sendSuccess(() -> Component.translatable("commands.neoforge.chunkgen.success"), true);

ChunkGenWorker worker = new ChunkGenWorker(source, chunkpos, count, dim, interval);
source.sendSuccess(() -> worker.getStartMessage(source), true);
WorldWorkerManager.addWorker(worker);
if (error > 0) {
source.sendFailure(Component.translatable("commands.neoforge.chunkgen.error"));
}

return 0;
if (generationBar != null) {
generationBar.close();
generationBar = null;
}
activeTask = null;
}
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*
* Copyright (c) NeoForged and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/

package net.neoforged.neoforge.server.command.generation;

import com.google.common.collect.AbstractIterator;
import java.util.Iterator;
import java.util.NoSuchElementException;
import net.minecraft.world.level.ChunkPos;

/**
* Special thanks to Jasmine and Gegy for allowing us to use their pregenerator mod as a model to use in NeoForge!
* Original code: <a href="https://github.com/jaskarth/fabric-chunkpregenerator">https://github.com/jaskarth/fabric-chunkpregenerator</a>
*/
public class CoarseOnionIterator extends AbstractIterator<ChunkPos> {
private final int radius;
private final int cellSize;

private final OnionIterator cells;
private CellIterator cell;

public CoarseOnionIterator(int radius, int cellSize) {
this.radius = radius;
this.cellSize = cellSize;

this.cells = new OnionIterator((radius + cellSize - 1) / cellSize);
}

@Override
protected ChunkPos computeNext() {
OnionIterator cells = this.cells;
CellIterator cell = this.cell;
while (cell == null || !cell.hasNext()) {
if (!cells.hasNext()) {
return this.endOfData();
}

ChunkPos cellPos = cells.next();
this.cell = cell = this.createCellIterator(cellPos);
}

return cell.next();
}

private CellIterator createCellIterator(ChunkPos pos) {
int size = this.cellSize;
int radius = this.radius;

int x0 = pos.x * size;
int z0 = pos.z * size;
int x1 = x0 + size - 1;
int z1 = z0 + size - 1;
return new CellIterator(
Math.max(x0, -radius), Math.max(z0, -radius),
Math.min(x1, radius), Math.min(z1, radius));
}

private static final class CellIterator implements Iterator<ChunkPos> {
private final int x0;
private final int x1;
private final int z1;

private int x;
private int z;

private CellIterator(int x0, int z0, int x1, int z1) {
this.x = x0;
this.z = z0;
this.x0 = x0;
this.x1 = x1;
this.z1 = z1;
}

@Override
public boolean hasNext() {
return this.z <= this.z1;
}

@Override
public ChunkPos next() {
if (!this.hasNext()) {
throw new NoSuchElementException();
}

ChunkPos pos = new ChunkPos(this.x, this.z);
if (this.x++ >= this.x1) {
this.x = this.x0;
this.z++;
}

return pos;
}
}
}
Loading

0 comments on commit 823e543

Please sign in to comment.