package micdoodle8.mods.galacticraft.core.util; import micdoodle8.mods.miccore.IntCache; import net.minecraft.util.MathHelper; import net.minecraft.world.World; import net.minecraft.world.WorldType; import net.minecraft.world.biome.BiomeCache; import net.minecraft.world.biome.BiomeGenBase; import net.minecraft.world.biome.WorldChunkManager; import net.minecraft.world.gen.ChunkProviderSettings; import net.minecraft.world.gen.NoiseGenerator; import net.minecraft.world.gen.NoiseGeneratorOctaves; import net.minecraft.world.gen.NoiseGeneratorPerlin; import net.minecraft.world.gen.layer.GenLayer; import net.minecraft.world.storage.WorldInfo; import org.apache.commons.io.FileUtils; import java.io.File; import java.io.IOException; import java.lang.reflect.Method; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.channels.FileChannel; import java.util.ArrayList; import java.util.Random; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; public class MapGen extends WorldChunkManager implements Runnable { public static boolean disabled; private static final float[] parabolicField = new float[25]; public boolean mapNeedsCalculating = false; public AtomicBoolean finishedCalculating; private AtomicBoolean paused; private AtomicBoolean aborted; private AtomicInteger progressX; private int progressZ; private int biomeMapX; private int biomeMapZ; private int biomeMap0; private int biomeMapCx; private int biomeMapCz; private int biomeMapFactor; private int imagefactor; private BiomeGenBase[] biomeList; private BiomeCache biomeCache; private GenLayer genBiomes; private GenLayer biomeIndexLayer; public File biomeMapFile; private byte[] biomeAndHeightArray = null; private int biomeMapSizeX; private int biomeMapSizeZ; private Random rand; private int[] heights = null; private double[] heighttemp = null; private World world; private WorldType worldType; private WorldInfo worldInfo; private ChunkProviderSettings settings = null; private int[] biomesGrid = null; //Memory efficient to keep re-using the same one. private BiomeGenBase[] biomesGridHeights = null; private int[] biomeCount = null; private final int dimID; static { for (int j = -2; j <= 2; ++j) { for (int k = -2; k <= 2; ++k) { float f = 10.0F / MathHelper.sqrt_float((float) (j * j + k * k) + 0.2F); parabolicField[j + 2 + (k + 2) * 5] = f; } } } public MapGen(World worldIn, int sx, int sz, int cx, int cz, int scale, File file) { this.dimID = GCCoreUtil.getDimensionID(worldIn); if (MapGen.disabled) { this.mapNeedsCalculating = false; return; } this.biomeMapSizeX = sx; this.biomeMapSizeZ = sz; this.biomeMapFactor = scale; int progress = this.checkProgress(file); if (progress < 0) { this.mapNeedsCalculating = false; return; } this.mapNeedsCalculating = true; this.rand = new Random(); this.finishedCalculating = new AtomicBoolean(); this.paused = new AtomicBoolean(); this.aborted = new AtomicBoolean(); this.biomeMapCx = cx >> 4; this.biomeMapCz = cz >> 4; this.biomeMapFile = file; this.imagefactor = 16 / biomeMapFactor; if (this.imagefactor < 1) { this.imagefactor = 1; } int limitX = biomeMapSizeX * biomeMapFactor / 32; int limitZ = biomeMapSizeZ * biomeMapFactor / 32; this.biomeMap0 = -limitZ; this.biomeMapX = -limitX; this.biomeMapZ = this.biomeMap0; this.progressX = new AtomicInteger(); this.progressZ = 0; this.world = worldIn; this.worldInfo = worldIn.getWorldInfo(); long seed = worldInfo.getSeed(); this.biomeList = BiomeGenBase.getBiomeGenArray(); this.biomeCache = new BiomeCache(this); this.worldType = worldInfo.getTerrainType(); String options = worldInfo.getGeneratorOptions(); GenLayer[] agenlayer; try { if (options != null) { this.settings = ChunkProviderSettings.Factory.jsonToFactory(options).func_177864_b(); } if (CompatibilityManager.isBOPWorld(this.worldType)) { Object settingsBOP = CompatibilityManager.classBOPws.getConstructor(String.class).newInstance(options); Method bopSetup = CompatibilityManager.classBOPwcm.getMethod("setupBOPGenLayers", long.class, CompatibilityManager.classBOPWorldType, settingsBOP.getClass()); agenlayer = (GenLayer[]) bopSetup.invoke(null, seed, this.worldType, settingsBOP); } else { agenlayer = GenLayer.initializeAllBiomeGenerators(seed, worldType, options); } agenlayer = getModdedBiomeGenerators(worldType, seed, agenlayer); } catch (Exception e) { GCLog.severe("Galacticraft background map image generator not able to run (probably a mod conflict?)"); GCLog.severe("Please report this at https://github.com/micdoodle8/Galacticraft/issues/2481"); e.printStackTrace(); this.mapNeedsCalculating = false; MapGen.disabled = true; return; } this.genBiomes = agenlayer[0]; this.biomeIndexLayer = agenlayer[1]; GCLog.debug("Starting map generation " + file.getName() + " top left " + ((biomeMapCx - limitX) * 16) + "," + ((biomeMapCz - limitZ) * 16)); if (progress > 0) { this.resumeProgress(progress); } } /** * Returns -1 if the map file saved by the server on disk is already complete. * Returns 0 if a new file is needed * Returns >0 if an existing file is in progress (in this case, initialises * biomeAndHeightArray and smaller arrays to match that existing file. */ private int checkProgress(File file) { if (!file.exists()) return 0; if (file.length() != biomeMapSizeX * biomeMapSizeZ * 2) { return 0; } int progress = 0; if (file.getName().equals("Overworld" + MapUtil.OVERWORLD_LARGEMAP_WIDTH + ".bin")) { int len = (int) file.length(); FileChannel fc; try { fc = (FileChannel.open(file.toPath())); fc.position(len - 8); byte[] flagdata = new byte[8]; ByteBuffer databuff = ByteBuffer.wrap(flagdata); fc.read(databuff); if (testFlag(flagdata)) { databuff.order(ByteOrder.BIG_ENDIAN); databuff.position(0); progress = databuff.getInt(); } else { //No progress flag data, therefore the file must be complete return -1; } fc.close(); } catch (IOException e) { e.printStackTrace(); } if (progress > 0 && progress <= biomeMapSizeX - imagefactor) { try { this.biomeAndHeightArray = FileUtils.readFileToByteArray(file); this.initialiseSmallerArrays(); return progress; } catch (IOException e) { e.printStackTrace(); this.biomeAndHeightArray = null; } } return 0; } //Smaller files are always complete, if present and name and size match return -1; } @Override public void run() { //Allow some time for the pause on any other map gen thread to become effective try { Thread.currentThread().sleep(90); } catch (InterruptedException e) {} long seed = worldInfo.getSeed(); this.initialise(seed); //Generate this map from start to finish within the thread while (!this.aborted.get()) { if (this.paused.get()) { try { //Sleep for a bit, next time around maybe will not be paused? Thread.currentThread().sleep(1211); } catch (InterruptedException e) {} } else { //Do the actual work of the thread if (this.BiomeMapOneTick()) { break; //finished! } } } this.finishedCalculating.set(true); } public void pause() { this.paused.set(true); } public void resume() { this.paused.set(false); } public void abort() { this.aborted.set(true); } private void flagProgress() { int progX = this.progressX.get(); if (progX > biomeMapSizeX - imagefactor) return; GCLog.debug("Saving partial map image progress " + progX); int offset = this.biomeAndHeightArray.length; this.biomeAndHeightArray[offset - 1] = (byte) 0xFE; this.biomeAndHeightArray[offset - 2] = (byte) 0x06; this.biomeAndHeightArray[offset - 3] = (byte) 0x03; this.biomeAndHeightArray[offset - 4] = (byte) 0x0E; this.biomeAndHeightArray[offset - 5] = (byte) (progX & 0xFF); this.biomeAndHeightArray[offset - 6] = (byte) (progX >> 8 & 0xFF); this.biomeAndHeightArray[offset - 7] = (byte) (progX >> 16 & 0xFF); this.biomeAndHeightArray[offset - 8] = (byte) (progX >> 24 & 0xFF); } public static boolean testFlag(byte[] bb) { return (bb[7] & 0xFF) == 0xFE && bb[6] == 0x06 && bb[5] == 0x03 && bb[4] == 0x0E; } private void resumeProgress(int progress) { int multifactor = biomeMapFactor >> 4; if (multifactor < 1) { multifactor = 1; } int progCount = progress / imagefactor; progressX.set(progress); biomeMapX = multifactor * progCount - (biomeMapSizeX * biomeMapFactor / 32); if (biomeMapX > -biomeMap0 * 4) { biomeMapX += biomeMap0 * 8; } } /** * This is outside the multithreaded portion of the code * This should be called after the finishedCalculating flag is set. */ public void writeOutputFile(boolean sendToClientImmediately) { if (this.biomeAndHeightArray == null) return; if (!this.aborted.get()) //It should be error-free if it wasn't aborted { try { if (!this.biomeMapFile.exists() || (this.biomeMapFile.canWrite() && this.biomeMapFile.canRead())) { this.flagProgress(); FileUtils.writeByteArrayToFile(this.biomeMapFile, this.biomeAndHeightArray); } } catch (IOException ex) { ex.printStackTrace(); } if (sendToClientImmediately) { MapUtil.sendMapPacketToAll(this.biomeMapCx << 4, this.biomeMapCz << 4, this.biomeAndHeightArray); } } this.biomeAndHeightArray = null; } private void initialiseSmallerArrays() { int limit = Math.min(biomeMapFactor, 16); this.heights = new int[256]; this.heighttemp = new double[825]; this.biomeCount = new int[limit * limit]; } /* * Return false while there are further ticks to carry out * Return true when completed */ public boolean BiomeMapOneTick() { int limit = Math.min(biomeMapFactor, 16); if (this.biomeAndHeightArray == null) { this.biomeAndHeightArray = new byte[biomeMapSizeX * biomeMapSizeZ * 2]; this.initialiseSmallerArrays(); } int multifactor = biomeMapFactor >> 4; if (multifactor < 1) { multifactor = 1; } int progX = this.progressX.get(); try { biomeMapOneChunk(biomeMapCx + biomeMapX, biomeMapCz + biomeMapZ, progX, progressZ, limit); } catch (Exception e) { GCLog.severe("Galacticraft background map image generator hit an error (probably a mod conflict?)"); GCLog.severe("--> Please report this at https://github.com/micdoodle8/Galacticraft/issues/2544 <--"); e.printStackTrace(); MapGen.disabled = true; this.aborted.set(true); return true; } biomeMapZ += multifactor; progressZ += imagefactor; if (progressZ > biomeMapSizeZ - imagefactor) { progressZ = 0; // if (progX % 3 == 0) // { // System.out.println("Finished map column " + progX + " at " + (biomeMapCx + biomeMapX) + "," + (biomeMapCz + biomeMapZ)); // } this.progressX.set(progX + imagefactor); biomeMapZ = biomeMap0; biomeMapX += multifactor; if (biomeMapX > -biomeMap0 * 4) { biomeMapX += biomeMap0 * 8; } return progX > biomeMapSizeX - imagefactor - imagefactor; } return false; } private void biomeMapOneChunk(int x0, int z0, int ix, int iz, int limit) { biomesGrid = this.getBiomeGenAt(biomesGrid, x0 << 4, z0 << 4, 16, 16); if (biomesGrid == null) { return; } this.getHeightMap(x0, z0); int factor = this.biomeMapFactor; int halfFactor = limit * limit / 2; ArrayList<Integer> cols = new ArrayList<Integer>(); for (int j = 0; j < biomeCount.length; j++) { biomeCount[j] = 0; } for (int x = 0; x < 16; x += factor) { int izstore = iz; for (int z = 0; z < 16; z += factor) { cols.clear(); int maxcount = 0; int maxindex = -1; int biome = -1; int lastcol = -1; int idx = 0; int avgHeight = 0; int divisor = 0; //TODO: start in centre instead of top left BIOMEDONE: for (int xx = 0; xx < limit; xx++) { int hidx = ((xx + x) << 4) + z; for (int zz = 0; zz < limit; zz++) { int height = heights[hidx + zz]; avgHeight += height; divisor++; biome = biomesGrid[xx + x + ((zz + z) << 4)]; if (biome != lastcol) { idx = cols.indexOf(biome); if (idx == -1) { idx = cols.size(); cols.add(biome); } lastcol = biome; } biomeCount[idx]++; if (biomeCount[idx] > maxcount) { maxcount = biomeCount[idx]; maxindex = idx; if (maxcount > halfFactor) { break BIOMEDONE; } } } } //Clear the array for next time for (int j = cols.size() - 1; j >= 0; j--) { biomeCount[j] = 0; } int arrayIndex = (ix * biomeMapSizeZ + iz) * 2; this.biomeAndHeightArray[arrayIndex] = (byte) (cols.get(maxindex).intValue()); this.biomeAndHeightArray[arrayIndex + 1] = (byte) ((avgHeight + (divisor + 1) / 2) / divisor); iz++; } iz = izstore; ix++; } } public void getHeightMap(int cx, int cz) { rand.setSeed((long) cx * 341873128712L + (long) cz * 132897987541L); biomesGridHeights = this.getBiomesForGeneration(biomesGridHeights, cx * 4 - 2, cz * 4 - 2, 10, 10); this.generateHeightMap(cx * 4, 0, cz * 4); final double d0 = 0.125D; final double d9 = 0.25D; for (int xx = 0; xx < 4; ++xx) { int xa = xx * 5; int xb = xa + 5; for (int zz = 0; zz < 4; ++zz) { int aa = (xa + zz) * 33; int ab = aa + 33; int ba = (xb + zz) * 33; int bb = ba + 33; for (int yy = 2; yy < 18; ++yy) { double d1 = heighttemp[aa + yy]; double d2 = heighttemp[ab + yy]; double d3 = heighttemp[ba + yy]; double d4 = heighttemp[bb + yy]; double d5 = (heighttemp[aa + yy + 1] - d1) * d0; double d6 = (heighttemp[ab + yy + 1] - d2) * d0; double d7 = (heighttemp[ba + yy + 1] - d3) * d0; double d8 = (heighttemp[bb + yy + 1] - d4) * d0; for (int y = 0; y < 8; ++y) { double d10 = d1; double d11 = d2; double d12 = (d3 - d1) * d9; double d13 = (d4 - d2) * d9; int truey = yy * 8 + y; for (int x = 0; x < 4; ++x) { int idx = x + xx * 4 << 4 | zz * 4; double d16 = (d11 - d10) * d9; double d15 = d10 - d16; for (int z = 0; z < 4; ++z) { if ((d15 += d16) > 0.0D) { heights[idx + z] = truey; } } d10 += d12; d11 += d13; } d1 += d5; d2 += d6; d3 += d7; d4 += d8; } } } } } static double[] noiseField3; static double[] noiseField1; static double[] noiseField2; static double[] noiseField4; private NoiseGeneratorOctaves noiseGen1; private NoiseGeneratorOctaves noiseGen2; private NoiseGeneratorOctaves noiseGen3; public NoiseGeneratorOctaves noiseGen4; public void initialise(long seed) { rand = new Random(seed); noiseGen1 = new NoiseGeneratorOctaves(rand, 16); noiseGen2 = new NoiseGeneratorOctaves(rand, 16); noiseGen3 = new NoiseGeneratorOctaves(rand, 8); NoiseGeneratorPerlin ignore1 = new NoiseGeneratorPerlin(this.rand, 4); NoiseGeneratorOctaves ignore2 = new NoiseGeneratorOctaves(this.rand, 10); noiseGen4 = new NoiseGeneratorOctaves(rand, 16); NoiseGeneratorOctaves ignore3 = new NoiseGeneratorOctaves(this.rand, 8); NoiseGenerator[] noiseGens = {noiseGen1, noiseGen2, noiseGen3, ignore1, ignore2, noiseGen4, ignore3}; noiseGens = net.minecraftforge.event.terraingen.TerrainGen.getModdedNoiseGenerators(this.world, this.rand, noiseGens); noiseGen1 = (NoiseGeneratorOctaves)noiseGens[0]; noiseGen2 = (NoiseGeneratorOctaves)noiseGens[1]; noiseGen3 = (NoiseGeneratorOctaves)noiseGens[2]; noiseGen4 = (NoiseGeneratorOctaves)noiseGens[5]; } private void generateHeightMap(int cx, int cy, int cz) { float f = this.settings.coordinateScale; float f1 = this.settings.heightScale; noiseField4 = noiseGen4.generateNoiseOctaves(noiseField4, cx, cz, 5, 5, (double)this.settings.depthNoiseScaleX, (double)this.settings.depthNoiseScaleZ, (double)this.settings.depthNoiseScaleExponent); noiseField3 = noiseGen3.generateNoiseOctaves(noiseField3, cx, cy, cz, 5, 33, 5, (double)(f / this.settings.mainNoiseScaleX), (double)(f1 / this.settings.mainNoiseScaleY), (double)(f / this.settings.mainNoiseScaleZ)); noiseField1 = noiseGen1.generateNoiseOctaves(noiseField1, cx, cy, cz, 5, 33, 5, (double)f, (double)f1, (double)f); noiseField2 = noiseGen2.generateNoiseOctaves(noiseField2, cx, cy, cz, 5, 33, 5, (double)f, (double)f1, (double)f); boolean flag1 = false; boolean flag = false; int l = 2; int i1 = 0; double d4 = 8.5D; boolean amplified = this.worldType == WorldType.AMPLIFIED; double minLimitScale = (double)this.settings.lowerLimitScale; double maxLimitScale = (double)this.settings.upperLimitScale; double stretchY = (double)this.settings.stretchY * 128.0D / 256.0D; double baseSize = (double)this.settings.baseSize; for (int xx = 0; xx < 5; ++xx) { for (int zz = 0; zz < 5; ++zz) { float f2 = 0.0F; float f3 = 0.0F; float f4 = 0.0F; float theMinHeight = biomesGridHeights[xx + 22 + zz * 10].minHeight; for (int x = -2; x <= 2; ++x) { int baseIndex = xx + x + 22 + zz * 10; for (int z = -2; z <= 2; ++z) { BiomeGenBase biomegenbase1 = biomesGridHeights[baseIndex + z * 10]; float f5 = this.settings.biomeDepthOffSet + biomegenbase1.minHeight * this.settings.biomeDepthWeight; float f6 = this.settings.biomeScaleOffset + biomegenbase1.maxHeight * this.settings.biomeScaleWeight; if (amplified && f5 > 0.0F) { f5 = 1.0F + f5 + f5; f6 = 1.0F + f6 * 4.0F; } float f7 = parabolicField[x + 12 + z * 5] / (f5 + 2.0F); if (biomegenbase1.minHeight > theMinHeight) { f7 /= 2.0F; } f2 += f6 * f7; f3 += f5 * f7; f4 += f7; } } f2 /= f4; f3 /= f4; f2 = f2 * 0.9F + 0.1F; f3 = f3 / 2.0F - 0.125F; double d12 = noiseField4[i1] / 8000.0D; if (d12 < 0.0D) { d12 = -d12 * 0.3D; } d12 = d12 * 3.0D - 2.0D; if (d12 < 0.0D) { d12 /= 2.0D; if (d12 < -1.0D) { d12 = -1.0D; } d12 /= 1.4D; d12 /= 2.0D; } else { if (d12 > 1.0D) { d12 = 1.0D; } d12 /= 8.0D; } ++i1; double d13 = (double) f3; final double d14 = (double) f2 / 6.0D; d13 += d12 * 0.2D; d13 = d13 * baseSize / 8.0D; double d5 = baseSize + d13 * 4.0D; for (int j2 = 2; j2 < 19; ++j2) { double d6 = ((double) j2 - d5) * stretchY / d14; if (d6 < 0.0D) { d6 *= 4.0D; } double d7 = noiseField1[l] / minLimitScale; double d8 = noiseField2[l] / maxLimitScale; double d9 = (noiseField3[l] / 10.0D + 1.0D) / 2.0D; heighttemp[l] = MathHelper.denormalizeClamp(d7, d8, d9) - d6; ++l; } l += 16; } } } /** * REPLICATES method in WorldChunkManager * Returns an array of biomes for the location input, used for generating the height map */ @Override public BiomeGenBase[] getBiomesForGeneration(BiomeGenBase[] biomes, int x, int z, int width, int height) { IntCache.resetIntCacheGC(); int[] aint = this.genBiomes.getInts(x, z, width, height); int size = width * height; if (biomes == null || biomes.length < size) { biomes = new BiomeGenBase[size]; } for (int i = 0; i < size; ++i) { int biomeId = aint[i]; BiomeGenBase biomegenbase = null; if (biomeId >= 0 && biomeId <= biomeList.length) { biomegenbase = biomeList[biomeId]; } // else // System.err.println("MapGen: Biome ID is out of bounds: " + biomeId + ", defaulting to 0 (Ocean)"); biomes[i] = biomegenbase == null ? BiomeGenBase.ocean : biomegenbase; } return biomes; } /** * REPLICATES method in WorldChunkManager (with higher performance!) * Return a list of ints representing mapgen biomes at the specified coordinates. Args: listToReuse, x, y, width, height * This is after all genlayers (oceans, islands, hills, rivers, etc) */ public int[] getBiomeGenAt(int[] listToReuse, int x, int z, int width, int height) { IntCache.resetIntCacheGC(); int[] aint = this.biomeIndexLayer.getInts(x, z, width, height); int size = width * height; if (listToReuse == null || listToReuse.length < size) { listToReuse = new int[size]; } System.arraycopy(aint, 0, listToReuse, 0, size); return listToReuse; } }