Compare commits

...

10 Commits

Author SHA1 Message Date
Astrash c0aaf6c6e8 Add messages to exceptions 2023-11-28 10:36:30 +11:00
Astrash 1ab3233cba Reformat code 2023-11-25 15:14:16 +11:00
Astrash 59ea5a69d8 Refactor pack loading
- Combine initial load and reload logic together between each platform implementation
- Should prevent pack load errors from blocking other packs from loading.
2023-11-25 15:10:43 +11:00
Astrash 4ba71e9c27 packDirectory -> rootPath 2023-11-25 15:07:45 +11:00
Astrash 5c7441241c Replace Loader with java.nio.files 2023-11-25 13:31:42 +11:00
Astrash ffb1198da2 Merge branch 'master' into ver/6.5.0 2023-11-25 12:33:51 +11:00
Astrash 2c211f0aa6 Merge branch 'master' into ver/6.5.0 2023-11-25 12:29:54 +11:00
Astrashh 3aef977384 Patch version 6.4.1 (#435)
* Bump version to 6.4.1

* fix fabric dev env

* Invert exposed ore logic (#433)

* Invert exposed ore logic

* Bump ore addon version

* Use logger in Gradle over println (#434)

* Log info instead of println in gradle scripts

* Missed buildSrc printlns

---------

Co-authored-by: Zoë <duplexsys@protonmail.com>
2023-11-25 01:02:45 +00:00
Zoë 6060ceae57 Merge pull request #432 from PolyhedralDev/dev/codeowners
update condeowners
2023-11-18 10:05:10 +00:00
Zoë Gidiere e499ddebd4 update condeowners 2023-11-17 13:34:56 -07:00
34 changed files with 284 additions and 520 deletions
+6 -12
View File
@@ -1,19 +1,13 @@
# Global owners, automatically request review when pull request is submitted
* @dfsek @solonovamax
* @dfsek @solonovamax @duplexsystem @Astrashh @justaureus
# Platforms
## dfsek wrote the majority of the platform impls
/platforms/bukkit/ @dfsek
/platforms/fabric/ @dfsek
/platforms/forge/ @dfsek
/platforms/sponge/ @dfsek
## solonovamax is working on the region generator (unless duplexsystem takes it over)
/platforms/region/ @solonovamax
/platforms/ @dfsek @solonovamax @duplexsystem @justaureus
# Common
/common/ @dfsek @solonovamax
/common/ @dfsek @solonovamax @duplexsystem @Astrashh
# Gradle Stuff
## Most gradle stuff was written by solonovamax
/buildSrc/ @solonovamax
*.gradle.kts @solonovamax
/buildSrc/ @dfsek @solonovamax @duplexsystem
*.gradle.kts @dfsek @solonovamax @duplexsystem
/gradle/ @dfsek @solonovamax @duplexsystem
+2 -2
View File
@@ -18,7 +18,7 @@ fun Project.addonDir(dir: File, task: Task) {
matchingAddons(dir) {
it.name.startsWith("Terra-") // Assume everything that starts with Terra- is a core addon.
}.forEach {
println("Deleting old addon: " + it.absolutePath)
logger.info("Deleting old addon: " + it.absolutePath)
it.delete()
}
forSubProjects(":common:addons") {
@@ -29,7 +29,7 @@ fun Project.addonDir(dir: File, task: Task) {
val base = "${jar.archiveBaseName.get()}-${version}"
println("Copying addon ${jar.archiveFileName.get()} to ${target.absolutePath}. Base name: $base")
logger.info("Copying addon ${jar.archiveFileName.get()} to ${target.absolutePath}. Base name: $base")
jar.archiveFile.orNull?.asFile?.copyTo(target)
}
@@ -53,7 +53,7 @@ fun Project.configureDistribution() {
forSubProjects(":common:addons") {
val jar = getJarTask()
println("Packaging addon ${jar.archiveFileName.get()} to $dest. size: ${jar.archiveFile.get().asFile.length() / 1024}KB")
logger.info("Packaging addon ${jar.archiveFileName.get()} to $dest. size: ${jar.archiveFile.get().asFile.length() / 1024}KB")
val boot = if (extra.has("bootstrap") && extra.get("bootstrap") as Boolean) "bootstrap/" else ""
val addonPath = fs.getPath("/addons/$boot${jar.archiveFileName.get()}")
+2 -2
View File
@@ -42,10 +42,10 @@ fun preRelease(preRelease: Boolean) {
fun Project.versionProjects(project: String, version: String) {
forSubProjects(project) {
this.version = version
println("Setting version of $path to $version")
logger.info("Setting version of $path to $version")
}
project(project).version = version
println("Setting version of $project to $version")
logger.info("Setting version of $project to $version")
}
fun Project.version(version: String): String {
@@ -82,7 +82,7 @@ abstract class GenerateDocsTask : DefaultTask() {
}
template.add(keyName.toString(), description.toString().ifBlank {
println("No description provided for field " + field.name + " in class " + name)
logger.info("No description provided for field " + field.name + " in class " + name)
"*No description provided.*"
})
}
@@ -7,13 +7,13 @@
package com.dfsek.terra.addons.biome.image.v2;
import java.util.Optional;
import com.dfsek.terra.addons.image.colorsampler.ColorSampler;
import com.dfsek.terra.addons.image.converter.ColorConverter;
import com.dfsek.terra.api.world.biome.Biome;
import com.dfsek.terra.api.world.biome.generation.BiomeProvider;
import java.util.Optional;
public class ImageBiomeProvider implements BiomeProvider {
private final int resolution;
+1 -1
View File
@@ -1,4 +1,4 @@
version = version("1.1.0")
version = version("1.1.1")
dependencies {
compileOnlyApi(project(":common:addons:manifest-addon-loader"))
@@ -8,31 +8,24 @@ import com.dfsek.terra.api.world.WritableWorld;
public class VanillaOreUtils {
protected static boolean shouldNotDiscard(Random random, double chance) {
if(chance <= 0.0F) {
return true;
} else if(chance >= 1.0F) {
return false;
} else {
return random.nextFloat() >= chance;
}
private static boolean shouldExpose(Random random, double exposedChance) {
if(exposedChance >= 1.0F) return true;
if(exposedChance <= 0.0F) return false;
return random.nextFloat() < exposedChance;
}
public static boolean shouldPlace(MaterialSet replaceable, BlockType type, Double exposed, Random random, WritableWorld world, int x,
public static boolean shouldPlace(MaterialSet replaceable, BlockType type, Double exposedChance, Random random, WritableWorld world,
int x,
int y, int z) {
if(!replaceable.contains(type)) {
return false;
} else if(shouldNotDiscard(random, exposed)) {
return true;
} else {
return !(world.getBlockState(x, y, z - 1).isAir() ||
world.getBlockState(x, y, z + 1).isAir() ||
world.getBlockState(x, y - 1, z).isAir() ||
world.getBlockState(x, y + 1, z).isAir() ||
world.getBlockState(x - 1, y, z).isAir() ||
world.getBlockState(x + 1, y, z).isAir());
}
if(!replaceable.contains(type)) return false;
if(shouldExpose(random, exposedChance)) return true; // Exposed blocks can be placed regardless of adjacency to air
// Adjacency is checked after determining not exposed rather than vice-versa, assuming block checks are more expensive
boolean adjacentAir = world.getBlockState(x, y, z - 1).isAir() ||
world.getBlockState(x, y, z + 1).isAir() ||
world.getBlockState(x, y - 1, z).isAir() ||
world.getBlockState(x, y + 1, z).isAir() ||
world.getBlockState(x - 1, y, z).isAir() ||
world.getBlockState(x + 1, y, z).isAir();
return !adjacentAir; // Exposed check did not pass earlier so only blocks not adjacent air should place
}
}
@@ -11,12 +11,16 @@ import com.dfsek.tectonic.yaml.YamlConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.nio.file.Files;
import com.dfsek.terra.addons.manifest.api.AddonInitializer;
import com.dfsek.terra.api.Platform;
import com.dfsek.terra.api.addon.BaseAddon;
import com.dfsek.terra.api.event.events.config.ConfigurationDiscoveryEvent;
import com.dfsek.terra.api.event.functional.FunctionalEventHandler;
import com.dfsek.terra.api.inject.annotations.Inject;
import com.dfsek.terra.api.util.FileUtil;
public class YamlAddon implements AddonInitializer {
@@ -33,10 +37,21 @@ public class YamlAddon implements AddonInitializer {
platform.getEventManager()
.getHandler(FunctionalEventHandler.class)
.register(addon, ConfigurationDiscoveryEvent.class)
.then(event -> event.getLoader().open("", ".yml").thenEntries(entries -> entries.forEach(entry -> {
LOGGER.debug("Discovered config {}", entry.getKey());
event.register(entry.getKey(), new YamlConfiguration(entry.getValue(), entry.getKey()));
})).close())
.then(event -> {
try {
FileUtil.filesWithExtension(event.getPack().getRootPath(), ".yml")
.forEach((key, value) -> {
LOGGER.debug("Discovered config {}", key);
try {
event.register(key, new YamlConfiguration(Files.newInputStream(value), key));
} catch(IOException e) {
throw new RuntimeException("Failed to load config " + key, e);
}
});
} catch(IOException e) {
throw new RuntimeException("Error occurred while reading config pack files", e);
}
})
.failThrough();
}
}
@@ -60,8 +60,8 @@ public class ImageLibraryAddon implements AddonInitializer {
.then(event -> {
ConfigPack pack = event.getPack();
CheckedRegistry<Supplier<ObjectTemplate<Image>>> imageRegistry = pack.getOrCreateRegistry(IMAGE_REGISTRY_KEY);
imageRegistry.register(addon.key("BITMAP"), () -> new ImageTemplate(pack.getLoader(), pack));
imageRegistry.register(addon.key("STITCHED_BITMAP"), () -> new StitchedImageTemplate(pack.getLoader(), pack));
imageRegistry.register(addon.key("BITMAP"), () -> new ImageTemplate(pack));
imageRegistry.register(addon.key("STITCHED_BITMAP"), () -> new StitchedImageTemplate(pack));
})
.then(event -> {
event.getPack()
@@ -4,8 +4,10 @@ import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import javax.imageio.ImageIO;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.concurrent.TimeUnit;
import com.dfsek.terra.addons.image.config.ImageLibraryPackConfigTemplate;
@@ -13,7 +15,6 @@ import com.dfsek.terra.addons.image.image.BufferedImageWrapper;
import com.dfsek.terra.addons.image.image.Image;
import com.dfsek.terra.addons.image.image.SuppliedImage;
import com.dfsek.terra.api.config.ConfigPack;
import com.dfsek.terra.api.config.Loader;
import com.dfsek.terra.api.properties.Properties;
import com.dfsek.terra.api.util.generic.Lazy;
@@ -22,13 +23,13 @@ import com.dfsek.terra.api.util.generic.Lazy;
* Cache prevents configs from loading the same image multiple times into memory
*/
record ImageCache(LoadingCache<String, Image> cache) implements Properties {
public static Image load(String path, ConfigPack pack, Loader files) throws IOException {
public static Image load(String path, ConfigPack pack) throws IOException {
ImageLibraryPackConfigTemplate config = pack.getContext().get(ImageLibraryPackConfigTemplate.class);
ImageCache images;
if(!pack.getContext().has(ImageCache.class)) {
var cacheBuilder = Caffeine.newBuilder();
if(config.unloadOnTimeout()) cacheBuilder.expireAfterAccess(config.getCacheTimeout(), TimeUnit.SECONDS);
images = new ImageCache(cacheBuilder.build(s -> loadImage(s, files)));
images = new ImageCache(cacheBuilder.build(s -> loadImage(s, pack.getRootPath())));
pack.getContext().put(images);
} else images = pack.getContext().get(ImageCache.class);
@@ -45,17 +46,8 @@ record ImageCache(LoadingCache<String, Image> cache) implements Properties {
return images.cache.get(path);
}
private static Image loadImage(String path, Loader files) throws IOException {
try {
return new BufferedImageWrapper(ImageIO.read(files.get(path)));
} catch(IllegalArgumentException e) {
throw new IllegalArgumentException("Unable to load image (image might be too large?)", e);
} catch(IOException e) {
if(e instanceof FileNotFoundException) {
// Rethrow using nicer message
throw new IOException("Unable to load image: No such file or directory: " + path, e);
}
throw new IOException("Unable to load image", e);
}
private static Image loadImage(String path, Path directory) throws IOException {
InputStream is = Files.newInputStream(directory.resolve(path));
return new BufferedImageWrapper(ImageIO.read(is));
}
}
@@ -7,25 +7,22 @@ import java.io.IOException;
import com.dfsek.terra.addons.image.image.Image;
import com.dfsek.terra.api.config.ConfigPack;
import com.dfsek.terra.api.config.Loader;
public class ImageTemplate implements ObjectTemplate<Image> {
private final Loader files;
private final ConfigPack pack;
@Value("path")
private String path;
public ImageTemplate(Loader files, ConfigPack pack) {
this.files = files;
public ImageTemplate(ConfigPack pack) {
this.pack = pack;
}
@Override
public Image get() {
try {
return ImageCache.load(path, pack, files);
return ImageCache.load(path, pack);
} catch(IOException e) {
throw new RuntimeException(e);
}
@@ -11,12 +11,10 @@ import java.io.IOException;
import com.dfsek.terra.addons.image.image.Image;
import com.dfsek.terra.addons.image.image.StitchedImage;
import com.dfsek.terra.api.config.ConfigPack;
import com.dfsek.terra.api.config.Loader;
public class StitchedImageTemplate implements ObjectTemplate<Image>, ValidatedConfigTemplate {
private final Loader files;
private final ConfigPack pack;
@Value("path-format")
private String path;
@@ -28,8 +26,7 @@ public class StitchedImageTemplate implements ObjectTemplate<Image>, ValidatedCo
@Default
private boolean zeroIndexed = false;
public StitchedImageTemplate(Loader files, ConfigPack pack) {
this.files = files;
public StitchedImageTemplate(ConfigPack pack) {
this.pack = pack;
}
@@ -39,7 +36,7 @@ public class StitchedImageTemplate implements ObjectTemplate<Image>, ValidatedCo
for(int i = 0; i < rows; i++) {
for(int j = 0; j < cols; j++) {
try {
grid[i][j] = ImageCache.load(getFormattedPath(i, j), pack, files);
grid[i][j] = ImageCache.load(getFormattedPath(i, j), pack);
} catch(IOException e) {
throw new RuntimeException(e);
}
@@ -16,6 +16,7 @@ import net.querz.nbt.tag.Tag;
import java.io.IOException;
import java.io.InputStream;
import java.io.PushbackInputStream;
import java.nio.file.Files;
import java.util.HashMap;
import java.util.Map;
import java.util.zip.GZIPInputStream;
@@ -29,7 +30,7 @@ import com.dfsek.terra.api.event.functional.FunctionalEventHandler;
import com.dfsek.terra.api.inject.annotations.Inject;
import com.dfsek.terra.api.registry.CheckedRegistry;
import com.dfsek.terra.api.structure.Structure;
import com.dfsek.terra.api.util.StringUtil;
import com.dfsek.terra.api.util.FileUtil;
import com.dfsek.terra.api.util.vector.Vector3Int;
@@ -58,13 +59,21 @@ public class SpongeSchematicAddon implements AddonInitializer {
.register(addon, ConfigPackPreLoadEvent.class)
.then(event -> {
CheckedRegistry<Structure> structureRegistry = event.getPack().getOrCreateRegistry(Structure.class);
event.getPack()
.getLoader()
.open("", ".schem")
.thenEntries(entries -> entries
try {
FileUtil.filesWithExtension(event.getPack().getRootPath(), ".schem")
.entrySet()
.stream()
.map(entry -> convert(entry.getValue(), StringUtil.fileName(entry.getKey())))
.forEach(structureRegistry::register)).close();
.map(entry -> {
try {
return convert(Files.newInputStream(entry.getValue()), FileUtil.fileName(entry.getKey()));
} catch(IOException e) {
throw new RuntimeException("Failed to load config " + entry.getKey(), e);
}
})
.forEach(structureRegistry::register);
} catch(IOException e) {
throw new RuntimeException("Error occurred while reading config pack files", e);
}
})
.failThrough();
}
@@ -7,6 +7,9 @@
package com.dfsek.terra.addons.terrascript;
import java.io.IOException;
import java.nio.file.Files;
import com.dfsek.terra.addons.manifest.api.AddonInitializer;
import com.dfsek.terra.addons.terrascript.parser.exceptions.ParseException;
import com.dfsek.terra.addons.terrascript.parser.lang.functions.FunctionBuilder;
@@ -19,7 +22,7 @@ import com.dfsek.terra.api.inject.annotations.Inject;
import com.dfsek.terra.api.registry.CheckedRegistry;
import com.dfsek.terra.api.structure.LootTable;
import com.dfsek.terra.api.structure.Structure;
import com.dfsek.terra.api.util.StringUtil;
import com.dfsek.terra.api.util.FileUtil;
public class TerraScriptAddon implements AddonInitializer {
@@ -37,26 +40,28 @@ public class TerraScriptAddon implements AddonInitializer {
.then(event -> {
CheckedRegistry<Structure> structureRegistry = event.getPack().getOrCreateRegistry(Structure.class);
CheckedRegistry<LootTable> lootRegistry = event.getPack().getOrCreateRegistry(LootTable.class);
event.getPack().getLoader().open("", ".tesf").thenEntries(
entries ->
entries.stream()
.parallel()
.map(entry -> {
try {
String id = StringUtil.fileName(entry.getKey());
return new StructureScript(entry.getValue(),
addon.key(id),
platform,
structureRegistry,
lootRegistry,
event.getPack().getOrCreateRegistry(FunctionBuilder.class));
} catch(ParseException e) {
throw new RuntimeException("Failed to load script \"" + entry.getKey() + "\"", e);
}
})
.toList()
.forEach(structureRegistry::register))
.close();
try {
FileUtil.filesWithExtension(event.getPack().getRootPath(), ".tesf")
.entrySet()
.stream()
.parallel()
.map(entry -> {
try {
String id = FileUtil.fileName(entry.getKey());
return new StructureScript(Files.newInputStream(entry.getValue()),
addon.key(id),
platform,
structureRegistry,
lootRegistry,
event.getPack().getOrCreateRegistry(FunctionBuilder.class));
} catch(ParseException | IOException e) {
throw new RuntimeException("Failed to load script \"" + entry.getKey() + "\"", e);
}
})
.forEach(structureRegistry::register);
} catch(IOException e) {
throw new RuntimeException("Error occurred while reading config pack files", e);
}
})
.priority(100)
.failThrough();
@@ -10,6 +10,7 @@ package com.dfsek.terra.api.config;
import ca.solostudios.strata.version.Version;
import ca.solostudios.strata.version.VersionRange;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
@@ -43,7 +44,7 @@ public interface ConfigPack extends LoaderRegistrar,
List<GenerationStage> getStages();
Loader getLoader();
Path getRootPath();
String getAuthor();
@@ -1,47 +0,0 @@
/*
* Copyright (c) 2020-2023 Polyhedral Development
*
* The Terra API is licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in the common/api directory.
*/
package com.dfsek.terra.api.config;
import com.dfsek.tectonic.api.exception.ConfigException;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
public interface Loader {
Loader thenNames(Consumer<List<String>> consumer) throws ConfigException;
Loader thenEntries(Consumer<Set<Map.Entry<String, InputStream>>> consumer) throws ConfigException;
/**
* Get a single file from this Loader.
*
* @param singleFile File to get
*
* @return InputStream from file.
*/
InputStream get(String singleFile) throws IOException;
/**
* Open a subdirectory.
*
* @param directory Directory to open
* @param extension File extension
*/
Loader open(String directory, String extension);
/**
* Close all InputStreams opened.
*/
Loader close();
}
@@ -12,7 +12,6 @@ import com.dfsek.tectonic.api.config.Configuration;
import java.util.function.BiConsumer;
import com.dfsek.terra.api.config.ConfigPack;
import com.dfsek.terra.api.config.Loader;
import com.dfsek.terra.api.event.events.FailThroughEvent;
import com.dfsek.terra.api.event.events.PackEvent;
@@ -25,13 +24,11 @@ import com.dfsek.terra.api.event.events.PackEvent;
*/
public class ConfigurationDiscoveryEvent implements PackEvent, FailThroughEvent {
private final ConfigPack pack;
private final Loader loader;
private final BiConsumer<String, Configuration> consumer;
public ConfigurationDiscoveryEvent(ConfigPack pack, Loader loader, BiConsumer<String, Configuration> consumer) {
public ConfigurationDiscoveryEvent(ConfigPack pack, BiConsumer<String, Configuration> consumer) {
this.pack = pack;
this.loader = loader;
this.consumer = consumer;
}
@@ -43,8 +40,4 @@ public class ConfigurationDiscoveryEvent implements PackEvent, FailThroughEvent
public ConfigPack getPack() {
return pack;
}
public Loader getLoader() {
return loader;
}
}
@@ -7,12 +7,12 @@
package com.dfsek.terra.api.structure;
import java.util.Random;
import com.dfsek.terra.api.util.Rotation;
import com.dfsek.terra.api.util.vector.Vector3Int;
import com.dfsek.terra.api.world.WritableWorld;
import java.util.Random;
public interface Structure {
boolean generate(Vector3Int location, WritableWorld world, Random random, Rotation rotation);
@@ -0,0 +1,38 @@
package com.dfsek.terra.api.util;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Collections;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static java.util.function.Function.identity;
public class FileUtil {
public static Map<String, Path> filesWithExtension(Path start, String... extensions) throws IOException {
if(Files.notExists(start) || !Files.isDirectory(start)) return Collections.emptyMap();
try(Stream<Path> paths = Files.walk(start)) {
return paths
.filter(Files::isRegularFile)
.filter(p -> Arrays.stream(extensions).anyMatch(e -> p.getFileName().toString().endsWith(e)))
.collect(Collectors.toMap(p -> start.relativize(p).toString(), identity()));
}
}
public static String fileName(String path) {
if(path.contains(File.separator)) {
return path.substring(path.lastIndexOf(File.separatorChar) + 1, path.lastIndexOf('.'));
} else if(path.contains("/")) {
return path.substring(path.lastIndexOf("/") + 1, path.lastIndexOf('.'));
} else if(path.contains(".")) {
return path.substring(0, path.lastIndexOf('.'));
} else {
return path;
}
}
}
@@ -1,25 +0,0 @@
/*
* Copyright (c) 2020-2023 Polyhedral Development
*
* The Terra API is licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in the common/api directory.
*/
package com.dfsek.terra.api.util;
import java.io.File;
public class StringUtil {
public static String fileName(String path) {
if(path.contains(File.separator)) {
return path.substring(path.lastIndexOf(File.separatorChar) + 1, path.lastIndexOf('.'));
} else if(path.contains("/")) {
return path.substring(path.lastIndexOf("/") + 1, path.lastIndexOf('.'));
} else if(path.contains(".")) {
return path.substring(0, path.lastIndexOf('.'));
} else {
return path;
}
}
}
@@ -18,6 +18,9 @@
package com.dfsek.terra;
import com.dfsek.tectonic.api.TypeRegistry;
import com.dfsek.terra.registry.master.ConfigRegistry.PackLoadFailuresException;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.jetbrains.annotations.NotNull;
@@ -147,18 +150,29 @@ public abstract class AbstractPlatform implements Platform {
eventManager.getHandler(FunctionalEventHandler.class)
.register(internalAddon, PlatformInitializationEvent.class)
.then(event -> {
logger.info("Loading config packs...");
configRegistry.loadAll(this);
logger.info("Loaded packs.");
})
.then(event -> loadConfigPacks())
.global();
logger.info("Terra addons successfully loaded.");
logger.info("Finished initialization.");
}
protected boolean loadConfigPacks() {
logger.info("Loading config packs...");
ConfigRegistry configRegistry = getRawConfigRegistry();
configRegistry.clear();
try {
configRegistry.loadAll(this);
} catch(IOException e) {
logger.error("Failed to load config packs", e);
return false;
} catch(PackLoadFailuresException e) {
e.getExceptions().forEach(ex -> logger.error("Failed to load config pack", ex));
return false;
}
return true;
}
protected InternalAddon loadAddons() {
List<BaseAddon> addonList = new ArrayList<>();
@@ -1,66 +0,0 @@
/*
* This file is part of Terra.
*
* Terra is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Terra is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Terra. If not, see <https://www.gnu.org/licenses/>.
*/
package com.dfsek.terra.config.fileloaders;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.stream.Stream;
/**
* Load all {@code *.yml} files from a {@link java.nio.file.Path}.
*/
public class FolderLoader extends LoaderImpl {
private static final Logger logger = LoggerFactory.getLogger(FolderLoader.class);
private final Path path;
public FolderLoader(Path path) {
this.path = path;
}
@Override
public InputStream get(String singleFile) throws IOException {
return new FileInputStream(new File(path.toFile(), singleFile));
}
protected void load(String directory, String extension) {
File newPath = new File(path.toFile(), directory);
newPath.mkdirs();
try(Stream<Path> paths = Files.walk(newPath.toPath())) {
paths.filter(Files::isRegularFile).filter(file -> file.toString().toLowerCase().endsWith(extension)).forEach(file -> {
try {
String rel = newPath.toPath().relativize(file).toString();
streams.put(rel, new FileInputStream(file.toFile()));
} catch(FileNotFoundException e) {
logger.error("Could not find file to load", e);
}
});
} catch(IOException e) {
logger.error("Error while loading files", e);
}
}
}
@@ -1,83 +0,0 @@
/*
* This file is part of Terra.
*
* Terra is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Terra is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Terra. If not, see <https://www.gnu.org/licenses/>.
*/
package com.dfsek.terra.config.fileloaders;
import com.dfsek.tectonic.api.exception.ConfigException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import com.dfsek.terra.api.config.Loader;
public abstract class LoaderImpl implements Loader {
private static final Logger logger = LoggerFactory.getLogger(LoaderImpl.class);
protected final Map<String, InputStream> streams = new HashMap<>();
@Override
public Loader thenNames(Consumer<List<String>> consumer) throws ConfigException {
consumer.accept(new ArrayList<>(streams.keySet()));
return this;
}
@Override
public Loader thenEntries(Consumer<Set<Map.Entry<String, InputStream>>> consumer) throws ConfigException {
consumer.accept(streams.entrySet());
return this;
}
/**
* Open a subdirectory.
*
* @param directory Directory to open
* @param extension File extension
*/
@Override
public LoaderImpl open(String directory, String extension) {
if(!streams.isEmpty()) throw new IllegalStateException("Attempted to load new directory before closing existing InputStreams");
load(directory, extension);
return this;
}
/**
* Close all InputStreams opened.
*/
@Override
public Loader close() {
streams.forEach((name, input) -> {
try {
input.close();
} catch(IOException e) {
logger.error("Error occurred while loading", e);
}
});
streams.clear();
return this;
}
protected abstract void load(String directory, String extension);
}
@@ -1,63 +0,0 @@
/*
* This file is part of Terra.
*
* Terra is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Terra is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Terra. If not, see <https://www.gnu.org/licenses/>.
*/
package com.dfsek.terra.config.fileloaders;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
public class ZIPLoader extends LoaderImpl {
private static final Logger logger = LoggerFactory.getLogger(ZIPLoader.class);
private final ZipFile file;
public ZIPLoader(ZipFile file) {
this.file = file;
}
@Override
public InputStream get(String singleFile) throws IOException {
Enumeration<? extends ZipEntry> entries = file.entries();
while(entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
if(!entry.isDirectory() && entry.getName().equals(singleFile)) return file.getInputStream(entry);
}
throw new IllegalArgumentException("No such file: " + singleFile);
}
protected void load(String directory, String extension) {
Enumeration<? extends ZipEntry> entries = file.entries();
while(entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
if(!entry.isDirectory() && entry.getName().startsWith(directory) && entry.getName().endsWith(extension)) {
try {
String rel = entry.getName().substring(directory.length());
streams.put(rel, file.getInputStream(entry));
} catch(IOException e) {
logger.error("Error while loading file from zip", e);
}
}
}
}
}
@@ -27,10 +27,10 @@ import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.lang.reflect.AnnotatedType;
import java.nio.file.Files;
import java.util.concurrent.ConcurrentHashMap;
import com.dfsek.terra.api.config.ConfigPack;
import com.dfsek.terra.api.config.Loader;
import com.dfsek.terra.api.properties.Properties;
@@ -39,12 +39,10 @@ import com.dfsek.terra.api.properties.Properties;
*/
@Deprecated
public class BufferedImageLoader implements TypeLoader<BufferedImage> {
private final Loader files;
private final ConfigPack pack;
public BufferedImageLoader(Loader files, ConfigPack pack) {
this.files = files;
public BufferedImageLoader(ConfigPack pack) {
this.pack = pack;
if(!pack.getContext().has(ImageCache.class))
pack.getContext().put(new ImageCache(new ConcurrentHashMap<>()));
@@ -55,7 +53,7 @@ public class BufferedImageLoader implements TypeLoader<BufferedImage> {
throws LoadException {
return pack.getContext().get(ImageCache.class).map.computeIfAbsent((String) c, s -> {
try {
return ImageIO.read(files.get(s));
return ImageIO.read(Files.newInputStream(pack.getRootPath().resolve(s)));
} catch(IOException e) {
throw new LoadException("Unable to load image", e, depthTracker);
}
@@ -26,13 +26,37 @@ import com.dfsek.tectonic.api.loader.AbstractConfigLoader;
import com.dfsek.tectonic.api.loader.ConfigLoader;
import com.dfsek.tectonic.api.loader.type.TypeLoader;
import com.dfsek.tectonic.yaml.YamlConfiguration;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Multimaps;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.awt.image.BufferedImage;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Supplier;
import com.dfsek.terra.api.Platform;
import com.dfsek.terra.api.addon.BaseAddon;
import com.dfsek.terra.api.config.ConfigFactory;
import com.dfsek.terra.api.config.ConfigPack;
import com.dfsek.terra.api.config.ConfigType;
import com.dfsek.terra.api.config.Loader;
import com.dfsek.terra.api.config.meta.Meta;
import com.dfsek.terra.api.event.events.config.ConfigurationDiscoveryEvent;
import com.dfsek.terra.api.event.events.config.ConfigurationLoadEvent;
@@ -45,39 +69,24 @@ import com.dfsek.terra.api.registry.OpenRegistry;
import com.dfsek.terra.api.registry.Registry;
import com.dfsek.terra.api.registry.key.RegistryKey;
import com.dfsek.terra.api.tectonic.ShortcutLoader;
import com.dfsek.terra.api.util.generic.Construct;
import com.dfsek.terra.api.util.generic.pair.Pair;
import com.dfsek.terra.api.util.reflection.ReflectionUtil;
import com.dfsek.terra.api.util.reflection.TypeKey;
import com.dfsek.terra.api.world.biome.generation.BiomeProvider;
import com.dfsek.terra.api.world.chunk.generation.stage.GenerationStage;
import com.dfsek.terra.api.world.chunk.generation.util.provider.ChunkGeneratorProvider;
import com.dfsek.terra.config.fileloaders.FolderLoader;
import com.dfsek.terra.config.fileloaders.ZIPLoader;
import com.dfsek.terra.config.loaders.GenericTemplateSupplierLoader;
import com.dfsek.terra.config.loaders.config.BufferedImageLoader;
import com.dfsek.terra.config.preprocessor.*;
import com.dfsek.terra.config.preprocessor.MetaListLikePreprocessor;
import com.dfsek.terra.config.preprocessor.MetaMapPreprocessor;
import com.dfsek.terra.config.preprocessor.MetaNumberPreprocessor;
import com.dfsek.terra.config.preprocessor.MetaStringPreprocessor;
import com.dfsek.terra.config.preprocessor.MetaValuePreprocessor;
import com.dfsek.terra.config.prototype.ProtoConfig;
import com.dfsek.terra.registry.CheckedRegistryImpl;
import com.dfsek.terra.registry.OpenRegistryImpl;
import com.dfsek.terra.registry.ShortcutHolder;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Multimaps;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.awt.image.BufferedImage;
import java.io.*;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Supplier;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
/**
* Represents a Terra configuration pack.
@@ -92,7 +101,7 @@ public class ConfigPackImpl implements ConfigPack {
private final AbstractConfigLoader abstractConfigLoader = new AbstractConfigLoader();
private final ConfigLoader selfLoader = new ConfigLoader();
private final Platform platform;
private final Loader loader;
private final Path rootPath;
private final Map<BaseAddon, VersionRange> addons;
@@ -106,40 +115,29 @@ public class ConfigPackImpl implements ConfigPack {
private final RegistryKey key;
public ConfigPackImpl(File folder, Platform platform) {
this(new FolderLoader(folder.toPath()), Construct.construct(() -> {
try {
return new YamlConfiguration(new FileInputStream(new File(folder, "pack.yml")), "pack.yml");
} catch(FileNotFoundException e) {
throw new UncheckedIOException("No pack.yml file found in " + folder.getAbsolutePath(), e);
}
}), platform);
}
public ConfigPackImpl(ZipFile file, Platform platform) {
this(new ZIPLoader(file), Construct.construct(() -> {
ZipEntry pack = null;
Enumeration<? extends ZipEntry> entries = file.entries();
while(entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
if(entry.getName().equals("pack.yml")) pack = entry;
}
if(pack == null) throw new IllegalArgumentException("No pack.yml file found in " + file.getName());
try {
return new YamlConfiguration(file.getInputStream(pack), "pack.yml");
} catch(IOException e) {
throw new UncheckedIOException("Unable to load pack.yml from ZIP file", e);
}
}), platform);
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private ConfigPackImpl(Loader loader, Configuration packManifest, Platform platform) {
@SuppressWarnings({ "rawtypes" })
public ConfigPackImpl(Path path, Platform platform) throws IOException {
long start = System.nanoTime();
this.loader = loader;
if(Files.notExists(path)) throw new FileNotFoundException("Could not load config pack, " + path + " does not exist");
if(Files.isDirectory(path)) {
this.rootPath = path;
} else if(Files.isRegularFile(path)) {
if(!path.getFileName().toString().endsWith(".zip")) {
throw new IOException("Could not load config pack, file " + path + " is not a zip");
}
FileSystem zipfs = FileSystems.newFileSystem(path);
this.rootPath = zipfs.getPath("/");
} else {
throw new IOException("Could not load config pack from " + path);
}
Path packManifestPath = rootPath.resolve("pack.yml");
if(Files.notExists(packManifestPath)) throw new IOException("No pack.yml found in " + path);
Configuration packManifest = new YamlConfiguration(Files.newInputStream(packManifestPath),
packManifestPath.getFileName().toString());
this.platform = platform;
this.configTypeRegistry = createConfigRegistry();
@@ -223,7 +221,7 @@ public class ConfigPackImpl implements ConfigPack {
private Map<String, Configuration> discoverConfigurations() {
Map<String, Configuration> configurations = new HashMap<>();
platform.getEventManager().callEvent(new ConfigurationDiscoveryEvent(this, loader,
platform.getEventManager().callEvent(new ConfigurationDiscoveryEvent(this,
(s, c) -> configurations.put(s.replace("\\", "/"),
c))); // Create all the configs.
return configurations;
@@ -268,7 +266,7 @@ public class ConfigPackImpl implements ConfigPack {
@Override
public void register(TypeRegistry registry) {
registry.registerLoader(ConfigType.class, configTypeRegistry)
.registerLoader(BufferedImage.class, new BufferedImageLoader(loader, this));
.registerLoader(BufferedImage.class, new BufferedImageLoader(this));
registryMap.forEach(registry::registerLoader);
shortcuts.forEach(registry::registerLoader); // overwrite with delegated shortcuts if present
}
@@ -294,7 +292,6 @@ public class ConfigPackImpl implements ConfigPack {
return seededBiomeProvider;
}
@SuppressWarnings("unchecked")
@Override
public <T> CheckedRegistry<T> getOrCreateRegistry(TypeKey<T> typeKey) {
return (CheckedRegistry<T>) registryMap.computeIfAbsent(typeKey.getType(), c -> {
@@ -333,8 +330,8 @@ public class ConfigPackImpl implements ConfigPack {
}
@Override
public Loader getLoader() {
return loader;
public Path getRootPath() {
return rootPath;
}
@Override
@@ -347,7 +344,7 @@ public class ConfigPackImpl implements ConfigPack {
return template.getVersion();
}
@SuppressWarnings("unchecked,rawtypes")
@SuppressWarnings("rawtypes")
@Override
public <T> ConfigPack registerShortcut(TypeKey<T> clazz, String shortcut, ShortcutLoader<T> loader) {
ShortcutHolder<?> holder = shortcuts
@@ -391,12 +388,10 @@ public class ConfigPackImpl implements ConfigPack {
}
@Override
@SuppressWarnings("unchecked")
public <T> CheckedRegistry<T> getRegistry(Type type) {
return (CheckedRegistry<T>) registryMap.get(type);
}
@SuppressWarnings("unchecked")
@Override
public <T> CheckedRegistry<T> getCheckedRegistry(Type type) throws IllegalStateException {
return (CheckedRegistry<T>) registryMap.get(type);
@@ -17,14 +17,13 @@
package com.dfsek.terra.registry.master;
import com.dfsek.tectonic.api.exception.ConfigException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.util.Objects;
import java.util.zip.ZipFile;
import java.io.Serial;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
import com.dfsek.terra.api.Platform;
import com.dfsek.terra.api.config.ConfigPack;
@@ -37,44 +36,42 @@ import com.dfsek.terra.registry.OpenRegistryImpl;
* Class to hold config packs
*/
public class ConfigRegistry extends OpenRegistryImpl<ConfigPack> {
private static final Logger logger = LoggerFactory.getLogger(ConfigRegistry.class);
public ConfigRegistry() {
super(TypeKey.of(ConfigPack.class));
}
public void load(File folder, Platform platform) throws ConfigException {
ConfigPack pack = new ConfigPackImpl(folder, platform);
registerChecked(pack.getRegistryKey(), pack);
public void loadAll(Platform platform) throws IOException, PackLoadFailuresException {
Path packsDirectory = platform.getDataFolder().toPath().resolve("packs");
Files.createDirectories(packsDirectory);
List<IOException> failedLoads = new ArrayList<>();
try(Stream<Path> packs = Files.list(packsDirectory)) {
packs.forEach(path -> {
try {
ConfigPack pack = new ConfigPackImpl(path, platform);
registerChecked(pack.getRegistryKey(), pack);
} catch(IOException e) {
failedLoads.add(e);
}
});
}
if(!failedLoads.isEmpty()) {
throw new PackLoadFailuresException(failedLoads);
}
}
public boolean loadAll(Platform platform) {
boolean valid = true;
File packsFolder = new File(platform.getDataFolder(), "packs");
packsFolder.mkdirs();
for(File dir : Objects.requireNonNull(packsFolder.listFiles(File::isDirectory))) {
try {
load(dir, platform);
} catch(ConfigException e) {
logger.error("Error loading config pack {}", dir.getName(), e);
valid = false;
}
}
for(File zip : Objects.requireNonNull(
packsFolder.listFiles(file -> file.getName().endsWith(".zip") || file.getName().endsWith(".terra")))) {
try {
logger.info("Loading ZIP archive: {}", zip.getName());
load(new ZipFile(zip), platform);
} catch(IOException | ConfigException e) {
logger.error("Error loading config pack {}", zip.getName(), e);
valid = false;
}
}
return valid;
}
public static class PackLoadFailuresException extends Exception {
@Serial
private static final long serialVersionUID = 538998844645186306L;
public void load(ZipFile file, Platform platform) throws ConfigException {
ConfigPackImpl pack = new ConfigPackImpl(file, platform);
registerChecked(pack.getRegistryKey(), pack);
private final List<Throwable> exceptions;
public PackLoadFailuresException(List<? extends Throwable> exceptions) {
this.exceptions = (List<Throwable>) exceptions;
}
public List<Throwable> getExceptions() {
return exceptions;
}
}
}
@@ -20,6 +20,9 @@ package com.dfsek.terra.bukkit;
import com.dfsek.tectonic.api.TypeRegistry;
import com.dfsek.tectonic.api.depth.DepthTracker;
import com.dfsek.tectonic.api.exception.LoadException;
import com.dfsek.terra.registry.master.ConfigRegistry.PackLoadFailuresException;
import org.bukkit.Bukkit;
import org.bukkit.entity.EntityType;
import org.jetbrains.annotations.NotNull;
@@ -27,6 +30,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Locale;
@@ -63,8 +67,7 @@ public class PlatformImpl extends AbstractPlatform {
@Override
public boolean reload() {
getTerraConfig().load(this);
getRawConfigRegistry().clear();
boolean succeed = getRawConfigRegistry().loadAll(this);
boolean succeed = loadConfigPacks();
Bukkit.getWorlds().forEach(world -> {
if(world.getGenerator() instanceof BukkitChunkGeneratorWrapper wrapper) {
+2
View File
@@ -29,6 +29,8 @@ dependencies {
modImplementation("cloud.commandframework", "cloud-fabric", Versions.Libraries.cloud)
include("cloud.commandframework", "cloud-fabric", Versions.Libraries.cloud)
modRuntimeOnly("net.fabricmc.fabric-api", "fabric-api", Versions.Fabric.fabricAPI)
}
loom {
@@ -20,6 +20,9 @@ package com.dfsek.terra.forge;
import ca.solostudios.strata.Versions;
import ca.solostudios.strata.parser.tokenizer.ParseException;
import ca.solostudios.strata.version.Version;
import com.dfsek.terra.registry.master.ConfigRegistry.PackLoadFailuresException;
import net.minecraft.MinecraftVersion;
import net.minecraft.registry.Registry;
import net.minecraft.server.MinecraftServer;
@@ -34,6 +37,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@@ -62,8 +66,7 @@ public class ForgePlatform extends ModPlatform {
@Override
public boolean reload() {
getTerraConfig().load(this);
getRawConfigRegistry().clear();
boolean succeed = getRawConfigRegistry().loadAll(this);
boolean succeed = loadConfigPacks();
MinecraftServer server = getServer();
+2 -2
View File
@@ -13,7 +13,7 @@ val dump = tasks.create("dumpDependents") {
doFirst {
taskSet.forEach {
val resource = File(resourcesDir, it.archiveFileName.get())
println("Including archive " + it.archiveFileName.orNull + " in directory " + resource.absolutePath)
logger.info("Including archive " + it.archiveFileName.orNull + " in directory " + resource.absolutePath)
it.archiveFile.get().asFile.copyTo(resource, true)
}
}
@@ -32,6 +32,6 @@ afterEvaluate {
}
tasks["dumpDependents"].dependsOn(task)
taskSet.add(task)
println("Merged JAR will incorporate task ${task.name} from platform ${it.name}.")
logger.info("Merged JAR will incorporate task ${task.name} from platform ${it.name}.")
}
}
@@ -3,6 +3,9 @@ package com.dfsek.terra.lifecycle;
import ca.solostudios.strata.Versions;
import ca.solostudios.strata.parser.tokenizer.ParseException;
import ca.solostudios.strata.version.Version;
import com.dfsek.terra.registry.master.ConfigRegistry.PackLoadFailuresException;
import net.minecraft.MinecraftVersion;
import net.minecraft.registry.Registry;
import net.minecraft.registry.RegistryKeys;
@@ -14,6 +17,7 @@ import net.minecraft.world.gen.chunk.ChunkGeneratorSettings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@@ -63,9 +67,7 @@ public abstract class LifecyclePlatform extends ModPlatform {
@Override
public boolean reload() {
getTerraConfig().load(this);
getRawConfigRegistry().clear();
boolean succeed = getRawConfigRegistry().loadAll(this);
boolean succeed = loadConfigPacks();
if(server != null) {
BiomeUtil.registerBiomes(server.getRegistryManager().get(RegistryKeys.BIOME));
+1 -1
View File
@@ -5,7 +5,7 @@ fun includeImmediateChildren(dir: File, type: String) {
dir.walkTopDown().maxDepth(1).forEach {
if (!it.isDirectory || !File(it, "build.gradle.kts").exists()) return@forEach
val addonDir = it.relativeTo(file(".")).path.replace("/", ":").replace("\\", ":")
println("Including $type directory \"$addonDir\" as subproject.")
logger.info("Including $type directory \"$addonDir\" as subproject.")
include(addonDir)
}
}