[Advanced] Chunk Generatation

Discussion in 'Plugin Development' started by iFamasssxD, Nov 7, 2013.

Thread Status:
Not open for further replies.
  1. Offline

    iFamasssxD

    I was wondering how you can generate a world that is 500x500 in size. Vanilla generation everytime. Then it just cuts off into void after this. I looked into ChunkGenerator classes and this doesnt seem to be what I need since the method generate() is called when every chunk is generated.
    Im going to call for the best here: chasechocolate
     
  2. Offline

    Darq

    I don't know the specifics of the world gen API but I assume that the "generate()" method is called containing both and X and Z coordinate for that chunk. Couldn't you use that to determine if a chunk is outside the range you're looking for? :)
     
  3. Offline

    iFamasssxD

    Darq I dont really think so. No where in the generate code does it really handle that. This is the current generate code I have to generate air:
    Code:java
    1. public byte[] generate(World world, Random random, int cx, int cz) {
    2. byte[] result = new byte[32768];
    3.  
    4. for (int x = 0; x < 16; x++) {
    5. for (int z = 0; z < 16; z++) {
    6. int height = getHeight(world, cx + x * 0.0625, cz + z * 0.0625, 2) + 60;
    7. for (int y = 0; y < height; y++) {
    8. result[(x * 16 + z) * 128 + y] = (byte) Material.AIR.getId();
    9.  
    10. }
    11. }
    12. }
    13. return result;
    14. }


    But I think you may be onto something.
     
  4. Offline

    NathanWolf

    I think you could use a BlockPopulator for this. Install a block populator to the server that checks the chunk coordinates and sets the whole thing to air (or bedrock or whatever you want) if it's out of bounds.
     
  5. Offline

    iFamasssxD

    NathanWolf This might be the way to go. Im not sure how it will be for performance however. This is for a Minigame so it might not be an issue since its loaded on the start and thats it.
     
  6. Offline

    NathanWolf

    That should be fine - that's when you install your block populator (in your plugin onEnable)

    Performance-wise you should be ok- it will be expensive to create the new chunks, but once your world is built out no more chunks should get made!
     
  7. Offline

    iFamasssxD

    NathanWolf Well after some testing this doesnt seem like the way to go. This populator runs before any other structures are generated. Also its very server intensive since it runs so much.
     
  8. Offline

    NathanWolf

    That's odd! I'm using a BlockPopulator in my plugin to add items to naturally generated chests, it seems to work great. I also tested some weird stuff like replacing all grass blocks with glowstone- so I imagine what you're trying to do should work...

    Here's what I'd suggest, slightly modified from what I do in my plugin:

    Code:java
    1.  
    2. // In your event handler
    3. @EventHandler
    4. public void onWorldInit(WorldInitEvent event) {
    5. World world = event.getWorld();
    6. world.getPopulators().add(new VoidBlockPopulator());
    7. }
    8. }
    9.  
    10. // Blockpopulator class:
    11. package com.elmakers.mine.bukkit.plugins.magic;
    12.  
    13. import java.util.Random;
    14.  
    15. import org.bukkit.Chunk;
    16. import org.bukkit.Material;
    17. import org.bukkit.World;
    18. import org.bukkit.block.Block;
    19. import org.bukkit.generator.BlockPopulator;
    20.  
    21. public class VoidBlockPopulator extends BlockPopulator {
    22.  
    23. public VoidBlockPopulator() {
    24. }
    25.  
    26. protected void clearChunk(Chunk chunk) {
    27.  
    28. for (int x = 0; x < 15; x++) {
    29. for (int z = 0; z < 15; z++) {
    30. for (int y = 0; y < 255; y++) {
    31. Block block = chunk.getBlock(x, y, z);
    32. block.setType(Material.AIR);
    33. }
    34. }
    35. }
    36. }
    37.  
    38. @Override
    39. public void populate(World world, Random random, Chunk source) {
    40. // This should leave a 1024x1024 block area in the center
    41. if (source.getX() < -32 || source.getX() > 32 || source.getZ() < -32 || source.getZ() > 32) {
    42. clearChunk(source);
    43. }
    44. }
    45.  
    46. }
    47.  


    Is that like what you tried?


    It might be a bit expensive, but it will only happen a few times before the borders are populated ... and it's really not that bad, certainly no worse than what goes on during actual world generation, I'd bet!
     
  9. Offline

    iFamasssxD

    NathanWolf
    Well I think it would be worse since this is after all the blocks are in place and its going through and setting them all to air. Also as I mentioned this goes before Mineshafts and other things are generated.
     
  10. Offline

    NathanWolf

    Are you sure? As I mentioned I'm using them to populate chests (like in dungeons, strongholds, etc)- surely those get generated after mineshafts and other structures?

    I agree it's a shame you go through the default world generation first only to overwrite it with air, it's wasted time- but probably worth trying if this is what you want :)
     
  11. Offline

    FinalFred

    I once tried doing exactly what you suggested (block popular to destroy blocks) but unfortunately it's terribly, terribly inefficient in Bukkit. It causes lag that's just unreasonable, even if it's a one time thing. This is speaking from experience when the build limit was only 128. Now that the height doubled, the lag is probably even worse.

    What I ended up doing was something similar to what iFamasssxD said he was doing... but my approach was a pretty weird abuse of the Bukkit API. I would get scolded so badly if I posted it! :(

    Show Spoiler

    Shhhh it'll be our little secret. The general idea is make a custom chunk generator that just hooks into whatever Bukkit is doing. The only way to do that is with CraftBukkit references (frowned upon).

    To really figure out what you need to do for the current version of Bukkit, you should grab the CraftBukkit source from the git repo and look for whatever generates chunks. It's probably still called InternalChunkGenerator, or something similar.



    Disclaimer: Old code, you will have to (at the VERY least) change imports. It might not even work at all anymore. Regardless, the following "used" to work ;)
    ------------------------------------------
    import java.util.Random;

    import net.minecraft.server.Chunk;
    import net.minecraft.server.IChunkProvider;
    import net.minecraft.server.IProgressUpdate;

    import org.bukkit.World;
    import org.bukkit.craftbukkit.generator.InternalChunkGenerator;

    public class RandomRespawnChunkGenerator extends InternalChunkGenerator {

    private RandomRespawnPlugin plugin;
    private InternalChunkGenerator actualGenerator;
    private net.minecraft.server.World world;

    public RandomRespawnChunkGenerator(RandomRespawnPlugin master, net.minecraft.server.World world, InternalChunkGenerator gen) {
    this.plugin = master;
    this.world = world;
    this.actualGenerator = gen;

    plugin.log("Started being mean...");
    }

    @Override
    public boolean canSave() {
    return actualGenerator.canSave();
    }

    @Override
    public Chunk getChunkAt(int arg0, int arg1) {
    if (plugin.isChunkOutOfBounds(arg0, arg1)) {
    return getOrCreateChunk(arg0,arg1);
    }
    return actualGenerator.getChunkAt(arg0,arg1);
    }

    @Override
    public void getChunkAt(IChunkProvider arg0, int arg1, int arg2) {
    if (plugin.isChunkOutOfBounds(arg1, arg2)) {
    // Nothing! :)
    return;
    }
    actualGenerator.getChunkAt(arg0,arg1,arg2);
    }

    @Override
    public Chunk getOrCreateChunk(int arg0, int arg1) {
    if (plugin.isChunkOutOfBounds(arg0, arg1)) {
    byte[] types = new byte[32768];
    Chunk chunk = new Chunk(world, types, arg0, arg1);
    chunk.initLighting();

    return chunk;
    }
    return actualGenerator.getOrCreateChunk(arg0,arg1);
    }

    @Override
    public boolean isChunkLoaded(int arg0, int arg1) {
    if (plugin.isChunkOutOfBounds(arg0, arg1)) {
    return true;
    }
    return actualGenerator.isChunkLoaded(arg0,arg1);
    }

    @Override
    public boolean saveChunks(boolean arg0, IProgressUpdate arg1) {
    return actualGenerator.saveChunks(arg0,arg1);
    }

    @Override
    public boolean unloadChunks() {
    return actualGenerator.unloadChunks();
    }

    @Override
    public byte[] generate(World arg0, Random arg1, int arg2, int arg3) {
    return actualGenerator.generate(arg0,arg1,arg2,arg3);
    }

    @Override
    public boolean canSpawn(org.bukkit.World world, int x, int z) {
    return true; // We change the spawn point anyway.
    }
    }
    ------------------------------------------------------------
    public void hackCraftBukkit(World world) {
    if (world == null) {
    log("Unable to hack CraftBukkit -- world does not yet exist");
    System.exit(1);
    return;
    }

    log("Trying to hack world " + world.getName());

    world.getPopulators().add(new RandomRespawnBlockPopulator(this));

    net.minecraft.server.WorldServer w = ((CraftWorld) world).getHandle();

    if (w == null) {
    log("Unable to hack CraftBukkit -- WorldServer does not yet exist");
    System.exit(1);
    return;
    }
    w.chunkProviderServer.chunkProvider = new RandomRespawnChunkGenerator(
    this, w,
    (InternalChunkGenerator) w.chunkProviderServer.chunkProvider);
    }



    If this is really a one time thing to make a map and be done with it, I recommend just generating a 500x500 world in vanilla minecraft and then opening up the map in something like MCEdit and just deleting all the chunks you don't want. Well, by "deleting" I mean filling them with air. In my experience MCEdit was significantly faster than Bukkit was at doing that sort of thing.
     
  12. Offline

    clienthax

    Code:java
    1. @EventHandler
    2. public void onChunkLoadEvent(ChunkLoadEvent event)
    3. {
    4.  
    5. Chunk chunk = event.getChunk();
    6. int x = chunk.getX();
    7. int z = chunk.getZ();
    8.  
    9. if((x > 500 || x < 0) || (z > 500 || z < 0))
    10. {
    11. event.getChunk().unload(false, false);
    12. }
    13.  
    14. }
     
  13. Offline

    iFamasssxD

    I havent tested this yet but this still doesnt do what I want. This just never allows the chunk to load. Which would just cause the player to glitch out.

    FinalFred
    So basically you are creating your own InternalGenerator but calling to the original methods for everything else except for the things you want to change? That's genius.

    EDIT by Moderator: merged posts, please use the edit button instead of double posting.
     
    Last edited by a moderator: Jun 5, 2016
  14. Offline

    FinalFred

    That's the idea, yeah. I just realized though it will definitely have to be changed. If nothing else new byte[32768] is no longer valid. I'm not sure how chunks are generated now-a-days.
     
  15. Offline

    iFamasssxD


    Code:
     public byte[] generate(World world, Random random, int cx, int cz) {
            byte[] result = new byte[32768];
     
            for (int x = 0; x < 16; x++) {
                for (int z = 0; z < 16; z++) {
                    int height = getHeight(world, cx + x * 0.0625, cz + z * 0.0625, 2) + 60;
                    for (int y = 0; y < height; y++) {
                        result[(x * 16 + z) * 128 + y] = (byte)Material.AIR.getId();
                    }
                }
            }
     
            return result;
        }
    
    Stole this from DBone's generator. Will this work in that?:p
     
  16. Offline

    FinalFred

    That still looks like it assumes a height of 128, unfortunately. I would look for what CraftBukkit does and just copy it, stripping out pretty much everything except initialization code.
     
  17. Offline

    iFamasssxD

    FinalFred
    Ive been looking for it and I cant seem to find the actually generation code.. Its getting frustrating. I just made a ChunkGenerator that sets all the blocks to generate to 0 unless its in the region, if it is im just making it stone for the moment. Until I can find the actual code to mimic the generation.
     
  18. Offline

    FinalFred

    Looking at the CraftBukkit source, I think stealing the ChunkGenerator generator in CraftWorld is your best bet. The only issue I could see going wrong is the default BlockPopular's getting in your way and making some structures.

    You have to use reflection to access it, but there's a field called "generator" you can over-write in CraftWorld. Steal the chunk generator first, save a reference to it, and just delegate all responsibility of your custom generator to it... with one exception: generateBlockSections. This method should return a 2D byte array. The array is pretty much just 16 slots each set to null (null represents "16x16x16 area is full of air" -- lucky us!) assuming the requested coordinates are out of bounds.

    To deal with the chunk generators... either it's not a problem (if you don't notice anything weird) or you can try to change the CraftWorld.populars field to reflect either the default populators or an empty list. Luckily, Bukkit actually lets you access the populars field without reflection (just call getPopulars() -- it's a list, so you can modify it)... Unless they're being sneaky and wraped the list with unmodifiableList. In that case, just use reflection and over-write it as necessary in response to attempting to generate.
     
  19. Offline

    iFamasssxD

    Well the way I was hoping to do it was by making a FieldGen class extend ChunkGenerator. Then just check the location of the chunk. If its greater then what I want I just return a byte[] of AIR. This works, but the thing I need now is for it to generate real land in the areas I select AKA the 500x500 mentioned earlier. I cant seem to find the original generate() method anywhere in the NMS or Bukkit source to add to the area I need. So im not sure what to use for this.
     
  20. Offline

    iFamasssxD

    Still looking for a solution if anyone has it :p
     
Thread Status:
Not open for further replies.

Share This Page