diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/ConfigurationFile.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/ConfigurationFile.java new file mode 100644 index 00000000..9bc1ce32 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/ConfigurationFile.java @@ -0,0 +1,289 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package de.bluecolored.bluemap.core.config; + +import java.io.File; +import java.io.IOException; +import java.net.InetAddress; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collection; + +import org.apache.commons.io.FileUtils; + +import com.google.common.base.Preconditions; + +import de.bluecolored.bluemap.core.render.RenderSettings; +import de.bluecolored.bluemap.core.web.WebServerConfig; +import ninja.leaping.configurate.ConfigurationNode; +import ninja.leaping.configurate.commented.CommentedConfigurationNode; +import ninja.leaping.configurate.hocon.HoconConfigurationLoader; +import ninja.leaping.configurate.loader.ConfigurationLoader; + +public class ConfigurationFile implements WebServerConfig { + + private String configVersion; + + private boolean webserverEnabled; + private int webserverPort; + private int webserverMaxConnections; + private InetAddress webserverBindAdress; + + private Path webRoot; + private Path webDataPath; + + private int renderThreadCount; + + private Collection mapConfigs; + + private ConfigurationFile(File configFile) throws IOException { + ConfigurationLoader configLoader = HoconConfigurationLoader.builder() + .setFile(configFile) + .build(); + + CommentedConfigurationNode rootNode = configLoader.load(); + + configVersion = rootNode.getNode("version").getString("-"); + + loadWebConfig(rootNode.getNode("web")); + + int defaultCount = (int) Math.max(Math.min((double) Runtime.getRuntime().availableProcessors() * 0.75, 16), 1); + renderThreadCount = rootNode.getNode("renderThreadCount").getInt(defaultCount); + if (renderThreadCount <= 0) renderThreadCount = defaultCount; + + loadMapConfigs(rootNode.getNode("maps")); + } + + private void loadWebConfig(ConfigurationNode node) throws IOException { + webserverEnabled = node.getNode("enabled").getBoolean(false); + + String webRootString = node.getNode("webroot").getString(); + if (webserverEnabled && webRootString == null) throw new IOException("Invalid configuration: Node web.webroot is not defined"); + webRoot = toFolder(webRootString); + + if (webserverEnabled) { + webserverPort = node.getNode("port").getInt(8100); + webserverMaxConnections = node.getNode("maxConnectionCount").getInt(100); + + String webserverBindAdressString = node.getNode("ip").getString(""); + if (webserverBindAdressString.isEmpty()) { + webserverBindAdress = InetAddress.getLocalHost(); + } else { + webserverBindAdress = InetAddress.getByName(webserverBindAdressString); + } + } + + String webDataString = node.getNode("data").getString(); + if (webDataString != null) + webDataPath = toFolder(webDataString); + else if (webRoot != null) + webDataPath = webRoot.resolve("data"); + else + throw new IOException("Invalid configuration: Node web.data is not defined in config"); + } + + private void loadMapConfigs(ConfigurationNode node) throws IOException { + mapConfigs = new ArrayList<>(); + for (ConfigurationNode mapConfigNode : node.getChildrenList()) { + mapConfigs.add(new MapConfig(mapConfigNode)); + } + } + + public boolean isWebserverEnabled() { + return webserverEnabled; + } + + public Path getWebDataPath() { + return webDataPath; + } + + @Override + public int getWebserverPort() { + return webserverPort; + } + + @Override + public int getWebserverMaxConnections() { + return webserverMaxConnections; + } + + @Override + public InetAddress getWebserverBindAdress() { + return webserverBindAdress; + } + + @Override + public Path getWebRoot() { + return webRoot; + } + + public String getConfigVersion() { + return configVersion; + } + + public int getRenderThreadCount() { + return renderThreadCount; + } + + public Collection getMapConfigs(){ + return mapConfigs; + } + + private Path toFolder(String pathString) throws IOException { + Preconditions.checkNotNull(pathString); + + File file = new File(pathString); + if (file.exists() && !file.isDirectory()) throw new IOException("Invalid configuration: Path '" + file.getAbsolutePath() + "' is a file (should be a directory)"); + if (!file.exists() && !file.mkdirs()) throw new IOException("Invalid configuration: Folders to path '" + file.getAbsolutePath() + "' could not be created"); + return file.toPath(); + } + + public static ConfigurationFile loadOrCreate(File configFile) throws IOException { + if (!configFile.exists()) { + configFile.getParentFile().mkdirs(); + + FileUtils.copyURLToFile(ConfigurationFile.class.getResource("/bluemap.conf"), configFile, 10000, 10000); + } + + return new ConfigurationFile(configFile); + } + + public class MapConfig implements RenderSettings { + + private String id; + private String name; + private String world; + + private boolean renderCaves; + private float ambientOcclusion; + private float lighting; + + private int maxY, minY, sliceY; + + private int hiresTileSize; + private float hiresViewDistance; + + private int lowresPointsPerHiresTile; + private int lowresPointsPerLowresTile; + private float lowresViewDistance; + + private MapConfig(ConfigurationNode node) throws IOException { + this.id = node.getNode("id").getString(""); + if (id.isEmpty()) throw new IOException("Invalid configuration: Node maps[?].id is not defined"); + + this.name = node.getNode("name").getString(id); + + this.world = node.getNode("world").getString(""); + if (world.isEmpty()) throw new IOException("Invalid configuration: Node maps[?].world is not defined"); + + this.renderCaves = node.getNode("renderCaves").getBoolean(false); + this.ambientOcclusion = node.getNode("ambientOcclusion").getFloat(0.25f); + this.lighting = node.getNode("lighting").getFloat(0.8f); + + this.maxY = node.getNode("maxY").getInt(RenderSettings.super.getMaxY()); + this.minY = node.getNode("minY").getInt(RenderSettings.super.getMinY()); + this.sliceY = node.getNode("sliceY").getInt(RenderSettings.super.getSliceY()); + + this.hiresTileSize = node.getNode("hires", "tileSize").getInt(32); + this.hiresViewDistance = node.getNode("hires", "viewDistance").getFloat(3.5f); + + this.lowresPointsPerHiresTile = node.getNode("lowres", "pointsPerHiresTile").getInt(4); + this.lowresPointsPerLowresTile = node.getNode("lowres", "pointsPerLowresTile").getInt(50); + this.lowresViewDistance = node.getNode("lowres", "viewDistance").getFloat(4f); + + //check valid configuration values + double blocksPerPoint = (double) this.hiresTileSize / (double) this.lowresPointsPerHiresTile; + if (blocksPerPoint != Math.floor(blocksPerPoint)) throw new IOException("Invalid configuration: Invalid map resolution settings of map " + id + ": hires.tileSize / lowres.pointsPerTile has to be an integer result"); + } + + public String getId() { + return id; + } + + public String getName() { + return name; + } + + public String getWorldId() { + return world; + } + + public boolean isRenderCaves() { + return renderCaves; + } + + @Override + public float getAmbientOcclusionStrenght() { + return ambientOcclusion; + } + + @Override + public float getLightShadeMultiplier() { + return lighting; + } + + public int getHiresTileSize() { + return hiresTileSize; + } + + public float getHiresViewDistance() { + return hiresViewDistance; + } + + public int getLowresPointsPerHiresTile() { + return lowresPointsPerHiresTile; + } + + public int getLowresPointsPerLowresTile() { + return lowresPointsPerLowresTile; + } + + public float getLowresViewDistance() { + return lowresViewDistance; + } + + @Override + public boolean isExcludeFacesWithoutSunlight() { + return !isRenderCaves(); + } + + @Override + public int getMaxY() { + return maxY; + } + + @Override + public int getMinY() { + return minY; + } + + @Override + public int getSliceY() { + return sliceY; + } + + } + +} diff --git a/BlueMapCore/src/main/resources/bluemap.conf b/BlueMapCore/src/main/resources/bluemap.conf new file mode 100644 index 00000000..9e82ba49 --- /dev/null +++ b/BlueMapCore/src/main/resources/bluemap.conf @@ -0,0 +1,152 @@ +## ## +## BlueMap ## +## ## +## by Blue (Lukas Rieger) ## +## http://bluecolored.de/ ## +## ## + +# !! Don't change this !! +# This is used to detect version-changes in the configuration +# and update configuration correctly. +version: "1.0.0" + +web { + # With this setting you can disable the web-server. + # This is usefull if you want to only render the map-data for later use, or if you setup your own webserver. + enabled: true + + # The webroot of the website that displays the map. + webroot: "bluemap/web" + + # The IP-Adress that the webserver binds to. + # If this setting is commented out, bluemap tries to find the default ip-adress of your system. + # If you only want to access it locally use "localhost". + #ip: "localhost" + #ip: "127.0.0.1" + + # The port that the webserver listenes to. + # Default is 8100 + port: 8100 + + # Max number of simultaneous connections that the webserver allows + # Default is 100 + maxConnectionCount: 100 + + # Unncomment this to override the path where bluemap stores the data-files. + # Default is "/data" + #data: "path/to/data/folder" +} + +# This changes the amount of threads that BlueMap will use to render the maps. +# A higher value can improve render-speed but could impact performance on the host machine. +# This should be always below or equal to the number of available processor-cores. +# If this value is commented out BlueMap tries to find the optimal thread count to max out render-performance +#renderThreadCount: 2 + +# This is an array with multiple configured maps. +# You can define multiple maps, for different worlds with different render-settings here +maps: [ + + { + # The id of this map + # Should only contain word-charactes: [a-zA-Z0-9_] + id: "world" + + # The name of this map + # This defines the display name of this map, you can change this at any time + # Default is the id of this map + name: "World" + + # The path to the save-folder of the world to render + world: "world" + + # If this is false, BlueMap tries to omit all blocks that are not visible from above-ground. + # More specific: Block-Faces that have a sunlight/skylight value of 0 are removed. + # This improves the performance of the map on slower devices by a lot, but might cause some blocks to disappear that should normally be visible. + # Default is false + renderCaves: false + + # AmbientOcclusion adds soft shadows into corners, which gives the map a much better look. + # This has only a small impact on render-time and has no impact on the web-performance of the map. + # The value defines the strength of the shading, a value of 0 disables ambientOcclusion. + # Default is 0.25 + ambientOcclusion: 0.25 + + # Lighting uses the light-data in minecraft to shade each block-face. + # If this is enabled, caves and inside buildings without torches will be darker. + # The value defines the strength of the shading and a value of 0 disables lighting (every block will be fully lit). + # Default is 0.8 + lighting: 0.8 + + # Using this, BlueMap pretends that every Block above the defined value is AIR. + # Default is disabled + #sliceY: 90 + + # With the below values you can just not render blocks at certain heights. + # This can be used to ignore the nethers ceiling. + # Default is no min or max y value + #minY: 50 + #maxY: 126 + + # HIRES is the high-resolution render of the map. Where you see every block. + hires { + # Defines the size of one map-tile in blocks. + # If you change this value, the lowres values might need adjustment as well! + # Default is 32 + tileSize: 32 + + # The View-Distance for hires tiles on the web-map (the value is the radius in tiles) + # Default is 3.5 + viewDistance: 3.5 + } + + # LOWRES is the low-resolution render of the map. THats the model that you see if you zoom far out to get an overview. + lowres { + # Defines resolution of the lowres model. E.g. If the hires.tileSize is 32, a value of 4 means that every 8*8 blocks will be summarized by one point on the lowres map. + # Calculation: 32 / 4 = 8 + # You can only use values that result in an integer if you use the above calculation! + # Default is 4 + pointsPerHiresTile: 4 + + # Defines the size of one lowres-map-tile in points. + # Default is 50 + pointsPerLowresTile: 50 + + # The View-Distance for lowres tiles on the web-map (the value is the radius in tiles) + # Default is 4 + viewDistance: 4 + } + } + + # Here another example for the End-Map + # Things we dont want to change from default we can just omit + { + id: "end" + name: "End" + world: "world/DIM1" + + # In the end is no light, so we need to enable this or we don't see anything. + renderCaves: true + + # Same here, we don't want a dark map. But not completely disabled, so we see the effect of e.g torches. + lighting: 0.4 + } + + # Here another example for the Nether-Map + { + id: "nether" + name: "Nether" + world: "world/DIM-1" + + renderCaves: true + lighting: 0.6 + + # We slice the whole world at y:90 so evrery block above 90 will be air. + # This way we dont render the nethers ceiling. + sliceY: 90 + + # Instead of slicing we also could do this, that would look like an x-ray view through the ceiling. + #maxY: 126 + } + +] \ No newline at end of file