-
-
Save boy0001/e12ff4a80ad06a15ff24 to your computer and use it in GitHub Desktop.
PlotGen
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.boydti.gen; | |
import java.util.ArrayList; | |
import java.util.List; | |
import java.util.Random; | |
import org.bukkit.World; | |
import org.bukkit.block.Biome; | |
import org.bukkit.generator.BlockPopulator; | |
import org.bukkit.generator.ChunkGenerator; | |
public class Gen extends ChunkGenerator { | |
public final int PLOT_HEIGHT = 64; // Plot height of 64 | |
public final int PLOT_WIDTH = 42; // Plot width of 42 | |
public final int ROAD_WIDTH = 7; // Road width of 7 | |
public final short ROAD_BLOCK = 155; // Quartz | |
public final short MAIN_BLOCK = 1; // Stone | |
public final short WALL_BLOCK = 7; // Bedrock | |
public final short BORDER_BLOCK = 44; // Stone slab | |
public final short[] FLOOR_BLOCK = new short[] {3, 19}; // Grass and sponge | |
/** | |
* I'm using my PseudoRandom class as it's more efficient and we don't need secure randomness | |
*/ | |
public final PseudoRandom RANDOM = new PseudoRandom(); | |
/** | |
* This cache maps the indexes of the x,y,z to the chunk blocks | |
* - It's not necessary but speeds things up | |
*/ | |
public short[][][] CACHE_I = null; | |
public short[][][] CACHE_J = null; | |
// These variables will be calculated based on the configuration above ^ | |
public final int total_width; | |
public final int road_width_lower; | |
public final int road_width_upper; | |
/** | |
* The base template used for all chunks | |
*/ | |
private short[][] base_result; | |
public Gen() { | |
initCache(); | |
total_width = PLOT_WIDTH + ROAD_WIDTH; | |
// Calculating the bottom and top road portions (this is for a PlotSquared compatible generator, but you can have any offset you want) | |
if ((ROAD_WIDTH % 2) == 0) { | |
road_width_lower = ROAD_WIDTH / 2 - 1; | |
} else { | |
road_width_lower = ROAD_WIDTH / 2; | |
} | |
road_width_upper = road_width_lower + PLOT_WIDTH + 1; | |
// Since all plots will have a bedrock base, we can have a partially made template that we use for each chunk | |
base_result = new short[1 + PLOT_HEIGHT / 16][]; | |
for (int x = 0; x < 16; x++) { | |
for (int z = 0; z < 16; z++) { | |
setBlock(base_result, x, 0, z, (short) 7); | |
} | |
} | |
} | |
/** | |
* This method will cache a map the x,y,z coordinates to the chunk blocks (chunks are broken into blocks of 16x16) | |
*/ | |
public void initCache() { | |
if (CACHE_I == null) { | |
CACHE_I = new short[256][16][16]; | |
CACHE_J = new short[256][16][16]; | |
for (int x = 0; x < 16; x++) { | |
for (int z = 0; z < 16; z++) { | |
for (int y = 0; y <= PLOT_HEIGHT; y++) { | |
short i = (short) (y >> 4); | |
short j = (short) (((y & 0xF) << 8) | (z << 4) | x); | |
CACHE_I[y][x][z] = i; | |
CACHE_J[y][x][z] = j; | |
} | |
} | |
} | |
} | |
} | |
@Override | |
public List<BlockPopulator> getDefaultPopulators(World world) { | |
// Here is a good place to set any world settings such as mob spawning: | |
world.setSpawnFlags(false, false); | |
world.setAmbientSpawnLimit(0); | |
world.setAnimalSpawnLimit(0); | |
world.setMonsterSpawnLimit(0); | |
world.setWaterAnimalSpawnLimit(0); | |
// Here we would return our populator if we had one... | |
return new ArrayList<>(); | |
} | |
/** | |
* This part here is where your generation goes | |
*/ | |
@Override | |
public short[][] generateExtBlockSections(World world, Random r, int cx, int cz, BiomeGrid biomes) { | |
short[][] result = new short[1 + PLOT_HEIGHT / 16][];; | |
try { | |
// If you have any random elements to your generation, you will want to set the state of the random class | |
RANDOM.state = pair(cx, cz); | |
// Copy over our template array which has the bedrock already there | |
result[0] = new short[4096]; | |
System.arraycopy( base_result[0], 0, result[0], 0, base_result[0].length ); | |
// We want all plots to be the same | |
// To do this we will need to reduce the coordinates to the same base location | |
// To get the world coord from a chunk coord we multiply by 16 `cx << 4` | |
// Then we find the remainder of that `(cx << 4) % total_width` | |
// We don't want negative numbers though so add the `total_width` if the remainder is less than 0 | |
// We have to do this as the plot size will not necessarily have to be a multiple of 16, and so it won't always align to the chunk | |
// If the total width is a multiple of 16, you can in fact make some neat optimizations, see PlotSquaredMG source for more info | |
int bx = (cx << 4) % total_width + (cx < 0 ? total_width : 0); | |
int bz = (cz << 4) % total_width + (cz < 0 ? total_width : 0); | |
// This is our main loop where we go over all the columns in the chunk and set the blocks | |
for (int x = 0; x < 16; x++) { | |
for (int z = 0; z < 16; z++) { | |
biomes.setBiome(x, z, Biome.FOREST); | |
// Getting the reduced coordinate | |
int xx = (x + bx); | |
int zz = (z + bz); | |
// If it's greater than the total width, we need to reduce it | |
// Although we reduced the chunk coordinates before, that only means the base coordinate of the chunk is reduced | |
// The top coordinate could still be outside our desired range | |
if (xx >= total_width) xx -= total_width; | |
if (zz >= total_width) zz -= total_width; | |
// ROAD | |
if (xx < road_width_lower || zz < road_width_lower || xx > road_width_upper || zz > road_width_upper) { | |
for (int y = 0; y < PLOT_HEIGHT; y++) setBlock(result, x, y, z, ROAD_BLOCK); | |
} | |
// WALL | |
else if (xx == road_width_lower || zz == road_width_lower || xx == road_width_upper || zz == road_width_upper) { | |
// Set the wall block | |
for (int y = 0; y < PLOT_HEIGHT; y++) setBlock(result, x, y, z, WALL_BLOCK); | |
// Set the border block (on top of the wall) | |
setBlock(result, x, PLOT_HEIGHT, z, BORDER_BLOCK); | |
} | |
// PLOT | |
else { | |
// Set the main plot block | |
for (int y = 0; y < PLOT_HEIGHT - 1; y++) setBlock(result, x, y, z, MAIN_BLOCK); | |
// Set the plot floor | |
setBlock(result, x, PLOT_HEIGHT - 1, z, FLOOR_BLOCK); | |
} | |
} | |
} | |
} | |
catch (Exception e) { | |
// Normally if something goes wrong in your code it will fail silently with world generators | |
// Having this try/catch will help recover the exception | |
e.printStackTrace(); | |
} | |
return result; | |
} | |
/** | |
* This simple pairing function is used for the seed for each chunk, | |
* - This is useful if you want generation to appear random, but be the same each time | |
* - You could also use a simple hash function like `return x + y * 31` - but this looks fancier | |
* @param x | |
* @param y | |
* @return | |
*/ | |
public int pair(int x, int y) { | |
long hash; | |
if (x >= 0) { | |
if (y >= 0) { | |
hash = (x * x) + (3 * x) + (2 * x * y) + y + (y * y) + 2; | |
} else { | |
final int y1 = -y; | |
hash = (x * x) + (3 * x) + (2 * x * y1) + y1 + (y1 * y1) + 1; | |
} | |
} else { | |
final int x1 = -x; | |
if (y >= 0) { | |
hash = -((x1 * x1) + (3 * x1) + (2 * x1 * y) + y + (y * y)); | |
} else { | |
final int y1 = -y; | |
hash = -((x1 * x1) + (3 * x1) + (2 * x1 * y1) + y1 + (y1 * y1) + 1); | |
} | |
} | |
return (int) (hash % Integer.MAX_VALUE); | |
} | |
public void setBlock(final short[][] result, final int x, final int y, final int z, final short blkid) { | |
if (result[CACHE_I[y][x][z]] == null) { | |
result[CACHE_I[y][x][z]] = new short[4096]; | |
} | |
result[CACHE_I[y][x][z]][CACHE_J[y][x][z]] = blkid; | |
} | |
public void setBlock(final short[][] result, final int x, final int y, final int z, final short[] blkid) { | |
if (blkid.length == 1) { | |
setBlock(result, x, y, z, blkid[0]); | |
} | |
short id = blkid[RANDOM.random(blkid.length)]; | |
if (result[CACHE_I[y][x][z]] == null) { | |
result[CACHE_I[y][x][z]] = new short[4096]; | |
} | |
result[CACHE_I[y][x][z]][CACHE_J[y][x][z]] = id; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.boydti.gen; | |
import org.bukkit.generator.ChunkGenerator; | |
import org.bukkit.plugin.java.JavaPlugin; | |
public class Main extends JavaPlugin { | |
@Override | |
public ChunkGenerator getDefaultWorldGenerator(String worldName, String args) { | |
// If we want to do anything fancy, we can accept arguments | |
// e.g. with Multiverse /mv create <world> normal -g <generator>:<args> | |
// We could then pass these arguments to the generator e.g. if we wanted different blocks for the road | |
return new Gen(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.boydti.gen; | |
public class PseudoRandom { | |
public long state = 1; | |
public long nextLong() { | |
final long a = state; | |
state = xorShift64(a); | |
return a; | |
} | |
public long xorShift64(long a) { | |
a ^= (a << 21); | |
a ^= (a >>> 35); | |
a ^= (a << 4); | |
return a; | |
} | |
public int random(final int n) { | |
if (n == 1) { | |
return 0; | |
} | |
final long r = ((nextLong() >>> 32) * n) >> 32; | |
return (int) r; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment