diff --git a/build.gradle b/build.gradle index 8fa4064d6..ee384e0de 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ plugins { } group 'com.volmit.iris' -version '1.5-TOTALLY-UNSTABLE' +version '1.5-UNSTABLE-MAP' def apiVersion = '1.17' def name = 'Iris' def main = 'com.volmit.iris.Iris' diff --git a/src/main/java/com/volmit/iris/generator/IrisComplex.java b/src/main/java/com/volmit/iris/generator/IrisComplex.java index 5f9a95f17..849b4e834 100644 --- a/src/main/java/com/volmit/iris/generator/IrisComplex.java +++ b/src/main/java/com/volmit/iris/generator/IrisComplex.java @@ -78,6 +78,10 @@ public class IrisComplex implements DataProvider { } public IrisComplex(Engine engine) { + this(engine, false); + } + + public IrisComplex(Engine engine, boolean simple) { int cacheSize = IrisSettings.get().getCache().getStreamingCacheSize(); this.rng = new RNG(engine.getWorld().getSeed()); this.data = engine.getData(); diff --git a/src/main/java/com/volmit/iris/manager/command/studio/CommandIrisStudioMap.java b/src/main/java/com/volmit/iris/manager/command/studio/CommandIrisStudioMap.java index ba78cca1d..fe4a14ae6 100644 --- a/src/main/java/com/volmit/iris/manager/command/studio/CommandIrisStudioMap.java +++ b/src/main/java/com/volmit/iris/manager/command/studio/CommandIrisStudioMap.java @@ -2,15 +2,24 @@ package com.volmit.iris.manager.command.studio; import com.volmit.iris.Iris; import com.volmit.iris.IrisSettings; -import com.volmit.iris.manager.gui.IrisVision; -import com.volmit.iris.scaffold.IrisWorlds; +import com.volmit.iris.generator.IrisComplex; +import com.volmit.iris.manager.IrisDataManager; +import com.volmit.iris.map.MapVision; +import com.volmit.iris.object.IrisDimension; import com.volmit.iris.scaffold.engine.IrisAccess; +import com.volmit.iris.util.FakeEngine; +import com.volmit.iris.util.FakeWorld; import com.volmit.iris.util.KList; import com.volmit.iris.util.MortarCommand; import com.volmit.iris.util.MortarSender; +import org.bukkit.World; -public class CommandIrisStudioMap extends MortarCommand { - public CommandIrisStudioMap() { +import java.io.File; + +public class CommandIrisStudioMap extends MortarCommand +{ + public CommandIrisStudioMap() + { super("map", "render"); setDescription("Render a map (gui outside of mc)"); requiresPermission(Iris.perm.studio); @@ -23,33 +32,79 @@ public class CommandIrisStudioMap extends MortarCommand { } @Override - public boolean handle(MortarSender sender, String[] args) { - if (!IrisSettings.get().isStudio()) { + public boolean handle(MortarSender sender, String[] args) + { + if(!IrisSettings.get().isStudio()) + { sender.sendMessage("To use Iris Studio, please enable studio in Iris/settings.json"); return true; } - if (!IrisSettings.get().isUseServerLaunchedGuis()) { + if(!IrisSettings.get().isUseServerLaunchedGuis()) + { sender.sendMessage("To use Iris Guis, please enable serverLaunchedGuis in Iris/settings.json"); return true; } + IrisComplex complex; - try { + if (args.length > 0) { + String type = ""; + long seed = 1337; + for(String i : args) + { + if (i.contains("=")) { + type = i.startsWith("type=") ? i.split("\\Q=\\E")[1] : type; + seed = i.startsWith("seed=") ? Long.valueOf(i.split("\\Q=\\E")[1]) : seed; + } else { + if (type.equals("")) { + type = i; + } else if (seed == 1337) { + seed = Long.valueOf(i); + } + } + } + + if (type.equals("")) { + sender.sendMessage("Open this in a studio world or do /iris studio map [pack]"); + return true; + } + + IrisDimension dim = IrisDataManager.loadAnyDimension(type); + + if (dim == null) { + sender.sendMessage("Can't find dimension: " + type); + return true; + } + + if (dim.getEnvironment() == null) { + dim.setEnvironment(World.Environment.NORMAL); + } + + //Setup the fake world and engine objects so we can get an IrisComplex for the terrain they will + //generate without actually generating any of it + sender.sendMessage("Preparing map..."); + FakeWorld world = new FakeWorld(dim.getName(), 0, 256, seed, new File(dim.getName()), dim.getEnvironment()); + FakeEngine engine = new FakeEngine(dim, world); + complex = new IrisComplex(engine, true); + sender.sendMessage("Opening Map!"); + } else if (Iris.proj.isProjectOpen()) { IrisAccess g = Iris.proj.getActiveProject().getActiveProvider(); - IrisVision.launch(g, 0); - sender.sendMessage("Opening Map!"); - } catch (Throwable e) { - IrisAccess g = IrisWorlds.access(sender.player().getWorld()); - IrisVision.launch(g, 0); - sender.sendMessage("Opening Map!"); + complex = g.getCompound().getDefaultEngine().getFramework().getComplex(); + sender.sendMessage("Opening map for existing studio world!"); + } else { + sender.sendMessage("Open this in a studio world or do /iris studio map [pack]"); + return true; } + MapVision map = new MapVision(complex); + map.open(); return true; } @Override - protected String getArgsUsage() { - return ""; + protected String getArgsUsage() + { + return "[pack] [seed=1337]"; } } diff --git a/src/main/java/com/volmit/iris/map/BiomeMap.java b/src/main/java/com/volmit/iris/map/BiomeMap.java new file mode 100644 index 000000000..694662e2f --- /dev/null +++ b/src/main/java/com/volmit/iris/map/BiomeMap.java @@ -0,0 +1,67 @@ +package com.volmit.iris.map; + +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; +import com.volmit.iris.object.IrisBiome; +import com.volmit.iris.object.IrisDimension; +import com.volmit.iris.object.IrisRegion; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class BiomeMap { + + private BiMap biomeMap; + private BiMap regionMap; + private IrisDimension dimension; + + private Set activeBiomes = new HashSet<>(); + private Set activeRegions = new HashSet<>(); + + public BiomeMap(IrisDimension dimension) { + this.dimension = dimension; + + List biomes = dimension.getAllAnyBiomes(); + List regions = dimension.getAllAnyRegions(); + + biomeMap = HashBiMap.create(biomes.size()); + regionMap = HashBiMap.create(regions.size()); + + int nextID = 0; + + for (IrisBiome biome : biomes) { + biomeMap.putIfAbsent(biome, nextID); + activeBiomes.add(nextID); + nextID++; + } + + nextID = 0; + + for (IrisRegion region : regions) { + regionMap.putIfAbsent(region, nextID); + activeRegions.add(nextID); + nextID++; + } + } + + public IrisDimension getDimension() { + return dimension; + } + + public IrisBiome getBiome(int id) { + return biomeMap.inverse().get(id); + } + + public int getBiomeId(IrisBiome biome) { + return biomeMap.get(biome); + } + + public IrisRegion getRegion(int id) { + return regionMap.inverse().get(id); + } + + public int getRegionId(IrisRegion region) { + return regionMap.get(region); + } +} diff --git a/src/main/java/com/volmit/iris/map/MapVision.java b/src/main/java/com/volmit/iris/map/MapVision.java new file mode 100644 index 000000000..26230e775 --- /dev/null +++ b/src/main/java/com/volmit/iris/map/MapVision.java @@ -0,0 +1,586 @@ +package com.volmit.iris.map; + +import com.volmit.iris.Iris; +import com.volmit.iris.generator.IrisComplex; +import com.volmit.iris.util.J; +import com.volmit.iris.util.KMap; +import com.volmit.iris.util.KSet; +import com.volmit.iris.util.PrecisionStopwatch; +import com.volmit.iris.util.RandomColor; +import com.volmit.iris.util.RollingSequence; +import io.netty.util.internal.ConcurrentSet; +import org.jetbrains.annotations.NotNull; + +import javax.annotation.Nullable; +import javax.imageio.ImageIO; +import javax.swing.JButton; +import javax.swing.JFrame; +import javax.swing.JPanel; +import java.awt.Color; +import java.awt.FlowLayout; +import java.awt.Font; +import java.awt.Graphics; +import java.awt.Point; +import java.awt.event.ComponentEvent; +import java.awt.event.ComponentListener; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; +import java.awt.event.MouseEvent; +import java.awt.event.MouseMotionListener; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.stream.Collectors; + +public class MapVision extends JPanel { + + private int threadId = 0; + + private static final int TILE_SIZE = 128; //Tile size in pixels + private static final int TILE_REALITY = 512; //How many blocks a tile is + private static final int TILE_SIZE_R = 7; //The number of bits to shift to get the pixel side + private static final int TILE_REALITY_R = 9; //The number of bits to shift to get the real size + + private static final int DEF_WIDTH = 1440; + private static final int DEF_HEIGHT = 820; + + + private IrisComplex complex; + private RenderType currentType = RenderType.BIOME_LAND; + + private int mouseX; //The current mouse coords + private int mouseY; + private double draggedOffsetX; //The amount the mouse has dragged the map + private double draggedOffsetY; + private int centerTileX; //The center tile in the screen + private int centerTileY; + private int offsetX; //Offset to draw tiles to + private int offsetY; + private int lastTileWidth; + + private boolean dirty = true; //Whether to repaint textures + private double scale = 1; + private boolean realname = false; + + private KMap tiles = new KMap<>(); + + private Set visibleTiles = new ConcurrentSet<>(); //Tiles that are visible on screen + private Set halfDirtyTiles = new ConcurrentSet<>(); //Tiles that should be drawn next draw + + private short[][] spiral; //See #generateSpiral + + private final Color overlay = new Color(80, 80, 80); + private final Font overlayFont = new Font("Arial", Font.BOLD, 16); + + private RollingSequence roll = new RollingSequence(50); + + private boolean debug = false; + private int[] debugBorder = new int[] {-5, -3, 6, 4}; + + private boolean recalculating; + + // IrisComplex is the main class I need for a biome map. You can make one from an Engine object, + // which does need a FakeWorld object in it for the seed + public MapVision(IrisComplex worldComplex) + { + this.complex = worldComplex; + this.setBackground(Color.BLACK); + this.setVisible(true); + roll.put(1); + generateSpiral(64); + + addMouseWheelListener((mouseWheelEvent) -> { + double oldScale = this.scale; + this.scale = Math.min(4, Math.max(scale + scale * mouseWheelEvent.getWheelRotation() * 0.2, 1)); + double wx = getWidth(); + double hy = getHeight(); + double xScale = (mouseX - wx) / wx * 0.5; + double yScale = (mouseY - hy) / hy * 0.5; + + if (mouseWheelEvent.getWheelRotation() > 0) { //Only on zoom in, adjust the position to zoom into + this.draggedOffsetX += xScale * (wx / 2) * (oldScale - scale); + this.draggedOffsetY += yScale * (hy / 2) * (oldScale - scale); + } + + dirty = true; + repaint(); + softRecalculate(); + }); + addMouseMotionListener(new MouseMotionListener() + { + @Override + public void mouseMoved(MouseEvent e) + { + Point cp = e.getPoint(); + mouseX = cp.x; + mouseY = cp.y; + } + + @Override + public void mouseDragged(MouseEvent e) + { + Point cp = e.getPoint(); + draggedOffsetX -= (mouseX - cp.x) / scale; + draggedOffsetY -= (mouseY - cp.y) / scale; + mouseX = cp.x; + mouseY = cp.y; + softRecalculate(); + dirty = true; + } + }); + recalculate(); //Setup + + } + + /** + * Open this GUI + */ + public void open() { + JFrame frame = new JFrame("Iris Map (" + complex.getData().getDataFolder().getName() + ")"); + frame.add(this); + frame.setSize(DEF_WIDTH, DEF_HEIGHT); + frame.setBackground(Color.BLACK); + frame.addComponentListener(new ComponentListener() { + + @Override + public void componentResized(ComponentEvent e) { + dirty = true; + softRecalculate(); + repaint(); + } + + @Override + public void componentMoved(ComponentEvent e) { + dirty = true; + repaint(); + } + + @Override + public void componentShown(ComponentEvent e) { } + + @Override + public void componentHidden(ComponentEvent e) { } + }); + frame.addKeyListener(new KeyListener() { + @Override + public void keyTyped(KeyEvent e) { } + + @Override + public void keyPressed(KeyEvent e) { + if (e.getKeyCode() == KeyEvent.VK_SHIFT) + realname = true; + else if (e.getKeyCode() == KeyEvent.VK_ALT) debug = !debug; + else if (e.getKeyCode() == KeyEvent.VK_R) { + dirty = true; + repaint(); + } + } + + @Override + public void keyReleased(KeyEvent e) { + if (e.getKeyCode() == KeyEvent.VK_SHIFT) + realname = false; + } + }); + File file = Iris.getCached("Iris Icon", "https://raw.githubusercontent.com/VolmitSoftware/Iris/master/icon.png"); + + if(file != null) { + try { + frame.setIconImage(ImageIO.read(file)); + } catch(IOException ignored) { } + } + + frame.setVisible(true); + frame.requestFocus(); + frame.toFront(); + } + + @Override + public void paint(Graphics gx) { + //super.paint(gx); + PrecisionStopwatch stopwatch = PrecisionStopwatch.start(); + + int windowOffsetX = getWidth() / 2; + int windowOffsetY = getHeight() / 2; + + offsetX = (int) Math.round(draggedOffsetX * scale) + windowOffsetX; + offsetY = (int) Math.round(draggedOffsetY * scale) + windowOffsetY; + + //If we should do a full repaint of the entire frame. Only done when the zoom level changes, etc + if (dirty) { + super.paint(gx); //Clear the frame first + for (Iterator iterator = visibleTiles.iterator(); iterator.hasNext();) { + Tile tile = iterator.next(); + drawTile(gx, tile); + } + dirty = false; + } else { + //Loop through all the tiles that haven't been drawn last draw and draw them + //This saves us having to do a FULL redraw when only 1 new tile has been added + for (Iterator iterator = halfDirtyTiles.iterator(); iterator.hasNext();) { + Tile tile = iterator.next(); + drawTile(gx, tile); + iterator.remove(); + } + } + + gx.setColor(overlay); + gx.fillRect(getWidth() - 400, 4, 396, 27); + gx.setColor(Color.WHITE); + //int x = (int) (((int) ((mouseX - windowOffsetX)) << 2) + (draggedOffsetX * scale)); + //int y = (int) (((int) ((mouseY - windowOffsetY)) << 2) + (draggedOffsetY * scale)); + int x = (int) (((int) ((mouseX - windowOffsetX))) + (-draggedOffsetX * scale)) << 2; + int y = (int) (((int) ((mouseY - windowOffsetY))) + (-draggedOffsetY * scale)) << 2; + String text = " [" + x+ ", " + y + "]"; + if (realname) + text = complex.getLandBiomeStream().get(x, y).getLoadKey().toUpperCase() + text; + else + text = complex.getLandBiomeStream().get(x, y).getName().toUpperCase() + text; + gx.setFont(overlayFont); + gx.drawString(text, getWidth() - 400 + 6, 23); + + if (debug) { + gx.setColor(Color.RED); + int xx = (int) Math.round((debugBorder[0] << TILE_SIZE_R) / scale + offsetX); + int yy = (int) Math.round((debugBorder[1] << TILE_SIZE_R) / scale + offsetY); + int xx2 = (int) Math.round((debugBorder[2] << TILE_SIZE_R) / scale + offsetX); + int yy2 = (int) Math.round((debugBorder[3] << TILE_SIZE_R) / scale + offsetY); + gx.drawRect(xx, yy, xx2, yy2); + gx.drawRect(xx-1, yy-1, xx2+1, yy2+1); + gx.drawRect(xx-2, yy-2, xx2+2, yy2+2); + + + gx.setColor(overlay); + gx.fillRect(10, 10, 220, 200); + gx.setColor(Color.WHITE); + gx.drawString("Center [" + centerTileX + ", " + centerTileY + "]", 20, 25); + gx.drawString((60 / (Math.max(roll.getAverage(), 1))) + " fps", 20, 45); + gx.drawString("Width = " + lastTileWidth, 20, 65); + gx.drawString("Dirty = " + dirty, 20, 85); + gx.drawString("Scale = " + scale, 20, 105); + gx.drawString("Tiles (Visible)" + visibleTiles.size(), 20, 125); + gx.drawString("Tiles (Total) " + tiles.size(), 20, 145); + + x = (int) (((int) ((mouseX - windowOffsetX))) + (-draggedOffsetX * scale)) >> TILE_SIZE_R; + y = (int) (((int) ((mouseY - windowOffsetY))) + (-draggedOffsetY * scale)) >> TILE_SIZE_R; + Tile t = getTile((short)x, (short)y); + boolean b1 = t != null; + boolean b2 = b1 && visibleTiles.contains(t); + gx.drawString("Cursor Tile [" + x + ", " + y + "]", 20, 165); + gx.drawString("Tile Details [" + String.valueOf(b1).toUpperCase() + ", " + String.valueOf(b2).toUpperCase() + "]", 20, 185); + + } + + stopwatch.end(); + roll.put(stopwatch.getMillis()); + + /*J.a(() -> + { + J.sleep(1000 / targetFPS); + repaint(); + });*/ + J.a(sleepTask); + } + + public void drawTile(Graphics gx, Tile tile) { + if (gx == null) return; + + int x = (int) Math.round((tile.getX() << TILE_SIZE_R) / scale + offsetX); + int y = (int) Math.round((tile.getY() << TILE_SIZE_R) / scale + offsetY); + //int x = (int) ((tile.getX() * TILE_SIZE) / scale + offsetX); + //int y = (int) ((tile.getY() * TILE_SIZE) / scale + offsetY); + + int size = (int) (TILE_SIZE / scale); + int off = (int) (TILE_SIZE % scale); + gx.drawImage(tile.getImage(), x, y, size, size, null); + } + + private Runnable sleepTask = new Runnable() { + @Override + public void run() { + double t = Math.max(Math.min(roll.getAverage(), 1000), 30); + J.sleep((long) t); + repaint(); + } + }; + + /** + * Check if we should do a full recalculation of what tiles should be visible + */ + public void softRecalculate() { + short x = (short) (((-draggedOffsetX * scale)) / TILE_SIZE * scale); + short y = (short) (((-draggedOffsetY * scale)) / TILE_SIZE * scale); + int xTiles = (((int)(getWidth() * scale) >> TILE_SIZE_R)) / 2 + 1; + + if (centerTileX != x || centerTileY != y || xTiles != lastTileWidth) { + recalculate(); + } + + centerTileX = x; + centerTileY = y; + } + + /** + * Recalculate what tiles should be visible on screen, as well as queue + * new tiles to be created + */ + public void recalculate() { + PrecisionStopwatch stopwatch = PrecisionStopwatch.start(); + + //Clears out the queue of existing tiles to do because we are redoing them anyway + //If we don't do this, the queue gets so clogged that it literally takes up the + //entire CPU with thread locking/unlocking + executorService.getQueue().clear(); + + int W = getWidth(); + int H = getHeight(); + + if (W == 0|| H == 0) { //The window hasn't fully opened yet; assume defaults + W = DEF_WIDTH; + H = DEF_HEIGHT; + } + + short centerTileX = (short) (((-draggedOffsetX * scale)) / TILE_SIZE * scale); + short centerTileY = (short) (((-draggedOffsetY * scale)) / TILE_SIZE * scale); + + //Iris.info("Center is " + centerTileX + ", " + centerTileY); + //Iris.info("Width is " + W + ", " + H); + + int woh = Math.max(W, H); + int newSize = ((int)(woh * scale) >> TILE_SIZE_R) + 1; + int checkSizeX = (((int)(W * scale) >> TILE_SIZE_R)) / 2; + int checkSizeY = (((int)(H * scale) >> TILE_SIZE_R)) / 2; + lastTileWidth = checkSizeX; + generateSpiral(newSize); + + Set checked = new HashSet<>(); + Set clone = new HashSet(visibleTiles.stream().map((t) -> + getTileId(t.getX(), t.getY())) + .collect(Collectors.toSet())); //Clone the visible tiles + + if (debug) { //These are the 4 corners of the red line that shows the visibility check region for tiles + debugBorder[0] = -checkSizeX + centerTileX; + debugBorder[1] = -checkSizeY + centerTileY; + debugBorder[2] = checkSizeX + 1 + centerTileX; + debugBorder[3] = checkSizeY + 1 + centerTileY; + } + + for (short[] coords : spiral) { //Start from the center of the spiral and work outwards to find new tiles to queue + short x = (short)(coords[0] + centerTileX); + short y = (short)(coords[1] + centerTileY); + + //When it goes offscreen, don't queue the tile by continuing + if (Math.abs(coords[0]) > checkSizeX + 1) { + continue; + } + if (Math.abs(coords[1]) > checkSizeY + 1) { + continue; + } + + int id = getTileId(x, y); + + //If the tile is not already made + if (!tiles.containsKey(id)) { + short[] c = getTileCoords(id); + queue(c[0], c[1]); //Queue for creation + } else { + checked.add(id); + } + } + + clone.removeAll(checked); //Remove the tiles that we know are onscreen + + for (int id : clone) { //Loop through the invisible tiles and mark them for removal from memory + short[] c = getTileCoords(id); + queueForRemoval(getTile(c[0], c[1])); + //visibleTiles.remove(t); + } + + stopwatch.end(); + roll.put(stopwatch.getMillis()); + } + + /** + * Queue a tile for creation + * @param tileX X tile coord + * @param tileY Y tile coord + */ + public void queue(short tileX, short tileY) { + //If the tile still exists but just isn't visible + if (tiles.containsKey(getTileId(tileX, tileY))) { + Tile tile = getTile(tileX, tileY); + if (visibleTiles.contains(tile)) return; + + visibleTiles.add(tile); + halfDirtyTiles.add(tile); //Re-render it without doing a full repaint + //dirty = true; + return; + } + + //I turned all lambda around here into objects just to see if they would + //show up in timings instead of "$lambda". But they didn't. So it's not + //not my code DIRECTLY. I believe the thing timings show is just to do + //with threads stopping and starting/halting in the thread pool. Don't + //know why or how to fix it, though + + /*executorService.execute(() -> { + Tile tile = new Tile(tileX, tileY); + tile.render(complex, currentType); + tiles.put(getTileId(tileX, tileY), tile); + visibleTiles.add(tile); + dirty = true; + });*/ + executorService.execute(queueTask(tileX, tileY)); + + } + + public Runnable queueTask(short tileX, short tileY) { + return new Runnable() { + @Override + public void run() { + Tile tile = new Tile(tileX, tileY); + tile.render(complex, currentType); + tiles.put(getTileId(tileX, tileY), tile); + visibleTiles.add(tile); + //dirty = true; //Disabled marking as dirty so a redraw of the entire map isn't needed + halfDirtyTiles.add(tile); + } + }; + } + + /** + * Pend a tile for removal from the screen + * @param tile The tile to remove + */ + public void queueForRemoval(Tile tile) { + //TODO Change from using the async task system as it may be putting strain on the server from being called so often + J.a(() -> visibleTiles.remove(tile), 20); //Remove visibility in a bit + + J.a(() -> { //Remove it completely from memory after 5 seconds if it's still not visible + if (!visibleTiles.contains(tile)) { + tiles.remove(getTileId(tile.getX(), tile.getY())); + } + }, 20 * 6); + } + + /** + * Get a tile based on the X and Z coords of the tile + * @param tileX X Coord + * @param tileY Y Coord + * @return + */ + @Nullable + public Tile getTile(short tileX, short tileY) { + return tiles.get(getTileId(tileX, tileY)); + } + + /** + * Get an integer that represents a tile's location + * @param tileX X Coord + * @param tileY Y Coord + * @return + */ + public int getTileId(short tileX, short tileY) { + return tileX | tileY << 16; + } + + /** + * Converts an integer representing a tiles location back into 2 shorts + * @param id The tile integer + * @return + */ + public short[] getTileCoords(int id) { + return new short[] {(short) (id & 0x0000FFFF), (short) (id >> 16)}; + } + + /** + * Generates a 2D array of relative tile locations. This is so we know what order + * to search for new tiles in a nice, spiral way + * @param size Size of the array + */ + public void generateSpiral(int size) { + if (size % 2 == 0) size++; + short[][] newSpiral = new short[size * size][2]; + + int x = 0; // current position; x + int y = 0; // current position; y + int d = 0; // current direction; 0=RIGHT, 1=DOWN, 2=LEFT, 3=UP + int s = 1; // chain size + int c = 0; // count + + // starting point + x = ((int)(size/2.0))-1; + y = ((int)(size/2.0))-1; + int offset = (size / 2) - 1; + + for (int k=1; k<=(size-1); k++) + { + for (int j=0; j<(k<(size-1)?2:3); j++) + { + for (int i=0; i { + threadId++; + Thread t = new Thread(r); + t.setName("Iris Map Renderer " + threadId); + t.setPriority(Thread.MIN_PRIORITY); + t.setUncaughtExceptionHandler((et, e) -> + { + Iris.info("Exception encountered in " + et.getName()); + e.printStackTrace(); + }); + + return t; + });*/ + + private ThreadFactory factory = new ThreadFactory() { + @Override + public Thread newThread(@NotNull Runnable r) { + threadId++; + Thread t = new Thread(r); + t.setName("Iris Map Renderer " + threadId); + t.setPriority(Thread.MIN_PRIORITY); + t.setDaemon(true); + t.setUncaughtExceptionHandler((et, e) -> + { + Iris.info("Exception encountered in " + et.getName()); + e.printStackTrace(); + }); + + return t; + } + }; + + //Our thread pool that draws the tiles for us + private final ThreadPoolExecutor executorService = (ThreadPoolExecutor) Executors.newFixedThreadPool(8, factory); + + +} diff --git a/src/main/java/com/volmit/iris/map/RenderType.java b/src/main/java/com/volmit/iris/map/RenderType.java new file mode 100644 index 000000000..991d8dd43 --- /dev/null +++ b/src/main/java/com/volmit/iris/map/RenderType.java @@ -0,0 +1,5 @@ +package com.volmit.iris.map; + +public enum RenderType { + BIOME_LAND, REGION, CAVE_LAND +} diff --git a/src/main/java/com/volmit/iris/map/Tile.java b/src/main/java/com/volmit/iris/map/Tile.java new file mode 100644 index 000000000..43506bca4 --- /dev/null +++ b/src/main/java/com/volmit/iris/map/Tile.java @@ -0,0 +1,87 @@ +package com.volmit.iris.map; + +import com.volmit.iris.generator.IrisComplex; +import com.volmit.iris.object.IrisBiome; +import com.volmit.iris.object.IrisRegion; +import com.volmit.iris.scaffold.stream.ProceduralStream; +import lombok.Getter; +import lombok.Setter; + +import java.awt.image.BufferedImage; +import java.util.Set; +import java.util.function.BiFunction; + +public class Tile { + + @Getter + private short x; + @Getter + private short y; + + @Getter + private BufferedImage image; + + private Set biomes; + private Set regions; + + + + @Getter + @Setter + private boolean dirty; + + @Getter + private boolean rendering; + + public Tile(short x, short y) { + this.x = x; + this.y = y; + } + + public boolean hasBiome(int biome) { + return biomes.contains(biome); + } + + public boolean hasRegion(int region) { + return regions.contains(region); + } + + /** + * Render the tile + * @param complex The world complex + * @param type The type of render + * @return True when rendered + */ + public boolean render(IrisComplex complex, RenderType type) { + BufferedImage newImage = new BufferedImage(128, 128, BufferedImage.TYPE_INT_RGB); + + ProceduralStream stream; + BiFunction getColor; + if (type == RenderType.BIOME_LAND) { + stream = complex.getLandBiomeStream(); + getColor = (x, z) -> ((ProceduralStream)stream).get(x, z).getColor().getAsRGB(); + } else if (type == RenderType.REGION) { + stream = complex.getRegionStream(); + getColor = (x, z) -> ((ProceduralStream)stream).get(x, z).getColor().getColor().getRGB(); + } else { + stream = complex.getCaveBiomeStream(); + getColor = (x, z) -> ((ProceduralStream)stream).get(x, z).getColor().getAsRGB(); + } + + for (int i = 0; i < 128; i++) { + for (int j = 0; j < 128; j++) { + newImage.setRGB(i, j, getColor.apply(translate(x, i), translate(y, j))); + } + } + image = newImage; + rendering = false; + dirty = false; + return true; + } + + public static int translate(int section, int pixel) { + return (section << 9) | (pixel << 2) | 2; + } + + +} diff --git a/src/main/java/com/volmit/iris/object/IrisBiome.java b/src/main/java/com/volmit/iris/object/IrisBiome.java index 32d55ac23..8e79ca688 100644 --- a/src/main/java/com/volmit/iris/object/IrisBiome.java +++ b/src/main/java/com/volmit/iris/object/IrisBiome.java @@ -1,5 +1,6 @@ package com.volmit.iris.object; +import com.volmit.iris.Iris; import com.volmit.iris.generator.IrisComplex; import com.volmit.iris.generator.noise.CNG; import com.volmit.iris.manager.IrisDataManager; @@ -583,4 +584,26 @@ public class IrisBiome extends IrisRegistrant implements IRare { return getLayers().get(0).get(rng, x, 0, z, idm); } + + public IrisColor getColor() { + if (this.color == null) { + RandomColor randomColor = new RandomColor(getName().hashCode()); + if (this.getVanillaDerivative() == null) { + this.color = new IrisColor(); + this.color.setRed(255).setGreen(255).setBlue(255); + Iris.warn("No vanilla biome found for " + getName()); + } + RandomColor.Color col = VanillaBiomeMap.getColorType(this.getVanillaDerivative()); + RandomColor.Luminosity lum = VanillaBiomeMap.getColorLuminosity(this.getVanillaDerivative()); + RandomColor.SaturationType sat = VanillaBiomeMap.getColorSaturatiom(this.getVanillaDerivative()); + int newColorI = randomColor.randomColor(col, col == RandomColor.Color.MONOCHROME ? RandomColor.SaturationType.MONOCHROME : sat, lum); + + + Color newColor = new Color(newColorI); + this.color = new IrisColor(); + this.color.setRed(newColor.getRed()).setBlue(newColor.getBlue()).setGreen(newColor.getGreen()); + } + + return this.color; + } } diff --git a/src/main/java/com/volmit/iris/object/IrisColor.java b/src/main/java/com/volmit/iris/object/IrisColor.java index 9da4c7f1c..9d648371d 100644 --- a/src/main/java/com/volmit/iris/object/IrisColor.java +++ b/src/main/java/com/volmit/iris/object/IrisColor.java @@ -86,4 +86,17 @@ public class IrisColor { return new Color(a << 24 | r << 16 | g << 8 | b); } + + public int getAsRGB() { + if (hex != null) { + try { + if (hex.startsWith("#")) hex = hex.substring(1); + return Integer.parseInt(hex, 16); + } catch (NumberFormatException e) { + return 0; + } + } + + return red << 16 | green << 8 | blue; + } } diff --git a/src/main/java/com/volmit/iris/util/FakeEngine.java b/src/main/java/com/volmit/iris/util/FakeEngine.java new file mode 100644 index 000000000..66654b736 --- /dev/null +++ b/src/main/java/com/volmit/iris/util/FakeEngine.java @@ -0,0 +1,139 @@ +package com.volmit.iris.util; + +import com.volmit.iris.manager.IrisDataManager; +import com.volmit.iris.object.IrisBiome; +import com.volmit.iris.object.IrisDimension; +import com.volmit.iris.scaffold.engine.Engine; +import com.volmit.iris.scaffold.engine.EngineCompound; +import com.volmit.iris.scaffold.engine.EngineEffects; +import com.volmit.iris.scaffold.engine.EngineFramework; +import com.volmit.iris.scaffold.engine.EngineMetrics; +import com.volmit.iris.scaffold.engine.EngineTarget; +import com.volmit.iris.scaffold.engine.EngineWorldManager; +import com.volmit.iris.scaffold.hunk.Hunk; +import com.volmit.iris.util.FakeWorld; +import lombok.Getter; +import org.bukkit.World; +import org.bukkit.block.Biome; +import org.bukkit.block.data.BlockData; + + +public class FakeEngine implements Engine { + + @Getter + private IrisDimension dimension; + + @Getter + private World world; + + public FakeEngine(IrisDimension dimension, FakeWorld world) { + this.dimension = dimension; + this.world = world; + } + + @Override + public void close() { } + + @Override + public boolean isClosed() { + return false; + } + + @Override + public IrisDataManager getData() { + return dimension.getLoader().copy(); + } + + @Override + public EngineWorldManager getWorldManager() { + return null; + } + + @Override + public void setParallelism(int parallelism) { } + + @Override + public int getParallelism() { + return 0; + } + + @Override + public EngineTarget getTarget() { + return null; + } + + @Override + public EngineFramework getFramework() { + return null; + } + + @Override + public void setMinHeight(int min) { } + + @Override + public void recycle() { } + + @Override + public int getIndex() { + return 0; + } + + @Override + public int getMinHeight() { + return 0; + } + + @Override + public int getHeight() { + return 64; + } + + @Override + public double modifyX(double x) { + return 0; + } + + @Override + public double modifyZ(double z) { + return 0; + } + + @Override + public void generate(int x, int z, Hunk blocks, Hunk biomes) { } + + @Override + public EngineMetrics getMetrics() { + return null; + } + + @Override + public EngineEffects getEffects() { + return null; + } + + @Override + public EngineCompound getCompound() { + return null; + } + + @Override + public IrisBiome getFocus() { + return null; + } + + @Override + public void fail(String error, Throwable e) { } + + @Override + public boolean hasFailed() { + return false; + } + + @Override + public int getCacheID() { + return 0; + } + + @Override + public void hotload() { } +} diff --git a/src/main/java/com/volmit/iris/util/RandomColor.java b/src/main/java/com/volmit/iris/util/RandomColor.java new file mode 100644 index 000000000..23e11ffa9 --- /dev/null +++ b/src/main/java/com/volmit/iris/util/RandomColor.java @@ -0,0 +1,506 @@ +package com.volmit.iris.util; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Random; + +/** + * Credit to https://github.com/lzyzsd/AndroidRandomColor + */ +public class RandomColor { + + public static int hueOffset = 0; + + public static class ColorInfo { + Range hueRange; + Range saturationRange; + Range brightnessRange; + List lowerBounds; + + public ColorInfo(Range hueRange, Range saturationRange, Range brightnessRange, List lowerBounds) { + this.hueRange = hueRange; + this.saturationRange = saturationRange; + this.brightnessRange = brightnessRange; + this.lowerBounds = lowerBounds; + } + + public Range getHueRange() { + return hueRange; + } + + public void setHueRange(Range hueRange) { + this.hueRange = hueRange; + } + + public Range getSaturationRange() { + return saturationRange; + } + + public void setSaturationRange(Range saturationRange) { + this.saturationRange = saturationRange; + } + + public Range getBrightnessRange() { + return brightnessRange; + } + + public void setBrightnessRange(Range brightnessRange) { + this.brightnessRange = brightnessRange; + } + + public List getLowerBounds() { + return lowerBounds; + } + + public void setLowerBounds(List lowerBounds) { + this.lowerBounds = lowerBounds; + } + } + + public static class Range { + int start; + int end; + + public Range(int start, int end) { + this.start = start; + this.end = end; + } + + public boolean contain(int value) { + return value >= start && value <= end; + } + + @Override + public String toString() { + return "start: " + start + " end: " + end; + } + } + + private Random random; + + public static enum SaturationType { + RANDOM, MONOCHROME, HIGH, LOW, MEDIUM + } + + public static enum Luminosity { + BRIGHT, LIGHT, DARK, RANDOM + } + + public static class Options { + int hue; + SaturationType saturationType; + Luminosity luminosity; + + public int getHue() { + return hue; + } + + public void setHue(int hue) { + this.hue = hue; + } + + public SaturationType getSaturationType() { + return saturationType; + } + + public void setSaturationType(SaturationType saturationType) { + this.saturationType = saturationType; + } + + public Luminosity getLuminosity() { + return luminosity; + } + + public void setLuminosity(Luminosity luminosity) { + this.luminosity = luminosity; + } + } + + private HashMap colors = new HashMap<>(); + + public RandomColor() { + loadColorBounds(); + random = new Random(); + } + + public RandomColor(long seed){ + loadColorBounds(); + random = new Random(); + random.setSeed(seed); + } + + private int getColor(int hue, int saturation, int brightness) { + return java.awt.Color.getHSBColor((float)(hue + hueOffset % 360) / 360, (float)saturation / 100, (float)brightness / 100).getRGB(); + } + + public int randomColor() { + return randomColor(0, null, null); + } + + public int randomColor(int value, SaturationType saturationType, Luminosity luminosity) { + int hue = value; + hue = pickHue(hue); + int saturation = pickSaturation(hue, saturationType, luminosity); + int brightness = pickBrightness(hue, saturation, luminosity); + + return getColor(hue, saturation, brightness); + } + + public int randomColor(Color color, SaturationType saturationType, Luminosity luminosity) { + int hue = pickHue(color.name()); + int saturation = pickSaturation(hue, saturationType, luminosity); + int brightness = pickBrightness(hue, saturation, luminosity); + + return getColor(hue, saturation, brightness); + } + + public int[] randomColor(int count) { + if (count <= 0) { + throw new IllegalArgumentException("count must be greater than 0"); + } + + int[] colors = new int[count]; + for (int i = 0; i < count; i++) { + colors[i] = randomColor(); + } + + return colors; + } + + public int randomColor(Color color) { + int hue = pickHue(color.name()); + int saturation = pickSaturation(color, null, null); + int brightness = pickBrightness(color, saturation, null); + + int colorValue = getColor(hue, saturation, brightness); + return colorValue; + } + + public int[] random(Color color, int count) { + if (count <= 0) { + throw new IllegalArgumentException("count must be greater than 0"); + } + + int[] colors = new int[count]; + for (int i = 0; i < count; i++) { + colors[i] = randomColor(color); + } + + return colors; + } + + private int pickHue(int hue) { + Range hueRange = getHueRange(hue); + return doPickHue(hueRange); + } + + private int doPickHue(Range hueRange) { + int hue = randomWithin(hueRange); + + // Instead of storing red as two seperate ranges, + // we group them, using negative numbers + if (hue < 0) { + hue = 360 + hue; + } + + return hue; + } + + private int pickHue(String name) { + Range hueRange = getHueRange(name); + return doPickHue(hueRange); + } + + private Range getHueRange(int number) { + if (number < 360 && number > 0) { + return new Range(number, number); + } + + return new Range(0, 360); + } + + private Range getHueRange(String name) { + if (colors.containsKey(name)) { + return colors.get(name).getHueRange(); + } + + return new Range(0, 360); + } + + private int pickSaturation(int hue, SaturationType saturationType, Luminosity luminosity) { + return pickSaturation(getColorInfo(hue), saturationType, luminosity); + } + + private int pickSaturation(Color color, SaturationType saturationType, Luminosity luminosity) { + ColorInfo colorInfo = colors.get(color.name()); + return pickSaturation(colorInfo, saturationType, luminosity); + } + + private int pickSaturation(ColorInfo colorInfo, SaturationType saturationType, Luminosity luminosity) { + if (saturationType != null) { + switch (saturationType) { + case RANDOM: + return randomWithin(new Range(0, 100)); + case MONOCHROME: + return 0; + case HIGH: + return randomWithin(new Range(75, 100)); + case MEDIUM: + return randomWithin(new Range(55, 75)); + case LOW: + return randomWithin(new Range(35, 55)); + } + } + + if (colorInfo == null) { + return 0; + } + + Range saturationRange = colorInfo.getSaturationRange(); + + int min = saturationRange.start; + int max = saturationRange.end; + + if (luminosity != null) { + switch (luminosity) { + case LIGHT: + min = 55; + break; + case BRIGHT: + min = max - 10; + break; + case DARK: + max = 55; + break; + } + } + + return randomWithin(new Range(min, max)); + } + + private int pickBrightness(int hue, int saturation, Luminosity luminosity) { + ColorInfo colorInfo = getColorInfo(hue); + + return pickBrightness(colorInfo, saturation, luminosity); + } + + private int pickBrightness(Color color, int saturation, Luminosity luminosity) { + ColorInfo colorInfo = colors.get(color.name()); + + return pickBrightness(colorInfo, saturation, luminosity); + } + + private int pickBrightness(ColorInfo colorInfo, int saturation, Luminosity luminosity) { + int min = getMinimumBrightness(colorInfo, saturation), + max = 100; + + if (luminosity != null) { + switch (luminosity) { + + case DARK: + max = min + 20; + break; + + case LIGHT: + min = (max + min) / 2; + break; + + case RANDOM: + min = 0; + max = 100; + break; + } + } + + return randomWithin(new Range(min, max)); + } + + private int getMinimumBrightness(ColorInfo colorInfo, int saturation) { + if (colorInfo == null) { + return 0; + } + + List lowerBounds = colorInfo.getLowerBounds(); + for (int i = 0; i < lowerBounds.size() - 1; i++) { + + int s1 = lowerBounds.get(i).start, + v1 = lowerBounds.get(i).end; + + if (i == lowerBounds.size() - 1) { + break; + } + int s2 = lowerBounds.get(i + 1).start, + v2 = lowerBounds.get(i + 1).end; + + if (saturation >= s1 && saturation <= s2) { + + float m = (v2 - v1)/(float) (s2 - s1), + b = v1 - m*s1; + + return (int) (m*saturation + b); + } + + } + + return 0; + } + + private ColorInfo getColorInfo(int hue) { + // Maps red colors to make picking hue easier + if (hue >= 334 && hue <= 360) { + hue-= 360; + } + + for(String key : colors.keySet()) { + ColorInfo colorInfo = colors.get(key); + if (colorInfo.getHueRange() != null && colorInfo.getHueRange().contain(hue)) { + return colorInfo; + } + } + + return null; + } + + private int randomWithin (Range range) { + return (int) Math.floor(range.start + random.nextDouble()*(range.end + 1 - range.start)); + } + + public void defineColor(String name, Range hueRange, List lowerBounds) { + int sMin = lowerBounds.get(0).start; + int sMax = lowerBounds.get(lowerBounds.size() - 1).start; + int bMin = lowerBounds.get(lowerBounds.size() - 1).end; + int bMax = lowerBounds.get(0).end; + + colors.put(name, new ColorInfo(hueRange, new Range(sMin, sMax), new Range(bMin, bMax), lowerBounds)); + } + + private void loadColorBounds() { + List lowerBounds1 = new ArrayList<>(); + lowerBounds1.add(new Range(0, 0)); + lowerBounds1.add(new Range(100, 0)); + defineColor( + Color.MONOCHROME.name(), + new Range(0, 0), + lowerBounds1 + ); + + List lowerBounds2 = new ArrayList<>(); + lowerBounds2.add(new Range(20, 100)); + lowerBounds2.add(new Range(30, 92)); + lowerBounds2.add(new Range(40, 89)); + lowerBounds2.add(new Range(50, 85)); + lowerBounds2.add(new Range(60, 78)); + lowerBounds2.add(new Range(70, 70)); + lowerBounds2.add(new Range(80, 60)); + lowerBounds2.add(new Range(90, 55)); + lowerBounds2.add(new Range(100, 50)); + defineColor( + Color.RED.name(), + new Range(-26, 18), + lowerBounds2 + ); + + List lowerBounds3 = new ArrayList(); + lowerBounds3.add(new Range(20, 100)); + lowerBounds3.add(new Range(30, 93)); + lowerBounds3.add(new Range(40, 88)); + lowerBounds3.add(new Range(50, 86)); + lowerBounds3.add(new Range(60, 85)); + lowerBounds3.add(new Range(70, 70)); + lowerBounds3.add(new Range(100, 70)); + defineColor( + Color.ORANGE.name(), + new Range(19, 46), + lowerBounds3 + ); + + List lowerBounds4 = new ArrayList<>(); + lowerBounds4.add(new Range(25, 100)); + lowerBounds4.add(new Range(40, 94)); + lowerBounds4.add(new Range(50, 89)); + lowerBounds4.add(new Range(60, 86)); + lowerBounds4.add(new Range(70, 84)); + lowerBounds4.add(new Range(80, 82)); + lowerBounds4.add(new Range(90, 80)); + lowerBounds4.add(new Range(100, 75)); + + defineColor( + Color.YELLOW.name(), + new Range(47, 62), + lowerBounds4 + ); + + List lowerBounds5 = new ArrayList<>(); + lowerBounds5.add(new Range(30, 100)); + lowerBounds5.add(new Range(40, 90)); + lowerBounds5.add(new Range(50, 85)); + lowerBounds5.add(new Range(60, 81)); + lowerBounds5.add(new Range(70, 74)); + lowerBounds5.add(new Range(80, 64)); + lowerBounds5.add(new Range(90, 50)); + lowerBounds5.add(new Range(100, 40)); + + defineColor( + Color.GREEN.name(), + new Range(63,178), + lowerBounds5 + ); + + List lowerBounds6 = new ArrayList<>(); + lowerBounds6.add(new Range(20, 100)); + lowerBounds6.add(new Range(30, 86)); + lowerBounds6.add(new Range(40, 80)); + lowerBounds6.add(new Range(50, 74)); + lowerBounds6.add(new Range(60, 60)); + lowerBounds6.add(new Range(70, 52)); + lowerBounds6.add(new Range(80, 44)); + lowerBounds6.add(new Range(90, 39)); + lowerBounds6.add(new Range(100, 35)); + + defineColor( + Color.BLUE.name(), + new Range(179, 257), + lowerBounds6 + ); + + List lowerBounds7 = new ArrayList<>(); + lowerBounds7.add(new Range(20, 100)); + lowerBounds7.add(new Range(30, 87)); + lowerBounds7.add(new Range(40, 79)); + lowerBounds7.add(new Range(50, 70)); + lowerBounds7.add(new Range(60, 65)); + lowerBounds7.add(new Range(70, 59)); + lowerBounds7.add(new Range(80, 52)); + lowerBounds7.add(new Range(90, 45)); + lowerBounds7.add(new Range(100, 42)); + + defineColor( + Color.PURPLE.name(), + new Range(258, 282), + lowerBounds7 + ); + + List lowerBounds8 = new ArrayList<>(); + lowerBounds8.add(new Range(20, 100)); + lowerBounds8.add(new Range(30, 90)); + lowerBounds8.add(new Range(40, 86)); + lowerBounds8.add(new Range(60, 84)); + lowerBounds8.add(new Range(80, 80)); + lowerBounds8.add(new Range(90, 75)); + lowerBounds8.add(new Range(100, 73)); + + defineColor( + Color.PINK.name(), + new Range(283, 334), + lowerBounds8 + ); + } + + public static enum Color { + MONOCHROME, RED, ORANGE, YELLOW, GREEN, BLUE, PURPLE, PINK + } + +} + + diff --git a/src/main/java/com/volmit/iris/util/VanillaBiomeMap.java b/src/main/java/com/volmit/iris/util/VanillaBiomeMap.java new file mode 100644 index 000000000..ddb6fe099 --- /dev/null +++ b/src/main/java/com/volmit/iris/util/VanillaBiomeMap.java @@ -0,0 +1,128 @@ +package com.volmit.iris.util; + +import org.apache.commons.lang3.tuple.ImmutableTriple; +import org.apache.commons.lang3.tuple.Triple; +import org.bukkit.block.Biome; +import com.volmit.iris.util.RandomColor.*; + +public class VanillaBiomeMap { + + private static KMap BIOME_HEX = new KMap<>(); + private static KMap BIOME_COLOR = new KMap<>(); + private static KMap BIOME_LUMINOSITY = new KMap<>(); + private static KMap BIOME_SATURATION = new KMap<>(); + private static KMap BIOME_IDs = new KMap<>(); + + private static void add(Biome biome, int color, short id, Color randomColor, Luminosity luminosity, SaturationType saturation) { + BIOME_HEX.put(biome, color); + BIOME_COLOR.put(biome, randomColor); + if (luminosity != null) BIOME_LUMINOSITY.put(biome, luminosity); + if (saturation != null) BIOME_SATURATION.put(biome, saturation); + BIOME_IDs.put(biome, id); + } + + private static void add(Biome biome, int color, short id, Color randomColor, Luminosity luminosity) { + add(biome, color, id, randomColor, luminosity, null); + } + + public static int getColor(Biome biome) { + return BIOME_HEX.get(biome); + } + + public static Color getColorType(Biome biome) { + return BIOME_COLOR.get(biome); + } + + public static Luminosity getColorLuminosity(Biome biome) { + return BIOME_LUMINOSITY.get(biome); + } + + public static SaturationType getColorSaturatiom(Biome biome) { + return BIOME_SATURATION.get(biome); + } + + public static short getId(Biome biome) { + return BIOME_IDs.get(biome); + } + + static { + add(Biome.OCEAN, 0x000070, (short) 0, Color.BLUE, Luminosity.BRIGHT, SaturationType.MEDIUM); + add(Biome.PLAINS, 0x8DB360, (short) 1, Color.GREEN, Luminosity.LIGHT, SaturationType.MEDIUM); + add(Biome.DESERT, 0xFA9418, (short) 2, Color.YELLOW, Luminosity.LIGHT, SaturationType.MEDIUM); + add(Biome.MOUNTAINS, 0x606060, (short) 3, Color.MONOCHROME, Luminosity.BRIGHT, null); + add(Biome.FOREST, 0x056621, (short) 4, Color.GREEN, Luminosity.BRIGHT); + add(Biome.TAIGA, 0x0B6659, (short) 5, Color.GREEN, Luminosity.BRIGHT, SaturationType.MEDIUM); + add(Biome.SWAMP, 0x07F9B2, (short) 6, Color.ORANGE, Luminosity.DARK, SaturationType.MEDIUM); + add(Biome.RIVER, 0x0000FF, (short) 7, Color.BLUE, Luminosity.LIGHT, SaturationType.LOW); + add(Biome.NETHER_WASTES, 0xBF3B3B, (short) 8, Color.RED, Luminosity.LIGHT, SaturationType.MEDIUM); + add(Biome.THE_END, 0x8080FF, (short) 9, Color.PURPLE, Luminosity.LIGHT, SaturationType.LOW); + add(Biome.FROZEN_OCEAN, 0x7070D6, (short) 10, Color.BLUE, Luminosity.BRIGHT, SaturationType.MEDIUM); + add(Biome.FROZEN_RIVER, 0xA0A0FF, (short) 11, Color.BLUE, Luminosity.BRIGHT, SaturationType.MEDIUM); + add(Biome.SNOWY_TUNDRA, 0xFFFFFF, (short) 12, Color.MONOCHROME, Luminosity.LIGHT); + add(Biome.SNOWY_MOUNTAINS, 0xA0A0A0, (short) 13, Color.MONOCHROME, Luminosity.LIGHT); + add(Biome.MUSHROOM_FIELDS, 0xFF00FF, (short) 14, Color.PURPLE, Luminosity.BRIGHT); + add(Biome.MUSHROOM_FIELD_SHORE, 0xA000FF, (short) 15, Color.PURPLE, Luminosity.BRIGHT); + add(Biome.BEACH, 0xFADE55, (short) 16, Color.YELLOW, Luminosity.LIGHT, SaturationType.LOW); + add(Biome.DESERT_HILLS, 0xD25F12, (short) 17, Color.YELLOW, Luminosity.LIGHT, SaturationType.MEDIUM); + add(Biome.WOODED_HILLS, 0x22551C, (short) 18, Color.GREEN, Luminosity.LIGHT); + add(Biome.TAIGA_HILLS, 0x163933, (short) 19, Color.GREEN, Luminosity.BRIGHT, SaturationType.MEDIUM); + add(Biome.MOUNTAIN_EDGE, 0x72789A, (short) 20, Color.MONOCHROME, Luminosity.BRIGHT); + add(Biome.JUNGLE, 0x537B09, (short) 21, Color.GREEN, Luminosity.BRIGHT, SaturationType.HIGH); + add(Biome.JUNGLE_HILLS, 0x2C4205, (short) 22, Color.GREEN, Luminosity.DARK, SaturationType.HIGH); + add(Biome.JUNGLE_EDGE, 0x628B17, (short) 23, Color.GREEN, Luminosity.BRIGHT, SaturationType.HIGH); + add(Biome.DEEP_OCEAN, 0x000030, (short) 24, Color.BLUE, Luminosity.DARK); + add(Biome.STONE_SHORE, 0xA2A284, (short) 25, Color.GREEN, Luminosity.DARK); + add(Biome.SNOWY_BEACH, 0xFAF0C0, (short) 26, Color.YELLOW, Luminosity.LIGHT); + add(Biome.BIRCH_FOREST, 0x307444, (short) 27, Color.GREEN, Luminosity.LIGHT); + add(Biome.BIRCH_FOREST_HILLS, 0x1F5F32, (short) 28, Color.GREEN, Luminosity.LIGHT); + add(Biome.DARK_FOREST, 0x40511A, (short) 29, Color.GREEN, Luminosity.DARK); + add(Biome.SNOWY_TAIGA, 0x31554A, (short) 30, Color.BLUE, Luminosity.LIGHT); + add(Biome.SNOWY_TAIGA_HILLS, 0x243F36, (short) 31, Color.BLUE, Luminosity.LIGHT); + add(Biome.GIANT_TREE_TAIGA, 0x596651, (short) 32, Color.ORANGE, Luminosity.LIGHT); + add(Biome.GIANT_TREE_TAIGA_HILLS, 0x454F3E, (short) 33, Color.ORANGE, Luminosity.LIGHT); + add(Biome.WOODED_MOUNTAINS, 0x507050, (short) 34, Color.MONOCHROME, Luminosity.BRIGHT); + add(Biome.SAVANNA, 0xBDB25F, (short) 35, Color.GREEN, Luminosity.LIGHT); + add(Biome.SAVANNA_PLATEAU, 0xA79D64, (short) 36, Color.GREEN, Luminosity.LIGHT); + add(Biome.BADLANDS, 0xD94515, (short) 37, Color.ORANGE, Luminosity.BRIGHT, SaturationType.MEDIUM); + add(Biome.WOODED_BADLANDS_PLATEAU, 0xB09765, (short) 38, Color.ORANGE, Luminosity.BRIGHT, SaturationType.HIGH); + add(Biome.BADLANDS_PLATEAU, 0xCA8C65, (short) 39, Color.ORANGE, Luminosity.BRIGHT, SaturationType.HIGH); + add(Biome.END_MIDLANDS, 0x8080FF, (short) 41, Color.YELLOW, Luminosity.LIGHT, SaturationType.LOW); + add(Biome.END_HIGHLANDS, 0x8080FF, (short) 42, Color.PURPLE, Luminosity.LIGHT, SaturationType.LOW); + add(Biome.END_BARRENS, 0x8080FF, (short) 43, Color.PURPLE, Luminosity.LIGHT, SaturationType.MEDIUM); + add(Biome.WARM_OCEAN, 0x0000AC, (short) 44, Color.BLUE, Luminosity.BRIGHT, SaturationType.LOW); + add(Biome.LUKEWARM_OCEAN, 0x000090, (short) 45, Color.BLUE, Luminosity.BRIGHT, SaturationType.MEDIUM); + add(Biome.COLD_OCEAN, 0x202070, (short) 46, Color.BLUE, Luminosity.BRIGHT, SaturationType.HIGH); + add(Biome.DEEP_WARM_OCEAN, 0x000050, (short) 47, Color.BLUE, Luminosity.DARK, SaturationType.LOW); + add(Biome.DEEP_LUKEWARM_OCEAN, 0x000040, (short) 48, Color.BLUE, Luminosity.DARK, SaturationType.MEDIUM); + add(Biome.DEEP_COLD_OCEAN, 0x202038, (short) 49, Color.BLUE, Luminosity.DARK, SaturationType.HIGH); + add(Biome.DEEP_FROZEN_OCEAN, 0x404090, (short) 50, Color.BLUE, Luminosity.LIGHT, SaturationType.LOW); + add(Biome.THE_VOID, 0x000000, (short) 127, Color.MONOCHROME, Luminosity.DARK); + add(Biome.SUNFLOWER_PLAINS, 0xB5DB88, (short) 129, Color.GREEN, Luminosity.LIGHT, SaturationType.LOW); + add(Biome.DESERT_LAKES, 0xFFBC40, (short) 130, Color.BLUE, Luminosity.LIGHT, SaturationType.LOW); + add(Biome.GRAVELLY_MOUNTAINS, 0x888888, (short) 131, Color.MONOCHROME, Luminosity.LIGHT); + add(Biome.FLOWER_FOREST, 0x2D8E49, (short) 132, Color.RED, Luminosity.LIGHT, SaturationType.LOW); + add(Biome.TAIGA_MOUNTAINS, 0x338E81, (short) 133, Color.GREEN, Luminosity.DARK, SaturationType.MEDIUM); + add(Biome.SWAMP_HILLS, 0x2FFFDA, (short) 134, Color.ORANGE, Luminosity.DARK, SaturationType.MEDIUM); + add(Biome.ICE_SPIKES, 0xB4DCDC, (short) 140, Color.BLUE, Luminosity.LIGHT, SaturationType.LOW); + add(Biome.MODIFIED_JUNGLE, 0x7BA331, (short) 149, Color.GREEN, Luminosity.BRIGHT, SaturationType.HIGH); + add(Biome.MODIFIED_JUNGLE_EDGE, 0x8AB33F, (short) 151, Color.GREEN, Luminosity.BRIGHT, SaturationType.HIGH); + add(Biome.TALL_BIRCH_FOREST, 0x589C6C, (short) 155, Color.GREEN, Luminosity.LIGHT); + add(Biome.TALL_BIRCH_HILLS, 0x47875A, (short) 156, Color.GREEN, Luminosity.LIGHT); + add(Biome.DARK_FOREST_HILLS, 0x687942, (short) 157, Color.GREEN, Luminosity.DARK); + add(Biome.SNOWY_TAIGA_MOUNTAINS, 0x597D72, (short) 158, Color.BLUE, Luminosity.LIGHT); + add(Biome.GIANT_SPRUCE_TAIGA, 0x818E79, (short) 160, Color.ORANGE, Luminosity.DARK, SaturationType.HIGH); + add(Biome.GIANT_SPRUCE_TAIGA_HILLS, 0x6D7766, (short) 161, Color.ORANGE, Luminosity.DARK, SaturationType.HIGH); + add(Biome.GRAVELLY_MOUNTAINS, 0x789878, (short) 162, Color.MONOCHROME, Luminosity.LIGHT); + add(Biome.SHATTERED_SAVANNA, 0xE5DA87, (short) 163, Color.ORANGE, Luminosity.LIGHT, SaturationType.HIGH); + add(Biome.SHATTERED_SAVANNA_PLATEAU, 0xCFC58C, (short) 164, Color.ORANGE, Luminosity.LIGHT, SaturationType.HIGH); + add(Biome.ERODED_BADLANDS, 0xFF6D3D, (short) 165, Color.ORANGE, Luminosity.LIGHT, SaturationType.HIGH); + add(Biome.MODIFIED_WOODED_BADLANDS_PLATEAU, 0xD8BF8D, (short) 166, Color.ORANGE, Luminosity.BRIGHT); + add(Biome.MODIFIED_BADLANDS_PLATEAU, 0xF2B48D, (short) 167, Color.ORANGE, Luminosity.BRIGHT); + add(Biome.BAMBOO_JUNGLE, 0x768E14, (short) 168, Color.GREEN, Luminosity.BRIGHT, SaturationType.HIGH); + add(Biome.BAMBOO_JUNGLE_HILLS, 0x3B470A, (short) 169, Color.GREEN, Luminosity.BRIGHT, SaturationType.HIGH); + add(Biome.SOUL_SAND_VALLEY, 0x5E3830, (short) 170, Color.BLUE, Luminosity.BRIGHT, SaturationType.MEDIUM); + add(Biome.CRIMSON_FOREST, 0xDD0808, (short) 171, Color.RED, Luminosity.DARK, SaturationType.HIGH); + add(Biome.WARPED_FOREST, 0x49907B, (short) 172, Color.BLUE, Luminosity.BRIGHT); + add(Biome.BASALT_DELTAS, 0x403636, (short) 173, Color.MONOCHROME, Luminosity.DARK); + } +}