Add new /forge gen command to generate large amounts of chunks.

Usage: /forge gen <position> <count> [dimension] [notifications]
Position is in Block Coords, and can be relative to the player. These will be converted to chunk coords for you.
Count is the number of chunks to load. This is not a radius, actual total number of chunks.
Dimension is optional, you can use this to pregen other worlds.
Notifications is the chunk interval to inform you of the generating progress. This is optional and will be 5% or 100 whichever is higher.

Added new config option to modify vanilla world gen to fix various cascading issues. MC-117810 MC-114332 and more.
This WILL change block placement from vanilla world gen. So this is a opt-in value. Do not report differences in worlds with this flag enabled.
This commit is contained in:
LexManos 2017-09-15 19:01:59 -07:00
parent 036191cd52
commit 0ad4218bc2
9 changed files with 349 additions and 9 deletions

View File

@ -35,7 +35,7 @@
}
public void func_180622_a(World p_180622_1_, Random p_180622_2_, ChunkPrimer p_180622_3_, int p_180622_4_, int p_180622_5_, double p_180622_6_)
@@ -88,4 +81,24 @@
@@ -88,4 +81,25 @@
EXTRA_TREES,
MUTATED;
}
@ -48,7 +48,8 @@
+ int count = 3 + rand.nextInt(6);
+ for (int i = 0; i < count; i++)
+ {
+ BlockPos blockpos = pos.func_177982_a(rand.nextInt(16), rand.nextInt(28) + 4, rand.nextInt(16));
+ int offset = net.minecraftforge.common.ForgeModContainer.fixVanillaCascading ? 8 : 0; // MC-114332
+ BlockPos blockpos = pos.func_177982_a(rand.nextInt(16) + offset, rand.nextInt(28) + 4, rand.nextInt(16) + offset);
+
+ net.minecraft.block.state.IBlockState state = worldIn.func_180495_p(blockpos);
+ if (state.func_177230_c().isReplaceableOreGen(state, worldIn, blockpos, net.minecraft.block.state.pattern.BlockMatcher.func_177642_a(Blocks.field_150348_b)))

View File

@ -93,7 +93,7 @@
for (int l1 = 0; l1 < 16; ++l1)
{
this.field_177467_w.func_180709_b(this.field_185952_n, this.field_185954_p, blockpos.func_177982_a(this.field_185954_p.nextInt(16), this.field_185954_p.nextInt(108) + 10, this.field_185954_p.nextInt(16)));
@@ -402,17 +434,22 @@
@@ -402,17 +434,23 @@
int i2 = this.field_185952_n.func_181545_F() / 2 + 1;
@ -106,7 +106,9 @@
+ if (net.minecraftforge.event.terraingen.TerrainGen.populate(this, this.field_185952_n, this.field_185954_p, p_185931_1_, p_185931_2_, false, net.minecraftforge.event.terraingen.PopulateChunkEvent.Populate.EventType.NETHER_LAVA2))
for (int j2 = 0; j2 < 16; ++j2)
{
this.field_177473_x.func_180709_b(this.field_185952_n, this.field_185954_p, blockpos.func_177982_a(this.field_185954_p.nextInt(16), this.field_185954_p.nextInt(108) + 10, this.field_185954_p.nextInt(16)));
- this.field_177473_x.func_180709_b(this.field_185952_n, this.field_185954_p, blockpos.func_177982_a(this.field_185954_p.nextInt(16), this.field_185954_p.nextInt(108) + 10, this.field_185954_p.nextInt(16)));
+ int offset = net.minecraftforge.common.ForgeModContainer.fixVanillaCascading ? 8 : 0; // MC-117810
+ this.field_177473_x.func_180709_b(this.field_185952_n, this.field_185954_p, blockpos.func_177982_a(this.field_185954_p.nextInt(16) + offset, this.field_185954_p.nextInt(108) + 10, this.field_185954_p.nextInt(16) + offset));
}
biome.func_180624_a(this.field_185952_n, this.field_185954_p, new BlockPos(i, 0, j));

View File

@ -0,0 +1,12 @@
--- ../src-base/minecraft/net/minecraft/world/gen/feature/WorldGenLakes.java
+++ ../src-work/minecraft/net/minecraft/world/gen/feature/WorldGenLakes.java
@@ -161,7 +161,8 @@
if (p_180709_1_.func_175675_v(p_180709_3_.func_177982_a(k2, 4, l3)))
{
- p_180709_1_.func_180501_a(p_180709_3_.func_177982_a(k2, 4, l3), Blocks.field_150432_aD.func_176223_P(), 2);
+ int flag = net.minecraftforge.common.ForgeModContainer.fixVanillaCascading ? 2| 16 : 2; //Forge: With bit 5 unset, it will notify neighbors and load adjacent chunks.
+ p_180709_1_.func_180501_a(p_180709_3_.func_177982_a(k2, 4, l3), Blocks.field_150432_aD.func_176223_P(), flag); //Forge
}
}
}

