Add some more resources-compatibility and tweak some values in configs and colors

This commit is contained in:
Blue (Lukas Rieger) 2020-01-04 20:04:47 +01:00
parent 22c2772c70
commit c60a62a6cd
14 changed files with 423 additions and 97 deletions

View File

@ -64,6 +64,7 @@
import de.bluecolored.bluemap.core.resourcepack.ParseResourceException; import de.bluecolored.bluemap.core.resourcepack.ParseResourceException;
import de.bluecolored.bluemap.core.resourcepack.ResourcePack; import de.bluecolored.bluemap.core.resourcepack.ResourcePack;
import de.bluecolored.bluemap.core.web.BlueMapWebServer; import de.bluecolored.bluemap.core.web.BlueMapWebServer;
import de.bluecolored.bluemap.core.web.WebFilesManager;
import de.bluecolored.bluemap.core.web.WebSettings; import de.bluecolored.bluemap.core.web.WebSettings;
import de.bluecolored.bluemap.core.world.World; import de.bluecolored.bluemap.core.world.World;
@ -192,7 +193,6 @@ public void startWebserver() throws IOException {
Logger.global.logInfo("Starting webserver..."); Logger.global.logInfo("Starting webserver...");
BlueMapWebServer webserver = new BlueMapWebServer(configManager.getMainConfig()); BlueMapWebServer webserver = new BlueMapWebServer(configManager.getMainConfig());
webserver.updateWebfiles();
webserver.start(); webserver.start();
} }
@ -240,7 +240,7 @@ private boolean loadResources() throws IOException, ParseResourceException {
private boolean handleMissingResources(File resourceFile) { private boolean handleMissingResources(File resourceFile) {
if (configManager.getMainConfig().isDownloadAccepted()) { if (configManager.getMainConfig().isDownloadAccepted()) {
try { try {
Logger.global.logInfo("Downloading " + ResourcePack.MINECRAFT_CLIENT_URL + " to " + resourceFile + " ..."); Logger.global.logInfo("Downloading " + ResourcePack.MINECRAFT_CLIENT_URL + " to " + resourceFile.getCanonicalPath() + " ...");
ResourcePack.downloadDefaultResource(resourceFile); ResourcePack.downloadDefaultResource(resourceFile);
return true; return true;
} catch (IOException e) { } catch (IOException e) {
@ -286,6 +286,12 @@ public static void main(String[] args) throws IOException, ParseResourceExceptio
return; return;
} }
WebFilesManager webFilesManager = new WebFilesManager(config.getMainConfig().getWebRoot());
if (webFilesManager.needsUpdate()) {
Logger.global.logInfo("Updating webfiles in " + config.getMainConfig().getWebRoot().normalize() + "...");
webFilesManager.updateFiles();
}
BlueMapCLI bluemap = new BlueMapCLI(config, configFolder, cmd.hasOption("f")); BlueMapCLI bluemap = new BlueMapCLI(config, configFolder, cmd.hasOption("f"));
if (config.getMainConfig().isWebserverEnabled()) { if (config.getMainConfig().isWebserverEnabled()) {
@ -352,7 +358,7 @@ private static void printHelp() {
if (file.isFile()) { if (file.isFile()) {
try { try {
filename = "." + File.separator + new File("").getAbsoluteFile().toPath().relativize(file.toPath()).toString(); filename = "." + File.separator + new File("").getCanonicalFile().toPath().relativize(file.toPath()).toString();
} catch (IllegalArgumentException ex) { } catch (IllegalArgumentException ex) {
filename = file.getAbsolutePath(); filename = file.getAbsolutePath();
} }

View File

@ -7,3 +7,4 @@ web {
port: 8100 port: 8100
maxConnectionCount: 100 maxConnectionCount: 100
} }
renderThreadCount: 0

View File

@ -56,8 +56,10 @@ web {
# This changes the amount of threads that BlueMap will use to render the maps. # 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. # 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. # 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 # Zero or a negative value means the amount of of available processor-cores subtracted by the value.
#renderThreadCount: 2 # (So a value of -2 with 6 cores results in 4 render-processes)
# Default is 0
renderThreadCount: 0
# This is an array with multiple configured maps. # This is an array with multiple configured maps.
# You can define multiple maps, for different worlds with different render-settings here # You can define multiple maps, for different worlds with different render-settings here
@ -112,8 +114,8 @@ maps: [
tileSize: 32 tileSize: 32
# The View-Distance for hires tiles on the web-map (the value is the radius in tiles) # The View-Distance for hires tiles on the web-map (the value is the radius in tiles)
# Default is 3.5 # Default is 4.5
viewDistance: 3.5 viewDistance: 4.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 is the low-resolution render of the map. THats the model that you see if you zoom far out to get an overview.
@ -129,8 +131,8 @@ maps: [
pointsPerLowresTile: 50 pointsPerLowresTile: 50
# The View-Distance for lowres tiles on the web-map (the value is the radius in tiles) # The View-Distance for lowres tiles on the web-map (the value is the radius in tiles)
# Default is 4 # Default is 7
viewDistance: 4 viewDistance: 7
} }
} }

View File

@ -68,9 +68,10 @@ public MainConfig(ConfigurationNode node) throws IOException {
loadWebConfig(node.getNode("web")); loadWebConfig(node.getNode("web"));
int defaultCount = Runtime.getRuntime().availableProcessors(); int processors = Runtime.getRuntime().availableProcessors();
renderThreadCount = node.getNode("renderThreadCount").getInt(defaultCount); renderThreadCount = node.getNode("renderThreadCount").getInt(0);
if (renderThreadCount <= 0) renderThreadCount = defaultCount; if (renderThreadCount <= 0) renderThreadCount = processors + renderThreadCount;
if (renderThreadCount <= 0) renderThreadCount = 1;
loadMapConfigs(node.getNode("maps")); loadMapConfigs(node.getNode("maps"));
} }

View File

@ -72,6 +72,7 @@ public BlockStateModel build(BlockState blockState, BlockModelResource bmr) {
int level = getLiquidLevel(blockState); int level = getLiquidLevel(blockState);
float[] heights = new float[]{16f, 16f, 16f, 16f}; float[] heights = new float[]{16f, 16f, 16f, 16f};
float coloralpha = 0.2f;
if (level < 8 && !(level == 0 && isLiquid(context.getRelativeBlock(0, 1, 0)))){ if (level < 8 && !(level == 0 && isLiquid(context.getRelativeBlock(0, 1, 0)))){
heights = new float[]{ heights = new float[]{
@ -80,6 +81,8 @@ public BlockStateModel build(BlockState blockState, BlockModelResource bmr) {
getLiquidCornerHeight(0, 0, -1), getLiquidCornerHeight(0, 0, -1),
getLiquidCornerHeight(0, 0, 0) getLiquidCornerHeight(0, 0, 0)
}; };
coloralpha = 0.8f;
} }
BlockStateModel model = new BlockStateModel(); BlockStateModel model = new BlockStateModel();
@ -114,7 +117,7 @@ public BlockStateModel build(BlockState blockState, BlockModelResource bmr) {
//calculate mapcolor //calculate mapcolor
Vector4f mapcolor = texture.getColor(); Vector4f mapcolor = texture.getColor();
mapcolor = mapcolor.mul(tintcolor.toVector4(0.5)); mapcolor = mapcolor.mul(tintcolor.toVector4(coloralpha));
model.setMapColor(mapcolor); model.setMapColor(mapcolor);
return model; return model;

View File

@ -31,6 +31,7 @@
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.EnumMap; import java.util.EnumMap;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@ -213,6 +214,8 @@ public static Builder builder(FileAccess sourcesAccess, ResourcePack resourcePac
} }
public static class Builder { public static class Builder {
private static final String JSON_COMMENT = "__comment";
private FileAccess sourcesAccess; private FileAccess sourcesAccess;
private ResourcePack resourcePack; private ResourcePack resourcePack;
@ -227,7 +230,12 @@ private Builder(FileAccess sourcesAccess, ResourcePack resourcePack) {
} }
public synchronized BlockModelResource build(String modelPath) throws IOException, ParseResourceException { public synchronized BlockModelResource build(String modelPath) throws IOException, ParseResourceException {
textures.clear(); return build(modelPath, Collections.emptyMap());
}
public synchronized BlockModelResource build(String modelPath, Map<String, String> textures) throws IOException, ParseResourceException {
this.textures.clear();
this.textures.putAll(textures);
return buildNoReset(modelPath, true, modelPath); return buildNoReset(modelPath, true, modelPath);
} }
@ -239,6 +247,8 @@ private BlockModelResource buildNoReset(String modelPath, boolean renderElements
.load(); .load();
for (Entry<Object, ? extends ConfigurationNode> entry : config.getNode("textures").getChildrenMap().entrySet()) { for (Entry<Object, ? extends ConfigurationNode> entry : config.getNode("textures").getChildrenMap().entrySet()) {
if (entry.getKey().equals(JSON_COMMENT)) continue;
textures.putIfAbsent(entry.getKey().toString(), entry.getValue().getString(null)); textures.putIfAbsent(entry.getKey().toString(), entry.getValue().getString(null));
} }

View File

@ -30,7 +30,9 @@
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
@ -39,6 +41,7 @@
import com.flowpowered.math.vector.Vector3i; import com.flowpowered.math.vector.Vector3i;
import de.bluecolored.bluemap.core.logger.Logger; import de.bluecolored.bluemap.core.logger.Logger;
import de.bluecolored.bluemap.core.resourcepack.PropertyCondition.All;
import de.bluecolored.bluemap.core.resourcepack.fileaccess.FileAccess; import de.bluecolored.bluemap.core.resourcepack.fileaccess.FileAccess;
import de.bluecolored.bluemap.core.util.MathUtils; import de.bluecolored.bluemap.core.util.MathUtils;
import de.bluecolored.bluemap.core.world.BlockState; import de.bluecolored.bluemap.core.world.BlockState;
@ -46,113 +49,138 @@
import ninja.leaping.configurate.gson.GsonConfigurationLoader; import ninja.leaping.configurate.gson.GsonConfigurationLoader;
public class BlockStateResource { public class BlockStateResource {
private List<Variant> variants = new ArrayList<>(); private List<Variant> variants = new ArrayList<>();
private Collection<Variant> multipart = new ArrayList<>(); private Collection<Variant> multipart = new ArrayList<>();
private BlockStateResource() {} private BlockStateResource() {
}
public Collection<TransformedBlockModelResource> getModels(BlockState blockState){
public Collection<TransformedBlockModelResource> getModels(BlockState blockState) {
return getModels(blockState, Vector3i.ZERO); return getModels(blockState, Vector3i.ZERO);
} }
public Collection<TransformedBlockModelResource> getModels(BlockState blockState, Vector3i pos){ public Collection<TransformedBlockModelResource> getModels(BlockState blockState, Vector3i pos) {
Collection<TransformedBlockModelResource> models = new ArrayList<>(); Collection<TransformedBlockModelResource> models = new ArrayList<>();
Variant allMatch = null;
for (Variant variant : variants) { for (Variant variant : variants) {
if (variant.condition.matches(blockState)) { if (variant.condition.matches(blockState)) {
if (variant.condition instanceof All) { //only use "all" conditioned if nothing else matched
if (allMatch == null) allMatch = variant;
continue;
}
models.add(variant.getModel(pos)); models.add(variant.getModel(pos));
return models; return models;
} }
} }
if (allMatch != null) {
models.add(allMatch.getModel(pos));
return models;
}
for (Variant variant : multipart) { for (Variant variant : multipart) {
if (variant.condition.matches(blockState)) { if (variant.condition.matches(blockState)) {
models.add(variant.getModel(pos)); models.add(variant.getModel(pos));
} }
} }
return models; return models;
} }
private class Variant { private class Variant {
private PropertyCondition condition = PropertyCondition.all(); private PropertyCondition condition = PropertyCondition.all();
private Collection<Weighted<TransformedBlockModelResource>> models = new ArrayList<>(); private Collection<Weighted<TransformedBlockModelResource>> models = new ArrayList<>();
private double totalWeight; private double totalWeight;
private Variant() {} private Variant() {
}
public TransformedBlockModelResource getModel(Vector3i pos) { public TransformedBlockModelResource getModel(Vector3i pos) {
double selection = MathUtils.hashToFloat(pos, 827364) * totalWeight; //random based on position double selection = MathUtils.hashToFloat(pos, 827364) * totalWeight; // random based on position
for (Weighted<TransformedBlockModelResource> w : models) { for (Weighted<TransformedBlockModelResource> w : models) {
selection -= w.weight; selection -= w.weight;
if (selection < 0) return w.value; if (selection < 0)
return w.value;
} }
throw new RuntimeException("This line should never be reached!"); throw new RuntimeException("This line should never be reached!");
} }
public void updateTotalWeight() { public void updateTotalWeight() {
totalWeight = 0d; totalWeight = 0d;
for (Weighted<?> w : models) { for (Weighted<?> w : models) {
totalWeight += w.weight; totalWeight += w.weight;
} }
} }
} }
private static class Weighted<T> { private static class Weighted<T> {
private T value; private T value;
private double weight; private double weight;
public Weighted(T value, double weight) { public Weighted(T value, double weight) {
this.value = value; this.value = value;
this.weight = weight; this.weight = weight;
} }
} }
public static Builder builder(FileAccess sourcesAccess, ResourcePack resourcePack) { public static Builder builder(FileAccess sourcesAccess, ResourcePack resourcePack) {
return new Builder(sourcesAccess, resourcePack); return new Builder(sourcesAccess, resourcePack);
} }
public static class Builder { public static class Builder {
private static final String JSON_COMMENT = "__comment";
private final FileAccess sourcesAccess; private final FileAccess sourcesAccess;
private final ResourcePack resourcePack; private final ResourcePack resourcePack;
private Builder(FileAccess sourcesAccess, ResourcePack resourcePack) { private Builder(FileAccess sourcesAccess, ResourcePack resourcePack) {
this.sourcesAccess = sourcesAccess; this.sourcesAccess = sourcesAccess;
this.resourcePack = resourcePack; this.resourcePack = resourcePack;
} }
public BlockStateResource build(String blockstateFile) throws IOException { public BlockStateResource build(String blockstateFile) throws IOException {
BlockStateResource blockState = new BlockStateResource();
ConfigurationNode config = GsonConfigurationLoader.builder() ConfigurationNode config = GsonConfigurationLoader.builder()
.setSource(() -> new BufferedReader(new InputStreamReader(sourcesAccess.readFile(blockstateFile), StandardCharsets.UTF_8))) .setSource(() -> new BufferedReader(
.build() new InputStreamReader(sourcesAccess.readFile(blockstateFile), StandardCharsets.UTF_8)))
.load(); .build().load();
//create variants if (!config.getNode("forge_marker").isVirtual()) {
return buildForge(config, blockstateFile);
}
BlockStateResource blockState = new BlockStateResource();
// create variants
for (Entry<Object, ? extends ConfigurationNode> entry : config.getNode("variants").getChildrenMap().entrySet()) { for (Entry<Object, ? extends ConfigurationNode> entry : config.getNode("variants").getChildrenMap().entrySet()) {
if (entry.getKey().equals(JSON_COMMENT)) continue;
try { try {
String conditionString = entry.getKey().toString(); String conditionString = entry.getKey().toString();
ConfigurationNode transformedModelNode = entry.getValue(); ConfigurationNode transformedModelNode = entry.getValue();
Variant variant = blockState.new Variant(); Variant variant = blockState.new Variant();
variant.condition = parseConditionString(conditionString); variant.condition = parseConditionString(conditionString);
variant.models = loadModels(transformedModelNode, blockstateFile); variant.models = loadModels(transformedModelNode, blockstateFile, null);
variant.updateTotalWeight(); variant.updateTotalWeight();
blockState.variants.add(variant); blockState.variants.add(variant);
} catch (Exception ex) { } catch (Exception ex) {
Logger.global.logWarning("Failed to parse a variant of " + blockstateFile + ": " + ex); Logger.global.logWarning("Failed to parse a variant of " + blockstateFile + ": " + ex);
} }
} }
//create multipart // create multipart
for (ConfigurationNode partNode : config.getNode("multipart").getChildrenList()) { for (ConfigurationNode partNode : config.getNode("multipart").getChildrenList()) {
try { try {
Variant variant = blockState.new Variant(); Variant variant = blockState.new Variant();
@ -160,64 +188,64 @@ public BlockStateResource build(String blockstateFile) throws IOException {
if (!whenNode.isVirtual()) { if (!whenNode.isVirtual()) {
variant.condition = parseCondition(whenNode); variant.condition = parseCondition(whenNode);
} }
variant.models = loadModels(partNode.getNode("apply"), blockstateFile); variant.models = loadModels(partNode.getNode("apply"), blockstateFile, null);
variant.updateTotalWeight(); variant.updateTotalWeight();
blockState.multipart.add(variant); blockState.multipart.add(variant);
} catch (Exception ex) { } catch (Exception ex) {
Logger.global.logWarning("Failed to parse a multipart-part of " + blockstateFile + ": " + ex); Logger.global.logWarning("Failed to parse a multipart-part of " + blockstateFile + ": " + ex);
} }
} }
return blockState; return blockState;
} }
private Collection<Weighted<TransformedBlockModelResource>> loadModels(ConfigurationNode node, String blockstateFile) { private Collection<Weighted<TransformedBlockModelResource>> loadModels(ConfigurationNode node, String blockstateFile, Map<String, String> overrideTextures) {
Collection<Weighted<TransformedBlockModelResource>> models = new ArrayList<>(); Collection<Weighted<TransformedBlockModelResource>> models = new ArrayList<>();
if (node.hasListChildren()) { if (node.hasListChildren()) {
for (ConfigurationNode modelNode : node.getChildrenList()) { for (ConfigurationNode modelNode : node.getChildrenList()) {
try { try {
models.add(loadModel(modelNode)); models.add(loadModel(modelNode, overrideTextures));
} catch (ParseResourceException ex) { } catch (ParseResourceException ex) {
Logger.global.logWarning("Failed to load a model trying to parse " + blockstateFile + ": " + ex); Logger.global.logWarning("Failed to load a model trying to parse " + blockstateFile + ": " + ex);
} }
} }
} else if (node.hasMapChildren()) { } else if (node.hasMapChildren()) {
try { try {
models.add(loadModel(node)); models.add(loadModel(node, overrideTextures));
} catch (ParseResourceException ex) { } catch (ParseResourceException ex) {
Logger.global.logWarning("Failed to load a model trying to parse " + blockstateFile + ": " + ex); Logger.global.logWarning("Failed to load a model trying to parse " + blockstateFile + ": " + ex);
} }
} }
return models; return models;
} }
private Weighted<TransformedBlockModelResource> loadModel(ConfigurationNode node) throws ParseResourceException { private Weighted<TransformedBlockModelResource> loadModel(ConfigurationNode node, Map<String, String> overrideTextures) throws ParseResourceException {
String modelPath = node.getNode("model").getString(); String namespacedModelPath = node.getNode("model").getString();
if (modelPath == null) throw new ParseResourceException("No model defined!"); if (namespacedModelPath == null)
throw new ParseResourceException("No model defined!");
modelPath = ResourcePack.namespacedToAbsoluteResourcePath(modelPath, "models") + ".json";
String modelPath = ResourcePack.namespacedToAbsoluteResourcePath(namespacedModelPath, "models") + ".json";
BlockModelResource model = resourcePack.blockModelResources.get(modelPath); BlockModelResource model = resourcePack.blockModelResources.get(modelPath);
if (model == null) { if (model == null) {
BlockModelResource.Builder builder = BlockModelResource.builder(sourcesAccess, resourcePack);
try { try {
model = BlockModelResource.builder(sourcesAccess, resourcePack).build(modelPath); if (overrideTextures != null) model = builder.build(modelPath, overrideTextures);
else model = builder.build(modelPath);
} catch (IOException e) { } catch (IOException e) {
throw new ParseResourceException("Failed to load model " + modelPath, e); throw new ParseResourceException("Failed to load model " + modelPath, e);
} }
resourcePack.blockModelResources.put(modelPath, model); resourcePack.blockModelResources.put(modelPath, model);
} }
Vector2i rotation = new Vector2i( Vector2i rotation = new Vector2i(node.getNode("x").getInt(0), node.getNode("y").getInt(0));
node.getNode("x").getInt(0),
node.getNode("y").getInt(0)
);
boolean uvLock = node.getNode("uvlock").getBoolean(false); boolean uvLock = node.getNode("uvlock").getBoolean(false);
TransformedBlockModelResource transformedModel = new TransformedBlockModelResource(rotation, uvLock, model); TransformedBlockModelResource transformedModel = new TransformedBlockModelResource(rotation, uvLock, model);
return new Weighted<TransformedBlockModelResource>(transformedModel, node.getNode("weight").getDouble(1d)); return new Weighted<TransformedBlockModelResource>(transformedModel, node.getNode("weight").getDouble(1d));
} }
@ -226,32 +254,36 @@ private PropertyCondition parseCondition(ConfigurationNode conditionNode) {
List<PropertyCondition> andConditions = new ArrayList<>(); List<PropertyCondition> andConditions = new ArrayList<>();
for (Entry<Object, ? extends ConfigurationNode> entry : conditionNode.getChildrenMap().entrySet()) { for (Entry<Object, ? extends ConfigurationNode> entry : conditionNode.getChildrenMap().entrySet()) {
String key = entry.getKey().toString(); String key = entry.getKey().toString();
if (key.equals(JSON_COMMENT)) continue;
if (key.equals("OR")) { if (key.equals("OR")) {
List<PropertyCondition> orConditions = new ArrayList<>(); List<PropertyCondition> orConditions = new ArrayList<>();
for (ConfigurationNode orConditionNode : entry.getValue().getChildrenList()) { for (ConfigurationNode orConditionNode : entry.getValue().getChildrenList()) {
orConditions.add(parseCondition(orConditionNode)); orConditions.add(parseCondition(orConditionNode));
} }
andConditions.add(PropertyCondition.or(orConditions.toArray(new PropertyCondition[orConditions.size()]))); andConditions.add(
PropertyCondition.or(orConditions.toArray(new PropertyCondition[orConditions.size()])));
} else { } else {
String[] values = StringUtils.split(entry.getValue().getString(""), '|'); String[] values = StringUtils.split(entry.getValue().getString(""), '|');
andConditions.add(PropertyCondition.property(key, values)); andConditions.add(PropertyCondition.property(key, values));
} }
} }
return PropertyCondition.and(andConditions.toArray(new PropertyCondition[andConditions.size()])); return PropertyCondition.and(andConditions.toArray(new PropertyCondition[andConditions.size()]));
} }
private PropertyCondition parseConditionString(String conditionString) throws IllegalArgumentException { private PropertyCondition parseConditionString(String conditionString) throws IllegalArgumentException {
List<PropertyCondition> conditions = new ArrayList<>(); List<PropertyCondition> conditions = new ArrayList<>();
if (!conditionString.isEmpty() && !conditionString.equals("default") && !conditionString.equals("normal")) { if (!conditionString.isEmpty() && !conditionString.equals("default") && !conditionString.equals("normal")) {
String[] conditionSplit = StringUtils.split(conditionString, ','); String[] conditionSplit = StringUtils.split(conditionString, ',');
for (String element : conditionSplit) { for (String element : conditionSplit) {
String[] keyval = StringUtils.split(element, "=", 2); String[] keyval = StringUtils.split(element, "=", 2);
if (keyval.length < 2) throw new IllegalArgumentException("Condition-String '" + conditionString + "' is invalid!"); if (keyval.length < 2)
throw new IllegalArgumentException("Condition-String '" + conditionString + "' is invalid!");
conditions.add(PropertyCondition.property(keyval[0], keyval[1])); conditions.add(PropertyCondition.property(keyval[0], keyval[1]));
} }
} }
PropertyCondition condition; PropertyCondition condition;
if (conditions.isEmpty()) { if (conditions.isEmpty()) {
condition = PropertyCondition.all(); condition = PropertyCondition.all();
@ -260,8 +292,129 @@ private PropertyCondition parseConditionString(String conditionString) throws Il
} else { } else {
condition = PropertyCondition.and(conditions.toArray(new PropertyCondition[conditions.size()])); condition = PropertyCondition.and(conditions.toArray(new PropertyCondition[conditions.size()]));
} }
return condition; return condition;
} }
private BlockStateResource buildForge(ConfigurationNode config, String blockstateFile) {
ConfigurationNode modelDefaults = config.getNode("defaults");
List<ForgeVariant> variants = new ArrayList<>();
for (Entry<Object, ? extends ConfigurationNode> entry : config.getNode("variants").getChildrenMap().entrySet()) {
if (entry.getKey().equals(JSON_COMMENT)) continue;
if (isForgeStraightVariant(entry.getValue())) continue;
// create variants for single property
List<ForgeVariant> propertyVariants = new ArrayList<>();
String key = entry.getKey().toString();
for (Entry<Object, ? extends ConfigurationNode> value : entry.getValue().getChildrenMap().entrySet()) {
if (value.getKey().equals(JSON_COMMENT)) continue;
ForgeVariant variant = new ForgeVariant();
variant.properties.put(key, value.getKey().toString());
variant.node = value.getValue();
propertyVariants.add(variant);
}
// join variants
List<ForgeVariant> oldVariants = variants;
variants = new ArrayList<>(oldVariants.size() * propertyVariants.size());
for (ForgeVariant oldVariant : oldVariants) {
for (ForgeVariant addVariant : propertyVariants) {
variants.add(oldVariant.createMerge(addVariant));
}
}
}
//create all possible property-variants
BlockStateResource blockState = new BlockStateResource();
for (ForgeVariant forgeVariant : variants) {
Variant variant = blockState.new Variant();
ConfigurationNode modelNode = forgeVariant.node.mergeValuesFrom(modelDefaults);
Map<String, String> textures = new HashMap<>();
for (Entry<Object, ? extends ConfigurationNode> entry : modelNode.getNode("textures").getChildrenMap().entrySet()) {
if (entry.getKey().equals(JSON_COMMENT)) continue;
textures.putIfAbsent(entry.getKey().toString(), entry.getValue().getString(null));
}
List<PropertyCondition> conditions = new ArrayList<>(forgeVariant.properties.size());
for (Entry<String, String> property : forgeVariant.properties.entrySet()) {
conditions.add(PropertyCondition.property(property.getKey(), property.getValue()));
}
variant.condition = PropertyCondition.and(conditions.toArray(new PropertyCondition[conditions.size()]));
variant.models.addAll(loadModels(modelNode, blockstateFile, textures));
for (Entry<Object, ? extends ConfigurationNode> entry : modelNode.getNode("submodel").getChildrenMap().entrySet()) {
if (entry.getKey().equals(JSON_COMMENT)) continue;
variant.models.addAll(loadModels(entry.getValue(), blockstateFile, textures));
}
variant.updateTotalWeight();
blockState.variants.add(variant);
}
//create default straight variant
ConfigurationNode normalNode = config.getNode("variants", "normal");
if (normalNode.isVirtual() || isForgeStraightVariant(normalNode)) {
normalNode.mergeValuesFrom(modelDefaults);
Map<String, String> textures = new HashMap<>();
for (Entry<Object, ? extends ConfigurationNode> entry : normalNode.getNode("textures").getChildrenMap().entrySet()) {
if (entry.getKey().equals(JSON_COMMENT)) continue;
textures.putIfAbsent(entry.getKey().toString(), entry.getValue().getString(null));
}
Variant variant = blockState.new Variant();
variant.condition = PropertyCondition.all();
variant.models.addAll(loadModels(normalNode, blockstateFile, textures));
for (Entry<Object, ? extends ConfigurationNode> entry : normalNode.getNode("submodel").getChildrenMap().entrySet()) {
if (entry.getKey().equals(JSON_COMMENT)) continue;
variant.models.addAll(loadModels(entry.getValue(), blockstateFile, textures));
}
variant.updateTotalWeight();
blockState.variants.add(variant);
}
return blockState;
}
private boolean isForgeStraightVariant(ConfigurationNode node) {
if (node.hasListChildren())
return true;
for (Entry<Object, ? extends ConfigurationNode> entry : node.getChildrenMap().entrySet()) {
if (entry.getKey().equals(JSON_COMMENT)) continue;
if (!entry.getValue().hasMapChildren()) return true;
}
return false;
}
private class ForgeVariant {
public Map<String, String> properties = new HashMap<>();
public ConfigurationNode node = GsonConfigurationLoader.builder().build().createEmptyNode();
public ForgeVariant createMerge(ForgeVariant other) {
ForgeVariant merge = new ForgeVariant();
merge.properties.putAll(this.properties);
merge.properties.putAll(other.properties);
merge.node.mergeValuesFrom(this.node);
merge.node.mergeValuesFrom(other.node);
return merge;
}
}
} }
} }

View File

@ -31,8 +31,8 @@
@FunctionalInterface @FunctionalInterface
public interface PropertyCondition { public interface PropertyCondition {
static final PropertyCondition MATCH_ALL = state -> true; static final PropertyCondition MATCH_ALL = new All();
static final PropertyCondition MATCH_NONE = state -> false; static final PropertyCondition MATCH_NONE = new None();
boolean matches(BlockState state); boolean matches(BlockState state);
@ -95,6 +95,24 @@ public boolean matches(BlockState state) {
} }
class All implements PropertyCondition {
@Override
public boolean matches(BlockState state) {
return true;
}
}
class None implements PropertyCondition {
@Override
public boolean matches(BlockState state) {
return false;
}
}
static PropertyCondition all() { static PropertyCondition all() {
return MATCH_ALL; return MATCH_ALL;
} }

View File

@ -38,6 +38,7 @@
import de.bluecolored.bluemap.core.resourcepack.BlockStateResource.Builder; import de.bluecolored.bluemap.core.resourcepack.BlockStateResource.Builder;
import de.bluecolored.bluemap.core.resourcepack.fileaccess.CombinedFileAccess; import de.bluecolored.bluemap.core.resourcepack.fileaccess.CombinedFileAccess;
import de.bluecolored.bluemap.core.resourcepack.fileaccess.FileAccess; import de.bluecolored.bluemap.core.resourcepack.fileaccess.FileAccess;
import de.bluecolored.bluemap.core.resourcepack.fileaccess.ResourcePackOldFormatFileAccess;
import de.bluecolored.bluemap.core.world.BlockState; import de.bluecolored.bluemap.core.world.BlockState;
/** /**
@ -109,14 +110,15 @@ public void load(Collection<File> sources) throws IOException {
* @param sources The list of {@link File} sources. Each can be a folder or any zip-compressed file. (E.g. .zip or .jar) * @param sources The list of {@link File} sources. Each can be a folder or any zip-compressed file. (E.g. .zip or .jar)
*/ */
public void load(File... sources) { public void load(File... sources) {
try (CombinedFileAccess sourcesAccess = new CombinedFileAccess()){ try (CombinedFileAccess combinedSourcesAccess = new CombinedFileAccess()){
for (File file : sources) { for (File file : sources) {
try { try {
sourcesAccess.addFileAccess(FileAccess.of(file)); combinedSourcesAccess.addFileAccess(FileAccess.of(file));
} catch (IOException e) { } catch (IOException e) {
Logger.global.logError("Failed to read ResourcePack: " + file, e); Logger.global.logError("Failed to read ResourcePack: " + file, e);
} }
} }
FileAccess sourcesAccess = ResourcePackOldFormatFileAccess.from(combinedSourcesAccess);
textures.reloadAllTextures(sourcesAccess); textures.reloadAllTextures(sourcesAccess);
@ -183,6 +185,8 @@ public static void downloadDefaultResource(File file) throws IOException {
protected static String namespacedToAbsoluteResourcePath(String namespacedPath, String resourceTypeFolder) { protected static String namespacedToAbsoluteResourcePath(String namespacedPath, String resourceTypeFolder) {
String path = namespacedPath; String path = namespacedPath;
resourceTypeFolder = FileAccess.normalize(resourceTypeFolder);
int namespaceIndex = path.indexOf(':'); int namespaceIndex = path.indexOf(':');
String namespace = "minecraft"; String namespace = "minecraft";
if (namespaceIndex != -1) { if (namespaceIndex != -1) {
@ -190,7 +194,11 @@ protected static String namespacedToAbsoluteResourcePath(String namespacedPath,
path = path.substring(namespaceIndex + 1); path = path.substring(namespaceIndex + 1);
} }
path = "assets/" + namespace + "/" + resourceTypeFolder + "/" + FileAccess.normalize(path); if (resourceTypeFolder.isEmpty()) {
path = "assets/" + namespace + "/" + FileAccess.normalize(path);
} else {
path = "assets/" + namespace + "/" + resourceTypeFolder + "/" + FileAccess.normalize(path);
}
return path; return path;
} }

View File

@ -57,7 +57,9 @@ static String getFileName(String path) {
} }
static String normalize(String path) { static String normalize(String path) {
if (path.isEmpty()) return path;
if (path.charAt(path.length() - 1) == '/') path = path.substring(0, path.length() - 1); if (path.charAt(path.length() - 1) == '/') path = path.substring(0, path.length() - 1);
if (path.isEmpty()) return path;
if (path.charAt(0) == '/') path = path.substring(1); if (path.charAt(0) == '/') path = path.substring(1);
return path; return path;
} }

View File

@ -0,0 +1,115 @@
/*
* This file is part of BlueMap, licensed under the MIT License (MIT).
*
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
* 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.resourcepack.fileaccess;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.regex.Pattern;
/**
* This {@link FileAccess} tries to make 1.12/1.13/1.14 ResourcePacks compatible with each other
*/
public class ResourcePackOldFormatFileAccess implements FileAccess {
private FileAccess parent;
protected ResourcePackOldFormatFileAccess(FileAccess parent) {
this.parent = parent;
}
@Override
public void close() throws IOException {
parent.close();
}
@Override
public InputStream readFile(String path) throws FileNotFoundException, IOException {
try {
return parent.readFile(path);
} catch (FileNotFoundException ex) {
for (String altPath : otherPathsToTry(path)) {
try {
return parent.readFile(altPath);
} catch (FileNotFoundException ex2) {
ex.addSuppressed(ex2);
} catch (IOException ex2) {
ex.addSuppressed(ex2);
throw ex;
}
}
throw ex;
}
}
@Override
public Collection<String> listFiles(String path, boolean recursive) {
return parent.listFiles(path, recursive);
}
@Override
public Collection<String> listFolders(String path) {
return parent.listFolders(path);
}
private Collection<String> otherPathsToTry(String path){
path = FileAccess.normalize(path);
List<String> paths = new ArrayList<>();
String[] parts = path.split(Pattern.quote("/"));
//handle block/blocks folder-differences
if (parts.length >= 4 && parts[0].equals("assets") && parts[2].equals("models")) {
if (parts[3].equals("block")) {
parts[3] = "blocks";
paths.add(String.join("/", parts));
} else if (parts[3].equals("blocks")) {
parts[3] = "block";
paths.add(String.join("/", parts));
} else {
String[] newParts = new String[parts.length + 1];
System.arraycopy(parts, 0, newParts, 0, 3);
System.arraycopy(parts, 3, newParts, 4, parts.length - 3);
newParts[3] = "blocks";
paths.add(String.join("/", newParts));
newParts[3] = "block";
paths.add(String.join("/", newParts));
}
}
return paths;
}
public static ResourcePackOldFormatFileAccess from(FileAccess source) {
if (source instanceof ResourcePackOldFormatFileAccess) return (ResourcePackOldFormatFileAccess) source;
return new ResourcePackOldFormatFileAccess(source);
}
}

View File

@ -131,6 +131,9 @@ public synchronized void load() throws ExecutionException, IOException, Interrup
if (loaded) return; if (loaded) return;
unload(); //ensure nothing is left running (from a failed load or something) unload(); //ensure nothing is left running (from a failed load or something)
//register reload command in case bluemap crashes during loading
Sponge.getCommandManager().register(this, new Commands(this).createStandaloneReloadCommand(), "bluemap");
//load configs //load configs
URL defaultSpongeConfig = SpongePlugin.class.getResource("/bluemap-sponge.conf"); URL defaultSpongeConfig = SpongePlugin.class.getResource("/bluemap-sponge.conf");
URL spongeConfigDefaults = SpongePlugin.class.getResource("/bluemap-sponge-defaults.conf"); URL spongeConfigDefaults = SpongePlugin.class.getResource("/bluemap-sponge-defaults.conf");
@ -151,7 +154,7 @@ public synchronized void load() throws ExecutionException, IOException, Interrup
if (!defaultResourceFile.exists()) { if (!defaultResourceFile.exists()) {
handleMissingResources(defaultResourceFile, configManager.getMainConfigFile()); handleMissingResources(defaultResourceFile, configManager.getMainConfigFile());
unload(); unload();
Sponge.getCommandManager().register(this, new Commands(this).createStandaloneReloadCommand(), "bluemap"); Sponge.getCommandManager().register(this, new Commands(this).createStandaloneReloadCommand(), "bluemap");
return; return;
} }
@ -183,7 +186,7 @@ public synchronized void load() throws ExecutionException, IOException, Interrup
File worldFolder = new File(mapConfig.getWorldPath()); File worldFolder = new File(mapConfig.getWorldPath());
if (!worldFolder.exists() || !worldFolder.isDirectory()) { if (!worldFolder.exists() || !worldFolder.isDirectory()) {
Logger.global.logError("Failed to load map '" + id + "': '" + worldFolder + "' does not exist or is no directory!", new IOException()); Logger.global.logError("Failed to load map '" + id + "': '" + worldFolder.getCanonicalPath() + "' does not exist or is no directory!", new IOException());
continue; continue;
} }
@ -276,6 +279,7 @@ public synchronized void load() throws ExecutionException, IOException, Interrup
} }
//init commands //init commands
Sponge.getCommandManager().getOwnedBy(this).forEach(Sponge.getCommandManager()::removeMapping);
Sponge.getCommandManager().register(this, new Commands(this).createRootCommand(), "bluemap"); Sponge.getCommandManager().register(this, new Commands(this).createRootCommand(), "bluemap");
//metrics //metrics
@ -410,7 +414,7 @@ private void handleMissingResources(File resourceFile, File mainConfigFile) {
} else { } else {
Logger.global.logWarning("BlueMap is missing important resources!"); Logger.global.logWarning("BlueMap is missing important resources!");
Logger.global.logWarning("You need to accept the download of the required files in order of BlueMap to work!"); Logger.global.logWarning("You need to accept the download of the required files in order of BlueMap to work!");
Logger.global.logWarning("Please check: " + mainConfigFile); try { Logger.global.logWarning("Please check: " + mainConfigFile.getCanonicalPath()); } catch (IOException ignored) {}
Logger.global.logInfo("If you have changed the config you can simply reload the plugin using: /bluemap reload"); Logger.global.logInfo("If you have changed the config you can simply reload the plugin using: /bluemap reload");
} }
} }

View File

@ -7,3 +7,4 @@ web {
port: 8100 port: 8100
maxConnectionCount: 100 maxConnectionCount: 100
} }
renderThreadCount: -2

View File

@ -52,8 +52,10 @@ web {
# This changes the amount of threads that BlueMap will use to render the maps. # 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. # 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. # 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 # Zero or a negative value means the amount of of available processor-cores subtracted by the value.
#renderThreadCount: 2 # (So a value of -2 with 6 cores results in 4 render-processes)
# Default is -2
renderThreadCount: -2
# This is an array with multiple configured maps. # This is an array with multiple configured maps.
# You can define multiple maps, for different worlds with different render-settings here # You can define multiple maps, for different worlds with different render-settings here
@ -108,8 +110,8 @@ maps: [
tileSize: 32 tileSize: 32
# The View-Distance for hires tiles on the web-map (the value is the radius in tiles) # The View-Distance for hires tiles on the web-map (the value is the radius in tiles)
# Default is 3.5 # Default is 4.5
viewDistance: 3.5 viewDistance: 4.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 is the low-resolution render of the map. THats the model that you see if you zoom far out to get an overview.
@ -125,8 +127,8 @@ maps: [
pointsPerLowresTile: 50 pointsPerLowresTile: 50
# The View-Distance for lowres tiles on the web-map (the value is the radius in tiles) # The View-Distance for lowres tiles on the web-map (the value is the radius in tiles)
# Default is 4 # Default is 7
viewDistance: 4 viewDistance: 7
} }
} }