Automatic mod-resource-loading, loading fabric netsted jars and more

This commit is contained in:
Lukas Rieger (Blue) 2022-06-01 00:03:35 +02:00
parent f98376fe27
commit 79a12d4d80
No known key found for this signature in database
GPG Key ID: 2D09EC5ED2687FF2
36 changed files with 264 additions and 42 deletions

View File

@ -176,7 +176,7 @@ private synchronized void loadWorldsAndMaps() throws ConfigurationException, Int
String name = mapConfig.getName(); String name = mapConfig.getName();
Path worldFolder = mapConfig.getWorld(); Path worldFolder = mapConfig.getWorld();
if (!Files.exists(worldFolder) || !Files.isDirectory(worldFolder)) { if (!Files.isDirectory(worldFolder)) {
throw new ConfigurationException("Failed to load map '" + id + "': \n" + throw new ConfigurationException("Failed to load map '" + id + "': \n" +
"'" + worldFolder.toAbsolutePath().normalize() + "' does not exist or is no directory!\n" + "'" + worldFolder.toAbsolutePath().normalize() + "' does not exist or is no directory!\n" +
"Check if the 'world' setting in the config-file for that map is correct, or remove the entire config-file if you don't want that map."); "Check if the 'world' setting in the config-file for that map is correct, or remove the entire config-file if you don't want that map.");
@ -323,10 +323,48 @@ public synchronized ResourcePack getResourcePack() throws ConfigurationException
}); });
} }
if (configs.getCoreConfig().isScanForModResources()) {
// load from mods folder
Path modsFolder = serverInterface.getModsFolder().orElse(null);
if (modsFolder != null && Files.isDirectory(modsFolder)) {
try (Stream<Path> resourcepackFiles = Files.list(modsFolder)) {
resourcepackFiles
.filter(Files::isRegularFile)
.filter(file -> file.getFileName().toString().endsWith(".jar"))
.forEach(resourcepackFile -> {
try {
resourcePack.loadResources(resourcepackFile);
} catch (IOException e) {
throw new CompletionException(e);
}
});
}
}
// load from datapacks
for (Path worldFolder : getWorldFolders()) {
Path datapacksFolder = worldFolder.resolve("datapacks");
if (!Files.isDirectory(datapacksFolder)) continue;
try (Stream<Path> resourcepackFiles = Files.list(worldFolder.resolve("datapacks"))) {
resourcepackFiles
.forEach(resourcepackFile -> {
try {
resourcePack.loadResources(resourcepackFile);
} catch (IOException e) {
throw new CompletionException(e);
}
});
}
}
}
resourcePack.loadResources(resourceExtensionsFile); resourcePack.loadResources(resourceExtensionsFile);
resourcePack.loadResources(defaultResourceFile); resourcePack.loadResources(defaultResourceFile);
resourcePack.bake(); resourcePack.bake();
Logger.global.logInfo("Resources loaded.");
} catch (IOException | RuntimeException e) { } catch (IOException | RuntimeException e) {
throw new ConfigurationException("Failed to parse resources!\n" + throw new ConfigurationException("Failed to parse resources!\n" +
"Is one of your resource-packs corrupted?", e); "Is one of your resource-packs corrupted?", e);
@ -337,6 +375,17 @@ public synchronized ResourcePack getResourcePack() throws ConfigurationException
return resourcePack; return resourcePack;
} }
private Collection<Path> getWorldFolders() {
Set<Path> folders = new HashSet<>();
for (MapConfig mapConfig : configs.getMapConfigs().values()) {
Path folder = mapConfig.getWorld().toAbsolutePath().normalize();
if (Files.isDirectory(folder)) {
folders.add(folder);
}
}
return folders;
}
public BlueMapConfigs getConfigs() { public BlueMapConfigs getConfigs() {
return configs; return configs;
} }

View File