View File

@ -30,6 +30,8 @@ import net.minecraftforge.event.world.WorldEvent;
import net.minecraftforge.fml.common.FMLLog;
import net.minecraftforge.fml.common.eventhandler.EventPriority;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.gameevent.TickEvent;
import net.minecraftforge.fml.common.gameevent.TickEvent.ServerTickEvent;
public class ForgeInternalHandler
{
@ -68,7 +70,7 @@ public class ForgeInternalHandler
@SubscribeEvent(priority = EventPriority.HIGHEST)
public void onDimensionSave(WorldEvent.Save event)
{
ForgeChunkManager.saveWorld(event.getWorld());
ForgeChunkManager.saveWorld(event.getWorld());
}
@SubscribeEvent(priority = EventPriority.HIGHEST)
@ -78,4 +80,11 @@ public class ForgeInternalHandler
if (event.getWorld() instanceof WorldServer)
FakePlayerFactory.unloadWorld((WorldServer) event.getWorld());
}
@SubscribeEvent
public void onServerTick(ServerTickEvent event)
{
WorldWorkerManager.tick(event.phase == TickEvent.Phase.START);
}
}

View File

@ -91,6 +91,7 @@ import net.minecraftforge.fml.common.event.FMLModIdMappingEvent;
import net.minecraftforge.fml.common.event.FMLPostInitializationEvent;
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
import net.minecraftforge.fml.common.event.FMLServerStartingEvent;
import net.minecraftforge.fml.common.event.FMLServerStoppingEvent;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.gameevent.PlayerEvent;
import net.minecraftforge.fml.common.network.NetworkRegistry;
@ -117,6 +118,8 @@ public class ForgeModContainer extends DummyModContainer implements WorldAccessC
public static boolean alwaysSetupTerrainOffThread = false; // In RenderGlobal.setupTerrain, always force the chunk render updates to be queued to the thread
public static int dimensionUnloadQueueDelay = 0;
public static boolean logCascadingWorldGeneration = true; // see Chunk#logCascadingWorldGeneration()
public static boolean fixVanillaCascading = false; // There are various places in vanilla that cause cascading worldgen. Enabling this WILL change where blocks are placed to prevent this.
// DO NOT contact Forge about worldgen not 'matching' vanilla if this flag is set.
static final Logger log = LogManager.getLogger(ForgeVersion.MOD_ID);
@ -288,6 +291,12 @@ public class ForgeModContainer extends DummyModContainer implements WorldAccessC
prop.setLanguageKey("forge.configgui.logCascadingWorldGeneration");
propOrder.add(prop.getName());
prop = config.get(Configuration.CATEGORY_GENERAL, "fixVanillaCascading", false,
"Fix vanilla issues that cause worldgen cascading. This DOES change vanilla worldgen so DO NOT report bugs related to world differences if this flag is on.");
fixVanillaCascading = prop.getBoolean();
prop.setLanguageKey("forge.configgui.fixVanillaCascading");
propOrder.add(prop.getName());
prop = config.get(Configuration.CATEGORY_GENERAL, "dimensionUnloadQueueDelay", 0,
"The time in ticks the server will wait when a dimension was queued to unload. " +
"This can be useful when rapidly loading and unloading dimensions, like e.g. throwing items through a nether portal a few time per second.");
@ -509,6 +518,13 @@ public class ForgeModContainer extends DummyModContainer implements WorldAccessC
{
evt.registerServerCommand(new ForgeCommand());
}
@Subscribe
public void serverStopping(FMLServerStoppingEvent evt)
{
WorldWorkerManager.clear();
}
@Override
public NBTTagCompound getDataForWriting(SaveHandler handler, WorldInfo info)
{

View File

@ -0,0 +1,84 @@
/*
* Minecraft Forge
* Copyright (c) 2016.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.minecraftforge.common;
import java.util.ArrayList;
import java.util.List;
public class WorldWorkerManager
{
private static List<IWorker> workers = new ArrayList<IWorker>();
private static long startTime = -1;
public static void tick(boolean start)
{
if (start)
{
startTime = System.currentTimeMillis();
return;
}
IWorker task = getNext();
if (task == null)
return;
long time = 50 - (System.currentTimeMillis() - startTime);
if (time < 10)
time = 10; //If ticks are lagging, give us at least 10ms to do something.
time += System.currentTimeMillis();
while (System.currentTimeMillis() < time)
{
task.work();
if (!task.hasWork())
{
time = 0; //Break loop
remove(task);
}
}
}
public static synchronized void addWorker(IWorker worker)
{
workers.add(worker);
}
private static synchronized IWorker getNext()
{
return workers.size() > 0 ? workers.get(0) : null;
}
private static synchronized void remove(IWorker worker)
{
workers.remove(worker);
}
//Internal only, used to clear everything when the server shuts down.
public static synchronized void clear()
{
workers.clear();
}
public static interface IWorker
{
boolean hasWork();
void work();
}
}

View File

@ -0,0 +1,160 @@
/*
* Minecraft Forge
* Copyright (c) 2016.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.minecraftforge.server.command;
import java.util.ArrayDeque;
import java.util.Queue;
import net.minecraft.command.ICommandSender;
import net.minecraft.server.management.PlayerChunkMapEntry;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.text.TextComponentTranslation;
import net.minecraft.world.WorldServer;
import net.minecraft.world.chunk.Chunk;
import net.minecraftforge.common.DimensionManager;
import net.minecraftforge.common.ForgeChunkManager;
import net.minecraftforge.common.ForgeModContainer;
import net.minecraftforge.common.WorldWorkerManager.IWorker;
public class ChunkGenWorker implements IWorker
{
private final ICommandSender listener;
protected final BlockPos start;
protected final int total;
private final int dim;
private final Queue<BlockPos> queue;
private final int notificationFrequency;
private int lastNotification = 0;
private int genned = 0;
private int oldUnloadDelay = -1;
public ChunkGenWorker(ICommandSender listener, BlockPos start, int total, int dim, int interval)
{
this.listener = listener;
this.start = start;
this.total = total;
this.dim = dim;
this.queue = buildQueue();
this.notificationFrequency = interval != -1 ? interval : Math.max(total / 20, 100); //Every 5% or every 100, whichever is more.
}
protected Queue<BlockPos> buildQueue()
{
Queue<BlockPos> ret = new ArrayDeque<BlockPos>();
ret.add(start);
//This *should* spiral outwards, starting on right side, down, left, up, right, but hey we'll see!
int radius = 1;
while (ret.size() < total)
{
for (int q = -radius + 1; q <= radius && ret.size() < total; q++)
ret.add(start.add(radius, 0, q));
for (int q = radius - 1; q >= -radius && ret.size() < total; q--)
ret.add(start.add(q, 0, radius));
for (int q = radius - 1; q >= -radius && ret.size() < total; q--)
ret.add(start.add(-radius, 0, q));
for (int q = -radius + 1; q <= radius && ret.size() < total; q++)
ret.add(start.add(q, 0, -radius));
radius++;
}
return ret;
}
public TextComponentTranslation getStartMessage()
{
return new TextComponentTranslation("commands.forge.gen.start", total, start.getX(), start.getZ(), dim);
}
@Override
public boolean hasWork()
{
return queue.size() > 0;
}
@Override
public void work()
{
BlockPos next = queue.poll();
if (next != null)
{
WorldServer world = DimensionManager.getWorld(dim);
if (world == null)
{
DimensionManager.initDimension(dim);
world = DimensionManager.getWorld(dim);
if (world == null)
{
listener.sendMessage(new TextComponentTranslation("commands.forge.gen.dim_fail", dim));
queue.clear();
return;
}
}
//While we work we don't want to cause world load spam so pause unloading worlds.
if (oldUnloadDelay == -1)
{
oldUnloadDelay = ForgeModContainer.dimensionUnloadQueueDelay;
ForgeModContainer.dimensionUnloadQueueDelay = Integer.MAX_VALUE;
}
if (++lastNotification >= notificationFrequency)
{
listener.sendMessage(new TextComponentTranslation("commands.forge.gen.progress", total - queue.size(), total));
lastNotification = 0;
}
int x = next.getX();
int z = next.getZ();
Chunk target = world.getChunkFromChunkCoords(x, z);
Chunk[] chunks = { target };
if (!target.isTerrainPopulated())
{
// In order for a chunk to populate, The chunks around its bottom right corner need to be loaded.
// So lets load those chunks, but this needs to be done in a certain order to make this trigger.
// So this does load more chunks then it should, and is a hack, but lets go!.
chunks = new Chunk[] {
target,
world.getChunkFromChunkCoords(x + 1, z),
world.getChunkFromChunkCoords(x + 1, z + 1),
world.getChunkFromChunkCoords(x, z + 1),
};
genned++;
}
for (Chunk chunk : chunks) //Now lets unload them. Note: Saving is done off thread so there may be cache hits, but this should still unload everything.
{
PlayerChunkMapEntry watchers = world.getPlayerChunkMap().getEntry(chunk.x, chunk.z);
if (watchers == null) //If there are no players watching this, this will be null, so we can unload.
world.getChunkProvider().queueUnload(chunk);
}
}
if (queue.size() == 0)
{
listener.sendMessage(new TextComponentTranslation("commands.forge.gen.complete", genned, total, dim));
ForgeModContainer.dimensionUnloadQueueDelay = oldUnloadDelay;
}
}
}

View File

@ -20,14 +20,21 @@
package net.minecraftforge.server.command;
import java.text.DecimalFormat;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import javax.annotation.Nullable;
import net.minecraft.command.CommandBase;
import net.minecraft.command.CommandException;
import net.minecraft.command.ICommandSender;
import net.minecraft.command.WrongUsageException;
import net.minecraft.server.MinecraftServer;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.text.TextComponentTranslation;
import net.minecraftforge.common.DimensionManager;
import net.minecraftforge.common.WorldWorkerManager;
import net.minecraftforge.server.ForgeTimeTracker;
public class ForgeCommand extends CommandBase {
@ -64,22 +71,47 @@ public class ForgeCommand extends CommandBase {
}
else if ("tps".equals(args[0]))
{
displayTPS(server, sender,args);
displayTPS(server, sender, args);
}
else if ("tpslog".equals(args[0]))
/*else if ("tpslog".equals(args[0]))
{
doTPSLog(server, sender,args);
doTPSLog(server, sender, args);
}
*/
else if ("track".equals(args[0]))
{
handleTracking(server, sender, args);
}
else if ("gen".equals(args[0]))
{
handleGen(server, sender, args);
}
else
{
throw new WrongUsageException("commands.forge.usage");
}
}
@Override
public List<String> getTabCompletions(MinecraftServer server, ICommandSender sender, String[] args, @Nullable BlockPos targetPos)
{
if (args.length <= 1)
return getListOfStringsMatchingLastWord(args, new String[] {"help", "tps", "track", "gen"});
switch (args[0].toLowerCase(Locale.ENGLISH))
{
case "gen":
if (args.length > 1 && args.length <= 4)
return getTabCompletionCoordinate(args, 1, targetPos);
if (args.length == 5) //Chunk Count? No completion
return Collections.emptyList();
if (args.length == 6) // Dimension, Add support for names? Get list of ids? Meh
return Collections.emptyList();
break;
}
return Collections.emptyList();
}
private void handleTracking(MinecraftServer server, ICommandSender sender, String[] args) throws CommandException
{
if (args.length != 3)
@ -140,6 +172,23 @@ public class ForgeCommand extends CommandBase {
}
}
private void handleGen(MinecraftServer server, ICommandSender sender, String[] args) throws CommandException
{
// gen x y z chunkCount [dim] [shape?]
if (args.length < 5)
throw new WrongUsageException("commands.forge.gen.usage");
BlockPos blockpos = parseBlockPos(sender, args, 1, false);
int count = parseInt(args[4], 10);
int dim = args.length >= 6 ? parseInt(args[5]) : sender.getEntityWorld().provider.getDimension();
int interval = args.length >= 7 ? parseInt(args[6]) : -1;
BlockPos chunkpos = new BlockPos(blockpos.getX() >> 4, 0, blockpos.getZ() >> 4);
ChunkGenWorker worker = new ChunkGenWorker(sender, chunkpos, count, dim, interval);
sender.sendMessage(worker.getStartMessage());
WorldWorkerManager.addWorker(worker);
}
private static long mean(long[] values)
{
long sum = 0l;

View File

@ -1,6 +1,11 @@
commands.forge.usage=Use /forge <subcommand>. Subcommands are tps, track
commands.forge.usage=Use /forge <subcommand>. Subcommands are tps, track, gen
commands.forge.usage.tracking=Use /forge track <type> <duration>. Valid types are te (Tile Entities). Duration is < 60.
commands.forge.tps.summary=%s : Mean tick time: %d ms. Mean TPS: %d
commands.forge.gen.usage=Use /forge gen <x> <y> <z> <chunkCount> [dimension] [interval]
commands.forge.gen.dim_fail=Failed to load world for dimension %d, Task terminated.
commands.forge.gen.progress=Generation Progress: %d/%d
commands.forge.gen.complete=Finished generating %d new chunks (out of %d) for dimension %d.
commands.forge.gen.start=Starting to generate %d chunks in a spiral around %d, %d in dimension %d.
commands.forge.tracking.te.enabled=Tile Entity tracking enabled for %d seconds.
commands.tree_base.invalid_cmd=Invalid subcommand '%s'!
@ -69,6 +74,8 @@ forge.configgui.playerTicketCount.tooltip=The number of tickets a player can be
forge.configgui.playerTicketCount=Player Ticket Limit
forge.configgui.logCascadingWorldGeneration=Log Cascading World Gen
forge.configgui.logCascadingWorldGeneration.tooltip=Log cascading chunk generation issues during terrain population.
forge.configgui.fixVanillaCascading=Fix Vanilla Cascading
forge.configgui.fixVanillaCascading.tooltip=Fix various bugs in vanilla world gen that causes extra chunks to load. This WILL change your worldgen from vanilla. Do not report differences if this is enabled.
fml.config.sample.basicDouble.tooltip=A double property with no defined bounds.
fml.config.sample.basicDouble=Unbounded Double