@ -2,14 +2,17 @@
import de.bluecolored.bluemap.common.config.storage.StorageConfig; import de.bluecolored.bluemap.common.config.storage.StorageConfig;
import de.bluecolored.bluemap.common.plugin.serverinterface.ServerInterface; import de.bluecolored.bluemap.common.plugin.serverinterface.ServerInterface;
import de.bluecolored.bluemap.core.BlueMap;
import de.bluecolored.bluemap.core.debug.DebugDump; import de.bluecolored.bluemap.core.debug.DebugDump;
import de.bluecolored.bluemap.core.logger.Logger; import de.bluecolored.bluemap.core.logger.Logger;
import de.bluecolored.bluemap.core.util.Tristate;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.nio.file.StandardOpenOption; import java.nio.file.StandardOpenOption;
import java.time.LocalDateTime;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Locale; import java.util.Locale;
@ -36,7 +39,7 @@ public BlueMapConfigs(ServerInterface serverInterface) throws ConfigurationExcep
this.coreConfig = loadCoreConfig(); this.coreConfig = loadCoreConfig();
this.webserverConfig = loadWebserverConfig(); this.webserverConfig = loadWebserverConfig();
this.webappConfig = loadWebappConfig(); this.webappConfig = loadWebappConfig();
this.pluginConfig = loadPluginConfig(); this.pluginConfig = serverInterface.isPluginConfigEnabled() ? loadPluginConfig() : new PluginConfig();
this.storageConfigs = Collections.unmodifiableMap(loadStorageConfigs()); this.storageConfigs = Collections.unmodifiableMap(loadStorageConfigs());
this.mapConfigs = Collections.unmodifiableMap(loadMapConfigs()); this.mapConfigs = Collections.unmodifiableMap(loadMapConfigs());
} }
@ -75,11 +78,27 @@ private synchronized CoreConfig loadCoreConfig() throws ConfigurationException {
Path configFolder = configFile.getParent(); Path configFolder = configFile.getParent();
if (!Files.exists(configFile)) { if (!Files.exists(configFile)) {
// determine render-thread preset (very pessimistic, rather let people increase it themselves)
Runtime runtime = Runtime.getRuntime();
int availableCores = runtime.availableProcessors();
long availableMemoryMiB = runtime.maxMemory() / 1024L / 1024L;
int presetRenderThreadCount = 1;
if (availableCores >= 6 && availableMemoryMiB >= 4096)
presetRenderThreadCount = 2;
if (availableCores >= 10 && availableMemoryMiB >= 8192)
presetRenderThreadCount = 3;
try { try {
Files.createDirectories(configFolder); Files.createDirectories(configFolder);
Files.writeString( Files.writeString(
configFolder.resolve("core.conf"), configFolder.resolve("core.conf"),
configManager.loadConfigTemplate("/de/bluecolored/bluemap/config/core.conf") configManager.loadConfigTemplate("/de/bluecolored/bluemap/config/core.conf")
.setConditional("metrics", serverInterface.isMetricsEnabled() == Tristate.UNDEFINED)
.setVariable("timestamp", LocalDateTime.now().withNano(0).toString())
.setVariable("version", BlueMap.VERSION)
.setVariable("implementation", "bukkit")
.setVariable("render-thread-count", Integer.toString(presetRenderThreadCount))
.build(), .build(),
StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING
); );

View File

@ -18,6 +18,8 @@ public class CoreConfig {
private Path data = Path.of("bluemap"); private Path data = Path.of("bluemap");
private boolean scanForModResources = true;
public boolean isAcceptDownload() { public boolean isAcceptDownload() {
return acceptDownload; return acceptDownload;
} }
@ -39,4 +41,8 @@ public Path getData() {
return data; return data;
} }
public boolean isScanForModResources() {
return scanForModResources;
}
} }

View File

@ -3,10 +3,6 @@
import de.bluecolored.bluemap.core.debug.DebugDump; import de.bluecolored.bluemap.core.debug.DebugDump;
import org.spongepowered.configurate.objectmapping.ConfigSerializable; import org.spongepowered.configurate.objectmapping.ConfigSerializable;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;

View File

@ -60,6 +60,11 @@ default Optional<ServerWorld> getWorld(Path worldFolder) {
*/ */
Path getConfigFolder(); Path getConfigFolder();
/**
* Returns the folder that contains the mod-jars
*/
Optional<Path> getModsFolder();
/** /**
* Gives the possibility to override the metrics-setting in the config * Gives the possibility to override the metrics-setting in the config
*/ */
@ -78,5 +83,8 @@ default Tristate isMetricsEnabled() {
*/ */
Optional<Player> getPlayer(UUID uuid); Optional<Player> getPlayer(UUID uuid);
default boolean isPluginConfigEnabled() {
return true;
}
} }

View File

@ -11,20 +11,25 @@
# ${timestamp} # ${timestamp}
accept-download: false accept-download: false
# The folder where bluemap saves data-files it needs during runtime or to save e.g. the render-progress to resume it later.
# Default is "bluemap"
data: "bluemap"
# 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.
# Zero or a negative value means the amount of of available processor-cores subtracted by the value. # Zero or a negative value means the amount of of available processor-cores subtracted by the value.
# (So a value of -2 with 6 cores results in 4 render-processes) # (So a value of -2 with 6 cores results in 4 render-processes)
# Default is 1 # Default is 1
render-thread-count: 1 render-thread-count: ${render-thread-count}
# Controls whether BlueMap should try to find and load mod-resources and datapacks from the server/world-directories.
# Default is true
scan-for-mod-resources: true
${metrics<< ${metrics<<
# If this is true, BlueMap might send really basic metrics reports containing only the implementation-type and the version that is being used to https://metrics.bluecolored.de/bluemap/ # If this is true, BlueMap might send really basic metrics reports containing only the implementation-type and the version that is being used to https://metrics.bluecolored.de/bluemap/
# This allows me to track the basic usage of BlueMap and helps me stay motivated to further develop this tool! Please leave it on :) # This allows me to track the basic usage of BlueMap and helps me stay motivated to further develop this tool! Please leave it on :)
# An example report looks like this: {"implementation":"${implementation}","version":"${version}"} # An example report looks like this: {"implementation":"${implementation}","version":"${version}"}
# Default is true # Default is true
metrics: true metrics: true
>>} >>}
# The folder where bluemap saves data-files it needs during runtime or to save e.g. the render-progress to resume it later.
# Default is "bluemap"
data: "bluemap"

View File

@ -8,10 +8,6 @@
# Default is true # Default is true
live-player-markers: true live-player-markers: true
# Download the skin from mojang-serves when a player joins your server, so it can be used for the player-markers.
# Default is true
skin-download: true
# A list of gamemodes that will prevent a player from appearing on the map. # A list of gamemodes that will prevent a player from appearing on the map.
# Possible values are: survival, creative, spectator, adventure # Possible values are: survival, creative, spectator, adventure
hidden-gamemodes: [ hidden-gamemodes: [
@ -30,6 +26,10 @@ hide-invisible: true
# Default is false # Default is false
hide-sneaking: false hide-sneaking: false
# Download the skin from mojang-serves when a player joins your server, so it can be used for the player-markers.
# Default is true
skin-download: true
# The amount of players that is needed to pause BlueMap's render-threads. # The amount of players that is needed to pause BlueMap's render-threads.
# -> If this amount of players or more is online, bluemap will stop rendering map-updates until enough players # -> If this amount of players or more is online, bluemap will stop rendering map-updates until enough players
# have logged off again # have logged off again

View File

@ -30,6 +30,7 @@
import java.io.IOException; import java.io.IOException;
import java.util.concurrent.ForkJoinPool; import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinWorkerThread;
public class BlueMap { public class BlueMap {
@ -60,7 +61,12 @@ public class BlueMap {
public static final ForkJoinPool THREAD_POOL = new ForkJoinPool( public static final ForkJoinPool THREAD_POOL = new ForkJoinPool(
Runtime.getRuntime().availableProcessors(), Runtime.getRuntime().availableProcessors(),
ForkJoinPool.defaultForkJoinWorkerThreadFactory, pool -> {
ForkJoinWorkerThread thread = ForkJoinPool.defaultForkJoinWorkerThreadFactory.newThread(pool);
thread.setContextClassLoader(BlueMap.class.getClassLoader()); // use plugin-intended classloader
thread.setName("BlueMap-FJ-" + thread.getPoolIndex());
return thread;
},
(thread, ex) -> { (thread, ex) -> {
if (ex instanceof ClassNotFoundException && ex.getMessage().contains("RemovalCause")) { if (ex instanceof ClassNotFoundException && ex.getMessage().contains("RemovalCause")) {
Logger.global.noFloodWarning("RemovalCauseError", ex.getMessage()); Logger.global.noFloodWarning("RemovalCauseError", ex.getMessage());

View File

@ -60,7 +60,10 @@ public static TextureGallery readTexturesFile(InputStream in) throws IOException
Texture[] textures = ResourcesGson.INSTANCE.fromJson(reader, Texture[].class); Texture[] textures = ResourcesGson.INSTANCE.fromJson(reader, Texture[].class);
gallery.nextId = textures.length; gallery.nextId = textures.length;
for (int ordinal = 0; ordinal < textures.length; ordinal++) { for (int ordinal = 0; ordinal < textures.length; ordinal++) {
gallery.ordinalMap.put(textures[ordinal].getResourcePath(), ordinal); Texture texture = textures[ordinal];
if (texture != null) {
gallery.ordinalMap.put(textures[ordinal].getResourcePath(), ordinal);
}
} }
} catch (JsonIOException ex) { } catch (JsonIOException ex) {
throw new IOException(ex); throw new IOException(ex);

View File

@ -19,6 +19,7 @@ public void write(JsonWriter out, Direction value) throws IOException {
public Direction read(JsonReader in) throws IOException { public Direction read(JsonReader in) throws IOException {
String name = in.nextString(); String name = in.nextString();
if (name.equalsIgnoreCase("bottom")) return Direction.DOWN; if (name.equalsIgnoreCase("bottom")) return Direction.DOWN;
if (name.equalsIgnoreCase("top")) return Direction.UP;
return Direction.fromString(name); return Direction.fromString(name);
} }

View File

@ -7,11 +7,14 @@
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.GsonBuilder; import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken; import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import de.bluecolored.bluemap.core.resources.resourcepack.blockmodel.Face; import de.bluecolored.bluemap.core.resources.resourcepack.blockmodel.Face;
import de.bluecolored.bluemap.core.util.Direction; import de.bluecolored.bluemap.core.util.Direction;
import de.bluecolored.bluemap.core.util.math.Axis; import de.bluecolored.bluemap.core.util.math.Axis;
import de.bluecolored.bluemap.core.util.math.Color; import de.bluecolored.bluemap.core.util.math.Color;
import java.io.IOException;
import java.util.EnumMap; import java.util.EnumMap;
public class ResourcesGson { public class ResourcesGson {
@ -37,4 +40,9 @@ private static Gson createGson() {
} }
public static String nextStringOrBoolean(JsonReader in) throws IOException {
if (in.peek() == JsonToken.BOOLEAN) return Boolean.toString(in.nextBoolean());
return in.nextString();
}
} }

View File

@ -2,6 +2,8 @@
import com.github.benmanes.caffeine.cache.Caffeine; import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache; import com.github.benmanes.caffeine.cache.LoadingCache;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import de.bluecolored.bluemap.core.BlueMap; import de.bluecolored.bluemap.core.BlueMap;
import de.bluecolored.bluemap.core.debug.DebugDump; import de.bluecolored.bluemap.core.debug.DebugDump;
import de.bluecolored.bluemap.core.logger.Logger; import de.bluecolored.bluemap.core.logger.Logger;
@ -31,13 +33,14 @@
import java.util.Map; import java.util.Map;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException; import java.util.concurrent.CompletionException;
import java.util.regex.Pattern;
import java.util.stream.Stream; import java.util.stream.Stream;
@DebugDump @DebugDump
public class ResourcePack { public class ResourcePack {
public static final ResourcePath<BlockState> MISSING_BLOCK_STATE = new ResourcePath<>("bluemap", "missing"); public static final ResourcePath<BlockState> MISSING_BLOCK_STATE = new ResourcePath<>("bluemap", "missing");
public static final ResourcePath<BlockModel> MISSING_BLOCK_MODEL = new ResourcePath<>("bluemap", "missing"); public static final ResourcePath<BlockModel> MISSING_BLOCK_MODEL = new ResourcePath<>("bluemap", "block/missing");
public static final ResourcePath<Texture> MISSING_TEXTURE = new ResourcePath<>("bluemap", "missing"); public static final ResourcePath<Texture> MISSING_TEXTURE = new ResourcePath<>("bluemap", "block/missing");
private final Map<String, ResourcePath<BlockState>> blockStatePaths; private final Map<String, ResourcePath<BlockState>> blockStatePaths;
private final Map<ResourcePath<BlockState>, BlockState> blockStates; private final Map<ResourcePath<BlockState>, BlockState> blockStates;
@ -155,18 +158,38 @@ private BlockProperties loadBlockProperties(de.bluecolored.bluemap.core.world.Bl
} }
public synchronized void loadResources(Path root) throws IOException { public synchronized void loadResources(Path root) throws IOException {
Logger.global.logInfo("Loading resources from: " + root); Logger.global.logDebug("Loading resources from: " + root + " ...");
loadResourcesInternal(root);
}
private synchronized void loadResourcesInternal(Path root) throws IOException {
if (!Files.isDirectory(root)) { if (!Files.isDirectory(root)) {
try (FileSystem fileSystem = FileSystems.newFileSystem(root, (ClassLoader) null)) { try (FileSystem fileSystem = FileSystems.newFileSystem(root, (ClassLoader) null)) {
for (Path fsRoot : fileSystem.getRootDirectories()) { for (Path fsRoot : fileSystem.getRootDirectories()) {
if (!Files.isDirectory(fsRoot)) continue; if (!Files.isDirectory(fsRoot)) continue;
this.loadResources(fsRoot); this.loadResourcesInternal(fsRoot);
} }
} catch (Exception ex) {
Logger.global.logDebug("Failed to read '" + root + "': " + ex);
} }
return; return;
} }
// load nested jars from fabric.mod.json if present
Path fabricModJson = root.resolve("fabric.mod.json");
if (Files.isRegularFile(fabricModJson)) {
try (BufferedReader reader = Files.newBufferedReader(fabricModJson)) {
JsonObject rootElement = ResourcesGson.INSTANCE.fromJson(reader, JsonObject.class);
for (JsonElement element : rootElement.getAsJsonArray("jars")) {
Path file = root.resolve(element.getAsJsonObject().get("file").getAsString());
if (Files.exists(file)) loadResourcesInternal(file);
}
} catch (Exception ex) {
Logger.global.logDebug("Failed to read fabric.mod.json: " + ex);
}
}
try { try {
// do those in parallel // do those in parallel
CompletableFuture.allOf( CompletableFuture.allOf(
@ -184,12 +207,14 @@ public synchronized void loadResources(Path root) throws IOException {
return ResourcesGson.INSTANCE.fromJson(reader, BlockState.class); return ResourcesGson.INSTANCE.fromJson(reader, BlockState.class);
} }
}, blockStates)); }, blockStates));
}), }, BlueMap.THREAD_POOL),
// load blockmodels // load blockmodels
CompletableFuture.runAsync(() -> { CompletableFuture.runAsync(() -> {
list(root.resolve("assets")) list(root.resolve("assets"))
.map(path -> path.resolve("models").resolve("block")) .map(path -> path.resolve("models"))
.flatMap(ResourcePack::list)
.filter(path -> Pattern.matches("blocks?", path.getFileName().toString()))
.filter(Files::isDirectory) .filter(Files::isDirectory)
.flatMap(ResourcePack::walk) .flatMap(ResourcePack::walk)
.filter(path -> path.getFileName().toString().endsWith(".json")) .filter(path -> path.getFileName().toString().endsWith(".json"))
@ -199,12 +224,14 @@ public synchronized void loadResources(Path root) throws IOException {
return ResourcesGson.INSTANCE.fromJson(reader, BlockModel.class); return ResourcesGson.INSTANCE.fromJson(reader, BlockModel.class);
} }
}, blockModels)); }, blockModels));
}), }, BlueMap.THREAD_POOL),
// load textures // load textures
CompletableFuture.runAsync(() -> { CompletableFuture.runAsync(() -> {
list(root.resolve("assets")) list(root.resolve("assets"))
.map(path -> path.resolve("textures").resolve("block")) .map(path -> path.resolve("textures"))
.flatMap(ResourcePack::list)
.filter(path -> Pattern.matches("blocks?", path.getFileName().toString()))
.filter(Files::isDirectory) .filter(Files::isDirectory)
.flatMap(ResourcePack::walk) .flatMap(ResourcePack::walk)
.filter(path -> path.getFileName().toString().endsWith(".png")) .filter(path -> path.getFileName().toString().endsWith(".png"))
@ -215,7 +242,7 @@ public synchronized void loadResources(Path root) throws IOException {
return Texture.from(resourcePath, ImageIO.read(in)); return Texture.from(resourcePath, ImageIO.read(in));
} }
}, textures)); }, textures));
}), }, BlueMap.THREAD_POOL),
// load colormaps // load colormaps
CompletableFuture.runAsync(() -> { CompletableFuture.runAsync(() -> {
@ -227,7 +254,7 @@ public synchronized void loadResources(Path root) throws IOException {
return ImageIO.read(in); return ImageIO.read(in);
} }
}, colormaps)); }, colormaps));
}), }, BlueMap.THREAD_POOL),
// load block-color configs // load block-color configs
CompletableFuture.runAsync(() -> { CompletableFuture.runAsync(() -> {
@ -241,7 +268,7 @@ public synchronized void loadResources(Path root) throws IOException {
Logger.global.logDebug("Failed to parse resource-file '" + file + "': " + ex); Logger.global.logDebug("Failed to parse resource-file '" + file + "': " + ex);
} }
}); });
}), }, BlueMap.THREAD_POOL),
// load biome configs // load biome configs
CompletableFuture.runAsync(() -> { CompletableFuture.runAsync(() -> {
@ -269,7 +296,7 @@ public synchronized void loadResources(Path root) throws IOException {
} }
}) })
); );
}), }, BlueMap.THREAD_POOL),
// load block-properties configs // load block-properties configs
CompletableFuture.runAsync(() -> { CompletableFuture.runAsync(() -> {
@ -283,7 +310,7 @@ public synchronized void loadResources(Path root) throws IOException {
Logger.global.logDebug("Failed to parse resource-file '" + file + "': " + ex); Logger.global.logDebug("Failed to parse resource-file '" + file + "': " + ex);
} }
}); });
}) }, BlueMap.THREAD_POOL)
).join(); ).join();
@ -296,7 +323,7 @@ public synchronized void loadResources(Path root) throws IOException {
} }
public synchronized void bake() throws IOException { public synchronized void bake() throws IOException {
Logger.global.logInfo("Baking resources..."); Logger.global.logDebug("Baking resources...");
// fill path maps // fill path maps
blockStates.keySet().forEach(path -> blockStatePaths.put(path.getFormatted(), path)); blockStates.keySet().forEach(path -> blockStatePaths.put(path.getFormatted(), path));

View File

@ -5,6 +5,7 @@
import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonReader;
import de.bluecolored.bluemap.core.debug.DebugDump; import de.bluecolored.bluemap.core.debug.DebugDump;
import de.bluecolored.bluemap.core.resources.AbstractTypeAdapterFactory; import de.bluecolored.bluemap.core.resources.AbstractTypeAdapterFactory;
import de.bluecolored.bluemap.core.resources.adapter.ResourcesGson;
import de.bluecolored.bluemap.core.world.BlockState; import de.bluecolored.bluemap.core.world.BlockState;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
@ -52,8 +53,11 @@ public Multipart read(JsonReader in, Gson gson) throws IOException {
in.beginObject(); in.beginObject();
while (in.hasNext()) { while (in.hasNext()) {
String key = in.nextName(); String key = in.nextName();
if (key.equals("when")) condition = readCondition(in); switch (key) {
if (key.equals("apply")) variantSet = gson.fromJson(in, VariantSet.class); case "when": condition = readCondition(in); break;
case "apply": variantSet = gson.fromJson(in, VariantSet.class); break;
default: in.skipValue(); break;
}
} }
in.endObject(); in.endObject();
@ -71,7 +75,10 @@ public BlockStateCondition readCondition(JsonReader in) throws IOException {
in.beginObject(); in.beginObject();
while (in.hasNext()) { while (in.hasNext()) {
String name = in.nextName(); String name = in.nextName();
if (name.equals(JSON_COMMENT)) continue; if (name.equals(JSON_COMMENT)) {
in.skipValue();
continue;
}
if (name.equals("OR")) { if (name.equals("OR")) {
List<BlockStateCondition> orConditions = new ArrayList<>(); List<BlockStateCondition> orConditions = new ArrayList<>();
@ -82,8 +89,17 @@ public BlockStateCondition readCondition(JsonReader in) throws IOException {
in.endArray(); in.endArray();
andConditions.add( andConditions.add(
BlockStateCondition.or(orConditions.toArray(new BlockStateCondition[0]))); BlockStateCondition.or(orConditions.toArray(new BlockStateCondition[0])));
} else if (name.equals("AND")) {
List<BlockStateCondition> andArray = new ArrayList<>();
in.beginArray();
while (in.hasNext()) {
andArray.add(readCondition(in));
}
in.endArray();
andConditions.add(
BlockStateCondition.and(andArray.toArray(new BlockStateCondition[0])));
} else { } else {
String[] values = StringUtils.split(in.nextString(), '|'); String[] values = StringUtils.split(ResourcesGson.nextStringOrBoolean(in), '|');
andConditions.add(BlockStateCondition.property(name, values)); andConditions.add(BlockStateCondition.property(name, values));
} }
} }

View File

@ -4,6 +4,7 @@
import com.google.gson.annotations.JsonAdapter; import com.google.gson.annotations.JsonAdapter;
import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonReader;
import de.bluecolored.bluemap.core.debug.DebugDump; import de.bluecolored.bluemap.core.debug.DebugDump;
import de.bluecolored.bluemap.core.logger.Logger;
import de.bluecolored.bluemap.core.resources.AbstractTypeAdapterFactory; import de.bluecolored.bluemap.core.resources.AbstractTypeAdapterFactory;
import de.bluecolored.bluemap.core.world.BlockState; import de.bluecolored.bluemap.core.world.BlockState;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
@ -60,7 +61,10 @@ public Variants read(JsonReader in, Gson gson) throws IOException {
in.beginObject(); in.beginObject();
while (in.hasNext()) { while (in.hasNext()) {
String name = in.nextName(); String name = in.nextName();
if (name.equals(JSON_COMMENT)) continue; if (name.equals(JSON_COMMENT)) {
in.skipValue();
continue;
}
BlockStateCondition condition = parseConditionString(name); BlockStateCondition condition = parseConditionString(name);
VariantSet variantSet = gson.fromJson(in, VariantSet.class); VariantSet variantSet = gson.fromJson(in, VariantSet.class);
@ -68,7 +72,7 @@ public Variants read(JsonReader in, Gson gson) throws IOException {
if (variantSet.getCondition() == BlockStateCondition.all()) { if (variantSet.getCondition() == BlockStateCondition.all()) {
result.defaultVariant = variantSet; result.defaultVariant = variantSet;
} else { } else if (variantSet.getCondition() != BlockStateCondition.none()) {
result.variants.add(variantSet); result.variants.add(variantSet);
} }
} }
@ -79,19 +83,23 @@ public Variants read(JsonReader in, Gson gson) throws IOException {
private BlockStateCondition parseConditionString(String conditionString) { private BlockStateCondition parseConditionString(String conditionString) {
List<BlockStateCondition> conditions = new ArrayList<>(); List<BlockStateCondition> conditions = new ArrayList<>();
boolean invalid = false;
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) if (keyval.length < 2) {
throw new IllegalArgumentException("Condition-String '" + conditionString + "' is invalid!"); Logger.global.logDebug("Failed to parse condition: Condition-String '" + conditionString + "' is invalid!");
invalid = true;
continue;
}
conditions.add(BlockStateCondition.property(keyval[0], keyval[1])); conditions.add(BlockStateCondition.property(keyval[0], keyval[1]));
} }
} }
BlockStateCondition condition; BlockStateCondition condition;
if (conditions.isEmpty()) { if (conditions.isEmpty()) {
condition = BlockStateCondition.all(); condition = invalid ? BlockStateCondition.none() : BlockStateCondition.all();
} else if (conditions.size() == 1) { } else if (conditions.size() == 1) {
condition = conditions.get(0); condition = conditions.get(0);
} else { } else {

View File

@ -623,7 +623,7 @@ private int loadMapTileCompressionFK(Compression compression) throws SQLExceptio
return lookupFK("bluemap_map_tile_compression", "id", "compression", compression.getTypeId()); return lookupFK("bluemap_map_tile_compression", "id", "compression", compression.getTypeId());
} }
@SuppressWarnings("SameParameterValue") @SuppressWarnings({"SameParameterValue", "SqlResolve"})
private int lookupFK(String table, String idField, String valueField, String value) throws SQLException, IOException { private int lookupFK(String table, String idField, String valueField, String value) throws SQLException, IOException {
return recoveringConnection(connection -> { return recoveringConnection(connection -> {
int key; int key;

View File

@ -239,6 +239,11 @@ public Path getConfigFolder() {
return configFolder; return configFolder;
} }
@Override
public Optional<Path> getModsFolder() {
return Optional.empty();
}
@Override @Override
public Collection<Player> getOnlinePlayers() { public Collection<Player> getOnlinePlayers() {
return Collections.emptyList(); return Collections.emptyList();
@ -249,6 +254,11 @@ public Optional<Player> getPlayer(UUID uuid) {
return Optional.empty(); return Optional.empty();
} }
@Override
public boolean isPluginConfigEnabled() {
return false;
}
public static void main(String[] args) { public static void main(String[] args) {
CommandLineParser parser = new DefaultParser(); CommandLineParser parser = new DefaultParser();

View File

@ -151,6 +151,11 @@ public Path getConfigFolder() {
return Path.of("config", "bluemap"); return Path.of("config", "bluemap");
} }
@Override
public Optional<Path> getModsFolder() {
return Optional.of(Path.of("mods"));
}
public void onPlayerJoin(MinecraftServer server, ServerPlayerEntity playerInstance) { public void onPlayerJoin(MinecraftServer server, ServerPlayerEntity playerInstance) {
if (this.serverInstance != server) return; if (this.serverInstance != server) return;

View File

@ -151,6 +151,11 @@ public Path getConfigFolder() {
return Path.of("config", "bluemap"); return Path.of("config", "bluemap");
} }
@Override
public Optional<Path> getModsFolder() {
return Optional.of(Path.of("mods"));
}
public void onPlayerJoin(MinecraftServer server, ServerPlayerEntity playerInstance) { public void onPlayerJoin(MinecraftServer server, ServerPlayerEntity playerInstance) {
if (this.serverInstance != server) return; if (this.serverInstance != server) return;

View File

@ -151,6 +151,11 @@ public Path getConfigFolder() {
return Path.of("config", "bluemap"); return Path.of("config", "bluemap");
} }
@Override
public Optional<Path> getModsFolder() {
return Optional.of(Path.of("mods"));
}
public void onPlayerJoin(MinecraftServer server, ServerPlayerEntity playerInstance) { public void onPlayerJoin(MinecraftServer server, ServerPlayerEntity playerInstance) {
if (this.serverInstance != server) return; if (this.serverInstance != server) return;

View File

@ -151,6 +151,11 @@ public Path getConfigFolder() {
return Path.of("config", "bluemap"); return Path.of("config", "bluemap");
} }
@Override
public Optional<Path> getModsFolder() {
return Optional.of(Path.of("mods"));
}
public void onPlayerJoin(MinecraftServer server, ServerPlayerEntity playerInstance) { public void onPlayerJoin(MinecraftServer server, ServerPlayerEntity playerInstance) {
if (this.serverInstance != server) return; if (this.serverInstance != server) return;

View File

@ -151,6 +151,11 @@ public Path getConfigFolder() {
return Path.of("config", "bluemap"); return Path.of("config", "bluemap");
} }
@Override
public Optional<Path> getModsFolder() {
return Optional.of(Path.of("mods"));
}
public void onPlayerJoin(MinecraftServer server, ServerPlayerEntity playerInstance) { public void onPlayerJoin(MinecraftServer server, ServerPlayerEntity playerInstance) {
if (this.serverInstance != server) return; if (this.serverInstance != server) return;

View File

@ -164,6 +164,11 @@ public Path getConfigFolder() {
return Path.of("config", "bluemap"); return Path.of("config", "bluemap");
} }
@Override
public Optional<Path> getModsFolder() {
return Optional.of(Path.of("mods"));
}
@SubscribeEvent @SubscribeEvent
public void onPlayerJoin(PlayerLoggedInEvent evt) { public void onPlayerJoin(PlayerLoggedInEvent evt) {
PlayerEntity playerInstance = evt.getPlayer(); PlayerEntity playerInstance = evt.getPlayer();

View File

@ -164,6 +164,11 @@ public Path getConfigFolder() {
return Path.of("config", "bluemap"); return Path.of("config", "bluemap");
} }
@Override
public Optional<Path> getModsFolder() {
return Optional.of(Path.of("mods"));
}
@SubscribeEvent @SubscribeEvent
public void onPlayerJoin(PlayerLoggedInEvent evt) { public void onPlayerJoin(PlayerLoggedInEvent evt) {
PlayerEntity playerInstance = evt.getPlayer(); PlayerEntity playerInstance = evt.getPlayer();

View File

@ -164,6 +164,11 @@ public Path getConfigFolder() {
return Path.of("config", "bluemap"); return Path.of("config", "bluemap");
} }
@Override
public Optional<Path> getModsFolder() {
return Optional.of(Path.of("mods"));
}
@SubscribeEvent @SubscribeEvent
public void onPlayerJoin(PlayerLoggedInEvent evt) { public void onPlayerJoin(PlayerLoggedInEvent evt) {
PlayerEntity playerInstance = evt.getPlayer(); PlayerEntity playerInstance = evt.getPlayer();

View File

@ -163,6 +163,11 @@ public Path getConfigFolder() {
return Path.of("config", "bluemap"); return Path.of("config", "bluemap");
} }
@Override
public Optional<Path> getModsFolder() {
return Optional.of(Path.of("mods"));
}
@SubscribeEvent @SubscribeEvent
public void onPlayerJoin(PlayerLoggedInEvent evt) { public void onPlayerJoin(PlayerLoggedInEvent evt) {
var playerInstance = evt.getPlayer(); var playerInstance = evt.getPlayer();

View File

@ -163,6 +163,11 @@ public Path getConfigFolder() {
return Path.of("config", "bluemap"); return Path.of("config", "bluemap");
} }
@Override
public Optional<Path> getModsFolder() {
return Optional.of(Path.of("mods"));
}
@SubscribeEvent @SubscribeEvent
public void onPlayerJoin(PlayerLoggedInEvent evt) { public void onPlayerJoin(PlayerLoggedInEvent evt) {
var playerInstance = evt.getPlayer(); var playerInstance = evt.getPlayer();

View File

@ -199,6 +199,11 @@ public Path getConfigFolder() {
return getDataFolder().toPath(); return getDataFolder().toPath();
} }
@Override
public Optional<Path> getModsFolder() {
return Optional.of(Path.of("mods")); // in case this is a Bukkit/Forge hybrid
}
public Plugin getPlugin() { public Plugin getPlugin() {
return pluginInstance; return pluginInstance;
} }

View File

@ -235,6 +235,11 @@ public Path getConfigFolder() {
return configurationDir; return configurationDir;
} }
@Override
public Optional<Path> getModsFolder() {
return Optional.of(Path.of("mods"));
}
@Override @Override
public Collection<Player> getOnlinePlayers() { public Collection<Player> getOnlinePlayers() {
return onlinePlayerMap.values(); return onlinePlayerMap.values();